@quenty/clienttranslator 14.19.0 → 14.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/package.json +13 -13
- package/src/Shared/Conversion/LocalizationEntryParserUtils.lua +14 -7
- package/src/Shared/JSONTranslator.lua +150 -62
- package/src/Shared/Numbers/NumberLocalizationUtils.lua +113 -99
- package/src/Shared/Numbers/NumberLocalizationUtils.spec.lua +5 -5
- package/src/Shared/Numbers/RoundingBehaviourTypes.lua +13 -4
- package/src/Shared/TranslatorService.lua +55 -37
- package/src/Shared/Utils/LocalizationServiceUtils.lua +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [14.19.1](https://github.com/Quenty/NevermoreEngine/compare/@quenty/clienttranslator@14.19.0...@quenty/clienttranslator@14.19.1) (2025-04-05)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* Add types to packages ([2374fb2](https://github.com/Quenty/NevermoreEngine/commit/2374fb2b043cfbe0e9b507b3316eec46a4e353a0))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
# [14.19.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/clienttranslator@14.18.2...@quenty/clienttranslator@14.19.0) (2025-04-02)
|
|
7
18
|
|
|
8
19
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/clienttranslator",
|
|
3
|
-
"version": "14.19.
|
|
3
|
+
"version": "14.19.1",
|
|
4
4
|
"description": "Gets local translator for player",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Roblox",
|
|
@@ -25,20 +25,20 @@
|
|
|
25
25
|
"Quenty"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@quenty/blend": "^12.18.
|
|
29
|
-
"@quenty/instanceutils": "^13.17.
|
|
30
|
-
"@quenty/loader": "^10.8.
|
|
31
|
-
"@quenty/maid": "^3.4.
|
|
32
|
-
"@quenty/promise": "^10.10.
|
|
33
|
-
"@quenty/promisemaid": "^5.10.
|
|
34
|
-
"@quenty/pseudolocalize": "^3.4.
|
|
35
|
-
"@quenty/rx": "^13.17.
|
|
36
|
-
"@quenty/string": "^3.3.
|
|
37
|
-
"@quenty/table": "^3.7.
|
|
38
|
-
"@quenty/valueobject": "^13.17.
|
|
28
|
+
"@quenty/blend": "^12.18.1",
|
|
29
|
+
"@quenty/instanceutils": "^13.17.1",
|
|
30
|
+
"@quenty/loader": "^10.8.1",
|
|
31
|
+
"@quenty/maid": "^3.4.1",
|
|
32
|
+
"@quenty/promise": "^10.10.2",
|
|
33
|
+
"@quenty/promisemaid": "^5.10.2",
|
|
34
|
+
"@quenty/pseudolocalize": "^3.4.1",
|
|
35
|
+
"@quenty/rx": "^13.17.1",
|
|
36
|
+
"@quenty/string": "^3.3.2",
|
|
37
|
+
"@quenty/table": "^3.7.2",
|
|
38
|
+
"@quenty/valueobject": "^13.17.1"
|
|
39
39
|
},
|
|
40
40
|
"publishConfig": {
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "78c3ac0ab08dd18085b6e6e6e4f745e76ed99f68"
|
|
44
44
|
}
|
|
@@ -21,7 +21,7 @@ function LocalizationEntryParserUtils.decodeFromInstance(tableName, sourceLocale
|
|
|
21
21
|
local lookupTable = {}
|
|
22
22
|
local baseKey = ""
|
|
23
23
|
|
|
24
|
-
for _, descendant in
|
|
24
|
+
for _, descendant in folder:GetDescendants() do
|
|
25
25
|
if descendant:IsA("StringValue") then
|
|
26
26
|
local localeId = LocalizationEntryParserUtils._parseLocaleFromName(descendant.Name)
|
|
27
27
|
local decodedTable = HttpService:JSONDecode(descendant.Value)
|
|
@@ -36,7 +36,7 @@ function LocalizationEntryParserUtils.decodeFromInstance(tableName, sourceLocale
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
local results = {}
|
|
39
|
-
for _, item in
|
|
39
|
+
for _, item in lookupTable do
|
|
40
40
|
table.insert(results, item)
|
|
41
41
|
end
|
|
42
42
|
return results
|
|
@@ -50,10 +50,17 @@ function LocalizationEntryParserUtils.decodeFromTable(tableName, localeId, dataT
|
|
|
50
50
|
local lookupTable = {}
|
|
51
51
|
|
|
52
52
|
local baseKey = ""
|
|
53
|
-
LocalizationEntryParserUtils._parseTableToResultsList(
|
|
53
|
+
LocalizationEntryParserUtils._parseTableToResultsList(
|
|
54
|
+
lookupTable,
|
|
55
|
+
localeId,
|
|
56
|
+
localeId,
|
|
57
|
+
baseKey,
|
|
58
|
+
dataTable,
|
|
59
|
+
tableName
|
|
60
|
+
)
|
|
54
61
|
|
|
55
62
|
local results = {}
|
|
56
|
-
for _, item in
|
|
63
|
+
for _, item in lookupTable do
|
|
57
64
|
table.insert(results, item)
|
|
58
65
|
end
|
|
59
66
|
return results
|
|
@@ -75,7 +82,7 @@ function LocalizationEntryParserUtils._parseTableToResultsList(lookupTable, sour
|
|
|
75
82
|
assert(type(dataTable) == "table", "Bad dataTable")
|
|
76
83
|
assert(type(tableName) == "string", "Bad tableName")
|
|
77
84
|
|
|
78
|
-
for index, text in
|
|
85
|
+
for index, text in dataTable do
|
|
79
86
|
local key = baseKey .. index
|
|
80
87
|
if type(text) == "table" then
|
|
81
88
|
LocalizationEntryParserUtils._parseTableToResultsList(lookupTable, sourceLocaleId, localeId, key .. ".", text, tableName)
|
|
@@ -92,9 +99,9 @@ function LocalizationEntryParserUtils._parseTableToResultsList(lookupTable, sour
|
|
|
92
99
|
Values = {
|
|
93
100
|
[localeId] = text;
|
|
94
101
|
};
|
|
95
|
-
}
|
|
102
|
+
}
|
|
96
103
|
|
|
97
|
-
lookupTable[key] = found
|
|
104
|
+
lookupTable[key] = found
|
|
98
105
|
end
|
|
99
106
|
|
|
100
107
|
-- Ensure assignment
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
--!strict
|
|
1
2
|
--[=[
|
|
2
3
|
Utility function that loads a translator from a folder or a table.
|
|
3
4
|
|
|
@@ -27,16 +28,33 @@ local TranslationKeyUtils = require("TranslationKeyUtils")
|
|
|
27
28
|
local TranslatorService = require("TranslatorService")
|
|
28
29
|
local ValueObject = require("ValueObject")
|
|
29
30
|
local NumberLocalizationUtils = require("NumberLocalizationUtils")
|
|
31
|
+
local _ServiceBag = require("ServiceBag")
|
|
32
|
+
local _Observable = require("Observable")
|
|
33
|
+
local _Promise = require("Promise")
|
|
30
34
|
|
|
31
35
|
local JSONTranslator = {}
|
|
32
36
|
JSONTranslator.ClassName = "JSONTranslator"
|
|
33
37
|
JSONTranslator.ServiceName = "JSONTranslator"
|
|
34
38
|
JSONTranslator.__index = JSONTranslator
|
|
35
39
|
|
|
40
|
+
export type JSONTranslator = typeof(setmetatable(
|
|
41
|
+
{} :: {
|
|
42
|
+
_maid: Maid.Maid,
|
|
43
|
+
_serviceBag: _ServiceBag.ServiceBag,
|
|
44
|
+
_translatorService: TranslatorService.TranslatorService,
|
|
45
|
+
_translatorName: string,
|
|
46
|
+
_entries: { [string]: any },
|
|
47
|
+
_localizationTable: any,
|
|
48
|
+
_localTranslator: ValueObject.ValueObject<any>,
|
|
49
|
+
_sourceTranslator: ValueObject.ValueObject<any>,
|
|
50
|
+
},
|
|
51
|
+
JSONTranslator
|
|
52
|
+
))
|
|
53
|
+
|
|
36
54
|
--[=[
|
|
37
55
|
Constructs a new JSONTranslator from the given args.
|
|
38
56
|
|
|
39
|
-
```
|
|
57
|
+
```
|
|
40
58
|
local translator = JSONTranslator.new("MyTranslator", en", {
|
|
41
59
|
actions = {
|
|
42
60
|
respawn = "Respawn {playerName}";
|
|
@@ -63,7 +81,7 @@ JSONTranslator.__index = JSONTranslator
|
|
|
63
81
|
@param dataTable table
|
|
64
82
|
@return JSONTranslator
|
|
65
83
|
]=]
|
|
66
|
-
function JSONTranslator.new(translatorName, localeId, dataTable)
|
|
84
|
+
function JSONTranslator.new(translatorName: string, localeId: string, dataTable): JSONTranslator
|
|
67
85
|
assert(type(translatorName) == "string", "Bad translatorName")
|
|
68
86
|
|
|
69
87
|
local self = setmetatable({}, JSONTranslator)
|
|
@@ -81,12 +99,12 @@ function JSONTranslator.new(translatorName, localeId, dataTable)
|
|
|
81
99
|
error("Must pass a localeId and dataTable")
|
|
82
100
|
end
|
|
83
101
|
|
|
84
|
-
return self
|
|
102
|
+
return self :: any
|
|
85
103
|
end
|
|
86
104
|
|
|
87
|
-
function JSONTranslator
|
|
105
|
+
function JSONTranslator.Init(self: JSONTranslator, serviceBag: _ServiceBag.ServiceBag)
|
|
88
106
|
self._serviceBag = assert(serviceBag, "No serviceBag")
|
|
89
|
-
self._translatorService = self._serviceBag:GetService(TranslatorService)
|
|
107
|
+
self._translatorService = self._serviceBag:GetService(TranslatorService) :: any
|
|
90
108
|
|
|
91
109
|
self._maid = Maid.new()
|
|
92
110
|
self._localTranslator = self._maid:Add(ValueObject.new(nil))
|
|
@@ -94,8 +112,8 @@ function JSONTranslator:Init(serviceBag)
|
|
|
94
112
|
|
|
95
113
|
self._localizationTable = self._translatorService:GetLocalizationTable()
|
|
96
114
|
|
|
97
|
-
for _, item in
|
|
98
|
-
for localeId, text in
|
|
115
|
+
for _, item in self._entries do
|
|
116
|
+
for localeId, text in item.Values do
|
|
99
117
|
self._localizationTable:SetEntryValue(item.Key, item.Source, item.Context, localeId, text)
|
|
100
118
|
end
|
|
101
119
|
self._localizationTable:SetEntryExample(item.Key, item.Source, item.Context, item.Example)
|
|
@@ -105,84 +123,107 @@ function JSONTranslator:Init(serviceBag)
|
|
|
105
123
|
self._maid:GiveTask(self._translatorService:ObserveLocaleId():Subscribe(function(localeId)
|
|
106
124
|
self._localTranslator.Value = self._localizationTable:GetTranslator(localeId)
|
|
107
125
|
end))
|
|
108
|
-
self._maid:GiveTask(
|
|
109
|
-
|
|
110
|
-
|
|
126
|
+
self._maid:GiveTask(
|
|
127
|
+
RxInstanceUtils.observeProperty(self._localizationTable, "SourceLocaleId"):Subscribe(function(localeId)
|
|
128
|
+
self._sourceTranslator.Value = self._localizationTable:GetTranslator(localeId)
|
|
129
|
+
end)
|
|
130
|
+
)
|
|
111
131
|
end
|
|
112
132
|
|
|
113
|
-
function JSONTranslator
|
|
133
|
+
function JSONTranslator.ObserveNumber(self: JSONTranslator, number: number): _Observable.Observable<string>
|
|
114
134
|
return Rx.combineLatest({
|
|
115
|
-
localeId = self:ObserveLocaleId()
|
|
116
|
-
number = number
|
|
135
|
+
localeId = self:ObserveLocaleId(),
|
|
136
|
+
number = number,
|
|
117
137
|
}):Pipe({
|
|
118
138
|
Rx.map(function(state)
|
|
119
139
|
return NumberLocalizationUtils.localize(state.number, state.localeId)
|
|
120
|
-
end)
|
|
121
|
-
})
|
|
140
|
+
end) :: any,
|
|
141
|
+
}) :: any
|
|
122
142
|
end
|
|
123
143
|
|
|
124
|
-
function JSONTranslator
|
|
144
|
+
function JSONTranslator.ObserveAbbreviatedNumber(
|
|
145
|
+
self: JSONTranslator,
|
|
146
|
+
number: number,
|
|
147
|
+
roundingBehaviourType,
|
|
148
|
+
numSignificantDigits: number?
|
|
149
|
+
)
|
|
125
150
|
return Rx.combineLatest({
|
|
126
|
-
localeId = self:ObserveLocaleId()
|
|
127
|
-
roundingBehaviourType = roundingBehaviourType
|
|
128
|
-
numSignificantDigits = numSignificantDigits
|
|
129
|
-
number = number
|
|
151
|
+
localeId = self:ObserveLocaleId(),
|
|
152
|
+
roundingBehaviourType = roundingBehaviourType,
|
|
153
|
+
numSignificantDigits = numSignificantDigits,
|
|
154
|
+
number = number,
|
|
130
155
|
}):Pipe({
|
|
131
156
|
Rx.map(function(state)
|
|
132
157
|
return NumberLocalizationUtils.abbreviate(
|
|
133
158
|
state.number,
|
|
134
159
|
state.localeId,
|
|
135
160
|
state.roundingBehaviourType,
|
|
136
|
-
state.numSignificantDigits
|
|
137
|
-
|
|
161
|
+
state.numSignificantDigits
|
|
162
|
+
)
|
|
163
|
+
end) :: any,
|
|
138
164
|
})
|
|
139
165
|
end
|
|
140
166
|
|
|
141
|
-
|
|
142
167
|
--[=[
|
|
143
168
|
Observes the translated value
|
|
144
169
|
@param translationKey string
|
|
145
170
|
@param translationArgs table? -- May have observables (or convertable to observables) in it.
|
|
146
171
|
@return Observable<string>
|
|
147
172
|
]=]
|
|
148
|
-
function JSONTranslator
|
|
149
|
-
|
|
173
|
+
function JSONTranslator.ObserveFormatByKey(
|
|
174
|
+
self: JSONTranslator,
|
|
175
|
+
translationKey: string,
|
|
176
|
+
translationArgs
|
|
177
|
+
): _Observable.Observable<string>
|
|
178
|
+
assert((self :: any) ~= JSONTranslator, "Construct a new version of this class to use it")
|
|
150
179
|
assert(type(translationKey) == "string", "Key must be a string")
|
|
151
180
|
|
|
152
181
|
return Rx.combineLatest({
|
|
153
|
-
cloudTranslator = self:ObserveTranslator()
|
|
154
|
-
translationKey = translationKey
|
|
155
|
-
translationArgs = self:_observeArgs(translationArgs)
|
|
182
|
+
cloudTranslator = self:ObserveTranslator(),
|
|
183
|
+
translationKey = translationKey,
|
|
184
|
+
translationArgs = self:_observeArgs(translationArgs),
|
|
156
185
|
}):Pipe({
|
|
157
|
-
Rx.switchMap(function(mainState)
|
|
186
|
+
Rx.switchMap(function(mainState): any
|
|
158
187
|
if mainState.cloudTranslator then
|
|
159
188
|
return self._translatorService:ObserveLocaleId():Pipe({
|
|
160
189
|
Rx.map(function()
|
|
161
|
-
return self:_doTranslation(
|
|
162
|
-
|
|
190
|
+
return self:_doTranslation(
|
|
191
|
+
mainState.cloudTranslator,
|
|
192
|
+
mainState.translationKey,
|
|
193
|
+
mainState.translationArgs
|
|
194
|
+
)
|
|
195
|
+
end) :: any,
|
|
163
196
|
})
|
|
164
197
|
end
|
|
165
198
|
|
|
166
199
|
-- Fall back to local or source translator
|
|
167
200
|
return Rx.combineLatest({
|
|
168
|
-
localTranslator = self._localTranslator:Observe()
|
|
169
|
-
sourceTranslator = self._sourceTranslator:Observe()
|
|
201
|
+
localTranslator = self._localTranslator:Observe(),
|
|
202
|
+
sourceTranslator = self._sourceTranslator:Observe(),
|
|
170
203
|
}):Pipe({
|
|
171
|
-
Rx.map(function(state)
|
|
204
|
+
Rx.map(function(state): string?
|
|
172
205
|
if state.localTranslator then
|
|
173
|
-
return self:_doTranslation(
|
|
206
|
+
return self:_doTranslation(
|
|
207
|
+
state.localTranslator,
|
|
208
|
+
mainState.translationKey,
|
|
209
|
+
mainState.translationArgs
|
|
210
|
+
)
|
|
174
211
|
elseif state.sourceTranslator then
|
|
175
|
-
return self:_doTranslation(
|
|
212
|
+
return self:_doTranslation(
|
|
213
|
+
state.sourceTranslator,
|
|
214
|
+
mainState.translationKey,
|
|
215
|
+
mainState.translationArgs
|
|
216
|
+
)
|
|
176
217
|
else
|
|
177
218
|
return nil
|
|
178
219
|
end
|
|
179
|
-
end)
|
|
220
|
+
end) :: any,
|
|
180
221
|
Rx.where(function(value)
|
|
181
222
|
return value ~= nil
|
|
182
|
-
end)
|
|
223
|
+
end) :: any,
|
|
183
224
|
})
|
|
184
|
-
end)
|
|
185
|
-
})
|
|
225
|
+
end) :: any,
|
|
226
|
+
}) :: any
|
|
186
227
|
end
|
|
187
228
|
|
|
188
229
|
--[=[
|
|
@@ -197,8 +238,8 @@ end
|
|
|
197
238
|
@param args table?
|
|
198
239
|
@return Promise<string>
|
|
199
240
|
]=]
|
|
200
|
-
function JSONTranslator
|
|
201
|
-
assert(self ~= JSONTranslator, "Construct a new version of this class to use it")
|
|
241
|
+
function JSONTranslator.PromiseFormatByKey(self: JSONTranslator, translationKey: string, args)
|
|
242
|
+
assert((self :: any) ~= JSONTranslator, "Construct a new version of this class to use it")
|
|
202
243
|
assert(type(translationKey) == "string", "Key must be a string")
|
|
203
244
|
|
|
204
245
|
-- Always waits for full translator to be loaded since we only get one shot
|
|
@@ -207,11 +248,20 @@ function JSONTranslator:PromiseFormatByKey(translationKey, args)
|
|
|
207
248
|
end)
|
|
208
249
|
end
|
|
209
250
|
|
|
210
|
-
|
|
251
|
+
--[=[
|
|
252
|
+
Returns a promise that will resolve once the Roblox translator is loaded from the cloud.
|
|
253
|
+
@return Promise<Translator>
|
|
254
|
+
]=]
|
|
255
|
+
function JSONTranslator.PromiseTranslator(self: JSONTranslator): _Promise.Promise<Translator>
|
|
211
256
|
return self._translatorService:PromiseTranslator()
|
|
212
257
|
end
|
|
213
258
|
|
|
214
|
-
|
|
259
|
+
--[=[
|
|
260
|
+
Observes the current Roblox translator for this translator.
|
|
261
|
+
|
|
262
|
+
@return Observable<Translator>
|
|
263
|
+
]=]
|
|
264
|
+
function JSONTranslator.ObserveTranslator(self: JSONTranslator): _Observable.Observable<Translator>
|
|
215
265
|
return self._translatorService:ObserveTranslator()
|
|
216
266
|
end
|
|
217
267
|
|
|
@@ -220,7 +270,7 @@ end
|
|
|
220
270
|
|
|
221
271
|
@return Observable<string>
|
|
222
272
|
]=]
|
|
223
|
-
function JSONTranslator
|
|
273
|
+
function JSONTranslator.ObserveLocaleId(self: JSONTranslator): _Observable.Observable<string>
|
|
224
274
|
return self._translatorService:ObserveLocaleId()
|
|
225
275
|
end
|
|
226
276
|
|
|
@@ -235,7 +285,14 @@ end
|
|
|
235
285
|
@param localeId string
|
|
236
286
|
@param text string
|
|
237
287
|
]=]
|
|
238
|
-
function JSONTranslator
|
|
288
|
+
function JSONTranslator.SetEntryValue(
|
|
289
|
+
self: JSONTranslator,
|
|
290
|
+
translationKey: string,
|
|
291
|
+
source: string,
|
|
292
|
+
context: string,
|
|
293
|
+
localeId: string,
|
|
294
|
+
text: string
|
|
295
|
+
)
|
|
239
296
|
assert(type(translationKey) == "string", "Bad translationKey")
|
|
240
297
|
assert(type(source) == "string", "Bad source")
|
|
241
298
|
assert(type(context) == "string", "Bad context")
|
|
@@ -245,18 +302,44 @@ function JSONTranslator:SetEntryValue(translationKey, source, context, localeId,
|
|
|
245
302
|
self._localizationTable:SetEntryValue(translationKey, source, context, localeId, text or source)
|
|
246
303
|
|
|
247
304
|
if RunService:IsStudio() then
|
|
248
|
-
self._localizationTable:SetEntryValue(
|
|
305
|
+
self._localizationTable:SetEntryValue(
|
|
306
|
+
translationKey,
|
|
307
|
+
source,
|
|
308
|
+
context,
|
|
309
|
+
PseudoLocalize.getDefaultPseudoLocaleId(),
|
|
310
|
+
PseudoLocalize.pseudoLocalize(text)
|
|
311
|
+
)
|
|
249
312
|
end
|
|
250
313
|
end
|
|
251
314
|
|
|
252
|
-
|
|
315
|
+
--[=[
|
|
316
|
+
Observes a translation key and formats it with the given args.
|
|
317
|
+
|
|
318
|
+
@param prefix string
|
|
319
|
+
@param text string
|
|
320
|
+
@param translationArgs table?
|
|
321
|
+
@return Observable<string>
|
|
322
|
+
]=]
|
|
323
|
+
function JSONTranslator.ObserveTranslation(
|
|
324
|
+
self: JSONTranslator,
|
|
325
|
+
prefix: string,
|
|
326
|
+
text: string,
|
|
327
|
+
translationArgs
|
|
328
|
+
): _Observable.Observable<string>
|
|
253
329
|
assert(type(prefix) == "string", "Bad text")
|
|
254
330
|
assert(type(text) == "string", "Bad text")
|
|
255
331
|
|
|
256
332
|
return self:ObserveFormatByKey(self:ToTranslationKey(prefix, text), translationArgs)
|
|
257
333
|
end
|
|
258
334
|
|
|
259
|
-
|
|
335
|
+
--[=[
|
|
336
|
+
Converts the given prefix and text into a translation key.
|
|
337
|
+
|
|
338
|
+
@param prefix string
|
|
339
|
+
@param text string
|
|
340
|
+
@return string
|
|
341
|
+
]=]
|
|
342
|
+
function JSONTranslator.ToTranslationKey(self: JSONTranslator, prefix: string, text: string): string
|
|
260
343
|
assert(type(prefix) == "string", "Bad text")
|
|
261
344
|
assert(type(text) == "string", "Bad text")
|
|
262
345
|
|
|
@@ -274,16 +357,16 @@ end
|
|
|
274
357
|
|
|
275
358
|
@return string
|
|
276
359
|
]=]
|
|
277
|
-
function JSONTranslator
|
|
360
|
+
function JSONTranslator.GetLocaleId(self: JSONTranslator): string
|
|
278
361
|
return self._translatorService:GetLocaleId()
|
|
279
362
|
end
|
|
280
363
|
|
|
281
364
|
--[=[
|
|
282
365
|
Gets the localization table the translation is using.
|
|
283
366
|
|
|
284
|
-
@return
|
|
367
|
+
@return LocalizationTable
|
|
285
368
|
]=]
|
|
286
|
-
function JSONTranslator
|
|
369
|
+
function JSONTranslator.GetLocalizationTable(self: JSONTranslator): LocalizationTable
|
|
287
370
|
return self._localizationTable
|
|
288
371
|
end
|
|
289
372
|
|
|
@@ -291,7 +374,7 @@ end
|
|
|
291
374
|
Returns a promise that will resolve once the translator is loaded from the cloud.
|
|
292
375
|
@return Promise
|
|
293
376
|
]=]
|
|
294
|
-
function JSONTranslator
|
|
377
|
+
function JSONTranslator.PromiseLoaded(self: JSONTranslator): _Promise.Promise<()>
|
|
295
378
|
return self:PromiseTranslator()
|
|
296
379
|
end
|
|
297
380
|
|
|
@@ -307,8 +390,8 @@ end
|
|
|
307
390
|
@param args table?
|
|
308
391
|
@return string
|
|
309
392
|
]=]
|
|
310
|
-
function JSONTranslator
|
|
311
|
-
assert(self ~= JSONTranslator, "Construct a new version of this class to use it")
|
|
393
|
+
function JSONTranslator.FormatByKey(self: JSONTranslator, translationKey: string, args): string
|
|
394
|
+
assert((self :: any) ~= JSONTranslator, "Construct a new version of this class to use it")
|
|
312
395
|
assert(type(translationKey) == "string", "Key must be a string")
|
|
313
396
|
|
|
314
397
|
local translator = self._translatorService:GetTranslator()
|
|
@@ -319,24 +402,29 @@ function JSONTranslator:FormatByKey(translationKey, args)
|
|
|
319
402
|
return self:_doTranslation(translator, translationKey, args)
|
|
320
403
|
end
|
|
321
404
|
|
|
322
|
-
function JSONTranslator
|
|
405
|
+
function JSONTranslator._observeArgs(_self: JSONTranslator, translationArgs): _Observable.Observable<any>
|
|
323
406
|
if translationArgs == nil then
|
|
324
407
|
return Rx.of(nil)
|
|
325
408
|
end
|
|
326
409
|
|
|
327
410
|
local args = {}
|
|
328
|
-
for argKey, value in
|
|
411
|
+
for argKey, value in translationArgs do
|
|
329
412
|
args[argKey] = Blend.toPropertyObservable(value) or Rx.of(value)
|
|
330
413
|
end
|
|
331
414
|
|
|
332
415
|
return Rx.combineLatest(args)
|
|
333
416
|
end
|
|
334
417
|
|
|
335
|
-
function JSONTranslator
|
|
418
|
+
function JSONTranslator._doTranslation(
|
|
419
|
+
self: JSONTranslator,
|
|
420
|
+
translator: Translator,
|
|
421
|
+
translationKey: string,
|
|
422
|
+
args
|
|
423
|
+
): string
|
|
336
424
|
assert(typeof(translator) == "Instance", "Bad translator")
|
|
337
425
|
assert(type(translationKey) == "string", "Bad translationKey")
|
|
338
426
|
|
|
339
|
-
local translation
|
|
427
|
+
local translation: string
|
|
340
428
|
local ok, err = pcall(function()
|
|
341
429
|
translation = translator:FormatByKey(translationKey, args)
|
|
342
430
|
end)
|
|
@@ -371,7 +459,7 @@ function JSONTranslator:_doTranslation(translator, translationKey, args)
|
|
|
371
459
|
end)
|
|
372
460
|
end
|
|
373
461
|
|
|
374
|
-
if ok and not err then
|
|
462
|
+
if ok and not err and translation then
|
|
375
463
|
return translation
|
|
376
464
|
end
|
|
377
465
|
|
|
@@ -382,9 +470,9 @@ end
|
|
|
382
470
|
Cleans up the translator and deletes the localization table if it exists.
|
|
383
471
|
Should be called by [ServiceBag]
|
|
384
472
|
]=]
|
|
385
|
-
function JSONTranslator
|
|
473
|
+
function JSONTranslator.Destroy(self: JSONTranslator)
|
|
386
474
|
self._maid:DoCleaning()
|
|
387
|
-
setmetatable(self, nil)
|
|
475
|
+
setmetatable(self :: any, nil)
|
|
388
476
|
end
|
|
389
477
|
|
|
390
478
|
return JSONTranslator
|
|
@@ -38,145 +38,145 @@ local localeInfos = {}
|
|
|
38
38
|
localeInfos["en-us"] = {
|
|
39
39
|
[DECIMAL_SEPARATOR] = ".",
|
|
40
40
|
[GROUP_DELIMITER] = ",",
|
|
41
|
-
{ 1, ""
|
|
42
|
-
{ 1e3, "K"
|
|
43
|
-
{ 1e6, "M"
|
|
44
|
-
{ 1e9, "B"
|
|
41
|
+
{ 1, "" },
|
|
42
|
+
{ 1e3, "K" },
|
|
43
|
+
{ 1e6, "M" },
|
|
44
|
+
{ 1e9, "B" },
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
localeInfos["es-es"] = {
|
|
48
48
|
[DECIMAL_SEPARATOR] = ",",
|
|
49
49
|
[GROUP_DELIMITER] = ".",
|
|
50
|
-
{ 1, ""
|
|
51
|
-
{ 1e3, " mil"
|
|
52
|
-
{ 1e6, " M"
|
|
50
|
+
{ 1, "" },
|
|
51
|
+
{ 1e3, " mil" },
|
|
52
|
+
{ 1e6, " M" },
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
localeInfos["fr-fr"] = {
|
|
56
56
|
[DECIMAL_SEPARATOR] = ",",
|
|
57
57
|
[GROUP_DELIMITER] = " ",
|
|
58
|
-
{ 1, ""
|
|
59
|
-
{ 1e3, " k"
|
|
60
|
-
{ 1e6, " M"
|
|
61
|
-
{ 1e9, " Md"
|
|
58
|
+
{ 1, "" },
|
|
59
|
+
{ 1e3, " k" },
|
|
60
|
+
{ 1e6, " M" },
|
|
61
|
+
{ 1e9, " Md" },
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
localeInfos["de-de"] = {
|
|
65
65
|
[DECIMAL_SEPARATOR] = ",",
|
|
66
66
|
[GROUP_DELIMITER] = " ",
|
|
67
|
-
{ 1, ""
|
|
68
|
-
{ 1e3, " Tsd."
|
|
69
|
-
{ 1e6, " Mio."
|
|
70
|
-
{ 1e9, " Mrd."
|
|
67
|
+
{ 1, "" },
|
|
68
|
+
{ 1e3, " Tsd." },
|
|
69
|
+
{ 1e6, " Mio." },
|
|
70
|
+
{ 1e9, " Mrd." },
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
localeInfos["pt-br"] = {
|
|
74
74
|
[DECIMAL_SEPARATOR] = ",",
|
|
75
75
|
[GROUP_DELIMITER] = ".",
|
|
76
|
-
{ 1, ""
|
|
77
|
-
{ 1e3, " mil"
|
|
78
|
-
{ 1e6, " mi"
|
|
79
|
-
{ 1e9, " bi"
|
|
76
|
+
{ 1, "" },
|
|
77
|
+
{ 1e3, " mil" },
|
|
78
|
+
{ 1e6, " mi" },
|
|
79
|
+
{ 1e9, " bi" },
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
localeInfos["zh-cn"] = {
|
|
83
83
|
[DECIMAL_SEPARATOR] = ".",
|
|
84
84
|
[GROUP_DELIMITER] = ",", -- Chinese commonly uses 3 digit groupings, despite 10000s rule
|
|
85
|
-
{ 1, ""
|
|
86
|
-
{ 1e3, "千"
|
|
87
|
-
{ 1e4, "万"
|
|
88
|
-
{ 1e8, "亿"
|
|
85
|
+
{ 1, "" },
|
|
86
|
+
{ 1e3, "千" },
|
|
87
|
+
{ 1e4, "万" },
|
|
88
|
+
{ 1e8, "亿" },
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
localeInfos["zh-cjv"] = {
|
|
92
92
|
[DECIMAL_SEPARATOR] = ".",
|
|
93
93
|
[GROUP_DELIMITER] = ",",
|
|
94
|
-
{ 1, ""
|
|
95
|
-
{ 1e3, "千"
|
|
96
|
-
{ 1e4, "万"
|
|
97
|
-
{ 1e8, "亿"
|
|
94
|
+
{ 1, "" },
|
|
95
|
+
{ 1e3, "千" },
|
|
96
|
+
{ 1e4, "万" },
|
|
97
|
+
{ 1e8, "亿" },
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
localeInfos["zh-tw"] = {
|
|
101
101
|
[DECIMAL_SEPARATOR] = ".",
|
|
102
102
|
[GROUP_DELIMITER] = ",",
|
|
103
|
-
{ 1, ""
|
|
104
|
-
{ 1e3, "千"
|
|
105
|
-
{ 1e4, "萬"
|
|
106
|
-
{ 1e8, "億"
|
|
103
|
+
{ 1, "" },
|
|
104
|
+
{ 1e3, "千" },
|
|
105
|
+
{ 1e4, "萬" },
|
|
106
|
+
{ 1e8, "億" },
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
localeInfos["ko-kr"] = {
|
|
110
110
|
[DECIMAL_SEPARATOR] = ".",
|
|
111
111
|
[GROUP_DELIMITER] = ",",
|
|
112
|
-
{ 1, ""
|
|
113
|
-
{ 1e3, "천"
|
|
114
|
-
{ 1e4, "만"
|
|
115
|
-
{ 1e8, "억"
|
|
112
|
+
{ 1, "" },
|
|
113
|
+
{ 1e3, "천" },
|
|
114
|
+
{ 1e4, "만" },
|
|
115
|
+
{ 1e8, "억" },
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
localeInfos["ja-jp"] = {
|
|
119
119
|
[DECIMAL_SEPARATOR] = ".",
|
|
120
120
|
[GROUP_DELIMITER] = ",",
|
|
121
|
-
{ 1, ""
|
|
122
|
-
{ 1e3, "千"
|
|
123
|
-
{ 1e4, "万"
|
|
124
|
-
{ 1e8, "億"
|
|
121
|
+
{ 1, "" },
|
|
122
|
+
{ 1e3, "千" },
|
|
123
|
+
{ 1e4, "万" },
|
|
124
|
+
{ 1e8, "億" },
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
localeInfos["it-it"] = {
|
|
128
128
|
[DECIMAL_SEPARATOR] = ",",
|
|
129
129
|
[GROUP_DELIMITER] = " ",
|
|
130
|
-
{ 1, ""
|
|
131
|
-
{ 1e3, " mila"
|
|
132
|
-
{ 1e6, " Mln"
|
|
133
|
-
{ 1e9, " Mld"
|
|
130
|
+
{ 1, "" },
|
|
131
|
+
{ 1e3, " mila" },
|
|
132
|
+
{ 1e6, " Mln" },
|
|
133
|
+
{ 1e9, " Mld" },
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
localeInfos["ru-ru"] = {
|
|
137
137
|
[DECIMAL_SEPARATOR] = ",",
|
|
138
138
|
[GROUP_DELIMITER] = ".",
|
|
139
|
-
{ 1, ""
|
|
140
|
-
{ 1e3, " тыс"
|
|
141
|
-
{ 1e6, " млн"
|
|
142
|
-
{ 1e9, " млрд"
|
|
139
|
+
{ 1, "" },
|
|
140
|
+
{ 1e3, " тыс" },
|
|
141
|
+
{ 1e6, " млн" },
|
|
142
|
+
{ 1e9, " млрд" },
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
localeInfos["id-id"] = {
|
|
146
146
|
[DECIMAL_SEPARATOR] = ",",
|
|
147
147
|
[GROUP_DELIMITER] = ".",
|
|
148
|
-
{ 1, ""
|
|
149
|
-
{ 1e3, " rb"
|
|
150
|
-
{ 1e6, " jt"
|
|
151
|
-
{ 1e9, " M"
|
|
148
|
+
{ 1, "" },
|
|
149
|
+
{ 1e3, " rb" },
|
|
150
|
+
{ 1e6, " jt" },
|
|
151
|
+
{ 1e9, " M" },
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
localeInfos["vi-vn"] = {
|
|
155
155
|
[DECIMAL_SEPARATOR] = ".",
|
|
156
156
|
[GROUP_DELIMITER] = " ",
|
|
157
|
-
{ 1, ""
|
|
158
|
-
{ 1e3, " N"
|
|
159
|
-
{ 1e6, " Tr"
|
|
160
|
-
{ 1e9, " T"
|
|
157
|
+
{ 1, "" },
|
|
158
|
+
{ 1e3, " N" },
|
|
159
|
+
{ 1e6, " Tr" },
|
|
160
|
+
{ 1e9, " T" },
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
localeInfos["th-th"] = {
|
|
164
164
|
[DECIMAL_SEPARATOR] = ".",
|
|
165
165
|
[GROUP_DELIMITER] = ",",
|
|
166
|
-
{ 1, ""
|
|
167
|
-
{ 1e3, " พ"
|
|
168
|
-
{ 1e4, " ม"
|
|
169
|
-
{ 1e5, " ส"
|
|
170
|
-
{ 1e6, " ล"
|
|
166
|
+
{ 1, "" },
|
|
167
|
+
{ 1e3, " พ" },
|
|
168
|
+
{ 1e4, " ม" },
|
|
169
|
+
{ 1e5, " ส" },
|
|
170
|
+
{ 1e6, " ล" },
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
localeInfos["tr-tr"] = {
|
|
174
174
|
[DECIMAL_SEPARATOR] = ",",
|
|
175
175
|
[GROUP_DELIMITER] = ".",
|
|
176
|
-
{ 1, ""
|
|
177
|
-
{ 1e3, " B"
|
|
178
|
-
{ 1e6, " Mn"
|
|
179
|
-
{ 1e9, " Mr"
|
|
176
|
+
{ 1, "" },
|
|
177
|
+
{ 1e3, " B" },
|
|
178
|
+
{ 1e6, " Mn" },
|
|
179
|
+
{ 1e9, " Mr" },
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
-- Aliases for languages that use the same mappings.
|
|
@@ -184,12 +184,12 @@ localeInfos["en"] = localeInfos["en-us"]
|
|
|
184
184
|
localeInfos["en-gb"] = localeInfos["en-us"]
|
|
185
185
|
localeInfos["es-mx"] = localeInfos["es-es"]
|
|
186
186
|
|
|
187
|
-
local function findDecimalPointIndex(numberStr)
|
|
187
|
+
local function findDecimalPointIndex(numberStr: string)
|
|
188
188
|
return string.find(numberStr, "%.") or #numberStr + 1
|
|
189
189
|
end
|
|
190
190
|
|
|
191
191
|
-- Find the base 10 offset needed to make 0.1 <= abs(number) < 1
|
|
192
|
-
local function findDecimalOffset(number)
|
|
192
|
+
local function findDecimalOffset(number: number)
|
|
193
193
|
if number == 0 then
|
|
194
194
|
return 0
|
|
195
195
|
end
|
|
@@ -198,19 +198,23 @@ local function findDecimalOffset(number)
|
|
|
198
198
|
return -(offsetToOnesRange + 1) -- Offset one more (or less) digit
|
|
199
199
|
end
|
|
200
200
|
|
|
201
|
-
local function roundToSignificantDigits(
|
|
201
|
+
local function roundToSignificantDigits(
|
|
202
|
+
number: number,
|
|
203
|
+
significantDigits: number,
|
|
204
|
+
roundingBehaviourType: RoundingBehaviourTypes.RoundingBehaviourType
|
|
205
|
+
)
|
|
202
206
|
local offset = findDecimalOffset(number)
|
|
203
|
-
local multiplier = 10^(significantDigits + offset)
|
|
207
|
+
local multiplier = 10 ^ (significantDigits + offset)
|
|
204
208
|
local significand
|
|
205
209
|
if roundingBehaviourType == RoundingBehaviourTypes.TRUNCATE then
|
|
206
210
|
significand = math.modf(number * multiplier)
|
|
207
211
|
else
|
|
208
212
|
significand = math.floor(number * multiplier + 0.5)
|
|
209
213
|
end
|
|
210
|
-
return significand / multiplier
|
|
214
|
+
return significand / multiplier
|
|
211
215
|
end
|
|
212
216
|
|
|
213
|
-
local function addGroupDelimiters(numberStr, delimiter)
|
|
217
|
+
local function addGroupDelimiters(numberStr, delimiter: string): string
|
|
214
218
|
local formatted = numberStr
|
|
215
219
|
local delimiterSubStr = string.format("%%1%s%%2", delimiter)
|
|
216
220
|
while true do
|
|
@@ -223,7 +227,7 @@ local function addGroupDelimiters(numberStr, delimiter)
|
|
|
223
227
|
return formatted
|
|
224
228
|
end
|
|
225
229
|
|
|
226
|
-
local function findDenominationEntry(localeInfo, number, roundingBehaviourType)
|
|
230
|
+
local function findDenominationEntry(localeInfo, number: number, roundingBehaviourType)
|
|
227
231
|
local denominationEntry = localeInfo[1] -- Default to base denominations
|
|
228
232
|
local absOfNumber = math.abs(number)
|
|
229
233
|
for i = #localeInfo, 2, -1 do
|
|
@@ -232,7 +236,7 @@ local function findDenominationEntry(localeInfo, number, roundingBehaviourType)
|
|
|
232
236
|
if roundingBehaviourType == RoundingBehaviourTypes.TRUNCATE then
|
|
233
237
|
baseValue = entry[1]
|
|
234
238
|
else
|
|
235
|
-
baseValue = entry[1] -
|
|
239
|
+
baseValue = entry[1] - localeInfo[i - 1][1] / 2
|
|
236
240
|
end
|
|
237
241
|
if baseValue <= absOfNumber then
|
|
238
242
|
denominationEntry = entry
|
|
@@ -242,7 +246,7 @@ local function findDenominationEntry(localeInfo, number, roundingBehaviourType)
|
|
|
242
246
|
return denominationEntry
|
|
243
247
|
end
|
|
244
248
|
|
|
245
|
-
function NumberLocalizationUtils.localize(number, locale)
|
|
249
|
+
function NumberLocalizationUtils.localize(number: number, locale: string): string
|
|
246
250
|
if number == 0 then
|
|
247
251
|
return "0"
|
|
248
252
|
end
|
|
@@ -250,15 +254,20 @@ function NumberLocalizationUtils.localize(number, locale)
|
|
|
250
254
|
local localeInfo = localeInfos[locale]
|
|
251
255
|
if not localeInfo then
|
|
252
256
|
localeInfo = localeInfos[DEFAULT_LOCALE]
|
|
253
|
-
warn(
|
|
254
|
-
|
|
257
|
+
warn(
|
|
258
|
+
string.format(
|
|
259
|
+
"[NumberLocalizationUtils] - Locale not found: '%s', reverting to '%s' instead.",
|
|
260
|
+
tostring(locale),
|
|
261
|
+
DEFAULT_LOCALE
|
|
262
|
+
)
|
|
263
|
+
)
|
|
255
264
|
end
|
|
256
265
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
266
|
+
if localeInfo.groupDelimiter then
|
|
267
|
+
return addGroupDelimiters(number, localeInfo.groupDelimiter)
|
|
268
|
+
end
|
|
260
269
|
|
|
261
|
-
|
|
270
|
+
return tostring(number)
|
|
262
271
|
end
|
|
263
272
|
|
|
264
273
|
--[=[
|
|
@@ -272,11 +281,14 @@ end
|
|
|
272
281
|
@param locale string
|
|
273
282
|
@param roundingBehaviourType RoundingBehaviourType?
|
|
274
283
|
@param numSignificantDigits number?
|
|
284
|
+
@return string
|
|
275
285
|
]=]
|
|
276
|
-
function NumberLocalizationUtils.abbreviate(number, locale, roundingBehaviourType
|
|
286
|
+
function NumberLocalizationUtils.abbreviate(number: number, locale: string, roundingBehaviourType: RoundingBehaviourTypes.RoundingBehaviourType?, numSignificantDigits: number?): string
|
|
277
287
|
assert(type(number) == "number", "Bad number")
|
|
288
|
+
local roundingBehavior = roundingBehaviourType or RoundingBehaviourTypes.ROUND_TO_CLOSEST
|
|
289
|
+
local significantDigits = numSignificantDigits or 3
|
|
278
290
|
|
|
279
|
-
if
|
|
291
|
+
if roundingBehavior == RoundingBehaviourTypes.NONE then
|
|
280
292
|
return NumberLocalizationUtils.localize(number, locale)
|
|
281
293
|
end
|
|
282
294
|
|
|
@@ -284,41 +296,43 @@ function NumberLocalizationUtils.abbreviate(number, locale, roundingBehaviourTyp
|
|
|
284
296
|
return "0"
|
|
285
297
|
end
|
|
286
298
|
|
|
287
|
-
if roundingBehaviourType == nil then
|
|
288
|
-
roundingBehaviourType = RoundingBehaviourTypes.ROUND_TO_CLOSEST
|
|
289
|
-
end
|
|
290
|
-
|
|
291
|
-
if numSignificantDigits == nil then
|
|
292
|
-
numSignificantDigits = 3
|
|
293
|
-
end
|
|
294
|
-
|
|
295
299
|
local localeInfo = localeInfos[locale]
|
|
296
300
|
if not localeInfo then
|
|
297
301
|
localeInfo = localeInfos[DEFAULT_LOCALE]
|
|
298
|
-
warn(
|
|
299
|
-
|
|
302
|
+
warn(
|
|
303
|
+
string.format(
|
|
304
|
+
"[NumberLocalizationUtils] - Locale not found: '%s', reverting to '%s' instead.",
|
|
305
|
+
tostring(locale),
|
|
306
|
+
DEFAULT_LOCALE
|
|
307
|
+
)
|
|
308
|
+
)
|
|
300
309
|
end
|
|
301
310
|
|
|
302
311
|
-- select which denomination we are going to use
|
|
303
|
-
local denominationEntry = findDenominationEntry(localeInfo, number,
|
|
312
|
+
local denominationEntry = findDenominationEntry(localeInfo, number, roundingBehavior)
|
|
304
313
|
local baseValue = denominationEntry[1]
|
|
305
314
|
local symbol = denominationEntry[2]
|
|
306
315
|
|
|
307
316
|
-- Round to required significant digits
|
|
308
|
-
local significantQuotient = roundToSignificantDigits(number / baseValue,
|
|
317
|
+
local significantQuotient = roundToSignificantDigits(number / baseValue, significantDigits, roundingBehavior)
|
|
309
318
|
|
|
310
319
|
-- trim decimal points
|
|
311
320
|
local trimmedQuotientString
|
|
312
321
|
local symbolsAboveDecimal = math.ceil(math.log10(significantQuotient))
|
|
313
|
-
local maxDecimals = math.max(1,
|
|
322
|
+
local maxDecimals = math.max(1, significantDigits - symbolsAboveDecimal)
|
|
314
323
|
local trimmedQuotient
|
|
315
|
-
local roundingFactor = 10^maxDecimals
|
|
316
|
-
if
|
|
324
|
+
local roundingFactor = 10 ^ maxDecimals
|
|
325
|
+
if roundingBehavior == RoundingBehaviourTypes.TRUNCATE then
|
|
317
326
|
trimmedQuotient = math.modf(significantQuotient * roundingFactor) / roundingFactor
|
|
318
|
-
elseif
|
|
327
|
+
elseif roundingBehavior == RoundingBehaviourTypes.ROUND_TO_CLOSEST then
|
|
319
328
|
trimmedQuotient = math.floor(significantQuotient * roundingFactor + 0.5) / roundingFactor
|
|
320
329
|
else
|
|
321
|
-
error(
|
|
330
|
+
error(
|
|
331
|
+
string.format(
|
|
332
|
+
"[NumberLocalizationUtils.abbreviate] - Unknown roundingBehaviourType %q",
|
|
333
|
+
tostring(roundingBehavior)
|
|
334
|
+
)
|
|
335
|
+
)
|
|
322
336
|
end
|
|
323
337
|
|
|
324
338
|
trimmedQuotientString = tostring(trimmedQuotient)
|
|
@@ -340,4 +354,4 @@ function NumberLocalizationUtils.abbreviate(number, locale, roundingBehaviourTyp
|
|
|
340
354
|
end
|
|
341
355
|
end
|
|
342
356
|
|
|
343
|
-
return NumberLocalizationUtils
|
|
357
|
+
return NumberLocalizationUtils
|
|
@@ -8,13 +8,13 @@ local describe = Jest.Globals.describe
|
|
|
8
8
|
local expect = Jest.Globals.expect
|
|
9
9
|
local it = Jest.Globals.it
|
|
10
10
|
|
|
11
|
-
local function checkLocale(locale, responseMapping)
|
|
12
|
-
for input, output in
|
|
11
|
+
local function checkLocale(locale: string, responseMapping)
|
|
12
|
+
for input, output in responseMapping do
|
|
13
13
|
expect(NumberLocalizationUtils.localize(input, locale)).toBe(output)
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
local function checkValid_en_zh(locale)
|
|
17
|
+
local function checkValid_en_zh(locale: string)
|
|
18
18
|
checkLocale(locale, {
|
|
19
19
|
[0] = "0",
|
|
20
20
|
[1] = "1",
|
|
@@ -105,8 +105,8 @@ describe("NumberLocalizationUtils.abbreviate", function()
|
|
|
105
105
|
[-1499.99] = "-1.4K",
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
for input, output in
|
|
108
|
+
for input, output in roundToZeroMap do
|
|
109
109
|
expect(NumberLocalizationUtils.abbreviate(input, "en-us", RoundingBehaviourTypes.TRUNCATE)).toBe(output)
|
|
110
110
|
end
|
|
111
111
|
end)
|
|
112
|
-
end)
|
|
112
|
+
end)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
--!strict
|
|
1
2
|
--[=[
|
|
2
3
|
@class RoundingBehaviourTypes
|
|
3
4
|
]=]
|
|
@@ -6,8 +7,16 @@ local require = require(script.Parent.loader).load(script)
|
|
|
6
7
|
|
|
7
8
|
local Table = require("Table")
|
|
8
9
|
|
|
10
|
+
export type RoundingBehaviourType = "roundToClosest" | "truncate" | "None"
|
|
11
|
+
|
|
12
|
+
export type RoundingBehaviourTypeMap = {
|
|
13
|
+
ROUND_TO_CLOSEST: "roundToClosest",
|
|
14
|
+
TRUNCATE: "truncate",
|
|
15
|
+
NONE: "none",
|
|
16
|
+
}
|
|
17
|
+
|
|
9
18
|
return Table.readonly({
|
|
10
|
-
ROUND_TO_CLOSEST = "roundToClosest"
|
|
11
|
-
TRUNCATE = "truncate"
|
|
12
|
-
NONE = "
|
|
13
|
-
})
|
|
19
|
+
ROUND_TO_CLOSEST = "roundToClosest",
|
|
20
|
+
TRUNCATE = "truncate",
|
|
21
|
+
NONE = "none",
|
|
22
|
+
} :: RoundingBehaviourTypeMap)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
--!strict
|
|
1
2
|
--[=[
|
|
2
3
|
Handles selecting the right locale/translator for Studio, and Roblox games.
|
|
3
4
|
|
|
@@ -10,17 +11,33 @@ local Players = game:GetService("Players")
|
|
|
10
11
|
local RunService = game:GetService("RunService")
|
|
11
12
|
local LocalizationService = game:GetService("LocalizationService")
|
|
12
13
|
|
|
13
|
-
local RxInstanceUtils = require("RxInstanceUtils")
|
|
14
|
-
local Rx = require("Rx")
|
|
15
14
|
local LocalizationServiceUtils = require("LocalizationServiceUtils")
|
|
16
|
-
local ValueObject = require("ValueObject")
|
|
17
15
|
local Maid = require("Maid")
|
|
18
16
|
local Promise = require("Promise")
|
|
17
|
+
local Rx = require("Rx")
|
|
18
|
+
local RxInstanceUtils = require("RxInstanceUtils")
|
|
19
|
+
local ValueObject = require("ValueObject")
|
|
20
|
+
local _Observable = require("Observable")
|
|
21
|
+
local _ServiceBag = require("ServiceBag")
|
|
19
22
|
|
|
20
23
|
local TranslatorService = {}
|
|
21
24
|
TranslatorService.ServiceName = "TranslatorService"
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
export type TranslatorService = typeof(setmetatable(
|
|
27
|
+
{} :: {
|
|
28
|
+
_maid: Maid.Maid,
|
|
29
|
+
_serviceBag: _ServiceBag.ServiceBag,
|
|
30
|
+
_translator: ValueObject.ValueObject<Translator>,
|
|
31
|
+
_localizationTable: LocalizationTable?,
|
|
32
|
+
_pendingTranslatorPromise: Promise.Promise<Translator>?,
|
|
33
|
+
_localeIdValue: ValueObject.ValueObject<string>?,
|
|
34
|
+
_loadedPlayerObservable: _Observable.Observable<Player>?,
|
|
35
|
+
_loadedPlayer: Player?,
|
|
36
|
+
},
|
|
37
|
+
{ __index = TranslatorService }
|
|
38
|
+
))
|
|
39
|
+
|
|
40
|
+
function TranslatorService.Init(self: TranslatorService, serviceBag: _ServiceBag.ServiceBag)
|
|
24
41
|
assert(not self._serviceBag, "Already initialized")
|
|
25
42
|
self._serviceBag = assert(serviceBag, "No serviceBag")
|
|
26
43
|
self._maid = Maid.new()
|
|
@@ -29,7 +46,7 @@ function TranslatorService:Init(serviceBag)
|
|
|
29
46
|
self._translator:Mount(self:_observeTranslatorImpl())
|
|
30
47
|
end
|
|
31
48
|
|
|
32
|
-
function TranslatorService
|
|
49
|
+
function TranslatorService.GetLocalizationTable(self: TranslatorService): LocalizationTable
|
|
33
50
|
if self._localizationTable then
|
|
34
51
|
return self._localizationTable
|
|
35
52
|
end
|
|
@@ -47,7 +64,7 @@ function TranslatorService:GetLocalizationTable()
|
|
|
47
64
|
return localizationTable
|
|
48
65
|
end
|
|
49
66
|
|
|
50
|
-
function TranslatorService
|
|
67
|
+
function TranslatorService._getLocalizationTableName(_self: TranslatorService): string
|
|
51
68
|
if RunService:IsServer() then
|
|
52
69
|
return "GeneratedJSONTable_Server"
|
|
53
70
|
else
|
|
@@ -60,7 +77,7 @@ end
|
|
|
60
77
|
|
|
61
78
|
@return Observable<Translator>
|
|
62
79
|
]=]
|
|
63
|
-
function TranslatorService
|
|
80
|
+
function TranslatorService.ObserveTranslator(self: TranslatorService): _Observable.Observable<Translator>
|
|
64
81
|
return self._translator:Observe()
|
|
65
82
|
end
|
|
66
83
|
|
|
@@ -69,7 +86,7 @@ end
|
|
|
69
86
|
|
|
70
87
|
@return Observable<Translator>
|
|
71
88
|
]=]
|
|
72
|
-
function TranslatorService
|
|
89
|
+
function TranslatorService.PromiseTranslator(self: TranslatorService): Promise.Promise<Translator>
|
|
73
90
|
local found = self._translator.Value
|
|
74
91
|
if found then
|
|
75
92
|
return Promise.resolved(found)
|
|
@@ -95,7 +112,7 @@ function TranslatorService:PromiseTranslator()
|
|
|
95
112
|
end
|
|
96
113
|
end)
|
|
97
114
|
|
|
98
|
-
maid:GiveTask(self._translator:Observe():Subscribe(function(translator)
|
|
115
|
+
maid:GiveTask(self._translator:Observe():Subscribe(function(translator: Translator)
|
|
99
116
|
if translator then
|
|
100
117
|
promise:Resolve(translator)
|
|
101
118
|
end
|
|
@@ -109,7 +126,7 @@ end
|
|
|
109
126
|
|
|
110
127
|
@return Translator?
|
|
111
128
|
]=]
|
|
112
|
-
function TranslatorService
|
|
129
|
+
function TranslatorService.GetTranslator(self: TranslatorService): Translator?
|
|
113
130
|
return self._translator.Value
|
|
114
131
|
end
|
|
115
132
|
|
|
@@ -118,34 +135,34 @@ end
|
|
|
118
135
|
|
|
119
136
|
@return Observable<string>
|
|
120
137
|
]=]
|
|
121
|
-
function TranslatorService
|
|
138
|
+
function TranslatorService.ObserveLocaleId(self: TranslatorService): _Observable.Observable<string>
|
|
122
139
|
if self._localeIdValue then
|
|
123
140
|
return self._localeIdValue:Observe()
|
|
124
141
|
end
|
|
125
142
|
|
|
126
|
-
|
|
143
|
+
local valueObject = self._maid:Add(ValueObject.new("en-us", "string"))
|
|
127
144
|
|
|
128
|
-
|
|
129
|
-
Rx.switchMap(function(translator)
|
|
145
|
+
valueObject:Mount(self._translator:Observe():Pipe({
|
|
146
|
+
Rx.switchMap(function(translator: Translator): any
|
|
130
147
|
if translator then
|
|
131
148
|
return RxInstanceUtils.observeProperty(translator, "LocaleId")
|
|
132
149
|
else
|
|
133
150
|
-- Fallback
|
|
134
151
|
return self:_observeLoadedPlayer():Pipe({
|
|
135
|
-
Rx.switchMap(function(player)
|
|
152
|
+
Rx.switchMap(function(player: Player)
|
|
136
153
|
if player then
|
|
137
154
|
return RxInstanceUtils.observeProperty(player, "LocaleId")
|
|
138
155
|
else
|
|
139
156
|
return RxInstanceUtils.observeProperty(LocalizationService, "RobloxLocaleId")
|
|
140
157
|
end
|
|
141
|
-
end)
|
|
158
|
+
end) :: any,
|
|
142
159
|
})
|
|
143
160
|
end
|
|
144
|
-
end)
|
|
145
|
-
Rx.distinct()
|
|
161
|
+
end) :: any,
|
|
162
|
+
Rx.distinct(),
|
|
146
163
|
}))
|
|
147
|
-
|
|
148
|
-
return
|
|
164
|
+
self._localeIdValue = valueObject
|
|
165
|
+
return valueObject:Observe()
|
|
149
166
|
end
|
|
150
167
|
|
|
151
168
|
--[=[
|
|
@@ -153,7 +170,7 @@ end
|
|
|
153
170
|
|
|
154
171
|
@return string
|
|
155
172
|
]=]
|
|
156
|
-
function TranslatorService
|
|
173
|
+
function TranslatorService.GetLocaleId(self: TranslatorService): string
|
|
157
174
|
local found = self._translator.Value
|
|
158
175
|
if found then
|
|
159
176
|
return found.LocaleId
|
|
@@ -168,52 +185,53 @@ function TranslatorService:GetLocaleId()
|
|
|
168
185
|
end
|
|
169
186
|
end
|
|
170
187
|
|
|
171
|
-
function TranslatorService
|
|
188
|
+
function TranslatorService._observeTranslatorImpl(self: TranslatorService): _Observable.Observable<Translator>
|
|
172
189
|
return self:_observeLoadedPlayer():Pipe({
|
|
173
|
-
Rx.switchMap(function(loadedPlayer)
|
|
190
|
+
Rx.switchMap(function(loadedPlayer: Player): any
|
|
174
191
|
if loadedPlayer then
|
|
175
192
|
return Rx.fromPromise(LocalizationServiceUtils.promisePlayerTranslator(loadedPlayer))
|
|
176
193
|
end
|
|
177
194
|
|
|
178
195
|
return RxInstanceUtils.observeProperty(LocalizationService, "RobloxLocaleId"):Pipe({
|
|
179
|
-
Rx.switchMap(function(localeId)
|
|
196
|
+
Rx.switchMap(function(localeId: string): any
|
|
180
197
|
-- This can actually take a while (20-30 seconds)
|
|
181
198
|
return Rx.fromPromise(LocalizationServiceUtils.promiseTranslatorForLocale(localeId))
|
|
182
|
-
end)
|
|
199
|
+
end) :: any,
|
|
183
200
|
})
|
|
184
|
-
end)
|
|
185
|
-
})
|
|
201
|
+
end) :: any,
|
|
202
|
+
}) :: any
|
|
186
203
|
end
|
|
187
204
|
|
|
188
|
-
function TranslatorService
|
|
205
|
+
function TranslatorService._observeLoadedPlayer(self: TranslatorService): _Observable.Observable<Player>
|
|
189
206
|
if self._loadedPlayerObservable then
|
|
190
207
|
return self._loadedPlayerObservable
|
|
191
208
|
end
|
|
192
209
|
|
|
193
|
-
|
|
194
|
-
Rx.switchMap(function(player)
|
|
210
|
+
local observable: any = RxInstanceUtils.observeProperty(Players, "LocalPlayer"):Pipe({
|
|
211
|
+
Rx.switchMap(function(player: Player): any
|
|
195
212
|
if not player then
|
|
196
213
|
return Rx.of(nil)
|
|
197
214
|
end
|
|
198
215
|
|
|
199
216
|
return RxInstanceUtils.observeProperty(player, "LocaleId"):Pipe({
|
|
200
|
-
Rx.map(function(localeId)
|
|
217
|
+
Rx.map(function(localeId): Player?
|
|
201
218
|
if localeId == "" then
|
|
202
219
|
return nil
|
|
203
220
|
else
|
|
204
221
|
return player
|
|
205
222
|
end
|
|
206
|
-
end)
|
|
223
|
+
end) :: any,
|
|
207
224
|
})
|
|
208
|
-
end)
|
|
209
|
-
Rx.distinct()
|
|
210
|
-
Rx.cache()
|
|
225
|
+
end) :: any,
|
|
226
|
+
Rx.distinct() :: any,
|
|
227
|
+
Rx.cache() :: any,
|
|
211
228
|
})
|
|
229
|
+
self._loadedPlayerObservable = observable
|
|
212
230
|
|
|
213
|
-
return
|
|
231
|
+
return observable
|
|
214
232
|
end
|
|
215
233
|
|
|
216
|
-
function TranslatorService
|
|
234
|
+
function TranslatorService.Destroy(self: TranslatorService)
|
|
217
235
|
self._maid:DoCleaning()
|
|
218
236
|
end
|
|
219
237
|
|
|
@@ -39,7 +39,7 @@ function LocalizationServiceUtils.promiseTranslatorForLocale(localeId)
|
|
|
39
39
|
end)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
function LocalizationServiceUtils.promisePlayerTranslator(player)
|
|
42
|
+
function LocalizationServiceUtils.promisePlayerTranslator(player: Player)
|
|
43
43
|
local promiseTranslator = Promise.spawn(function(resolve, reject)
|
|
44
44
|
local translator = nil
|
|
45
45
|
local ok, err = pcall(function()
|