add gs
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"language": {
|
||||
"mode": "nonstrict"
|
||||
},
|
||||
"lint": {
|
||||
"LocalShadow": "fatal",
|
||||
"LocalUnused": "fatal",
|
||||
"ImportUnused": "fatal",
|
||||
"ImplicitReturn": "fatal",
|
||||
"DeprecatedGlobal": "fatal"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local LocalizationService = game:GetService("LocalizationService")
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local ExternalEventConnection = require(CorePackages.RoactUtilities.ExternalEventConnection)
|
||||
|
||||
local ArgCheck = require(CorePackages.ArgCheck)
|
||||
local LocalizationKey = require(CorePackages.Localization.LocalizationKey)
|
||||
|
||||
local LocalizationConsumer = Roact.Component:extend("LocalizationConsumer")
|
||||
|
||||
function LocalizationConsumer:init()
|
||||
local localization = self._context[LocalizationKey].localization
|
||||
|
||||
if localization == nil then
|
||||
error("LocalizationConsumer must be below a LocalizationProvider.")
|
||||
end
|
||||
|
||||
self.state = {
|
||||
locale = LocalizationService.RobloxLocaleId,
|
||||
}
|
||||
|
||||
self.updateLocalization = function(newLocale)
|
||||
if settings():GetFFlag("AppBridgeStartupController") then
|
||||
newLocale = localization:GetLocale()
|
||||
end
|
||||
if newLocale ~= self.state.locale then
|
||||
self:setState({
|
||||
locale = newLocale
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
if settings():GetFFlag("AppBridgeStartupController") then
|
||||
self.connections = {
|
||||
localization.changed:connect(self.updateLocalization)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
function LocalizationConsumer:willUnmount()
|
||||
if settings():GetFFlag("AppBridgeStartupController") then
|
||||
for _, connection in pairs(self.connections) do
|
||||
connection:disconnect()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function LocalizationConsumer:render()
|
||||
local localization = self._context[LocalizationKey].localization
|
||||
local render = self.props.render
|
||||
local stringsToBeLocalized = self.props.stringsToBeLocalized
|
||||
|
||||
ArgCheck.isType(render, "function", "LocalizationConsumer.props.render")
|
||||
ArgCheck.isType(stringsToBeLocalized, "table", "LocalizationConsumer.props.stringsToBeLocalized")
|
||||
|
||||
local localizedStrings = {}
|
||||
for stringName, stringInfo in pairs(stringsToBeLocalized) do
|
||||
if typeof(stringInfo) == "table" then
|
||||
if typeof(stringInfo[1]) == "string" then
|
||||
local success, result = pcall(function()
|
||||
return localization:Format(stringInfo[1], stringInfo)
|
||||
end)
|
||||
|
||||
ArgCheck.isEqual(success, true, string.format(
|
||||
"LocalizationConsumer finding value for translation key[%s]: %s", stringName, stringInfo[1]))
|
||||
|
||||
localizedStrings[stringName] = success and result or ""
|
||||
else
|
||||
error(string.format("%s[1] in stringsToBeLocalized must be a string, got %s instead",
|
||||
stringName, typeof(stringInfo[1])))
|
||||
end
|
||||
elseif typeof(stringInfo) == "string" then
|
||||
local success, result = pcall(function()
|
||||
return localization:Format(stringInfo)
|
||||
end)
|
||||
|
||||
ArgCheck.isEqual(success, true, string.format(
|
||||
"LocalizationConsumer finding value for translation key[%s]: %s", stringName, stringInfo))
|
||||
|
||||
localizedStrings[stringName] = success and result or ""
|
||||
else
|
||||
error(string.format("%s in stringsToBeLocalized must be a string or table, got %s instead",
|
||||
stringName, typeof(stringInfo)))
|
||||
end
|
||||
end
|
||||
|
||||
if settings():GetFFlag("AppBridgeStartupController") then
|
||||
return render(localizedStrings)
|
||||
else
|
||||
return Roact.createElement(ExternalEventConnection, {
|
||||
event = LocalizationService:GetPropertyChangedSignal("RobloxLocaleId"),
|
||||
callback = self.updateLocalization,
|
||||
}, {
|
||||
Component = render(localizedStrings),
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return LocalizationConsumer
|
||||
@@ -0,0 +1,4 @@
|
||||
return function()
|
||||
itSKIP("SOC-6353 - These unit tests need to be moved from LuaApp", function()
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,142 @@
|
||||
--[[
|
||||
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
|
||||
@@ -0,0 +1,66 @@
|
||||
return function()
|
||||
local LocalizationContext = require(script.Parent.LocalizationContext)
|
||||
|
||||
it("should pull from the correct language if available", function()
|
||||
local context = LocalizationContext.new({
|
||||
["es-mx"] = {
|
||||
["SomeKey"] = "Foo",
|
||||
},
|
||||
["es"] = {
|
||||
["SomeKey"] = "Bar",
|
||||
},
|
||||
["en-us"] = {
|
||||
["SomeKey"] = "Baz",
|
||||
},
|
||||
})
|
||||
|
||||
expect(context:getString("es-mx", "SomeKey")).to.equal("Foo")
|
||||
expect(context:getString("es", "SomeKey")).to.equal("Bar")
|
||||
expect(context:getString("en", "SomeKey")).to.equal("Baz")
|
||||
end)
|
||||
|
||||
it("should fall through to a language's base language", function()
|
||||
local context = LocalizationContext.new({
|
||||
["es-mx"] = {},
|
||||
["es"] = {
|
||||
["SomeKey"] = "Bar",
|
||||
},
|
||||
["en"] = {
|
||||
["SomeKey"] = "Baz",
|
||||
},
|
||||
})
|
||||
|
||||
expect(context:getString("es-mx", "SomeKey")).to.equal("Bar")
|
||||
expect(context:getString("es", "SomeKey")).to.equal("Bar")
|
||||
expect(context:getString("en", "SomeKey")).to.equal("Baz")
|
||||
end)
|
||||
|
||||
it("should fall through to English if keys are missing in each table", function()
|
||||
local context = LocalizationContext.new({
|
||||
["es-mx"] = {},
|
||||
["es"] = {},
|
||||
["en-us"] = {
|
||||
["SomeKey"] = "Baz",
|
||||
},
|
||||
})
|
||||
|
||||
expect(context:getString("es-mx", "SomeKey")).to.equal("Baz")
|
||||
expect(context:getString("es", "SomeKey")).to.equal("Baz")
|
||||
expect(context:getString("en_us", "SomeKey")).to.equal("Baz")
|
||||
end)
|
||||
|
||||
it("should replace formatting identifiers of the form {name}", function()
|
||||
local context = LocalizationContext.new({
|
||||
["en-us"] = {
|
||||
["SomeKey"] = "{greeting}, {target}!",
|
||||
},
|
||||
})
|
||||
|
||||
local value = context:getString("en-us", "SomeKey", {
|
||||
greeting = "Hello",
|
||||
target = "world",
|
||||
})
|
||||
|
||||
expect(value).to.equal("Hello, world!")
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Symbol = require(CorePackages.Symbol)
|
||||
|
||||
return Symbol.named("Localization")
|
||||
@@ -0,0 +1,19 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local LocalizationKey = require(CorePackages.Localization.LocalizationKey)
|
||||
|
||||
local LocalizationProvider = Roact.Component:extend("LocalizationProvider")
|
||||
|
||||
function LocalizationProvider:init(props)
|
||||
local localization = props.localization
|
||||
self._context[LocalizationKey] = {
|
||||
localization = localization
|
||||
}
|
||||
end
|
||||
|
||||
function LocalizationProvider:render()
|
||||
return Roact.oneChild(self.props[Roact.Children])
|
||||
end
|
||||
|
||||
return LocalizationProvider
|
||||
@@ -0,0 +1,308 @@
|
||||
-- Example locale-sensitive number formatting:
|
||||
-- https://docs.oracle.com/cd/E19455-01/806-0169/overview-9/index.html
|
||||
|
||||
--[[
|
||||
Locale specification:
|
||||
[DECIMAL_SEPARATOR] = string for decimal point, if needed
|
||||
[GROUP_DELIMITER] = string for groupings of numbers left of the decimal
|
||||
List section = abbreviations for language, in increasing order
|
||||
|
||||
Missing features in this code:
|
||||
- No support for differences in number of digits per GROUP_DELIMITER.
|
||||
Some Chinese dialects group by 10000 instead of 1000.
|
||||
- No support for variable differences in number of digits per GROUP_DELIMITER.
|
||||
Indian natural language groups the first 3 to left of decimal, then every 2 after that.
|
||||
|
||||
See https://en.wikipedia.org/wiki/Decimal_separator#Digit_grouping
|
||||
]]
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Logging = require(CorePackages.Logging)
|
||||
|
||||
local RoundingBehaviour = require(script.Parent.RoundingBehaviour)
|
||||
|
||||
local localeInfos = {}
|
||||
|
||||
local DEFAULT_LOCALE = "en-us"
|
||||
|
||||
-- Separator aliases to help avoid spelling errors
|
||||
local DECIMAL_SEPARATOR = "decimalSeparator"
|
||||
local GROUP_DELIMITER = "groupDelimiter"
|
||||
|
||||
localeInfos["en-us"] = {
|
||||
[DECIMAL_SEPARATOR] = ".",
|
||||
[GROUP_DELIMITER] = ",",
|
||||
{ 1, "", },
|
||||
{ 1e3, "K", },
|
||||
{ 1e6, "M", },
|
||||
{ 1e9, "B", },
|
||||
}
|
||||
|
||||
localeInfos["es-es"] = {
|
||||
[DECIMAL_SEPARATOR] = ",",
|
||||
[GROUP_DELIMITER] = ".",
|
||||
{ 1, "", },
|
||||
{ 1e3, " mil", },
|
||||
{ 1e6, " M", },
|
||||
}
|
||||
|
||||
localeInfos["fr-fr"] = {
|
||||
[DECIMAL_SEPARATOR] = ",",
|
||||
[GROUP_DELIMITER] = " ",
|
||||
{ 1, "", },
|
||||
{ 1e3, " k", },
|
||||
{ 1e6, " M", },
|
||||
{ 1e9, " Md", },
|
||||
}
|
||||
|
||||
localeInfos["de-de"] = {
|
||||
[DECIMAL_SEPARATOR] = ",",
|
||||
[GROUP_DELIMITER] = " ",
|
||||
{ 1, "", },
|
||||
{ 1e3, " Tsd.", },
|
||||
{ 1e6, " Mio.", },
|
||||
{ 1e9, " Mrd.", },
|
||||
}
|
||||
|
||||
localeInfos["pt-br"] = {
|
||||
[DECIMAL_SEPARATOR] = ",",
|
||||
[GROUP_DELIMITER] = ".",
|
||||
{ 1, "", },
|
||||
{ 1e3, " mil", },
|
||||
{ 1e6, " mi", },
|
||||
{ 1e9, " bi", },
|
||||
}
|
||||
|
||||
localeInfos["zh-cn"] = {
|
||||
[DECIMAL_SEPARATOR] = ".",
|
||||
[GROUP_DELIMITER] = ",", -- Chinese commonly uses 3 digit groupings, despite 10000s rule
|
||||
{ 1, "", },
|
||||
{ 1e3, "千", },
|
||||
{ 1e4, "万", },
|
||||
{ 1e8, "亿", },
|
||||
}
|
||||
|
||||
localeInfos["zh-cjv"] = {
|
||||
[DECIMAL_SEPARATOR] = ".",
|
||||
[GROUP_DELIMITER] = ",",
|
||||
{ 1, "", },
|
||||
{ 1e3, "千", },
|
||||
{ 1e4, "万", },
|
||||
{ 1e8, "亿", },
|
||||
}
|
||||
|
||||
localeInfos["zh-tw"] = {
|
||||
[DECIMAL_SEPARATOR] = ".",
|
||||
[GROUP_DELIMITER] = ",",
|
||||
{ 1, "", },
|
||||
{ 1e3, "千", },
|
||||
{ 1e4, "萬", },
|
||||
{ 1e8, "億", },
|
||||
}
|
||||
|
||||
localeInfos["ko-kr"] = {
|
||||
[DECIMAL_SEPARATOR] = ".",
|
||||
[GROUP_DELIMITER] = ",",
|
||||
{ 1, "", },
|
||||
{ 1e3, "천", },
|
||||
{ 1e4, "만", },
|
||||
{ 1e8, "억", },
|
||||
}
|
||||
|
||||
localeInfos["ja-jp"] = {
|
||||
[DECIMAL_SEPARATOR] = ".",
|
||||
[GROUP_DELIMITER] = ",",
|
||||
{ 1, "", },
|
||||
{ 1e3, "千", },
|
||||
{ 1e4, "万", },
|
||||
{ 1e8, "億", },
|
||||
}
|
||||
|
||||
localeInfos["it-it"] = {
|
||||
[DECIMAL_SEPARATOR] = ",",
|
||||
[GROUP_DELIMITER] = " ",
|
||||
{ 1, "", },
|
||||
{ 1e3, " mila", },
|
||||
{ 1e6, " Mln", },
|
||||
{ 1e9, " Mld", },
|
||||
}
|
||||
|
||||
localeInfos["ru-ru"] = {
|
||||
[DECIMAL_SEPARATOR] = ",",
|
||||
[GROUP_DELIMITER] = ".",
|
||||
{ 1, "", },
|
||||
{ 1e3, " тыс", },
|
||||
{ 1e6, " млн", },
|
||||
{ 1e9, " млрд", },
|
||||
}
|
||||
|
||||
localeInfos["id-id"] = {
|
||||
[DECIMAL_SEPARATOR] = ",",
|
||||
[GROUP_DELIMITER] = ".",
|
||||
{ 1, "", },
|
||||
{ 1e3, " rb", },
|
||||
{ 1e6, " jt", },
|
||||
{ 1e9, " M", },
|
||||
}
|
||||
|
||||
localeInfos["vi-vn"] = {
|
||||
[DECIMAL_SEPARATOR] = ".",
|
||||
[GROUP_DELIMITER] = " ",
|
||||
{ 1, "", },
|
||||
{ 1e3, " N", },
|
||||
{ 1e6, " Tr", },
|
||||
{ 1e9, " T", },
|
||||
}
|
||||
|
||||
localeInfos["th-th"] = {
|
||||
[DECIMAL_SEPARATOR] = ".",
|
||||
[GROUP_DELIMITER] = ",",
|
||||
{ 1, "", },
|
||||
{ 1e3, " พ", },
|
||||
{ 1e4, " ม", },
|
||||
{ 1e5, " ส", },
|
||||
{ 1e6, " ล", },
|
||||
}
|
||||
|
||||
localeInfos["tr-tr"] = {
|
||||
[DECIMAL_SEPARATOR] = ",",
|
||||
[GROUP_DELIMITER] = ".",
|
||||
{ 1, "", },
|
||||
{ 1e3, " B", },
|
||||
{ 1e6, " Mn", },
|
||||
{ 1e9, " Mr", },
|
||||
}
|
||||
|
||||
-- Aliases for languages that use the same mappings.
|
||||
localeInfos["en-gb"] = localeInfos["en-us"]
|
||||
localeInfos["es-mx"] = localeInfos["es-es"]
|
||||
|
||||
local function findDecimalPointIndex(numberStr)
|
||||
return string.find(numberStr, "%.") or #numberStr + 1
|
||||
end
|
||||
|
||||
-- Find the base 10 offset needed to make 0.1 <= abs(number) < 1
|
||||
local function findDecimalOffset(number)
|
||||
if number == 0 then
|
||||
return 0
|
||||
end
|
||||
|
||||
local offsetToOnesRange = math.floor(math.log10(math.abs(number)))
|
||||
return -(offsetToOnesRange + 1) -- Offset one more (or less) digit
|
||||
end
|
||||
|
||||
local function roundToSignificantDigits(number, significantDigits, roundingBehaviour)
|
||||
local offset = findDecimalOffset(number)
|
||||
local multiplier = 10^(significantDigits + offset)
|
||||
local significand
|
||||
if roundingBehaviour == RoundingBehaviour.Truncate then
|
||||
significand = math.modf(number * multiplier)
|
||||
else
|
||||
significand = math.floor(number * multiplier + 0.5)
|
||||
end
|
||||
return significand / multiplier;
|
||||
end
|
||||
|
||||
local function addGroupDelimiters(numberStr, delimiter)
|
||||
local formatted = numberStr
|
||||
local delimiterSubStr = string.format("%%1%s%%2", delimiter)
|
||||
while true do
|
||||
local lFormatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", delimiterSubStr)
|
||||
formatted = lFormatted
|
||||
if k == 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
return formatted
|
||||
end
|
||||
|
||||
local function findDenominationEntry(localeInfo, number, roundingBehaviour)
|
||||
local denominationEntry = localeInfo[1] -- Default to base denominations
|
||||
local absOfNumber = math.abs(number)
|
||||
for i = #localeInfo, 2, -1 do
|
||||
local entry = localeInfo[i]
|
||||
local baseValue
|
||||
if roundingBehaviour == RoundingBehaviour.Truncate then
|
||||
baseValue = entry[1]
|
||||
else
|
||||
baseValue = entry[1] - (localeInfo[i - 1][1]) / 2
|
||||
end
|
||||
if baseValue <= absOfNumber then
|
||||
denominationEntry = entry
|
||||
break
|
||||
end
|
||||
end
|
||||
return denominationEntry
|
||||
end
|
||||
|
||||
local NumberLocalization = { }
|
||||
|
||||
function NumberLocalization.localize(number, locale)
|
||||
if number == 0 then
|
||||
return "0"
|
||||
end
|
||||
|
||||
local localeInfo = localeInfos[locale]
|
||||
if not localeInfo then
|
||||
localeInfo = localeInfos[DEFAULT_LOCALE]
|
||||
Logging.warn(string.format("Warning: Locale not found: '%s', reverting to '%s' instead.",
|
||||
tostring(locale), DEFAULT_LOCALE))
|
||||
end
|
||||
|
||||
if localeInfo.groupDelimiter then
|
||||
return addGroupDelimiters(number, localeInfo.groupDelimiter)
|
||||
end
|
||||
|
||||
return number
|
||||
end
|
||||
|
||||
function NumberLocalization.abbreviate(number, locale, roundingBehaviour)
|
||||
if number == 0 then
|
||||
return "0"
|
||||
end
|
||||
|
||||
if roundingBehaviour == nil then
|
||||
roundingBehaviour = RoundingBehaviour.RoundToClosest
|
||||
end
|
||||
|
||||
local localeInfo = localeInfos[locale]
|
||||
if not localeInfo then
|
||||
localeInfo = localeInfos[DEFAULT_LOCALE]
|
||||
Logging.warn(string.format("Warning: Locale not found: '%s', reverting to '%s' instead.",
|
||||
tostring(locale), DEFAULT_LOCALE))
|
||||
end
|
||||
|
||||
-- select which denomination we are going to use
|
||||
local denominationEntry = findDenominationEntry(localeInfo, number, roundingBehaviour)
|
||||
local baseValue = denominationEntry[1]
|
||||
local symbol = denominationEntry[2]
|
||||
|
||||
-- Round to required significant digits
|
||||
local significantQuotient = roundToSignificantDigits(number / baseValue, 3, roundingBehaviour)
|
||||
|
||||
-- trim to 1 decimal point
|
||||
local trimmedQuotient
|
||||
if roundingBehaviour == RoundingBehaviour.Truncate then
|
||||
trimmedQuotient = math.modf(significantQuotient * 10) / 10
|
||||
else
|
||||
trimmedQuotient = math.floor(significantQuotient * 10 + 0.5) / 10
|
||||
end
|
||||
local trimmedQuotientString = tostring(trimmedQuotient)
|
||||
|
||||
-- Split the string into integer and fraction parts
|
||||
local decimalPointIndex = findDecimalPointIndex(trimmedQuotientString)
|
||||
local integerPart = string.sub(trimmedQuotientString, 1, decimalPointIndex - 1)
|
||||
local fractionPart = string.sub(trimmedQuotientString, decimalPointIndex + 1, #trimmedQuotientString)
|
||||
|
||||
-- Add group delimiters to integer part
|
||||
if localeInfo.groupDelimiter then
|
||||
integerPart = addGroupDelimiters(integerPart, localeInfo.groupDelimiter)
|
||||
end
|
||||
|
||||
if #fractionPart > 0 then
|
||||
return integerPart .. localeInfo.decimalSeparator .. fractionPart .. symbol
|
||||
else
|
||||
return integerPart .. symbol
|
||||
end
|
||||
end
|
||||
|
||||
return NumberLocalization
|
||||
@@ -0,0 +1,110 @@
|
||||
return function()
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Logging = require(CorePackages.Logging)
|
||||
local NumberLocalization = require(CorePackages.Localization.NumberLocalization)
|
||||
|
||||
local RoundingBehaviour = require(script.Parent.RoundingBehaviour)
|
||||
|
||||
local function checkLocale(locale, responseMapping)
|
||||
for input, output in pairs(responseMapping) do
|
||||
expect(NumberLocalization.localize(input, locale)).to.equal(output)
|
||||
end
|
||||
end
|
||||
|
||||
local function checkValid_en_zh(locale)
|
||||
checkLocale(locale, {
|
||||
[0] = "0",
|
||||
[1] = "1",
|
||||
[25] = "25",
|
||||
[364] = "364",
|
||||
[4120] = "4,120",
|
||||
[57860] = "57,860",
|
||||
[624390] = "624,390",
|
||||
[7857000] = "7,857,000",
|
||||
[-12345678] = "-12,345,678",
|
||||
[23987.45678] = "23,987.45678",
|
||||
[-12.3456] = "-12.3456",
|
||||
[-23987.45678] = "-23,987.45678",
|
||||
})
|
||||
end
|
||||
|
||||
describe("NumberLocalization.localize", function()
|
||||
it("should default to en-us when locale is not recognized", function()
|
||||
local logs = Logging.capture(function()
|
||||
checkValid_en_zh("bad_locale")
|
||||
end)
|
||||
expect(string.match(logs.warnings[1], "^Warning: Locale not found:") ~= nil).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should default to en-us when locale is nil", function()
|
||||
local logs = Logging.capture(function()
|
||||
checkValid_en_zh(nil)
|
||||
end)
|
||||
expect(string.match(logs.warnings[1], "^Warning: Locale not found:") ~= nil).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should default to en-us when locale is empty", function()
|
||||
local logs = Logging.capture(function()
|
||||
checkValid_en_zh("")
|
||||
end)
|
||||
expect(string.match(logs.warnings[1], "^Warning: Locale not found:") ~= nil).to.equal(true)
|
||||
end)
|
||||
|
||||
it("should localize correctly. (en-us)", function()
|
||||
checkValid_en_zh("en-us")
|
||||
end)
|
||||
|
||||
it("should localize correctly. (en-gb)", function()
|
||||
checkValid_en_zh("en-gb")
|
||||
end)
|
||||
|
||||
it("should localize correctly. (zh-cn)", function()
|
||||
checkValid_en_zh("zh-cn")
|
||||
end)
|
||||
|
||||
it("should localize correctly. (zh-tw)", function()
|
||||
checkValid_en_zh("zh-tw")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("NumberLocalization.abbreviate", function()
|
||||
it("should round towards zero when using RoundingBehaviour.Truncate", function()
|
||||
local roundToZeroMap = {
|
||||
[0] = "0",
|
||||
[1] = "1",
|
||||
[25] = "25",
|
||||
[364] = "364",
|
||||
[4120] = "4.1K",
|
||||
[57860] = "57.8K",
|
||||
[624390] = "624K",
|
||||
[999999] = "999K",
|
||||
[7857000] = "7.8M",
|
||||
[8e7] = "80M",
|
||||
[9e8] = "900M",
|
||||
[1e9] = "1B",
|
||||
[1e12] = "1,000B",
|
||||
[-0] = "0",
|
||||
[-1] = "-1",
|
||||
[-25] = "-25",
|
||||
[-364] = "-364",
|
||||
[-4120] = "-4.1K",
|
||||
[-57860] = "-57.8K",
|
||||
[-624390] = "-624K",
|
||||
[-999999] = "-999K",
|
||||
[-7857000] = "-7.8M",
|
||||
[-8e7] = "-80M",
|
||||
[-9e8] = "-900M",
|
||||
[-1e9] = "-1B",
|
||||
[-1e12] = "-1,000B",
|
||||
[1.1] = "1.1",
|
||||
[1499.99] = "1.4K",
|
||||
[-1.1] = "-1.1",
|
||||
[-1499.99] = "-1.4K",
|
||||
}
|
||||
|
||||
for input, output in pairs(roundToZeroMap) do
|
||||
expect(NumberLocalization.abbreviate(input, "en-us", RoundingBehaviour.Truncate)).to.equal(output)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local enumerate = require(CorePackages.enumerate)
|
||||
|
||||
return enumerate("RoundingBehaviour", {
|
||||
"RoundToClosest",
|
||||
"Truncate",
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local ArgCheck = require(CorePackages.ArgCheck)
|
||||
local LocalizationConsumer = require(CorePackages.Localization.LocalizationConsumer)
|
||||
|
||||
local function withLocalization(stringsToBeLocalized)
|
||||
ArgCheck.isType(stringsToBeLocalized, "table", "stringsToBeLocalized passed to withLocalization()")
|
||||
|
||||
return function(render)
|
||||
ArgCheck.isType(render, "function", "render passed to withLocalization()")
|
||||
|
||||
return Roact.createElement(LocalizationConsumer, {
|
||||
render = render,
|
||||
stringsToBeLocalized = stringsToBeLocalized,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return withLocalization
|
||||
@@ -0,0 +1,4 @@
|
||||
return function()
|
||||
itSKIP("SOC-6353 - These unit tests need to be moved from LuaApp", function()
|
||||
end)
|
||||
end
|
||||
Reference in New Issue
Block a user