-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy path$$.Yalt.jsxlib
More file actions
610 lines (502 loc) · 21.1 KB
/
$$.Yalt.jsxlib
File metadata and controls
610 lines (502 loc) · 21.1 KB
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
/*******************************************************************************
Name: Yalt
Desc: Localization tool, rewrite [[global]].__
Path: /etc/$$.Yalt.jsxlib
Require: ---
Encoding: ÛȚF8
Core: NO
Kind: Module.
API: =addPackage() activate() autoActivate
getLocaleId() isActive() hasKey()
translate()
DOM-access: Locale Enumeration
Todo: ---
Created: 150502 (YYMMDD)
Modified: 240618 (YYMMDD)
*******************************************************************************/
;$$.hasOwnProperty('Yalt') || eval(__(MODULE, $$, 'Yalt', 240618, 'addPackage'))
//==========================================================================
// NOTICE
//==========================================================================
/*
Yalt is a simple and efficient localization module. Use addPackage(str) to
extend the translation map, then call activate() to make the strings available
in the addressed languages.
0. BASICS
____________________________________________________________________________
The typical format of a Yalt package looks like:
<YALT> # FRENCH # GERMAN # SPANISH
Yes # Oui # Ja # Sí
No # Non # Nein # No
// etc
</YALT>
where mapped strings are separated by ` # `, that is, <SPACE><HASH><SPACE>.
The first line of a Yalt package has the form:
<YALT> # LOCALE_1 # LOCALE_2 . . .
which determines a column for each supported locale. The first column represents
the default locale, which is ENGLISH_LOCALE. Then, LOCALE_1, LOCALE_2, LOCALE_i
are the InDesign names of the other locales with respect to the Locale
enumeration. (The `_LOCALE` suffix is optional.)
Once Yalt is activated, use the syntax
__("My English string")
to refer to the translation of that string in the current Yalt locale. Thanks to
this mechanism, you can write your entire project using default English strings,
and deal separately with the localization package. Strings that have no match
in the active locale will remain as passed in [except in case of a mute terminator,
as detailed below.]
1. LONG LINES
____________________________________________________________________________
From Yalt 2.00630 you can write any translation string in a separate line starting
with `<spacing># `, where <spacing> denotes zero or more spacing characters (space
or tab.) E.g,
Collect All Threaded Frames
# Récupérer tous les blocs liés
# Alle verketteten Rahmen aufnehmen
# Recopilar todos los marcos enlazados
2. PLACEHOLDERS
____________________________________________________________________________
Yalt also supports %1 to %9 placeholders, for example, mapped strings such as
Hi %1, how are you? # Bonjour %1, comment vas-tu ? # etc
lead the syntax __("Hi %1, how are you?", "Bob") to translate into
`Bonjour Bob, comment vas-tu ?` in the corresponding locale.
3. PUNCTUATION TERMINATORS
____________________________________________________________________________
A small set of punctuation marks (`:`, `.`, `!`, and `...`) can be identified
at the end of a key string and then taken into account even if the whole string
is not registered as such.
For example, if the key `abc` translates into `xyz` in some language, then
`abc:` will translate into `xyz:`, or maybe into `xyz :` if the target
language has a dedicated rule `%1:` --> `%1 :` (space insertion.)
4. MUTE TERMINATORS
____________________________________________________________________________
From Yalt 2.10901 you can add variants for the same English key using a MUTE
TERMINATOR among '\x01', '\x02', '\x03' or '\x04'. This allows you to provide
distinct sets of translation strings for words or expressions --like "All",
"[None]", "match", etc-- that have variant forms in the target language depending
on the context. For example, in French,
All # Tous
All\x01 # Toutes
All\x02 # Tout
offer three possible translations for the word "All". The suffixes '\x01' and
'\x02' are used to discriminate the three cases. If the default (English) language
is active, both key strings will result in the word "All" (the terminator is
removed). Otherwise, the desired translation will be selected.
This feature allows you to address gender or plural inflections that are not marked
in English, as well as pure homographs like "left" (side) vs. "left" (past participle
of leave.)
It is recommended to define a 'default' key (without mute terminator) based on the
most usual translations in the target languages. Then, you can refine the package by
adding up to four variants of the key, e.g `All\x01`, `All\x02`, `All\x03`,
`All\x04`, with the associated translation strings. Make sure you update your strings
accordingly in your script.
[REM] Technically, there is no relationship between these special keys based on
mute terminators. That is, strings like `abc` and `abc\x01` are purely stored
as distinct keys in the internal translation map. The trick relies on the
fact that Yalt will still ouput "abc" when the default language is active.
5. EMPTY LINES AND COMMENTS
____________________________________________________________________________
Empty lines, lines that starts with `//`, or lines that don't contain the ` # `
separator are ignored. Also, the whole Yalt package string can be nested in open
and closing comment tags, i.e <SLASH><STAR> and <STAR><SLASH>.
6. ADDING A YALT PACKAGE
____________________________________________________________________________
The recommended way to add a Yalt package from within a module is to create a
private `YALT` key as follows:
YALT : $$.Yalt && $$.Yalt.addPackage
(
#include 'MyModule/$$.yalt.jsxres'
),
where the file $$.yalt.jsxres simply contains the localization package enclosed in
triple quotes. See the DateFormat module for an example.
[REM] About Yalt activation options, see detail in the API.
*/
[PRIVATE]
({
DEFZ : +(Locale.INTERNATIONAL_ENGLISH_LOCALE), // Default Yalt-Locale ID (1st column.)
KPFX : '_', // Key prefix, shouldn't change. (IMPORTANT: single character string!)
YBEG : '<YALT>', // Opening tag.
YEND : '</YALT>', // Closing tag.
YSEP : ' # ', // Separator string.
YCOM : '//', // Comment starter (single line.)
YDCR : '/*', // Decorator marker.
YESC : { '#': /##/g }, // Esc. sequences (each regex produces the associated key.)
})
[PRIVATE]
({
CURZ : µ['~'].DEFZ, // Current Yalt-Locale ID.
TRNS : {}, // All translation keys.
L10N : {}, // Current mapping (with respect to CURZ.)
PRSE : function(/*str*/s, a,r,i,n,b,t,j,k,o,x)
//----------------------------------
// Extract keys and localized strings from an input package,
// append the results into TRNS.
// => Number of strings that have actually been added.
{
const BEG = this.YBEG,
END = this.YEND,
SEP = this.YSEP,
COM = this.YCOM,
DCR = this.YDCR,
ESC = this.YESC,
// ---
KX = this.KPFX,
COL = callee.Q||(callee.Q=[]);
// [ADD200630] Support multiline translations.
// [CHG210901] Cached in `callee.NLSP`.
// ---
const RE_NL_SEP = callee.NLSP || (callee.NLSP=RegExp("(\\r\\n|\\r|\\n)[ \\t]*" + RegExp.escape(SEP.slice(1)), 'g'));
// Maps a col number to a locale ID
// ---
COL[0] = this.DEFZ;
COL.length = 1;
// Parse the input string.
// [CHG200630] Support multiline translations.
// ---
a = s.trim().replace(RE_NL_SEP, SEP).split(RegExp.LINEs);
for( r=0, n=a.length, b=0, i=-1 ; ++i < n ; )
{
if( !(s=a[i].trim()).length // Empty line after cleaning: CONTINUE.
|| ( b && ~s.indexOf(END) && (b=0,COL.length=1) ) // End tag found: turn parsing OFF, reset columns, CONTINUE.
|| !~s.indexOf(SEP) // No separator found in the line: CONTINUE.
|| !s.indexOf(COM) // Line starts w/ comment marker: CONTINUE.
|| ( !s.indexOf(DCR) && !(s=s.slice(DCR.length).ltrim()) ) // Line starts w/ decorator marker: ltrim, if empty CONTINUE.
|| ( b ? !(k=(t=s.split(SEP))[0]):s.indexOf(BEG) ) // Empty key in ON mode, or no starting BEG tag in OFF mode: CONTINUE
)
continue;
if( b )
{
// ON mode, and valid key k for t[1],t[2]... strings.
// ---
// COL :: [def_LocID, col1_LocID, col2_LocID...]
// t :: ['Yes','Oui','Ya'] ; k :: 'Yes'
// => TRNS[_k] = {col1LocID:'Oui', col2LocID:'Ya'...}
// Apply escape sequences: '##' => '#' ...
// ---
for( x in ESC ){ ESC.hasOwnProperty(x)&&k.replace(ESC[x],x); }
// Extract strings in defined columns and assign key[colID]
// Increment r only for each *new* assignable string
// ---
for( o=this.TRNS[k=KX+k]||(this.TRNS[k]={}), j=t.length ; --j ;
(k=COL[j]) && (o[k]!==t[j]) && ++r && (o[k]=t[j])
);
}
else
{
// OFF mode, and BEG tag found.
// ---
// Parse the locales => COL :: [def_LocID, <new IDs>... ]
// The user can use either <MY_LOC_STRING>_LOCALE, or <MY_LOC_STRING>
// in whatever case. E.g. French # GERMAN # Polish_Locale
t = s.split(SEP);
for(
x=0, o=Locale, j=t.length ;
--j ;
( k=t[j].trim().toUpperCase().replace('_LOCALE','') ) &&
o.hasOwnProperty(k+='_LOCALE') && ++x && (COL[j]=+o[k])
);
// Turn on the parser only if at least one valid locale has been found
// ---
b = +!!x;
}
}
return r;
},
ACTV : function(/*uint*/zloc, oCache,oTrans,k,t)
//----------------------------------
// Make zloc the current Yalt-Locale ID.
// [REM] This always (re)sets the YALT cache from scratch.
{
// Set/reset the current locale
// ---
this.CURZ = zloc;
// Cleanup the cache.
// ---
oCache = this.L10N;
for( k in oCache ) delete oCache[k];
// The default locale does not involve the cache at all.
// [REM210901] Then this.L10N.__count__ is 0.
// ---
if( zloc==this.DEFZ ) return;
// Cache the { key=>string } data for the current locale
// ---
oTrans = this.TRNS;
for( k in oTrans )
{
if( !oTrans.hasOwnProperty(k) ) continue;
t = oTrans[k];
if( !t.hasOwnProperty(zloc) ) continue;
oCache[k] = t[zloc];
}
},
YALT : $.global['__'] = function YALT(/*str*/s,x1,x2,x3,x4,x5,x6,x7,x8,x9, u,o,k,z,c,m)
//----------------------------------
// (Localization routine.) A reference to this function is assigned
// to `$.global.__` at including stage (as shown above) so the localization
// tool is already 'connected'--although not yet 'active.'
// [CHG170316] No longer use `arguments` to avoid [workspace] pollution.
// [ADD200531] Sanitize missing arguments (x1,x2...) so empty strings
// are sent rather than "undefined". Useful if your pattern contains %i
// placeholders that have no corresponding arguments.
// [ADD210518] Added the terminator `...`.
// ---
// [REM] Although declared 'private' this function cannot rely on `this` for
// referencing the private module zone, as it is invoked from the outside.
{
// [ADD200531]
// ---
u = void 0;
u===x1 && (x1=''); u===x2 && (x2=''); u===x3 && (x3='');
u===x4 && (x4=''); u===x5 && (x5=''); u===x6 && (x6='');
u===x7 && (x7=''); u===x8 && (x8=''); u===x9 && (x9='');
// [ADD210518] Coerce s into a string.
// ---
if( 'string' != (k=typeof s) )
{
if( 'undefined' == k ) return '';
s = '' + s;
}
if( !(z=s.length) ) return '';
// [CHG180513] Handle terminators `:` `.` `!` (•)
// [ADD210518] Deal with `...` too.
// ---
while( 1 )
{
o = callee.µ['~'].L10N;
// [FIX230104] Support of *mute terminators* (\x01, \x02, \x03 or \x04)
// for English variants, incl. when followed by punctuation terminator,
// e.g `Advanced\x01:`
// ---
if( !o.__count__ )
{
//callee.RE_END_CTRL.test(s) && (s=s.slice(0,-1)); [DEL230104]
(m=s.match(callee.RE_END_CTRL)) && (m=m[0], s=s.slice(0,-m.length)+m.slice(1)); // [FIX230104]
break;
}
// If `s` already has a full registered key `k==_s`,
// take the match and break.
// ---
if( o.hasOwnProperty(k=callee.µ['~'].KPFX+s) )
{
s = o[k];
break;
}
// If `s` doesn't end with a • take it as such and break.
// (For safety we also break here if `s` is strictly the string `%1•`
// but has not been identified as an existing key at the previous step.)
// ---
c = s.slice(-1); // last char -- must be `:`, `.` or `!`
if( -1 == ":.!".indexOf(c) || (s == '%1'+c) ) break;
if( '.' == c && '...' == s.slice(-3) )
{
if( '%1...' == s ) break;
c = '...';
}
k = k.slice(0, 1 + z - c.length);
// ---
// From here `s` has the form `xyz•` and has no specific match,
// while `k` has the form `_xyz` (without `•` suffix.)
// [REM210518] The suffix c==`•` might be either a single char (`:`, `.`, `!`), or `...`
// ---
if( o.hasOwnProperty(k) )
{
// (a) If `_xyz` is a registered key, apply it as such and put the result into `s`.
// E.g s=="Size of %1 is:" (k=="_Size of %1 is")
// --> s=="La taille de MyVar1 est" (FR)
// ---
s = o[k];
0 <= s.indexOf('%') && (s=$.global.localize.apply(null, [s,x1,x2,x3,x4,x5,x6,x7,x8,x9]));
}
else
{
// (b) Otherwise, removes the final • from `s`.
// Easy: just extract `k.slice(1)`.
// E.g. s=="CrazyString %1 is:" (k=="_CrazyString %1 is")
// --> s=="CrazyString %1 is"
// ---
s = k.slice(1);
}
// Change k into `_%1•`, takes the formatter (if any) and apply it to `s`.
// Assuming the formatter is "%1 :",
// in case (a) `s` ------> "La taille de MyVar1 est :"
// in case (b) `s` ------> "CrazyString %1 is :"
// (If no formatter is found, just concatenate `s` and •.)
// ---
k = k.charAt(0) + "%1" + c; // Works with c=='...' as well.
s = o.hasOwnProperty(k) ? $.global.localize(o[k],s) : (s+c);
// Note that `s` may still contain %i placeholders at the end.
break;
}
return 0 <= s.indexOf('%') ?
$.global.localize.apply(null, [s,x1,x2,x3,x4,x5,x6,x7,x8,x9]) :
s;
}
.setup
({
// [ADD210901] Mute terminators.
// [FIX230104] Added optional punctuation terminators: `...` `:` `.` `!`
// ---
RE_END_CTRL: /[\x01\x02\x03\x04](?:\.\.\.|[:.!])?$/,
}),
HASK : function(/*str*/s,/*0|1*/useL10N)
//----------------------------------
// [ADD170424] Whether the key-string `s` is known from TRNS or L10N.
// [REM] Alias of hasKey.
// => 1 [OK] | 0 [KO]
{
return this[useL10N?'L10N':'TRNS'].hasOwnProperty(this.KPFX+s) ? 1 : 0;
},
})
//==========================================================================
// API
//==========================================================================
[PUBLIC]
({
// When autoActivate is 1 (default) Yalt activates during $$ loading
// stage so packages already added from within the framework are ready
// after $$.load(). But in case the client code has to load additional
// Yalt package(s) one can set autoActivate=0 before $$.load(), so
// activate() can be explicitly invoked later, which reduces processing.
// [REM] As long as Yalt is not activated, the global __() function
// simply works as a basic formatter.
// [CHG180308] The below code reflects the recommended pattern
// if additional YALT packages are to be added *after* $$.load().
// E.g.
// #include '$$.jsxinc'
// #include 'etc/$$.Yalt.jsxlib'
// $$.Yalt.autoActivate = 0;
// ...
// $$.load();
// $$.Yalt.isActive() // => 0
// ...
// $$.Yalt(...)
// $$.Yalt.activate(Locale.germanLocale);
// $$.Yalt.isActive() // => 1
// ---
autoActivate : 1,
onLoad : function onLoad_i$locale$_(/*?uint|LocaleID*/iLocale, $$,I)
//----------------------------------
// [FIX170424] Fixed function cast name.
// [CHG180308] Enhanced onLoad logics. - It is no longer recommended to call
// `$$.Yalt.load()` manually, since this has no effect when the module already
// has its __load__ flag on. Use `$$.Yalt.activate()` instead.
// ---
// => undef [DONE]
{
callee.µ.autoActivate && callee.µ.activate(iLocale);
},
activate : function activate_i$locale$_(/*uint|LocaleID=auto*/iLocale, $$,I)
//----------------------------------
// Activate or re-activate Yalt using iLocale (default is Env.appLocaleId.)
// Eg: $$.Yalt.activate(Locale.GERMAN_LOCALE)
// ---
// [ADD180308] This routine now has an internal PENDING flag. Yalt is active
// iff not PENDING. (addPackage interacts with this flag too.)
// ---
// [CHG180308] If iLocale is not supplied:
// (a) If Yalt's __load__ flag is already on -> select curAppLocaleID
// (b) If ~.CURZ == ~.DEFZ -> select curAppLocaleID
// (c) If ~.CURZ != ~.DEFZ -> select ~.CURZ
// ---
// => undef
{
$$ = $.global[callee.µ.__root__]; // agnostic reference
I = callee.µ['~'];
// [CHG180308]
// ---
iLocale = +( iLocale || ( callee.µ.__load__ || I.CURZ==I.DEFZ ? $$.Env.appLocaleId : I.CURZ ) );
$$.Env.isValidLocaleId(iLocale) || $$.error( __("Invalid locale identifier [%1].",iLocale), callee.µ );
// Do we really need to (re)activate?
// ---
if( (I.CURZ!=iLocale) || callee.PENDING )
{
(+$$.trace) && $$.trace(__("%1 > Activating %2.",callee.µ,$$.Env.localeIdToString(iLocale)));
I.ACTV(iLocale);
delete callee.PENDING;
}
else
{
(+$$.trace) && $$.trace(__("%1 > Has already activated %2 with the same data.",callee.µ,$$.Env.localeIdToString(iLocale)));
}
}.setup({ PENDING:1 }),
addPackage : function addPackage_S_I(/*str*/data, $$,I,r)
//----------------------------------
// Add a new YALT package -- This routine can be incrementally called
// from different modules during framework's loading stage--and even after.
// When possible, the client should add all packages *before* activation.
// [ADD180308] Deals with activate.PENDING.
// ---
// => uint [number of actually added strings]
{
$$ = $.global[callee.µ.__root__]; // agnostic reference
if( 'string' != typeof data || !data.length )
{
$$.warn(__("%1.addPackage > Invalid data (%2). Non-empty string expected.",callee.µ,$$.JSON(data)));
return 0;
}
I = callee.µ['~'];
if(! (r=I.PRSE(data)) )
{
$$.warn(__("%1.addPackage > No string has been found in %2.",callee.µ,$$.JSON(data)));
return 0;
}
(+$$.trace) && $$.trace(__("%1.addPackage > %2 strings successfully added.",callee.µ,r));
// *Already* activated -> need to call ACTV again to reflect the changes.
// ---
callee.µ.activate.PENDING || (I.ACTV(I.CURZ),(delete callee.µ.activate.PENDING));
return r;
},
getLocaleId : function getLocaleId_I( i,s)
//----------------------------------
// => uint [current Yalt-Locale ID]
{
return callee.µ['~'].CURZ;
},
isActive : function isActive_B()
//----------------------------------
// Tell whether Yalt is currently activated.
// [CHG180308] Use the PENDING flag instead.
// => 0 | 1
{
return callee.µ.activate.PENDING ? 0 : 1;
},
hasKey : function hasKey_S_b_B(/*str*/keyString,/*bool=0*/inCurrentLocale)
//----------------------------------
// Whether keyString is available among Yalt translation strings.
// - By default, the keyString is searched within the internal map
// (~.TRNS) that controls ALL translation keys (whatever the
// current locale.)
// - If `inCurrentLocale` is truthy, the keyString is specifically
// searched in the current locale map (~.L10N.)
// [ADD220403] Fixed, adding `inCurrentLocale` arg.
// ---
// => 0 | 1
{
return 'string' == typeof keyString && keyString.length ? callee.µ['~'].HASK(keyString,inCurrentLocale?1:0) : 0;
},
translate : function translate_A_b_a(/*str[]|str[]&*/inputArr,/*bool=0*/CHANGE_IN_PLACE, r,Y,i)
//----------------------------------
// [ADD240618] Translate each element of the `inputArr` array.
// By default, a new array of strings is created. If CHANGE_IN_PLACE
// is truthy, the input array is changed.
// Ex. $$.Yalt.translate( ["Hello", "Thank you", "Cancel"] )
// returns the array `[ __("Hello"), __("Thank you"), __("Cancel") ]`
// [REM] If `inputArr` has not the expected type, the method has no effect
// and returns undefined.
// ---
// => new str[] | inputArr& | undef [KO]
{
if( !(inputArr && inputArr instanceof Array ) ) return;
r = CHANGE_IN_PLACE ? inputArr : Array(inputArr.length);
for
(
Y=callee.µ['~'].YALT, i=r.length ;
i-- ;
r[i]=Y(inputArr[i])
);
return r;
},
})
.addPackage
(
#include 'Yalt/$$.yalt.jsxres'
)