i18next 25.8.20 → 25.9.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":"25.8.20"}
1
+ {"type":"module","version":"25.9.0"}
package/jsr.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@i18next/i18next",
3
- "version": "25.8.20",
3
+ "version": "25.9.0",
4
4
  "license": "MIT",
5
5
  "exports": "./src/index.js",
6
6
  "publish": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next",
3
- "version": "25.8.20",
3
+ "version": "25.9.0",
4
4
  "description": "i18next internationalization framework",
5
5
  "main": "./dist/cjs/i18next.js",
6
6
  "module": "./dist/esm/i18next.js",
package/typescript/t.d.ts CHANGED
@@ -234,6 +234,27 @@ export type KeyWithContext<Key, TOpt extends TOptions> = TOpt['context'] extends
234
234
  ? `${Key & string}${_ContextSeparator}${TOpt['context']}`
235
235
  : Key;
236
236
 
237
+ export type ContextOfKey<
238
+ Key extends string,
239
+ Ns extends Namespace = DefaultNamespace,
240
+ TOpt extends TOptions = {},
241
+ KPrefix = undefined,
242
+ Keys extends $Dictionary = KeysByTOptions<TOpt>,
243
+ ActualNS extends Namespace = NsByTOptions<Ns, TOpt>,
244
+ ActualKeys =
245
+ | ParseKeysByKeyPrefix<Keys[$FirstNamespace<ActualNS>], KPrefix>
246
+ | ParseKeysByNamespaces<ActualNS, Keys>
247
+ | ParseKeysByFallbackNs<Keys>,
248
+ > = $IsResourcesDefined extends true
249
+ ? Key extends ActualKeys
250
+ ? string
251
+ : ActualKeys extends
252
+ | `${Key}${_ContextSeparator}${infer Context}${_PluralSeparator}${PluralSuffix}`
253
+ | `${Key}${_ContextSeparator}${infer Context}`
254
+ ? Context
255
+ : never
256
+ : string;
257
+
237
258
  // helper that maps the configured fallbackNS value to the matching resources slice
238
259
  type FallbackResourcesOf<FallbackNS, R> = FallbackNS extends readonly (infer FN)[]
239
260
  ? R[FN & keyof R]
@@ -337,7 +358,12 @@ interface TFunctionStrict<
337
358
  Ret extends TFunctionReturn<Ns, AppendKeyPrefix<Key, EffectiveKPrefix<KPrefix, TOpt>>, TOpt>,
338
359
  >(
339
360
  key: Key | Key[],
340
- options?: TOpt & InterpolationMap<Ret>,
361
+ options?: TOpt &
362
+ InterpolationMap<Ret> & {
363
+ context?: Key extends string
364
+ ? ContextOfKey<Key, Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>>
365
+ : never;
366
+ },
341
367
  ): TFunctionReturnOptionalDetails<TFunctionProcessReturnValue<$NoInfer<Ret>, never>, TOpt>;
342
368
  <
343
369
  const Key extends ParseKeys<Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>> | TemplateStringsArray,
@@ -346,7 +372,12 @@ interface TFunctionStrict<
346
372
  >(
347
373
  key: Key | Key[],
348
374
  defaultValue: string,
349
- options?: TOpt & InterpolationMap<Ret>,
375
+ options?: TOpt &
376
+ InterpolationMap<Ret> & {
377
+ context?: Key extends string
378
+ ? ContextOfKey<Key, Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>>
379
+ : never;
380
+ },
350
381
  ): TFunctionReturnOptionalDetails<TFunctionProcessReturnValue<$NoInfer<Ret>, never>, TOpt>;
351
382
  }
352
383
 
@@ -358,7 +389,17 @@ interface TFunctionNonStrict<
358
389
  const Key extends ParseKeys<Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>> | TemplateStringsArray,
359
390
  const TOpt extends TOptions,
360
391
  Ret extends TFunctionReturn<Ns, AppendKeyPrefix<Key, EffectiveKPrefix<KPrefix, TOpt>>, TOpt>,
