locl-js 1.0.2 → 2.0.1
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/README.md +540 -0
- package/dist/LocL-v4yNIiKt.d.mts +438 -0
- package/dist/LocL-v4yNIiKt.d.ts +438 -0
- package/dist/compat.d.mts +54 -0
- package/dist/compat.d.ts +54 -0
- package/dist/compat.js +138 -0
- package/dist/compat.mjs +112 -0
- package/dist/index.d.mts +20 -341
- package/dist/index.d.ts +20 -341
- package/dist/index.js +296 -77
- package/dist/index.mjs +291 -74
- package/dist/react.d.mts +118 -0
- package/dist/react.d.ts +118 -0
- package/dist/react.js +229 -0
- package/dist/react.mjs +189 -0
- package/package.json +24 -1
- package/readme.md +0 -321
package/dist/index.mjs
CHANGED
|
@@ -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,22 +197,39 @@ 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
|
-
const result = new Proxy(
|
|
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
|
});
|
|
214
|
-
|
|
230
|
+
if (root.config.useCache) {
|
|
231
|
+
root.cache.set(cacheKey, result);
|
|
232
|
+
}
|
|
215
233
|
return result;
|
|
216
234
|
}
|
|
217
235
|
/**
|
|
@@ -223,18 +241,110 @@ var LocL = class _LocL {
|
|
|
223
241
|
clone(language = this.language, scope) {
|
|
224
242
|
return new _LocL({
|
|
225
243
|
...this.config,
|
|
244
|
+
resources: this.resources,
|
|
226
245
|
language,
|
|
227
246
|
scope
|
|
228
247
|
});
|
|
229
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.isUnsafeObjectKey(lang)) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const keys = key.split(".");
|
|
284
|
+
if (keys.some((k) => this.isUnsafeObjectKey(k))) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (!this.resources[lang]) {
|
|
288
|
+
this.resources[lang] = /* @__PURE__ */ Object.create(null);
|
|
289
|
+
}
|
|
290
|
+
let current = this.resources[lang];
|
|
291
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
292
|
+
const k = keys[i];
|
|
293
|
+
if (!current[k] || typeof current[k] !== "object") {
|
|
294
|
+
current[k] = /* @__PURE__ */ Object.create(null);
|
|
295
|
+
}
|
|
296
|
+
current = current[k];
|
|
297
|
+
}
|
|
298
|
+
const lastKey = keys[keys.length - 1];
|
|
299
|
+
current[lastKey] = value;
|
|
300
|
+
this.invalidateCacheForLang(lang);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Deeply merges a resource bundle into the specified language at runtime.
|
|
304
|
+
* @param lang - Target language code.
|
|
305
|
+
* @param bundle - Object of translations to merge.
|
|
306
|
+
*/
|
|
307
|
+
addResources(lang, bundle) {
|
|
308
|
+
if (this.isUnsafeObjectKey(lang)) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (!this.resources[lang]) {
|
|
312
|
+
this.resources[lang] = /* @__PURE__ */ Object.create(null);
|
|
313
|
+
}
|
|
314
|
+
const deepMerge = (target, source) => {
|
|
315
|
+
for (const k of Object.keys(source)) {
|
|
316
|
+
if (this.isUnsafeObjectKey(k)) {
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (source[k] && typeof source[k] === "object" && !Array.isArray(source[k])) {
|
|
320
|
+
if (!target[k] || typeof target[k] !== "object") {
|
|
321
|
+
target[k] = /* @__PURE__ */ Object.create(null);
|
|
322
|
+
}
|
|
323
|
+
deepMerge(target[k], source[k]);
|
|
324
|
+
} else {
|
|
325
|
+
target[k] = source[k];
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
deepMerge(this.resources[lang], bundle);
|
|
330
|
+
this.invalidateCacheForLang(lang);
|
|
331
|
+
}
|
|
230
332
|
/**
|
|
231
333
|
* Changes the current language of the translator.
|
|
232
334
|
* @param lang - The new language to set.
|
|
233
335
|
*/
|
|
234
336
|
changeLanguage(lang) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
337
|
+
const validated = this.isLanguage(lang);
|
|
338
|
+
if (validated && validated !== this.language) {
|
|
339
|
+
const prev = this.language;
|
|
340
|
+
this.language = validated;
|
|
341
|
+
for (const sub of this.subscribers) {
|
|
342
|
+
try {
|
|
343
|
+
sub(this.language, prev);
|
|
344
|
+
} catch (err) {
|
|
345
|
+
console.error("[LocL] Error in subscriber:", err);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
238
348
|
}
|
|
239
349
|
}
|
|
240
350
|
t(key, values, format) {
|
|
@@ -243,6 +353,9 @@ var LocL = class _LocL {
|
|
|
243
353
|
}
|
|
244
354
|
const pluralCheckResult = this.checkPlural(key, values);
|
|
245
355
|
if (pluralCheckResult !== void 0) {
|
|
356
|
+
if (format && format.formatter) {
|
|
357
|
+
return this.applyFormat(pluralCheckResult, format.formatter, format.args);
|
|
358
|
+
}
|
|
246
359
|
return pluralCheckResult;
|
|
247
360
|
}
|
|
248
361
|
const result = this.lookupWithFallback(key);
|
|
@@ -273,45 +386,54 @@ var LocL = class _LocL {
|
|
|
273
386
|
format(value, formatter, args) {
|
|
274
387
|
return this.applyFormat(value, formatter, args);
|
|
275
388
|
}
|
|
389
|
+
plural(key, values, format) {
|
|
390
|
+
return this.t(key, values, format);
|
|
391
|
+
}
|
|
276
392
|
/**
|
|
277
|
-
* Translates a key
|
|
278
|
-
*
|
|
279
|
-
* @param key - The
|
|
280
|
-
* @param
|
|
281
|
-
* @param
|
|
282
|
-
* @returns
|
|
393
|
+
* Translates a key and interpolates rich elements using tag functions.
|
|
394
|
+
* Matches `<tag>content</tag>` in translation templates.
|
|
395
|
+
* @param key - The key to translate.
|
|
396
|
+
* @param tags - An object mapping tag names to functions that transform their inner content.
|
|
397
|
+
* @param values - Values to interpolate into variable placeholders ({name}, {{name}}).
|
|
398
|
+
* @returns An array of chunks (strings and custom rendered tag values).
|
|
399
|
+
* @example
|
|
400
|
+
* ```ts
|
|
401
|
+
* translator.rich("terms", {
|
|
402
|
+
* link: (content) => `<a href="/terms">${content}</a>`
|
|
403
|
+
* }, { name: "Alice" });
|
|
404
|
+
* ```
|
|
283
405
|
*/
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
this.config.devMode && console.warn(`[LocL] Missing plural: "${baseKey}" in "${this.language}"`);
|
|
297
|
-
interpolated = baseKey;
|
|
406
|
+
rich(key, tags, values) {
|
|
407
|
+
const rawStr = this.t(key, values);
|
|
408
|
+
if (typeof rawStr !== "string") {
|
|
409
|
+
return [rawStr];
|
|
410
|
+
}
|
|
411
|
+
const tagRegex = /<([a-zA-Z0-9_-]+)>([\s\S]*?)<\/\1>/g;
|
|
412
|
+
const result = [];
|
|
413
|
+
let lastIndex = 0;
|
|
414
|
+
let match;
|
|
415
|
+
while ((match = tagRegex.exec(rawStr)) !== null) {
|
|
416
|
+
if (match.index > lastIndex) {
|
|
417
|
+
result.push(rawStr.slice(lastIndex, match.index));
|
|
298
418
|
}
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
const
|
|
302
|
-
|
|
419
|
+
const tagName = match[1];
|
|
420
|
+
const content = match[2];
|
|
421
|
+
const renderer = tags[tagName];
|
|
422
|
+
typeof renderer === "function" ? result.push(renderer(content)) : result.push(match[0]);
|
|
423
|
+
lastIndex = match.index + match[0].length;
|
|
303
424
|
}
|
|
304
|
-
if (
|
|
305
|
-
|
|
425
|
+
if (lastIndex < rawStr.length) {
|
|
426
|
+
result.push(rawStr.slice(lastIndex));
|
|
306
427
|
}
|
|
307
|
-
return
|
|
428
|
+
return result;
|
|
308
429
|
}
|
|
309
430
|
get(key) {
|
|
310
431
|
const base = this.buildTranslationObject(this.language);
|
|
311
432
|
if (!key) {
|
|
312
|
-
return base;
|
|
433
|
+
return base ? this.makeReadOnly(base) : void 0;
|
|
313
434
|
}
|
|
314
|
-
|
|
435
|
+
const val = this.lookupWithFallback(key, base);
|
|
436
|
+
return val && typeof val === "object" ? this.makeReadOnly(val) : val;
|
|
315
437
|
}
|
|
316
438
|
/**
|
|
317
439
|
* Gets a nested object from the translations.
|
|
@@ -326,12 +448,27 @@ var LocL = class _LocL {
|
|
|
326
448
|
if (!value) {
|
|
327
449
|
return null;
|
|
328
450
|
}
|
|
329
|
-
const
|
|
330
|
-
|
|
451
|
+
const available = Object.keys(this.resources);
|
|
452
|
+
if (available.includes(value)) {
|
|
453
|
+
return value;
|
|
454
|
+
}
|
|
455
|
+
const prefix = value.substring(0, 2);
|
|
456
|
+
if (available.includes(prefix)) {
|
|
457
|
+
return prefix;
|
|
458
|
+
}
|
|
459
|
+
return null;
|
|
331
460
|
}
|
|
332
461
|
getCacheKey(language) {
|
|
333
|
-
const scopeKey = Array.isArray(this.scope) ? this.scope.join("|") : this.scope
|
|
334
|
-
return `${language
|
|
462
|
+
const scopeKey = Array.isArray(this.scope) ? this.scope.join("|") : this.scope;
|
|
463
|
+
return `${language}::${scopeKey}`;
|
|
464
|
+
}
|
|
465
|
+
invalidateCacheForLang(lang) {
|
|
466
|
+
const prefix = `${lang}::`;
|
|
467
|
+
for (const key of this.cache.keys()) {
|
|
468
|
+
if (key.startsWith(prefix)) {
|
|
469
|
+
this.cache.delete(key);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
335
472
|
}
|
|
336
473
|
lookupWithFallback(key, base) {
|
|
337
474
|
const keys = key.split(".");
|
|
@@ -349,7 +486,7 @@ var LocL = class _LocL {
|
|
|
349
486
|
return result;
|
|
350
487
|
}
|
|
351
488
|
buildTranslationObject(language) {
|
|
352
|
-
const langObject = this.
|
|
489
|
+
const langObject = this.resources[language];
|
|
353
490
|
if (!this.scope) {
|
|
354
491
|
return langObject;
|
|
355
492
|
}
|
|
@@ -380,11 +517,10 @@ var LocL = class _LocL {
|
|
|
380
517
|
}, combined);
|
|
381
518
|
}
|
|
382
519
|
}
|
|
383
|
-
const roCombined = this.makeReadOnly(combined);
|
|
384
520
|
if (this.config.useCache) {
|
|
385
|
-
this.cache.set(cacheKey,
|
|
521
|
+
this.cache.set(cacheKey, combined);
|
|
386
522
|
}
|
|
387
|
-
return
|
|
523
|
+
return combined;
|
|
388
524
|
}
|
|
389
525
|
if (this.config.useCache) {
|
|
390
526
|
this.cache.set(cacheKey, void 0);
|
|
@@ -403,7 +539,7 @@ var LocL = class _LocL {
|
|
|
403
539
|
return void 0;
|
|
404
540
|
}
|
|
405
541
|
const keyTest = keysCopy.join(".");
|
|
406
|
-
if (
|
|
542
|
+
if (Object.prototype.hasOwnProperty.call(current, keyTest)) {
|
|
407
543
|
return current[keyTest];
|
|
408
544
|
}
|
|
409
545
|
if (!(key in current)) {
|
|
@@ -414,13 +550,54 @@ var LocL = class _LocL {
|
|
|
414
550
|
}
|
|
415
551
|
return current;
|
|
416
552
|
}
|
|
553
|
+
getPluralRules(lang) {
|
|
554
|
+
let rules = this.pluralRulesCache.get(lang);
|
|
555
|
+
if (!rules) {
|
|
556
|
+
rules = new Intl.PluralRules(lang);
|
|
557
|
+
this.pluralRulesCache.set(lang, rules);
|
|
558
|
+
}
|
|
559
|
+
return rules;
|
|
560
|
+
}
|
|
417
561
|
checkPlural(key, values) {
|
|
418
|
-
if (values?.count !== void 0 && !key.match(/_(one|few|many|other)$/)) {
|
|
419
|
-
const category = this.
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
562
|
+
if (values?.count !== void 0 && !key.match(/_(zero|one|two|few|many|other)$/)) {
|
|
563
|
+
const category = this.getPluralRules(this.language).select(values.count);
|
|
564
|
+
const baseValue = this.lookupWithFallback(key, void 0);
|
|
565
|
+
if (baseValue && typeof baseValue === "object") {
|
|
566
|
+
if (values.count === 0 && "zero" in baseValue) {
|
|
567
|
+
return this.interpolate(baseValue.zero, values);
|
|
568
|
+
}
|
|
569
|
+
if (category in baseValue) {
|
|
570
|
+
return this.interpolate(baseValue[category], values);
|
|
571
|
+
} else if ("other" in baseValue) {
|
|
572
|
+
this.config.devMode && console.warn(`[LocL] Missing plural: "${key}" in "${this.language}"`);
|
|
573
|
+
return this.interpolate(baseValue.other, values);
|
|
574
|
+
} else {
|
|
575
|
+
this.config.devMode && console.warn(`[LocL] Missing plural: "${key}" in "${this.language}"`);
|
|
576
|
+
return key;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (values.count === 0) {
|
|
580
|
+
const zeroResult = this.lookupWithFallback(`${key}_zero`, void 0);
|
|
581
|
+
if (zeroResult !== void 0) {
|
|
582
|
+
return this.interpolate(zeroResult, values);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
const categoryResult = this.lookupWithFallback(`${key}_${category}`, void 0);
|
|
586
|
+
if (categoryResult !== void 0) {
|
|
587
|
+
return this.interpolate(categoryResult, values);
|
|
588
|
+
}
|
|
589
|
+
const otherResult = this.lookupWithFallback(`${key}_other`, void 0);
|
|
590
|
+
if (otherResult !== void 0) {
|
|
591
|
+
this.config.devMode && console.warn(`[LocL] Missing plural: "${key}" in "${this.language}"`);
|
|
592
|
+
return this.interpolate(otherResult, values);
|
|
593
|
+
}
|
|
594
|
+
const anySuffixExists = ["zero", "one", "two", "few", "many"].some((cat) => this.lookupWithFallback(`${key}_${cat}`, void 0) !== void 0);
|
|
595
|
+
if (anySuffixExists) {
|
|
596
|
+
this.config.devMode && console.warn(`[LocL] Missing plural: "${key}" in "${this.language}"`);
|
|
597
|
+
return key;
|
|
598
|
+
}
|
|
423
599
|
}
|
|
600
|
+
return void 0;
|
|
424
601
|
}
|
|
425
602
|
interpolate(result, values) {
|
|
426
603
|
if (!values || typeof result !== "string") {
|
|
@@ -438,34 +615,66 @@ var LocL = class _LocL {
|
|
|
438
615
|
depth--;
|
|
439
616
|
if (depth === 0 && start >= 0) {
|
|
440
617
|
const raw = result.slice(start, i + 1);
|
|
441
|
-
|
|
618
|
+
let inner = raw.slice(1, -1);
|
|
619
|
+
if (inner.startsWith("{") && inner.endsWith("}")) {
|
|
620
|
+
inner = inner.slice(1, -1);
|
|
621
|
+
}
|
|
622
|
+
tokens.push({ raw, inner });
|
|
442
623
|
start = -1;
|
|
443
624
|
}
|
|
444
625
|
}
|
|
445
626
|
}
|
|
446
627
|
let output = result;
|
|
628
|
+
const processed = /* @__PURE__ */ new Set();
|
|
447
629
|
for (const token of tokens) {
|
|
630
|
+
if (processed.has(token.raw)) {
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
processed.add(token.raw);
|
|
448
634
|
let replacement = "";
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
635
|
+
const selectMatch = token.inner.match(/^\s*([^,]+),\s*select\s*,\s*([\s\S]+)$/);
|
|
636
|
+
if (selectMatch) {
|
|
637
|
+
const field = selectMatch[1].trim();
|
|
638
|
+
const casesBody = selectMatch[2];
|
|
639
|
+
const value = String(values[field] ?? "other");
|
|
640
|
+
const caseMap = {};
|
|
641
|
+
let i = 0;
|
|
642
|
+
while (i < casesBody.length) {
|
|
643
|
+
while (i < casesBody.length && /\s/.test(casesBody[i])) i++;
|
|
644
|
+
if (i >= casesBody.length) break;
|
|
645
|
+
let keyStart = i;
|
|
646
|
+
while (i < casesBody.length && casesBody[i] !== "{" && !/\s/.test(casesBody[i])) i++;
|
|
647
|
+
const caseKey = casesBody.slice(keyStart, i).trim();
|
|
648
|
+
while (i < casesBody.length && casesBody[i] !== "{") i++;
|
|
649
|
+
if (casesBody[i] === "{") {
|
|
650
|
+
let bodyDepth = 1;
|
|
651
|
+
let bodyStart = i + 1;
|
|
652
|
+
i++;
|
|
653
|
+
while (i < casesBody.length && bodyDepth > 0) {
|
|
654
|
+
if (casesBody[i] === "{") bodyDepth++;
|
|
655
|
+
else if (casesBody[i] === "}") bodyDepth--;
|
|
656
|
+
i++;
|
|
657
|
+
}
|
|
658
|
+
const caseText = casesBody.slice(bodyStart, i - 1).trim();
|
|
659
|
+
if (caseKey) {
|
|
660
|
+
caseMap[caseKey] = caseText;
|
|
661
|
+
}
|
|
459
662
|
}
|
|
460
|
-
replacement = caseMap[value] ?? caseMap.other ?? "";
|
|
461
663
|
}
|
|
664
|
+
const rawReplacement = caseMap[value] ?? caseMap.other ?? "";
|
|
665
|
+
replacement = rawReplacement.includes("{") ? this.interpolate(rawReplacement, values) : rawReplacement;
|
|
462
666
|
} else {
|
|
463
667
|
const [fieldName, formatterAndArgs] = token.inner.split("|").map((x) => x.trim());
|
|
464
668
|
const [formatterName, argsStr] = formatterAndArgs ? formatterAndArgs.split(":") : [];
|
|
465
|
-
const args = argsStr ? argsStr.split(",") : [];
|
|
669
|
+
const args = argsStr ? argsStr.split(",").map((a) => a.trim()) : [];
|
|
466
670
|
const val = values[fieldName];
|
|
467
|
-
if (formatterName
|
|
468
|
-
|
|
671
|
+
if (formatterName) {
|
|
672
|
+
if (this.formatters?.[formatterName]) {
|
|
673
|
+
replacement = this.formatters[formatterName](val, args);
|
|
674
|
+
} else {
|
|
675
|
+
this.config.devMode && console.warn(`[LocL] Formatter "${formatterName}" not found.`);
|
|
676
|
+
replacement = val !== void 0 ? String(val) : token.raw;
|
|
677
|
+
}
|
|
469
678
|
} else if (val instanceof Date) {
|
|
470
679
|
replacement = new Intl.DateTimeFormat(this.language).format(val);
|
|
471
680
|
} else if (typeof val === "number") {
|
|
@@ -474,7 +683,7 @@ var LocL = class _LocL {
|
|
|
474
683
|
replacement = val !== void 0 ? String(val) : token.raw;
|
|
475
684
|
}
|
|
476
685
|
}
|
|
477
|
-
output = output.
|
|
686
|
+
output = output.split(token.raw).join(replacement);
|
|
478
687
|
}
|
|
479
688
|
return output;
|
|
480
689
|
}
|
|
@@ -504,12 +713,20 @@ var LocL = class _LocL {
|
|
|
504
713
|
this.proxyCache.set(obj, proxy);
|
|
505
714
|
return proxy;
|
|
506
715
|
}
|
|
716
|
+
isUnsafeObjectKey(key) {
|
|
717
|
+
return key === "__proto__" || key === "constructor" || key === "prototype";
|
|
718
|
+
}
|
|
507
719
|
};
|
|
508
720
|
|
|
509
721
|
// src/index.ts
|
|
510
722
|
function initLocL(config) {
|
|
511
723
|
return new LocL(config);
|
|
512
724
|
}
|
|
725
|
+
function defineResources(resources) {
|
|
726
|
+
return resources;
|
|
727
|
+
}
|
|
513
728
|
export {
|
|
729
|
+
LocL,
|
|
730
|
+
defineResources,
|
|
514
731
|
initLocL
|
|
515
732
|
};
|
package/dist/react.d.mts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import React, { ReactElement, ReactNode } from 'react';
|
|
2
|
+
import { S as ScopeType, N as NestedKeyOf, T as TranslationObjectFor, f as PluralKeys, e as IsEmptyParams, c as ParamsFor, d as PathValue, I as InterpolationOptions, a as LocL, g as Language, h as Scope } from './LocL-v4yNIiKt.mjs';
|
|
3
|
+
|
|
4
|
+
type TransKey<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined> = (NestedKeyOf<TranslationObjectFor<S, T, Fallback>> | PluralKeys<S, T, Fallback>) & string;
|
|
5
|
+
type TransValues<T extends Record<string, any>, Fallback extends keyof T & string, S extends ScopeType<T, Fallback>, K extends string> = IsEmptyParams<ParamsFor<PathValue<TranslationObjectFor<S, T, Fallback>, K>>> extends true ? InterpolationOptions | undefined : ParamsFor<PathValue<TranslationObjectFor<S, T, Fallback>, K>> & InterpolationOptions;
|
|
6
|
+
type TransProps<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined, K extends TransKey<T, Fallback, S> = TransKey<T, Fallback, S>> = {
|
|
7
|
+
/** Strictly typed translation key */
|
|
8
|
+
i18nKey: K;
|
|
9
|
+
values?: TransValues<T, Fallback, S, K>;
|
|
10
|
+
components?: Record<string, ReactElement | ((content: ReactNode) => ReactNode)>;
|
|
11
|
+
translator?: LocL<T, any, any, any, any>;
|
|
12
|
+
scope?: S;
|
|
13
|
+
fallback?: string;
|
|
14
|
+
} | {
|
|
15
|
+
/** Dynamic / fallback key allowed ONLY when fallback string is provided */
|
|
16
|
+
i18nKey: K | (string & {});
|
|
17
|
+
values?: InterpolationOptions;
|
|
18
|
+
components?: Record<string, ReactElement | ((content: ReactNode) => ReactNode)>;
|
|
19
|
+
translator?: LocL<T, any, any, any, any>;
|
|
20
|
+
scope?: S;
|
|
21
|
+
fallback: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Renders translated text with rich React component interpolation without `dangerouslySetInnerHTML`.
|
|
25
|
+
* Subscribes reactively to language updates and supports strict type checking and autocompletion.
|
|
26
|
+
* @example
|
|
27
|
+
* ```tsx
|
|
28
|
+
* <Trans
|
|
29
|
+
* i18nKey="agreement"
|
|
30
|
+
* values={{ name: "Alice" }}
|
|
31
|
+
* components={{
|
|
32
|
+
* bold: <strong />,
|
|
33
|
+
* link: (content) => <a href="/terms">{content}</a>
|
|
34
|
+
* }}
|
|
35
|
+
* />
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare function Trans<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined, K extends TransKey<T, Fallback, S> = TransKey<T, Fallback, S>>(props: TransProps<T, Fallback, S, K>): ReactElement;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Global type registry for LocL React.
|
|
42
|
+
* Augment this interface in user application code for zero-boilerplate,
|
|
43
|
+
* 100% type-safe autocompletion across all hooks and components:
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* declare module "locl-js/react" {
|
|
48
|
+
* interface LocLRegister {
|
|
49
|
+
* translator: typeof translator;
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
interface LocLRegister {
|
|
55
|
+
}
|
|
56
|
+
type DefaultResources = LocLRegister extends {
|
|
57
|
+
translator: LocL<infer Res, any, any, any, any>;
|
|
58
|
+
} ? Res : LocLRegister extends {
|
|
59
|
+
resources: infer Res extends Record<string, any>;
|
|
60
|
+
} ? Res : Record<string, any>;
|
|
61
|
+
type DefaultFallback<T = DefaultResources> = LocLRegister extends {
|
|
62
|
+
translator: LocL<any, infer FB, any, any, any>;
|
|
63
|
+
} ? FB : LocLRegister extends {
|
|
64
|
+
fallbackLanguage: infer FB extends string;
|
|
65
|
+
} ? FB : [keyof T & string] extends [never] ? string : keyof T & string;
|
|
66
|
+
declare const LocLContext: React.Context<LocL<any, any, any, any, any> | null>;
|
|
67
|
+
interface LocLProviderProps<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined> {
|
|
68
|
+
translator: LocL<T, Fallback, S, any, any>;
|
|
69
|
+
children: ReactNode;
|
|
70
|
+
}
|
|
71
|
+
declare function LocLProvider<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined>({ translator, children }: LocLProviderProps<T, Fallback, S>): React.JSX.Element;
|
|
72
|
+
interface UseLocLResult<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined> {
|
|
73
|
+
translator: LocL<T, Fallback, S, any, any>;
|
|
74
|
+
language: Language<T>;
|
|
75
|
+
changeLanguage: (lang: Language<T>) => void;
|
|
76
|
+
t: LocL<T, Fallback, S>["t"];
|
|
77
|
+
plural: LocL<T, Fallback, S>["plural"];
|
|
78
|
+
rich: LocL<T, Fallback, S>["rich"];
|
|
79
|
+
format: LocL<T, Fallback, S>["format"];
|
|
80
|
+
}
|
|
81
|
+
interface UseTranslationResult<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined> extends UseLocLResult<T, Fallback, S> {
|
|
82
|
+
}
|
|
83
|
+
declare function useLocL<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined>(customTranslator?: LocL<T, Fallback, S, any, any> | LocL<any, any, any, any, any>): UseLocLResult<T, Fallback, S>;
|
|
84
|
+
declare function useTranslation<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>>(scope?: undefined, customTranslator?: LocL<T, Fallback, any, any, any> | LocL<any, any, any, any, any>): UseTranslationResult<T, Fallback, undefined>;
|
|
85
|
+
declare function useTranslation<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = ScopeType<T, Fallback>>(scope: S, customTranslator?: LocL<T, Fallback, any, any, any> | LocL<any, any, any, any, any>): UseTranslationResult<T, Fallback, S>;
|
|
86
|
+
interface BoundLocLProviderProps<T extends Record<string, any>, Fallback extends keyof T & string> {
|
|
87
|
+
translator?: LocL<any, any, any, any, any>;
|
|
88
|
+
children: ReactNode;
|
|
89
|
+
}
|
|
90
|
+
interface LocLReactSuite<T extends Record<string, any>, Fallback extends keyof T & string> {
|
|
91
|
+
LocLContext: React.Context<LocL<any, any, any, any, any> | null>;
|
|
92
|
+
LocLProvider: (props: BoundLocLProviderProps<T, Fallback>) => ReactElement;
|
|
93
|
+
useLocL: <S extends ScopeType<T, Fallback> = undefined>(customTranslator?: LocL<any, any, any, any, any>) => UseLocLResult<T, Fallback, S>;
|
|
94
|
+
useTranslation: {
|
|
95
|
+
(): UseTranslationResult<T, Fallback, undefined>;
|
|
96
|
+
<S extends Scope<T, Fallback>>(scope: S): UseTranslationResult<T, Fallback, S>;
|
|
97
|
+
<S extends Scope<T, Fallback>[]>(scope: S): UseTranslationResult<T, Fallback, S>;
|
|
98
|
+
(scope?: undefined, customTranslator?: LocL<any, any, any, any, any>): UseTranslationResult<T, Fallback, undefined>;
|
|
99
|
+
<S extends ScopeType<T, Fallback>>(scope: S, customTranslator?: LocL<any, any, any, any, any>): UseTranslationResult<T, Fallback, S>;
|
|
100
|
+
};
|
|
101
|
+
Trans: <S extends ScopeType<T, Fallback> = undefined, K extends TransKey<T, Fallback, S> = TransKey<T, Fallback, S>>(props: TransProps<T, Fallback, S, K>) => ReactElement;
|
|
102
|
+
translator: LocL<T, Fallback, any, any, any>;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Creates a pre-bound, zero-declaration React integration suite for a LocL translator instance.
|
|
106
|
+
*
|
|
107
|
+
* Inifers resources, fallback language, translation keys, and namespace scopes
|
|
108
|
+
* automatically without requiring `.d.ts` module declarations or manual generics.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* // i18n-react.ts
|
|
113
|
+
* export const { LocLProvider, useTranslation, useLocL, Trans } = createLocLReact(translator);
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
declare function createLocLReact<T extends Record<string, any>, Fallback extends keyof T & string>(defaultTranslator: LocL<T, Fallback, any, any, any>): LocLReactSuite<T, Fallback>;
|
|
117
|
+
|
|
118
|
+
export { type BoundLocLProviderProps, type DefaultFallback, type DefaultResources, LocLContext, LocLProvider, type LocLProviderProps, type LocLReactSuite, type LocLRegister, Trans, type TransKey, type TransProps, type TransValues, type UseLocLResult, type UseTranslationResult, createLocLReact, useLocL, useTranslation };
|