Add extern/i18n.lua

lua_on_mac
Petr Mikheev 2 years ago
parent 23e279c23e
commit f91a5499d3

@ -0,0 +1,17 @@
if (NOT DEFINED OPENMW_RESOURCES_ROOT)
message( FATAL_ERROR "OPENMW_RESOURCES_ROOT is not set" )
endif()
# Copy resource files into the build directory
set(SDIR ${CMAKE_CURRENT_SOURCE_DIR})
set(DDIRRELATIVE resources/lua_libs/i18n)
set(I18N_LUA_FILES
i18n/init.lua
i18n/interpolate.lua
i18n/plural.lua
i18n/variants.lua
i18n/version.lua
)
copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_RESOURCES_ROOT} ${DDIRRELATIVE} "${I18N_LUA_FILES}")

@ -0,0 +1,22 @@
MIT License Terms
=================
Copyright (c) 2012 Enrique García Cota.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,164 @@
i18n.lua
========
[![Build Status](https://travis-ci.org/kikito/i18n.lua.png?branch=master)](https://travis-ci.org/kikito/i18n.lua)
A very complete i18n lib for Lua
Description
===========
``` lua
i18n = require 'i18n'
-- loading stuff
i18n.set('en.welcome', 'welcome to this program')
i18n.load({
en = {
good_bye = "good-bye!",
age_msg = "your age is %{age}.",
phone_msg = {
one = "you have one new message.",
other = "you have %{count} new messages."
}
}
})
i18n.loadFile('path/to/your/project/i18n/de.lua') -- load German language file
i18n.loadFile('path/to/your/project/i18n/fr.lua') -- load French language file
… -- section 'using language files' below describes structure of files
-- setting the translation context
i18n.setLocale('en') -- English is the default locale anyway
-- getting translations
i18n.translate('welcome') -- Welcome to this program
i18n('welcome') -- Welcome to this program
i18n('age_msg', {age = 18}) -- Your age is 18.
i18n('phone_msg', {count = 1}) -- You have one new message.
i18n('phone_msg', {count = 2}) -- You have 2 new messages.
i18n('good_bye') -- Good-bye!
```
Interpolation
=============
You can interpolate variables in 3 different ways:
``` lua
-- the most usual one
i18n.set('variables', 'Interpolating variables: %{name} %{age}')
i18n('variables', {name='john', 'age'=10}) -- Interpolating variables: john 10
i18n.set('lua', 'Traditional Lua way: %d %s')
i18n('lua', {1, 'message'}) -- Traditional Lua way: 1 message
i18n.set('combined', 'Combined: %<name>.q %<age>.d %<age>.o')
i18n('combined', {name='john', 'age'=10}) -- Combined: john 10 12k
```
Pluralization
=============
This lib implements the [unicode.org plural rules](http://cldr.unicode.org/index/cldr-spec/plural-rules). Just set the locale you want to use and it will deduce the appropiate pluralization rules:
``` lua
i18n = require 'i18n'
i18n.load({
en = {
msg = {
one = "one message",
other = "%{count} messages"
}
},
ru = {
msg = {
one = "1 сообщение",
few = "%{count} сообщения",
many = "%{count} сообщений",
other = "%{count} сообщения"
}
}
})
i18n('msg', {count = 1}) -- one message
i18n.setLocale('ru')
i18n('msg', {count = 5}) -- 5 сообщений
```
The appropiate rule is chosen by finding the 'root' of the locale used: for example if the current locale is 'fr-CA', the 'fr' rules will be applied.
If the provided functions are not enough (i.e. invented languages) it's possible to specify a custom pluralization function in the second parameter of setLocale. This function must return 'one', 'few', 'other', etc given a number.
Fallbacks
=========
When a value is not found, the lib has several fallback mechanisms:
* First, it will look in the current locale's parents. For example, if the locale was set to 'en-US' and the key 'msg' was not found there, it will be looked over in 'en'.
* Second, if the value is not found in the locale ancestry, a 'fallback locale' (by default: 'en') can be used. If the fallback locale has any parents, they will be looked over too.
* Third, if all the locales have failed, but there is a param called 'default' on the provided data, it will be used.
* Otherwise the translation will return nil.
The parents of a locale are found by splitting the locale by its hyphens. Other separation characters (spaces, underscores, etc) are not supported.
Using language files
====================
It might be a good idea to store each translation in a different file. This is supported via the 'i18n.loadFile' directive:
``` lua
i18n.loadFile('path/to/your/project/i18n/de.lua') -- German translation
i18n.loadFile('path/to/your/project/i18n/en.lua') -- English translation
i18n.loadFile('path/to/your/project/i18n/fr.lua') -- French translation
```
The German language file 'de.lua' should read:
``` lua
return {
de = {
good_bye = "Auf Wiedersehen!",
age_msg = "Ihr Alter beträgt %{age}.",
phone_msg = {
one = "Sie haben eine neue Nachricht.",
other = "Sie haben %{count} neue Nachrichten."
}
}
}
```
If desired, you can also store all translations in one single file (eg. 'translations.lua'):
``` lua
return {
de = {
good_bye = "Auf Wiedersehen!",
age_msg = "Ihr Alter beträgt %{age}.",
phone_msg = {
one = "Sie haben eine neue Nachricht.",
other = "Sie haben %{count} neue Nachrichten."
}
},
fr = {
good_bye = "Au revoir !",
age_msg = "Vous avez %{age} ans.",
phone_msg = {
one = "Vous avez une noveau message.",
other = "Vous avez %{count} noveaux messages."
}
},
}
```
Specs
=====
This project uses [busted](https://github.com/Olivine-Labs/busted) for its specs. If you want to run the specs, you will have to install it first. Then just execute the following from the root inspect folder:
busted

@ -0,0 +1,188 @@
local i18n = {}
local store
local locale
local pluralizeFunction
local defaultLocale = 'en'
local fallbackLocale = defaultLocale
local currentFilePath = (...):gsub("%.init$","")
local plural = require(currentFilePath .. '.plural')
local interpolate = require(currentFilePath .. '.interpolate')
local variants = require(currentFilePath .. '.variants')
local version = require(currentFilePath .. '.version')
i18n.plural, i18n.interpolate, i18n.variants, i18n.version, i18n._VERSION =
plural, interpolate, variants, version, version
-- private stuff
local function dotSplit(str)
local fields, length = {},0
str:gsub("[^%.]+", function(c)
length = length + 1
fields[length] = c
end)
return fields, length
end
local function isPluralTable(t)
return type(t) == 'table' and type(t.other) == 'string'
end
local function isPresent(str)
return type(str) == 'string' and #str > 0
end
local function assertPresent(functionName, paramName, value)
if isPresent(value) then return end
local msg = "i18n.%s requires a non-empty string on its %s. Got %s (a %s value)."
error(msg:format(functionName, paramName, tostring(value), type(value)))
end
local function assertPresentOrPlural(functionName, paramName, value)
if isPresent(value) or isPluralTable(value) then return end
local msg = "i18n.%s requires a non-empty string or plural-form table on its %s. Got %s (a %s value)."
error(msg:format(functionName, paramName, tostring(value), type(value)))
end
local function assertPresentOrTable(functionName, paramName, value)
if isPresent(value) or type(value) == 'table' then return end
local msg = "i18n.%s requires a non-empty string or table on its %s. Got %s (a %s value)."
error(msg:format(functionName, paramName, tostring(value), type(value)))
end
local function assertFunctionOrNil(functionName, paramName, value)
if value == nil or type(value) == 'function' then return end
local msg = "i18n.%s requires a function (or nil) on param %s. Got %s (a %s value)."
error(msg:format(functionName, paramName, tostring(value), type(value)))
end
local function defaultPluralizeFunction(count)
return plural.get(variants.root(i18n.getLocale()), count)
end
local function pluralize(t, data)
assertPresentOrPlural('interpolatePluralTable', 't', t)
data = data or {}
local count = data.count or 1
local plural_form = pluralizeFunction(count)
return t[plural_form]
end
local function treatNode(node, data)
if type(node) == 'string' then
return interpolate(node, data)
elseif isPluralTable(node) then
return interpolate(pluralize(node, data), data)
end
return node
end
local function recursiveLoad(currentContext, data)
local composedKey
for k,v in pairs(data) do
composedKey = (currentContext and (currentContext .. '.') or "") .. tostring(k)
assertPresent('load', composedKey, k)
assertPresentOrTable('load', composedKey, v)
if type(v) == 'string' then
i18n.set(composedKey, v)
else
recursiveLoad(composedKey, v)
end
end
end
local function localizedTranslate(key, loc, data)
local path, length = dotSplit(loc .. "." .. key)
local node = store
for i=1, length do
node = node[path[i]]
if not node then return nil end
end
return treatNode(node, data)
end
-- public interface
function i18n.set(key, value)
assertPresent('set', 'key', key)
assertPresentOrPlural('set', 'value', value)
local path, length = dotSplit(key)
local node = store
for i=1, length-1 do
key = path[i]
node[key] = node[key] or {}
node = node[key]
end
local lastKey = path[length]
node[lastKey] = value
end
function i18n.translate(key, data)
assertPresent('translate', 'key', key)
data = data or {}
local usedLocale = data.locale or locale
local fallbacks = variants.fallbacks(usedLocale, fallbackLocale)
for i=1, #fallbacks do
local value = localizedTranslate(key, fallbacks[i], data)
if value then return value end
end
return data.default
end
function i18n.setLocale(newLocale, newPluralizeFunction)
assertPresent('setLocale', 'newLocale', newLocale)
assertFunctionOrNil('setLocale', 'newPluralizeFunction', newPluralizeFunction)
locale = newLocale
pluralizeFunction = newPluralizeFunction or defaultPluralizeFunction
end
function i18n.setFallbackLocale(newFallbackLocale)
assertPresent('setFallbackLocale', 'newFallbackLocale', newFallbackLocale)
fallbackLocale = newFallbackLocale
end
function i18n.getFallbackLocale()
return fallbackLocale
end
function i18n.getLocale()
return locale
end
function i18n.reset()
store = {}
plural.reset()
i18n.setLocale(defaultLocale)
i18n.setFallbackLocale(defaultLocale)
end
function i18n.load(data)
recursiveLoad(nil, data)
end
function i18n.loadFile(path)
local chunk = assert(loadfile(path))
local data = chunk()
i18n.load(data)
end
setmetatable(i18n, {__call = function(_, ...) return i18n.translate(...) end})
i18n.reset()
return i18n

@ -0,0 +1,60 @@
local unpack = unpack or table.unpack -- lua 5.2 compat
local FORMAT_CHARS = { c=1, d=1, E=1, e=1, f=1, g=1, G=1, i=1, o=1, u=1, X=1, x=1, s=1, q=1, ['%']=1 }
-- matches a string of type %{age}
local function interpolateValue(string, variables)
return string:gsub("(.?)%%{%s*(.-)%s*}",
function (previous, key)
if previous == "%" then
return
else
return previous .. tostring(variables[key])
end
end)
end
-- matches a string of type %<age>.d
local function interpolateField(string, variables)
return string:gsub("(.?)%%<%s*(.-)%s*>%.([cdEefgGiouXxsq])",
function (previous, key, format)
if previous == "%" then
return
else
return previous .. string.format("%" .. format, variables[key] or "nil")
end
end)
end
local function escapePercentages(string)
return string:gsub("(%%)(.?)", function(_, char)
if FORMAT_CHARS[char] then
return "%" .. char
else
return "%%" .. char
end
end)
end
local function unescapePercentages(string)
return string:gsub("(%%%%)(.?)", function(_, char)
if FORMAT_CHARS[char] then
return "%" .. char
else
return "%%" .. char
end
end)
end
local function interpolate(pattern, variables)
variables = variables or {}
local result = pattern
result = interpolateValue(result, variables)
result = interpolateField(result, variables)
result = escapePercentages(result)
result = string.format(result, unpack(variables))
result = unescapePercentages(result)
return result
end
return interpolate

@ -0,0 +1,280 @@
local plural = {}
local defaultFunction = nil
-- helper functions
local function assertPresentString(functionName, paramName, value)
if type(value) ~= 'string' or #value == 0 then
local msg = "Expected param %s of function %s to be a string, but got %s (a value of type %s) instead"
error(msg:format(paramName, functionName, tostring(value), type(value)))
end
end
local function assertNumber(functionName, paramName, value)
if type(value) ~= 'number' then
local msg = "Expected param %s of function %s to be a number, but got %s (a value of type %s) instead"
error(msg:format(paramName, functionName, tostring(value), type(value)))
end
end
-- transforms "foo bar baz" into {'foo','bar','baz'}
local function words(str)
local result, length = {}, 0
str:gsub("%S+", function(word)
length = length + 1
result[length] = word
end)
return result
end
local function isInteger(n)
return n == math.floor(n)
end
local function between(value, min, max)
return value >= min and value <= max
end
local function inside(v, list)
for i=1, #list do
if v == list[i] then return true end
end
return false
end
-- pluralization functions
local pluralization = {}
local f1 = function(n)
return n == 1 and "one" or "other"
end
pluralization[f1] = words([[
af asa bem bez bg bn brx ca cgg chr da de dv ee el
en eo es et eu fi fo fur fy gl gsw gu ha haw he is
it jmc kaj kcg kk kl ksb ku lb lg mas ml mn mr nah
nb nd ne nl nn no nr ny nyn om or pa pap ps pt rm
rof rwk saq seh sn so sq ss ssy st sv sw syr ta te
teo tig tk tn ts ur ve vun wae xh xog zu
]])
local f2 = function(n)
return (n == 0 or n == 1) and "one" or "other"
end
pluralization[f2] = words("ak am bh fil guw hi ln mg nso ti tl wa")
local f3 = function(n)
if not isInteger(n) then return 'other' end
return (n == 0 and "zero") or
(n == 1 and "one") or
(n == 2 and "two") or
(between(n % 100, 3, 10) and "few") or
(between(n % 100, 11, 99) and "many") or
"other"
end
pluralization[f3] = {'ar'}
local f4 = function()
return "other"
end
pluralization[f4] = words([[
az bm bo dz fa hu id ig ii ja jv ka kde kea km kn
ko lo ms my root sah ses sg th to tr vi wo yo zh
]])
local f5 = function(n)
if not isInteger(n) then return 'other' end
local n_10, n_100 = n % 10, n % 100
return (n_10 == 1 and n_100 ~= 11 and 'one') or
(between(n_10, 2, 4) and not between(n_100, 12, 14) and 'few') or
((n_10 == 0 or between(n_10, 5, 9) or between(n_100, 11, 14)) and 'many') or
'other'
end
pluralization[f5] = words('be bs hr ru sh sr uk')
local f6 = function(n)
if not isInteger(n) then return 'other' end
local n_10, n_100 = n % 10, n % 100
return (n_10 == 1 and not inside(n_100, {11,71,91}) and 'one') or
(n_10 == 2 and not inside(n_100, {12,72,92}) and 'two') or
(inside(n_10, {3,4,9}) and
not between(n_100, 10, 19) and
not between(n_100, 70, 79) and
not between(n_100, 90, 99)
and 'few') or
(n ~= 0 and n % 1000000 == 0 and 'many') or
'other'
end
pluralization[f6] = {'br'}
local f7 = function(n)
return (n == 1 and 'one') or
((n == 2 or n == 3 or n == 4) and 'few') or
'other'
end
pluralization[f7] = {'cz', 'sk'}
local f8 = function(n)
return (n == 0 and 'zero') or
(n == 1 and 'one') or
(n == 2 and 'two') or
(n == 3 and 'few') or
(n == 6 and 'many') or
'other'
end
pluralization[f8] = {'cy'}
local f9 = function(n)
return (n >= 0 and n < 2 and 'one') or
'other'
end
pluralization[f9] = {'ff', 'fr', 'kab'}
local f10 = function(n)
return (n == 1 and 'one') or
(n == 2 and 'two') or
((n == 3 or n == 4 or n == 5 or n == 6) and 'few') or
((n == 7 or n == 8 or n == 9 or n == 10) and 'many') or
'other'
end
pluralization[f10] = {'ga'}
local f11 = function(n)
return ((n == 1 or n == 11) and 'one') or
((n == 2 or n == 12) and 'two') or
(isInteger(n) and (between(n, 3, 10) or between(n, 13, 19)) and 'few') or
'other'
end
pluralization[f11] = {'gd'}
local f12 = function(n)
local n_10 = n % 10
return ((n_10 == 1 or n_10 == 2 or n % 20 == 0) and 'one') or
'other'
end
pluralization[f12] = {'gv'}
local f13 = function(n)
return (n == 1 and 'one') or
(n == 2 and 'two') or
'other'
end
pluralization[f13] = words('iu kw naq se sma smi smj smn sms')
local f14 = function(n)
return (n == 0 and 'zero') or
(n == 1 and 'one') or
'other'
end
pluralization[f14] = {'ksh'}
local f15 = function(n)
return (n == 0 and 'zero') or
(n > 0 and n < 2 and 'one') or
'other'
end
pluralization[f15] = {'lag'}
local f16 = function(n)
if not isInteger(n) then return 'other' end
if between(n % 100, 11, 19) then return 'other' end
local n_10 = n % 10
return (n_10 == 1 and 'one') or
(between(n_10, 2, 9) and 'few') or
'other'
end
pluralization[f16] = {'lt'}
local f17 = function(n)
return (n == 0 and 'zero') or
((n % 10 == 1 and n % 100 ~= 11) and 'one') or
'other'
end
pluralization[f17] = {'lv'}
local f18 = function(n)
return((n % 10 == 1 and n ~= 11) and 'one') or
'other'
end
pluralization[f18] = {'mk'}
local f19 = function(n)
return (n == 1 and 'one') or
((n == 0 or
(n ~= 1 and isInteger(n) and between(n % 100, 1, 19)))
and 'few') or
'other'
end
pluralization[f19] = {'mo', 'ro'}
local f20 = function(n)
if n == 1 then return 'one' end
if not isInteger(n) then return 'other' end
local n_100 = n % 100
return ((n == 0 or between(n_100, 2, 10)) and 'few') or
(between(n_100, 11, 19) and 'many') or
'other'
end
pluralization[f20] = {'mt'}
local f21 = function(n)
if n == 1 then return 'one' end
if not isInteger(n) then return 'other' end
local n_10, n_100 = n % 10, n % 100
return ((between(n_10, 2, 4) and not between(n_100, 12, 14)) and 'few') or
((n_10 == 0 or n_10 == 1 or between(n_10, 5, 9) or between(n_100, 12, 14)) and 'many') or
'other'
end
pluralization[f21] = {'pl'}
local f22 = function(n)
return (n == 0 or n == 1) and 'one' or
'other'
end
pluralization[f22] = {'shi'}
local f23 = function(n)
local n_100 = n % 100
return (n_100 == 1 and 'one') or
(n_100 == 2 and 'two') or
((n_100 == 3 or n_100 == 4) and 'few') or
'other'
end
pluralization[f23] = {'sl'}
local f24 = function(n)
return (isInteger(n) and (n == 0 or n == 1 or between(n, 11, 99)) and 'one')
or 'other'
end
pluralization[f24] = {'tzm'}
local pluralizationFunctions = {}
for f,locales in pairs(pluralization) do
for _,locale in ipairs(locales) do
pluralizationFunctions[locale] = f
end
end
-- public interface
function plural.get(locale, n)
assertPresentString('i18n.plural.get', 'locale', locale)
assertNumber('i18n.plural.get', 'n', n)
local f = pluralizationFunctions[locale] or defaultFunction
return f(math.abs(n))
end
function plural.setDefaultFunction(f)
defaultFunction = f
end
function plural.reset()
defaultFunction = pluralizationFunctions['en']
end
plural.reset()
return plural

@ -0,0 +1,49 @@
local variants = {}
local function reverse(arr, length)
local result = {}
for i=1, length do result[i] = arr[length-i+1] end
return result, length
end
local function concat(arr1, len1, arr2, len2)
for i = 1, len2 do
arr1[len1 + i] = arr2[i]
end
return arr1, len1 + len2
end
function variants.ancestry(locale)
local result, length, accum = {},0,nil
locale:gsub("[^%-]+", function(c)
length = length + 1
accum = accum and (accum .. '-' .. c) or c
result[length] = accum
end)
return reverse(result, length)
end
function variants.isParent(parent, child)
return not not child:match("^".. parent .. "%-")
end
function variants.root(locale)
return locale:match("[^%-]+")
end
function variants.fallbacks(locale, fallbackLocale)
if locale == fallbackLocale or
variants.isParent(fallbackLocale, locale) then
return variants.ancestry(locale)
end
if variants.isParent(locale, fallbackLocale) then
return variants.ancestry(fallbackLocale)
end
local ancestry1, length1 = variants.ancestry(locale)
local ancestry2, length2 = variants.ancestry(fallbackLocale)
return concat(ancestry1, length1, ancestry2, length2)
end
return variants

@ -0,0 +1 @@
return '0.9.2'

@ -3,3 +3,4 @@ add_subdirectory(shaders)
add_subdirectory(vfs)
add_subdirectory(builtin_scripts)
add_subdirectory(lua_api)
add_subdirectory(../extern/i18n.lua ${CMAKE_CURRENT_BINARY_DIR}/files)

Loading…
Cancel
Save