@esgettext/runtime 1.1.0 → 1.2.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/LICENSE +1 -1
- package/README.md +17 -19
- package/dist/core/catalog-cache.d.ts +11 -0
- package/dist/core/catalog-cache.d.ts.map +1 -1
- package/dist/core/catalog.d.ts +42 -4
- package/dist/core/catalog.d.ts.map +1 -1
- package/dist/core/data-viewlet.d.ts +10 -0
- package/dist/core/data-viewlet.d.ts.map +1 -1
- package/dist/core/locale-container.d.ts +26 -3
- package/dist/core/locale-container.d.ts.map +1 -1
- package/dist/core/resolve-impl.d.ts.map +1 -1
- package/dist/core/textdomain.d.ts +412 -4
- package/dist/core/textdomain.d.ts.map +1 -1
- package/dist/esgettext.cjs.js +684 -141
- package/dist/esgettext.cjs.js.map +1 -1
- package/dist/esgettext.esm.js +684 -141
- package/dist/esgettext.esm.js.map +1 -1
- package/dist/esgettext.js +1415 -0
- package/dist/esgettext.js.map +1 -0
- package/dist/esgettext.min.js +1 -1
- package/dist/esgettext.min.js.map +1 -1
- package/package.json +3 -3
package/dist/esgettext.cjs.js
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
var fs = require('fs');
|
|
4
4
|
|
|
5
5
|
let userLocalesSelected = ['C'];
|
|
6
|
+
/*
|
|
7
|
+
* Force an execution environment. By default, the environment (NodeJS or
|
|
8
|
+
* browser) is auto-detected. You can force the library to assume a certain
|
|
9
|
+
* environment with this function.
|
|
10
|
+
*
|
|
11
|
+
* @param browser - whether to assume a browser or not
|
|
12
|
+
* @returns the new setting.
|
|
13
|
+
*/
|
|
6
14
|
function userLocales(locales) {
|
|
7
15
|
if (typeof locales !== 'undefined') {
|
|
8
16
|
userLocalesSelected = locales;
|
|
@@ -10,6 +18,39 @@ function userLocales(locales) {
|
|
|
10
18
|
return userLocalesSelected;
|
|
11
19
|
}
|
|
12
20
|
|
|
21
|
+
/******************************************************************************
|
|
22
|
+
Copyright (c) Microsoft Corporation.
|
|
23
|
+
|
|
24
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
25
|
+
purpose with or without fee is hereby granted.
|
|
26
|
+
|
|
27
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
28
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
29
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
30
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
31
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
32
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
33
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
34
|
+
***************************************************************************** */
|
|
35
|
+
/* global Reflect, Promise, SuppressedError, Symbol */
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
39
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
40
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
41
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
42
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
43
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
44
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
49
|
+
var e = new Error(message);
|
|
50
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
|
13
54
|
class TransportHttp {
|
|
14
55
|
loadFile(url) {
|
|
15
56
|
return new Promise((resolve, reject) => {
|
|
@@ -40,23 +81,60 @@ class TransportFs {
|
|
|
40
81
|
}
|
|
41
82
|
}
|
|
42
83
|
|
|
84
|
+
/*
|
|
85
|
+
* Function for germanic plural. Returns singular (0) for 1 item, and
|
|
86
|
+
* 1 for everything else.
|
|
87
|
+
*
|
|
88
|
+
* @param numItems - number of items
|
|
89
|
+
* @returns the index into the plural translations
|
|
90
|
+
*/
|
|
43
91
|
function germanicPlural(numItems) {
|
|
44
92
|
return numItems === 1 ? 0 : 1;
|
|
45
93
|
}
|
|
46
94
|
|
|
95
|
+
/*
|
|
96
|
+
* A minimalistic buffer implementation that can only read 32 bit unsigned
|
|
97
|
+
* integers and strings.
|
|
98
|
+
*/
|
|
47
99
|
class DataViewlet {
|
|
100
|
+
/*
|
|
101
|
+
* Create a DataViewlet instance. All encodings that are supported by
|
|
102
|
+
* the runtime environments `TextDecoder` interface.
|
|
103
|
+
*
|
|
104
|
+
* @param array - a `Unit8Array` view on the binary buffer
|
|
105
|
+
* @param encoding - encoding of strings, defaults to utf-8
|
|
106
|
+
*/
|
|
48
107
|
constructor(array, encoding = 'utf-8') {
|
|
49
108
|
this.array = array;
|
|
50
109
|
this.decoder = new TextDecoder(encoding);
|
|
51
110
|
this._encoding = encoding;
|
|
52
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Get the encoding for strings.
|
|
114
|
+
*
|
|
115
|
+
* @returns the encoding in use
|
|
116
|
+
*/
|
|
53
117
|
get encoding() {
|
|
54
118
|
return this._encoding;
|
|
55
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Switch to a new encoding.
|
|
122
|
+
*
|
|
123
|
+
* @param encoding - new encoding to use
|
|
124
|
+
*/
|
|
56
125
|
set encoding(encoding) {
|
|
57
126
|
this.decoder = new TextDecoder(encoding);
|
|
58
127
|
this._encoding = encoding;
|
|
59
128
|
}
|
|
129
|
+
/*
|
|
130
|
+
* Reads an unsigned 32-bit integer from the buffer at
|
|
131
|
+
* the specified offset as big-endian.
|
|
132
|
+
*
|
|
133
|
+
* @param offset - Number of bytes to skip before starting to read.
|
|
134
|
+
* Must satisfy `0 <= offset <= buf.length - 4`.
|
|
135
|
+
* Default: 0.
|
|
136
|
+
* @returns the 32-bit unsigned integer at position `offset`.s
|
|
137
|
+
*/
|
|
60
138
|
readUInt32BE(offset = 0) {
|
|
61
139
|
if (offset + 4 > this.array.byteLength + this.array.byteOffset) {
|
|
62
140
|
throw new Error('read past array end');
|
|
@@ -67,6 +145,15 @@ class DataViewlet {
|
|
|
67
145
|
this.array[offset + 3]) >>>
|
|
68
146
|
0);
|
|
69
147
|
}
|
|
148
|
+
/*
|
|
149
|
+
* Reads an unsigned 32-bit integer from the buffer at
|
|
150
|
+
* the specified offset as little-endian.
|
|
151
|
+
*
|
|
152
|
+
* @param offset - Number of bytes to skip before starting to read.
|
|
153
|
+
* Must satisfy `0 <= offset <= buf.length - 4`.
|
|
154
|
+
* Default: 0.
|
|
155
|
+
* @returns the 32-bit unsigned integer at position `offset`.s
|
|
156
|
+
*/
|
|
70
157
|
readUInt32LE(offset = 0) {
|
|
71
158
|
if (offset + 4 > this.array.byteLength + this.array.byteOffset) {
|
|
72
159
|
throw new Error('read past array end');
|
|
@@ -77,6 +164,13 @@ class DataViewlet {
|
|
|
77
164
|
this.array[offset]) >>>
|
|
78
165
|
0);
|
|
79
166
|
}
|
|
167
|
+
/*
|
|
168
|
+
* Read a string at a specified offset.
|
|
169
|
+
*
|
|
170
|
+
* @param offset - to beginning of buffer in bytes
|
|
171
|
+
* @param length - of the string to read in bytes or to the end of the
|
|
172
|
+
* buffer if not specified.
|
|
173
|
+
*/
|
|
80
174
|
readString(offset = 0, length) {
|
|
81
175
|
if (offset + length >
|
|
82
176
|
this.array.byteLength + this.array.byteOffset) {
|
|
@@ -89,6 +183,15 @@ class DataViewlet {
|
|
|
89
183
|
}
|
|
90
184
|
}
|
|
91
185
|
|
|
186
|
+
/*
|
|
187
|
+
* Parse an MO file.
|
|
188
|
+
*
|
|
189
|
+
* An exception is thrown for invalid data.
|
|
190
|
+
*
|
|
191
|
+
* @param raw - The input as either a binary `String`, any `Array`-like byte
|
|
192
|
+
* storage (`Array`, `Uint8Array`, `Arguments`, `jQuery(Array)`, ...)
|
|
193
|
+
* @returns a Catalog
|
|
194
|
+
*/
|
|
92
195
|
function parseMoCatalog(raw) {
|
|
93
196
|
const catalog = {
|
|
94
197
|
major: 0,
|
|
@@ -101,15 +204,19 @@ function parseMoCatalog(raw) {
|
|
|
101
204
|
const magic = blob.readUInt32LE(offset);
|
|
102
205
|
let reader;
|
|
103
206
|
if (magic === 0x950412de) {
|
|
207
|
+
/* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
|
|
104
208
|
reader = (buf, off) => buf.readUInt32LE(off);
|
|
105
209
|
}
|
|
106
210
|
else if (magic === 0xde120495) {
|
|
211
|
+
/* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
|
|
107
212
|
reader = (buf, off) => buf.readUInt32BE(off);
|
|
108
213
|
}
|
|
109
214
|
else {
|
|
110
215
|
throw new Error(`invalid MO magic 0x${magic.toString(16)}`);
|
|
111
216
|
}
|
|
112
217
|
offset += 4;
|
|
218
|
+
// The revision is encoded in two shorts, major and minor. We don't care
|
|
219
|
+
// about the minor revision.
|
|
113
220
|
const major = reader(blob, offset) >> 16;
|
|
114
221
|
offset += 4;
|
|
115
222
|
if (major > 0) {
|
|
@@ -170,6 +277,10 @@ function parseMoCatalog(raw) {
|
|
|
170
277
|
}
|
|
171
278
|
|
|
172
279
|
function validateMoJsonCatalog(udata) {
|
|
280
|
+
// We could use ajv but it results in almost 300 k minimized code
|
|
281
|
+
// for the browser bundle. This validator instead is absolutely
|
|
282
|
+
// minimalistic, and only avoids exceptions that can occur, when
|
|
283
|
+
// accessing entries.
|
|
173
284
|
if (udata === null || typeof udata === 'undefined') {
|
|
174
285
|
throw new Error('catalog is either null or undefined');
|
|
175
286
|
}
|
|
@@ -177,6 +288,8 @@ function validateMoJsonCatalog(udata) {
|
|
|
177
288
|
if (data.constructor !== Object) {
|
|
178
289
|
throw new Error('catalog must be a dictionary');
|
|
179
290
|
}
|
|
291
|
+
// We don't care about major and minor because they are actually not
|
|
292
|
+
// used.
|
|
180
293
|
if (!Object.prototype.hasOwnProperty.call(data, 'entries')) {
|
|
181
294
|
throw new Error('catalog.entries does not exist');
|
|
182
295
|
}
|
|
@@ -201,11 +314,16 @@ function parseMoJsonCatalog(json) {
|
|
|
201
314
|
}
|
|
202
315
|
|
|
203
316
|
function validateJsonCatalog(udata) {
|
|
317
|
+
// We could use ajv but it results in almost 300 k minimized code
|
|
318
|
+
// for the browser bundle. This validator instead is absolutely
|
|
319
|
+
// minimalistic, and only avoids exceptions that can occur, when
|
|
320
|
+
// accessing entries.
|
|
204
321
|
if (udata === null || typeof udata === 'undefined') {
|
|
205
322
|
throw new Error('catalog is either null or undefined');
|
|
206
323
|
}
|
|
207
324
|
const entries = udata;
|
|
208
325
|
if (entries.constructor !== Object) ;
|
|
326
|
+
// Convert to a regular catalog.
|
|
209
327
|
const catalog = {
|
|
210
328
|
major: 0,
|
|
211
329
|
minor: 1,
|
|
@@ -213,6 +331,7 @@ function validateJsonCatalog(udata) {
|
|
|
213
331
|
entries: {},
|
|
214
332
|
};
|
|
215
333
|
for (const [msgid, msgstr] of Object.entries(entries)) {
|
|
334
|
+
// Just stringify all values but do not complain.
|
|
216
335
|
catalog.entries[msgid] = [msgstr.toString()];
|
|
217
336
|
}
|
|
218
337
|
return catalog;
|
|
@@ -258,8 +377,19 @@ function splitLocale(locale) {
|
|
|
258
377
|
return split;
|
|
259
378
|
}
|
|
260
379
|
|
|
380
|
+
/*
|
|
381
|
+
* Caches catalog lookups by path, locale, and textdomain.
|
|
382
|
+
*
|
|
383
|
+
* Failed lookups are stored as null values.
|
|
384
|
+
*
|
|
385
|
+
* It is also possible to store a Promise. In that case, if a request is
|
|
386
|
+
* made to bind the textdomain, the promise is settled. Note that this
|
|
387
|
+
* mechanism is never used for message lookup but only for loading the
|
|
388
|
+
* catalog via resolve.
|
|
389
|
+
*/
|
|
261
390
|
class CatalogCache {
|
|
262
391
|
constructor() {
|
|
392
|
+
/* Singleton. */
|
|
263
393
|
}
|
|
264
394
|
static getInstance() {
|
|
265
395
|
if (!CatalogCache.instance) {
|
|
@@ -270,6 +400,17 @@ class CatalogCache {
|
|
|
270
400
|
static clear() {
|
|
271
401
|
CatalogCache.cache = {};
|
|
272
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Lookup a Catalog for a given base path, locale, and textdomain.
|
|
405
|
+
*
|
|
406
|
+
* The locale key is usually the locale identifier (e.g. de-DE or sr\@latin).
|
|
407
|
+
* But it can also be a colon separated list of such locale identifiers.
|
|
408
|
+
*
|
|
409
|
+
*
|
|
410
|
+
* @param localeKey - the locale key
|
|
411
|
+
* @param textdomain - the textdomain
|
|
412
|
+
* @returns the cached Catalog, a Promise or null for failure
|
|
413
|
+
*/
|
|
273
414
|
static lookup(localeKey, textdomain) {
|
|
274
415
|
if (CatalogCache.cache[localeKey]) {
|
|
275
416
|
const ptr = CatalogCache.cache[localeKey];
|
|
@@ -281,6 +422,7 @@ class CatalogCache {
|
|
|
281
422
|
}
|
|
282
423
|
static store(localeKey, textdomain, entry) {
|
|
283
424
|
if (Promise.resolve(entry) !== entry) {
|
|
425
|
+
// Object.
|
|
284
426
|
entry = validateMoJsonCatalog(entry);
|
|
285
427
|
}
|
|
286
428
|
if (!CatalogCache.cache[localeKey]) {
|
|
@@ -322,144 +464,141 @@ function explodeLocale(locale, vary) {
|
|
|
322
464
|
}
|
|
323
465
|
|
|
324
466
|
function loadCatalog(url, format) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
parsedURL
|
|
332
|
-
parsedURL.protocol === '
|
|
333
|
-
|
|
467
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
468
|
+
let transportInstance;
|
|
469
|
+
{
|
|
470
|
+
let transport;
|
|
471
|
+
// Check whether this is a valid URL.
|
|
472
|
+
try {
|
|
473
|
+
const parsedURL = new URL(url);
|
|
474
|
+
if (parsedURL.protocol === 'https:' ||
|
|
475
|
+
parsedURL.protocol === 'http:' ||
|
|
476
|
+
parsedURL.protocol === 'file:') {
|
|
477
|
+
transport = 'http';
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
throw new Error(`unsupported scheme ${parsedURL.protocol}`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
catch (e) {
|
|
484
|
+
{
|
|
485
|
+
transport = 'fs';
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (transport === 'http') {
|
|
489
|
+
transportInstance = new TransportHttp();
|
|
334
490
|
}
|
|
335
491
|
else {
|
|
336
|
-
|
|
492
|
+
transportInstance = new TransportFs();
|
|
337
493
|
}
|
|
338
494
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
495
|
+
let validator;
|
|
496
|
+
if ('mo.json' === format) {
|
|
497
|
+
validator = parseMoJsonCatalog;
|
|
343
498
|
}
|
|
344
|
-
if (
|
|
345
|
-
|
|
499
|
+
else if ('.json' === format) {
|
|
500
|
+
validator = parseJsonCatalog;
|
|
346
501
|
}
|
|
347
502
|
else {
|
|
348
|
-
|
|
503
|
+
validator = parseMoCatalog;
|
|
504
|
+
}
|
|
505
|
+
try {
|
|
506
|
+
const data = yield transportInstance.loadFile(url);
|
|
507
|
+
return validator(data);
|
|
508
|
+
}
|
|
509
|
+
catch (_a) {
|
|
510
|
+
return null;
|
|
349
511
|
}
|
|
350
|
-
}
|
|
351
|
-
let validator;
|
|
352
|
-
if ('mo.json' === format) {
|
|
353
|
-
validator = parseMoJsonCatalog;
|
|
354
|
-
}
|
|
355
|
-
else if ('.json' === format) {
|
|
356
|
-
validator = parseJsonCatalog;
|
|
357
|
-
}
|
|
358
|
-
else {
|
|
359
|
-
validator = parseMoCatalog;
|
|
360
|
-
}
|
|
361
|
-
return new Promise((resolve, reject) => {
|
|
362
|
-
transportInstance
|
|
363
|
-
.loadFile(url)
|
|
364
|
-
.then(data => {
|
|
365
|
-
resolve(validator(data));
|
|
366
|
-
})
|
|
367
|
-
.catch(e => reject(e));
|
|
368
512
|
});
|
|
369
513
|
}
|
|
370
514
|
function assemblePath(base, id, domainname, extender) {
|
|
371
515
|
return `${base}/${id}/LC_MESSAGES/${domainname}.${extender}`;
|
|
372
516
|
}
|
|
373
517
|
function loadLanguageFromObject(ids, base, domainname) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
resolve(catalog);
|
|
391
|
-
}
|
|
392
|
-
else {
|
|
393
|
-
reject();
|
|
518
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
519
|
+
for (let i = 0; i < ids.length; ++i) {
|
|
520
|
+
const id = ids[i];
|
|
521
|
+
// Language exists?
|
|
522
|
+
if (!Object.prototype.hasOwnProperty.call(base, id)) {
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
// LC_MESSAGES?
|
|
526
|
+
if (!Object.prototype.hasOwnProperty.call(base[id], 'LC_MESSAGES')) {
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
// Textdomain?
|
|
530
|
+
if (!Object.prototype.hasOwnProperty.call(base[id].LC_MESSAGES, domainname)) {
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
return base[id].LC_MESSAGES[domainname];
|
|
394
534
|
}
|
|
535
|
+
return null;
|
|
395
536
|
});
|
|
396
537
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
538
|
+
/*
|
|
539
|
+
* First tries to load a catalog with the specified charset, then with the
|
|
540
|
+
* charset converted to uppercase (if it differs from the original charset),
|
|
541
|
+
* and finally without a charset.
|
|
542
|
+
*/
|
|
543
|
+
function loadLanguage(ids, base, domainname, format) {
|
|
544
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
545
|
+
// Check if `base` is an object (LocaleContainer).
|
|
546
|
+
if (typeof base === 'object' && base !== null) {
|
|
547
|
+
return loadLanguageFromObject(ids, base, domainname);
|
|
548
|
+
}
|
|
549
|
+
for (const id of ids) {
|
|
550
|
+
const catalog = yield loadCatalog(assemblePath(base, id, domainname, format), format);
|
|
551
|
+
if (catalog) {
|
|
552
|
+
return catalog;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return null;
|
|
410
556
|
});
|
|
411
557
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
if (
|
|
558
|
+
function loadDomain(exploded, localeKey, base, domainname, format) {
|
|
559
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
560
|
+
const entries = {};
|
|
561
|
+
const catalog = {
|
|
562
|
+
major: 0,
|
|
563
|
+
minor: 0,
|
|
564
|
+
pluralFunction: germanicPlural,
|
|
565
|
+
entries,
|
|
566
|
+
};
|
|
567
|
+
const cacheHit = yield CatalogCache.lookup(localeKey, domainname);
|
|
568
|
+
if (cacheHit !== null) {
|
|
423
569
|
return cacheHit;
|
|
424
570
|
}
|
|
425
|
-
|
|
426
|
-
|
|
571
|
+
for (const tries of exploded) {
|
|
572
|
+
const result = yield loadLanguage(tries, base, domainname, format);
|
|
573
|
+
if (result) {
|
|
574
|
+
catalog.major = result.major;
|
|
575
|
+
catalog.minor = result.minor;
|
|
576
|
+
catalog.entries = Object.assign(Object.assign({}, catalog.entries), result.entries);
|
|
577
|
+
}
|
|
427
578
|
}
|
|
428
|
-
|
|
429
|
-
const promises = new Array();
|
|
430
|
-
const results = new Array();
|
|
431
|
-
exploded.forEach((tries, i) => {
|
|
432
|
-
const p = loadLanguage(tries, base, domainname, format)
|
|
433
|
-
.then(catalog => (results[i] = catalog))
|
|
434
|
-
.catch(() => {
|
|
435
|
-
});
|
|
436
|
-
promises.push(p);
|
|
437
|
-
});
|
|
438
|
-
await Promise.all(promises);
|
|
439
|
-
results.forEach(result => {
|
|
440
|
-
catalog.major = result.major;
|
|
441
|
-
catalog.minor = result.minor;
|
|
442
|
-
catalog.entries = Object.assign(Object.assign({}, catalog.entries), result.entries);
|
|
443
|
-
});
|
|
444
|
-
return new Promise(resolve => {
|
|
445
|
-
resolve(catalog);
|
|
579
|
+
return catalog;
|
|
446
580
|
});
|
|
447
581
|
}
|
|
448
582
|
function pluralExpression(str) {
|
|
449
583
|
const tokens = str
|
|
450
584
|
.replace(/[ \t\r\013\014]/g, '')
|
|
451
585
|
.replace(/;$/, '')
|
|
586
|
+
// Do NOT allow square brackets here. JSFuck!
|
|
452
587
|
.split(/[<>!=]=|&&|\|\||[-!*/%+<>=?:;]/);
|
|
453
588
|
for (let i = 0; i < tokens.length; ++i) {
|
|
454
589
|
const token = tokens[i].replace(/^\(+/, '').replace(/\)+$/, '');
|
|
455
590
|
if (token !== 'nplurals' &&
|
|
456
591
|
token !== 'plural' &&
|
|
457
592
|
token !== 'n' &&
|
|
593
|
+
// Does not catch invalid octal numbers but the compiler
|
|
594
|
+
// takes care of that.
|
|
458
595
|
null === /^[0-9]+$/.exec(token)) {
|
|
459
596
|
throw new Error('invalid plural function');
|
|
460
597
|
}
|
|
461
598
|
}
|
|
462
599
|
const code = 'var nplurals = 1, plural = 0;' + str + '; return 0 + plural';
|
|
600
|
+
// This may throw an exception!
|
|
601
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
463
602
|
return new Function('n', code);
|
|
464
603
|
}
|
|
465
604
|
function setPluralFunction(catalog) {
|
|
@@ -471,33 +610,32 @@ function setPluralFunction(catalog) {
|
|
|
471
610
|
const tokens = header.split(':');
|
|
472
611
|
if ('plural-forms' === tokens.shift().toLowerCase()) {
|
|
473
612
|
const code = tokens.join(':');
|
|
474
|
-
|
|
613
|
+
try {
|
|
614
|
+
catalog.pluralFunction = pluralExpression(code);
|
|
615
|
+
}
|
|
616
|
+
catch (_a) {
|
|
617
|
+
catalog.pluralFunction = germanicPlural;
|
|
618
|
+
}
|
|
475
619
|
}
|
|
476
620
|
});
|
|
477
621
|
return catalog;
|
|
478
622
|
}
|
|
479
623
|
function resolveImpl(domainname, path, format, localeKey) {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
624
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
625
|
+
const defaultCatalog = {
|
|
626
|
+
major: 0,
|
|
627
|
+
minor: 0,
|
|
628
|
+
pluralFunction: germanicPlural,
|
|
629
|
+
entries: {},
|
|
630
|
+
};
|
|
631
|
+
if (localeKey === 'C' || localeKey === 'POSIX') {
|
|
632
|
+
return defaultCatalog;
|
|
633
|
+
}
|
|
490
634
|
const exploded = explodeLocale(splitLocale(localeKey));
|
|
491
|
-
loadDomain(exploded, localeKey, path, domainname, format)
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
resolve(catalog);
|
|
496
|
-
})
|
|
497
|
-
.catch(() => {
|
|
498
|
-
CatalogCache.store(localeKey, domainname, defaultCatalog);
|
|
499
|
-
resolve(defaultCatalog);
|
|
500
|
-
});
|
|
635
|
+
const catalog = yield loadDomain(exploded, localeKey, path, domainname, format);
|
|
636
|
+
setPluralFunction(catalog);
|
|
637
|
+
CatalogCache.store(localeKey, domainname, catalog);
|
|
638
|
+
return catalog;
|
|
501
639
|
});
|
|
502
640
|
}
|
|
503
641
|
|
|
@@ -577,10 +715,50 @@ function selectLocale(supported, requested) {
|
|
|
577
715
|
return 'C';
|
|
578
716
|
}
|
|
579
717
|
|
|
718
|
+
/**
|
|
719
|
+
* A Textdomain is a container for an esgettext configuration and all loaded
|
|
720
|
+
* LocaleContainer for the textual domain selected.
|
|
721
|
+
*
|
|
722
|
+
* The actual translation methods have quite funny names like `_()` or
|
|
723
|
+
* `_x()`. The purpose of this naming convention is to make the
|
|
724
|
+
* internationalization of your programs as little obtrusive as possible.
|
|
725
|
+
* Most of the times you just have to exchange
|
|
726
|
+
*
|
|
727
|
+
* ```
|
|
728
|
+
* doSomething('Hello, world!');
|
|
729
|
+
* ```
|
|
730
|
+
*
|
|
731
|
+
* with
|
|
732
|
+
*
|
|
733
|
+
* ```
|
|
734
|
+
* doSomething(gtx._('Hello, world!'));
|
|
735
|
+
* ```
|
|
736
|
+
*
|
|
737
|
+
* Besides, depending on the string extractor you are using, it may be useful
|
|
738
|
+
* that the method names do not collide with method names from other packages.
|
|
739
|
+
*/
|
|
580
740
|
class Textdomain {
|
|
741
|
+
/**
|
|
742
|
+
* Retrieve a translation for a string.
|
|
743
|
+
*
|
|
744
|
+
* @param msgid - the string to translate
|
|
745
|
+
*
|
|
746
|
+
* @returns the translated string
|
|
747
|
+
*/
|
|
581
748
|
_(msgid) {
|
|
582
749
|
return gettextImpl({ msgid: msgid, catalog: this.catalog });
|
|
583
750
|
}
|
|
751
|
+
/**
|
|
752
|
+
* Retrieve a translation for a string containing a possible plural.
|
|
753
|
+
* You will almost always want to call {@link _nx} instead so that
|
|
754
|
+
* you can interpolate the number of items into the strings.
|
|
755
|
+
*
|
|
756
|
+
* @param msgid - the string in the singular
|
|
757
|
+
* @param msgidPlural - the string in the plural
|
|
758
|
+
* @param numItems - the number of items
|
|
759
|
+
*
|
|
760
|
+
* @returns the translated string
|
|
761
|
+
*/
|
|
584
762
|
_n(msgid, msgidPlural, numItems) {
|
|
585
763
|
return gettextImpl({
|
|
586
764
|
msgid: msgid,
|
|
@@ -589,6 +767,14 @@ class Textdomain {
|
|
|
589
767
|
catalog: this.catalog,
|
|
590
768
|
});
|
|
591
769
|
}
|
|
770
|
+
/**
|
|
771
|
+
* Translate a string with a context.
|
|
772
|
+
*
|
|
773
|
+
* @param msgctxt - the message context
|
|
774
|
+
* @param msgid - the string to translate
|
|
775
|
+
*
|
|
776
|
+
* @returns the translated string
|
|
777
|
+
*/
|
|
592
778
|
_p(msgctxt, msgid) {
|
|
593
779
|
return gettextImpl({
|
|
594
780
|
msgctxt: msgctxt,
|
|
@@ -596,6 +782,17 @@ class Textdomain {
|
|
|
596
782
|
catalog: this.catalog,
|
|
597
783
|
});
|
|
598
784
|
}
|
|
785
|
+
/**
|
|
786
|
+
* The method `_np()` combines `_n()` with `_p()`.
|
|
787
|
+
* You will almost always want to call {@link _npx} instead so that
|
|
788
|
+
* you can interpolate the number of items into the strings.
|
|
789
|
+
|
|
790
|
+
*
|
|
791
|
+
* @param msgctxt - the message context
|
|
792
|
+
* @param msgid - the message id
|
|
793
|
+
* @param placeholders - a dictionary with placehoders
|
|
794
|
+
* @returns the translated string
|
|
795
|
+
*/
|
|
599
796
|
_np(msgctxt, msgid, msgidPlural, numItems) {
|
|
600
797
|
return gettextImpl({
|
|
601
798
|
msgctxt: msgctxt,
|
|
@@ -605,9 +802,29 @@ class Textdomain {
|
|
|
605
802
|
catalog: this.catalog,
|
|
606
803
|
});
|
|
607
804
|
}
|
|
805
|
+
/**
|
|
806
|
+
* Translate a string with placeholders. The placeholders should be
|
|
807
|
+
* wrapped into curly braces and must match the regular expression
|
|
808
|
+
* "[_a-zA-Z][_a-zA-Z0-9]*".
|
|
809
|
+
*
|
|
810
|
+
* @param msgid - the msgid to translate
|
|
811
|
+
* @param placeholders - an optional dictionary of placeholders
|
|
812
|
+
*
|
|
813
|
+
* @returns the translated string with placeholders expanded
|
|
814
|
+
*/
|
|
608
815
|
_x(msgid, placeholders) {
|
|
609
816
|
return Textdomain.expand(gettextImpl({ msgid: msgid, catalog: this.catalog }), placeholders || {});
|
|
610
817
|
}
|
|
818
|
+
/**
|
|
819
|
+
* Translate a string with a plural expression with placeholders.
|
|
820
|
+
*
|
|
821
|
+
* @param msgid - the string in the singular
|
|
822
|
+
* @param msgidPlural - the string in the plural
|
|
823
|
+
* @param numItems - the number of items
|
|
824
|
+
* @param placeholders - an optional dictionary of placeholders
|
|
825
|
+
*
|
|
826
|
+
* @returns the translated string
|
|
827
|
+
*/
|
|
611
828
|
_nx(msgid, msgidPlural, numItems, placeholders) {
|
|
612
829
|
return Textdomain.expand(gettextImpl({
|
|
613
830
|
msgid: msgid,
|
|
@@ -616,9 +833,28 @@ class Textdomain {
|
|
|
616
833
|
catalog: this.catalog,
|
|
617
834
|
}), placeholders || {});
|
|
618
835
|
}
|
|
836
|
+
/**
|
|
837
|
+
* The method `_px()` combines `_p()` with `_x()`.
|
|
838
|
+
*
|
|
839
|
+
* @param msgctxt - the message context
|
|
840
|
+
* @param msgid - the message id
|
|
841
|
+
* @param placeholders - an optional dictionary with placehoders
|
|
842
|
+
* @returns the translated string
|
|
843
|
+
*/
|
|
619
844
|
_px(msgctxt, msgid, placeholders) {
|
|
620
845
|
return Textdomain.expand(gettextImpl({ msgctxt: msgctxt, msgid: msgid, catalog: this.catalog }), placeholders || {});
|
|
621
846
|
}
|
|
847
|
+
/**
|
|
848
|
+
* The method `_npx()` brings it all together. It combines `_n()` and
|
|
849
|
+
* `_p()` and `_x()`.
|
|
850
|
+
*
|
|
851
|
+
* @param msgctxt - the message context
|
|
852
|
+
* @param msgid - the message id
|
|
853
|
+
* @param msgidPlural - the plural string
|
|
854
|
+
* @param numItems - the number of items
|
|
855
|
+
* @param placeholders - an optional dictionary with placehoders
|
|
856
|
+
* @returns the translated string
|
|
857
|
+
*/
|
|
622
858
|
_npx(msgctxt, msgid, msgidPlural, numItems, placeholders) {
|
|
623
859
|
return Textdomain.expand(gettextImpl({
|
|
624
860
|
msgctxt: msgctxt,
|
|
@@ -640,10 +876,31 @@ class Textdomain {
|
|
|
640
876
|
}
|
|
641
877
|
return catalog;
|
|
642
878
|
}
|
|
879
|
+
/**
|
|
880
|
+
* Retrieve a translation for a string with a fixed locale.
|
|
881
|
+
*
|
|
882
|
+
* @param locale - the locale identifier
|
|
883
|
+
* @param msgid - the string to translate
|
|
884
|
+
*
|
|
885
|
+
* @returns the translated string
|
|
886
|
+
*/
|
|
643
887
|
_l(locale, msgid) {
|
|
644
888
|
const catalog = Textdomain.getCatalog(locale, this.textdomain());
|
|
645
889
|
return gettextImpl({ msgid: msgid, catalog: catalog });
|
|
646
890
|
}
|
|
891
|
+
/**
|
|
892
|
+
* Retrieve a translation for a string containing a possible plural with
|
|
893
|
+
* a fixed locale.
|
|
894
|
+
* You will almost always want to call {@link _nx} instead so that
|
|
895
|
+
* you can interpolate the number of items into the strings.
|
|
896
|
+
*
|
|
897
|
+
* @param locale - the locale identifier
|
|
898
|
+
* @param msgid - the string in the singular
|
|
899
|
+
* @param msgidPlural - the string in the plural
|
|
900
|
+
* @param numItems - the number of items
|
|
901
|
+
*
|
|
902
|
+
* @returns the translated string
|
|
903
|
+
*/
|
|
647
904
|
_ln(locale, msgid, msgidPlural, numItems) {
|
|
648
905
|
const catalog = Textdomain.getCatalog(locale, this.textdomain());
|
|
649
906
|
return gettextImpl({
|
|
@@ -653,10 +910,31 @@ class Textdomain {
|
|
|
653
910
|
catalog: catalog,
|
|
654
911
|
});
|
|
655
912
|
}
|
|
913
|
+
/**
|
|
914
|
+
* Translate a string with a context with a fixed locale.
|
|
915
|
+
*
|
|
916
|
+
* @param locale - the locale identifier
|
|
917
|
+
* @param msgctxt - the message context
|
|
918
|
+
* @param msgid - the string to translate
|
|
919
|
+
*
|
|
920
|
+
* @returns the translated string
|
|
921
|
+
*/
|
|
656
922
|
_lp(locale, msgctxt, msgid) {
|
|
657
923
|
const catalog = Textdomain.getCatalog(locale, this.textdomain());
|
|
658
924
|
return gettextImpl({ msgctxt: msgctxt, msgid: msgid, catalog: catalog });
|
|
659
925
|
}
|
|
926
|
+
/**
|
|
927
|
+
* The method `_lnp()` combines `_ln()` with `_lp()`.
|
|
928
|
+
* You will almost always want to call {@link _npx} instead so that
|
|
929
|
+
* you can interpolate the number of items into the strings.
|
|
930
|
+
|
|
931
|
+
*
|
|
932
|
+
* @param locale - the locale identifier
|
|
933
|
+
* @param msgctxt - the message context
|
|
934
|
+
* @param msgid - the message id
|
|
935
|
+
* @param placeholders - a dictionary with placehoders
|
|
936
|
+
* @returns the translated string
|
|
937
|
+
*/
|
|
660
938
|
_lnp(locale, msgctxt, msgid, msgidPlural, numItems) {
|
|
661
939
|
const catalog = Textdomain.getCatalog(locale, this.textdomain());
|
|
662
940
|
return gettextImpl({
|
|
@@ -667,10 +945,34 @@ class Textdomain {
|
|
|
667
945
|
catalog: catalog,
|
|
668
946
|
});
|
|
669
947
|
}
|
|
948
|
+
/**
|
|
949
|
+
* Translate a string with placeholders for a fixed locale.
|
|
950
|
+
* The placeholders should be
|
|
951
|
+
* wrapped into curly braces and must match the regular expression
|
|
952
|
+
* "[_a-zA-Z][_a-zA-Z0-9]*".
|
|
953
|
+
*
|
|
954
|
+
* @param locale - the locale identifier
|
|
955
|
+
* @param msgid - the msgid to translate
|
|
956
|
+
* @param placeholders - an optional dictionary of placeholders
|
|
957
|
+
*
|
|
958
|
+
* @returns the translated string with placeholders expanded
|
|
959
|
+
*/
|
|
670
960
|
_lx(locale, msgid, placeholders) {
|
|
671
961
|
const catalog = Textdomain.getCatalog(locale, this.textdomain());
|
|
672
962
|
return Textdomain.expand(gettextImpl({ msgid: msgid, catalog: catalog }), placeholders || {});
|
|
673
963
|
}
|
|
964
|
+
/**
|
|
965
|
+
* Translate a string with a plural expression with placeholders into a
|
|
966
|
+
* fixed locale.
|
|
967
|
+
*
|
|
968
|
+
* @param locale - the locale identifier
|
|
969
|
+
* @param msgid - the string in the singular
|
|
970
|
+
* @param msgidPlural - the string in the plural
|
|
971
|
+
* @param numItems - the number of items
|
|
972
|
+
* @param placeholders - an optional dictionary of placeholders
|
|
973
|
+
*
|
|
974
|
+
* @returns the translated string
|
|
975
|
+
*/
|
|
674
976
|
_lnx(locale, msgid, msgidPlural, numItems, placeholders) {
|
|
675
977
|
const catalog = Textdomain.getCatalog(locale, this.textdomain());
|
|
676
978
|
return Textdomain.expand(gettextImpl({
|
|
@@ -680,10 +982,31 @@ class Textdomain {
|
|
|
680
982
|
catalog: catalog,
|
|
681
983
|
}), placeholders || {});
|
|
682
984
|
}
|
|
985
|
+
/**
|
|
986
|
+
* The method `_lpx()` combines `_lp()` with `_lx()`.
|
|
987
|
+
*
|
|
988
|
+
* @param locale - the locale identifier
|
|
989
|
+
* @param msgctxt - the message context
|
|
990
|
+
* @param msgid - the message id
|
|
991
|
+
* @param placeholders - an optional dictionary with placehoders
|
|
992
|
+
* @returns the translated string
|
|
993
|
+
*/
|
|
683
994
|
_lpx(locale, msgctxt, msgid, placeholders) {
|
|
684
995
|
const catalog = Textdomain.getCatalog(locale, this.textdomain());
|
|
685
996
|
return Textdomain.expand(gettextImpl({ msgctxt: msgctxt, msgid: msgid, catalog: catalog }), placeholders || {});
|
|
686
997
|
}
|
|
998
|
+
/**
|
|
999
|
+
* The method `_lnpx()` brings it all together. It combines `_ln()` and
|
|
1000
|
+
* `_lp()` and `_lx()`.
|
|
1001
|
+
*
|
|
1002
|
+
* @param locale - the locale identifier
|
|
1003
|
+
* @param msgctxt - the message context
|
|
1004
|
+
* @param msgid - the message id
|
|
1005
|
+
* @param msgidPlural - the plural string
|
|
1006
|
+
* @param numItems - the number of items
|
|
1007
|
+
* @param placeholders - an optional dictionary with placehoders
|
|
1008
|
+
* @returns the translated string
|
|
1009
|
+
*/
|
|
687
1010
|
_lnpx(locale, msgctxt, msgid, msgidPlural, numItems, placeholders) {
|
|
688
1011
|
const catalog = Textdomain.getCatalog(locale, this.textdomain());
|
|
689
1012
|
return Textdomain.expand(gettextImpl({
|
|
@@ -704,6 +1027,14 @@ class Textdomain {
|
|
|
704
1027
|
}
|
|
705
1028
|
});
|
|
706
1029
|
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Instantiate a Textdomain object. Textdomain objects are singletons
|
|
1032
|
+
* for each textdomain identifier.
|
|
1033
|
+
*
|
|
1034
|
+
* @param textdomain - the textdomain of your application or library.
|
|
1035
|
+
*
|
|
1036
|
+
* @returns a [[`Textdomain`]]
|
|
1037
|
+
*/
|
|
707
1038
|
static getInstance(textdomain) {
|
|
708
1039
|
if (typeof textdomain === 'undefined' ||
|
|
709
1040
|
textdomain === null ||
|
|
@@ -725,16 +1056,49 @@ class Textdomain {
|
|
|
725
1056
|
return domain;
|
|
726
1057
|
}
|
|
727
1058
|
}
|
|
1059
|
+
/**
|
|
1060
|
+
* Delete all existing singletons. This method should usually be called
|
|
1061
|
+
* only, when you want to free memory.
|
|
1062
|
+
*/
|
|
728
1063
|
static clearInstances() {
|
|
729
1064
|
Textdomain.boundDomains = {};
|
|
730
1065
|
}
|
|
1066
|
+
/**
|
|
1067
|
+
* This method is used for testing. Do not use it yourself!
|
|
1068
|
+
*/
|
|
731
1069
|
static forgetInstances() {
|
|
732
1070
|
Textdomain.clearInstances();
|
|
733
1071
|
Textdomain.domains = {};
|
|
734
1072
|
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Query the locale in use.
|
|
1075
|
+
*/
|
|
735
1076
|
static get locale() {
|
|
736
1077
|
return Textdomain._locale;
|
|
737
1078
|
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Change the locale.
|
|
1081
|
+
*
|
|
1082
|
+
* For the web you can use all valid language identifier tags that
|
|
1083
|
+
* [BCP47](https://tools.ietf.org/html/bcp47) allows
|
|
1084
|
+
* (and actually a lot more). The tag is always used unmodified.
|
|
1085
|
+
*
|
|
1086
|
+
* For server environments, the locale identifier has to match the following
|
|
1087
|
+
* scheme:
|
|
1088
|
+
*
|
|
1089
|
+
* `ll_CC.charset\@modifier`
|
|
1090
|
+
*
|
|
1091
|
+
* * `ll` is the two- or three-letter language code.
|
|
1092
|
+
* * `CC` is the optional two-letter country code.
|
|
1093
|
+
* * `charset` is an optional character set (letters, digits, and the hyphen).
|
|
1094
|
+
* * `modifier` is an optional variant (letters and digits).
|
|
1095
|
+
*
|
|
1096
|
+
* The language code is always converted to lowercase, the country code is
|
|
1097
|
+
* converted to uppercase, variant and charset are used as is.
|
|
1098
|
+
*
|
|
1099
|
+
* @param locale - the locale identifier
|
|
1100
|
+
* @returns the locale in use
|
|
1101
|
+
*/
|
|
738
1102
|
static set locale(locale) {
|
|
739
1103
|
const ucLocale = locale.toUpperCase();
|
|
740
1104
|
if (ucLocale === 'POSIX' || ucLocale === 'C') {
|
|
@@ -745,6 +1109,7 @@ class Textdomain {
|
|
|
745
1109
|
if (!split) {
|
|
746
1110
|
throw new Error('invalid locale identifier');
|
|
747
1111
|
}
|
|
1112
|
+
// Node.
|
|
748
1113
|
split.tags[0] = split.tags[0].toLowerCase();
|
|
749
1114
|
if (split.tags.length > 1) {
|
|
750
1115
|
split.tags[1] = split.tags[1].toUpperCase();
|
|
@@ -760,46 +1125,110 @@ class Textdomain {
|
|
|
760
1125
|
}
|
|
761
1126
|
constructor(domain) {
|
|
762
1127
|
this._catalogFormat = 'mo.json';
|
|
1128
|
+
this.catalog = undefined;
|
|
763
1129
|
this.domain = domain;
|
|
1130
|
+
const msg = "The property 'locale' is not an instance property but static. Use 'Textdomain.locale' instead!";
|
|
1131
|
+
Object.defineProperty(this, 'locale', {
|
|
1132
|
+
get: () => {
|
|
1133
|
+
throw new Error(msg);
|
|
1134
|
+
},
|
|
1135
|
+
set: () => {
|
|
1136
|
+
throw new Error(msg);
|
|
1137
|
+
},
|
|
1138
|
+
enumerable: true,
|
|
1139
|
+
configurable: true,
|
|
1140
|
+
});
|
|
764
1141
|
}
|
|
1142
|
+
/**
|
|
1143
|
+
* A textdomain is an identifier for your application or library. It is
|
|
1144
|
+
* the basename of your translation files which are either
|
|
1145
|
+
* TEXTDOMAIN.mo.json or TEXTDOMAIN.mo, depending on the format you have
|
|
1146
|
+
* chosen.
|
|
1147
|
+
*
|
|
1148
|
+
* FIXME! This should be a getter!
|
|
1149
|
+
*
|
|
1150
|
+
* @returns the textdomain
|
|
1151
|
+
*/
|
|
765
1152
|
textdomain() {
|
|
766
1153
|
return this.domain;
|
|
767
1154
|
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Bind a textdomain to a certain path or queries the path that a
|
|
1157
|
+
* textdomain is bound to. The catalog file will be searched
|
|
1158
|
+
* in `${path}/LC_MESSAGES/${domainname}.EXT` where `EXT` is the
|
|
1159
|
+
* selected catalog format (one of `mo.json`, `mo`, or `json`).
|
|
1160
|
+
*
|
|
1161
|
+
* Alternatively, you can pass a [[`LocaleContainer`]] that holds the
|
|
1162
|
+
* catalogs in memory.
|
|
1163
|
+
*
|
|
1164
|
+
* The returned string or `LocaleContainer` is valid until the next
|
|
1165
|
+
* `bindtextdomain` call with an argument.
|
|
1166
|
+
*
|
|
1167
|
+
* @param path - the base path or [[`LocaleContainer`]] for this textdomain
|
|
1168
|
+
*
|
|
1169
|
+
* @returns the current base directory or [[`LocaleContainer`]] for this domain, after possibly changing it.
|
|
1170
|
+
*/
|
|
768
1171
|
bindtextdomain(path) {
|
|
769
1172
|
if (typeof path !== 'undefined') {
|
|
770
1173
|
Textdomain.boundDomains[this.domain] = path;
|
|
771
1174
|
}
|
|
772
1175
|
return Textdomain.boundDomains[this.domain];
|
|
773
1176
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1177
|
+
/**
|
|
1178
|
+
* Resolve a textdomain, i.e. load the LocaleContainer for this domain and all
|
|
1179
|
+
* of its dependencies for the currently selected locale or the locale
|
|
1180
|
+
* specified.
|
|
1181
|
+
*
|
|
1182
|
+
* The promise will always resolve. If no catalog was found, an empty
|
|
1183
|
+
* catalog will be returned that is still usable.
|
|
1184
|
+
*
|
|
1185
|
+
* @param locale - an optional locale identifier, defaults to Textdomain.locale
|
|
1186
|
+
*
|
|
1187
|
+
* @returns a promise for a Catalog that will always resolve.
|
|
1188
|
+
*/
|
|
1189
|
+
resolve(locale) {
|
|
1190
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1191
|
+
const promises = [this.resolve1(locale)];
|
|
1192
|
+
for (const td in Textdomain.domains) {
|
|
1193
|
+
if (Object.prototype.hasOwnProperty.call(Textdomain.domains, td) &&
|
|
1194
|
+
Textdomain.domains[td] !== this) {
|
|
1195
|
+
promises.push(Textdomain.domains[td].resolve1(locale));
|
|
1196
|
+
}
|
|
780
1197
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
1198
|
+
return Promise.all(promises).then(values => {
|
|
1199
|
+
return new Promise(resolve => resolve(values[0]));
|
|
1200
|
+
});
|
|
784
1201
|
});
|
|
785
1202
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
const resolvedLocale = locale ? locale : Textdomain.locale;
|
|
793
|
-
return resolveImpl(this.domain, path, this.catalogFormat, resolvedLocale).then(catalog => {
|
|
794
|
-
if (!locale) {
|
|
795
|
-
this.catalog = catalog;
|
|
1203
|
+
resolve1(locale) {
|
|
1204
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1205
|
+
let path = this.bindtextdomain();
|
|
1206
|
+
if (typeof path === 'undefined' || path === null) {
|
|
1207
|
+
const parts = ['.', 'locale'];
|
|
1208
|
+
path = parts.join(pathSeparator);
|
|
796
1209
|
}
|
|
797
|
-
|
|
1210
|
+
const resolvedLocale = locale ? locale : Textdomain.locale;
|
|
1211
|
+
return resolveImpl(this.domain, path, this.catalogFormat, resolvedLocale).then(catalog => {
|
|
1212
|
+
if (!locale) {
|
|
1213
|
+
this.catalog = catalog;
|
|
1214
|
+
}
|
|
1215
|
+
return new Promise(resolve => resolve(catalog));
|
|
1216
|
+
});
|
|
798
1217
|
});
|
|
799
1218
|
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Get the catalog format in use.
|
|
1221
|
+
*
|
|
1222
|
+
* @returns one of 'mo.json' or 'mo' (default is 'mo.json')
|
|
1223
|
+
*/
|
|
800
1224
|
get catalogFormat() {
|
|
801
1225
|
return this._catalogFormat;
|
|
802
1226
|
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Set the catalog format to use.
|
|
1229
|
+
*
|
|
1230
|
+
* @param format - one of 'mo.json' or 'mo'
|
|
1231
|
+
*/
|
|
803
1232
|
set catalogFormat(format) {
|
|
804
1233
|
format = format.toLowerCase();
|
|
805
1234
|
if (format === 'mo.json') {
|
|
@@ -812,42 +1241,156 @@ class Textdomain {
|
|
|
812
1241
|
throw new Error(`unsupported format ${format}`);
|
|
813
1242
|
}
|
|
814
1243
|
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Queries the user's preferred locales. On the server it queries the
|
|
1246
|
+
* environment variables `LANGUAGE`, `LC_ALL`, `LANG`, and `LC_MESSAGES`
|
|
1247
|
+
* (in that order). In the browser, it parses it checks the user preferences
|
|
1248
|
+
* in the variables `navigator.languages`, `navigator.language`,
|
|
1249
|
+
* `navigator.userLanguage`, `navigator.browserLanguage`, and
|
|
1250
|
+
* `navigator.systemLanguage`.
|
|
1251
|
+
*
|
|
1252
|
+
* @returns the set of locales in order of preference
|
|
1253
|
+
*
|
|
1254
|
+
* Added in \@runtime 0.1.0.
|
|
1255
|
+
*/
|
|
815
1256
|
static userLocales() {
|
|
816
1257
|
return userLocales();
|
|
817
1258
|
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Select one of the supported locales from a list of locales accepted by
|
|
1261
|
+
* the user.
|
|
1262
|
+
*
|
|
1263
|
+
* @param supported - the list of locales supported by the application
|
|
1264
|
+
* @param requested - the list of locales accepted by the user
|
|
1265
|
+
*
|
|
1266
|
+
* If called with just one argument, then the list of requested locales
|
|
1267
|
+
* is determined by calling [[Textdomain.userLocales]].
|
|
1268
|
+
*
|
|
1269
|
+
* @returns the negotiated locale or 'C' if not possible.
|
|
1270
|
+
*/
|
|
818
1271
|
static selectLocale(supported, requested) {
|
|
819
1272
|
return selectLocale(supported, requested !== null && requested !== void 0 ? requested : Textdomain.userLocales());
|
|
820
1273
|
}
|
|
1274
|
+
/**
|
|
1275
|
+
* A no-op method for string marking.
|
|
1276
|
+
*
|
|
1277
|
+
* Sometimes you want to mark strings for translation but do not actually
|
|
1278
|
+
* want to translate them, at least not at the time of their definition.
|
|
1279
|
+
* This is often the case, when you have to preserve the original string.
|
|
1280
|
+
*
|
|
1281
|
+
* Take this example:
|
|
1282
|
+
*
|
|
1283
|
+
* ```
|
|
1284
|
+
* orangeColors = [gtx.N_('coral'), gtx.N_('tomato'), gtx.N_('orangered'),
|
|
1285
|
+
* gtx.N_('gold'), gtx.N_('orange'), gtx.N_('darkorange')]
|
|
1286
|
+
* ```
|
|
1287
|
+
*
|
|
1288
|
+
* These are standard CSS colors, and you cannot translate them inside
|
|
1289
|
+
* CSS styles. But for presentation you may want to translate them later:
|
|
1290
|
+
*
|
|
1291
|
+
* ```
|
|
1292
|
+
* console.log(gtx._x("The css color '{color}' is {translated}.",
|
|
1293
|
+
* {
|
|
1294
|
+
* color: orangeColors[2],
|
|
1295
|
+
* translated: gtx._(orangeColors[2]),
|
|
1296
|
+
* }
|
|
1297
|
+
* )
|
|
1298
|
+
* );
|
|
1299
|
+
* ```
|
|
1300
|
+
*
|
|
1301
|
+
* In other words: The method just marks strings for translation, so that
|
|
1302
|
+
* the extractor `esgettext-xgettext` finds them but it does not actually
|
|
1303
|
+
* translate anything.
|
|
1304
|
+
*
|
|
1305
|
+
* Similar methods are available for other cases (with placeholder
|
|
1306
|
+
* expansion, context, or both). They are *not* available for plural
|
|
1307
|
+
* methods because that would not make sense.
|
|
1308
|
+
*
|
|
1309
|
+
* Note that all of these methods are also available as instance methods.
|
|
1310
|
+
*
|
|
1311
|
+
* @param msgid - the message id
|
|
1312
|
+
* @returns the original string
|
|
1313
|
+
*/
|
|
821
1314
|
static N_(msgid) {
|
|
822
1315
|
return msgid;
|
|
823
1316
|
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Does the same as the static method `N_()`.
|
|
1319
|
+
*
|
|
1320
|
+
* @param msgid - the message id
|
|
1321
|
+
* @returns the original string
|
|
1322
|
+
*/
|
|
824
1323
|
N_(msgid) {
|
|
825
1324
|
return msgid;
|
|
826
1325
|
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Same as `N_()` but with placeholder expansion.
|
|
1328
|
+
*
|
|
1329
|
+
* @param msgid - the message id
|
|
1330
|
+
* @param placeholders - a dictionary of placeholders
|
|
1331
|
+
* @returns the original string with placeholders expanded
|
|
1332
|
+
*/
|
|
827
1333
|
N_x(msgid, placeholders) {
|
|
828
1334
|
return Textdomain.expand(msgid, placeholders !== null && placeholders !== void 0 ? placeholders : {});
|
|
829
1335
|
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Does the same as the static method `N_x()`.
|
|
1338
|
+
*
|
|
1339
|
+
* @param msgid - the message id
|
|
1340
|
+
* @param placeholders - a dictionary of placeholders
|
|
1341
|
+
* @returns the original string with placeholders expanded
|
|
1342
|
+
*/
|
|
830
1343
|
static N_x(msgid, placeholders) {
|
|
831
1344
|
return Textdomain.expand(msgid, placeholders !== null && placeholders !== void 0 ? placeholders : {});
|
|
832
1345
|
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Same as `N_()` but with context.
|
|
1348
|
+
*
|
|
1349
|
+
* @param _msgctxt - the message context (not used)
|
|
1350
|
+
* @param msgid - the message id
|
|
1351
|
+
* @returns the original string
|
|
1352
|
+
*/
|
|
833
1353
|
N_p(_msgctxt, msgid) {
|
|
834
1354
|
return msgid;
|
|
835
1355
|
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Does the same as the static method `N_p()`.
|
|
1358
|
+
*
|
|
1359
|
+
* @param _msgctxt - the message context (not used)
|
|
1360
|
+
* @param msgid - the message id
|
|
1361
|
+
* @returns the original string with placeholders expanded
|
|
1362
|
+
*/
|
|
836
1363
|
static N_p(_msgctxt, msgid) {
|
|
837
1364
|
return msgid;
|
|
838
1365
|
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Same as `N_()` but with context and placeholder expansion.
|
|
1368
|
+
*
|
|
1369
|
+
* @param _msgctxt - the message context (not used)
|
|
1370
|
+
* @param msgid - the message id
|
|
1371
|
+
* @param placeholders - a dictionary of placeholders
|
|
1372
|
+
* @returns the original string with placeholders expanded
|
|
1373
|
+
*/
|
|
839
1374
|
N_px(_msgctxt, msgid, placeholders) {
|
|
840
1375
|
return Textdomain.expand(msgid, placeholders !== null && placeholders !== void 0 ? placeholders : {});
|
|
841
1376
|
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Does the same as the static method `N_px()`.
|
|
1379
|
+
*
|
|
1380
|
+
* @param _msgctxt - the message context (not used)
|
|
1381
|
+
* @param msgid - the message id
|
|
1382
|
+
* @param placeholders - a dictionary of placeholders
|
|
1383
|
+
* @returns the original string with placeholders expanded
|
|
1384
|
+
*/
|
|
842
1385
|
static N_px(_msgctxt, msgid, placeholders) {
|
|
843
1386
|
return Textdomain.expand(msgid, placeholders !== null && placeholders !== void 0 ? placeholders : {});
|
|
844
1387
|
}
|
|
845
1388
|
}
|
|
846
1389
|
Textdomain.domains = {};
|
|
847
|
-
Textdomain.cache = CatalogCache.getInstance();
|
|
848
1390
|
Textdomain.boundDomains = {};
|
|
849
1391
|
Textdomain._locale = 'C';
|
|
850
1392
|
|
|
1393
|
+
// FIXME! Windows!
|
|
851
1394
|
if (typeof process.env.LANGUAGE !== 'undefined') {
|
|
852
1395
|
userLocales(process.env.LANGUAGE.split(':'));
|
|
853
1396
|
}
|