locl-js 2.0.0 → 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 CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  **LocL** is an ultra-fast, lightweight, and fully-featured TypeScript internationalization (i18n) library. Designed for modern web applications, backend services, and multi-framework setups, it provides compile-time type-safety, automatic pluralization, ICU-style select branching, built-in formatters, and reactive subscriptions with **zero external dependencies**.
6
6
 
7
- It is inspired by libraries like `i18next` and `react-i18next`, but engineered with a focus on strict TypeScript type checking, sub-millisecond execution, clean APIs, and 100% test coverage.
7
+ It is inspired by libraries like `i18next` and `react-i18next`, but engineered with a focus on strict TypeScript type checking, sub-millisecond execution, clean APIs, and **100% test coverage across every statement, branch, and function**.
8
8
 
9
9
  ---
10
10
 
@@ -16,12 +16,12 @@ It is inspired by libraries like `i18next` and `react-i18next`, but engineered w
16
16
  - **`createLocLReact` Hook Factory**: Zero-declaration, pre-bound React suite.
17
17
  - **`LocLRegister` Declaration Merging**: Ambient global autocomplete for standalone imports.
18
18
  - **📦 Zero Dependencies & Lightweight**: Pure TypeScript with no runtime dependencies.
19
- - **🔄 Drop-in i18next Compat**: Swap existing `i18next` codebases effortlessly using `locl-js/compat` without rewriting translation call sites. Supports colon namespaces (`auth:login`), default fallbacks, and event listener cleanup.
19
+ - **🔄 Drop-in i18next Compat**: Swap existing `i18next` codebases effortlessly using `locl-js/compat` without rewriting translation call sites. Supports colon namespaces (`auth:login`), strict parameter checking, default fallbacks, and event listener cleanup.
20
20
  - **🌐 Multi-Framework Reactive Store**: Implements the standard reactive store contract via `translator.subscribe()`, working out of the box in Svelte, Vue, Solid, and Node.js.
21
21
  - **🔢 Automatic Plurals**: Handles object plurals (`{ zero, one, other }`) and suffix plurals (`_zero`, `_one`, `_other`) directly via `t()` or dedicated `plural()`.
22
22
  - **🔀 ICU Select Support**: Conditional branching (`{gender, select, male {He} female {She} other {They}}`) with nested parameter interpolation (`{gender, select, male {He has {count} items}}`).
23
23
  - **🔧 Built-in & Custom Formatters**: Format dates, numbers, currencies, strings, and booleans inline via pipe syntax (`{val | number}`, `{date | date:short}`).
24
- - **⚡ High Performance Caching**: Granular scope-based translation caching and multi-proxy instance reuse.
24
+ - **⚡ High Performance Caching**: Granular scope-based translation caching and multi-proxy instance reuse with zero proxy-of-proxy overhead.
25
25
  - **🛡️ Prototype-Safe Dynamic Resources**: Safely merge resources dynamically at runtime with built-in prototype pollution guards.
26
26
  - **💯 100% Test Coverage**: Fully verified across every line, branch, function, and edge case.
27
27
 
@@ -114,7 +114,7 @@ translator.t("staticNotice");
114
114
 
115
115
  ### Workflow A: Zero-Declaration Factory (`createLocLReact`) ⭐ *Recommended*
116
116
 
117
- No `.d.ts` declaration merging required. Bind your translator once and export typed components and hooks:
117
+ No `.d.ts` declaration merging required. Pass your translator instance to `createLocLReact` once and export pre-bound, 100% typed components and hooks:
118
118
 
119
119
  ```tsx
120
120
  // src/i18n-react.ts
@@ -162,7 +162,7 @@ function Header() {
162
162
  }
163
163
 
164
164
  function Profile() {
165
- // Scoped autocomplete: keys are restricted to the "user" namespace!
165
+ // Scoped autocomplete: keys are restricted strictly to the "user" namespace!
166
166
  const { t } = useTranslation("user");
167
167
 
168
168
  return (
@@ -183,7 +183,7 @@ function Profile() {
183
183
 
184
184
  ### Workflow B: Global Type Registry (`LocLRegister`)
185
185
 
186
- If you prefer importing directly from `"locl-js/react"`, augment `LocLRegister` once in your project (e.g. `src/i18n.d.ts`):
186
+ If you prefer importing hooks and components directly from `"locl-js/react"`, augment `LocLRegister` once in your project (e.g. `src/i18n.d.ts`):
187
187
 
188
188
  ```typescript
189
189
  import { translator } from "./i18n";
@@ -195,7 +195,7 @@ declare module "locl-js/react" {
195
195
  }
196
196
  ```
197
197
 
198
- Now, standalone imports automatically have full autocomplete everywhere:
198
+ Now, standalone imports automatically have full autocomplete everywhere without passing any generic arguments:
199
199
 
200
200
  ```tsx
201
201
  import { useTranslation, Trans } from "locl-js/react";
