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/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,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
- if (Object.keys(this.config.resources).includes(lang)) {
236
- this.language = lang;
237
- this.pluralRules = new Intl.PluralRules(this.language);
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 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.
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
- 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;
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
- } 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;
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 (format && format.formatter) {
305
- return this.applyFormat(interpolated, format.formatter, format.args);
421
+ if (lastIndex < rawStr.length) {
422
+ result.push(rawStr.slice(lastIndex));
306
423
  }
307
- return interpolated;
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
- return this.lookupWithFallback(key, base);
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 language = value.substring(0, 2);
330
- return Object.keys(this.config.resources).includes(language) ? language : null;
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 ?? this.language}::${scopeKey}`;
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.config.resources[language];
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, roCombined);
517
+ this.cache.set(cacheKey, combined);
386
518
  }
387
- return roCombined;
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.pluralRules.select(values.count);
420
- const pluralKey = `${key}_${category}`;
421
- const pluralResult = this.lookupWithFallback(pluralKey);
422
- return this.interpolate(pluralResult, values);
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
- tokens.push({ raw, inner: raw.slice(1, -1).trim() });
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
- 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();
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 && this.formatters?.[formatterName]) {
468
- replacement = this.formatters[formatterName](val, args);
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.replace(token.raw, replacement);
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
  };
@@ -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 };