/
Locale.lua
530 lines (493 loc) · 14.5 KB
/
Locale.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
--[[
Title: A simple lib for managing localization completely in the scripting interface.
Author(s): LiXizhi
Date: 2006/11/24
Desc: I have referred to Locale-2.0 by ckknight
Use Lib:
-------------------------------------------------------
-- in localization file KidsUI-enUS.lua, we can build strings like these
NPL.load("(gl)script/ide/Locale.lua");
local L = CommonCtrl.Locale:new("KidsUI");
L:RegisterTranslations("enUS", function() return {
-- Bindings
["Hello!"] = true,
["Greating"] = "hi, there!",
} end);
-- in localization file KidsUI-zhCN.lua, we can build strings like these
NPL.load("(gl)script/ide/Locale.lua");
local L = CommonCtrl.Locale:new("KidsUI");
L:RegisterTranslations("zhCN", function() return {
-- Bindings
["Hello!"] = "你好!",
["Greating"] = "你好啊!",
} end);
-- when application starts, load all available local files, see "script/lang.lua"
NPL.load("(gl)script/ide/Locale.lua");
CommonCtrl.Locale.AutoLoadFile("KidsUI-zhCN.lua");
CommonCtrl.Locale.AutoLoadFile("KidsUI-enUS.lua");
-- GNU gettext way
local L = CommonCtrl.Locale:new("KidsUI");
L:SetStrictness("nocheck");
L:Reset();
local t = L:GetTranslationTable();
t["Hello"] = "你好"; -- add all entries
-- in normal scripts, we can use.
local L = CommonCtrl.Locale:new("KidsUI");
local str = L("Hello!")..L"Greating"
-------------------------------------------------------
]]
-- common library
NPL.load("(gl)script/ide/common_control.lua");
NPL.load("(gl)script/ide/commonlib.lua");
-- whether write error log
local enable_log = false;
-- whether enable locale globally.
local enable_locale = true;
-- used for printing errors
local function print_error(self, ...)
if(enable_log) then
commonlib.warning(self, ...);
end
end
-- define a new control in the common control libary
local Locale = {
registry = {},
error = "",
print_error = print_error,
}
CommonCtrl.Locale = Locale;
commonlib.Locale = Locale;
-- whether to enable error log. log is disabled by default. however, one can enable it to debug missing locale.
function Locale.EnableLog(bEnable)
enable_log = bEnable;
end
-- this will enable/disable locale string look globally. If false, querying a string will always return the string itself without checking.
function Locale.EnableLocale(bEnable)
enable_locale = bEnable;
end
-- only load the file if the locale in the file matches the current locale.
-- the file name must be in the format X[lang].lua, where [lang] is the locale string such as enUS.
-- if [lang] is not known locale, it will be discarded.
function Locale.AutoLoadFile(file)
-- get locale from file name
local len = string.len(file);
if(len > 8) then
local lang = string.sub(file, len-7, len-4);
if(lang == ParaEngine.GetLocale()) then
NPL.load("(gl)"..file);
end
end
end
-- get a given translation by its name. it will return nil if does not exist.
function Locale:GetByName(name)
return self.registry[name];
end
-- create or get a given translation.
function Locale:new(name)
if self.registry[name] then
return self.registry[name]
end
local o = setmetatable({}, {
__index = self.prototype,
__call = self.prototype.GetTranslation,
__tostring = function(self)
return "Locale(" .. name .. ")"
end
})
Locale.registry[name] = o
return o
end
setmetatable(Locale, { __call = Locale.new })
Locale.prototype = {
print_error = print_error,
}
Locale.prototype.class = Locale
function Locale.prototype:EnableDebugging()
if self.baseTranslations then
log("Cannot enable debugging after a translation has been registered.")
end
self.debugging = true
end
function Locale.prototype:RegisterTranslations(locale, func)
if self.baseTranslations and ParaEngine.GetLocale() ~= locale then
if self.debugging then
local t = func()
func = nil
if type(t) ~= "table" then
log("Bad argument #3 to `RegisterTranslation'. function did not return a table. (expected table)")
end
self.translationTables[locale] = t
t = nil
end
func = nil
collectgarbage()
return
end
local t = func()
func = nil
if type(t) ~= "table" then
log("Bad argument #3 to `RegisterTranslation'. function did not return a table. (expected table)")
end
self.translations = t
if not self.baseTranslations then
self.baseTranslations = t
self.baseLocale = locale
for key,value in pairs(self.baseTranslations) do
if value == true then
self.baseTranslations[key] = key
end
end
else
for key, value in pairs(self.translations) do
if not self.baseTranslations[key] then
self:print_error("Improper translation exists. %q is likely misspelled for locale %q.", key, locale)
elseif value == true then
self:print_error("Can only accept true as a value on the base locale. %q is the base locale, %q is not.", self.baseLocale, locale)
end
end
end
if self.debugging then
if not self.translationTables then
self.translationTables = {}
end
self.translationTables[locale] = t
end
t = nil
collectgarbage()
end
-- @param strictness: "strict", "nocheck", nil: default to nil. "nocheck" is recommended for it just returned the text
function Locale.prototype:SetStrictness(strictness)
local mt = getmetatable(self)
if not mt then
self:print_error("Cannot call `SetStrictness' without a metatable.")
end
if strictness == "strict" then
mt.__call = self.GetTranslationStrict
elseif strictness == "nocheck" then
mt.__call = self.GetTranslationNoCheck
else
mt.__call = self.GetTranslation
end
end
-- this function is mostly used by "nocheck" translation to register a new translation
function Locale.prototype:GetTranslationTable()
return self.translations;
end
-- clear all translations. this function may be called when language is changed at runtime.
function Locale.prototype:Reset()
self.baseTranslations = nil;
self.translations = {};
end
-- this is the fastest way to retrieve a translation for text.
-- it gives no errors, just return the translated text or text if not found.
function Locale.prototype:GetTranslationNoCheck(text)
return self.translations[text] or text;
end
function Locale.prototype:GetTranslationStrict(text, sublevel)
if(not enable_locale) then
return text;
end
if not self.translations then
self:print_error("No translations registered")
end
if sublevel then
local t = self.translations[text]
if type(t) ~= "table" then
if type(self.baseTranslations[text]) == "table" then
self:print_error("%q::%q has not been translated into %q", text, sublevel, locale)
--return Locale.error
return text
else
self:print_error("Translation for %q::%q does not exist", text, sublevel)
--return Locale.error
return text
end
end
local translation = t[sublevel]
if type(translation) ~= "string" then
if type(self.baseTranslations[text]) == "table" then
if type(self.baseTranslations[text][sublevel]) == "string" then
self:print_error("%q::%q has not been translated into %q", text, sublevel, locale)
--return Locale.error
return text
else
self:print_error("Translation for %q::%q does not exist", text, sublevel)
--return Locale.error
return text
end
else
self:print_error("Translation for %q::%q does not exist", text, sublevel)
--return Locale.error
return text
end
end
return translation
end
local translation = self.translations[text]
if type(translation) ~= "string" then
if type(self.baseTranslations[text]) == "string" then
self:print_error("%q has not been translated into %q", text, locale)
--return Locale.error
return text
else
self:print_error("Translation for %q does not exist", text)
--return Locale.error
return text
end
end
return translation
end
function Locale.prototype:GetTranslation(text, sublevel)
if(not enable_locale) then
return text;
end
if(not self.translations) then
self:print_error("Translation tables does not exist for %s", text)
return text;
end
if sublevel then
local t = self.translations[text]
if type(t) == "table" then
local translation = t[sublevel]
if type(translation) == "string" then
return translation
else
t = self.baseTranslations[text]
if type(t) ~= "table" then
self:print_error("Translation table %q does not exist", text)
--return Locale.error
return text
end
translation = t[sublevel]
if type(translation) ~= "string" then
self:print_error("Translation for %q::%q does not exist", text, sublevel)
--return Locale.error
return text
end
return translation
end
else
t = self.baseTranslations[text]
if type(t) ~= "table" then
self:print_error("Translation table %q does not exist", text)
--return Locale.error
return text
end
local translation = t[sublevel]
if type(translation) ~= "string" then
self:print_error("Translation for %q::%q does not exist", text, sublevel)
--return Locale.error
return text
end
return translation
end
end
local translation = self.translations[text]
if type(translation) == "string" then
return translation
else
translation = self.baseTranslations[text]
if type(translation) ~= "string" then
self:print_error("Translation for %q does not exist", text)
--return Locale.error
return text
end
return translation
end
end
local function initReverse(self)
self.reverseTranslations = {}
local alpha = self.translations
local bravo = self.reverseTranslations
for base, localized in pairs(alpha) do
bravo[localized] = base
end
end
function Locale.prototype:GetReverseTranslation(text)
if not self.reverseTranslations then
initReverse(self)
end
local translation = self.reverseTranslations[text]
if type(translation) ~= "string" then
self:print_error("Reverse translation for %q does not exist", text)
--return Locale.error
return text
end
return translation
end
function Locale.prototype:GetIterator()
Locale.assert(self, self.translations, "No translations registered")
return pairs(self.translations)
end
function Locale.prototype:GetReverseIterator()
Locale.assert(self, self.translations, "No translations registered")
if not self.reverseTranslations then
initReverse(self)
end
return pairs(self.reverseTranslations)
end
function Locale.prototype:HasTranslation(text, sublevel)
if(self.translations) then
if sublevel then
return type(self.translations[text]) == "table" and self.translations[text][sublevel] and true
end
return self.translations[text] and true
end
end
function Locale.prototype:HasReverseTranslation(text)
if not self.reverseTranslations then
initReverse(self)
end
return self.reverseTranslations[text] and true
end
function Locale.prototype:GetTableStrict(key, key2)
if key2 then
local t = self.translations[key]
if type(t) ~= "table" then
if type(self.baseTranslations[key]) == "table" then
self:print_error("%q::%q has not been translated into %q", key, key2, locale)
return
else
self:print_error("Translation table %q::%q does not exist", key, key2)
return
end
end
local translation = t[key2]
if type(translation) ~= "table" then
if type(self.baseTranslations[key]) == "table" then
if type(self.baseTranslations[key][key2]) == "table" then
self:print_error("%q::%q has not been translated into %q", key, key2, locale)
return
else
self:print_error("Translation table %q::%q does not exist", key, key2)
return
end
else
self:print_error("Translation table %q::%q does not exist", key, key2)
return
end
end
return translation
end
local translation = self.translations[key]
if type(translation) ~= "table" then
if type(self.baseTranslations[key]) == "table" then
self:print_error("%q has not been translated into %q", key, locale)
return
else
self:print_error("Translation table %q does not exist", key)
return
end
end
return translation
end
function Locale.prototype:GetTable(key, key2)
if key2 then
local t = self.translations[key]
if type(t) == "table" then
local translation = t[key2]
if type(translation) == "table" then
return translation
else
t = self.baseTranslations[key]
if type(t) ~= "table" then
self:print_error("Translation table %q does not exist", key)
return
end
translation = t[key2]
if type(translation) ~= "table" then
self:print_error("Translation table %q::%q does not exist", key, key2)
return
end
return translation
end
else
t = self.baseTranslations[key]
if type(t) ~= "table" then
self:print_error("Translation table %q does not exist", key)
return
end
local translation = t[key2]
if type(translation) ~= "table" then
self:print_error("Translation table %q::%q does not exist", key, key2)
return
end
return translation
end
end
local translation = self.translations[key]
if type(translation) == "table" then
return translation
else
translation = self.baseTranslations[key]
if type(translation) ~= "table" then
self:print_error("Translation table %q does not exist", key)
return
end
return translation
end
end
function Locale.prototype:Debug()
if not self.debugging then
return
end
local words = {}
local locales = {"enUS", "deDE", "frFR", "zhCN", "zhTW", "koKR"}
local localizations = {}
log("--- Locale Debug ---")
for _,locale in ipairs(locales) do
if not self.translationTables[locale] then
log(string.format("Locale %q not found", locale))
else
localizations[locale] = self.translationTables[locale]
end
end
local localeDebug = {}
for locale, localization in pairs(localizations) do
localeDebug[locale] = {}
for word in pairs(localization) do
if type(localization[word]) == "table" then
if type(words[word]) ~= "table" then
words[word] = {}
end
for bit in pairs(localization[word]) do
if type(localization[word][bit]) == "string" then
words[word][bit] = true
end
end
elseif type(localization[word]) == "string" then
words[word] = true
end
end
end
for word in pairs(words) do
if type(words[word]) == "table" then
for bit in pairs(words[word]) do
for locale, localization in pairs(localizations) do
if not localization[word] or not localization[word][bit] then
localeDebug[locale][word .. "::" .. bit] = true
end
end
end
else
for locale, localization in pairs(localizations) do
if not localization[word] then
localeDebug[locale][word] = true
end
end
end
end
for locale, t in pairs(localeDebug) do
if not next(t) then
log(string.format("Locale %q complete", locale))
else
log(string.format("Locale %q missing:", locale))
for word in pairs(t) do
log(string.format(" %q", word))
end
end
end
log("--- End Locale Debug ---")
end
-- set global
L = CommonCtrl.Locale("IDE");