@@ -224,38 +224,60 @@ Renders complex translations containing formatting tags (`<bold>read</bold>`, `<
224
224
  />
225
225
  ```
226
226
 
227
- - **Tag element replacement**: Replaces tags with JSX elements (e.g. `<strong />`) while preserving inner text as children.
228
- - **Function renderers**: Supports custom render functions: `(content) => <span style={{ fontWeight: "bold" }}>{content}</span>`.
229
- - **Reactive updates**: Subscribes to `useSyncExternalStore` so language changes re-render smoothly.
227
+ #### Key Capabilities
228
+
229
+ - **Strict Key Validation**: Autocompletes valid keys and flags typos at compile time.
230
+ - **Strict Value Validation**: If the template contains `{name}`, `values={{ name: ... }}` is required at compile time.
231
+ - **Dynamic Key Fallback**: Unregistered or dynamic keys are allowed whenever an explicit `fallback` string is provided (`fallback="Default string"`).
232
+ - **Scope Support**: Narrow keys using the `scope` prop: `<Trans scope="user" i18nKey="greeting" values={{ name: "Alice" }} />`.
233
+ - **Standalone `translator` Prop**: When passing `translator={translator}`, `Trans` automatically infers the dictionary and provides autocomplete for that translator's keys.
234
+ - **Tag Element Replacement**: Clones JSX elements (e.g. `<strong />`) while preserving inner text as children.
235
+ - **Function Renderers**: Supports custom render functions: `(content) => <span style={{ fontWeight: "bold" }}>{content}</span>`.
236
+ - **Reactive Updates**: Subscribes to `useSyncExternalStore` so language changes re-render smoothly.
230
237
 
231
238
  ---
232
239
 
233
240
  ## Drop-in `i18next` Compatibility (`locl-js/compat`)
234
241
 
235
- Migrate existing `i18next` codebases effortlessly without refactoring callsites:
242
+ Migrate existing `i18next` codebases effortlessly without refactoring callsites. `locl-js/compat` preserves `i18next` runtime flexibility while offering full compile-time strict type checking:
236
243
 
237
244
  ```typescript
238
- import { initLocL } from "locl-js";
245
+ import { initLocL, defineResources } from "locl-js";
239
246
  import { toI18next, createI18nextCompat } from "locl-js/compat";
240
247
 
248
+ const resources = defineResources({
249
+ en: {
250
+ common: {
251
+ hello: "Hello, {name}!",
252
+ save: "Save"
253
+ },
254
+ apple_one: "1 apple",
255
+ apple_other: "{count} apples"
256
+ }
257
+ });
241
258
  const locl = initLocL({ resources, fallbackLanguage: "en" });
242
259
  export const i18n = toI18next(locl);
243
260
 
244
- // Standard i18next patterns supported seamlessly:
245
- i18n.t("common:login", { name: "Alice" }); // Colon namespaces
246
- i18n.t("common.login"); // Dot namespaces
247
- i18n.t("missing.key", "Default String"); // Default values
248
- i18n.exists("common:login"); // Key existence checks
261
+ // Colon and dot syntax autocompleted and strictly enforced:
262
+ i18n.t("common:hello", { name: "Maria" }); // Requires { name }!
263
+ i18n.t("common.save"); // Static key: options optional!
264
+
265
+ // Plurals strictly enforce { count: number }:
266
+ i18n.t("apple", { count: 5 });
267
+
268
+ // Dynamic and fallback keys:
269
+ i18n.t("missing.key", "Default Fallback");
270
+ i18n.t("missing.key", { defaultValue: "Fallback Text" });
249
271
 
250
- // Plurals via count:
251
- i18n.t("apple", { count: 5 }); // "5 apples"
272
+ // Key existence check (with autocomplete for colon and dot keys):
273
+ i18n.exists("common:save"); // true
252
274
 
253
- // Dynamic resource bundle management:
275
+ // Resource bundle management:
254
276
  i18n.addResourceBundle("en", "dynamic", { title: "Title" });
255
- i18n.hasResourceBundle("en", "dynamic"); // true
256
- i18n.getResourceBundle("en", "dynamic"); // { title: "Title" }
277
+ i18n.hasResourceBundle("en", "dynamic"); // true
278
+ i18n.getResourceBundle("en", "dynamic"); // { title: "Title" }
257
279
 
258
- // Event listeners with unbind cleanup:
280
+ // Event listeners with cleanup callback:
259
281
  const unbind = i18n.on("languageChanged", (lng) => {
260
282
  console.log("Language changed to", lng);
261
283
  });
@@ -476,16 +498,16 @@ console.log(multiT.t("dashboard.welcome", { name: "Alice" })); // "Welcome, Alic
476
498
 
477
499
  ### React Exports (`locl-js/react`)
478
500
 
479
- - **`createLocLReact(translator)`**: Returns a pre-bound React suite (`LocLProvider`, `useTranslation`, `useLocL`, `Trans`).
501
+ - **`createLocLReact(translator)`**: Returns a pre-bound, zero-declaration React suite (`LocLProvider`, `useTranslation`, `useLocL`, `Trans`, `LocLContext`).
480
502
  - **`LocLProvider`**: React Context Provider for the active translator.
481
- - **`useTranslation(scope?, customTranslator?)`**: Translation hook with scope support.
503
+ - **`useTranslation(scope?, customTranslator?)`**: Translation hook with namespace scoping support.
482
504
  - **`useLocL(customTranslator?)`**: Hook returning translator instance and reactive language state.
483
- - **`<Trans />`**: Rich JSX tag interpolation component.
505
+ - **`<Trans />`**: Rich JSX component interpolation component supporting strict key/param checking, fallbacks, and scope narrowing.
484
506
  - **`LocLRegister`**: Ambient interface for global type registration.
485
507
 
486
508
  ### Compat Exports (`locl-js/compat`)
487
509
 
488
- - **`toI18next(translator)`**: Wraps a LocL instance into an `i18next`-compatible API.
510
+ - **`toI18next(translator)`**: Wraps a LocL instance into an `i18next`-compatible API with strict parameter checking.
489
511
  - **`createI18nextCompat(translator)`**: Alias of `toI18next`.
490
512
 
491
513
  ### `LocL` Instance Methods
@@ -432,6 +432,7 @@ declare class LocL<T extends Record<string, any>, Fallback extends keyof T & str
432
432
  private interpolate;
433
433
  private applyFormat;
434
434
  private makeReadOnly;
435
+ private isUnsafeObjectKey;
435
436
  }
436
437
 
437
- export { type Formatter as F, type InterpolationOptions as I, type LocLConfig as L, type NestedKeyOf as N, type PluralKeys as P, type ScopeType as S, type TranslationObjectFor as T, LocL as a, type LangWithPlurals as b, type Language as c };
438
+ export { type Formatter as F, type InterpolationOptions as I, type LocLConfig as L, type NestedKeyOf as N, type PluralParamsFor as P, type ScopeType as S, type TranslationObjectFor as T, LocL as a, type LangWithPlurals as b, type ParamsFor as c, type PathValue as d, type IsEmptyParams as e, type PluralKeys as f, type Language as g, type Scope as h };
@@ -432,6 +432,7 @@ declare class LocL<T extends Record<string, any>, Fallback extends keyof T & str
432
432
  private interpolate;
433
433
  private applyFormat;
434
434
  private makeReadOnly;
435
+ private isUnsafeObjectKey;
435
436
  }
436
437
 
437
- export { type Formatter as F, type InterpolationOptions as I, type LocLConfig as L, type NestedKeyOf as N, type PluralKeys as P, type ScopeType as S, type TranslationObjectFor as T, LocL as a, type LangWithPlurals as b, type Language as c };
438
+ export { type Formatter as F, type InterpolationOptions as I, type LocLConfig as L, type NestedKeyOf as N, type PluralParamsFor as P, type ScopeType as S, type TranslationObjectFor as T, LocL as a, type LangWithPlurals as b, type ParamsFor as c, type PathValue as d, type IsEmptyParams as e, type PluralKeys as f, type Language as g, type Scope as h };
package/dist/compat.d.mts CHANGED
@@ -1,22 +1,36 @@
1
- import { N as NestedKeyOf, I as InterpolationOptions, a as LocL } from './LocL-fN9Yari8.mjs';
1
+ import { N as NestedKeyOf, I as InterpolationOptions, P as PluralParamsFor, c as ParamsFor, d as PathValue, e as IsEmptyParams, T as TranslationObjectFor, f as PluralKeys, a as LocL } from './LocL-v4yNIiKt.mjs';
2
2
  import { DefaultResources, DefaultFallback } from './react.mjs';
3
3
  import 'react';
4
4
 
5
- /**
6
- * Key type that supports both colon syntax (`auth:login`) and dotted syntax (`auth.login`).
7
- */
8
- type I18nextKey<T> = T extends object ? {
9
- [K in keyof T & string]: T[K] extends object ? `${K}:${NestedKeyOf<T[K]>}` | `${K}.${NestedKeyOf<T[K]>}` | `${K}` : `${K}`;
10
- }[keyof T & string] | NestedKeyOf<T> : string;
5
+ type NormalizeKey<K extends string> = K extends `${infer NS}:${infer Rest}` ? `${NS}.${Rest}` : K;
6
+ type ColonKeys<K extends string> = K extends `${infer NS}.${infer Rest}` ? `${NS}:${Rest}` | `${NS}.${Rest}` : K;
7
+ type BaseKeys<Tr, PK extends string> = ((Tr extends object ? NestedKeyOf<Tr> : never) | PK) & string;
8
+ type I18nextKey<Tr, PK extends string> = ColonKeys<BaseKeys<Tr, PK>>;
11
9
  interface I18nextOptions extends InterpolationOptions {
12
10
  ns?: string;
13
11
  defaultValue?: string;
14
12
  count?: number;
15
- [key: string]: any;
16
13
  }
17
- interface I18nextCompat<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>> {
18
- t<K extends (I18nextKey<T[Fallback]> | (string & {}))>(key: K, options?: I18nextOptions): string;
19
- t<K extends (I18nextKey<T[Fallback]> | (string & {}))>(key: K, defaultValue?: string, options?: I18nextOptions): string;
14
+ type I18nextParamsFor<K extends string, Tr, PK extends string> = NormalizeKey<K> extends PK ? PluralParamsFor<NormalizeKey<K>, Tr> : ParamsFor<PathValue<Tr, NormalizeKey<K>>>;
15
+ type I18nextHasParams<K extends string, Tr, PK extends string> = NormalizeKey<K> extends PK ? true : IsEmptyParams<ParamsFor<PathValue<Tr, NormalizeKey<K>>>> extends true ? false : true;
16
+ type I18nextOptionsFor<K extends string, Tr, PK extends string> = I18nextParamsFor<K, Tr, PK> & I18nextOptions;
17
+ interface I18nextTranslation<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, Tr = TranslationObjectFor<undefined, T, Fallback>, PK extends string = PluralKeys<undefined, T, Fallback>> {
18
+ <K extends I18nextKey<Tr, PK>>(key: K, ...args: I18nextHasParams<K, Tr, PK> extends true ? [options: I18nextOptionsFor<K, Tr, PK>] : [options?: I18nextOptions]): string;
19
+ <K extends I18nextKey<Tr, PK>>(key: K, defaultValue: string, ...args: I18nextHasParams<K, Tr, PK> extends true ? [options: I18nextOptionsFor<K, Tr, PK>] : [options?: I18nextOptions]): string;
20
+ (key: string, defaultValue: string, options?: I18nextOptions): string;
21
+ (key: string, options: {
22
+ defaultValue: string;
23
+ } & I18nextOptions): string;
24
+ (key: string, options: {
25
+ ns: string;
26
+ } & I18nextOptions): string;
27
+ }
28
+ interface I18nextCompat<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, Tr = TranslationObjectFor<undefined, T, Fallback>, PK extends string = PluralKeys<undefined, T, Fallback>> {
29
+ t: I18nextTranslation<T, Fallback>;
30
+ exists<K extends I18nextKey<Tr, PK>>(key: K, options?: {
31
+ ns?: string;
32
+ [key: string]: any;
33
+ }): boolean;
20
34
  exists(key: string, options?: {
21
35
  ns?: string;
22
36
  [key: string]: any;
@@ -34,11 +48,7 @@ interface I18nextCompat<T extends Record<string, any> = DefaultResources, Fallba
34
48
  loadNamespaces(ns: string | string[], callback?: () => void): Promise<void>;
35
49
  translator: LocL<T, Fallback, any, any, any>;
36
50
  }
37
- /**
38
- * Wraps a LocL instance in an i18next-compatible interface.
39
- * Supports both colon (`common:login`) and dotted namespaces with full IDE autocomplete.
40
- */
41
51
  declare function toI18next<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>>(translator: LocL<T, Fallback, any, any, any>): I18nextCompat<T, Fallback>;
42
52
  declare const createI18nextCompat: typeof toI18next;
43
53
 
44
- export { type I18nextCompat, type I18nextKey, type I18nextOptions, createI18nextCompat, toI18next };
54
+ export { type BaseKeys, type ColonKeys, type I18nextCompat, type I18nextHasParams, type I18nextKey, type I18nextOptions, type I18nextOptionsFor, type I18nextParamsFor, type I18nextTranslation, type NormalizeKey, createI18nextCompat, toI18next };
package/dist/compat.d.ts CHANGED
@@ -1,22 +1,36 @@
1
- import { N as NestedKeyOf, I as InterpolationOptions, a as LocL } from './LocL-fN9Yari8.js';
1
+ import { N as NestedKeyOf, I as InterpolationOptions, P as PluralParamsFor, c as ParamsFor, d as PathValue, e as IsEmptyParams, T as TranslationObjectFor, f as PluralKeys, a as LocL } from './LocL-v4yNIiKt.js';
2
2
  import { DefaultResources, DefaultFallback } from './react.js';
3
3
  import 'react';
4
4
 
5
- /**
6
- * Key type that supports both colon syntax (`auth:login`) and dotted syntax (`auth.login`).
7
- */
8
- type I18nextKey<T> = T extends object ? {
9
- [K in keyof T & string]: T[K] extends object ? `${K}:${NestedKeyOf<T[K]>}` | `${K}.${NestedKeyOf<T[K]>}` | `${K}` : `${K}`;
10
- }[keyof T & string] | NestedKeyOf<T> : string;
5
+ type NormalizeKey<K extends string> = K extends `${infer NS}:${infer Rest}` ? `${NS}.${Rest}` : K;
6
+ type ColonKeys<K extends string> = K extends `${infer NS}.${infer Rest}` ? `${NS}:${Rest}` | `${NS}.${Rest}` : K;
7
+ type BaseKeys<Tr, PK extends string> = ((Tr extends object ? NestedKeyOf<Tr> : never) | PK) & string;
8
+ type I18nextKey<Tr, PK extends string> = ColonKeys<BaseKeys<Tr, PK>>;
11
9
  interface I18nextOptions extends InterpolationOptions {
12
10
  ns?: string;
13
11
  defaultValue?: string;
14
12
  count?: number;
15
- [key: string]: any;
16
13
  }
17
- interface I18nextCompat<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>> {
18
- t<K extends (I18nextKey<T[Fallback]> | (string & {}))>(key: K, options?: I18nextOptions): string;
19
- t<K extends (I18nextKey<T[Fallback]> | (string & {}))>(key: K, defaultValue?: string, options?: I18nextOptions): string;
14
+ type I18nextParamsFor<K extends string, Tr, PK extends string> = NormalizeKey<K> extends PK ? PluralParamsFor<NormalizeKey<K>, Tr> : ParamsFor<PathValue<Tr, NormalizeKey<K>>>;
15
+ type I18nextHasParams<K extends string, Tr, PK extends string> = NormalizeKey<K> extends PK ? true : IsEmptyParams<ParamsFor<PathValue<Tr, NormalizeKey<K>>>> extends true ? false : true;
16
+ type I18nextOptionsFor<K extends string, Tr, PK extends string> = I18nextParamsFor<K, Tr, PK> & I18nextOptions;
17
+ interface I18nextTranslation<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, Tr = TranslationObjectFor<undefined, T, Fallback>, PK extends string = PluralKeys<undefined, T, Fallback>> {
18
+ <K extends I18nextKey<Tr, PK>>(key: K, ...args: I18nextHasParams<K, Tr, PK> extends true ? [options: I18nextOptionsFor<K, Tr, PK>] : [options?: I18nextOptions]): string;
19
+ <K extends I18nextKey<Tr, PK>>(key: K, defaultValue: string, ...args: I18nextHasParams<K, Tr, PK> extends true ? [options: I18nextOptionsFor<K, Tr, PK>] : [options?: I18nextOptions]): string;
20
+ (key: string, defaultValue: string, options?: I18nextOptions): string;
21
+ (key: string, options: {
22
+ defaultValue: string;
23
+ } & I18nextOptions): string;
24
+ (key: string, options: {
25
+ ns: string;
26
+ } & I18nextOptions): string;
27
+ }
28
+ interface I18nextCompat<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, Tr = TranslationObjectFor<undefined, T, Fallback>, PK extends string = PluralKeys<undefined, T, Fallback>> {
29
+ t: I18nextTranslation<T, Fallback>;
30
+ exists<K extends I18nextKey<Tr, PK>>(key: K, options?: {
31
+ ns?: string;
32
+ [key: string]: any;
33
+ }): boolean;
20
34
  exists(key: string, options?: {
21
35
  ns?: string;
22
36
  [key: string]: any;
@@ -34,11 +48,7 @@ interface I18nextCompat<T extends Record<string, any> = DefaultResources, Fallba
34
48
  loadNamespaces(ns: string | string[], callback?: () => void): Promise<void>;
35
49
  translator: LocL<T, Fallback, any, any, any>;
36
50
  }
37
- /**
38
- * Wraps a LocL instance in an i18next-compatible interface.
39
- * Supports both colon (`common:login`) and dotted namespaces with full IDE autocomplete.
40
- */
41
51
  declare function toI18next<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>>(translator: LocL<T, Fallback, any, any, any>): I18nextCompat<T, Fallback>;
42
52
  declare const createI18nextCompat: typeof toI18next;
43
53
 
44
- export { type I18nextCompat, type I18nextKey, type I18nextOptions, createI18nextCompat, toI18next };
54
+ export { type BaseKeys, type ColonKeys, type I18nextCompat, type I18nextHasParams, type I18nextKey, type I18nextOptions, type I18nextOptionsFor, type I18nextParamsFor, type I18nextTranslation, type NormalizeKey, createI18nextCompat, toI18next };
package/dist/compat.js CHANGED
@@ -38,41 +38,42 @@ function toI18next(translator) {
38
38
  translator.subscribe((newLang, prevLang) => {
39
39
  emit("languageChanged", newLang, prevLang);
40
40
  });
41
+ const tFunction = (key, arg1, arg2) => {
42
+ let defaultValue;
43
+ let options = {};
44
+ if (typeof arg1 === "string") {
45
+ defaultValue = arg1;
46
+ options = typeof arg2 === "object" && arg2 !== null ? arg2 : {};
47
+ } else if (typeof arg1 === "object" && arg1 !== null) {
48
+ options = arg1;
49
+ defaultValue = options.defaultValue;
50
+ }
51
+ let lookupKey = key;
52
+ let ns = options.ns;
53
+ if (key.includes(":")) {
54
+ const colonIdx = key.indexOf(":");
55
+ ns = key.slice(0, colonIdx);
56
+ lookupKey = key.slice(colonIdx + 1);
57
+ }
58
+ let activeTranslator = translator;
59
+ if (ns) {
60
+ activeTranslator = translator.withConfig({ scope: ns });
61
+ }
62
+ let result;
63
+ if (typeof options.count === "number") {
64
+ result = activeTranslator.plural(lookupKey, options);
65
+ } else {
66
+ result = activeTranslator.t(lookupKey, options);
67
+ }
68
+ const keyExists = activeTranslator.get(lookupKey) !== void 0 || result !== lookupKey;
69
+ if (!keyExists && defaultValue !== void 0) {
70
+ return defaultValue;
71
+ }
72
+ return result ?? defaultValue ?? key;
73
+ };
41
74
  const compat = {
42
75
  translator,
43
- t(key, arg1, arg2) {
44
- let defaultValue;
45
- let options = {};
46
- if (typeof arg1 === "string") {
47
- defaultValue = arg1;
48
- options = typeof arg2 === "object" && arg2 !== null ? arg2 : {};
49
- } else if (typeof arg1 === "object" && arg1 !== null) {
50
- options = arg1;
51
- defaultValue = options.defaultValue;
52
- }
53
- let lookupKey = key;
54
- let ns = options.ns;
55
- if (key.includes(":")) {
56
- const colonIdx = key.indexOf(":");
57
- ns = key.slice(0, colonIdx);
58
- lookupKey = key.slice(colonIdx + 1);
59
- }
60
- let activeTranslator = translator;
61
- if (ns) {
62
- activeTranslator = translator.withConfig({ scope: ns });
63
- }
64
- let result;
65
- if (typeof options.count === "number") {
66
- result = activeTranslator.plural(lookupKey, options);
67
- } else {
68
- result = activeTranslator.t(lookupKey, options);
69
- }
70
- const keyExists = activeTranslator.get(lookupKey) !== void 0 || result !== lookupKey;
71
- if (!keyExists && defaultValue !== void 0) {
72
- return defaultValue;
73
- }
74
- return result ?? defaultValue ?? key;
75
- },
76
+ t: tFunction,
76
77
  exists(key, options) {
77
78
  let lookupKey = key;
78
79
  let ns = options?.ns;
@@ -112,7 +113,7 @@ function toI18next(translator) {
112
113
  addResources(lng, ns, resources) {
113
114
  translator.addResources(lng, { [ns]: resources });
114
115
  },
115
- addResourceBundle(lng, ns, resources, deep, overwrite) {
116
+ addResourceBundle(lng, ns, resources) {
116
117
  translator.addResources(lng, { [ns]: resources });
117
118
  },
118
119
  hasResourceBundle(lng, ns) {
package/dist/compat.mjs CHANGED
@@ -13,41 +13,42 @@ function toI18next(translator) {
13
13
  translator.subscribe((newLang, prevLang) => {
14
14
  emit("languageChanged", newLang, prevLang);
15
15
  });
16
+ const tFunction = (key, arg1, arg2) => {
17
+ let defaultValue;
18
+ let options = {};
19
+ if (typeof arg1 === "string") {
20
+ defaultValue = arg1;
21
+ options = typeof arg2 === "object" && arg2 !== null ? arg2 : {};
22
+ } else if (typeof arg1 === "object" && arg1 !== null) {
23
+ options = arg1;
24
+ defaultValue = options.defaultValue;
25
+ }
26
+ let lookupKey = key;
27
+ let ns = options.ns;
28
+ if (key.includes(":")) {
29
+ const colonIdx = key.indexOf(":");
30
+ ns = key.slice(0, colonIdx);
31
+ lookupKey = key.slice(colonIdx + 1);
32
+ }
33
+ let activeTranslator = translator;
34
+ if (ns) {
35
+ activeTranslator = translator.withConfig({ scope: ns });
36
+ }
37
+ let result;
38
+ if (typeof options.count === "number") {
39
+ result = activeTranslator.plural(lookupKey, options);
40
+ } else {
41
+ result = activeTranslator.t(lookupKey, options);
42
+ }
43
+ const keyExists = activeTranslator.get(lookupKey) !== void 0 || result !== lookupKey;
44
+ if (!keyExists && defaultValue !== void 0) {
45
+ return defaultValue;
46
+ }
47
+ return result ?? defaultValue ?? key;
48
+ };
16
49
  const compat = {
17
50
  translator,
18
- t(key, arg1, arg2) {
19
- let defaultValue;
20
- let options = {};
21
- if (typeof arg1 === "string") {
22
- defaultValue = arg1;
23
- options = typeof arg2 === "object" && arg2 !== null ? arg2 : {};
24
- } else if (typeof arg1 === "object" && arg1 !== null) {
25
- options = arg1;
26
- defaultValue = options.defaultValue;
27
- }
28
- let lookupKey = key;
29
- let ns = options.ns;
30
- if (key.includes(":")) {
31
- const colonIdx = key.indexOf(":");
32
- ns = key.slice(0, colonIdx);
33
- lookupKey = key.slice(colonIdx + 1);
34
- }
35
- let activeTranslator = translator;
36
- if (ns) {
37
- activeTranslator = translator.withConfig({ scope: ns });
38
- }
39
- let result;
40
- if (typeof options.count === "number") {
41
- result = activeTranslator.plural(lookupKey, options);
42
- } else {
43
- result = activeTranslator.t(lookupKey, options);
44
- }
45
- const keyExists = activeTranslator.get(lookupKey) !== void 0 || result !== lookupKey;
46
- if (!keyExists && defaultValue !== void 0) {
47
- return defaultValue;
48
- }
49
- return result ?? defaultValue ?? key;
50
- },
51
+ t: tFunction,
51
52
  exists(key, options) {
52
53
  let lookupKey = key;
53
54
  let ns = options?.ns;
@@ -87,7 +88,7 @@ function toI18next(translator) {
87
88
  addResources(lng, ns, resources) {
88
89
  translator.addResources(lng, { [ns]: resources });
89
90
  },
90
- addResourceBundle(lng, ns, resources, deep, overwrite) {
91
+ addResourceBundle(lng, ns, resources) {
91
92
  translator.addResources(lng, { [ns]: resources });
92
93
  },
93
94
  hasResourceBundle(lng, ns) {
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as ScopeType, F as Formatter, L as LocLConfig, a as LocL } from './LocL-fN9Yari8.mjs';
2
- export { b as LangWithPlurals } from './LocL-fN9Yari8.mjs';
1
+ import { S as ScopeType, F as Formatter, L as LocLConfig, a as LocL } from './LocL-v4yNIiKt.mjs';
2
+ export { b as LangWithPlurals } from './LocL-v4yNIiKt.mjs';
3
3
 
4
4
  /**
5
5
  * Initializes a new LocL instance.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as ScopeType, F as Formatter, L as LocLConfig, a as LocL } from './LocL-fN9Yari8.js';
2
- export { b as LangWithPlurals } from './LocL-fN9Yari8.js';
1
+ import { S as ScopeType, F as Formatter, L as LocLConfig, a as LocL } from './LocL-v4yNIiKt.js';
2
+ export { b as LangWithPlurals } from './LocL-v4yNIiKt.js';
3
3
 
4
4
  /**
5
5
  * Initializes a new LocL instance.
package/dist/index.js CHANGED
@@ -305,25 +305,26 @@ var LocL = class _LocL {
305
305
  * @param value - The translation value.
306
306
  */
307
307
  addResource(lang, key, value) {
308
- if (!this.resources[lang]) {
309
- this.resources[lang] = {};
308
+ if (this.isUnsafeObjectKey(lang)) {
309
+ return;
310
310
  }
311
311
  const keys = key.split(".");
312
+ if (keys.some((k) => this.isUnsafeObjectKey(k))) {
313
+ return;
314
+ }
315
+ if (!this.resources[lang]) {
316
+ this.resources[lang] = /* @__PURE__ */ Object.create(null);
317
+ }
312
318
  let current = this.resources[lang];
313
319
  for (let i = 0; i < keys.length - 1; i++) {
314
320
  const k = keys[i];
315
- if (k === "__proto__" || k === "constructor" || k === "prototype") {
316
- return;
317
- }
318
321
  if (!current[k] || typeof current[k] !== "object") {
319
- current[k] = {};
322
+ current[k] = /* @__PURE__ */ Object.create(null);
320
323
  }
321
324
  current = current[k];
322
325
  }
323
326
  const lastKey = keys[keys.length - 1];
324
- if (lastKey !== "__proto__" && lastKey !== "constructor" && lastKey !== "prototype") {
325
- current[lastKey] = value;
326
- }
327
+ current[lastKey] = value;
327
328
  this.invalidateCacheForLang(lang);
328
329
  }
329
330
  /**
@@ -332,17 +333,20 @@ var LocL = class _LocL {
332
333
  * @param bundle - Object of translations to merge.
333
334
  */
334
335
  addResources(lang, bundle) {
336
+ if (this.isUnsafeObjectKey(lang)) {
337
+ return;
338
+ }
335
339
  if (!this.resources[lang]) {
336
- this.resources[lang] = {};
340
+ this.resources[lang] = /* @__PURE__ */ Object.create(null);
337
341
  }
338
342
  const deepMerge = (target, source) => {
339
343
  for (const k of Object.keys(source)) {
340
- if (k === "__proto__" || k === "constructor" || k === "prototype") {
344
+ if (this.isUnsafeObjectKey(k)) {
341
345
  continue;
342
346
  }
343
347
  if (source[k] && typeof source[k] === "object" && !Array.isArray(source[k])) {
344
348
  if (!target[k] || typeof target[k] !== "object") {
345
- target[k] = {};
349
+ target[k] = /* @__PURE__ */ Object.create(null);
346
350
  }
347
351
  deepMerge(target[k], source[k]);
348
352
  } else {
@@ -563,7 +567,7 @@ var LocL = class _LocL {
563
567
  return void 0;
564
568
  }
565
569
  const keyTest = keysCopy.join(".");
566
- if (current.hasOwnProperty(keyTest)) {
570
+ if (Object.prototype.hasOwnProperty.call(current, keyTest)) {
567
571
  return current[keyTest];
568
572
  }
569
573
  if (!(key in current)) {
@@ -737,6 +741,9 @@ var LocL = class _LocL {
737
741
  this.proxyCache.set(obj, proxy);
738
742
  return proxy;
739
743
  }
744
+ isUnsafeObjectKey(key) {
745
+ return key === "__proto__" || key === "constructor" || key === "prototype";
746
+ }
740
747
  };
741
748
 
742
749
  // src/index.ts
package/dist/index.mjs CHANGED
@@ -277,25 +277,26 @@ var LocL = class _LocL {
277
277
  * @param value - The translation value.
278
278
  */
279
279
  addResource(lang, key, value) {
280
- if (!this.resources[lang]) {
281
- this.resources[lang] = {};
280
+ if (this.isUnsafeObjectKey(lang)) {
281
+ return;
282
282
  }
283
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
+ }
284
290
  let current = this.resources[lang];
285
291
  for (let i = 0; i < keys.length - 1; i++) {
286
292
  const k = keys[i];
287
- if (k === "__proto__" || k === "constructor" || k === "prototype") {
288
- return;
289
- }
290
293
  if (!current[k] || typeof current[k] !== "object") {
291
- current[k] = {};
294
+ current[k] = /* @__PURE__ */ Object.create(null);
292
295
  }
293
296
  current = current[k];
294
297
  }
295
298
  const lastKey = keys[keys.length - 1];
296
- if (lastKey !== "__proto__" && lastKey !== "constructor" && lastKey !== "prototype") {
297
- current[lastKey] = value;
298
- }
299
+ current[lastKey] = value;
299
300
  this.invalidateCacheForLang(lang);
300
301
  }
301
302
  /**
@@ -304,17 +305,20 @@ var LocL = class _LocL {
304
305
  * @param bundle - Object of translations to merge.
305
306
  */
306
307
  addResources(lang, bundle) {
308
+ if (this.isUnsafeObjectKey(lang)) {
309
+ return;
310
+ }
307
311
  if (!this.resources[lang]) {
308
- this.resources[lang] = {};
312
+ this.resources[lang] = /* @__PURE__ */ Object.create(null);
309
313
  }
310
314
  const deepMerge = (target, source) => {
311
315
  for (const k of Object.keys(source)) {
312
- if (k === "__proto__" || k === "constructor" || k === "prototype") {
316
+ if (this.isUnsafeObjectKey(k)) {
313
317
  continue;
314
318
  }
315
319
  if (source[k] && typeof source[k] === "object" && !Array.isArray(source[k])) {
316
320
  if (!target[k] || typeof target[k] !== "object") {
317
- target[k] = {};
321
+ target[k] = /* @__PURE__ */ Object.create(null);
318
322
  }
319
323
  deepMerge(target[k], source[k]);
320
324
  } else {
@@ -535,7 +539,7 @@ var LocL = class _LocL {
535
539
  return void 0;
536
540
  }
537
541
  const keyTest = keysCopy.join(".");
538
- if (current.hasOwnProperty(keyTest)) {
542
+ if (Object.prototype.hasOwnProperty.call(current, keyTest)) {
539
543
  return current[keyTest];
540
544
  }
541
545
  if (!(key in current)) {
@@ -709,6 +713,9 @@ var LocL = class _LocL {
709
713
  this.proxyCache.set(obj, proxy);
710
714
  return proxy;
711
715
  }
716
+ isUnsafeObjectKey(key) {
717
+ return key === "__proto__" || key === "constructor" || key === "prototype";
718
+ }
712
719
  };
713
720
 
714
721
  // src/index.ts
package/dist/react.d.mts CHANGED
@@ -1,28 +1,25 @@
1
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';
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
3
 
4
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) */
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 */
7
16
  i18nKey: K | (string & {});
8
- /** Values to interpolate into variable placeholders ({name}, {{name}}) */
9
17
  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
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 */
19
+ translator?: LocL<T, any, any, any, any>;
22
20
  scope?: S;
23
- /** Fallback string if key is not found */
24
- fallback?: string;
25
- }
21
+ fallback: string;
22
+ };
26
23
  /**
27
24
  * Renders translated text with rich React component interpolation without `dangerouslySetInnerHTML`.
28
25
  * Subscribes reactively to language updates and supports strict type checking and autocompletion.
@@ -33,12 +30,12 @@ interface TransProps<T extends Record<string, any> = DefaultResources, Fallback
33
30
  * values={{ name: "Alice" }}
34
31
  * components={{
35
32
  * bold: <strong />,
36
- * link: <a href="/terms" />
33
+ * link: (content) => <a href="/terms">{content}</a>
37
34
  * }}
38
35
  * />
39
36
  * ```
40
37
  */
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;
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;
42
39
 
43
40
  /**
44
41
  * Global type registry for LocL React.
@@ -83,11 +80,10 @@ interface UseLocLResult<T extends Record<string, any> = DefaultResources, Fallba
83
80
  }
84
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> {
85
82
  }
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>;
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>;
89
86
  interface BoundLocLProviderProps<T extends Record<string, any>, Fallback extends keyof T & string> {
90
- /** Optional override translator; defaults to the instance passed to `createLocLReact` */
91
87
  translator?: LocL<any, any, any, any, any>;
92
88
  children: ReactNode;
93
89
  }
