locl-js 1.0.2 → 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/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 +288 -76
- package/dist/index.mjs +283 -73
- 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 +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,106 @@ 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.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
|
+
}
|
|
230
328
|
/**
|
|
231
329
|
* Changes the current language of the translator.
|
|
232
330
|
* @param lang - The new language to set.
|
|
233
331
|
*/
|
|
234
332
|
changeLanguage(lang) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
+
}
|
|
238
344
|
}
|
|
239
345
|
}
|
|
240
346
|
t(key, values, format) {
|
|
@@ -243,6 +349,9 @@ var LocL = class _LocL {
|
|
|
243
349
|
}
|
|
244
350
|
const pluralCheckResult = this.checkPlural(key, values);
|
|
245
351
|
if (pluralCheckResult !== void 0) {
|
|
352
|
+
if (format && format.formatter) {
|
|
353
|
+
return this.applyFormat(pluralCheckResult, format.formatter, format.args);
|
|
354
|
+
}
|
|
246
355
|
return pluralCheckResult;
|
|
247
356
|
}
|
|
248
357
|
const result = this.lookupWithFallback(key);
|
|
@@ -273,45 +382,54 @@ var LocL = class _LocL {
|
|
|
273
382
|
format(value, formatter, args) {
|
|
274
383
|
return this.applyFormat(value, formatter, args);
|
|
275
384
|
}
|
|
385
|
+
plural(key, values, format) {
|
|
386
|
+
return this.t(key, values, format);
|
|
387
|
+
}
|
|
276
388
|
/**
|
|
277
|
-
* Translates a key
|
|
278
|
-
*
|
|
279
|
-
* @param key - The
|
|
280
|
-
* @param
|
|
281
|
-
* @param
|
|
282
|
-
* @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
|
+
* ```
|
|
283
401
|
*/
|
|
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;
|
|
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));
|
|
298
414
|
}
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
const
|
|
302
|
-
|
|
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;
|
|
303
420
|
}
|
|
304
|
-
if (
|
|
305
|
-
|
|
421
|
+
if (lastIndex < rawStr.length) {
|
|
422
|
+
result.push(rawStr.slice(lastIndex));
|
|
306
423
|
}
|
|
307
|
-
return
|
|
424
|
+
return result;
|
|
308
425
|
}
|
|
309
426
|
get(key) {
|
|
310
427
|
const base = this.buildTranslationObject(this.language);
|
|
311
428
|
if (!key) {
|
|
312
|
-
return base;
|
|
429
|
+
return base ? this.makeReadOnly(base) : void 0;
|
|
313
430
|
}
|
|
314
|
-
|
|
431
|
+
const val = this.lookupWithFallback(key, base);
|
|
432
|
+
return val && typeof val === "object" ? this.makeReadOnly(val) : val;
|
|
315
433
|
}
|
|
316
434
|
/**
|
|
317
435
|
* Gets a nested object from the translations.
|
|
@@ -326,12 +444,27 @@ var LocL = class _LocL {
|
|
|
326
444
|
if (!value) {
|
|
327
445
|
return null;
|
|
328
446
|
}
|
|
329
|
-
const
|
|
330
|
-
|
|
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;
|
|
331
456
|
}
|
|
332
457
|
getCacheKey(language) {
|
|
333
|
-
const scopeKey = Array.isArray(this.scope) ? this.scope.join("|") : this.scope
|
|
334
|
-
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
|
+
}
|
|
335
468
|
}
|
|
336
469
|
lookupWithFallback(key, base) {
|
|
337
470
|
const keys = key.split(".");
|
|
@@ -349,7 +482,7 @@ var LocL = class _LocL {
|
|
|
349
482
|
return result;
|
|
350
483
|
}
|
|
351
484
|
buildTranslationObject(language) {
|
|
352
|
-
const langObject = this.
|
|
485
|
+
const langObject = this.resources[language];
|
|
353
486
|
if (!this.scope) {
|
|
354
487
|
return langObject;
|
|
355
488
|
}
|
|
@@ -380,11 +513,10 @@ var LocL = class _LocL {
|
|
|
380
513
|
}, combined);
|
|
381
514
|
}
|
|
382
515
|
}
|
|
383
|
-
const roCombined = this.makeReadOnly(combined);
|
|
384
516
|
if (this.config.useCache) {
|
|
385
|
-
this.cache.set(cacheKey,
|
|
517
|
+
this.cache.set(cacheKey, combined);
|
|
386
518
|
}
|
|
387
|
-
return
|
|
519
|
+
return combined;
|
|
388
520
|
}
|
|
389
521
|
if (this.config.useCache) {
|
|
390
522
|
this.cache.set(cacheKey, void 0);
|
|
@@ -414,13 +546,54 @@ var LocL = class _LocL {
|
|
|
414
546
|
}
|
|
415
547
|
return current;
|
|
416
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
|
+
}
|
|
417
557
|
checkPlural(key, values) {
|
|
418
|
-
if (values?.count !== void 0 && !key.match(/_(one|few|many|other)$/)) {
|
|
419
|
-
const category = this.
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
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
|
+
}
|
|
423
595
|
}
|
|
596
|
+
return void 0;
|
|
424
597
|
}
|
|
425
598
|
interpolate(result, values) {
|
|
426
599
|
if (!values || typeof result !== "string") {
|
|
@@ -438,34 +611,66 @@ var LocL = class _LocL {
|
|
|
438
611
|
depth--;
|
|
439
612
|
if (depth === 0 && start >= 0) {
|
|
440
613
|
const raw = result.slice(start, i + 1);
|
|
441
|
-
|
|
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 });
|
|
442
619
|
start = -1;
|
|
443
620
|
}
|
|
444
621
|
}
|
|
445
622
|
}
|
|
446
623
|
let output = result;
|
|
624
|
+
const processed = /* @__PURE__ */ new Set();
|
|
447
625
|
for (const token of tokens) {
|
|
626
|
+
if (processed.has(token.raw)) {
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
processed.add(token.raw);
|
|
448
630
|
let replacement = "";
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
+
}
|
|
459
658
|
}
|
|
460
|
-
replacement = caseMap[value] ?? caseMap.other ?? "";
|
|
461
659
|
}
|
|
660
|
+
const rawReplacement = caseMap[value] ?? caseMap.other ?? "";
|
|
661
|
+
replacement = rawReplacement.includes("{") ? this.interpolate(rawReplacement, values) : rawReplacement;
|
|
462
662
|
} else {
|
|
463
663
|
const [fieldName, formatterAndArgs] = token.inner.split("|").map((x) => x.trim());
|
|
464
664
|
const [formatterName, argsStr] = formatterAndArgs ? formatterAndArgs.split(":") : [];
|
|
465
|
-
const args = argsStr ? argsStr.split(",") : [];
|
|
665
|
+
const args = argsStr ? argsStr.split(",").map((a) => a.trim()) : [];
|
|
466
666
|
const val = values[fieldName];
|
|
467
|
-
if (formatterName
|
|
468
|
-
|
|
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
|
+
}
|
|
469
674
|
} else if (val instanceof Date) {
|
|
470
675
|
replacement = new Intl.DateTimeFormat(this.language).format(val);
|
|
471
676
|
} else if (typeof val === "number") {
|
|
@@ -474,7 +679,7 @@ var LocL = class _LocL {
|
|
|
474
679
|
replacement = val !== void 0 ? String(val) : token.raw;
|
|
475
680
|
}
|
|
476
681
|
}
|
|
477
|
-
output = output.
|
|
682
|
+
output = output.split(token.raw).join(replacement);
|
|
478
683
|
}
|
|
479
684
|
return output;
|
|
480
685
|
}
|
|
@@ -510,6 +715,11 @@ var LocL = class _LocL {
|
|
|
510
715
|
function initLocL(config) {
|
|
511
716
|
return new LocL(config);
|
|
512
717
|
}
|
|
718
|
+
function defineResources(resources) {
|
|
719
|
+
return resources;
|
|
720
|
+
}
|
|
513
721
|
export {
|
|
722
|
+
LocL,
|
|
723
|
+
defineResources,
|
|
514
724
|
initLocL
|
|
515
725
|
};
|
package/dist/react.d.mts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import React, { ReactElement, ReactNode } from 'react';
|
|
2
|
+
import { S as ScopeType, N as NestedKeyOf, T as TranslationObjectFor, P as PluralKeys, I as InterpolationOptions, a as LocL, c as Language } from './LocL-fN9Yari8.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
|
+
interface TransProps<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined, K extends string = TransKey<T, Fallback, S>> {
|
|
6
|
+
/** The translation key to look up (with IDE autocomplete + fallback tolerance) */
|
|
7
|
+
i18nKey: K | (string & {});
|
|
8
|
+
/** Values to interpolate into variable placeholders ({name}, {{name}}) */
|
|
9
|
+
values?: InterpolationOptions;
|
|
10
|
+
/**
|
|
11
|
+
* Component elements or functions to substitute for `<tag>content</tag>`
|
|
12
|
+
* @example
|
|
13
|
+
* components={{
|
|
14
|
+
* bold: <strong />,
|
|
15
|
+
* link: (content) => <a href="/terms">{content}</a>
|
|
16
|
+
* }}
|
|
17
|
+
*/
|
|
18
|
+
components?: Record<string, ReactElement | ((content: ReactNode) => ReactNode)>;
|
|
19
|
+
/** Optional custom translator instance if not using LocLContext */
|
|
20
|
+
translator?: LocL<any, any, any, any, any>;
|
|
21
|
+
/** Optional namespace scope for the translation key */
|
|
22
|
+
scope?: S;
|
|
23
|
+
/** Fallback string if key is not found */
|
|
24
|
+
fallback?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Renders translated text with rich React component interpolation without `dangerouslySetInnerHTML`.
|
|
28
|
+
* Subscribes reactively to language updates and supports strict type checking and autocompletion.
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* <Trans
|
|
32
|
+
* i18nKey="agreement"
|
|
33
|
+
* values={{ name: "Alice" }}
|
|
34
|
+
* components={{
|
|
35
|
+
* bold: <strong />,
|
|
36
|
+
* link: <a href="/terms" />
|
|
37
|
+
* }}
|
|
38
|
+
* />
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
declare function Trans<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined, K extends string = TransKey<T, Fallback, S>>({ i18nKey, values, components, translator: customTranslator, scope, fallback }: TransProps<T, Fallback, S, K>): ReactElement;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Global type registry for LocL React.
|
|
45
|
+
* Augment this interface in user application code for zero-boilerplate,
|
|
46
|
+
* 100% type-safe autocompletion across all hooks and components:
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* declare module "locl-js/react" {
|
|
51
|
+
* interface LocLRegister {
|
|
52
|
+
* translator: typeof translator;
|
|
53
|
+
* }
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
interface LocLRegister {
|
|
58
|
+
}
|
|
59
|
+
type DefaultResources = LocLRegister extends {
|
|
60
|
+
translator: LocL<infer Res, any, any, any, any>;
|
|
61
|
+
} ? Res : LocLRegister extends {
|
|
62
|
+
resources: infer Res extends Record<string, any>;
|
|
63
|
+
} ? Res : Record<string, any>;
|
|
64
|
+
type DefaultFallback<T = DefaultResources> = LocLRegister extends {
|
|
65
|
+
translator: LocL<any, infer FB, any, any, any>;
|
|
66
|
+
} ? FB : LocLRegister extends {
|
|
67
|
+
fallbackLanguage: infer FB extends string;
|
|
68
|
+
} ? FB : [keyof T & string] extends [never] ? string : keyof T & string;
|
|
69
|
+
declare const LocLContext: React.Context<LocL<any, any, any, any, any> | null>;
|
|
70
|
+
interface LocLProviderProps<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined> {
|
|
71
|
+
translator: LocL<T, Fallback, S, any, any>;
|
|
72
|
+
children: ReactNode;
|
|
73
|
+
}
|
|
74
|
+
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;
|
|
75
|
+
interface UseLocLResult<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined> {
|
|
76
|
+
translator: LocL<T, Fallback, S, any, any>;
|
|
77
|
+
language: Language<T>;
|
|
78
|
+
changeLanguage: (lang: Language<T>) => void;
|
|
79
|
+
t: LocL<T, Fallback, S>["t"];
|
|
80
|
+
plural: LocL<T, Fallback, S>["plural"];
|
|
81
|
+
rich: LocL<T, Fallback, S>["rich"];
|
|
82
|
+
format: LocL<T, Fallback, S>["format"];
|
|
83
|
+
}
|
|
84
|
+
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> {
|
|
85
|
+
}
|
|
86
|
+
declare function useLocL<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined>(customTranslator?: LocL<any, any, any, any, any>): UseLocLResult<T, Fallback, S>;
|
|
87
|
+
declare function useTranslation<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>>(scope?: undefined, customTranslator?: LocL<any, any, any, any, any>): UseTranslationResult<T, Fallback, undefined>;
|
|
88
|
+
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<any, any, any, any, any>): UseTranslationResult<T, Fallback, S>;
|
|
89
|
+
interface BoundLocLProviderProps<T extends Record<string, any>, Fallback extends keyof T & string> {
|
|
90
|
+
/** Optional override translator; defaults to the instance passed to `createLocLReact` */
|
|
91
|
+
translator?: LocL<any, any, any, any, any>;
|
|
92
|
+
children: ReactNode;
|
|
93
|
+
}
|
|
94
|
+
interface LocLReactSuite<T extends Record<string, any>, Fallback extends keyof T & string> {
|
|
95
|
+
LocLContext: React.Context<LocL<any, any, any, any, any> | null>;
|
|
96
|
+
LocLProvider: (props: BoundLocLProviderProps<T, Fallback>) => ReactElement;
|
|
97
|
+
useLocL: <S extends ScopeType<T, Fallback> = undefined>(customTranslator?: LocL<any, any, any, any, any>) => UseLocLResult<T, Fallback, S>;
|
|
98
|
+
useTranslation: {
|
|
99
|
+
(scope?: undefined, customTranslator?: LocL<any, any, any, any, any>): UseTranslationResult<T, Fallback, undefined>;
|
|
100
|
+
<S extends ScopeType<T, Fallback>>(scope: S, customTranslator?: LocL<any, any, any, any, any>): UseTranslationResult<T, Fallback, S>;
|
|
101
|
+
<S extends ScopeType<T, Fallback> = any>(scope?: S, customTranslator?: LocL<any, any, any, any, any>): UseTranslationResult<T, Fallback, S>;
|
|
102
|
+
};
|
|
103
|
+
Trans: <S extends ScopeType<T, Fallback> = undefined, K extends string = TransKey<T, Fallback, S>>(props: TransProps<T, Fallback, S, K>) => ReactElement;
|
|
104
|
+
translator: LocL<T, Fallback, any, any, any>;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Creates a pre-bound, zero-declaration React integration suite for a LocL translator instance.
|
|
108
|
+
*
|
|
109
|
+
* Inifers resources, fallback language, translation keys, and namespace scopes
|
|
110
|
+
* automatically without requiring `.d.ts` module declarations or manual generics.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* // i18n-react.ts
|
|
115
|
+
* export const { LocLProvider, useTranslation, useLocL, Trans } = createLocLReact(translator);
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
declare function createLocLReact<T extends Record<string, any>, Fallback extends keyof T & string>(defaultTranslator: LocL<T, Fallback, any, any, any>): LocLReactSuite<T, Fallback>;
|
|
119
|
+
|
|
120
|
+
export { type BoundLocLProviderProps, type DefaultFallback, type DefaultResources, LocLContext, LocLProvider, type LocLProviderProps, type LocLReactSuite, type LocLRegister, Trans, type TransKey, type TransProps, type UseLocLResult, type UseTranslationResult, createLocLReact, useLocL, useTranslation };
|