SyntaxGameServer/RCCService2020/ExtraContent/LuaPackages/Localization/LocalizationContext.lua

142 lines
4.0 KiB
Lua

--[[
Contains all of the loaded translations and provides methods to translate
keys and parameters to strings.
LocalizationContext doesn't handle loading of specific languages, but does
recommend what languages should be loaded (if available).
To create a new LocalizationContext:
local currentLanguage = LocalizationService.RobloxLocaleId
local languages = LocalizationContext.getRelevantLanguages(currentLanguage)
local translations = {}
-- Use the list of languages to load a set of translation tables here.
-- A translation table is just a map from key to the translated string.
-- `translations` is a map from language to translation tables.
local context = LocalizationContext.new(translations)
-- Get a string that doesn't require parameters
context:getString(currentLanguage, "SOME_KEY")
-- Passing parameters:
context:getString(currentLanguage, "FANCY_KEY", {
apples = 5,
})
Additional languages can be added after the LocalizationContext is created
by calling `addTranslations`. Whenever the user's language changes, call
`getRelevantLanguages` to get a new list of languages to load, load them,
then call `addTranslations` to merge them in with the existing tables.
]]
--[[
Finds the base language code for the given language, if there is one.
We assume:
* Language codes are of the form LANGUAGE or LANGUAGE_COUNTRY
* LANGUAGE_COUNTRY is more specific than LANGUAGE
]]
local function getBaseLanguage(languageName)
return languageName:match("^(%w+)[-_]")
end
local LocalizationContext = {}
LocalizationContext.__index = LocalizationContext
function LocalizationContext.new(translations)
local self = {
_translations = translations,
}
setmetatable(self, LocalizationContext)
return self
end
--[[
Add translations to an existing LocalizationContext, such as when a user
switches languages while the app is running.
]]
function LocalizationContext:addTranslations(translations)
self._translations = translations
end
--[[
Yields a list of languages relevant to the current user.
When the user's language changes, query this value, load those translations,
and add them to the LocalizationContext using addTranslations.
]]
function LocalizationContext.getRelevantLanguages(primaryLanguage)
local languages = {}
-- Load the language itself if available.
table.insert(languages, primaryLanguage)
-- If there's a fallback for our current language, load that as well.
local fallbackLanguage = getBaseLanguage(primaryLanguage)
if fallbackLanguage then
table.insert(languages, fallbackLanguage)
end
-- We should always load English, as it should contain every valid key.
table.insert(languages, "en-us")
return languages
end
function LocalizationContext:_getSourceString(language, key)
local translationTable = self._translations[language]
if not translationTable then
return nil
end
return translationTable[key]
end
--[[
Translate a key with a set of arguments into the given language.
`language` must be explicitly provided
]]
function LocalizationContext:getString(language, key, parameters)
local exactValue = self:_getSourceString(language, key)
local baseLanguage = getBaseLanguage(language)
local baseLanguageValue
if baseLanguage then
baseLanguageValue = self:_getSourceString(baseLanguage, key)
end
local englishValue = self:_getSourceString("en-us", key)
-- We try to find source strings in descending priority here:
local sourceString = exactValue or baseLanguageValue or englishValue
-- Missing translations are considered a developer error, so we throw here.
if not sourceString then
local message = (
"Couldn't find value for translation key %q!\n" ..
"Tried these languages: %s, %s, %s"
):format(
key,
language, baseLanguage, "en-us"
)
error(message, 2)
end
-- If we have parameters to insert into the string, put them in!
-- We don't check for missing parameters, should we in the future?
if parameters then
return (sourceString:gsub("{(.-)}", parameters))
else
return sourceString
end
end
return LocalizationContext