locl-js 1.0.1 → 2.0.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 +21 -21
- package/README.md +518 -0
- package/dist/LocL-fN9Yari8.d.mts +437 -0
- package/dist/LocL-fN9Yari8.d.ts +437 -0
- package/dist/compat.d.mts +44 -0
- package/dist/compat.d.ts +44 -0
- package/dist/compat.js +137 -0
- package/dist/compat.mjs +111 -0
- package/dist/index.d.mts +20 -341
- package/dist/index.d.ts +20 -341
- package/dist/index.js +298 -84
- package/dist/index.mjs +293 -81
- package/dist/react.d.mts +120 -0
- package/dist/react.d.ts +120 -0
- package/dist/react.js +228 -0
- package/dist/react.mjs +188 -0
- package/package.json +28 -5
- package/readme.md +0 -319
package/dist/index.mjs
CHANGED
|
@@ -32,7 +32,7 @@ var defaultFormatters = {
|
|
|
32
32
|
* @param {string} [args[1]="..."] - The suffix to append if the string is truncated.
|
|
33
33
|
* @returns The truncated string.
|
|
34
34
|
*/
|
|
35
|
-
truncate: (val, args) => {
|
|
35
|
+
truncate: (val, args = []) => {
|
|
36
36
|
const length = parseInt(args[0]) || 10;
|
|
37
37
|
const suffix = args[1] || "...";
|
|
38
38
|
const str = String(val);
|
|
@@ -46,7 +46,7 @@ var defaultFormatters = {
|
|
|
46
46
|
* @param {string} [args[1]] - The style of formatting to use (e.g., "decimal", "percent").
|
|
47
47
|
* @returns The formatted number.
|
|
48
48
|
*/
|
|
49
|
-
number: (val, args) => {
|
|
49
|
+
number: (val, args = []) => {
|
|
50
50
|
const locales = args[0] || void 0;
|
|
51
51
|
const options = {};
|
|
52
52
|
if (args[1]) options.style = args[1];
|
|
@@ -60,7 +60,7 @@ var defaultFormatters = {
|
|
|
60
60
|
* @param {string} [args[1]] - The locale to use.
|
|
61
61
|
* @returns The formatted currency string.
|
|
62
62
|
*/
|
|
63
|
-
currency: (val, args) => {
|
|
63
|
+
currency: (val, args = []) => {
|
|
64
64
|
const currency = args[0] || "USD";
|
|
65
65
|
const locales = args[1] || void 0;
|
|
66
66
|
return new Intl.NumberFormat(locales, {
|
|
@@ -76,7 +76,7 @@ var defaultFormatters = {
|
|
|
76
76
|
* @param {string} [args[1]] - The date style to use (e.g., "short", "long").
|
|
77
77
|
* @returns The formatted date string.
|
|
78
78
|
*/
|
|
79
|
-
date: (val, args) => {
|
|
79
|
+
date: (val, args = []) => {
|
|
80
80
|
const locales = args[0] || void 0;
|
|
81
81
|
const options = {};
|
|
82
82
|
if (args[1]) options.dateStyle = args[1];
|
|
@@ -89,7 +89,7 @@ var defaultFormatters = {
|
|
|
89
89
|
* @param {string} [args[0]] - The locale to use.
|
|
90
90
|
* @returns The relative time string.
|
|
91
91
|
*/
|
|
92
|
-
relativeDate: (val, args) => {
|
|
92
|
+
relativeDate: (val, args = []) => {
|
|
93
93
|
const locales = args[0] || void 0;
|
|
94
94
|
const options = { numeric: "auto" };
|
|
95
95
|
const rtf = new Intl.RelativeTimeFormat(locales, options);
|
|
@@ -113,7 +113,7 @@ var defaultFormatters = {
|
|
|
113
113
|
* @param {string} [args[1]="No"] - The string to return for a falsy value.
|
|
114
114
|
* @returns "Yes" or "No".
|
|
115
115
|
*/
|
|
116
|
-
yesNo: (val, args) => {
|
|
116
|
+
yesNo: (val, args = []) => {
|
|
117
117
|
const [yesVal, noVal] = args || [];
|
|
118
118
|
return val ? yesVal || "Yes" : noVal || "No";
|
|
119
119
|
},
|
|
@@ -125,7 +125,7 @@ var defaultFormatters = {
|
|
|
125
125
|
* @param {string} [args[1]="false"] - The string to return for a falsy value.
|
|
126
126
|
* @returns "true" or "false".
|
|
127
127
|
*/
|
|
128
|
-
boolean: (val, args) => {
|
|
128
|
+
boolean: (val, args = []) => {
|
|
129
129
|
const [trueVal, falseVal] = args || [];
|
|
130
130
|
return val ? trueVal || "true" : falseVal || "false";
|
|
131
131
|
},
|
|
@@ -137,7 +137,7 @@ var defaultFormatters = {
|
|
|
137
137
|
* @param {string} [args[1]=" "] - The string to pad with.
|
|
138
138
|
* @returns The padded string.
|
|
139
139
|
*/
|
|
140
|
-
padStart: (val, args) => {
|
|
140
|
+
padStart: (val, args = []) => {
|
|
141
141
|
const length = parseInt(args[0]) || 0;
|
|
142
142
|
const fill = args[1] || " ";
|
|
143
143
|
return String(val).padStart(length, fill);
|
|
@@ -150,7 +150,7 @@ var defaultFormatters = {
|
|
|
150
150
|
* @param {string} [args[1]=" "] - The string to pad with.
|
|
151
151
|
* @returns The padded string.
|
|
152
152
|
*/
|
|
153
|
-
padEnd: (val, args) => {
|
|
153
|
+
padEnd: (val, args = []) => {
|
|
154
154
|
const length = parseInt(args[0]) || 0;
|
|
155
155
|
const fill = args[1] || " ";
|
|
156
156
|
return String(val).padEnd(length, fill);
|
|
@@ -166,25 +166,26 @@ var LocL = class _LocL {
|
|
|
166
166
|
constructor(config) {
|
|
167
167
|
this.cache = /* @__PURE__ */ new Map();
|
|
168
168
|
this.proxyCache = /* @__PURE__ */ new WeakMap();
|
|
169
|
+
this.pluralRulesCache = /* @__PURE__ */ new Map();
|
|
170
|
+
this.subscribers = /* @__PURE__ */ new Set();
|
|
171
|
+
this.rootInstance = this;
|
|
169
172
|
if (!config.resources) {
|
|
170
173
|
throw new Error("[LocL] `resources` is required");
|
|
171
174
|
}
|
|
172
175
|
if (!config.fallbackLanguage) {
|
|
173
176
|
throw new Error("[LocL] `fallbackLanguage` is required");
|
|
174
177
|
}
|
|
175
|
-
config = {
|
|
178
|
+
this.config = {
|
|
176
179
|
useDefaultFormatters: true,
|
|
177
180
|
devMode: false,
|
|
178
181
|
useCache: true,
|
|
179
182
|
...config
|
|
180
183
|
};
|
|
181
|
-
|
|
182
|
-
this.config = config;
|
|
184
|
+
this.resources = { ...config.resources };
|
|
183
185
|
this.language = this.isLanguage(config.language) ?? config.fallbackLanguage;
|
|
184
186
|
this.fallbackLanguage = config.fallbackLanguage;
|
|
185
187
|
this.scope = config.scope;
|
|
186
|
-
this.
|
|
187
|
-
this.formatters = config.useDefaultFormatters ? { ...defaultFormatters, ...config.formatters ?? {} } : { ...config.formatters ?? {} };
|
|
188
|
+
this.formatters = this.config.useDefaultFormatters ? { ...defaultFormatters, ...config.formatters ?? {} } : { ...config.formatters ?? {} };
|
|
188
189
|
}
|
|
189
190
|
/**
|
|
190
191
|
* Creates a new proxy translator instance with a different configuration.
|
|
@@ -196,21 +197,40 @@ var LocL = class _LocL {
|
|
|
196
197
|
* @returns A new proxy `LocL` instance.
|
|
197
198
|
*/
|
|
198
199
|
withConfig(config) {
|
|
200
|
+
const root = this.rootInstance;
|
|
199
201
|
const cacheKey = `proxy::${config.language ?? this.language}::${Array.isArray(config.scope) ? config.scope.join("|") : config.scope ?? "*"}`;
|
|
200
|
-
if (
|
|
201
|
-
return
|
|
202
|
+
if (root.cache.has(cacheKey)) {
|
|
203
|
+
return root.cache.get(cacheKey);
|
|
202
204
|
}
|
|
203
|
-
|
|
204
|
-
get(target, prop) {
|
|
205
|
+
const result = new Proxy(root, {
|
|
206
|
+
get(target, prop, receiver) {
|
|
205
207
|
if (prop === "language") {
|
|
206
208
|
return config.language ?? target.language;
|
|
207
209
|
}
|
|
208
210
|
if (prop === "scope") {
|
|
209
|
-
return config.scope;
|
|
211
|
+
return config.scope !== void 0 ? config.scope : target.scope;
|
|
212
|
+
}
|
|
213
|
+
if (prop === "rootInstance") {
|
|
214
|
+
return target;
|
|
215
|
+
}
|
|
216
|
+
if (prop === "subscribe" && config.language !== void 0) {
|
|
217
|
+
return (listener) => {
|
|
218
|
+
try {
|
|
219
|
+
listener(config.language, config.language);
|
|
220
|
+
} catch (err) {
|
|
221
|
+
console.error("[LocL] Error in subscriber:", err);
|
|
222
|
+
}
|
|
223
|
+
return () => {
|
|
224
|
+
};
|
|
225
|
+
};
|
|
210
226
|
}
|
|
211
|
-
return target
|
|
227
|
+
return Reflect.get(target, prop, receiver);
|
|
212
228
|
}
|
|
213
229
|
});
|
|
230
|
+
if (root.config.useCache) {
|
|
231
|
+
root.cache.set(cacheKey, result);
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
214
234
|
}
|
|
215
235
|
/**
|
|
216
236
|
* Creates a new `LocL` instance with a different language or scope.
|
|
@@ -221,18 +241,106 @@ var LocL = class _LocL {
|
|
|
221
241
|
clone(language = this.language, scope) {
|
|
222
242
|
return new _LocL({
|
|
223
243
|
...this.config,
|
|
244
|
+
resources: this.resources,
|
|
224
245
|
language,
|
|
225
246
|
scope
|
|
226
247
|
});
|
|
227
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* Subscribes a listener to language changes.
|
|
251
|
+
* Conforms to the standard reactive store contract (e.g. Svelte stores).
|
|
252
|
+
* Calls the listener immediately with the current language and returns an unsubscribe function.
|
|
253
|
+
* @param listener - The subscriber callback function.
|
|
254
|
+
* @returns An unsubscribe function.
|
|
255
|
+
*/
|
|
256
|
+
subscribe(listener) {
|
|
257
|
+
this.subscribers.add(listener);
|
|
258
|
+
try {
|
|
259
|
+
listener(this.language, this.language);
|
|
260
|
+
} catch (err) {
|
|
261
|
+
console.error("[LocL] Error in initial subscriber call:", err);
|
|
262
|
+
}
|
|
263
|
+
return () => {
|
|
264
|
+
this.subscribers.delete(listener);
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Gets the current active language.
|
|
269
|
+
*/
|
|
270
|
+
getLanguage() {
|
|
271
|
+
return this.language;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Adds or overrides a single translation key at runtime.
|
|
275
|
+
* @param lang - Target language code.
|
|
276
|
+
* @param key - Dotted path key.
|
|
277
|
+
* @param value - The translation value.
|
|
278
|
+
*/
|
|
279
|
+
addResource(lang, key, value) {
|
|
280
|
+
if (!this.resources[lang]) {
|
|
281
|
+
this.resources[lang] = {};
|
|
282
|
+
}
|
|
283
|
+
const keys = key.split(".");
|
|
284
|
+
let current = this.resources[lang];
|
|
285
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
286
|
+
const k = keys[i];
|
|
287
|
+
if (k === "__proto__" || k === "constructor" || k === "prototype") {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (!current[k] || typeof current[k] !== "object") {
|
|
291
|
+
current[k] = {};
|
|
292
|
+
}
|
|
293
|
+
current = current[k];
|
|
294
|
+
}
|
|
295
|
+
const lastKey = keys[keys.length - 1];
|
|
296
|
+
if (lastKey !== "__proto__" && lastKey !== "constructor" && lastKey !== "prototype") {
|
|
297
|
+
current[lastKey] = value;
|
|
298
|
+
}
|
|
299
|
+
this.invalidateCacheForLang(lang);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Deeply merges a resource bundle into the specified language at runtime.
|
|
303
|
+
* @param lang - Target language code.
|
|
304
|
+
* @param bundle - Object of translations to merge.
|
|
305
|
+
*/
|
|
306
|
+
addResources(lang, bundle) {
|
|
307
|
+
if (!this.resources[lang]) {
|
|
308
|
+
this.resources[lang] = {};
|
|
309
|
+
}
|
|
310
|
+
const deepMerge = (target, source) => {
|
|
311
|
+
for (const k of Object.keys(source)) {
|
|
312
|
+
if (k === "__proto__" || k === "constructor" || k === "prototype") {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (source[k] && typeof source[k] === "object" && !Array.isArray(source[k])) {
|
|
316
|
+
if (!target[k] || typeof target[k] !== "object") {
|
|
317
|
+
target[k] = {};
|
|
318
|
+
}
|
|
319
|
+
deepMerge(target[k], source[k]);
|
|
320
|
+
} else {
|
|
321
|
+
target[k] = source[k];
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
deepMerge(this.resources[lang], bundle);
|
|
326
|
+
this.invalidateCacheForLang(lang);
|
|
327
|
+
}
|
|
228
328
|
/**
|
|
229
329
|
* Changes the current language of the translator.
|
|
230
330
|
* @param lang - The new language to set.
|
|
231
331
|
*/
|
|
232
332
|
changeLanguage(lang) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
333
|
+
const validated = this.isLanguage(lang);
|
|
334
|
+
if (validated && validated !== this.language) {
|
|
335
|
+
const prev = this.language;
|
|
336
|
+
this.language = validated;
|
|
337
|
+
for (const sub of this.subscribers) {
|
|
338
|
+
try {
|
|
339
|
+
sub(this.language, prev);
|
|
340
|
+
} catch (err) {
|
|
341
|
+
console.error("[LocL] Error in subscriber:", err);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
236
344
|
}
|
|
237
345
|
}
|
|
238
346
|
t(key, values, format) {
|
|
@@ -241,6 +349,9 @@ var LocL = class _LocL {
|
|
|
241
349
|
}
|
|
242
350
|
const pluralCheckResult = this.checkPlural(key, values);
|
|
243
351
|
if (pluralCheckResult !== void 0) {
|
|
352
|
+
if (format && format.formatter) {
|
|
353
|
+
return this.applyFormat(pluralCheckResult, format.formatter, format.args);
|
|
354
|
+
}
|
|
244
355
|
return pluralCheckResult;
|
|
245
356
|
}
|
|
246
357
|
const result = this.lookupWithFallback(key);
|
|
@@ -271,45 +382,54 @@ var LocL = class _LocL {
|
|
|
271
382
|
format(value, formatter, args) {
|
|
272
383
|
return this.applyFormat(value, formatter, args);
|
|
273
384
|
}
|
|
385
|
+
plural(key, values, format) {
|
|
386
|
+
return this.t(key, values, format);
|
|
387
|
+
}
|
|
274
388
|
/**
|
|
275
|
-
* Translates a key
|
|
276
|
-
*
|
|
277
|
-
* @param key - The
|
|
278
|
-
* @param
|
|
279
|
-
* @param
|
|
280
|
-
* @returns
|
|
389
|
+
* Translates a key and interpolates rich elements using tag functions.
|
|
390
|
+
* Matches `<tag>content</tag>` in translation templates.
|
|
391
|
+
* @param key - The key to translate.
|
|
392
|
+
* @param tags - An object mapping tag names to functions that transform their inner content.
|
|
393
|
+
* @param values - Values to interpolate into variable placeholders ({name}, {{name}}).
|
|
394
|
+
* @returns An array of chunks (strings and custom rendered tag values).
|
|
395
|
+
* @example
|
|
396
|
+
* ```ts
|
|
397
|
+
* translator.rich("terms", {
|
|
398
|
+
* link: (content) => `<a href="/terms">${content}</a>`
|
|
399
|
+
* }, { name: "Alice" });
|
|
400
|
+
* ```
|
|
281
401
|
*/
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
this.config.devMode && console.warn(`[LocL] Missing plural: "${baseKey}" in "${this.language}"`);
|
|
295
|
-
interpolated = baseKey;
|
|
402
|
+
rich(key, tags, values) {
|
|
403
|
+
const rawStr = this.t(key, values);
|
|
404
|
+
if (typeof rawStr !== "string") {
|
|
405
|
+
return [rawStr];
|
|
406
|
+
}
|
|
407
|
+
const tagRegex = /<([a-zA-Z0-9_-]+)>([\s\S]*?)<\/\1>/g;
|
|
408
|
+
const result = [];
|
|
409
|
+
let lastIndex = 0;
|
|
410
|
+
let match;
|
|
411
|
+
while ((match = tagRegex.exec(rawStr)) !== null) {
|
|
412
|
+
if (match.index > lastIndex) {
|
|
413
|
+
result.push(rawStr.slice(lastIndex, match.index));
|
|
296
414
|
}
|
|
297
|
-
|
|
298
|
-
const
|
|
299
|
-
const
|
|
300
|
-
|
|
415
|
+
const tagName = match[1];
|
|
416
|
+
const content = match[2];
|
|
417
|
+
const renderer = tags[tagName];
|
|
418
|
+
typeof renderer === "function" ? result.push(renderer(content)) : result.push(match[0]);
|
|
419
|
+
lastIndex = match.index + match[0].length;
|
|
301
420
|
}
|
|
302
|
-
if (
|
|
303
|
-
|
|
421
|
+
if (lastIndex < rawStr.length) {
|
|
422
|
+
result.push(rawStr.slice(lastIndex));
|
|
304
423
|
}
|
|
305
|
-
return
|
|
424
|
+
return result;
|
|
306
425
|
}
|
|
307
426
|
get(key) {
|
|
308
427
|
const base = this.buildTranslationObject(this.language);
|
|
309
428
|
if (!key) {
|
|
310
|
-
return base;
|
|
429
|
+
return base ? this.makeReadOnly(base) : void 0;
|
|
311
430
|
}
|
|
312
|
-
|
|
431
|
+
const val = this.lookupWithFallback(key, base);
|
|
432
|
+
return val && typeof val === "object" ? this.makeReadOnly(val) : val;
|
|
313
433
|
}
|
|
314
434
|
/**
|
|
315
435
|
* Gets a nested object from the translations.
|
|
@@ -324,12 +444,27 @@ var LocL = class _LocL {
|
|
|
324
444
|
if (!value) {
|
|
325
445
|
return null;
|
|
326
446
|
}
|
|
327
|
-
const
|
|
328
|
-
|
|
447
|
+
const available = Object.keys(this.resources);
|
|
448
|
+
if (available.includes(value)) {
|
|
449
|
+
return value;
|
|
450
|
+
}
|
|
451
|
+
const prefix = value.substring(0, 2);
|
|
452
|
+
if (available.includes(prefix)) {
|
|
453
|
+
return prefix;
|
|
454
|
+
}
|
|
455
|
+
return null;
|
|
329
456
|
}
|
|
330
457
|
getCacheKey(language) {
|
|
331
|
-
const scopeKey = Array.isArray(this.scope) ? this.scope.join("|") : this.scope
|
|
332
|
-
return `${language
|
|
458
|
+
const scopeKey = Array.isArray(this.scope) ? this.scope.join("|") : this.scope;
|
|
459
|
+
return `${language}::${scopeKey}`;
|
|
460
|
+
}
|
|
461
|
+
invalidateCacheForLang(lang) {
|
|
462
|
+
const prefix = `${lang}::`;
|
|
463
|
+
for (const key of this.cache.keys()) {
|
|
464
|
+
if (key.startsWith(prefix)) {
|
|
465
|
+
this.cache.delete(key);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
333
468
|
}
|
|
334
469
|
lookupWithFallback(key, base) {
|
|
335
470
|
const keys = key.split(".");
|
|
@@ -347,7 +482,7 @@ var LocL = class _LocL {
|
|
|
347
482
|
return result;
|
|
348
483
|
}
|
|
349
484
|
buildTranslationObject(language) {
|
|
350
|
-
const langObject = this.
|
|
485
|
+
const langObject = this.resources[language];
|
|
351
486
|
if (!this.scope) {
|
|
352
487
|
return langObject;
|
|
353
488
|
}
|
|
@@ -378,11 +513,10 @@ var LocL = class _LocL {
|
|
|
378
513
|
}, combined);
|
|
379
514
|
}
|
|
380
515
|
}
|
|
381
|
-
const roCombined = this.makeReadOnly(combined);
|
|
382
516
|
if (this.config.useCache) {
|
|
383
|
-
this.cache.set(cacheKey,
|
|
517
|
+
this.cache.set(cacheKey, combined);
|
|
384
518
|
}
|
|
385
|
-
return
|
|
519
|
+
return combined;
|
|
386
520
|
}
|
|
387
521
|
if (this.config.useCache) {
|
|
388
522
|
this.cache.set(cacheKey, void 0);
|
|
@@ -412,13 +546,54 @@ var LocL = class _LocL {
|
|
|
412
546
|
}
|
|
413
547
|
return current;
|
|
414
548
|
}
|
|
549
|
+
getPluralRules(lang) {
|
|
550
|
+
let rules = this.pluralRulesCache.get(lang);
|
|
551
|
+
if (!rules) {
|
|
552
|
+
rules = new Intl.PluralRules(lang);
|
|
553
|
+
this.pluralRulesCache.set(lang, rules);
|
|
554
|
+
}
|
|
555
|
+
return rules;
|
|
556
|
+
}
|
|
415
557
|
checkPlural(key, values) {
|
|
416
|
-
if (values?.count !== void 0 && !key.match(/_(one|few|many|other)$/)) {
|
|
417
|
-
const category = this.
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
|
|
558
|
+
if (values?.count !== void 0 && !key.match(/_(zero|one|two|few|many|other)$/)) {
|
|
559
|
+
const category = this.getPluralRules(this.language).select(values.count);
|
|
560
|
+
const baseValue = this.lookupWithFallback(key, void 0);
|
|
561
|
+
if (baseValue && typeof baseValue === "object") {
|
|
562
|
+
if (values.count === 0 && "zero" in baseValue) {
|
|
563
|
+
return this.interpolate(baseValue.zero, values);
|
|
564
|
+
}
|
|
565
|
+
if (category in baseValue) {
|
|
566
|
+
return this.interpolate(baseValue[category], values);
|
|
567
|
+
} else if ("other" in baseValue) {
|
|
568
|
+
this.config.devMode && console.warn(`[LocL] Missing plural: "${key}" in "${this.language}"`);
|
|
569
|
+
return this.interpolate(baseValue.other, values);
|
|
570
|
+
} else {
|
|
571
|
+
this.config.devMode && console.warn(`[LocL] Missing plural: "${key}" in "${this.language}"`);
|
|
572
|
+
return key;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (values.count === 0) {
|
|
576
|
+
const zeroResult = this.lookupWithFallback(`${key}_zero`, void 0);
|
|
577
|
+
if (zeroResult !== void 0) {
|
|
578
|
+
return this.interpolate(zeroResult, values);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const categoryResult = this.lookupWithFallback(`${key}_${category}`, void 0);
|
|
582
|
+
if (categoryResult !== void 0) {
|
|
583
|
+
return this.interpolate(categoryResult, values);
|
|
584
|
+
}
|
|
585
|
+
const otherResult = this.lookupWithFallback(`${key}_other`, void 0);
|
|
586
|
+
if (otherResult !== void 0) {
|
|
587
|
+
this.config.devMode && console.warn(`[LocL] Missing plural: "${key}" in "${this.language}"`);
|
|
588
|
+
return this.interpolate(otherResult, values);
|
|
589
|
+
}
|
|
590
|
+
const anySuffixExists = ["zero", "one", "two", "few", "many"].some((cat) => this.lookupWithFallback(`${key}_${cat}`, void 0) !== void 0);
|
|
591
|
+
if (anySuffixExists) {
|
|
592
|
+
this.config.devMode && console.warn(`[LocL] Missing plural: "${key}" in "${this.language}"`);
|
|
593
|
+
return key;
|
|
594
|
+
}
|
|
421
595
|
}
|
|
596
|
+
return void 0;
|
|
422
597
|
}
|
|
423
598
|
interpolate(result, values) {
|
|
424
599
|
if (!values || typeof result !== "string") {
|
|
@@ -436,34 +611,66 @@ var LocL = class _LocL {
|
|
|
436
611
|
depth--;
|
|
437
612
|
if (depth === 0 && start >= 0) {
|
|
438
613
|
const raw = result.slice(start, i + 1);
|
|
439
|
-
|
|
614
|
+
let inner = raw.slice(1, -1);
|
|
615
|
+
if (inner.startsWith("{") && inner.endsWith("}")) {
|
|
616
|
+
inner = inner.slice(1, -1);
|
|
617
|
+
}
|
|
618
|
+
tokens.push({ raw, inner });
|
|
440
619
|
start = -1;
|
|
441
620
|
}
|
|
442
621
|
}
|
|
443
622
|
}
|
|
444
623
|
let output = result;
|
|
624
|
+
const processed = /* @__PURE__ */ new Set();
|
|
445
625
|
for (const token of tokens) {
|
|
626
|
+
if (processed.has(token.raw)) {
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
processed.add(token.raw);
|
|
446
630
|
let replacement = "";
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
631
|
+
const selectMatch = token.inner.match(/^\s*([^,]+),\s*select\s*,\s*([\s\S]+)$/);
|
|
632
|
+
if (selectMatch) {
|
|
633
|
+
const field = selectMatch[1].trim();
|
|
634
|
+
const casesBody = selectMatch[2];
|
|
635
|
+
const value = String(values[field] ?? "other");
|
|
636
|
+
const caseMap = {};
|
|
637
|
+
let i = 0;
|
|
638
|
+
while (i < casesBody.length) {
|
|
639
|
+
while (i < casesBody.length && /\s/.test(casesBody[i])) i++;
|
|
640
|
+
if (i >= casesBody.length) break;
|
|
641
|
+
let keyStart = i;
|
|
642
|
+
while (i < casesBody.length && casesBody[i] !== "{" && !/\s/.test(casesBody[i])) i++;
|
|
643
|
+
const caseKey = casesBody.slice(keyStart, i).trim();
|
|
644
|
+
while (i < casesBody.length && casesBody[i] !== "{") i++;
|
|
645
|
+
if (casesBody[i] === "{") {
|
|
646
|
+
let bodyDepth = 1;
|
|
647
|
+
let bodyStart = i + 1;
|
|
648
|
+
i++;
|
|
649
|
+
while (i < casesBody.length && bodyDepth > 0) {
|
|
650
|
+
if (casesBody[i] === "{") bodyDepth++;
|
|
651
|
+
else if (casesBody[i] === "}") bodyDepth--;
|
|
652
|
+
i++;
|
|
653
|
+
}
|
|
654
|
+
const caseText = casesBody.slice(bodyStart, i - 1).trim();
|
|
655
|
+
if (caseKey) {
|
|
656
|
+
caseMap[caseKey] = caseText;
|
|
657
|
+
}
|
|
457
658
|
}
|
|
458
|
-
replacement = caseMap[value] ?? caseMap.other ?? "";
|
|
459
659
|
}
|
|
660
|
+
const rawReplacement = caseMap[value] ?? caseMap.other ?? "";
|
|
661
|
+
replacement = rawReplacement.includes("{") ? this.interpolate(rawReplacement, values) : rawReplacement;
|
|
460
662
|
} else {
|
|
461
663
|
const [fieldName, formatterAndArgs] = token.inner.split("|").map((x) => x.trim());
|
|
462
664
|
const [formatterName, argsStr] = formatterAndArgs ? formatterAndArgs.split(":") : [];
|
|
463
|
-
const args = argsStr ? argsStr.split(",") : [];
|
|
665
|
+
const args = argsStr ? argsStr.split(",").map((a) => a.trim()) : [];
|
|
464
666
|
const val = values[fieldName];
|
|
465
|
-
if (formatterName
|
|
466
|
-
|
|
667
|
+
if (formatterName) {
|
|
668
|
+
if (this.formatters?.[formatterName]) {
|
|
669
|
+
replacement = this.formatters[formatterName](val, args);
|
|
670
|
+
} else {
|
|
671
|
+
this.config.devMode && console.warn(`[LocL] Formatter "${formatterName}" not found.`);
|
|
672
|
+
replacement = val !== void 0 ? String(val) : token.raw;
|
|
673
|
+
}
|
|
467
674
|
} else if (val instanceof Date) {
|
|
468
675
|
replacement = new Intl.DateTimeFormat(this.language).format(val);
|
|
469
676
|
} else if (typeof val === "number") {
|
|
@@ -472,7 +679,7 @@ var LocL = class _LocL {
|
|
|
472
679
|
replacement = val !== void 0 ? String(val) : token.raw;
|
|
473
680
|
}
|
|
474
681
|
}
|
|
475
|
-
output = output.
|
|
682
|
+
output = output.split(token.raw).join(replacement);
|
|
476
683
|
}
|
|
477
684
|
return output;
|
|
478
685
|
}
|
|
@@ -508,6 +715,11 @@ var LocL = class _LocL {
|
|
|
508
715
|
function initLocL(config) {
|
|
509
716
|
return new LocL(config);
|
|
510
717
|
}
|
|
718
|
+
function defineResources(resources) {
|
|
719
|
+
return resources;
|
|
720
|
+
}
|
|
511
721
|
export {
|
|
722
|
+
LocL,
|
|
723
|
+
defineResources,
|
|
512
724
|
initLocL
|
|
513
725
|
};
|