@quenty/clienttranslator 14.5.0 → 14.6.0
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 +11 -9
- package/src/Shared/Conversion/LocalizationEntryParserUtils.lua +114 -0
- package/src/Shared/JSONTranslator.lua +158 -171
- package/src/Shared/TranslatorService.lua +212 -0
- package/src/Shared/Utils/LocalizationServiceUtils.lua +38 -28
- package/src/Shared/Conversion/JsonToLocalizationTable.lua +0 -174
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.6.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/clienttranslator@14.5.0...@quenty/clienttranslator@14.6.0) (2024-09-12)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Unedited all changes ([60e64e3](https://github.com/Quenty/NevermoreEngine/commit/60e64e3efce17c10c4b8965871187d231b338dd4))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
# [14.5.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/clienttranslator@14.4.0...@quenty/clienttranslator@14.5.0) (2024-08-09)
|
|
7
18
|
|
|
8
19
|
**Note:** Version bump only for package @quenty/clienttranslator
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/clienttranslator",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.6.0",
|
|
4
4
|
"description": "Gets local translator for player",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Roblox",
|
|
@@ -25,18 +25,20 @@
|
|
|
25
25
|
"Quenty"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@quenty/blend": "^12.
|
|
29
|
-
"@quenty/instanceutils": "^13.
|
|
30
|
-
"@quenty/loader": "^10.
|
|
31
|
-
"@quenty/maid": "^3.
|
|
32
|
-
"@quenty/promise": "^10.
|
|
28
|
+
"@quenty/blend": "^12.5.0",
|
|
29
|
+
"@quenty/instanceutils": "^13.5.0",
|
|
30
|
+
"@quenty/loader": "^10.4.0",
|
|
31
|
+
"@quenty/maid": "^3.3.0",
|
|
32
|
+
"@quenty/promise": "^10.4.0",
|
|
33
|
+
"@quenty/promisemaid": "^5.4.0",
|
|
33
34
|
"@quenty/pseudolocalize": "^3.3.0",
|
|
34
|
-
"@quenty/rx": "^13.
|
|
35
|
+
"@quenty/rx": "^13.5.0",
|
|
35
36
|
"@quenty/string": "^3.2.0",
|
|
36
|
-
"@quenty/table": "^3.5.0"
|
|
37
|
+
"@quenty/table": "^3.5.0",
|
|
38
|
+
"@quenty/valueobject": "^13.5.0"
|
|
37
39
|
},
|
|
38
40
|
"publishConfig": {
|
|
39
41
|
"access": "public"
|
|
40
42
|
},
|
|
41
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "fb172906f3ee725269ec1e5f4daf9dca227e729d"
|
|
42
44
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
--[=[
|
|
2
|
+
Utility to build a localization table from json, intended to be used with rojo. Can also handle Rojo json
|
|
3
|
+
objects turned into tables!
|
|
4
|
+
|
|
5
|
+
@class LocalizationEntryParserUtils
|
|
6
|
+
]=]
|
|
7
|
+
|
|
8
|
+
local HttpService = game:GetService("HttpService")
|
|
9
|
+
local RunService = game:GetService("RunService")
|
|
10
|
+
|
|
11
|
+
local require = require(script.Parent.loader).load(script)
|
|
12
|
+
|
|
13
|
+
local PseudoLocalize = require("PseudoLocalize")
|
|
14
|
+
|
|
15
|
+
local LocalizationEntryParserUtils = {}
|
|
16
|
+
|
|
17
|
+
function LocalizationEntryParserUtils.decodeFromInstance(tableName, sourceLocaleId, folder)
|
|
18
|
+
assert(type(tableName) == "string", "Bad tableName")
|
|
19
|
+
assert(typeof(folder) == "Instance", "Bad folder")
|
|
20
|
+
|
|
21
|
+
local lookupTable = {}
|
|
22
|
+
local baseKey = ""
|
|
23
|
+
|
|
24
|
+
for _, descendant in pairs(folder:GetDescendants()) do
|
|
25
|
+
if descendant:IsA("StringValue") then
|
|
26
|
+
local localeId = LocalizationEntryParserUtils._parseLocaleFromName(descendant.Name)
|
|
27
|
+
local decodedTable = HttpService:JSONDecode(descendant.Value)
|
|
28
|
+
|
|
29
|
+
LocalizationEntryParserUtils._parseTableToResultsList(lookupTable, sourceLocaleId, localeId, baseKey, decodedTable, tableName)
|
|
30
|
+
elseif descendant:IsA("ModuleScript") then
|
|
31
|
+
local localeId = LocalizationEntryParserUtils._parseLocaleFromName(descendant.Name)
|
|
32
|
+
local decodedTable = require(descendant)
|
|
33
|
+
|
|
34
|
+
LocalizationEntryParserUtils._parseTableToResultsList(lookupTable, sourceLocaleId, localeId, baseKey, decodedTable, tableName)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
local results = {}
|
|
39
|
+
for _, item in pairs(lookupTable) do
|
|
40
|
+
table.insert(results, item)
|
|
41
|
+
end
|
|
42
|
+
return results
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
function LocalizationEntryParserUtils.decodeFromTable(tableName, localeId, dataTable)
|
|
46
|
+
assert(type(tableName) == "string", "Bad tableName")
|
|
47
|
+
assert(type(localeId) == "string", "Bad localeId")
|
|
48
|
+
assert(type(dataTable) == "table", "Bad dataTable")
|
|
49
|
+
|
|
50
|
+
local lookupTable = {}
|
|
51
|
+
|
|
52
|
+
local baseKey = ""
|
|
53
|
+
LocalizationEntryParserUtils._parseTableToResultsList(lookupTable, localeId, localeId, baseKey, dataTable, tableName)
|
|
54
|
+
|
|
55
|
+
local results = {}
|
|
56
|
+
for _, item in pairs(lookupTable) do
|
|
57
|
+
table.insert(results, item)
|
|
58
|
+
end
|
|
59
|
+
return results
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
function LocalizationEntryParserUtils._parseLocaleFromName(name)
|
|
63
|
+
if name:sub(-5) == ".json" then
|
|
64
|
+
return name:sub(1, #name-5)
|
|
65
|
+
else
|
|
66
|
+
return name
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
function LocalizationEntryParserUtils._parseTableToResultsList(lookupTable, sourceLocaleId, localeId, baseKey, dataTable, tableName)
|
|
71
|
+
assert(type(lookupTable) == "table", "Bad lookupTable")
|
|
72
|
+
assert(type(sourceLocaleId) == "string", "Bad sourceLocaleId")
|
|
73
|
+
assert(type(localeId) == "string", "Bad localeId")
|
|
74
|
+
assert(type(baseKey) == "string", "Bad baseKey")
|
|
75
|
+
assert(type(dataTable) == "table", "Bad dataTable")
|
|
76
|
+
assert(type(tableName) == "string", "Bad tableName")
|
|
77
|
+
|
|
78
|
+
for index, text in pairs(dataTable) do
|
|
79
|
+
local key = baseKey .. index
|
|
80
|
+
if type(text) == "table" then
|
|
81
|
+
LocalizationEntryParserUtils._parseTableToResultsList(lookupTable, sourceLocaleId, localeId, key .. ".", text, tableName)
|
|
82
|
+
elseif type(text) == "string" then
|
|
83
|
+
local found = lookupTable[key]
|
|
84
|
+
if found then
|
|
85
|
+
found.Values[localeId] = key
|
|
86
|
+
else
|
|
87
|
+
-- Guarantee the context is unique. This is important because Roblox will not
|
|
88
|
+
-- allow something with the same source without a differing context text.
|
|
89
|
+
local context = string.format("Generated from %s with key %s", tableName, key)
|
|
90
|
+
|
|
91
|
+
found = {
|
|
92
|
+
Context = context;
|
|
93
|
+
Example = text;
|
|
94
|
+
Key = key;
|
|
95
|
+
Source = sourceLocaleId;
|
|
96
|
+
Values = {
|
|
97
|
+
[localeId] = text;
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
if RunService:IsStudio() and sourceLocaleId == localeId then
|
|
102
|
+
found.Values[PseudoLocalize.getDefaultPseudoLocaleId()] = PseudoLocalize.pseudoLocalize(text)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
lookupTable[key] = found;
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
else
|
|
109
|
+
error(string.format("Bad type for text at key '%s'", key))
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
return LocalizationEntryParserUtils
|
|
@@ -15,19 +15,17 @@
|
|
|
15
15
|
|
|
16
16
|
local require = require(script.Parent.loader).load(script)
|
|
17
17
|
|
|
18
|
-
local Players = game:GetService("Players")
|
|
19
18
|
local RunService = game:GetService("RunService")
|
|
20
19
|
|
|
21
20
|
local Blend = require("Blend")
|
|
22
|
-
local
|
|
23
|
-
local LocalizationServiceUtils = require("LocalizationServiceUtils")
|
|
21
|
+
local LocalizationEntryParserUtils = require("LocalizationEntryParserUtils")
|
|
24
22
|
local Maid = require("Maid")
|
|
25
|
-
local Observable = require("Observable")
|
|
26
|
-
local Promise = require("Promise")
|
|
27
23
|
local PseudoLocalize = require("PseudoLocalize")
|
|
28
24
|
local Rx = require("Rx")
|
|
29
25
|
local RxInstanceUtils = require("RxInstanceUtils")
|
|
30
26
|
local TranslationKeyUtils = require("TranslationKeyUtils")
|
|
27
|
+
local TranslatorService = require("TranslatorService")
|
|
28
|
+
local ValueObject = require("ValueObject")
|
|
31
29
|
|
|
32
30
|
local JSONTranslator = {}
|
|
33
31
|
JSONTranslator.ClassName = "JSONTranslator"
|
|
@@ -60,28 +58,26 @@ JSONTranslator.__index = JSONTranslator
|
|
|
60
58
|
```
|
|
61
59
|
|
|
62
60
|
@param translatorName string -- Name of the translator. Used for source.
|
|
63
|
-
@param
|
|
61
|
+
@param localeId string
|
|
62
|
+
@param dataTable table
|
|
64
63
|
@return JSONTranslator
|
|
65
64
|
]=]
|
|
66
|
-
function JSONTranslator.new(translatorName,
|
|
65
|
+
function JSONTranslator.new(translatorName, localeId, dataTable)
|
|
66
|
+
assert(type(translatorName) == "string", "Bad translatorName")
|
|
67
|
+
|
|
67
68
|
local self = setmetatable({}, JSONTranslator)
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
self._translatorName = translatorName
|
|
70
71
|
self.ServiceName = translatorName
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
self._promiseTranslator = LocalizationServiceUtils.promiseTranslator(Players.LocalPlayer)
|
|
73
|
+
if type(localeId) == "string" and type(dataTable) == "table" then
|
|
74
|
+
self._entries = LocalizationEntryParserUtils.decodeFromTable(self._translatorName, localeId, dataTable)
|
|
75
|
+
elseif typeof(localeId) == "Instance" then
|
|
76
|
+
local parent = localeId
|
|
77
|
+
local sourceLocaleId = "en"
|
|
78
|
+
self._entries = LocalizationEntryParserUtils.decodeFromInstance(self._translatorName, sourceLocaleId, parent)
|
|
79
79
|
else
|
|
80
|
-
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
if RunService:IsStudio() then
|
|
84
|
-
PseudoLocalize.addToLocalizationTable(self._localizationTable, nil, "en")
|
|
80
|
+
error("Must pass a localeId and dataTable")
|
|
85
81
|
end
|
|
86
82
|
|
|
87
83
|
return self
|
|
@@ -89,6 +85,99 @@ end
|
|
|
89
85
|
|
|
90
86
|
function JSONTranslator:Init(serviceBag)
|
|
91
87
|
self._serviceBag = assert(serviceBag, "No serviceBag")
|
|
88
|
+
self._translatorService = self._serviceBag:GetService(TranslatorService)
|
|
89
|
+
|
|
90
|
+
self._maid = Maid.new()
|
|
91
|
+
self._localTranslator = self._maid:Add(ValueObject.new(nil))
|
|
92
|
+
self._sourceTranslator = self._maid:Add(ValueObject.new(nil))
|
|
93
|
+
|
|
94
|
+
self._localizationTable = self._translatorService:GetLocalizationTable()
|
|
95
|
+
|
|
96
|
+
for _, item in pairs(self._entries) do
|
|
97
|
+
for localeId, text in pairs(item.Values) do
|
|
98
|
+
self._localizationTable:SetEntryValue(item.Key, item.Source, item.Context, localeId, text)
|
|
99
|
+
end
|
|
100
|
+
self._localizationTable:SetEntryExample(item.Key, item.Source, item.Context, item.Example)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
self._maid:GiveTask(RxInstanceUtils.observeProperty(self._localizationTable, "SourceLocaleId"):Subscribe(function(localeId)
|
|
104
|
+
self._sourceTranslator.Value = self._localizationTable:GetTranslator(localeId)
|
|
105
|
+
end))
|
|
106
|
+
self._maid:GiveTask(self._translatorService:ObserveLocaleId():Subscribe(function(localeId)
|
|
107
|
+
self._localTranslator.Value = self._localizationTable:GetTranslator(localeId)
|
|
108
|
+
end))
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
--[=[
|
|
112
|
+
Observes the translated value
|
|
113
|
+
@param translationKey string
|
|
114
|
+
@param translationArgs table? -- May have observables (or convertable to observables) in it.
|
|
115
|
+
@return Observable<string>
|
|
116
|
+
]=]
|
|
117
|
+
function JSONTranslator:ObserveFormatByKey(translationKey, translationArgs)
|
|
118
|
+
assert(self ~= JSONTranslator, "Construct a new version of this class to use it")
|
|
119
|
+
assert(type(translationKey) == "string", "Key must be a string")
|
|
120
|
+
|
|
121
|
+
return Rx.combineLatest({
|
|
122
|
+
translator = self:ObserveTranslator();
|
|
123
|
+
translationKey = translationKey;
|
|
124
|
+
translationArgs = self:_observeArgs(translationArgs);
|
|
125
|
+
}):Pipe({
|
|
126
|
+
Rx.switchMap(function(mainState)
|
|
127
|
+
if mainState.translator then
|
|
128
|
+
return Rx.of(self:_doTranslation(mainState.translator, mainState.translationKey, mainState.translationArgs))
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
-- Fall back to local or source translator
|
|
132
|
+
return Rx.combineLatest({
|
|
133
|
+
localTranslator = self._localTranslator:Observe();
|
|
134
|
+
sourceTranslator = self._sourceTranslator:Observe();
|
|
135
|
+
}):Pipe({
|
|
136
|
+
Rx.map(function(state)
|
|
137
|
+
if state.localTranslator then
|
|
138
|
+
return self:_doTranslation(state.localTranslator, mainState.translationKey, mainState.translationArgs)
|
|
139
|
+
elseif state.sourceTranslator then
|
|
140
|
+
return self:_doTranslation(state.sourceTranslator, mainState.translationKey, mainState.translationArgs)
|
|
141
|
+
else
|
|
142
|
+
return nil
|
|
143
|
+
end
|
|
144
|
+
end);
|
|
145
|
+
Rx.where(function(value)
|
|
146
|
+
return value ~= nil
|
|
147
|
+
end);
|
|
148
|
+
})
|
|
149
|
+
end)
|
|
150
|
+
})
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
--[=[
|
|
154
|
+
Formats the resulting entry by args.
|
|
155
|
+
|
|
156
|
+
:::tip
|
|
157
|
+
You should use [JSONTranslator.ObserveFormatByKey] instead of this to respond
|
|
158
|
+
to locale changing.
|
|
159
|
+
:::
|
|
160
|
+
|
|
161
|
+
@param translationKey string
|
|
162
|
+
@param args table?
|
|
163
|
+
@return Promise<string>
|
|
164
|
+
]=]
|
|
165
|
+
function JSONTranslator:PromiseFormatByKey(translationKey, args)
|
|
166
|
+
assert(self ~= JSONTranslator, "Construct a new version of this class to use it")
|
|
167
|
+
assert(type(translationKey) == "string", "Key must be a string")
|
|
168
|
+
|
|
169
|
+
-- Always waits for full translator to be loaded since we only get one shot
|
|
170
|
+
return self:PromiseTranslator():Then(function(translator)
|
|
171
|
+
return self:_doTranslation(translator, translationKey, args)
|
|
172
|
+
end)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
function JSONTranslator:PromiseTranslator()
|
|
176
|
+
return self._translatorService:PromiseTranslator()
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
function JSONTranslator:ObserveTranslator()
|
|
180
|
+
return self._translatorService:ObserveTranslator()
|
|
92
181
|
end
|
|
93
182
|
|
|
94
183
|
--[=[
|
|
@@ -97,11 +186,7 @@ end
|
|
|
97
186
|
@return Observable<string>
|
|
98
187
|
]=]
|
|
99
188
|
function JSONTranslator:ObserveLocaleId()
|
|
100
|
-
return
|
|
101
|
-
Rx.switchMap(function(translator)
|
|
102
|
-
return RxInstanceUtils.observeProperty(translator, "LocaleId")
|
|
103
|
-
end)
|
|
104
|
-
})
|
|
189
|
+
return self._translatorService:ObserveLocaleId()
|
|
105
190
|
end
|
|
106
191
|
|
|
107
192
|
--[=[
|
|
@@ -125,17 +210,15 @@ function JSONTranslator:SetEntryValue(translationKey, source, context, localeId,
|
|
|
125
210
|
self._localizationTable:SetEntryValue(translationKey, source, context, localeId, text or source)
|
|
126
211
|
|
|
127
212
|
if RunService:IsStudio() then
|
|
128
|
-
self._localizationTable:SetEntryValue(translationKey, source, context,
|
|
129
|
-
PseudoLocalize.getDefaultPseudoLocaleId(),
|
|
130
|
-
PseudoLocalize.pseudoLocalize(text))
|
|
213
|
+
self._localizationTable:SetEntryValue(translationKey, source, context, PseudoLocalize.getDefaultPseudoLocaleId(), PseudoLocalize.pseudoLocalize(text))
|
|
131
214
|
end
|
|
132
215
|
end
|
|
133
216
|
|
|
134
|
-
function JSONTranslator:ObserveTranslation(prefix, text,
|
|
217
|
+
function JSONTranslator:ObserveTranslation(prefix, text, translationArgs)
|
|
135
218
|
assert(type(prefix) == "string", "Bad text")
|
|
136
219
|
assert(type(text) == "string", "Bad text")
|
|
137
220
|
|
|
138
|
-
return self:ObserveFormatByKey(self:ToTranslationKey(prefix, text),
|
|
221
|
+
return self:ObserveFormatByKey(self:ToTranslationKey(prefix, text), translationArgs)
|
|
139
222
|
end
|
|
140
223
|
|
|
141
224
|
function JSONTranslator:ToTranslationKey(prefix, text)
|
|
@@ -157,13 +240,7 @@ end
|
|
|
157
240
|
@return string
|
|
158
241
|
]=]
|
|
159
242
|
function JSONTranslator:GetLocaleId()
|
|
160
|
-
|
|
161
|
-
local translator = self._promiseTranslator:Wait()
|
|
162
|
-
return translator.LocaleId
|
|
163
|
-
else
|
|
164
|
-
warn("[JSONTranslator.GetLocaleId] - Translator is not loaded yet, returning english")
|
|
165
|
-
return "en"
|
|
166
|
-
end
|
|
243
|
+
return self._translatorService:GetLocaleId()
|
|
167
244
|
end
|
|
168
245
|
|
|
169
246
|
--[=[
|
|
@@ -180,188 +257,98 @@ end
|
|
|
180
257
|
@return Promise
|
|
181
258
|
]=]
|
|
182
259
|
function JSONTranslator:PromiseLoaded()
|
|
183
|
-
return self
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
--[=[
|
|
187
|
-
Makes the translator fall back to another translator if an entry cannot be found.
|
|
188
|
-
|
|
189
|
-
Mostly just used for testing.
|
|
190
|
-
|
|
191
|
-
@param translator JSONTranslator | Translator
|
|
192
|
-
]=]
|
|
193
|
-
function JSONTranslator:FallbackTo(translator)
|
|
194
|
-
assert(translator, "Bad translator")
|
|
195
|
-
assert(translator.FormatByKey, "Bad translator")
|
|
196
|
-
|
|
197
|
-
table.insert(self._fallbacks, translator)
|
|
260
|
+
return self:PromiseTranslator()
|
|
198
261
|
end
|
|
199
262
|
|
|
200
263
|
--[=[
|
|
201
|
-
Formats the
|
|
264
|
+
Formats or errors if the cloud translations are not loaded.
|
|
202
265
|
|
|
203
266
|
:::tip
|
|
204
267
|
You should use [JSONTranslator.ObserveFormatByKey] instead of this to respond
|
|
205
268
|
to locale changing.
|
|
206
269
|
:::
|
|
207
270
|
|
|
208
|
-
@param
|
|
271
|
+
@param translationKey string
|
|
209
272
|
@param args table?
|
|
210
|
-
@return
|
|
211
|
-
]=]
|
|
212
|
-
function JSONTranslator:PromiseFormatByKey(key, args)
|
|
213
|
-
assert(self ~= JSONTranslator, "Construct a new version of this class to use it")
|
|
214
|
-
assert(type(key) == "string", "Key must be a string")
|
|
215
|
-
|
|
216
|
-
return self._promiseTranslator:Then(function()
|
|
217
|
-
return self:FormatByKey(key, args)
|
|
218
|
-
end)
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
--[=[
|
|
222
|
-
Observes the translated value
|
|
223
|
-
@param key string
|
|
224
|
-
@param argData table? -- May have observables (or convertable to observables) in it.
|
|
225
|
-
@return Observable<string>
|
|
273
|
+
@return string
|
|
226
274
|
]=]
|
|
227
|
-
function JSONTranslator:
|
|
275
|
+
function JSONTranslator:FormatByKey(translationKey, args)
|
|
228
276
|
assert(self ~= JSONTranslator, "Construct a new version of this class to use it")
|
|
229
|
-
assert(type(
|
|
230
|
-
|
|
231
|
-
local argObservable
|
|
232
|
-
if argData then
|
|
233
|
-
local args = {}
|
|
234
|
-
for argKey, value in pairs(argData) do
|
|
235
|
-
args[argKey] = Blend.toPropertyObservable(value) or Rx.of(value)
|
|
236
|
-
end
|
|
277
|
+
assert(type(translationKey) == "string", "Key must be a string")
|
|
237
278
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
279
|
+
local translator = self._translatorService:GetTranslator()
|
|
280
|
+
if not translator then
|
|
281
|
+
error("Translator is not yet acquired yet")
|
|
241
282
|
end
|
|
242
283
|
|
|
243
|
-
return
|
|
244
|
-
local maid = Maid.new()
|
|
245
|
-
|
|
246
|
-
maid:GivePromise(self._promiseTranslator):Then(function(translator)
|
|
247
|
-
if argObservable then
|
|
248
|
-
maid:GiveTask(Rx.combineLatest({
|
|
249
|
-
localeId = RxInstanceUtils.observeProperty(translator, "LocaleId");
|
|
250
|
-
args = argObservable;
|
|
251
|
-
}):Subscribe(function(state)
|
|
252
|
-
sub:Fire(self:FormatByKey(key, state.args))
|
|
253
|
-
end))
|
|
254
|
-
else
|
|
255
|
-
maid:GiveTask(RxInstanceUtils.observeProperty(translator, "LocaleId"):Subscribe(function()
|
|
256
|
-
sub:Fire(self:FormatByKey(key, nil))
|
|
257
|
-
end))
|
|
258
|
-
end
|
|
259
|
-
end)
|
|
260
|
-
|
|
261
|
-
return maid
|
|
262
|
-
end)
|
|
284
|
+
return self:_doTranslation(translator, translationKey, args)
|
|
263
285
|
end
|
|
264
286
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
You should use [JSONTranslator.ObserveFormatByKey] instead of this to respond
|
|
270
|
-
to locale changing.
|
|
271
|
-
:::
|
|
272
|
-
|
|
273
|
-
@param key string
|
|
274
|
-
@param args table?
|
|
275
|
-
@return string
|
|
276
|
-
]=]
|
|
277
|
-
function JSONTranslator:FormatByKey(key, args)
|
|
278
|
-
assert(self ~= JSONTranslator, "Construct a new version of this class to use it")
|
|
279
|
-
assert(type(key) == "string", "Key must be a string")
|
|
287
|
+
function JSONTranslator:_observeArgs(translationArgs)
|
|
288
|
+
if translationArgs == nil then
|
|
289
|
+
return Rx.of(nil)
|
|
290
|
+
end
|
|
280
291
|
|
|
281
|
-
|
|
282
|
-
|
|
292
|
+
local args = {}
|
|
293
|
+
for argKey, value in pairs(translationArgs) do
|
|
294
|
+
args[argKey] = Blend.toPropertyObservable(value) or Rx.of(value)
|
|
283
295
|
end
|
|
284
296
|
|
|
285
|
-
|
|
297
|
+
return Rx.combineLatest(args)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
function JSONTranslator:_doTranslation(translator, translationKey, args)
|
|
301
|
+
assert(typeof(translator) == "Instance", "Bad translator")
|
|
302
|
+
assert(type(translationKey) == "string", "Bad translationKey")
|
|
286
303
|
|
|
287
|
-
local
|
|
304
|
+
local translation
|
|
288
305
|
local ok, err = pcall(function()
|
|
289
|
-
|
|
306
|
+
translation = translator:FormatByKey(translationKey, args)
|
|
290
307
|
end)
|
|
291
308
|
|
|
292
|
-
if
|
|
293
|
-
return
|
|
309
|
+
if translation then
|
|
310
|
+
return translation
|
|
294
311
|
end
|
|
295
312
|
|
|
296
313
|
if err then
|
|
297
314
|
warn(err)
|
|
298
315
|
else
|
|
299
|
-
warn("Failed to localize '" ..
|
|
316
|
+
warn("Failed to localize '" .. translationKey .. "'")
|
|
300
317
|
end
|
|
301
318
|
|
|
302
|
-
--
|
|
303
|
-
|
|
304
|
-
|
|
319
|
+
-- Try the local translator next (not from cloud)
|
|
320
|
+
local localTranslator = self._localTranslator.Value
|
|
321
|
+
if localTranslator then
|
|
305
322
|
ok, err = pcall(function()
|
|
306
|
-
|
|
323
|
+
translation = localTranslator:FormatByKey(translationKey, args)
|
|
307
324
|
end)
|
|
308
325
|
|
|
309
|
-
if
|
|
310
|
-
return
|
|
326
|
+
if translation then
|
|
327
|
+
return translation
|
|
311
328
|
end
|
|
312
329
|
end
|
|
313
330
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
if self._promiseTranslator:IsFulfilled() then
|
|
321
|
-
return assert(self._promiseTranslator:Wait(), "Failed to get translator")
|
|
322
|
-
else
|
|
323
|
-
error("Translator is not yet acquired yet")
|
|
324
|
-
return nil
|
|
331
|
+
-- Try the source translator next (we're missing the locale id)
|
|
332
|
+
local sourceTranslator = self._sourceTranslator.Value
|
|
333
|
+
if sourceTranslator then
|
|
334
|
+
ok, err = pcall(function()
|
|
335
|
+
translation = sourceTranslator:FormatByKey(translationKey, args)
|
|
336
|
+
end)
|
|
325
337
|
end
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
function JSONTranslator:_formatByKeyTestMode(key, args)
|
|
329
|
-
-- Can't read LocalizationService.ForcePlayModeRobloxLocaleId :(
|
|
330
|
-
local translator = self._localizationTable:GetTranslator("en")
|
|
331
|
-
local result
|
|
332
|
-
local ok, err = pcall(function()
|
|
333
|
-
result = translator:FormatByKey(key, args)
|
|
334
|
-
end)
|
|
335
338
|
|
|
336
339
|
if ok and not err then
|
|
337
|
-
return
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
for _, fallback in pairs(self._fallbacks) do
|
|
341
|
-
local value = fallback:FormatByKey(key, args)
|
|
342
|
-
if value then
|
|
343
|
-
return value
|
|
344
|
-
end
|
|
340
|
+
return translation
|
|
345
341
|
end
|
|
346
342
|
|
|
347
|
-
|
|
348
|
-
warn(err)
|
|
349
|
-
else
|
|
350
|
-
warn("[JSONTranslator._formatByKeyTestMode] - Failed to localize '" .. key .. "'")
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
return key
|
|
343
|
+
return translationKey
|
|
354
344
|
end
|
|
355
345
|
|
|
356
346
|
--[=[
|
|
357
347
|
Cleans up the translator and deletes the localization table if it exists.
|
|
348
|
+
Should be called by [ServiceBag]
|
|
358
349
|
]=]
|
|
359
350
|
function JSONTranslator:Destroy()
|
|
360
|
-
self.
|
|
361
|
-
self._localizationTable = nil
|
|
362
|
-
self._englishTranslator = nil
|
|
363
|
-
self._promiseTranslator = nil
|
|
364
|
-
|
|
351
|
+
self._maid:DoCleaning()
|
|
365
352
|
setmetatable(self, nil)
|
|
366
353
|
end
|
|
367
354
|
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
--[=[
|
|
2
|
+
Handles selecting the right locale/translator for Studio, and Roblox games.
|
|
3
|
+
|
|
4
|
+
@class TranslatorService
|
|
5
|
+
]=]
|
|
6
|
+
|
|
7
|
+
local require = require(script.Parent.loader).load(script)
|
|
8
|
+
|
|
9
|
+
local Players = game:GetService("Players")
|
|
10
|
+
local RunService = game:GetService("RunService")
|
|
11
|
+
local LocalizationService = game:GetService("LocalizationService")
|
|
12
|
+
|
|
13
|
+
local RxInstanceUtils = require("RxInstanceUtils")
|
|
14
|
+
local Rx = require("Rx")
|
|
15
|
+
local LocalizationServiceUtils = require("LocalizationServiceUtils")
|
|
16
|
+
local ValueObject = require("ValueObject")
|
|
17
|
+
local Maid = require("Maid")
|
|
18
|
+
local Promise = require("Promise")
|
|
19
|
+
|
|
20
|
+
local TranslatorService = {}
|
|
21
|
+
TranslatorService.ServiceName = "TranslatorService"
|
|
22
|
+
|
|
23
|
+
function TranslatorService:Init(serviceBag)
|
|
24
|
+
assert(not self._serviceBag, "Already initialized")
|
|
25
|
+
self._serviceBag = assert(serviceBag, "No serviceBag")
|
|
26
|
+
self._maid = Maid.new()
|
|
27
|
+
|
|
28
|
+
self._translator = self._maid:Add(ValueObject.new(nil))
|
|
29
|
+
self._translator:Mount(self:_observeTranslatorImpl())
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
function TranslatorService:GetLocalizationTable()
|
|
33
|
+
if self._localizationTable then
|
|
34
|
+
return self._localizationTable
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
local localizationTableName = self:_getLocalizationTableName()
|
|
38
|
+
local localizationTable = LocalizationService:FindFirstChild(localizationTableName)
|
|
39
|
+
|
|
40
|
+
if not localizationTable then
|
|
41
|
+
localizationTable = Instance.new("LocalizationTable")
|
|
42
|
+
localizationTable.Name = localizationTableName
|
|
43
|
+
localizationTable.Parent = LocalizationService
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
self._localizationTable = localizationTable
|
|
47
|
+
return localizationTable
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
function TranslatorService:_getLocalizationTableName()
|
|
51
|
+
if RunService:IsServer() then
|
|
52
|
+
return "GeneratedJSONTable_Server"
|
|
53
|
+
else
|
|
54
|
+
return "GeneratedJSONTable_Client"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
--[=[
|
|
59
|
+
Observes Roblox translator
|
|
60
|
+
|
|
61
|
+
@return Observable<Translator>
|
|
62
|
+
]=]
|
|
63
|
+
function TranslatorService:ObserveTranslator()
|
|
64
|
+
return self._translator:Observe()
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
--[=[
|
|
68
|
+
Promises the Roblox translator
|
|
69
|
+
|
|
70
|
+
@return Observable<Translator>
|
|
71
|
+
]=]
|
|
72
|
+
function TranslatorService:PromiseTranslator()
|
|
73
|
+
local found = self._translator.Value
|
|
74
|
+
if found then
|
|
75
|
+
return Promise.resolved(found)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
if self._pendingTranslatorPromise then
|
|
79
|
+
return self._pendingTranslatorPromise
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
local maid = Maid.new()
|
|
83
|
+
local promise = maid:Add(Promise.new())
|
|
84
|
+
|
|
85
|
+
self._maid._pendingTranslatorMaid = maid
|
|
86
|
+
self._pendingTranslatorPromise = promise
|
|
87
|
+
|
|
88
|
+
maid:GiveTask(function()
|
|
89
|
+
if self._maid._pendingTranslatorMaid == maid then
|
|
90
|
+
self._maid._pendingTranslatorMaid = nil
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if self._pendingTranslatorPromise == promise then
|
|
94
|
+
self._pendingTranslatorPromise = nil
|
|
95
|
+
end
|
|
96
|
+
end)
|
|
97
|
+
|
|
98
|
+
maid:GiveTask(self._translator:Observe():Subscribe(function(translator)
|
|
99
|
+
if translator then
|
|
100
|
+
promise:Resolve(translator)
|
|
101
|
+
end
|
|
102
|
+
end))
|
|
103
|
+
|
|
104
|
+
return promise
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
--[=[
|
|
108
|
+
Gets the current translator to use
|
|
109
|
+
|
|
110
|
+
@return Translator?
|
|
111
|
+
]=]
|
|
112
|
+
function TranslatorService:GetTranslator()
|
|
113
|
+
return self._translator.Value
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
--[=[
|
|
117
|
+
Observes the current locale id for this translator.
|
|
118
|
+
|
|
119
|
+
@return Observable<string>
|
|
120
|
+
]=]
|
|
121
|
+
function TranslatorService:ObserveLocaleId()
|
|
122
|
+
return self._translator:Observe():Pipe({
|
|
123
|
+
Rx.switchMap(function(translator)
|
|
124
|
+
if translator then
|
|
125
|
+
return RxInstanceUtils.observeProperty(translator, "LocaleId")
|
|
126
|
+
else
|
|
127
|
+
-- Fallback
|
|
128
|
+
return self:_observeLoadedPlayer():Pipe({
|
|
129
|
+
Rx.switchMap(function(player)
|
|
130
|
+
if player then
|
|
131
|
+
return RxInstanceUtils.observeProperty(player, "LocaleId")
|
|
132
|
+
else
|
|
133
|
+
return RxInstanceUtils.observeProperty(LocalizationService, "RobloxLocaleId")
|
|
134
|
+
end
|
|
135
|
+
end)
|
|
136
|
+
})
|
|
137
|
+
end
|
|
138
|
+
end);
|
|
139
|
+
Rx.distinct();
|
|
140
|
+
})
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
--[=[
|
|
144
|
+
Gets the localeId to use
|
|
145
|
+
|
|
146
|
+
@return string
|
|
147
|
+
]=]
|
|
148
|
+
function TranslatorService:GetLocaleId()
|
|
149
|
+
local found = self._translator.Value
|
|
150
|
+
if found then
|
|
151
|
+
return found.LocaleId
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
-- Fallback
|
|
155
|
+
local player = Players.LocalPlayer
|
|
156
|
+
if player and player.LocaleId ~= "" then
|
|
157
|
+
return player.LocaleId
|
|
158
|
+
else
|
|
159
|
+
return LocalizationService.RobloxLocaleId
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
function TranslatorService:_observeTranslatorImpl()
|
|
164
|
+
return self:_observeLoadedPlayer():Pipe({
|
|
165
|
+
Rx.switchMap(function(loadedPlayer)
|
|
166
|
+
if loadedPlayer then
|
|
167
|
+
return Rx.fromPromise(LocalizationServiceUtils.promisePlayerTranslator(loadedPlayer))
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
return RxInstanceUtils.observeProperty(LocalizationService, "RobloxLocaleId"):Pipe({
|
|
171
|
+
Rx.switchMap(function(localeId)
|
|
172
|
+
-- This can actually take a while (20-30 seconds)
|
|
173
|
+
return Rx.fromPromise(LocalizationServiceUtils.promiseTranslatorForLocale(localeId))
|
|
174
|
+
end)
|
|
175
|
+
})
|
|
176
|
+
end);
|
|
177
|
+
})
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
function TranslatorService:_observeLoadedPlayer()
|
|
181
|
+
if self._loadedPlayerObservable then
|
|
182
|
+
return self._loadedPlayerObservable
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
self._loadedPlayerObservable = RxInstanceUtils.observeProperty(Players, "LocalPlayer"):Pipe({
|
|
186
|
+
Rx.switchMap(function(player)
|
|
187
|
+
if not player then
|
|
188
|
+
return Rx.of(nil)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
return RxInstanceUtils.observeProperty(player, "LocaleId"):Pipe({
|
|
192
|
+
Rx.map(function(localeId)
|
|
193
|
+
if localeId == "" then
|
|
194
|
+
return nil
|
|
195
|
+
else
|
|
196
|
+
return player
|
|
197
|
+
end
|
|
198
|
+
end);
|
|
199
|
+
})
|
|
200
|
+
end);
|
|
201
|
+
Rx.distinct();
|
|
202
|
+
Rx.cache();
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
return self._loadedPlayerObservable
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
function TranslatorService:Destroy()
|
|
209
|
+
self._maid:DoCleaning()
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
return TranslatorService
|
|
@@ -8,53 +8,64 @@ local LocalizationService = game:GetService("LocalizationService")
|
|
|
8
8
|
local RunService = game:GetService("RunService")
|
|
9
9
|
|
|
10
10
|
local Promise = require("Promise")
|
|
11
|
+
local PromiseMaidUtils = require("PromiseMaidUtils")
|
|
12
|
+
|
|
13
|
+
local TIMEOUT = 20
|
|
14
|
+
if RunService:IsStudio() then
|
|
15
|
+
TIMEOUT = 0.5
|
|
16
|
+
end
|
|
17
|
+
|
|
11
18
|
local ERROR_PUBLISH_REQUIRED = "Publishing the game is required to use GetTranslatorForPlayerAsync API."
|
|
19
|
+
local ERROR_TIMEOUT = string.format("GetTranslatorForPlayerAsync is still pending after %f, using local table", TIMEOUT)
|
|
12
20
|
|
|
13
21
|
local LocalizationServiceUtils = {}
|
|
14
22
|
|
|
15
|
-
function LocalizationServiceUtils.
|
|
16
|
-
|
|
23
|
+
function LocalizationServiceUtils.promiseTranslatorForLocale(localeId)
|
|
24
|
+
return Promise.spawn(function(resolve, reject)
|
|
17
25
|
local translator = nil
|
|
18
26
|
local ok, err = pcall(function()
|
|
19
|
-
translator = LocalizationService:
|
|
27
|
+
translator = LocalizationService:GetTranslatorForLocaleAsync(localeId)
|
|
20
28
|
end)
|
|
21
29
|
|
|
22
30
|
if not ok then
|
|
23
|
-
reject(err or "Failed to
|
|
24
|
-
return
|
|
31
|
+
return reject(err or "Failed to GetTranslatorForLocaleAsync")
|
|
25
32
|
end
|
|
26
33
|
|
|
27
|
-
if translator then
|
|
28
|
-
|
|
29
|
-
resolve(translator)
|
|
30
|
-
return
|
|
34
|
+
if typeof(translator) ~= "Instance" then
|
|
35
|
+
return reject("Translator was not returned")
|
|
31
36
|
end
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
return
|
|
38
|
+
return resolve(translator)
|
|
35
39
|
end)
|
|
40
|
+
end
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
local
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
function LocalizationServiceUtils.promisePlayerTranslator(player)
|
|
43
|
+
local promiseTranslator = Promise.spawn(function(resolve, reject)
|
|
44
|
+
local translator = nil
|
|
45
|
+
local ok, err = pcall(function()
|
|
46
|
+
translator = LocalizationService:GetTranslatorForPlayerAsync(player)
|
|
47
|
+
end)
|
|
48
|
+
|
|
49
|
+
if not ok then
|
|
50
|
+
return reject(err or "Failed to GetTranslatorForPlayerAsync")
|
|
51
|
+
end
|
|
42
52
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if not asyncTranslatorPromise:IsPending() then
|
|
46
|
-
return
|
|
53
|
+
if typeof(translator) ~= "Instance" then
|
|
54
|
+
return reject("Translator was not returned")
|
|
47
55
|
end
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
asyncTranslatorPromise:Reject(
|
|
51
|
-
("GetTranslatorForPlayerAsync is still pending after %f, using local table")
|
|
52
|
-
:format(timeout))
|
|
57
|
+
return resolve(translator)
|
|
53
58
|
end)
|
|
54
59
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
PromiseMaidUtils.whilePromise(promiseTranslator, function(maid)
|
|
61
|
+
maid:GiveTask(task.delay(TIMEOUT, function()
|
|
62
|
+
promiseTranslator:Reject(ERROR_TIMEOUT)
|
|
63
|
+
end))
|
|
64
|
+
end)
|
|
65
|
+
|
|
66
|
+
return promiseTranslator:Catch(function(err)
|
|
67
|
+
if err ~= ERROR_PUBLISH_REQUIRED and error ~= ERROR_TIMEOUT then
|
|
68
|
+
warn(string.format("[LocalizationServiceUtils.promisePlayerTranslator] - %s", tostring(err)))
|
|
58
69
|
end
|
|
59
70
|
|
|
60
71
|
-- Fallback to just local stuff
|
|
@@ -63,5 +74,4 @@ function LocalizationServiceUtils.promiseTranslator(player)
|
|
|
63
74
|
end)
|
|
64
75
|
end
|
|
65
76
|
|
|
66
|
-
|
|
67
77
|
return LocalizationServiceUtils
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
--[=[
|
|
2
|
-
Utility to build a localization table from json, intended to be used with rojo. Can also handle Rojo json
|
|
3
|
-
objects turned into tables!
|
|
4
|
-
|
|
5
|
-
@class JsonToLocalizationTable
|
|
6
|
-
]=]
|
|
7
|
-
|
|
8
|
-
local LocalizationService = game:GetService("LocalizationService")
|
|
9
|
-
local HttpService = game:GetService("HttpService")
|
|
10
|
-
local RunService = game:GetService("RunService")
|
|
11
|
-
|
|
12
|
-
local JsonToLocalizationTable = {}
|
|
13
|
-
|
|
14
|
-
local LOCALIZATION_TABLE_NAME_CLIENT = "GeneratedJSONTable_Client"
|
|
15
|
-
local LOCALIZATION_TABLE_NAME_SERVER = "GeneratedJSONTable_Server"
|
|
16
|
-
|
|
17
|
-
--[[
|
|
18
|
-
Recursively iterates through the object to construct strings and add it to the localization table
|
|
19
|
-
|
|
20
|
-
@param localizationTable LocalizationTable
|
|
21
|
-
@param localeId string -- The localizationid to add
|
|
22
|
-
@param baseKey string -- the key to add
|
|
23
|
-
@param object any -- The value to iterate over
|
|
24
|
-
]]
|
|
25
|
-
local function recurseAdd(localizationTable, localeId, baseKey, object, tableName)
|
|
26
|
-
if baseKey ~= "" then
|
|
27
|
-
baseKey = baseKey .. "."
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
for index, value in pairs(object) do
|
|
31
|
-
local key = baseKey .. index
|
|
32
|
-
if type(value) == "table" then
|
|
33
|
-
recurseAdd(localizationTable, localeId, key, value, tableName)
|
|
34
|
-
elseif type(value) == "string" then
|
|
35
|
-
local source = ""
|
|
36
|
-
|
|
37
|
-
-- Guarantee the context is unique. This is important because Roblox will not
|
|
38
|
-
-- allow something with the same source without a differing context value.
|
|
39
|
-
local context = tableName .. "." .. key
|
|
40
|
-
|
|
41
|
-
if localeId == "en" then
|
|
42
|
-
source = value
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
localizationTable:SetEntryValue(key, source, context, localeId, value)
|
|
46
|
-
else
|
|
47
|
-
error("Bad type for value in '" .. key .. "'.")
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
--[=[
|
|
53
|
-
Extracts the locale from the name
|
|
54
|
-
|
|
55
|
-
@param name string -- The name to parse
|
|
56
|
-
@return string -- The locale
|
|
57
|
-
]=]
|
|
58
|
-
function JsonToLocalizationTable.localeFromName(name)
|
|
59
|
-
if name:sub(-5) == ".json" then
|
|
60
|
-
return name:sub(1, #name-5)
|
|
61
|
-
else
|
|
62
|
-
return name
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
--[=[
|
|
67
|
-
Gets or creates the global localization table. If the game isn't running (i.e. test mode), then
|
|
68
|
-
we'll just not parent it.
|
|
69
|
-
|
|
70
|
-
@return string -- The locale
|
|
71
|
-
]=]
|
|
72
|
-
function JsonToLocalizationTable.getOrCreateLocalizationTable()
|
|
73
|
-
local localizationTableName
|
|
74
|
-
if RunService:IsServer() then
|
|
75
|
-
localizationTableName = LOCALIZATION_TABLE_NAME_SERVER
|
|
76
|
-
else
|
|
77
|
-
localizationTableName = LOCALIZATION_TABLE_NAME_CLIENT
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
local localizationTable = LocalizationService:FindFirstChild(localizationTableName)
|
|
81
|
-
|
|
82
|
-
if not localizationTable then
|
|
83
|
-
localizationTable = Instance.new("LocalizationTable")
|
|
84
|
-
localizationTable.Name = localizationTableName
|
|
85
|
-
|
|
86
|
-
if RunService:IsRunning() then
|
|
87
|
-
localizationTable.Parent = LocalizationService
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
return localizationTable
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
--[=[
|
|
95
|
-
Loads a folder into a localization table.
|
|
96
|
-
|
|
97
|
-
@param tableName string -- Used for source
|
|
98
|
-
@param folder Folder -- A Roblox folder with StringValues containing JSON, named with the localization in mind
|
|
99
|
-
]=]
|
|
100
|
-
function JsonToLocalizationTable.loadFolder(tableName, folder)
|
|
101
|
-
assert(type(tableName) == "string", "Bad tableName")
|
|
102
|
-
|
|
103
|
-
local localizationTable = JsonToLocalizationTable.getOrCreateLocalizationTable()
|
|
104
|
-
|
|
105
|
-
for _, item in pairs(folder:GetDescendants()) do
|
|
106
|
-
if item:IsA("StringValue") then
|
|
107
|
-
local localeId = JsonToLocalizationTable.localeFromName(item.Name)
|
|
108
|
-
JsonToLocalizationTable.addJsonToTable(localizationTable, localeId, item.Value, tableName)
|
|
109
|
-
elseif item:IsA("ModuleScript") then
|
|
110
|
-
local localeId = JsonToLocalizationTable.localeFromName(item.Name)
|
|
111
|
-
recurseAdd(localizationTable, localeId, "", require(item), tableName)
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
return localizationTable
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
--[=[
|
|
118
|
-
Extracts the locale from the folder, or a locale and table.
|
|
119
|
-
|
|
120
|
-
@param tableName string -- Used for source
|
|
121
|
-
@param first Instance | string
|
|
122
|
-
@param second table?
|
|
123
|
-
@return LocalizationTable
|
|
124
|
-
]=]
|
|
125
|
-
function JsonToLocalizationTable.toLocalizationTable(tableName, first, second)
|
|
126
|
-
assert(type(tableName) == "string", "Bad tableName")
|
|
127
|
-
|
|
128
|
-
if typeof(first) == "Instance" then
|
|
129
|
-
local result = JsonToLocalizationTable.loadFolder(tableName, first)
|
|
130
|
-
-- result.Name = ("JSONTable_%s"):format(first.Name)
|
|
131
|
-
return result
|
|
132
|
-
elseif type(first) == "string" and type(second) == "table" then
|
|
133
|
-
local result = JsonToLocalizationTable.loadTable(tableName, first, second)
|
|
134
|
-
return result
|
|
135
|
-
else
|
|
136
|
-
error("Bad args")
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
--[=[
|
|
141
|
-
Extracts the locale from the name
|
|
142
|
-
|
|
143
|
-
@param tableName string -- Used for source
|
|
144
|
-
@param localeId string -- the defaultlocaleId
|
|
145
|
-
@param dataTable table -- Data table to load from
|
|
146
|
-
@return LocalizationTable
|
|
147
|
-
]=]
|
|
148
|
-
function JsonToLocalizationTable.loadTable(tableName, localeId, dataTable)
|
|
149
|
-
assert(type(tableName) == "string", "Bad tableName")
|
|
150
|
-
|
|
151
|
-
local localizationTable = JsonToLocalizationTable.getOrCreateLocalizationTable()
|
|
152
|
-
|
|
153
|
-
recurseAdd(localizationTable, localeId, "", dataTable, tableName)
|
|
154
|
-
|
|
155
|
-
return localizationTable
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
--[=[
|
|
159
|
-
Adds json to a localization table
|
|
160
|
-
|
|
161
|
-
@param localizationTable LocalizationTable -- The localization table to add to
|
|
162
|
-
@param localeId string -- The localeId to use
|
|
163
|
-
@param json string -- The json to add with
|
|
164
|
-
@param tableName string -- Used for source
|
|
165
|
-
]=]
|
|
166
|
-
function JsonToLocalizationTable.addJsonToTable(localizationTable, localeId, json, tableName)
|
|
167
|
-
assert(type(tableName) == "string", "Bad tableName")
|
|
168
|
-
|
|
169
|
-
local decodedTable = HttpService:JSONDecode(json)
|
|
170
|
-
recurseAdd(localizationTable, localeId, "", decodedTable, tableName)
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
return JsonToLocalizationTable
|
|
174
|
-
|