I have a simple command that I want to localize : "Select 5 objects". I had thought that I might template this as : "Select %d %s", where the number and objects might be formatted later. But this raised the question of how do you appropriately pluralize a string?
When the object can have multiple plurals :
What is a good way to structure your string templates to accommodate for optional plurality? This is especially important for localization later, so I'm trying to be smart about it now.
Reading online, a lot of the top hits for localization best practices rely on Apple's NSLocalizedString library, which allow for string dictionaries with custom rules around the cardinality of the word. But is there a good way to handle this when all of my strings are loaded into a CSV? Roblox provides a TranslationService that comes with a Translator class that can format strings by keys. So I've structured my resources like this :
localization.csv
Key, Example, en, fr, es
Command.SelectTemplate, "Select 3 apples", "Select {total : number} {thing : string}", "Sélectionnez {total : number} {thing : string}" "Selecciona {total : number} {thing : string}"
Object.AppleZero, "there are zero apples", "apples", "pommes", "manzanas"
Object.AppleOne, "there is one apple", "apple", "pomme", "manzana"
Object.AppleFew, "there are a few apples", "apples", "pommes", "manzanas"
example Script
local LocalizationService = game:GetService("LocalizationService")
local LOCALE = "en"
local res, translator = pcall(function()
return LocalizationService:GetTranslatorForLocaleAsync(LOCALE)
end)
if not res then
warn(string.format("Failed to load the translator with error %s", tostring(translator)))
return
end
local function getSelectionString(numToSelect : number, objectKey : string)
-- TODO : FIND A BETTER WAY TO DO THIS
assert(numToSelect >= 0, "Cannot select a negative number")
local lookupKey
if numToSelect == 0 then
lookupKey = objectKey .. "Zero"
elseif numToSelect == 1 then
lookupKey = objectKey .. "One"
else
lookupKey = objectKey .. "Few"
end
local objectString = translator:FormatByKey(lookupKey)
local formattingArgs = {
total = numToSelect,
thing = objectString,
}
local commandString = translator:FormatByKey("Command.SelectTemplate", formattingArgs)
return commandString
end
-- Test it out
local objectKey = "Object.Apple"
print(getSelectionString(0, objectKey)) -- "Select 0 apples"
print(getSelectionString(1, objectKey)) -- "Select 1 apple"
print(getSelectionString(2, objectKey)) -- "Select 2 apples"
While this technically works, it requires defining multiple keys for every single object that may have plurality in other languages, and it assumes a very English way pluralizing objects. So to reiterate my question from earlier, what is a good way to structure string templates to accommodate for optional plurality?
One of the best ways of handling plurals is ICU message syntax. ICU means International Components for Unicode – a widely used set of C/C++, Java, and other libraries providing Unicode and globalization support for software and applications.
ICU format allows you to create user-friendly texts that combine the use of different plural, gender, date and time forms in one string, which will vary depending on who is the user. For example, “Sam sent you 2 messages” and “Emma sent you 1 message”.
For example:
You have {count, plural,
=0 {no apples}
one {one apple}
other {# apples}
}
You can store these strings in different file formats, even in CSV. Different languages have different pluralization rules and different numbers of plural forms. So translators should translate ICU strings according to the target language rules.
Also, there are some localization file formats that have native plurals support, for example, Android XML, iOS Strings.
Modern Localization Management platforms support the ICU syntax. For example, Crowdin. Here you can read more about ICU Message Syntax support in Crowdin. In addition - The Ultimate Guide to ICU Message Format.