@@ -96,11 +92,13 @@ interface LocLReactSuite<T extends Record<string, any>, Fallback extends keyof T
96
92
  LocLProvider: (props: BoundLocLProviderProps<T, Fallback>) => ReactElement;
97
93
  useLocL: <S extends ScopeType<T, Fallback> = undefined>(customTranslator?: LocL<any, any, any, any, any>) => UseLocLResult<T, Fallback, S>;
98
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>;
99
98
  (scope?: undefined, customTranslator?: LocL<any, any, any, any, any>): UseTranslationResult<T, Fallback, undefined>;
100
99
  <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
100
  };
103
- Trans: <S extends ScopeType<T, Fallback> = undefined, K extends string = TransKey<T, Fallback, S>>(props: TransProps<T, Fallback, S, K>) => ReactElement;
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;
104
102
  translator: LocL<T, Fallback, any, any, any>;
105
103
  }
106
104
  /**
@@ -117,4 +115,4 @@ interface LocLReactSuite<T extends Record<string, any>, Fallback extends keyof T
117
115
  */
118
116
  declare function createLocLReact<T extends Record<string, any>, Fallback extends keyof T & string>(defaultTranslator: LocL<T, Fallback, any, any, any>): LocLReactSuite<T, Fallback>;
119
117
 
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 };
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 };
package/dist/react.d.ts CHANGED
@@ -1,28 +1,25 @@
1
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.js';
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.js';
3
3
 
