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.js
CHANGED
|
@@ -18,11 +18,13 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
|
|
20
20
|
// src/index.ts
|
|
21
|
-
var
|
|
22
|
-
__export(
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
LocL: () => LocL,
|
|
24
|
+
defineResources: () => defineResources,
|
|
23
25
|
initLocL: () => initLocL
|
|
24
26
|
});
|
|
25
|
-
module.exports = __toCommonJS(
|
|
27
|
+
module.exports = __toCommonJS(src_exports);
|
|
26
28
|
|
|
27
29
|
// src/formatters.ts
|
|
28
30
|
var defaultFormatters = {
|
|
@@ -58,7 +60,7 @@ var defaultFormatters = {
|
|
|
58
60
|
* @param {string} [args[1]="..."] - The suffix to append if the string is truncated.
|
|
59
61
|
* @returns The truncated string.
|
|
60
62
|
*/
|
|
61
|
-
truncate: (val, args) => {
|
|
63
|
+
truncate: (val, args = []) => {
|
|
62
64
|
const length = parseInt(args[0]) || 10;
|
|
63
65
|
const suffix = args[1] || "...";
|
|
64
66
|
const str = String(val);
|
|
@@ -72,7 +74,7 @@ var defaultFormatters = {
|
|
|
72
74
|
* @param {string} [args[1]] - The style of formatting to use (e.g., "decimal", "percent").
|
|
73
75
|
* @returns The formatted number.
|
|
74
76
|
*/
|
|
75
|
-
number: (val, args) => {
|
|
77
|
+
number: (val, args = []) => {
|
|
76
78
|
const locales = args[0] || void 0;
|
|
77
79
|
const options = {};
|
|
78
80
|
if (args[1]) options.style = args[1];
|
|
@@ -86,7 +88,7 @@ var defaultFormatters = {
|
|
|
86
88
|
* @param {string} [args[1]] - The locale to use.
|
|
87
89
|
* @returns The formatted currency string.
|
|
88
90
|
*/
|
|
89
|
-
currency: (val, args) => {
|
|
91
|
+
currency: (val, args = []) => {
|
|
90
92
|
const currency = args[0] || "USD";
|
|
91
93
|
const locales = args[1] || void 0;
|
|
92
94
|
return new Intl.NumberFormat(locales, {
|
|
@@ -102,7 +104,7 @@ var defaultFormatters = {
|
|
|
102
104
|
* @param {string} [args[1]] - The date style to use (e.g., "short", "long").
|
|
103
105
|
* @returns The formatted date string.
|
|
104
106
|
*/
|
|
105
|
-
date: (val, args) => {
|
|
107
|
+
date: (val, args = []) => {
|
|
106
108
|
const locales = args[0] || void 0;
|
|
107
109
|
const options = {};
|
|
108
110
|
if (args[1]) options.dateStyle = args[1];
|
|
@@ -115,7 +117,7 @@ var defaultFormatters = {
|
|
|
115
117
|
* @param {string} [args[0]] - The locale to use.
|
|
116
118
|
* @returns The relative time string.
|
|
117
119
|
*/
|
|
118
|
-
relativeDate: (val, args) => {
|
|
120
|
+
relativeDate: (val, args = []) => {
|
|
119
121
|
const locales = args[0] || void 0;
|
|
120
122
|
const options = { numeric: "auto" };
|
|
121
123
|
const rtf = new Intl.RelativeTimeFormat(locales, options);
|
|
@@ -139,7 +141,7 @@ var defaultFormatters = {
|
|
|
139
141
|
* @param {string} [args[1]="No"] - The string to return for a falsy value.
|
|
140
142
|
* @returns "Yes" or "No".
|
|
141
143
|
*/
|
|
142
|
-
yesNo: (val, args) => {
|
|
144
|
+
yesNo: (val, args = []) => {
|
|
143
145
|
const [yesVal, noVal] = args || [];
|
|
144
146
|
return val ? yesVal || "Yes" : noVal || "No";
|
|
145
147
|
},
|
|
@@ -151,7 +153,7 @@ var defaultFormatters = {
|
|
|
151
153
|
* @param {string} [args[1]="false"] - The string to return for a falsy value.
|
|
152
154
|
* @returns "true" or "false".
|
|
153
155
|
*/
|
|
154
|
-
boolean: (val, args) => {
|
|
156
|
+
boolean: (val, args = []) => {
|
|
155
157
|
const [trueVal, falseVal] = args || [];
|
|
156
158
|
return val ? trueVal || "true" : falseVal || "false";
|
|
157
159
|
},
|
|
@@ -163,7 +165,7 @@ var defaultFormatters = {
|
|
|
163
165
|
* @param {string} [args[1]=" "] - The string to pad with.
|
|
164
166
|
* @returns The padded string.
|
|
165
167
|
*/
|
|
166
|
-
padStart: (val, args) => {
|
|
168
|
+
padStart: (val, args = []) => {
|
|
167
169
|
const length = parseInt(args[0]) || 0;
|
|
168
170
|
const fill = args[1] || " ";
|
|
169
171
|
return String(val).padStart(length, fill);
|
|
@@ -176,7 +178,7 @@ var defaultFormatters = {
|
|
|
176
178
|
* @param {string} [args[1]=" "] - The string to pad with.
|
|
177
179
|
* @returns The padded string.
|
|
178
180
|
*/
|
|
179
|
-
padEnd: (val, args) => {
|
|
181
|
+
padEnd: (val, args = []) => {
|
|
180
182
|
const length = parseInt(args[0]) || 0;
|
|
181
183
|
const fill = args[1] || " ";
|
|
182
184
|
return String(val).padEnd(length, fill);
|
|
@@ -192,25 +194,26 @@ var LocL = class _LocL {
|
|
|
192
194
|
constructor(config) {
|
|
193
195
|
this.cache = /* @__PURE__ */ new Map();
|
|
194
196
|
this.proxyCache = /* @__PURE__ */ new WeakMap();
|
|
197
|
+
this.pluralRulesCache = /* @__PURE__ */ new Map();
|
|
198
|
+
this.subscribers = /* @__PURE__ */ new Set();
|
|
199
|
+
this.rootInstance = this;
|
|
195
200
|
if (!config.resources) {
|
|
196
201
|
throw new Error("[LocL] `resources` is required");
|
|
197
202
|
}
|
|
198
203
|
if (!config.fallbackLanguage) {
|
|
199
204
|
throw new Error("[LocL] `fallbackLanguage` is required");
|
|
200
205
|
}
|
|
201
|
-
config = {
|
|
206
|
+
this.config = {
|
|
202
207
|
useDefaultFormatters: true,
|
|
203
208
|
devMode: false,
|
|
204
209
|
useCache: true,
|
|
205
210
|
...config
|
|
206
211
|
};
|
|
207
|
-
|
|
208
|
-
this.config = config;
|
|
212
|
+
this.resources = { ...config.resources };
|
|
209
213
|
this.language = this.isLanguage(config.language) ?? config.fallbackLanguage;
|
|
210
214
|
this.fallbackLanguage = config.fallbackLanguage;
|
|
211
215
|
this.scope = config.scope;
|
|
212
|
-
this.
|
|
213
|
-
this.formatters = config.useDefaultFormatters ? { ...defaultFormatters, ...config.formatters ?? {} } : { ...config.formatters ?? {} };
|
|
216
|
+
this.formatters = this.config.useDefaultFormatters ? { ...defaultFormatters, ...config.formatters ?? {} } : { ...config.formatters ?? {} };
|
|
214
217
|
}
|
|
215
218
|
/**
|
|
216
219
|
* Creates a new proxy translator instance with a different configuration.
|
|
@@ -222,21 +225,40 @@ var LocL = class _LocL {
|
|
|
222
225
|
* @returns A new proxy `LocL` instance.
|
|
223
226
|
*/
|
|
224
227
|
withConfig(config) {
|
|
228
|
+
const root = this.rootInstance;
|
|
225
229
|
const cacheKey = `proxy::${config.language ?? this.language}::${Array.isArray(config.scope) ? config.scope.join("|") : config.scope ?? "*"}`;
|
|
226
|
-
if (
|
|
227
|
-
return
|
|
230
|
+
if (root.cache.has(cacheKey)) {
|
|
231
|
+
return root.cache.get(cacheKey);
|
|
228
232
|
}
|
|
229
|
-
|
|
230
|
-
get(target, prop) {
|
|
233
|
+
const result = new Proxy(root, {
|
|
234
|
+
get(target, prop, receiver) {
|
|
231
235
|
if (prop === "language") {
|
|
232
236
|
return config.language ?? target.language;
|
|
233
237
|
}
|
|
234
238
|
if (prop === "scope") {
|
|
235
|
-
return config.scope;
|
|
239
|
+
return config.scope !== void 0 ? config.scope : target.scope;
|
|
240
|
+
}
|
|
241
|
+
if (prop === "rootInstance") {
|
|
242
|
+
return target;
|
|
243
|
+
}
|
|
244
|
+
if (prop === "subscribe" && config.language !== void 0) {
|
|
245
|
+
return (listener) => {
|
|
246
|
+
try {
|
|
247
|
+
listener(config.language, config.language);
|
|
248
|
+
} catch (err) {
|
|
249
|
+
console.error("[LocL] Error in subscriber:", err);
|
|
250
|
+
}
|
|
251
|
+
return () => {
|
|
252
|
+
};
|
|
253
|
+
};
|
|
236
254
|
}
|
|
237
|
-
return target
|
|
255
|
+
return Reflect.get(target, prop, receiver);
|
|
238
256
|
}
|
|
239
257
|
});
|
|
258
|
+
if (root.config.useCache) {
|
|
259
|
+
root.cache.set(cacheKey, result);
|
|
260
|
+
}
|
|
261
|
+
return result;
|
|
240
262
|
}
|
|
241
263
|
/**
|
|
242
264
|
* Creates a new `LocL` instance with a different language or scope.
|
|
@@ -247,18 +269,106 @@ var LocL = class _LocL {
|
|
|
247
269
|
clone(language = this.language, scope) {
|
|
248
270
|
return new _LocL({
|
|
249
271
|
...this.config,
|
|
272
|
+
resources: this.resources,
|
|
250
273
|
language,
|
|
251
274
|
scope
|
|
252
275
|
});
|
|
253
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* Subscribes a listener to language changes.
|
|
279
|
+
* Conforms to the standard reactive store contract (e.g. Svelte stores).
|
|
280
|
+
* Calls the listener immediately with the current language and returns an unsubscribe function.
|
|
281
|
+
* @param listener - The subscriber callback function.
|
|
282
|
+
* @returns An unsubscribe function.
|
|
283
|
+
*/
|
|
284
|
+
subscribe(listener) {
|
|
285
|
+
this.subscribers.add(listener);
|
|
286
|
+
try {
|
|
287
|
+
listener(this.language, this.language);
|
|
288
|
+
} catch (err) {
|
|
289
|
+
console.error("[LocL] Error in initial subscriber call:", err);
|
|
290
|
+
}
|
|
291
|
+
return () => {
|
|
292
|
+
this.subscribers.delete(listener);
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Gets the current active language.
|
|
297
|
+
*/
|
|
298
|
+
getLanguage() {
|
|
299
|
+
return this.language;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Adds or overrides a single translation key at runtime.
|
|
303
|
+
* @param lang - Target language code.
|
|
304
|
+
* @param key - Dotted path key.
|
|
305
|
+
* @param value - The translation value.
|
|
306
|
+
*/
|
|
307
|
+
addResource(lang, key, value) {
|
|
308
|
+
if (!this.resources[lang]) {
|
|
309
|
+
this.resources[lang] = {};
|
|
310
|
+
}
|
|
311
|
+
const keys = key.split(".");
|
|
312
|
+
let current = this.resources[lang];
|
|
313
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
314
|
+
const k = keys[i];
|
|
315
|
+
if (k === "__proto__" || k === "constructor" || k === "prototype") {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (!current[k] || typeof current[k] !== "object") {
|
|
319
|
+
current[k] = {};
|
|
320
|
+
}
|
|
321
|
+
current = current[k];
|
|
322
|
+
}
|
|
323
|
+
const lastKey = keys[keys.length - 1];
|
|
324
|
+
if (lastKey !== "__proto__" && lastKey !== "constructor" && lastKey !== "prototype") {
|
|
325
|
+
current[lastKey] = value;
|
|
326
|
+
}
|
|
327
|
+
this.invalidateCacheForLang(lang);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Deeply merges a resource bundle into the specified language at runtime.
|
|
331
|
+
* @param lang - Target language code.
|
|
332
|
+
* @param bundle - Object of translations to merge.
|
|
333
|
+
*/
|
|
334
|
+
addResources(lang, bundle) {
|
|
335
|
+
if (!this.resources[lang]) {
|
|
336
|
+
this.resources[lang] = {};
|
|
337
|
+
}
|
|
338
|
+
const deepMerge = (target, source) => {
|
|
339
|
+
for (const k of Object.keys(source)) {
|
|
340
|
+
if (k === "__proto__" || k === "constructor" || k === "prototype") {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (source[k] && typeof source[k] === "object" && !Array.isArray(source[k])) {
|
|
344
|
+
if (!target[k] || typeof target[k] !== "object") {
|
|
345
|
+
target[k] = {};
|
|
346
|
+
}
|
|
347
|
+
deepMerge(target[k], source[k]);
|
|
348
|
+
} else {
|
|
349
|
+
target[k] = source[k];
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
deepMerge(this.resources[lang], bundle);
|
|
354
|
+
this.invalidateCacheForLang(lang);
|
|
355
|
+
}
|
|
254
356
|
/**
|
|
255
357
|
* Changes the current language of the translator.
|
|
256
358
|
* @param lang - The new language to set.
|
|
257
359
|
*/
|
|
258
360
|
changeLanguage(lang) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
361
|
+
const validated = this.isLanguage(lang);
|
|
362
|
+
if (validated && validated !== this.language) {
|
|
363
|
+
const prev = this.language;
|
|
364
|
+
this.language = validated;
|
|
365
|
+
for (const sub of this.subscribers) {
|
|
366
|
+
try {
|
|
367
|
+
sub(this.language, prev);
|
|
368
|
+
} catch (err) {
|
|
369
|
+
console.error("[LocL] Error in subscriber:", err);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
262
372
|
}
|
|
263
373
|
}
|
|
264
374
|
t(key, values, format) {
|
|
@@ -267,6 +377,9 @@ var LocL = class _LocL {
|
|
|
267
377
|
}
|
|
268
378
|
const pluralCheckResult = this.checkPlural(key, values);
|
|
269
379
|
if (pluralCheckResult !== void 0) {
|
|
380
|
+
if (format && format.formatter) {
|
|
381
|
+
return this.applyFormat(pluralCheckResult, format.formatter, format.args);
|
|
382
|
+
}
|
|
270
383
|
return pluralCheckResult;
|
|
271
384
|
}
|
|
272
385
|
const result = this.lookupWithFallback(key);
|
|
@@ -297,45 +410,54 @@ var LocL = class _LocL {
|
|
|
297
410
|
format(value, formatter, args) {
|
|
298
411
|
return this.applyFormat(value, formatter, args);
|
|
299
412
|
}
|
|
413
|
+
plural(key, values, format) {
|
|
414
|
+
return this.t(key, values, format);
|
|
415
|
+
}
|
|
300
416
|
/**
|
|
301
|
-
* Translates a key
|
|
302
|
-
*
|
|
303
|
-
* @param key - The
|
|
304
|
-
* @param
|
|
305
|
-
* @param
|
|
306
|
-
* @returns
|
|
417
|
+
* Translates a key and interpolates rich elements using tag functions.
|
|
418
|
+
* Matches `<tag>content</tag>` in translation templates.
|
|
419
|
+
* @param key - The key to translate.
|
|
420
|
+
* @param tags - An object mapping tag names to functions that transform their inner content.
|
|
421
|
+
* @param values - Values to interpolate into variable placeholders ({name}, {{name}}).
|
|
422
|
+
* @returns An array of chunks (strings and custom rendered tag values).
|
|
423
|
+
* @example
|
|
424
|
+
* ```ts
|
|
425
|
+
* translator.rich("terms", {
|
|
426
|
+
* link: (content) => `<a href="/terms">${content}</a>`
|
|
427
|
+
* }, { name: "Alice" });
|
|
428
|
+
* ```
|
|
307
429
|
*/
|
|
308
|
-
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
this.config.devMode && console.warn(`[LocL] Missing plural: "${baseKey}" in "${this.language}"`);
|
|
321
|
-
interpolated = baseKey;
|
|
430
|
+
rich(key, tags, values) {
|
|
431
|
+
const rawStr = this.t(key, values);
|
|
432
|
+
if (typeof rawStr !== "string") {
|
|
433
|
+
return [rawStr];
|
|
434
|
+
}
|
|
435
|
+
const tagRegex = /<([a-zA-Z0-9_-]+)>([\s\S]*?)<\/\1>/g;
|
|
436
|
+
const result = [];
|
|
437
|
+
let lastIndex = 0;
|
|
438
|
+
let match;
|
|
439
|
+
while ((match = tagRegex.exec(rawStr)) !== null) {
|
|
440
|
+
if (match.index > lastIndex) {
|
|
441
|
+
result.push(rawStr.slice(lastIndex, match.index));
|
|
322
442
|
}
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
const
|
|
326
|
-
|
|
443
|
+
const tagName = match[1];
|
|
444
|
+
const content = match[2];
|
|
445
|
+
const renderer = tags[tagName];
|
|
446
|
+
typeof renderer === "function" ? result.push(renderer(content)) : result.push(match[0]);
|
|
447
|
+
lastIndex = match.index + match[0].length;
|
|
327
448
|
}
|
|
328
|
-
if (
|
|
329
|
-
|
|
449
|
+
if (lastIndex < rawStr.length) {
|
|
450
|
+
result.push(rawStr.slice(lastIndex));
|
|
330
451
|
}
|
|
331
|
-
return
|
|
452
|
+
return result;
|
|
332
453
|
}
|
|
333
454
|
get(key) {
|
|
334
455
|
const base = this.buildTranslationObject(this.language);
|
|
335
456
|
if (!key) {
|
|
336
|
-
return base;
|
|
457
|
+
return base ? this.makeReadOnly(base) : void 0;
|
|
337
458
|
}
|
|
338
|
-
|
|
459
|
+
const val = this.lookupWithFallback(key, base);
|
|
460
|
+
return val && typeof val === "object" ? this.makeReadOnly(val) : val;
|
|
339
461
|
}
|
|
340
462
|
/**
|
|
341
463
|
* Gets a nested object from the translations.
|
|
@@ -350,12 +472,27 @@ var LocL = class _LocL {
|
|
|
350
472
|
if (!value) {
|
|
351
473
|
return null;
|
|
352
474
|
}
|
|
353
|
-
const
|
|
354
|
-
|
|
475
|
+
const available = Object.keys(this.resources);
|
|
476
|
+
if (available.includes(value)) {
|
|
477
|
+
return value;
|
|
478
|
+
}
|
|
479
|
+
const prefix = value.substring(0, 2);
|
|
480
|
+
if (available.includes(prefix)) {
|
|
481
|
+
return prefix;
|
|
482
|
+
}
|
|
483
|
+
return null;
|
|
355
484
|
}
|
|
356
485
|
getCacheKey(language) {
|
|
357
|
-
const scopeKey = Array.isArray(this.scope) ? this.scope.join("|") : this.scope
|
|
358
|
-
return `${language
|
|
486
|
+
const scopeKey = Array.isArray(this.scope) ? this.scope.join("|") : this.scope;
|
|
487
|
+
return `${language}::${scopeKey}`;
|
|
488
|
+
}
|
|
489
|
+
invalidateCacheForLang(lang) {
|
|
490
|
+
const prefix = `${lang}::`;
|
|
491
|
+
for (const key of this.cache.keys()) {
|
|
492
|
+
if (key.startsWith(prefix)) {
|
|
493
|
+
this.cache.delete(key);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
359
496
|
}
|
|
360
497
|
lookupWithFallback(key, base) {
|
|
361
498
|
const keys = key.split(".");
|
|
@@ -373,7 +510,7 @@ var LocL = class _LocL {
|
|
|
373
510
|
return result;
|
|
374
511
|
}
|
|
375
512
|
buildTranslationObject(language) {
|
|
376
|
-
const langObject = this.
|
|
513
|
+
const langObject = this.resources[language];
|
|
377
514
|
if (!this.scope) {
|
|
378
515
|
return langObject;
|
|
379
516
|
}
|
|
@@ -404,11 +541,10 @@ var LocL = class _LocL {
|
|
|
404
541
|
}, combined);
|
|
405
542
|
}
|
|
406
543
|
}
|
|
407
|
-
const roCombined = this.makeReadOnly(combined);
|
|
408
544
|
if (this.config.useCache) {
|
|
409
|
-
this.cache.set(cacheKey,
|
|
545
|
+
this.cache.set(cacheKey, combined);
|
|
410
546
|
}
|
|
411
|
-
return
|
|
547
|
+
return combined;
|
|
412
548
|
}
|
|
413
549
|
if (this.config.useCache) {
|
|
414
550
|
this.cache.set(cacheKey, void 0);
|
|
@@ -438,13 +574,54 @@ var LocL = class _LocL {
|
|
|
438
574
|
}
|
|
439
575
|
return current;
|
|
440
576
|
}
|
|
577
|
+
getPluralRules(lang) {
|
|
578
|
+
let rules = this.pluralRulesCache.get(lang);
|
|
579
|
+
if (!rules) {
|
|
580
|
+
rules = new Intl.PluralRules(lang);
|
|
581
|
+
this.pluralRulesCache.set(lang, rules);
|
|
582
|
+
}
|
|
583
|
+
return rules;
|
|
584
|
+
}
|
|
441
585
|
checkPlural(key, values) {
|
|
442
|
-
if (values?.count !== void 0 && !key.match(/_(one|few|many|other)$/)) {
|
|
443
|
-
const category = this.
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
586
|
+
if (values?.count !== void 0 && !key.match(/_(zero|one|two|few|many|other)$/)) {
|
|
587
|
+
const category = this.getPluralRules(this.language).select(values.count);
|
|
588
|
+
const baseValue = this.lookupWithFallback(key, void 0);
|
|
589
|
+
if (baseValue && typeof baseValue === "object") {
|
|
590
|
+
if (values.count === 0 && "zero" in baseValue) {
|
|
591
|
+
return this.interpolate(baseValue.zero, values);
|
|
592
|
+
}
|
|
593
|
+
if (category in baseValue) {
|
|
594
|
+
return this.interpolate(baseValue[category], values);
|
|
595
|
+
} else if ("other" in baseValue) {
|
|
596
|
+
this.config.devMode && console.warn(`[LocL] Missing plural: "${key}" in "${this.language}"`);
|
|
597
|
+
return this.interpolate(baseValue.other, values);
|
|
598
|
+
} else {
|
|
599
|
+
this.config.devMode && console.warn(`[LocL] Missing plural: "${key}" in "${this.language}"`);
|
|
600
|
+
return key;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
if (values.count === 0) {
|
|
604
|
+
const zeroResult = this.lookupWithFallback(`${key}_zero`, void 0);
|
|
605
|
+
if (zeroResult !== void 0) {
|
|
606
|
+
return this.interpolate(zeroResult, values);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
const categoryResult = this.lookupWithFallback(`${key}_${category}`, void 0);
|
|
610
|
+
if (categoryResult !== void 0) {
|
|
611
|
+
return this.interpolate(categoryResult, values);
|
|
612
|
+
}
|
|
613
|
+
const otherResult = this.lookupWithFallback(`${key}_other`, void 0);
|
|
614
|
+
if (otherResult !== void 0) {
|
|
615
|
+
this.config.devMode && console.warn(`[LocL] Missing plural: "${key}" in "${this.language}"`);
|
|
616
|
+
return this.interpolate(otherResult, values);
|
|
617
|
+
}
|
|
618
|
+
const anySuffixExists = ["zero", "one", "two", "few", "many"].some((cat) => this.lookupWithFallback(`${key}_${cat}`, void 0) !== void 0);
|
|
619
|
+
if (anySuffixExists) {
|
|
620
|
+
this.config.devMode && console.warn(`[LocL] Missing plural: "${key}" in "${this.language}"`);
|
|
621
|
+
return key;
|
|
622
|
+
}
|
|
447
623
|
}
|
|
624
|
+
return void 0;
|
|
448
625
|
}
|
|
449
626
|
interpolate(result, values) {
|
|
450
627
|
if (!values || typeof result !== "string") {
|
|
@@ -462,34 +639,66 @@ var LocL = class _LocL {
|
|
|
462
639
|
depth--;
|
|
463
640
|
if (depth === 0 && start >= 0) {
|
|
464
641
|
const raw = result.slice(start, i + 1);
|
|
465
|
-
|
|
642
|
+
let inner = raw.slice(1, -1);
|
|
643
|
+
if (inner.startsWith("{") && inner.endsWith("}")) {
|
|
644
|
+
inner = inner.slice(1, -1);
|
|
645
|
+
}
|
|
646
|
+
tokens.push({ raw, inner });
|
|
466
647
|
start = -1;
|
|
467
648
|
}
|
|
468
649
|
}
|
|
469
650
|
}
|
|
470
651
|
let output = result;
|
|
652
|
+
const processed = /* @__PURE__ */ new Set();
|
|
471
653
|
for (const token of tokens) {
|
|
654
|
+
if (processed.has(token.raw)) {
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
processed.add(token.raw);
|
|
472
658
|
let replacement = "";
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
659
|
+
const selectMatch = token.inner.match(/^\s*([^,]+),\s*select\s*,\s*([\s\S]+)$/);
|
|
660
|
+
if (selectMatch) {
|
|
661
|
+
const field = selectMatch[1].trim();
|
|
662
|
+
const casesBody = selectMatch[2];
|
|
663
|
+
const value = String(values[field] ?? "other");
|
|
664
|
+
const caseMap = {};
|
|
665
|
+
let i = 0;
|
|
666
|
+
while (i < casesBody.length) {
|
|
667
|
+
while (i < casesBody.length && /\s/.test(casesBody[i])) i++;
|
|
668
|
+
if (i >= casesBody.length) break;
|
|
669
|
+
let keyStart = i;
|
|
670
|
+
while (i < casesBody.length && casesBody[i] !== "{" && !/\s/.test(casesBody[i])) i++;
|
|
671
|
+
const caseKey = casesBody.slice(keyStart, i).trim();
|
|
672
|
+
while (i < casesBody.length && casesBody[i] !== "{") i++;
|
|
673
|
+
if (casesBody[i] === "{") {
|
|
674
|
+
let bodyDepth = 1;
|
|
675
|
+
let bodyStart = i + 1;
|
|
676
|
+
i++;
|
|
677
|
+
while (i < casesBody.length && bodyDepth > 0) {
|
|
678
|
+
if (casesBody[i] === "{") bodyDepth++;
|
|
679
|
+
else if (casesBody[i] === "}") bodyDepth--;
|
|
680
|
+
i++;
|
|
681
|
+
}
|
|
682
|
+
const caseText = casesBody.slice(bodyStart, i - 1).trim();
|
|
683
|
+
if (caseKey) {
|
|
684
|
+
caseMap[caseKey] = caseText;
|
|
685
|
+
}
|
|
483
686
|
}
|
|
484
|
-
replacement = caseMap[value] ?? caseMap.other ?? "";
|
|
485
687
|
}
|
|
688
|
+
const rawReplacement = caseMap[value] ?? caseMap.other ?? "";
|
|
689
|
+
replacement = rawReplacement.includes("{") ? this.interpolate(rawReplacement, values) : rawReplacement;
|
|
486
690
|
} else {
|
|
487
691
|
const [fieldName, formatterAndArgs] = token.inner.split("|").map((x) => x.trim());
|
|
488
692
|
const [formatterName, argsStr] = formatterAndArgs ? formatterAndArgs.split(":") : [];
|
|
489
|
-
const args = argsStr ? argsStr.split(",") : [];
|
|
693
|
+
const args = argsStr ? argsStr.split(",").map((a) => a.trim()) : [];
|
|
490
694
|
const val = values[fieldName];
|
|
491
|
-
if (formatterName
|
|
492
|
-
|
|
695
|
+
if (formatterName) {
|
|
696
|
+
if (this.formatters?.[formatterName]) {
|
|
697
|
+
replacement = this.formatters[formatterName](val, args);
|
|
698
|
+
} else {
|
|
699
|
+
this.config.devMode && console.warn(`[LocL] Formatter "${formatterName}" not found.`);
|
|
700
|
+
replacement = val !== void 0 ? String(val) : token.raw;
|
|
701
|
+
}
|
|
493
702
|
} else if (val instanceof Date) {
|
|
494
703
|
replacement = new Intl.DateTimeFormat(this.language).format(val);
|
|
495
704
|
} else if (typeof val === "number") {
|
|
@@ -498,7 +707,7 @@ var LocL = class _LocL {
|
|
|
498
707
|
replacement = val !== void 0 ? String(val) : token.raw;
|
|
499
708
|
}
|
|
500
709
|
}
|
|
501
|
-
output = output.
|
|
710
|
+
output = output.split(token.raw).join(replacement);
|
|
502
711
|
}
|
|
503
712
|
return output;
|
|
504
713
|
}
|
|
@@ -534,7 +743,12 @@ var LocL = class _LocL {
|
|
|
534
743
|
function initLocL(config) {
|
|
535
744
|
return new LocL(config);
|
|
536
745
|
}
|
|
746
|
+
function defineResources(resources) {
|
|
747
|
+
return resources;
|
|
748
|
+
}
|
|
537
749
|
// Annotate the CommonJS export names for ESM import in node:
|
|
538
750
|
0 && (module.exports = {
|
|
751
|
+
LocL,
|
|
752
|
+
defineResources,
|
|
539
753
|
initLocL
|
|
540
754
|
});
|