From f91a5499d353f0d8ffc018268380afcf74c626af Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 26 Dec 2021 19:12:04 +0100 Subject: [PATCH] Add extern/i18n.lua --- extern/i18n.lua/CMakeLists.txt | 17 ++ extern/i18n.lua/LICENSE | 22 +++ extern/i18n.lua/README.md | 164 ++++++++++++++++ extern/i18n.lua/i18n/init.lua | 188 ++++++++++++++++++ extern/i18n.lua/i18n/interpolate.lua | 60 ++++++ extern/i18n.lua/i18n/plural.lua | 280 +++++++++++++++++++++++++++ extern/i18n.lua/i18n/variants.lua | 49 +++++ extern/i18n.lua/i18n/version.lua | 1 + files/CMakeLists.txt | 1 + 9 files changed, 782 insertions(+) create mode 100644 extern/i18n.lua/CMakeLists.txt create mode 100644 extern/i18n.lua/LICENSE create mode 100644 extern/i18n.lua/README.md create mode 100644 extern/i18n.lua/i18n/init.lua create mode 100644 extern/i18n.lua/i18n/interpolate.lua create mode 100644 extern/i18n.lua/i18n/plural.lua create mode 100644 extern/i18n.lua/i18n/variants.lua create mode 100644 extern/i18n.lua/i18n/version.lua diff --git a/extern/i18n.lua/CMakeLists.txt b/extern/i18n.lua/CMakeLists.txt new file mode 100644 index 0000000000..aec4447470 --- /dev/null +++ b/extern/i18n.lua/CMakeLists.txt @@ -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}") diff --git a/extern/i18n.lua/LICENSE b/extern/i18n.lua/LICENSE new file mode 100644 index 0000000000..ddf484685b --- /dev/null +++ b/extern/i18n.lua/LICENSE @@ -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. diff --git a/extern/i18n.lua/README.md b/extern/i18n.lua/README.md new file mode 100644 index 0000000000..8b3271c321 --- /dev/null +++ b/extern/i18n.lua/README.md @@ -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: %.q %.d %.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 diff --git a/extern/i18n.lua/i18n/init.lua b/extern/i18n.lua/i18n/init.lua new file mode 100644 index 0000000000..6bcccd0572 --- /dev/null +++ b/extern/i18n.lua/i18n/init.lua @@ -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 diff --git a/extern/i18n.lua/i18n/interpolate.lua b/extern/i18n.lua/i18n/interpolate.lua new file mode 100644 index 0000000000..c6bb242f05 --- /dev/null +++ b/extern/i18n.lua/i18n/interpolate.lua @@ -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 %.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 diff --git a/extern/i18n.lua/i18n/plural.lua b/extern/i18n.lua/i18n/plural.lua new file mode 100644 index 0000000000..bb99804ee8 --- /dev/null +++ b/extern/i18n.lua/i18n/plural.lua @@ -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 diff --git a/extern/i18n.lua/i18n/variants.lua b/extern/i18n.lua/i18n/variants.lua new file mode 100644 index 0000000000..0cfad42f6c --- /dev/null +++ b/extern/i18n.lua/i18n/variants.lua @@ -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 diff --git a/extern/i18n.lua/i18n/version.lua b/extern/i18n.lua/i18n/version.lua new file mode 100644 index 0000000000..eb788884ac --- /dev/null +++ b/extern/i18n.lua/i18n/version.lua @@ -0,0 +1 @@ +return '0.9.2' diff --git a/files/CMakeLists.txt b/files/CMakeLists.txt index cea33f0f40..607ddeca49 100644 --- a/files/CMakeLists.txt +++ b/files/CMakeLists.txt @@ -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)