4
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) */
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 */
7
16
  i18nKey: K | (string & {});
8
- /** Values to interpolate into variable placeholders ({name}, {{name}}) */
9
17
  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
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 */
19
+ translator?: LocL<T, any, any, any, any>;
22
20
  scope?: S;
23
- /** Fallback string if key is not found */
24
- fallback?: string;
25
- }
21
+ fallback: string;
22
+ };
26
23
  /**
27
24
  * Renders translated text with rich React component interpolation without `dangerouslySetInnerHTML`.
28
25
  * Subscribes reactively to language updates and supports strict type checking and autocompletion.
@@ -33,12 +30,12 @@ interface TransProps<T extends Record<string, any> = DefaultResources, Fallback
33
30
  * values={{ name: "Alice" }}
34
31
  * components={{
35
32
  * bold: <strong />,
36
- * link: <a href="/terms" />
33
+ * link: (content) => <a href="/terms">{content}</a>
37
34
  * }}
38
35
  * />
39
36
  * ```
40
37
  */
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;
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;
42
39
 
43
40
  /**
44
41
  * Global type registry for LocL React.
@@ -83,11 +80,10 @@ interface UseLocLResult<T extends Record<string, any> = DefaultResources, Fallba
83
80
  }
84
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> {
85
82
  }
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>;
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>;
89
86
  interface BoundLocLProviderProps<T extends Record<string, any>, Fallback extends keyof T & string> {
90
- /** Optional override translator; defaults to the instance passed to `createLocLReact` */
91
87
  translator?: LocL<any, any, any, any, any>;
