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