i18next 26.4.7 → 26.5.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.
@@ -1 +1 @@
1
- {"type":"module","version":"26.4.7"}
1
+ {"type":"module","version":"26.5.0"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next",
3
- "version": "26.4.7",
3
+ "version": "26.5.0",
4
4
  "description": "i18next internationalization framework",
5
5
  "main": "./dist/cjs/i18next.js",
6
6
  "module": "./dist/esm/i18next.js",
@@ -189,8 +189,24 @@ export type TypeOptions = $MergeBy<
189
189
  * `}}`. This flag is type-only; runtime interpolation is governed by
190
190
  * `InterpolationOptions` and is unaffected.
191
191
  *
192
- * Required by `i18next-icu` users — see that package's docs for the
193
- * recommended `CustomTypeOptions` augmentation.
192
+ * Set to `'icu'` with `i18next-icu` to type `t()` options from the ICU
193
+ * MessageFormat arguments instead: `{name}` takes `string | number`,
194
+ * `{n, number}`, `plural` and `selectordinal` take `number`, `{d, date}`
195
+ * and `{t, time}` take `Date`, `select` takes its case keys or any string
196
+ * (for `other`), and arguments nested in cases are required too.
197
+ * `interpolationFormatTypeMap` overrides a type by its format name (e.g.
198
+ * `date: Date | number`). Options become required for a message with
199
+ * arguments.
200
+ *
201
+ * Set to `'mf2'` with `i18next-mf2` to type `t()` options from the
202
+ * MessageFormat 2 variables: `{$name}` takes `string | number`, a
203
+ * variable annotated with `:number`, `:integer`, `:currency`, `:percent`
204
+ * or `:unit` (directly or through `.input`/`.local`) takes `number`,
205
+ * `:datetime`, `:date` and `:time` take `Date`, `:string` takes `string`,
206
+ * option and markup variables (`minimumFractionDigits=$digits`,
207
+ * `{#link href=$url}`) take `string | number`, and `.local` variables are
208
+ * not arguments. `interpolationFormatTypeMap` overrides a type by its
209
+ * function name.
194
210
  *
195
211
  * @default true
196
212
  */
package/typescript/t.d.ts CHANGED
@@ -172,7 +172,7 @@ type ParseActualValue<Ret> = Ret extends `${_UnescapePrefix}${infer ActualValue}
172
172
  : Ret;
173
173
 
174
174
  /** Parses interpolation entries as `[variableName, formatSpecifier | never]` tuples. */
175
- type ParseInterpolationEntries<Ret> = [_ParseInterpolation] extends [false]
175
+ type ParseInterpolationEntries<Ret> = [_ParseInterpolation] extends [false | 'icu' | 'mf2']
176
176
  ? never
177
177
  : Ret extends `${string}${_InterpolationPrefix}${infer Value}${_InterpolationSuffix}${infer Rest}`
178
178
  ? | (Value extends `${infer ActualValue},${infer Format}`
@@ -225,10 +225,200 @@ type _InterpolationMapFromEntries<E> = _UnionToIntersection<
225
225
  : never
226
226
  >;
227
227
 
228
- export type InterpolationMap<Ret> = $PreservedValue<
229
- _InterpolationMapFromEntries<ParseInterpolationEntries<Ret>>,
230
- Record<string, unknown>
231
- >;
228
+ /** *********************************************************************
229
+ * ICU MessageFormat arguments (`parseInterpolation: 'icu'`) *
230
+ *********************************************************************** */
231
+ type _IcuWhitespace = ' ' | '\n' | '\t' | '\r';
232
+ type _IcuTrim<S> = S extends `${_IcuWhitespace}${infer R}`
233
+ ? _IcuTrim<R>
234
+ : S extends `${infer R}${_IcuWhitespace}`
235
+ ? _IcuTrim<R>
236
+ : S;
237
+
238
+ /**
239
+ * Drops ICU apostrophe quoting as intl-messageformat (i18next-icu) reads it:
240
+ * `''` is a literal apostrophe, `'{...}'` (also `'}'`, `'<'`, `'>'`, `'#'`) is
241
+ * literal text up to the next single apostrophe.
242
+ * Simplification: `'#` quotes everywhere here, at runtime only inside plurals.
243
+ */
244
+ type _IcuStripQuotes<S, Acc extends string = ''> = S extends `${infer L}'${infer R}`
245
+ ? R extends `'${infer R2}`
246
+ ? _IcuStripQuotes<R2, `${Acc}${L}`>
247
+ : R extends `${'{' | '}' | '<' | '>' | '#'}${string}`
248
+ ? _IcuStripQuotes<_IcuSkipQuoted<R>, `${Acc}${L}`>
249
+ : _IcuStripQuotes<R, `${Acc}${L}'`>
250
+ : `${Acc}${S & string}`;
251
+ type _IcuSkipQuoted<S> = S extends `${string}'${infer R}`
252
+ ? R extends `'${infer R2}`
253
+ ? _IcuSkipQuoted<R2>
254
+ : R
255
+ : '';
256
+
257
+ /** Reads up to the `}` closing an already opened `{`; returns `[content, rest]`. */
258
+ type _IcuBlock<
259
+ S,
260
+ Acc extends string = '',
261
+ Depth extends 0[] = [],
262
+ > = S extends `${infer L}}${infer R}`
263
+ ? L extends `${infer L2}{${infer R2}`
264
+ ? _IcuBlock<`${R2}}${R}`, `${Acc}${L2}{`, [...Depth, 0]>
265
+ : Depth extends [0, ...infer D extends 0[]]
266
+ ? _IcuBlock<R, `${Acc}${L}}`, D>
267
+ : [`${Acc}${L}`, R]
268
+ : never;
269
+
270
+ /** `[name, type]` entries of every argument in a message, nested cases included. */
271
+ type _IcuEntries<S, Acc = never> = S extends `${string}{${infer R}`
272
+ ? _IcuBlock<R> extends [infer Arg, infer Rest]
273
+ ? _IcuEntries<Rest, Acc | _IcuArg<Arg>>
274
+ : Acc
275
+ : Acc;
276
+
277
+ /** Walks plural/select cases: `[selector keys, entries of all case messages]`. */
278
+ type _IcuCases<S, Keys = never, Acc = never> = S extends `${infer L}{${infer R}`
279
+ ? _IcuBlock<R> extends [infer Msg, infer Rest]
280
+ ? _IcuCases<Rest, Keys | _IcuTrim<L>, Acc | _IcuEntries<Msg>>
281
+ : [Keys, Acc]
282
+ : [Keys, Acc];
283
+
284
+ type _IcuArg<A> = A extends `${infer Name},${infer Type},${infer Cases}`
285
+ ? _IcuTrim<Type> extends 'plural' | 'selectordinal'
286
+ ? [_IcuTrim<Name>, _IcuFormatType<_IcuTrim<Type>>] | _IcuCases<Cases>[1]
287
+ : _IcuTrim<Type> extends 'select'
288
+ ? _IcuCases<Cases> extends [infer Keys, infer E]
289
+ ? [_IcuTrim<Name>, 'other' extends Keys ? Exclude<Keys, 'other'> | (string & {}) : Keys] | E
290
+ : never
291
+ : [_IcuTrim<Name>, _IcuFormatType<_IcuTrim<Type>>] // `{n, number, ::currency/EUR}`
292
+ : A extends `${infer Name},${infer Type}`
293
+ ? [_IcuTrim<Name>, _IcuFormatType<_IcuTrim<Type>>]
294
+ : [_IcuTrim<A>, _IcuFormatType<never>];
295
+
296
+ /** `interpolationFormatTypeMap` wins, then ICU's built-in argument types. */
297
+ type _IcuFormatType<F> = [F] extends [never]
298
+ ? string | number
299
+ : F extends keyof _InterpolationFormatTypeMap
300
+ ? _InterpolationFormatTypeMap[F]
301
+ : F extends 'number' | 'plural' | 'selectordinal'
302
+ ? number
303
+ : F extends 'date' | 'time'
304
+ ? Date
305
+ : string | number;
306
+
307
+ /**
308
+ * Merges `[name, type]` entries into one record: a name used twice gets both
309
+ * types, a dotted MF2 name (`$user.name`) becomes a nested object.
310
+ */
311
+ type _ArgsFromEntries<E> =
312
+ _UnionToIntersection<
313
+ E extends [infer Name extends string, infer Type]
314
+ ? Name extends `${string}.${string}`
315
+ ? $StringKeyPathToRecord<Name, Type>
316
+ : { [K in Name]: Type }
317
+ : never
318
+ > extends infer M
319
+ ? [E] extends [never]
320
+ ? never
321
+ : { [K in keyof M]: M[K] }
322
+ : never;
323
+
324
+ type _IcuArgsMap<Ret> = Ret extends string
325
+ ? string extends Ret
326
+ ? never
327
+ : _ArgsFromEntries<_IcuEntries<_IcuStripQuotes<Ret>>>
328
+ : never;
329
+
330
+ /** *********************************************************************
331
+ * MessageFormat 2 variables (`parseInterpolation: 'mf2'`) *
332
+ *********************************************************************** */
333
+ /** Drops `\`-escapes (`\{`, `\}`, `\|`, `\\`), so they are neither braces nor quotes. */
334
+ type _Mf2StripEscapes<S, Acc extends string = ''> = S extends `${infer L}\\${string}${infer R}`
335
+ ? _Mf2StripEscapes<R, `${Acc}${L}`>
336
+ : `${Acc}${S & string}`;
337
+
338
+ /** Reads an expression up to its `}`, skipping `|quoted literals|`; returns `[body, rest]`. */
339
+ type _Mf2Expression<S, Acc extends string = ''> = S extends `${infer B}}${infer Rest}`
340
+ ? B extends `${infer Before}|${infer Lit}`
341
+ ? `${Lit}}${Rest}` extends `${string}|${infer After}`
342
+ ? _Mf2Expression<After, `${Acc}${Before} `>
343
+ : never
344
+ : [`${Acc}${B}`, Rest]
345
+ : never;
346
+
347
+ /** `[name, type]` entries of every variable in a message's expressions; `{{` opens a quoted pattern. */
348
+ type _Mf2Entries<S, Acc = never> = S extends `${string}{${infer R}`
349
+ ? R extends `{${infer Pattern}`
350
+ ? _Mf2Entries<Pattern, Acc>
351
+ : _Mf2Expression<R> extends [infer Body, infer Rest]
352
+ ? _Mf2Entries<Rest, Acc | _Mf2ExprEntries<_Mf2Spaces<Body>>>
353
+ : Acc
354
+ : Acc;
355
+
356
+ type _Mf2Space<S, C extends string> = S extends `${infer L}${C}${infer R}`
357
+ ? _Mf2Space<`${L} ${R}`, C>
358
+ : S;
359
+ type _Mf2Spaces<S> = _IcuTrim<_Mf2Space<_Mf2Space<_Mf2Space<S, '\n'>, '\t'>, '\r'>>;
360
+ type _Mf2Name<S> = S extends `${infer N} ${string}`
361
+ ? _Mf2Name<N>
362
+ : S extends `${infer N}/${string}`
363
+ ? N
364
+ : S;
365
+
366
+ /** `{$x :fn opt=$y}`: `$x` typed by its function, option and markup variables `string | number`. */
367
+ type _Mf2ExprEntries<Body> = Body extends `$${infer R}`
368
+ ? R extends `${infer Name} ${infer Tail}`
369
+ ? | [Name, _Mf2FunctionType<_IcuTrim<Tail> extends `:${infer F}` ? _Mf2Name<F> : never>]
370
+ | _Mf2Vars<Tail>
371
+ : [_Mf2Name<R>, _Mf2FunctionType<never>]
372
+ : _Mf2Vars<Body>;
373
+
374
+ /** Every `$name` in a string, typed `string | number`. */
375
+ type _Mf2Vars<S, Acc = never> = S extends `${string}$${infer R}`
376
+ ? _Mf2Vars<R, Acc | [_Mf2Name<R>, string | number]>
377
+ : Acc;
378
+
379
+ /** `interpolationFormatTypeMap` wins, then messageformat's built-in functions. */
380
+ type _Mf2FunctionType<F> = [F] extends [never]
381
+ ? string | number
382
+ : F extends keyof _InterpolationFormatTypeMap
383
+ ? _InterpolationFormatTypeMap[F]
384
+ : F extends 'number' | 'integer' | 'currency' | 'percent' | 'offset' | 'unit' | 'math'
385
+ ? number
386
+ : F extends 'datetime' | 'date' | 'time'
387
+ ? Date
388
+ : F extends 'string'
389
+ ? string
390
+ : string | number;
391
+
392
+ /** Names declared by `.local $name = ...` (local, not passed in). */
393
+ type _Mf2Locals<S, Acc = never> = S extends `${string}.local${infer R}`
394
+ ? _Mf2Locals<R, Acc | (_IcuTrim<R> extends `$${infer N}=${string}` ? _IcuTrim<N> : never)>
395
+ : Acc;
396
+ /** `.match $a $b`: selectors, normally also declared by `.input` or `.local`. */
397
+ type _Mf2Selectors<S> = S extends `${string}.match${infer R}`
398
+ ? _Mf2Vars<_Mf2Spaces<R extends `${infer Sel}{${string}` ? Sel : R>>
399
+ : never;
400
+
401
+ type _Mf2ArgsMap<Ret> = Ret extends string
402
+ ? string extends Ret
403
+ ? never
404
+ : _Mf2StripEscapes<Ret> extends infer S
405
+ ? _ArgsFromEntries<
406
+ Exclude<
407
+ _Mf2Entries<S> | _Mf2Selectors<S>,
408
+ [_IcuTrim<S> extends `.${string}` ? _Mf2Locals<S> : never, unknown]
409
+ >
410
+ >
411
+ : never
412
+ : never;
413
+
414
+ export type InterpolationMap<Ret> = [_ParseInterpolation] extends ['icu']
415
+ ? $PreservedValue<_IcuArgsMap<Ret>, Record<string, unknown>>
416
+ : [_ParseInterpolation] extends ['mf2']
417
+ ? $PreservedValue<_Mf2ArgsMap<Ret>, Record<string, unknown>>
418
+ : $PreservedValue<
419
+ _InterpolationMapFromEntries<ParseInterpolationEntries<Ret>>,
420
+ Record<string, unknown>
421
+ >;
232
422
 
233
423
  type ParseTReturnPlural<
234
424
  Res,
@@ -393,6 +583,28 @@ type EffectiveKPrefix<KPrefix, TOpt> = TOpt extends { keyPrefix: infer OptKP ext
393
583
  ? OptKP
394
584
  : KPrefix;
395
585
 
586
+ /** `parseInterpolation: 'icu'` or `'mf2'`: messages are typed by their arguments. */
587
+ type _ParsesMessageFormat = [_ParseInterpolation] extends ['icu'] | ['mf2'] ? true : false;
588
+
589
+ /**
590
+ * `t()` options are optional, except for a message with arguments under
591
+ * `parseInterpolation: 'icu'` or `'mf2'` (on a missing one, i18next-icu renders
592
+ * the raw message and i18next-mf2 a `{$name}` fallback). The conditional
593
+ * resolves eagerly; a generic one here would break inference.
594
+ */
595
+ type _KeyAndOptions<Key, Ret, Opts> = _ParsesMessageFormat extends true
596
+ ? [key: Key | Key[], options: Opts] | [key: (Key | Key[]) & _IfOptionalOptions<Ret>]
597
+ : [key: Key | Key[], options?: Opts];
598
+ /** `unknown` when options may be left out, else a type naming the missing arguments. */
599
+ type _IfOptionalOptions<Ret> = _ParsesMessageFormat extends true
600
+ ? {} extends InterpolationMap<Ret>
601
+ ? unknown
602
+ : { 'pass these arguments as t() options': InterpolationMap<Ret> }
603
+ : unknown;
604
+ /** Options parameter of a selector overload: required for a message with arguments. */
605
+ type _SelectorOptionsArg<Ret, Opts> =
606
+ unknown extends _IfOptionalOptions<Ret> ? [options?: Opts] : [options: Opts];
607
+
396
608
  /** ************************
397
609
  * T function declaration *
398
610
  ************************* */
@@ -407,7 +619,7 @@ interface TFunctionStrict<
407
619
  Ret extends TFunctionReturn<Ns, AppendKeyPrefix<Key, EffectiveKPrefix<KPrefix, TOpt>>, TOpt>,
408
620
  >(
409
621
  key: Key | Key[],
410
- options?: TOpt &
622
+ options: TOpt &
411
623
  InterpolationMap<Ret> & {
412
624
  context?: Key extends string
413
625
  ? unknown extends TOpt['context']
@@ -416,6 +628,13 @@ interface TFunctionStrict<
416
628
  : never;
417
629
  },
418
630
  ): TFunctionReturnOptionalDetails<TFunctionProcessReturnValue<$NoInfer<Ret>, never>, TOpt>;
631
+ <
632
+ const Key extends ParseKeys<Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>> | TemplateStringsArray,
633
+ const TOpt extends TOptions,
634
+ Ret extends TFunctionReturn<Ns, AppendKeyPrefix<Key, EffectiveKPrefix<KPrefix, TOpt>>, TOpt>,
635
+ >(
636
+ key: (Key | Key[]) & _IfOptionalOptions<$NoInfer<Ret>>,
637
+ ): TFunctionReturnOptionalDetails<TFunctionProcessReturnValue<$NoInfer<Ret>, never>, TOpt>;
419
638
  <
420
639
  const Key extends ParseKeys<Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>> | TemplateStringsArray,
421
640
  const TOpt extends TOptions,
@@ -460,9 +679,9 @@ interface TFunctionNonStrict<
460
679
  DefaultValue extends string = never,
461
680
  >(
462
681
  ...args:
463
- | [key: Key | Key[], options?: ActualOptions]
464
682
  | [key: string | string[], options: TOpt & $Dictionary & { defaultValue: DefaultValue }]
465
683
  | [key: string | string[], defaultValue: DefaultValue, options?: TOpt & $Dictionary]
684
+ | _KeyAndOptions<Key, $NoInfer<Ret>, ActualOptions>
466
685
  ): TFunctionReturnOptionalDetails<TFunctionProcessReturnValue<$NoInfer<Ret>, DefaultValue>, TOpt>;
467
686
  }
468
687
 
@@ -596,10 +815,13 @@ interface TFunctionSelector<Ns extends Namespace, KPrefix, Source> extends Brand
596
815
  const Opts extends SelectorOptions<Ns[number]> = SelectorOptions<Ns[number]>,
597
816
  >(
598
817
  selectors: Fns,
599
- options?: Opts &
600
- InterpolationMap<
601
- DeepUnwrapPlural<Fns[number] extends (...args: any[]) => infer R ? R : never>
602
- >,
818
+ ...args: _SelectorOptionsArg<
819
+ DeepUnwrapPlural<Fns[number] extends (...args: any[]) => infer R ? R : never>,
820
+ Opts &
821
+ InterpolationMap<
822
+ DeepUnwrapPlural<Fns[number] extends (...args: any[]) => infer R ? R : never>
823
+ >
824
+ >
603
825
  ): SelectorReturn<Fns[number] extends (...args: any[]) => infer R ? R : never, Opts>;
604
826
 
605
827
  // ── Single selector with context + returnObjects ────────────────────────────
@@ -667,7 +889,7 @@ interface TFunctionSelector<Ns extends Namespace, KPrefix, Source> extends Brand
667
889
  selector: Fn,
668
890
  ...args: ReturnType<Fn> extends PluralValue<string>
669
891
  ? [options: Opts & { count: number } & InterpolationMap<DeepUnwrapPlural<ReturnType<Fn>>>]
670
- : [options?: Opts & InterpolationMap<ReturnType<Fn>>]
892
+ : _SelectorOptionsArg<ReturnType<Fn>, Opts & InterpolationMap<ReturnType<Fn>>>
671
893
  ): SelectorReturn<ReturnType<Fn>, Opts>;
672
894
  }
673
895