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/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
- config.resources = this.makeReadOnly(config.resources);
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.pluralRules = new Intl.PluralRules(this.language);
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 (this.cache.has(cacheKey)) {
201
- return this.cache.get(cacheKey);
202
+ if (root.cache.has(cacheKey)) {
203
+ return root.cache.get(cacheKey);
202
204
  }
203
- const result = new Proxy(this, {
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[prop];
227
+ return Reflect.get(target, prop, receiver);
212
228
  }
213
229
  });
214
- this.cache.set(cacheKey, result);
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
- if (Object.keys(this.config.resources).includes(lang)) {
236
- this.language = lang;
237
- this.pluralRules = new Intl.PluralRules(this.language);
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 with pluralization.
278
- * It automatically selects the correct plural form based on the `count` value.
279
- * @param key - The base key for the pluralization.
280
- * @param values - An object with a `count` property and other values to interpolate.
281
- * @param format - An object with formatting options.
282
- * @returns The translated, pluralized, and formatted string.
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
- plural(key, values, format) {
285
- const category = this.pluralRules.select(values.count);
286
- const baseKey = key;
287
- const baseValue = this.lookupWithFallback(baseKey);
288
- let interpolated;
289
- if (baseValue && typeof baseValue === "object") {
290
- if (category in baseValue) {
291
- interpolated = this.interpolate(baseValue[category], values) ?? baseKey;
292
- } else if ("other" in baseValue) {
293
- this.config.devMode && console.warn(`[LocL] Missing plural: "${baseKey}" in "${this.language}"`);
294
- interpolated = this.interpolate(baseValue.other, values) ?? baseKey;
295
- } else {
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
- } else {
300
- const pluralKey = `${baseKey}_${category}`;
301
- const result = this.lookupWithFallback(pluralKey) ?? this.lookupWithFallback(`${baseKey}_other`) ?? this.lookupWithFallback(baseKey);
302
- interpolated = this.interpolate(result, values) ?? baseKey;
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 (format && format.formatter) {
305
- return this.applyFormat(interpolated, format.formatter, format.args);
425
+ if (lastIndex < rawStr.length) {
426
+ result.push(rawStr.slice(lastIndex));
306
427
  }
307
- return interpolated;
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
- return this.lookupWithFallback(key, base);
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 language = value.substring(0, 2);
330
- return Object.keys(this.config.resources).includes(language) ? language : null;
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 ?? this.language}::${scopeKey}`;
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.config.resources[language];
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, roCombined);
521
+ this.cache.set(cacheKey, combined);
386
522
  }
387
- return roCombined;
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 (current.hasOwnProperty(keyTest)) {
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.pluralRules.select(values.count);
420
- const pluralKey = `${key}_${category}`;
421
- const pluralResult = this.lookupWithFallback(pluralKey);
422
- return this.interpolate(pluralResult, values);
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
- tokens.push({ raw, inner: raw.slice(1, -1).trim() });
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
- if (token.inner.includes("select")) {
450
- const [field, type, ...rest] = token.inner.split(",").map((x) => x.trim());
451
- if (type === "select") {
452
- const value = String(values[field] ?? "other");
453
- const caseRegex = /(\w+)\s*\{([^}]*)\}/g;
454
- const caseMap = {};
455
- let match;
456
- while ((match = caseRegex.exec(rest.join(" "))) !== null) {
457
- const [, key, text] = match;
458
- caseMap[key] = text.trim();
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 && this.formatters?.[formatterName]) {
468
- replacement = this.formatters[formatterName](val, args);
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.replace(token.raw, replacement);
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
  };
@@ -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 };