361
- const ActualOptions extends TOpt & InterpolationMap<Ret> = TOpt & InterpolationMap<Ret>,
392
+ const ActualOptions extends Omit<TOpt, 'context'> &
393
+ InterpolationMap<Ret> & {
394
+ context?: Key extends string
395
+ ? ContextOfKey<Key, Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>>
396
+ : never;
397
+ } = TOpt &
398
+ InterpolationMap<Ret> & {
399
+ context?: Key extends string
400
+ ? ContextOfKey<Key, Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>>
401
+ : never;
402
+ },
362
403
  DefaultValue extends string = never,
363
404
  >(
364
405
  ...args:
@@ -388,9 +429,23 @@ export type KeyPrefix<Ns extends Namespace> = ResourceKeys<true>[$FirstNamespace
388
429
  /// ↆ selector ↆ ///
389
430
  /// ////////////// ///
390
431
 
432
+ declare const $PluralBrand: unique symbol;
433
+ /** Marks a value as coming from a plural-form key, requiring `count` in the selector options. */
434
+ type PluralValue<T extends string> = T & { readonly [$PluralBrand]: typeof $PluralBrand };
435
+ /** Recursively strips the {@link PluralValue} brand from a type (handles nested objects for `returnObjects`). */
436
+ type DeepUnwrapPlural<T> =
437
+ T extends PluralValue<infer U>
438
+ ? U
439
+ : T extends readonly any[]
440
+ ? { [I in keyof T]: DeepUnwrapPlural<T[I]> }
441
+ : T extends object
442
+ ? { [K in keyof T]: DeepUnwrapPlural<T[K]> }
443
+ : T;
444
+
391
445
  type NsArg<Ns extends Namespace> = Ns[number] | readonly Ns[number][];
392
446
 
393
447
  interface TFunctionSelector<Ns extends Namespace, KPrefix, Source> extends Branded<Ns> {
448
+ // ── Selector(s) with explicit `ns` ───────────────────────────────────────────
394
449
  <
395
450
  Target extends ConstrainTarget<Opts>,
396
451
  const NewNs extends NsArg<Ns> & Namespace,
@@ -403,16 +458,72 @@ interface TFunctionSelector<Ns extends Namespace, KPrefix, Source> extends Brand
403
458
  options: Opts & InterpolationMap<Target> & { ns: NewNs },
404
459
  ): SelectorReturn<Target, Opts>;
405
460
 
461
+ // ── Array of selectors with default `ns` ─────────────────────────────────────
462
+ // Captures the selector tuple as `const Fns` so TypeScript preserves each
463
+ // element's exact return type. The union of return types is then extracted
464
+ // via a distributive `infer`, which correctly handles mixed PluralValue<T> and
465
+ // plain-string callbacks that would otherwise cause TypeScript to "lock in" the
466
+ // type from the first element.
467
+ <
468
+ const Fns extends readonly ((src: Select<Source, Opts['context']>) => string | object)[],
469
+ const Opts extends SelectorOptions<Ns[number]> = SelectorOptions<Ns[number]>,
470
+ >(
471
+ selectors: Fns,
472
+ options?: Opts &
473
+ InterpolationMap<
474
+ DeepUnwrapPlural<Fns[number] extends (...args: any[]) => infer R ? R : never>
475
+ >,
476
+ ): SelectorReturn<Fns[number] extends (...args: any[]) => infer R ? R : never, Opts>;
477
+
478
+ // ── Single selector with context — bypasses count enforcement ────────────────
479
+ // When `context` is present in options, `Target` is derived from the
480
+ // context-filtered source (third mapped type of FilterKeys), which does NOT
481
+ // apply the PluralValue brand. A separate overload avoids the circular
482
+ // inference that would otherwise occur when `Opts['context']` sits inside the
483
+ // conditional rest tuple of the overload below.
406
484
  <
407
485
  Target extends ConstrainTarget<Opts>,
408
486
  const NewNs extends NsArg<Ns> = Ns[number],
409
- const Opts extends SelectorOptions<NewNs> = SelectorOptions<NewNs>,
487
+ const Opts extends SelectorOptions<NewNs> & { context: string } = SelectorOptions<NewNs> & {
488
+ context: string;
489
+ },
410
490
  >(
411
- selector:
412
- | SelectorFn<Source, ApplyTarget<Target, Opts>, Opts>
413
- | readonly SelectorFn<Source, ApplyTarget<Target, Opts>, Opts>[],
414
- options?: Opts & InterpolationMap<Target>,
491
+ selector: SelectorFn<Source, ApplyTarget<Target, Opts>, Opts>,
492
+ options: Opts & InterpolationMap<Target>,
415
493
  ): SelectorReturn<Target, Opts>;
494
+
495
+ // ── Single selector with defaultValue — preserves literal type of DV ────────
496
+ // `const Opts` loses literal precision for `defaultValue` when inferred
497
+ // through a conditional rest tuple (TypeScript limitation). This dedicated
498
+ // overload captures `DV` from a regular (non-conditional) parameter position,
499
+ // preserving its literal type. Count enforcement for plural keys is achieved
500
+ // via a conditional intersection on the options parameter.
501
+ <
502
+ const Fn extends (src: Select<Source, undefined>) => ConstrainTarget<Opts>,
503
+ const DV extends string,
504
+ const Opts extends SelectorOptions<Ns[number]> = SelectorOptions<Ns[number]>,
505
+ >(
506
+ selector: Fn,
507
+ options: Opts & { defaultValue: DV } & (ReturnType<Fn> extends PluralValue<string>
508
+ ? { count: number }
509
+ : {}) &
510
+ InterpolationMap<DeepUnwrapPlural<ReturnType<Fn>>>,
511
+ ): SelectorReturn<ReturnType<Fn>, Opts, DV>;
512
+
513
+ // ── Single selector without context — enforces count for plural keys ──────────
514
+ // Uses `const Fn` to capture the selector's exact return type independently of
515
+ // `Opts`, breaking the circular dependency between `Target` inference and the
516
+ // conditional rest tuple. `ReturnType<Fn>` drives both the plural check and
517
+ // the return type; `Opts` is inferred later from the resolved rest args.
518
+ <
519
+ const Fn extends (src: Select<Source, undefined>) => ConstrainTarget<Opts>,
520
+ const Opts extends SelectorOptions<Ns[number]> = SelectorOptions<Ns[number]>,
521
+ >(
522
+ selector: Fn,
523
+ ...args: ReturnType<Fn> extends PluralValue<string>
524
+ ? [options: Opts & { count: number } & InterpolationMap<DeepUnwrapPlural<ReturnType<Fn>>>]
525
+ : [options?: Opts & InterpolationMap<ReturnType<Fn>>]
526
+ ): SelectorReturn<ReturnType<Fn>, Opts>;
416
527
  }
417
528
 
418
529
  interface SelectorOptions<Ns = Namespace>
@@ -423,8 +534,9 @@ interface SelectorOptions<Ns = Namespace>
423
534
  type SelectorReturn<
424
535
  Target,
425
536
  Opts extends { defaultValue?: unknown; returnObjects?: boolean },
537
+ DV = Opts['defaultValue'],
426
538
  > = $IsResourcesDefined extends true
427
- ? TFunctionReturnOptionalDetails<ProcessReturnValue<Target, Opts['defaultValue']>, Opts>
539
+ ? TFunctionReturnOptionalDetails<ProcessReturnValue<DeepUnwrapPlural<Target>, DV>, Opts>
428
540
  : DefaultTReturn<Opts>;
429
541
 
430
542
  interface SelectorFn<Source, Target, Opts extends SelectorOptions<unknown>> {
@@ -501,7 +613,9 @@ type FilterKeys<T, Context> = never | T extends readonly any[]
501
613
  | `${infer Prefix}${_PluralSeparator}${PluralSuffix}`
502
614
  | `${infer Prefix}${_PluralSeparator}ordinal${_PluralSeparator}${PluralSuffix}`
503
615
  ? Prefix
504
- : never]: T[K] extends object ? FilterKeys<T[K], Context> : T[K];
616
+ : never]: T[K] extends object
617
+ ? FilterKeys<T[K], Context>
618
+ : PluralValue<T[K] & string>;
505
619
  } & {
506
620
  [K in keyof T as T[K] extends object
507
621
  ? never