i18next 26.1.0 → 26.3.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.
@@ -0,0 +1,29 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(gh pr *)",
5
+ "Read(//Users/adrai/Projects/i18next/react-i18next/**)",
6
+ "Bash(git add *)",
7
+ "Bash(git commit *)",
8
+ "Read(//Users/adrai/Projects/i18next/**)",
9
+ "Bash(gh issue *)",
10
+ "Read(//tmp/**)",
11
+ "WebFetch(domain:github.com)",
12
+ "Bash(grep -n '$PreservedValue\\\\b' /Users/adrai/Projects/i18next/i18next/typescript/helpers.d.ts)",
13
+ "Bash(npm run *)",
14
+ "Bash(cat > *)",
15
+ "Bash(python3 -c \"import json,sys; print\\(json.load\\(sys.stdin\\)['version']\\)\")",
16
+ "Bash(npm test *)",
17
+ "Bash(git pull *)",
18
+ "Bash(git push *)",
19
+ "Bash(gh api *)",
20
+ "Bash(curl -s \"https://raw.githubusercontent.com/GabenGar/todos/d6d77cef716fcdc5a3991f84605176f85a69cdc8/apps/frontend/src/lib/internationalization/augs.d.ts\")"
21
+ ],
22
+ "additionalDirectories": [
23
+ "/Users/adrai/Projects/i18next/react-i18next",
24
+ "/tmp",
25
+ "/Users/adrai/Projects/i18next/i18next-gitbook/overview",
26
+ "/Users/adrai/Projects/i18next/i18next-icu"
27
+ ]
28
+ }
29
+ }
package/README.md CHANGED
@@ -23,7 +23,7 @@ i18next provides:
23
23
  - Extensibility: eg. [sprintf](https://www.i18next.com/overview/plugins-and-utils#post-processors)
24
24
  - ...
25
25
 
26
- > **Pro Tip:** Looking for a way to manage your translations? Locize is the official service by i18next's creators and now offers a **[Free plan](https://www.locize.com/pricing?utm_source=i18next_readme&utm_medium=github&utm_campaign=readme)** for small projects.
26
+ > **Pro Tip:** Looking for a way to manage your translations? [Locize](https://www.locize.com?utm_source=i18next_readme&utm_medium=github&utm_campaign=readme) is the official service by i18next's creators — drop in [`i18next-locize-backend`](https://github.com/locize/i18next-locize-backend) for CDN delivery, AI translation, and no redeploys for copy changes. **[Free plan](https://www.locize.com/pricing?utm_source=i18next_readme&utm_medium=github&utm_campaign=readme)** available for small projects.
27
27
 
28
28
  For more information visit the website:
29
29
 
@@ -1 +1 @@
1
- {"type":"module","version":"26.1.0"}
1
+ {"type":"module","version":"26.3.0"}
package/index.d.ts CHANGED
@@ -584,6 +584,7 @@ export type {
584
584
  InitOptions,
585
585
  TypeOptions,
586
586
  CustomTypeOptions,
587
+ ResourceNamespaceMap,
587
588
  CustomPluginOptions,
588
589
  PluginOptions,
589
590
  FormatFunction,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next",
3
- "version": "26.1.0",
3
+ "version": "26.3.0",
4
4
  "description": "i18next internationalization framework",
5
5
  "main": "./dist/cjs/i18next.js",
6
6
  "module": "./dist/esm/i18next.js",
@@ -27,140 +27,246 @@ import type { $MergeBy, $PreservedValue, $Dictionary } from './helpers.js';
27
27
  */
28
28
  export interface CustomTypeOptions {}
29
29
 
30
+ /**
31
+ * Per-package namespace types for monorepos. Augment this in each package's
32
+ * `i18next.d.ts` instead of `CustomTypeOptions.resources` when several packages
33
+ * need their own namespaces — otherwise TypeScript reports TS2717 on merge.
34
+ *
35
+ * Works alongside the legacy `CustomTypeOptions.resources` field; both end up
36
+ * in `TypeOptions['resources']`.
37
+ *
38
+ * Scalar type options like `defaultNS`, `returnNull`, `enableSelector`, etc.,
39
+ * still belong on `CustomTypeOptions` — this interface is for namespace
40
+ * resource types only.
41
+ *
42
+ * @see https://github.com/i18next/i18next/issues/2409
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * // packages/ui/i18next.d.ts
47
+ * declare module 'i18next' {
48
+ * interface ResourceNamespaceMap {
49
+ * '@repo/ui': typeof uiTranslations;
50
+ * }
51
+ * }
52
+ * ```
53
+ */
54
+ export interface ResourceNamespaceMap {}
55
+
30
56
  /**
31
57
  * This interface can be augmented by users to add types to `i18next` default PluginOptions.
32
58
  */
33
59
  export interface CustomPluginOptions {}
34
60
 
35
- export type TypeOptions = $MergeBy<
36
- {
37
- /** @see {InitOptions.returnNull} */
38
- returnNull: false;
39
-
40
- /** @see {InitOptions.returnEmptyString} */
41
- returnEmptyString: true;
42
-
43
- /** @see {InitOptions.returnObjects} */
44
- returnObjects: false;
45
-
46
- /** @see {InitOptions.keySeparator} */
47
- keySeparator: '.';
48
-
49
- /** @see {InitOptions.nsSeparator} */
50
- nsSeparator: ':';
51
-
52
- /** @see {InitOptions.pluralSeparator} */
53
- pluralSeparator: '_';
61
+ type _LegacyResources = CustomTypeOptions extends { resources: infer R } ? R : object;
62
+
63
+ // Per-property merge of two object types. Unlike `L & R`, which TypeScript
64
+ // collapses to `never` whenever ANY property has incompatible literals (so a
65
+ // single same-key/different-literal conflict wipes out the whole namespace),
66
+ // this iterates keys and intersects them individually — the conflicting key
67
+ // becomes `never`, the rest survive.
68
+ type _PerPropMerge<L, R> = {
69
+ [K in keyof L | keyof R]: K extends keyof L
70
+ ? K extends keyof R
71
+ ? L[K] & R[K]
72
+ : L[K]
73
+ : K extends keyof R
74
+ ? R[K]
75
+ : never;
76
+ };
77
+
78
+ // Drop properties whose value resolved to `never`. Without this, the
79
+ // conflict key would leak into `keyof Resources[ns]`, then poison
80
+ // `KeysBuilder` recursion (it tries to walk a `never` value) and break
81
+ // `t()` overload resolution for the entire namespace.
82
+ // NOTE: must use the `Pick<T, NonNeverKeys>` form. A `[K in keyof T as ...]`
83
+ // remap does NOT eagerly evaluate `T[K]` for intersection types, so conflict
84
+ // keys would survive the filter.
85
+ type _NonNeverKeys<T> = {
86
+ [K in keyof T]: [T[K]] extends [never] ? never : K;
87
+ }[keyof T];
88
+ type _DropConflictKeys<T> = Pick<T, _NonNeverKeys<T>>;
89
+
90
+ // When the same namespace exists on both sides, deep-merge per property and
91
+ // strip same-key/different-literal conflicts. Otherwise pick from whichever
92
+ // side has it.
93
+ type _MergeNamespaces<L, R> = {
94
+ [K in keyof L | keyof R]: K extends keyof L
95
+ ? K extends keyof R
96
+ ? _DropConflictKeys<_PerPropMerge<L[K], R[K]>>
97
+ : L[K]
98
+ : K extends keyof R
99
+ ? R[K]
100
+ : never;
101
+ };
102
+
103
+ // NOTE: empty legacy + empty registry must stay `object` so unconfigured projects
104
+ // still get `$IsResourcesDefined === false`.
105
+ type _MergedResources = [keyof _LegacyResources] extends [never]
106
+ ? [keyof ResourceNamespaceMap] extends [never]
107
+ ? object
108
+ : ResourceNamespaceMap
109
+ : [keyof ResourceNamespaceMap] extends [never]
110
+ ? _LegacyResources
111
+ : _MergeNamespaces<_LegacyResources, ResourceNamespaceMap>;
54
112
 
55
- /** @see {InitOptions.contextSeparator} */
56
- contextSeparator: '_';
57
-
58
- /** @see {InitOptions.defaultNS} */
59
- defaultNS: 'translation';
60
-
61
- /** @see {InitOptions.fallbackNS} */
62
- fallbackNS: false;
63
-
64
- /** @see {InitOptions.compatibilityJSON} */
65
- compatibilityJSON: 'v4';
66
-
67
- /** @see {InitOptions.resources} */
68
- resources: object;
69
-
70
- /**
71
- * Flag that allows HTML elements to receive objects. This is only useful for React applications
72
- * where you pass objects to HTML elements so they can be replaced to their respective interpolation
73
- * values (mostly with Trans component)
74
- */
75
- allowObjectInHTMLChildren: false;
76
-
77
- /**
78
- * Flag that enables strict key checking even if a `defaultValue` has been provided.
79
- * This ensures all calls of `t` function don't accidentally use implicitly missing keys.
80
- */
81
- strictKeyChecks: false;
82
-
83
- /**
84
- * Prefix for interpolation
85
- */
86
- interpolationPrefix: '{{';
87
-
88
- /**
89
- * Suffix for interpolation
90
- */
91
- interpolationSuffix: '}}';
92
-
93
- /** @see {InterpolationOptions.unescapePrefix} */
94
- unescapePrefix: '-';
95
-
96
- /** @see {InterpolationOptions.unescapeSuffix} */
97
- unescapeSuffix: '';
98
-
99
- /**
100
- * Use a proxy-based selector to select a translation.
101
- *
102
- * Enables features like go-to definition, and better DX/faster autocompletion
103
- * for TypeScript developers.
104
- *
105
- * If you're working with an especially large set of translations and aren't
106
- * using context, you set `enableSelector` to `"optimize"` and i18next won't do
107
- * any type-level processing of your translations at all.
108
- *
109
- * With `enableSelector` set to `"optimize"`, i18next is capable of supporting
110
- * arbitrarily large/deep translation sets without causing any IDE slowdown
111
- * whatsoever.
112
- *
113
- * Set `enableSelector` to `"strict"` to require an explicit namespace as the
114
- * first selector path segment in every call. The selector proxy stops
115
- * exposing the primary namespace's keys flat on `$` — even
116
- * `useTranslation('only')` must use `$.only.foo`, never `$.foo`. At runtime,
117
- * a leading segment matching the scope's namespace list (primary included)
118
- * is always rewritten as a namespace prefix, fully decoupling selector
119
- * shape from resolution scope. Use this when you want a single mental model
120
- * for selector paths regardless of how many namespaces a hook was created
121
- * with, and to remove the silent miss that flat-primary paths can otherwise
122
- * cause in multi-ns hooks (see [#2429](https://github.com/i18next/i18next/issues/2429)).
123
- *
124
- * `"strict"` mode is incompatible with the `"optimize"` shortcut. If you
125
- * have keys whose names match sibling namespaces (the
126
- * [#2405](https://github.com/i18next/i18next/issues/2405) pattern), do not
127
- * enable strict mode the leading-segment rewrite would route those keys
128
- * into the wrong namespace.
129
- *
130
- * @default false
131
- */
132
- enableSelector: false;
133
-
134
- /**
135
- * Maps interpolation format specifiers to their expected value types.
136
- *
137
- * By default, i18next infers types from built-in formatter names:
138
- * - `number`, `currency` → `number`
139
- * - `datetime` → `Date`
140
- * - `relativetime` → `number`
141
- * - `list` → `readonly string[]`
142
- * - No format specifier `string | number` (since i18next stringifies values at runtime)
143
- *
144
- * Use this option to add mappings for custom formatters or to override
145
- * the built-in defaults.
146
- *
147
- * @default {} (empty built-in defaults apply)
148
- *
149
- * @example
150
- * ```ts
151
- * interface CustomTypeOptions {
152
- * interpolationFormatTypeMap: {
153
- * // custom formatter
154
- * uppercase: string;
155
- * // override built-in
156
- * currency: string;
157
- * };
158
- * }
159
- * ```
160
- */
161
- interpolationFormatTypeMap: {};
162
- },
163
- CustomTypeOptions
113
+ export type TypeOptions = $MergeBy<
114
+ $MergeBy<
115
+ {
116
+ /** @see {InitOptions.returnNull} */
117
+ returnNull: false;
118
+
119
+ /** @see {InitOptions.returnEmptyString} */
120
+ returnEmptyString: true;
121
+
122
+ /** @see {InitOptions.returnObjects} */
123
+ returnObjects: false;
124
+
125
+ /** @see {InitOptions.keySeparator} */
126
+ keySeparator: '.';
127
+
128
+ /** @see {InitOptions.nsSeparator} */
129
+ nsSeparator: ':';
130
+
131
+ /** @see {InitOptions.pluralSeparator} */
132
+ pluralSeparator: '_';
133
+
134
+ /** @see {InitOptions.contextSeparator} */
135
+ contextSeparator: '_';
136
+
137
+ /** @see {InitOptions.defaultNS} */
138
+ defaultNS: 'translation';
139
+
140
+ /** @see {InitOptions.fallbackNS} */
141
+ fallbackNS: false;
142
+
143
+ /** @see {InitOptions.compatibilityJSON} */
144
+ compatibilityJSON: 'v4';
145
+
146
+ /** @see {InitOptions.resources} */
147
+ resources: object;
148
+
149
+ /**
150
+ * Flag that allows HTML elements to receive objects. This is only useful for React applications
151
+ * where you pass objects to HTML elements so they can be replaced to their respective interpolation
152
+ * values (mostly with Trans component)
153
+ */
154
+ allowObjectInHTMLChildren: false;
155
+
156
+ /**
157
+ * Flag that enables strict key checking even if a `defaultValue` has been provided.
158
+ * This ensures all calls of `t` function don't accidentally use implicitly missing keys.
159
+ */
160
+ strictKeyChecks: false;
161
+
162
+ /**
163
+ * Prefix for interpolation
164
+ */
165
+ interpolationPrefix: '{{';
166
+
167
+ /**
168
+ * Suffix for interpolation
169
+ */
170
+ interpolationSuffix: '}}';
171
+
172
+ /** @see {InterpolationOptions.unescapePrefix} */
173
+ unescapePrefix: '-';
174
+
175
+ /** @see {InterpolationOptions.unescapeSuffix} */
176
+ unescapeSuffix: '';
177
+
178
+ /**
179
+ * Whether to extract interpolation variables from translation strings at
180
+ * the type level. When `true` (default), the type system parses each
181
+ * resource value for `{{variable}}` patterns and types the `t()` options
182
+ * accordingly.
183
+ *
184
+ * Set to `false` when your translation strings use a different
185
+ * interpolation syntax that the i18next type extractor cannot understand
186
+ * e.g. ICU MessageFormat plurals like `{count, plural, one {{count} row}
187
+ * other {{count} rows}}` would otherwise produce phantom variable names
188
+ * because the default extractor naively matches the outermost `{{` …
189
+ * `}}`. This flag is type-only; runtime interpolation is governed by
190
+ * `InterpolationOptions` and is unaffected.
191
+ *
192
+ * Required by `i18next-icu` users — see that package's docs for the
193
+ * recommended `CustomTypeOptions` augmentation.
194
+ *
195
+ * @default true
196
+ */
197
+ parseInterpolation: true;
198
+
199
+ /**
200
+ * Use a proxy-based selector to select a translation.
201
+ *
202
+ * Enables features like go-to definition, and better DX/faster autocompletion
203
+ * for TypeScript developers.
204
+ *
205
+ * If you're working with an especially large set of translations and aren't
206
+ * using context, you set `enableSelector` to `"optimize"` and i18next won't do
207
+ * any type-level processing of your translations at all.
208
+ *
209
+ * With `enableSelector` set to `"optimize"`, i18next is capable of supporting
210
+ * arbitrarily large/deep translation sets without causing any IDE slowdown
211
+ * whatsoever.
212
+ *
213
+ * Set `enableSelector` to `"strict"` to require an explicit namespace as the
214
+ * first selector path segment in every call. The selector proxy stops
215
+ * exposing the primary namespace's keys flat on `$` — even
216
+ * `useTranslation('only')` must use `$.only.foo`, never `$.foo`. At runtime,
217
+ * a leading segment matching the scope's namespace list (primary included)
218
+ * is always rewritten as a namespace prefix, fully decoupling selector
219
+ * shape from resolution scope. Use this when you want a single mental model
220
+ * for selector paths regardless of how many namespaces a hook was created
221
+ * with, and to remove the silent miss that flat-primary paths can otherwise
222
+ * cause in multi-ns hooks (see [#2429](https://github.com/i18next/i18next/issues/2429)).
223
+ *
224
+ * `"strict"` mode is incompatible with the `"optimize"` shortcut. If you
225
+ * have keys whose names match sibling namespaces (the
226
+ * [#2405](https://github.com/i18next/i18next/issues/2405) pattern), do not
227
+ * enable strict mode — the leading-segment rewrite would route those keys
228
+ * into the wrong namespace.
229
+ *
230
+ * @default false
231
+ */
232
+ enableSelector: false;
233
+
234
+ /**
235
+ * Maps interpolation format specifiers to their expected value types.
236
+ *
237
+ * By default, i18next infers types from built-in formatter names:
238
+ * - `number`, `currency` → `number`
239
+ * - `datetime` → `Date`
240
+ * - `relativetime` → `number`
241
+ * - `list` → `readonly string[]`
242
+ * - No format specifier → `string | number` (since i18next stringifies values at runtime)
243
+ *
244
+ * Use this option to add mappings for custom formatters or to override
245
+ * the built-in defaults.
246
+ *
247
+ * @default {} (empty — built-in defaults apply)
248
+ *
249
+ * @example
250
+ * ```ts
251
+ * interface CustomTypeOptions {
252
+ * interpolationFormatTypeMap: {
253
+ * // custom formatter
254
+ * uppercase: string;
255
+ * // override built-in
256
+ * currency: string;
257
+ * };
258
+ * }
259
+ * ```
260
+ */
261
+ interpolationFormatTypeMap: {};
262
+ },
263
+ CustomTypeOptions
264
+ >,
265
+ // HACK: apply merged resources *after* CustomTypeOptions so registry contributions
266
+ // survive when CustomTypeOptions.resources is also defined. Without this
267
+ // second step, the inner $MergeBy would replace the default `resources: object`
268
+ // with CustomTypeOptions['resources'] alone, dropping the registry namespaces.
269
+ { resources: _MergedResources }
164
270
  >;
165
271
 
166
272
  export type PluginOptions<T> = $MergeBy<
@@ -641,6 +747,19 @@ export interface InitOptions<T = object> extends PluginOptions<T> {
641
747
  */
642
748
  nsSeparator?: false | string;
643
749
 
750
+ /**
751
+ * Selector-API mode. Mirrors `CustomTypeOptions['enableSelector']`.
752
+ *
753
+ * - `false` (default): selector API disabled.
754
+ * - `true` / `'optimize'`: selector resolution enabled.
755
+ * - `'strict'`: require an explicit namespace as the first selector
756
+ * path segment in every call. The resolver always rewrites a leading
757
+ * namespace-matching segment as a namespace prefix.
758
+ *
759
+ * @default false
760
+ */
761
+ enableSelector?: false | true | 'optimize' | 'strict';
762
+
644
763
  /**
645
764
  * Char to split plural from key
646
765
  * @default '_'
package/typescript/t.d.ts CHANGED
@@ -36,6 +36,7 @@ type _UnescapePrefix = TypeOptions['unescapePrefix'];
36
36
  type _UnescapeSuffix = TypeOptions['unescapeSuffix'];
37
37
  type _StrictKeyChecks = TypeOptions['strictKeyChecks'];
38
38
  type _EnableSelector = TypeOptions['enableSelector'];
39
+ type _ParseInterpolation = TypeOptions['parseInterpolation'];
39
40
  type _InterpolationFormatTypeMap = TypeOptions['interpolationFormatTypeMap'];
40
41
 
41
42
  type $IsResourcesDefined = [keyof _Resources] extends [never] ? false : true;
@@ -76,19 +77,23 @@ interface Branded<Ns extends Namespace> {
76
77
  /** ****************************************************
77
78
  * Build all keys and key prefixes based on Resources *
78
79
  ***************************************************** */
79
- type KeysBuilderWithReturnObjects<Res, Key = keyof Res> = Key extends keyof Res
80
- ? Res[Key] extends $Dictionary | readonly unknown[]
81
- ?
82
- | JoinKeys<Key, WithOrWithoutPlural<keyof $OmitArrayKeys<Res[Key]>>>
83
- | JoinKeys<Key, KeysBuilderWithReturnObjects<Res[Key]>>
84
- : never
85
- : never;
80
+ type KeysBuilderWithReturnObjects<Res, Key = keyof Res> = [Res] extends [never]
81
+ ? never
82
+ : Key extends keyof Res
83
+ ? Res[Key] extends $Dictionary | readonly unknown[]
84
+ ?
85
+ | JoinKeys<Key, WithOrWithoutPlural<keyof $OmitArrayKeys<Res[Key]>>>
86
+ | JoinKeys<Key, KeysBuilderWithReturnObjects<Res[Key]>>
87
+ : never
88
+ : never;
86
89
 
87
- type KeysBuilderWithoutReturnObjects<Res, Key = keyof $OmitArrayKeys<Res>> = Key extends keyof Res
88
- ? Res[Key] extends $Dictionary | readonly unknown[]
89
- ? JoinKeys<Key, KeysBuilderWithoutReturnObjects<Res[Key]>>
90
- : Key
91
- : never;
90
+ type KeysBuilderWithoutReturnObjects<Res, Key = keyof $OmitArrayKeys<Res>> = [Res] extends [never]
91
+ ? never
92
+ : Key extends keyof Res
93
+ ? Res[Key] extends $Dictionary | readonly unknown[]
94
+ ? JoinKeys<Key, KeysBuilderWithoutReturnObjects<Res[Key]>>
95
+ : Key
96
+ : never;
92
97
 
93
98
  type KeysBuilder<Res, WithReturnObjects> = $IsResourcesDefined extends true
94
99
  ? WithReturnObjects extends true
@@ -168,8 +173,9 @@ type ParseActualValue<Ret> = Ret extends `${_UnescapePrefix}${infer ActualValue}
168
173
  : Ret;
169
174
 
170
175
  /** Parses interpolation entries as `[variableName, formatSpecifier | never]` tuples. */
171
- type ParseInterpolationEntries<Ret> =
172
- Ret extends `${string}${_InterpolationPrefix}${infer Value}${_InterpolationSuffix}${infer Rest}`
176
+ type ParseInterpolationEntries<Ret> = [_ParseInterpolation] extends [false]
177
+ ? never
178
+ : Ret extends `${string}${_InterpolationPrefix}${infer Value}${_InterpolationSuffix}${infer Rest}`
173
179
  ?
174
180
  | (Value extends `${infer ActualValue},${infer Format}`
175
181
  ? [ParseActualValue<ActualValue>, TrimSpaces<Format>]