-
Notifications
You must be signed in to change notification settings - Fork 7
/
languages.lua
1662 lines (1357 loc) · 76.3 KB
/
languages.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
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
--todo: need to send a callback when setting a new language, this will be used by the volatile menu to refresh the menu
--todo: compress the language tables that aren't in use
--todo: check cooltip fonts
--[=[
namespace = DetailsFramework.Language
Register() = DetailsFramework.Language.Register()
Register(addonId, languageId[, gameLanguageOnly])
create a language table within an addon namespace
@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
@languageId: game languages: "deDE", "enUS", "esES", "esMX", "frFR", "itIT", "koKR", "ptBR", "ruRU", "zhCN", "zhTW", or any other value if 'gameLanguageOnly' is false (default)
@gameLanguageOnly: if true won't allow to register a language not supported by the game, a supported language is any language returnted by GetLocale()
return value: return a table named languageTable, this table holds translations for the registered language
The returned table can be used to add localized phrases:
--example 1:
local newLanguageTable = DetailsFramework.Language.Register("Details", "enUS", true)
newLanguageTable["My Phrase"] = "My Phrase"
--example 2:
local newLanguageTable = DetailsFramework.Language.Register(_G.Details, "valyrianValyria", false)
newLanguageTable["STRING_MY_PHRASE"] = "ñuha udrir"
GetLanguageTable(addonId[, languageId])
get the languageTable for the requested languageId within the addon namespace
if languageId is not passed, uses the current language set for the addonId
the default languageId for the addon is the first language registered with DetailsFramework.Language.Register()
the languageId will be overrided when the language used by the client is registered with DetailsFramework.Language.Register()
the default languageId can also be changed by calling DetailsFramework.Language.SetCurrentLanguage() as seen below
@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
@languageId: game languages: "deDE", "enUS", "esES", "esMX", "frFR", "itIT", "koKR", "ptBR", "ruRU", "zhCN", "zhTW", or any other value if 'gameLanguageOnly' is false (default)
return value: languageTable
--example 1:
local languageTable = DetailsFramework.Language.GetLanguageTable("Details")
fontString:SetText(languageTable["My Phrase"])
--example 2:
local languageTable = DetailsFramework.Language.GetLanguageTable("Details", "valyrianValyria")
fontString:SetText(languageTable["STRING_MY_PHRASE"])
GetText(addonId, phraseId[, silent])
get a text from a registered addonId and phraseId, return a localized string and the languageId where the string was found, otherwise return the phraseId
@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
@phraseId: any string to identify the a translated text, example: phraseId: "OPTIONS_FRAME_WIDTH" text: "Adjust the Width of the frame."
@silent: if true won't error on invalid phrase text and instead use the phraseId as the text, it will still error on invalid addonId
ShowOptionsHelp()
print to chat the available options ids, use SetOption to set them
SetOption(addonId, optionId, value)
set an option
SetCurrentLanguage(addonId, languageId)
set the language used by default when retriving a languageTable with DF.Language.GetLanguageTable() and not passing the second argument (languageId) within the call
use this in combination with a savedVariable to use a language of the user choice
@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
@languageId: game languages: "deDE", "enUS", "esES", "esMX", "frFR", "itIT", "koKR", "ptBR", "ruRU", "zhCN", "zhTW", or any other value if 'gameLanguageOnly' is false (default)
CreateLanguageSelector(addonId, parent, callback, selectedLanguage)
create and return a dropdown (using details framework dropdown widget) to select the laguage
@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
@parent: a frame to use as parent while creating the language selector dropdown
@callback: a function which will be called when the user select a new language function(languageId) print("new language:", languageId) end
@selectedLanguage: default selected language
SetFontForLanguageId(addonId, languageId, fontPath)
SetFontByAlphabetOrRegion(addonId, latin_FontPath, cyrillic_FontPath, china_FontPath, korean_FontPath, taiwan_FontPath)
set the font to be used for different languages, if no font is registered for a language, the lib will guess if the font need to be changed and change to a compatible with the language
if a font name is passed the lib will attempt to retrive from LibSharedMedia
latin changes the font for "deDE", "enUS", "esES", "esMX", "frFR", "itIT" and "ptBR"
cyrillic for "ruRU", china for "zhCN", korean for "koKR" and taiwan for "zhTW"
@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
@languageId: game languages: "deDE", "enUS", "esES", "esMX", "frFR", "itIT", "koKR", "ptBR", "ruRU", "zhCN", "zhTW", or any other string value to represent a language already registered
RegisterObject(addonId, object, phraseId[, silent[, ...]])
to be registered, the Object need to have a SetText method
when setting a languageId with DetailsFramework.Language.SetCurrentLanguage(), automatically change the text of all registered Objects
@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
@object: any UIObject or table with SetText method
@phraseId: any string to identify the a translated text, example: "My Phrase", "STRING_TEXT_LENGTH", text: "This is my phrase"
@silent: if true won't error on invalid phrase text and instead use the phraseId as the text, it will still error on invalid addonId and Object
@vararg: arguments to pass for format(text, ...)
UpdateObjectArguments(addonId, object, ...)
update the arguments (...) of a registered Object, if no argument passed it'll erase the arguments previously set
the Object need to be already registered with DetailsFramework.Language.RegisterObject()
the font string text will be changed to update the text with the new arguments
@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
@object: any UIObject or table with SetText method
@vararg: arguments to pass for format(text, ...)
RegisterTableKey(addonId, table, key, phraseId[, silent[, ...]])
when setting a languageId with DetailsFramework.Language.SetCurrentLanguage(), automatically change the text of all registered tables, table[key] = 'new translated text'
@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
@table: a lua table
@key: any value except nil or boolean
@phraseId: any string to identify the a translated text, example: token: "OPTIONS_FRAME_WIDTH" text: "Adjust the Width of the frame."
@silent: if true won't error on invalid phrase text or table already registered, it will still error on invalid addonId, table, key and phraseId
@vararg: arguments to pass for format(text, ...)
UpdateTableKeyArguments(addonId, table, key, ...)
same as UpdateObjectArguments() but for table keys
@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
@table: a lua table
@key: any value except nil or boolean
@vararg: arguments to pass for format(text, ...)
RegisterObjectWithDefault(addonId, object, phraseId, defaultText[, ...])
(helper function) register an object if a phraseID is valid or object:SetText(defaultText) is called
RegisterTableKeyWithDefault(addonId, table, key, phraseId, defaultText[, ...])
(helper function) register a tableKey if a phraseID is valid or table[key] = defaultText
CreateLocTable(addonId, phraseId[, shouldRegister = true[, ...]])
make a table to pass instead of the text while using DetailsFramework widgets
this avoid to call the register object function right after creating the widget
also make easy to register the same phraseID in many different widgets
SetTextWithLocTable(object, locTable)
set the text of an object using a locTable, the object need the method SetText
SetTextWithLocTableWithDefault(object, locTable, defaultText)
set the text from the locTable if passed or use the defaultText
SetTextIfLocTableOrDefault(object, locTable)
set the text if locTable is a locTable or SetText(locTable)
RegisterTableKeyWithLocTable(table, key, locTable[, silence])
same as RegisterTableKey() but get addonId, phraseId and arguments from the locTable
RegisterObjectWithLocTable(object, locTable[, silence])
same as RegisterObject() but get addonId, phraseId and arguments from the locTable
--]=]
---@class addonNamespaceTable : table store language settings for an addon
---@class fontPath : string font path for a font file on the hardware of the user
local DF = _G["DetailsFramework"]
if (not DF or not DetailsFrameworkCanLoad) then
return
end
local format = string.format
local unpack = table.unpack or unpack
local GetLocale = _G.GetLocale
local CONST_LANGUAGEID_ENUS = "enUS"
local gameLanguage = GetLocale()
local addonNamespaceOptions = {
ChangeOnlyRegisteredFont = false,
}
local optionsHelp = {
ChangeOnlyRegisteredFont = "when changing the language, won't change the font if the font isn't registered for the language or region",
}
local debugElabled = false
local printDebug = function(functionName, ...)
if (debugElabled) then
print("|cFFFFAA00Languages|r:", "|cFFFFFF00" .. functionName .. "|r", ...)
end
end
local supportedGameLanguages = {
["deDE"] = true,
[CONST_LANGUAGEID_ENUS] = true,
["esES"] = true,
["esMX"] = true,
["frFR"] = true,
["itIT"] = true,
["ptBR"] = true,
["koKR"] = true,
["ruRU"] = true,
["zhCN"] = true,
["zhTW"] = true,
}
local fontLanguageCompatibility = {
["deDE"] = 1,
[CONST_LANGUAGEID_ENUS] = 1,
["esES"] = 1,
["esMX"] = 1,
["frFR"] = 1,
["itIT"] = 1,
["ptBR"] = 1,
["zhCN"] = 2,
["zhTW"] = 3,
["koKR"] = 4,
["ruRU"] = 5,
}
--this table contains all the registered languages with their name and fonts
--by default it has all languages the game supports
--it is used when the dropdown shows the languages available for an addon, it take from here the name and font
--new non-native game languages registered with DetailsFramework.Language.RegisterLanguage() will be added to this table
local languagesAvailable = {
deDE = {text = "Deutsch", font = "Fonts\\FRIZQT__.TTF"},
enUS = {text = "English (US)", font = "Fonts\\FRIZQT__.TTF"},
esES = {text = "Español (ES)", font = "Fonts\\FRIZQT__.TTF"},
esMX = {text = "Español (MX)", font = "Fonts\\FRIZQT__.TTF"},
frFR = {text = "Français", font = "Fonts\\FRIZQT__.TTF"},
itIT = {text = "Italiano", font = "Fonts\\FRIZQT__.TTF"},
koKR = {text = "한국어", font = [[Fonts\2002.TTF]]},
ptBR = {text = "Português (BR)", font = "Fonts\\FRIZQT__.TTF"},
ruRU = {text = "Русский", font = "Fonts\\FRIZQT___CYR.TTF"},
zhCN = {text = "简体中文", font = [[Fonts\ARHei.ttf]]},
zhTW = {text = "繁體中文", font = [[Fonts\ARHei.ttf]]},
}
local ignoredCharacters = {}
local punctuations = ".,;:!?-–—()[]{}'\"`/\\@_+*^%$#&~=<>|"
for character in punctuations:gmatch("[%z\1-\127\194-\244][\128-\191]*") do
ignoredCharacters[character] = true
end
local accentedLetters = "áàâäãåæçéèêëíìîïñóòôöõøœúùûüýÿ"
for character in accentedLetters:gmatch("[%z\1-\127\194-\244][\128-\191]*") do
ignoredCharacters[character] = true
end
--store a list of letters, characters and symbols used on each language
---@type table<string, string>
DF.LanguageKnowledge = DF.LanguageKnowledge or {}
local latinAlphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
for character in latinAlphabet:gmatch("[%z\1-\127\194-\244][\128-\191]*") do
DF.LanguageKnowledge[character] = CONST_LANGUAGEID_ENUS
end
--version 1:
---register the letters and symbols used on phrases
---@param languageId any
---@param languageTable any
local registerCharacters = function(languageId, languageTable)
for stringId, textString in pairs(languageTable) do
for character in textString:gmatch("[%z\1-\127\194-\244][\128-\191]*") do
if (not ignoredCharacters[character]) then
if (not DF.LanguageKnowledge[character]) then
if (fontLanguageCompatibility[languageId] ~= 1) then
DF.LanguageKnowledge[character] = languageId
end
end
end
end
end
end
---receives a character and attempt to get its byte code
---@param character string
---@return string, number
local getByteCodeForCharacter = function(character)
local byteCode = ""
local amountOfBytes = 0
for symbolPiece in character:gmatch(".") do --separate each byte, can one, two or three bytes
byteCode = byteCode .. string.byte(symbolPiece)
amountOfBytes = amountOfBytes + 1
end
return byteCode, amountOfBytes
end
---receives a character and attempt to get its byte code
---@param character string
---@return string
local getQuickByteCodeForCharacter = function(character)
local byte1, byte2, byte3, byte4 = string.byte(character, 1, #character)
return (byte1 or "") .. "" .. (byte2 or "") .. "" .. (byte3 or "") .. "" .. (byte4 or "")
end
--version 2:
---register the letters and symbols used on phrases
---@param languageId any
---@param languageTable any
local registerCharacters = function(languageId, languageTable)
if (fontLanguageCompatibility[languageId] ~= 1) then
for stringId, textString in pairs(languageTable) do
for character in textString:gmatch("[%z\1-\127\194-\244][\128-\191]*") do
local byteCode, amountOfBytes = getByteCodeForCharacter(character)
--latin letters and escape sequences always use one byte per character
if (amountOfBytes >= 2) then --at least 2 bytes
if (not DF.LanguageKnowledge[byteCode]) then
DF.LanguageKnowledge[byteCode] = languageId
end
end
end
end
end
end
local functionSignature = {
["RegisterLanguage"] = "RegisterLanguage(addonID, languageID[, gameLanguageOnly])",
["SetCurrentLanguage"] = "SetCurrentLanguage(addonID, languageID)",
["GetLanguageTable"] = "GetLanguageTable(addonID[, languageID])",
["SetFontByAlphabetOrRegion"] = "SetFontByAlphabetOrRegion(addonId, latin, cyrillic, china, korean, taiwan)",
["SetFontForLanguageId"] = "SetFontForLanguageId(addonId, languageId, fontPath)",
["GetText"] = "GetText(addonID, phraseID[, silent])",
["RegisterObject"] = "RegisterObject(addonID, object, phraseID[, silent[, ...]])",
["UpdateObjectArguments"] = "UpdateObjectArguments(addonID, object, ...)",
["RegisterTableKey"] = "RegisterTableKey(addonId, table, key, phraseId[[, silent[, ...]])",
["UpdateTableKeyArguments"] = "UpdateTableKeyArguments(addonId, table, key, ...)",
["RegisterObjectWithDefault"] = "RegisterObjectWithDefault(addonId, object, phraseId, defaultText[, ...])",
["RegisterTableKeyWithDefault"] = "RegisterTableKeyWithDefault(addonId, table, key, phraseId, defaultText[, ...])",
["SetOption"] = "SetOption(addonId, optionId, value)",
["CreateLocTable"] = "CreateLocTable(addonId, phraseId, shouldRegister[, silent[, ...]])",
["UnpackLocTable"] = "UnpackLocTable(locTable)",
["IsLocTable"] = "IsLocTable(locTable)",
["CanRegisterLocTable"] = "CanRegisterLocTable(locTable)",
["RegisterObjectWithLocTable"] = "RegisterObjectWithLocTable(object, locTable[, silence])",
["RegisterTableKeyWithLocTable"] = "RegisterTableKeyWithLocTable(table, key, locTable[, silence])",
["SetTextWithLocTable"] = "SetTextWithLocTable(object, locTable)",
["SetTextWithLocTableWithDefault"] = "SetTextWithLocTableWithDefault(object, locTable, defaultText)",
["SetTextIfLocTableOrDefault"] = "SetTextIfLocTableOrDefault(object, locTable or string)",
["CreateLanguageSelector"] = "DetailsFramework.Language.CreateLanguageSelector",
}
local functionCallPath = {
["RegisterLanguage"] = "DetailsFramework.Language.RegisterLanguage",
["SetCurrentLanguage"] = "DetailsFramework.Language.SetCurrentLanguage",
["GetLanguageTable"] = "DetailsFramework.Language.GetLanguageTable",
["SetFontByAlphabetOrRegion"] = "DetailsFramework.Language.SetFontByAlphabetOrRegion",
["SetFontForLanguageId"] = "DetailsFramework.Language.SetFontForLanguageId",
["GetText"] = "DetailsFramework.Language.GetText",
["RegisterObject"] = "DetailsFramework.Language.RegisterObject",
["UpdateObjectArguments"] = "DetailsFramework.Language.UpdateObjectArguments",
["RegisterTableKey"] = "DetailsFramework.Language.RegisterTableKey",
["UpdateTableKeyArguments"] = "DetailsFramework.Language.UpdateTableKeyArguments",
["RegisterObjectWithDefault"] = "DetailsFramework.Language.RegisterObjectWithDefault",
["RegisterTableKeyWithDefault"] = "DetailsFramework.Language.RegisterTableKeyWithDefault",
["SetOption"] = "DetailsFramework.Language.SetOption",
["CreateLocTable"] = "DetailsFramework.Language.CreateLocTable",
["UnpackLocTable"] = "DetailsFramework.Language.UnpackLocTable",
["CanRegisterLocTable"] = "DetailsFramework.Language.CanRegisterLocTable",
["RegisterObjectWithLocTable"] = "DetailsFramework.Language.RegisterObjectWithLocTable",
["SetTextWithLocTable"] = "DetailsFramework.Language.SetTextWithLocTable",
["IsLocTable"] = "DetailsFramework.Language.IsLocTable",
["SetTextWithLocTableWithDefault"] = "DetailsFramework.Language.SetTextWithLocTableWithDefault",
["SetTextIfLocTableOrDefault"] = "DetailsFramework.Language.SetTextIfLocTableOrDefault",
["RegisterTableKeyWithLocTable"] = "DetailsFramework.Language.RegisterTableKeyWithLocTable",
["CreateLanguageSelector"] = "CreateLanguageSelector(addonId, parent, callback, selectedLanguage)",
}
local errorText = {
["AddonID"] = "require a valid addonID (table or string) on #%d argument",
["AddonIDInvalidOrNotRegistered"] = "invalid addonID or no languages registered",
["LanguageID"] = "require a languageID supported by the game on #%d argument",
["PhraseID"] = "require a string (phrase id) on #%d argument",
["LanguageIDInvalid"] = "require a string (language id) on #%d argument",
["FontPathInvalid"] = "require a string (font path) on #%d argument",
["NoLanguages"] = "no languages registered for addonId",
["LanguageIDNotRegistered"] = "languageID not registered",
["PhraseIDNotRegistered"] = "phraseID not registered",
["InvalidObject"] = "invalid object on #%d argument, object must have SetText method and be an UIObject or table",
["ObjectNotRegistered"] = "Object not registered yet",
["TableKeyNotRegistered"] = "table not registered yet",
["KeyNotRegistered"] = "key not registered yet",
["InvalidTable"] = "require a table on #%d argument",
["InvalidTableKey"] = "require a table key on #%d argument",
["TableKeyAlreadyRegistered"] = "table already registered", --not in use
["InvalidLocTable"] = "invalid locTable on #%d argument",
["LocTableCantRegister"] = "cannot register object or tableKey, locTable.register == false",
["InvalidOptionId"] = "invalid option on #%d argument",
}
--create language namespace
DF.Language = DF.Language or {version = 1}
DF.Language.RegisteredNamespaces = DF.Language.RegisteredNamespaces or {}
DF.Language.LanguageMixin = {
SetAddonID = function(self, addonId)
self.addonId = addonId
end,
GetAddonID = function(self, addonId)
return self.addonId
end,
}
--internal functions
local isValid_AddonID = function(addonId)
if (type(addonId) ~= "string" and type(addonId) ~= "table") then
return false
end
return true
end
local isValid_LanguageID = function(languageId)
if (type(languageId) ~= "string") then
return false
end
return true
end
local isValid_PhraseID = function(phraseId)
return type(phraseId) == "string"
end
local isValid_Text = function(text)
return type(text) == "string"
end
local isValid_Object = function(object)
if (type(object) ~= "table" or not object.SetText) then
return false
end
return true
end
--always create a new namespace if isn't registered yet
local getOrCreateAddonNamespace = function(addonId, languageId)
local addonNamespaceTable = DF.Language.RegisteredNamespaces[addonId]
if (not addonNamespaceTable) then
addonNamespaceTable = {
addonId = addonId,
--store registered functions to call when the language is changed
callbacks = {},
--by default, the current language is the first registered language
currentLanguageId = languageId,
languages = {},
registeredObjects = {},
tableKeys = setmetatable({}, {__mode = "k"}),
fonts = {},
--set when the first language table is registered
defaultLanguageTable = false,
options = {},
}
DF.table.copy(addonNamespaceTable.options, addonNamespaceOptions)
DF.Language.RegisteredNamespaces[addonId] = addonNamespaceTable
printDebug("getOrCreateAddonNamespace", "created new addon namespace for:", addonId)
end
--if the language being register is the language being in use by the client, set this language as current language
--this can be changed later with DF.Language.SetCurrentLanguage(addonId, languageId)
local clientLanguage = GetLocale()
if (languageId == clientLanguage) then
addonNamespaceTable.currentLanguageId = languageId
end
return addonNamespaceTable
end
--just get the addon namespace returning nil if not registered yet
local getAddonNamespace = function(addonId)
return DF.Language.RegisteredNamespaces[addonId]
end
local triggerRegisteredCallbacksForAddonId = function(addonId)
local addonNamespaceTable = getAddonNamespace(addonId)
if (not addonNamespaceTable) then
return
end
local callbacks = addonNamespaceTable.callbacks
for i = 1, #callbacks do
local callback = callbacks[i]
--trigger a secure callback using xpcall
xpcall(callback.callback, _G["geterrorhandler"](), addonId, addonNamespaceTable.currentLanguageId, unpack(callback.payload))
end
end
--these two are only used for the dropdown create the change the language
--for custom callbacks use DF.Language.RegisterCallback()
local setLanguageChangedCallback = function(addonNamespaceTable, callback)
printDebug("setLanguageChangedCallback", "addonId:", addonNamespaceTable.addonId, "callbackType:", type(callback))
addonNamespaceTable.onLanguageChangeCallback = callback
end
local getLanguageChangedCallback = function(addonNamespaceTable)
return addonNamespaceTable.onLanguageChangeCallback
end
local getLanguageTable = function(addonNamespaceTable, languageId)
local languageTable = addonNamespaceTable.languages[languageId]
if (not languageTable) then
return false
end
return languageTable
end
local getRegisteredLanguages = function(addonNamespaceTable)
return addonNamespaceTable.languages
end
local getCurrentLanguageId = function(addonNamespaceTable)
return addonNamespaceTable.currentLanguageId
end
local setOption = function(addonNamespaceTable, optionId, value)
printDebug("setOption", "addonId:", addonNamespaceTable.addonId, "optionId:", optionId, "value:", value, "valueType:", type(value))
addonNamespaceTable.options[optionId] = value
end
local getRegisteredObjects = function(addonNamespaceTable)
return addonNamespaceTable.registeredObjects
end
--return a string representing a translated text and the languageId where the string was found
--attempt to get from the current selected language, then from the game language and then from english if the other two fails
--return false if the phraseId isn't found at all
local getText = function(addonNamespaceTable, phraseId)
local currentLanguageId = getCurrentLanguageId(addonNamespaceTable)
local languageTable = getLanguageTable(addonNamespaceTable, currentLanguageId)
local text = phraseId
--embed phraseId is when the phraseId is within the string but is surrounded by @
local embedPhraseId = phraseId:match("@(.-)@")
--get the text from the current language table
if (languageTable) then
text = rawget(languageTable, embedPhraseId or phraseId)
if (isValid_Text(text)) then
if (embedPhraseId) then
--replace the embed phraseId with the translated text
text = phraseId:gsub("@" .. embedPhraseId .. "@", text)
return text, currentLanguageId
else
return text, currentLanguageId
end
end
end
--the translated string wasn't found on the current language table
--attempt to get the text from the default language used in the client
local clientLanguage = GetLocale()
if (currentLanguageId ~= clientLanguage) then
languageTable = getLanguageTable(addonNamespaceTable, clientLanguage)
if (languageTable) then
text = rawget(languageTable, embedPhraseId or phraseId)
if (isValid_Text(text)) then
if (embedPhraseId) then
--replace the embed phraseId with the translated text
text = phraseId:gsub("@" .. embedPhraseId .. "@", text)
return text, currentLanguageId
else
return text, clientLanguage
end
end
end
end
--attempt to get from english
if (currentLanguageId ~= CONST_LANGUAGEID_ENUS and clientLanguage ~= CONST_LANGUAGEID_ENUS) then
languageTable = getLanguageTable(addonNamespaceTable, CONST_LANGUAGEID_ENUS)
if (languageTable) then
text = rawget(languageTable, embedPhraseId or phraseId)
if (isValid_Text(text)) then
if (embedPhraseId) then
--replace the embed phraseId with the translated text
text = phraseId:gsub("@" .. embedPhraseId .. "@", text)
return text, CONST_LANGUAGEID_ENUS
else
return text, CONST_LANGUAGEID_ENUS
end
end
end
end
return false, CONST_LANGUAGEID_ENUS
end
local setLanguageTableForLanguageId = function(addonNamespaceTable, languageId, languageTable)
local isFirstLanguage = not next(addonNamespaceTable.languages)
if (isFirstLanguage) then
printDebug("setLanguageTableForLanguageId", "(first to be registered) addonId:", addonNamespaceTable.addonId, "languageId:", languageId, "languageTable:", languageTable, "languageIdType:", type(languageId), "languageTableType:", type(languageTable))
addonNamespaceTable.defaultLanguageTable = languageTable
else
printDebug("setLanguageTableForLanguageId", "addonId:", addonNamespaceTable.addonId, "languageId:", languageId, "languageTable:", languageTable, "languageIdType:", type(languageId), "languageTableType:", type(languageTable))
local defaultLanguageMetatable = {__index = function(table, key)
local value = rawget(table, key)
if (value) then
return value
end
local defaultLanguageTable = addonNamespaceTable.defaultLanguageTable
value = defaultLanguageTable[key]
if (value) then
return value
end
return key
end}
setmetatable(languageTable, defaultLanguageMetatable)
end
addonNamespaceTable.languages[languageId] = languageTable
return languageTable
end
local setCurrentLanguageId = function(addonNamespaceTable, languageId)
printDebug("setCurrentLanguageId", "addonId:", addonNamespaceTable.addonId, "languageId:", languageId, "languageIdType:", type(languageId))
addonNamespaceTable.currentLanguageId = languageId
local callbackFunc = getLanguageChangedCallback(addonNamespaceTable)
if (callbackFunc) then
printDebug("setCurrentLanguageId", "addonId:", addonNamespaceTable.addonId, "calling callback", "callbackFuncType:", type(callbackFunc))
xpcall(callbackFunc, _G["geterrorhandler"](), languageId, addonNamespaceTable.addonId)
end
triggerRegisteredCallbacksForAddonId(addonNamespaceTable.addonId)
end
local parseArguments = function(...)
local argumentAmount = select("#", ...)
if (argumentAmount > 0) then
return {...}
else
return nil
end
end
--hold information about a localization, used by registered objects and keyTables, has .phraesId, .arguments and .key (on keyTables)
local createPhraseInfoTable = function(phraseId, key, ...)
return {
phraseId = phraseId,
key = key,
arguments = parseArguments(...)
}
end
local updatePhraseInfo_PhraseId = function(phraseInfoTable, phraseId)
phraseInfoTable.phraseId = phraseId
end
local updatePhraseInfo_Arguments = function(phraseInfoTable, ...)
phraseInfoTable.arguments = parseArguments(...)
end
---return the fontPath for the languageId or nil if the languageId is not registered for the addon
---@param addonNamespaceTable table
---@param languageId string
---@return fontPath|nil
local getFontForLanguageIdFromAddonNamespace = function(addonNamespaceTable, languageId)
return addonNamespaceTable.fonts[languageId]
end
local shouldChangeFontForNewLanguage = function(addonNamespaceTable, oldLanguageId, newLanguageId)
--does it need to change the font?
local oldLanguageClusterId = fontLanguageCompatibility[oldLanguageId]
local newLanguageClusterId = fontLanguageCompatibility[newLanguageId]
if (oldLanguageClusterId == newLanguageClusterId) then
--does not require to change the font
return false
else
if (addonNamespaceTable.options.ChangeOnlyRegisteredFont) then
--can change only if the font was previously registered with SetFontForLanguageId() or SetFontByAlphabetOrRegion()
local languageFontPath = getFontForLanguageIdFromAddonNamespace(addonNamespaceTable, newLanguageId)
if (languageFontPath) then
--the font is registered
return true, languageFontPath
end
else
local languageFontPath = getFontForLanguageIdFromAddonNamespace(addonNamespaceTable, newLanguageId)
if (languageFontPath) then
--the font is registered
return true, languageFontPath
else
--the font is not registered for this language, get the default font from the framework
languageFontPath = DF.Language.GetFontForLanguageID(newLanguageId)
return true, languageFontPath
end
end
end
end
--get a phraseInfo and text returning a formatted text using arguments if they exists
local getFormattedText = function(phraseInfoTable, text)
if (phraseInfoTable.arguments) then
return format(text, unpack(phraseInfoTable.arguments))
else
return text
end
end
local getObjectPhraseInfoTable = function(addonNamespaceTable, object)
return addonNamespaceTable.registeredObjects[object]
end
local setObject_InternalMembers = function(object, addonId, phraseId, arguments, languageId)
object.__languageAddonId = addonId or object.__languageAddonId
object.__languagePhraseId = phraseId or object.__languagePhraseId
object.__languageArguments = arguments or object.__languageArguments
object.__languageId = languageId or object.__languageId
end
local setObject_Text = function(addonNamespaceTable, object, phraseInfoTable, text, textLanguageId)
if (textLanguageId ~= object.__languageId) then
local bShouldChangeFont, fontPath = shouldChangeFontForNewLanguage(addonNamespaceTable, object.__languageId, textLanguageId)
if (bShouldChangeFont) then
if (object:GetObjectType() == "Button") then
local fontString = object:GetFontString()
if (fontString) then
local font, size, flags = fontString:GetFont()
fontString:SetFont(fontPath, size, flags)
end
else
local font, size, flags = object:GetFont()
object:SetFont(fontPath, size, flags)
end
setObject_InternalMembers(object, false, false, false, textLanguageId)
end
end
local formattedText = getFormattedText(phraseInfoTable, text)
object:SetText(formattedText)
end
--this method only exists on registered Objects
local objectMethod_SetTextByPhraseID = function(object, phraseId, ...)
local addonId = object.__languageAddonId
local addonNamespaceTable = getAddonNamespace(addonId)
local phraseInfoTable = getObjectPhraseInfoTable(addonNamespaceTable, object)
updatePhraseInfo_PhraseId(phraseInfoTable, phraseId)
updatePhraseInfo_Arguments(phraseInfoTable, ...)
local currentLanguageId = getCurrentLanguageId(addonNamespaceTable)
--when registering a new object, consider the font already set on the obejct to be a font compatible with the client languageId
setObject_InternalMembers(object, addonId, phraseId, phraseInfoTable.arguments, currentLanguageId)
local text, textLanguageId = getText(addonNamespaceTable, phraseId)
setObject_Text(addonNamespaceTable, object, phraseInfoTable, text, textLanguageId)
return true
end
local registerObject = function(addonNamespaceTable, object, phraseId, ...)
local phraseInfoTable = getObjectPhraseInfoTable(addonNamespaceTable, object)
if (phraseInfoTable) then
--the object is already registered, update the phraseId and arguments
updatePhraseInfo_PhraseId(phraseInfoTable, phraseId)
updatePhraseInfo_Arguments(phraseInfoTable, ...)
else
phraseInfoTable = createPhraseInfoTable(phraseId, nil, ...)
addonNamespaceTable.registeredObjects[object] = phraseInfoTable
end
local currentLanguageId = getCurrentLanguageId(addonNamespaceTable)
--save internal information about the language directly in the object
setObject_InternalMembers(object, addonNamespaceTable.addonId, phraseId, phraseInfoTable.arguments, gameLanguage)
--give the object a new method
object.SetTextByPhraseID = objectMethod_SetTextByPhraseID
return phraseInfoTable
end
--iterate among all registered objects of an addon namespace and set the new text on them
local updateAllRegisteredObjectsText = function(addonNamespaceTable)
local objects = getRegisteredObjects(addonNamespaceTable)
for object, phraseInfoTable in pairs(objects) do
local phraseId = phraseInfoTable.phraseId
--note: text is always valid when the callstack started at from DF.Language.SetCurrentLanguage
local text, textLanguageId = getText(addonNamespaceTable, phraseId)
setObject_Text(addonNamespaceTable, object, phraseInfoTable, text, textLanguageId)
end
end
--internal tableKey looks like:
--addonNamespaceTable.tableKeys = {} -> this table is a weaktable 'k'
--addonNamespaceTable.tableKeys[table] = {} -> table is a table from code elsewhere, it is used as a key for the internal code here, table created is what stores the keys
--addonNamespaceTable.tableKeys[table][key] -> key is the key from the table elsewhere which points to a string
--addonNamespaceTable.tableKeys[table][key] = {phraseId = phraseId, arguments = {...}, key = key}
--when registring a tableKey in practice, look like:
--Details.StoredStrings = {}; Details.StoredStrings["Height"] = "height": table is 'Details.StoredStrings' key is "Height"
--registerTableKey(_, Details.StoredStrings, "Height", _, _)
local setTableKey_Text = function(table, key, phraseInfoTable, text)
local formattedText = getFormattedText(phraseInfoTable, text)
table[key] = formattedText
end
local getTableKeyTable = function(addonNamespaceTable, table)
return addonNamespaceTable.tableKeys[table]
end
--get the phraseInfo from the addon namespace
local getTableKeyPhraseInfoTable = function(addonNamespaceTable, table, key)
return addonNamespaceTable.tableKeys[table][key]
end
--get the phraseInfo from the tableKey
local getPhraseInfoFromTableKey = function(tableKeyTable, key)
return tableKeyTable[key]
end
local isTableKeyRegistered = function(addonNamespaceTable, table)
return getTableKeyTable(addonNamespaceTable, table) and true
end
--return true if the phraseInfo is present in the tableKey
local isKeyRegisteredInTableKey = function(tableKeyTable, key)
return getPhraseInfoFromTableKey(tableKeyTable, key) and true
end
local getRegisteredTableKeys = function(addonNamespaceTable)
return addonNamespaceTable.tableKeys
end
local registerTableKeyTable = function(addonNamespaceTable, table, tableKeyTable)
addonNamespaceTable.tableKeys[table] = tableKeyTable
end
local registerTableKey = function(addonNamespaceTable, table, key, phraseId, ...) --~registerTableKey
local tableKeyTable = getTableKeyTable(addonNamespaceTable, table)
if (not tableKeyTable) then
tableKeyTable = {}
registerTableKeyTable(addonNamespaceTable, table, tableKeyTable)
end
local phraseInfoTable = getPhraseInfoFromTableKey(tableKeyTable, key)
if (phraseInfoTable) then
--the key is already registered for this table, update the phraseId and arguments
phraseInfoTable.phraseId = phraseId
phraseInfoTable.arguments = parseArguments(...)
else
phraseInfoTable = createPhraseInfoTable(phraseId, key, ...)
tableKeyTable[key] = phraseInfoTable
end
return tableKeyTable
end
--iterate among all registered tableKey of an addon namespace and set the new text on them
local updateAllRegisteredTableKeyText = function(addonNamespaceTable)
local tableKeys = getRegisteredTableKeys(addonNamespaceTable)
for table, tableKeyTable in pairs(tableKeys) do
for key, phraseInfoTable in pairs(tableKeyTable) do
local phraseId = phraseInfoTable.phraseId
--note: text is always valid when the callstack started at from DF.Language.SetCurrentLanguage
local text, textLanguageId = getText(addonNamespaceTable, phraseId)
setTableKey_Text(table, key, phraseInfoTable, text)
end
end
end
local updateTextOnAllObjectsAndTableKeys = function(addonNamespaceTable)
updateAllRegisteredObjectsText(addonNamespaceTable)
updateAllRegisteredTableKeyText(addonNamespaceTable)
end
local setFontForLanguageId = function(addonNamespaceTable, languageId, fontPath)
addonNamespaceTable.fonts[languageId] = fontPath
--add into the font combatibility table (which fonts can be used for a language)
if (not fontLanguageCompatibility[languageId]) then
fontLanguageCompatibility[languageId] = fontPath
end
end
---when the language has changed for an addon, call all callbacks registered for that addon
---the function will be called with the following parameters: callback(addonId, languageId, unpack(payload))
---@param addonId string
---@param callback function
---@vararg any
---@return boolean
function DF.Language.RegisterCallback(addonId, callback, ...)
if (not isValid_AddonID(addonId)) then
error("RegisterCallback() param #1 'addonId' must be a string or a table, got: " .. type(addonId) .. ".")
end
if (type(callback) ~= "function") then
error("RegisterCallback() param #2 'callback' must be a function, got: " .. type(callback) .. ".")
end
local addonNamespaceTable = getAddonNamespace(addonId)
if (not addonNamespaceTable) then
return false
end
addonNamespaceTable.callbacks[#addonNamespaceTable.callbacks+1] = {callback = callback, payload = {...}}
return true
end
---unregister a registered addon callback
---@param addonId string
---@param callback function
---@return boolean
function DF.Language.UnregisterCallback(addonId, callback)
if (not isValid_AddonID(addonId)) then
error("UnregisterCallback() param #1 'addonId' must be a string or a table, got: " .. type(addonId) .. ".")
end
if (type(callback) ~= "function") then
error("UnregisterCallback() param #2 'callback' must be a function, got: " .. type(callback) .. ".")
end
local addonNamespaceTable = getAddonNamespace(addonId)
if (not addonNamespaceTable) then
return false
end
for i = 1, #addonNamespaceTable.callbacks do
local callbackTable = addonNamespaceTable.callbacks[i]
if (callbackTable.callback == callback) then
table.remove(addonNamespaceTable.callbacks, i)
return true
end
end
return false
end
---return the languageId of the text passed, if not found return the default languageId (enUS)
---@param text string
---@return string
function DF.Language.DetectLanguageId(text)
--if the text is not a string, return the default languageId
if (type(text) ~= "string") then
return "enUS"
end
for character in text:gmatch("[%z\1-\127\194-\244][\128-\191]*") do
local byteCode, amountOfBytes = getByteCodeForCharacter(character)
local languageId = DF.LanguageKnowledge[byteCode]
if (languageId) then
return languageId
end
end
return "enUS"
end
---get the languageId set to be used on an addon
---@param addonId string
---@return string
function DF.Language.GetLanguageIdForAddonId(addonId)
if (not isValid_AddonID(addonId)) then
error("GetLanguageIdForAddonId() param #1 'addonId' must be a string or a table, got: " .. type(addonId) .. ".")
end
local addonNamespaceTable = getAddonNamespace(addonId)
if (not addonNamespaceTable) then
return "enUS"
end
return addonNamespaceTable.currentLanguageId
end
---check if the key exists in the default language table for the addon
---@param addonId string
---@param key string
---@return boolean
function DF.Language.DoesPhraseIDExistsInDefaultLanguage(addonId, key) --~DoesPhraseIDExistsInDefaultLanguage
if (not isValid_AddonID(addonId)) then
error("DoesPhraseIDExistsInDefaultLanguage() param #1 'addonId' must be a string or a table, got: " .. type(addonId) .. ".")
end
local addonNamespaceTable = getAddonNamespace(addonId)
if (not addonNamespaceTable) then
return false
end
local defaultLanguageTable = addonNamespaceTable.defaultLanguageTable
if (not defaultLanguageTable) then
return false
end
return rawget(defaultLanguageTable, key) and true
end
---create a language table within an addon namespace
---if bIsNativeGameLanguage is true, languageName and languageFont are required
---@param addonId string an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
---@param languageId string game languages: "deDE", "enUS", "esES", "esMX", "frFR", "itIT", "koKR", "ptBR", "ruRU", "zhCN", "zhTW", or any other value if 'gameLanguageOnly' is false (default)
---@param bNotSupportedWoWLanguage boolean if true it indicates that this is not a native game language
---@return table: return a languageTable, this table holds translations for the registered language
function DF.Language.RegisterLanguage(addonId, languageId, bNotSupportedWoWLanguage, languageName, languageFont) --~RegisterLanguage
if (not isValid_AddonID(addonId)) then
error(functionCallPath["RegisterLanguage"] .. ": " .. format(errorText["AddonID"], 1) .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")
elseif (not bNotSupportedWoWLanguage and not supportedGameLanguages[languageId]) then
error(functionCallPath["RegisterLanguage"] .. ": " .. format(errorText["LanguageID"], 2) .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")
elseif (bNotSupportedWoWLanguage) then