92
88
  children: ReactNode;
93
89
  }
@@ -96,11 +92,13 @@ interface LocLReactSuite<T extends Record<string, any>, Fallback extends keyof T
96
92
  LocLProvider: (props: BoundLocLProviderProps<T, Fallback>) => ReactElement;
97
93
  useLocL: <S extends ScopeType<T, Fallback> = undefined>(customTranslator?: LocL<any, any, any, any, any>) => UseLocLResult<T, Fallback, S>;
98
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>;
99
98
  (scope?: undefined, customTranslator?: LocL<any, any, any, any, any>): UseTranslationResult<T, Fallback, undefined>;
100
99
  <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
100
  };
103
- Trans: <S extends ScopeType<T, Fallback> = undefined, K extends string = TransKey<T, Fallback, S>>(props: TransProps<T, Fallback, S, K>) => ReactElement;
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;
104
102
  translator: LocL<T, Fallback, any, any, any>;
105
103
  }
106
104
  /**
@@ -117,4 +115,4 @@ interface LocLReactSuite<T extends Record<string, any>, Fallback extends keyof T
117
115
  */
118
116
  declare function createLocLReact<T extends Record<string, any>, Fallback extends keyof T & string>(defaultTranslator: LocL<T, Fallback, any, any, any>): LocLReactSuite<T, Fallback>;
119
117
 
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 };
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 };
package/dist/react.js CHANGED
@@ -43,14 +43,15 @@ var import_react2 = require("react");
43
43
  // src/react/Trans.tsx
44
44
  var import_react = __toESM(require("react"));
45
45
  var import_jsx_runtime = require("react/jsx-runtime");
46
- function Trans({
47
- i18nKey,
48
- values,
49
- components = {},
50
- translator: customTranslator,
51
- scope,
52
- fallback
53
- }) {
46
+ function Trans(props) {
47
+ const {
48
+ i18nKey,
49
+ values,
50
+ components = {},
51
+ translator: customTranslator,
52
+ scope,
53
+ fallback
54
+ } = props;
54
55
  const contextTranslator = (0, import_react.useContext)(LocLContext);
55
56
  const activeTranslator = customTranslator ?? contextTranslator;
56
57
  const getSnapshot = () => activeTranslator ? activeTranslator.getLanguage() : "";
package/dist/react.mjs CHANGED
@@ -4,14 +4,15 @@ import { createContext, useContext as useContext2, useSyncExternalStore as useSy
4
4
  // src/react/Trans.tsx
5
5
  import React, { isValidElement, cloneElement, useContext, useMemo, useSyncExternalStore } from "react";
6
6
  import { Fragment, jsx } from "react/jsx-runtime";
7
- function Trans({
8
- i18nKey,
9
- values,
10
- components = {},
11
- translator: customTranslator,
12
- scope,
13
- fallback
14
- }) {
7
+ function Trans(props) {
8
+ const {
9
+ i18nKey,
10
+ values,
11
+ components = {},
12
+ translator: customTranslator,
13
+ scope,
14
+ fallback
15
+ } = props;
15
16
  const contextTranslator = useContext(LocLContext);
16
17
  const activeTranslator = customTranslator ?? contextTranslator;
17
18
  const getSnapshot = () => activeTranslator ? activeTranslator.getLanguage() : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "locl-js",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Modern TypeScript internationalization (i18n) library with type-safety, performance, and backend usability",
5
5
  "license": "MIT",
6
6
  "author": "Sztrida Sámuel <sztsam@gmail.com>",