UNPKG

i18next

Version:

i18next internationalization framework

791 lines (709 loc) 30.2 kB
import type { $OmitArrayKeys, $PreservedValue, $Dictionary, $SpecialObject, $StringKeyPathToRecord, $NoInfer, $Prune, $Turtles, } from './helpers.js'; import type { TypeOptions, Namespace, FlatNamespace, DefaultNamespace, TOptions, TOptionsBase, } from './options.js'; /** @todo consider to replace {} with Record<string, never> */ // Type Options type _ReturnObjects = TypeOptions['returnObjects']; type _ReturnEmptyString = TypeOptions['returnEmptyString']; type _ReturnNull = TypeOptions['returnNull']; type _KeySeparator = TypeOptions['keySeparator']; type _NsSeparator = TypeOptions['nsSeparator']; type _PluralSeparator = TypeOptions['pluralSeparator']; type _ContextSeparator = TypeOptions['contextSeparator']; type _FallbackNamespace = TypeOptions['fallbackNS']; type _Resources = TypeOptions['resources']; type _CompatibilityJSON = TypeOptions['compatibilityJSON']; type _InterpolationPrefix = TypeOptions['interpolationPrefix']; type _InterpolationSuffix = TypeOptions['interpolationSuffix']; type _UnescapePrefix = TypeOptions['unescapePrefix']; type _UnescapeSuffix = TypeOptions['unescapeSuffix']; type _StrictKeyChecks = TypeOptions['strictKeyChecks']; type _EnableSelector = TypeOptions['enableSelector']; type _InterpolationFormatTypeMap = TypeOptions['interpolationFormatTypeMap']; type $IsResourcesDefined = [keyof _Resources] extends [never] ? false : true; type $ValueIfResourcesDefined<Value, Fallback> = $IsResourcesDefined extends true ? Value : Fallback; type $FirstNamespace<Ns extends Namespace> = Ns extends readonly any[] ? Ns[0] : Ns; type Resources = $ValueIfResourcesDefined<_Resources, $Dictionary<string>>; type PluralSuffix = 'zero' | 'one' | 'two' | 'few' | 'many' | 'other'; type WithOrWithoutPlural<Key> = _CompatibilityJSON extends 'v4' ? Key extends `${infer KeyWithoutOrdinalPlural}${_PluralSeparator}ordinal${_PluralSeparator}${PluralSuffix}` ? KeyWithoutOrdinalPlural | Key : Key extends `${infer KeyWithoutPlural}${_PluralSeparator}${PluralSuffix}` ? KeyWithoutPlural | Key : Key : Key; type JoinKeys<K1, K2> = `${K1 & string}${_KeySeparator}${K2 & string}`; type AppendNamespace<Ns, Keys> = `${Ns & string}${_NsSeparator}${Keys & string}`; type TrimSpaces<T extends string, Acc extends string = ''> = T extends `${infer Char}${infer Rest}` ? Char extends ' ' ? TrimSpaces<Rest, Acc> : TrimSpaces<Rest, `${Acc}${Char}`> : T extends '' ? Acc : never; interface Branded<Ns extends Namespace> { $TFunctionBrand: $IsResourcesDefined extends true ? `${Ns extends readonly any[] ? Ns[0] : Ns}` : never; } /** **************************************************** * Build all keys and key prefixes based on Resources * ***************************************************** */ type KeysBuilderWithReturnObjects<Res, Key = keyof Res> = Key extends keyof Res ? Res[Key] extends $Dictionary | readonly unknown[] ? | JoinKeys<Key, WithOrWithoutPlural<keyof $OmitArrayKeys<Res[Key]>>> | JoinKeys<Key, KeysBuilderWithReturnObjects<Res[Key]>> : never : never; type KeysBuilderWithoutReturnObjects<Res, Key = keyof $OmitArrayKeys<Res>> = Key extends keyof Res ? Res[Key] extends $Dictionary | readonly unknown[] ? JoinKeys<Key, KeysBuilderWithoutReturnObjects<Res[Key]>> : Key : never; type KeysBuilder<Res, WithReturnObjects> = $IsResourcesDefined extends true ? WithReturnObjects extends true ? keyof Res | KeysBuilderWithReturnObjects<Res> : KeysBuilderWithoutReturnObjects<Res> : string; type KeysWithReturnObjects = { [Ns in FlatNamespace]: WithOrWithoutPlural<KeysBuilder<Resources[Ns], true>>; }; type KeysWithoutReturnObjects = { [Ns in FlatNamespace]: WithOrWithoutPlural<KeysBuilder<Resources[Ns], false>>; }; type ResourceKeys<WithReturnObjects = _ReturnObjects> = WithReturnObjects extends true ? KeysWithReturnObjects : KeysWithoutReturnObjects; /** ********************************************************************** * Parse t function keys based on the namespace, options and key prefix * *********************************************************************** */ export type KeysByTOptions<TOpt extends TOptions> = TOpt['returnObjects'] extends true ? ResourceKeys<true> : ResourceKeys; export type NsByTOptions<Ns extends Namespace, TOpt extends TOptions> = TOpt['ns'] extends Namespace ? TOpt['ns'] : Ns; type ParseKeysByKeyPrefix<Keys, KPrefix> = KPrefix extends string ? Keys extends `${KPrefix}${_KeySeparator}${infer Key}` ? Key : never : Keys; type ParseKeysByNamespaces<Ns extends Namespace, Keys> = Ns extends readonly (infer UnionNsps)[] ? UnionNsps extends keyof Keys ? AppendNamespace<UnionNsps, Keys[UnionNsps]> : never : never; type ParseKeysByFallbackNs<Keys extends $Dictionary> = _FallbackNamespace extends false ? never : _FallbackNamespace extends (infer UnionFallbackNs extends string)[] ? Keys[UnionFallbackNs] : Keys[_FallbackNamespace & string]; export type FilterKeysByContext<Keys, Context> = Context extends string ? Keys extends | `${infer Prefix}${_ContextSeparator}${Context}${_PluralSeparator}${PluralSuffix}` | `${infer Prefix}${_ContextSeparator}${Context}` ? Prefix : never : Keys; export type ParseKeys< Ns extends Namespace = DefaultNamespace, TOpt extends TOptions = {}, KPrefix = undefined, Keys extends $Dictionary = KeysByTOptions<TOpt>, ActualNS extends Namespace = NsByTOptions<Ns, TOpt>, Context extends TOpt['context'] = TOpt['context'], > = $IsResourcesDefined extends true ? FilterKeysByContext< | ParseKeysByKeyPrefix<Keys[$FirstNamespace<ActualNS>], KPrefix> | ParseKeysByNamespaces<ActualNS, Keys> | ParseKeysByFallbackNs<Keys>, Context > : string; /** ******************************************************* * Parse t function return type and interpolation values * ******************************************************** */ type ParseActualValue<Ret> = Ret extends `${_UnescapePrefix}${infer ActualValue}${_UnescapeSuffix}` ? TrimSpaces<ActualValue> : Ret; /** Parses interpolation entries as `[variableName, formatSpecifier | never]` tuples. */ type ParseInterpolationEntries<Ret> = Ret extends `${string}${_InterpolationPrefix}${infer Value}${_InterpolationSuffix}${infer Rest}` ? | (Value extends `${infer ActualValue},${infer Format}` ? [ParseActualValue<ActualValue>, TrimSpaces<Format>] : [ParseActualValue<Value>, never]) | ParseInterpolationEntries<Rest> : never; /** Extracts just the variable names (kept for backward compat with ParseInterpolationValues usage). */ type ParseInterpolationValues<Ret> = ParseInterpolationEntries<Ret>[0]; /** Built-in i18next formatter name → value type mapping. */ type _BuiltInFormatTypeMap = { number: number; currency: number; datetime: Date; relativetime: number; list: readonly string[]; }; /** Strips inline formatting options, e.g. `currency(EUR)` → `currency`. */ type _StripFormatOptions<F> = F extends `${infer Name}(${string})` ? Name : F; /** Resolves the type for a single interpolation entry based on name and format. */ type _ResolveEntryType<Name extends string, Format> = [Format] extends [never] ? Name extends 'count' ? number : string : Format extends keyof _InterpolationFormatTypeMap ? _InterpolationFormatTypeMap[Format] : _StripFormatOptions<Format> extends keyof _InterpolationFormatTypeMap ? _InterpolationFormatTypeMap[_StripFormatOptions<Format> & keyof _InterpolationFormatTypeMap] : Format extends keyof _BuiltInFormatTypeMap ? _BuiltInFormatTypeMap[Format] : _StripFormatOptions<Format> extends keyof _BuiltInFormatTypeMap ? _BuiltInFormatTypeMap[_StripFormatOptions<Format> & keyof _BuiltInFormatTypeMap] : string; /** Local union-to-intersection (not exported from helpers). */ type _UnionToIntersection<T> = (T extends unknown ? (k: T) => void : never) extends ( k: infer I, ) => void ? I : never; /** Builds a per-entry typed record from parsed interpolation entries and intersects them. */ type _InterpolationMapFromEntries<E> = _UnionToIntersection< E extends [infer Name extends string, infer Format] ? $StringKeyPathToRecord<Name, _ResolveEntryType<Name, Format>> : never >; export type InterpolationMap<Ret> = $PreservedValue< _InterpolationMapFromEntries<ParseInterpolationEntries<Ret>>, Record<string, unknown> >; type ParseTReturnPlural< Res, Key, KeyWithPlural = `${Key & string}${_PluralSeparator}${PluralSuffix}`, > = Res[(KeyWithPlural | Key) & keyof Res]; type ParseTReturnPluralOrdinal< Res, Key, KeyWithOrdinalPlural = `${Key & string}${_PluralSeparator}ordinal${_PluralSeparator}${PluralSuffix}`, > = Res[(KeyWithOrdinalPlural | Key) & keyof Res]; type ParseTReturnWithFallback<Key, Val> = Val extends '' ? _ReturnEmptyString extends true ? '' : Key : Val extends null ? _ReturnNull extends true ? null : Key : Val; type ParseTReturn<Key, Res, TOpt extends TOptions = {}> = ParseTReturnWithFallback< Key, Key extends `${infer K1}${_KeySeparator}${infer RestKey}` ? ParseTReturn<RestKey, Res[K1 & keyof Res], TOpt> : // Process plurals only if count is provided inside options TOpt['count'] extends number ? TOpt['ordinal'] extends boolean ? ParseTReturnPluralOrdinal<Res, Key> : ParseTReturnPlural<Res, Key> : // otherwise access plain key without adding plural and ordinal suffixes Res extends readonly unknown[] ? Key extends `${infer NKey extends number}` ? Res[NKey] : never : Res[Key & keyof Res] >; type TReturnOptionalNull = _ReturnNull extends true ? null : never; type TReturnOptionalObjects<TOpt extends { returnObjects?: unknown }> = _ReturnObjects extends true ? $SpecialObject | string : TOpt['returnObjects'] extends true ? $SpecialObject : string; type DefaultTReturn<TOpt extends { returnObjects?: unknown }> = | TReturnOptionalObjects<TOpt> | TReturnOptionalNull; export type KeyWithContext<Key, TOpt extends TOptions> = TOpt['context'] extends string ? `${Key & string}${_ContextSeparator}${TOpt['context']}` : Key; export type ContextOfKey< Key extends string, Ns extends Namespace = DefaultNamespace, TOpt extends TOptions = {}, KPrefix = undefined, Keys extends $Dictionary = KeysByTOptions<TOpt>, ActualNS extends Namespace = NsByTOptions<Ns, TOpt>, ActualKeys = | ParseKeysByKeyPrefix<Keys[$FirstNamespace<ActualNS>], KPrefix> | ParseKeysByNamespaces<ActualNS, Keys> | ParseKeysByFallbackNs<Keys>, > = $IsResourcesDefined extends true ? Key extends ActualKeys ? string : ActualKeys extends | `${Key}${_ContextSeparator}${infer Context}${_PluralSeparator}${PluralSuffix}` | `${Key}${_ContextSeparator}${infer Context}` ? Context : never : string; // helper that maps the configured fallbackNS value to the matching resources slice type FallbackResourcesOf<FallbackNS, R> = FallbackNS extends readonly (infer FN)[] ? R[FN & keyof R] : [FallbackNS] extends [false] ? never : R[Extract<FallbackNS, keyof R> & keyof R]; /* reuse the parse helpers as top-level aliases (no nested type declarations) */ type _PrimaryParse< ActualKey, PrimaryNS extends keyof Resources, TOpt extends TOptions, > = ParseTReturn<ActualKey, Resources[PrimaryNS], TOpt>; type _FallbackParse<ActualKey, FallbackNS, TOpt extends TOptions> = [ FallbackResourcesOf<FallbackNS, Resources>, ] extends [never] ? never : ParseTReturn<ActualKey, FallbackResourcesOf<FallbackNS, Resources>, TOpt>; export type TFunctionReturn< Ns extends Namespace, Key, TOpt extends TOptions, ActualNS extends Namespace = NsByTOptions<Ns, TOpt>, ActualKey = KeyWithContext<Key, TOpt>, > = $IsResourcesDefined extends true ? ActualKey extends `${infer Nsp}${_NsSeparator}${infer RestKey}` ? ParseTReturn<RestKey, Resources[Nsp & keyof Resources], TOpt> : $FirstNamespace<ActualNS> extends infer PrimaryNS ? [PrimaryNS] extends [keyof Resources] ? [_PrimaryParse<ActualKey, PrimaryNS & keyof Resources, TOpt>] extends [never] ? [_FallbackParse<ActualKey, _FallbackNamespace, TOpt>] extends [never] ? DefaultTReturn<TOpt> : _FallbackParse<ActualKey, _FallbackNamespace, TOpt> : _PrimaryParse<ActualKey, PrimaryNS & keyof Resources, TOpt> : never : never : DefaultTReturn<TOpt>; export type TFunctionDetailedResult<T = string, TOpt extends TOptions = {}> = { /** * The plain used key */ usedKey: string; /** * The translation result. */ res: T; /** * The key with context / plural */ exactUsedKey: string; /** * The used language for this translation. */ usedLng: string; /** * The used namespace for this translation. */ usedNS: string; /** * The parameters used for interpolation. */ usedParams: InterpolationMap<T> & { count?: TOpt['count'] }; }; type TFunctionProcessReturnValue<Ret, DefaultValue> = Ret extends string | $SpecialObject | null ? Ret : [DefaultValue] extends [never] ? Ret : DefaultValue; type TFunctionReturnOptionalDetails<Ret, TOpt extends TOptions> = TOpt['returnDetails'] extends true ? TFunctionDetailedResult<Ret, TOpt> : Ret; type AppendKeyPrefix<Key, KPrefix> = KPrefix extends string ? `${KPrefix}${_KeySeparator}${Key & string}` : Key; /** * Resolves the effective key prefix by preferring a per-call `keyPrefix` from * options over the interface-level `KPrefix` (set via getFixedT's 3rd argument). */ type EffectiveKPrefix<KPrefix, TOpt> = TOpt extends { keyPrefix: infer OptKP extends string } ? OptKP : KPrefix; /** ************************ * T function declaration * ************************* */ interface TFunctionStrict< Ns extends Namespace = DefaultNamespace, KPrefix = undefined, > extends Branded<Ns> { < const Key extends ParseKeys<Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>> | TemplateStringsArray, const TOpt extends TOptions, Ret extends TFunctionReturn<Ns, AppendKeyPrefix<Key, EffectiveKPrefix<KPrefix, TOpt>>, TOpt>, >( key: Key | Key[], options?: TOpt & InterpolationMap<Ret> & { context?: Key extends string ? unknown extends TOpt['context'] ? TOpt['context'] : ContextOfKey<Key, Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>> : never; }, ): TFunctionReturnOptionalDetails<TFunctionProcessReturnValue<$NoInfer<Ret>, never>, TOpt>; < const Key extends ParseKeys<Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>> | TemplateStringsArray, const TOpt extends TOptions, Ret extends TFunctionReturn<Ns, AppendKeyPrefix<Key, EffectiveKPrefix<KPrefix, TOpt>>, TOpt>, >( key: Key | Key[], defaultValue: string, options?: TOpt & InterpolationMap<Ret> & { context?: Key extends string ? unknown extends TOpt['context'] ? TOpt['context'] : ContextOfKey<Key, Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>> : never; }, ): TFunctionReturnOptionalDetails<TFunctionProcessReturnValue<$NoInfer<Ret>, never>, TOpt>; } interface TFunctionNonStrict< Ns extends Namespace = DefaultNamespace, KPrefix = undefined, > extends Branded<Ns> { < const Key extends ParseKeys<Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>> | TemplateStringsArray, const TOpt extends TOptions, Ret extends TFunctionReturn<Ns, AppendKeyPrefix<Key, EffectiveKPrefix<KPrefix, TOpt>>, TOpt>, const ActualOptions extends Omit<TOpt, 'context'> & InterpolationMap<Ret> & { context?: Key extends string ? unknown extends TOpt['context'] ? TOpt['context'] : ContextOfKey<Key, Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>> : never; } = TOpt & InterpolationMap<Ret> & { context?: Key extends string ? unknown extends TOpt['context'] ? TOpt['context'] : ContextOfKey<Key, Ns, TOpt, EffectiveKPrefix<KPrefix, TOpt>> : never; }, DefaultValue extends string = never, >( ...args: | [key: Key | Key[], options?: ActualOptions] | [key: string | string[], options: TOpt & $Dictionary & { defaultValue: DefaultValue }] | [key: string | string[], defaultValue: DefaultValue, options?: TOpt & $Dictionary] ): TFunctionReturnOptionalDetails<TFunctionProcessReturnValue<$NoInfer<Ret>, DefaultValue>, TOpt>; } type TFunctionSignature< Ns extends Namespace = DefaultNamespace, KPrefix = undefined, > = _EnableSelector extends true | 'optimize' ? TFunctionSelector<Ns, KPrefix, GetSource<Ns, KPrefix>> : _StrictKeyChecks extends true ? TFunctionStrict<Ns, KPrefix> : TFunctionNonStrict<Ns, KPrefix>; export interface TFunction< Ns extends Namespace = DefaultNamespace, KPrefix = undefined, > extends TFunctionSignature<Ns, KPrefix> {} export type KeyPrefix<Ns extends Namespace> = ResourceKeys<true>[$FirstNamespace<Ns>] | undefined; /** The raw (unfiltered) resource object for a given namespace. */ export type NsResource<Ns extends Namespace> = Ns extends readonly [keyof Resources, any, ...any] ? Resources[Ns[0]] & PickNamespaces<Resources, Ns[number]> : Resources[$FirstNamespace<Ns>]; /** A selector function that can be used as `keyPrefix` to scope `t()` to a sub-tree of the resource. */ export type KeyPrefixSelector<Ns extends Namespace> = (src: NsResource<Ns>) => object; /** * The type of a selector function accepted by `t()` for a given namespace and optional key prefix. * Use this instead of `Parameters<TFunction<Ns>>[0]` for a stable, readable type. * * @example * ```ts * import type { SelectorParam } from 'i18next'; * * interface CmpProps { * i18nKey: SelectorParam<'myNamespace'>; * } * ``` */ export type SelectorParam<Ns extends Namespace = DefaultNamespace, KPrefix = undefined> = ( src: Select<GetSource<Ns, KPrefix>, undefined>, ) => string; /// ////////////// /// /// ↆ selector ↆ /// /// ////////////// /// declare const $PluralBrand: unique symbol; /** Marks a value as coming from a plural-form key, requiring `count` in the selector options. */ type PluralValue<T extends string> = T & { readonly [$PluralBrand]: typeof $PluralBrand }; declare const $SelectorKeyBrand: unique symbol; /** * A branded string produced by {@link keyFromSelector}. * Can be passed directly to `t()` when the selector API is enabled. */ export type SelectorKey = string & { readonly [$SelectorKeyBrand]: typeof $SelectorKeyBrand }; /** * Type-safe signature for {@link keyFromSelector}. * Constrains the selector callback against the default namespace's resources * (same source that `t()` uses for selectors without explicit `ns`). * When resources are not defined, accepts any selector. */ export type KeyFromSelectorFn = < Ns extends Namespace = DefaultNamespace, KPrefix extends KeyPrefix<Ns> = undefined, >( selector: (src: Select<GetSource<Ns, KPrefix>, undefined>) => any, opts?: { ns?: Ns; keyPrefix?: KPrefix }, ) => SelectorKey; /** Recursively strips the {@link PluralValue} brand from a type (handles nested objects for `returnObjects`). */ type DeepUnwrapPlural<T> = T extends PluralValue<infer U> ? U : T extends readonly any[] ? { [I in keyof T]: DeepUnwrapPlural<T[I]> } : T extends object ? { [K in keyof T]: DeepUnwrapPlural<T[K]> } : T; type NsArg<Ns extends Namespace> = Ns[number] | readonly Ns[number][]; interface TFunctionSelector<Ns extends Namespace, KPrefix, Source> extends Branded<Ns> { // ── Pre-computed key(s) from keyFromSelector ──────────────────────────────── // Accepts a branded `SelectorKey` (or array of them) produced by `keyFromSelector`. // Return-type precision is traded for the flexibility of decoupled key creation. // Placed first so that the more general selector overloads (below) are last — // TypeScript's `Parameters<>` utility resolves against the last call signature. <const Opts extends SelectorOptions<Ns[number]> = SelectorOptions<Ns[number]>>( key: SelectorKey | SelectorKey[], ...args: [options?: Opts & $Dictionary] ): DefaultTReturn<Opts>; // ── Selector(s) with explicit `ns` ─────────────────────────────────────────── < Target extends ConstrainTarget<Opts>, const NewNs extends NsArg<Ns> & Namespace, const Opts extends SelectorOptions<NewNs>, NewSrc extends GetSource<NewNs, KPrefix>, >( selector: | SelectorFn<NewSrc, ApplyTarget<Target, Opts>, Opts> | readonly SelectorFn<NewSrc, ApplyTarget<Target, Opts>, Opts>[], options: Opts & InterpolationMap<Target> & { ns: NewNs }, ): SelectorReturn<Target, Opts>; // ── Array of selectors with default `ns` ───────────────────────────────────── // Captures the selector tuple as `const Fns` so TypeScript preserves each // element's exact return type. The union of return types is then extracted // via a distributive `infer`, which correctly handles mixed PluralValue<T> and // plain-string callbacks that would otherwise cause TypeScript to "lock in" the // type from the first element. < const Fns extends readonly ((src: Select<Source, Opts['context']>) => string | object)[], const Opts extends SelectorOptions<Ns[number]> = SelectorOptions<Ns[number]>, >( selectors: Fns, options?: Opts & InterpolationMap< DeepUnwrapPlural<Fns[number] extends (...args: any[]) => infer R ? R : never> >, ): SelectorReturn<Fns[number] extends (...args: any[]) => infer R ? R : never, Opts>; // ── Single selector with context — bypasses count enforcement ──────────────── // When `context` is present in options, `Target` is derived from the // context-filtered source (third mapped type of FilterKeys), which does NOT // apply the PluralValue brand. A separate overload avoids the circular // inference that would otherwise occur when `Opts['context']` sits inside the // conditional rest tuple of the overload below. < Target extends ConstrainTarget<Opts>, const NewNs extends NsArg<Ns> = Ns[number], const Opts extends SelectorOptions<NewNs> & { context: string } = SelectorOptions<NewNs> & { context: string; }, >( selector: SelectorFn<Source, ApplyTarget<Target, Opts>, Opts>, options: Opts & InterpolationMap<Target>, ): SelectorReturn<Target, Opts>; // ── Single selector with defaultValue — preserves literal type of DV ──────── // `const Opts` loses literal precision for `defaultValue` when inferred // through a conditional rest tuple (TypeScript limitation). This dedicated // overload captures `DV` from a regular (non-conditional) parameter position, // preserving its literal type. Count enforcement for plural keys is achieved // via a conditional intersection on the options parameter. < const Fn extends (src: Select<Source, undefined>) => ConstrainTarget<Opts>, const DV extends string, const Opts extends SelectorOptions<Ns[number]> = SelectorOptions<Ns[number]>, >( selector: Fn, options: Opts & { defaultValue: DV } & (ReturnType<Fn> extends PluralValue<string> ? { count: number } : {}) & InterpolationMap<DeepUnwrapPlural<ReturnType<Fn>>>, ): SelectorReturn<ReturnType<Fn>, Opts, DV>; // ── Single selector without context — enforces count for plural keys ────────── // Uses `const Fn` to capture the selector's exact return type independently of // `Opts`, breaking the circular dependency between `Target` inference and the // conditional rest tuple. `ReturnType<Fn>` drives both the plural check and // the return type; `Opts` is inferred later from the resolved rest args. < const Fn extends (src: Select<Source, undefined>) => ConstrainTarget<Opts>, const Opts extends SelectorOptions<Ns[number]> = SelectorOptions<Ns[number]>, >( selector: Fn, ...args: ReturnType<Fn> extends PluralValue<string> ? [options: Opts & { count: number } & InterpolationMap<DeepUnwrapPlural<ReturnType<Fn>>>] : [options?: Opts & InterpolationMap<ReturnType<Fn>>] ): SelectorReturn<ReturnType<Fn>, Opts>; } interface SelectorOptions<Ns = Namespace> extends Omit<TOptionsBase, 'ns' | 'nsSeparator'>, $Dictionary { ns?: Ns; } type SelectorReturn< Target, Opts extends { defaultValue?: unknown; returnObjects?: boolean }, DV = Opts['defaultValue'], > = $IsResourcesDefined extends true ? TFunctionReturnOptionalDetails<ProcessReturnValue<DeepUnwrapPlural<Target>, DV>, Opts> : DefaultTReturn<Opts>; interface SelectorFn<Source, Target, Opts extends SelectorOptions<unknown>> { (translations: Select<Source, Opts['context']>): Target; } type ApplyKeyPrefix< T extends [any], KPrefix, > = KPrefix extends `${infer Head}${_KeySeparator}${infer Tail}` ? ApplyKeyPrefix<[T[0][Head]], Tail> : T[0][KPrefix & string]; type ApplyTarget< Target, Opts extends { returnObjects?: unknown }, > = Opts['returnObjects'] extends true ? unknown : Target; type ConstrainTarget<Opts extends SelectorOptions<any>> = _ReturnObjects extends true ? unknown : Opts['returnObjects'] extends true ? unknown : $IsResourcesDefined extends false ? unknown : string; type ProcessReturnValue<Target, DefaultValue> = $Turtles extends Target ? string : [DefaultValue] extends [never] ? Target : unknown extends DefaultValue ? Target : Target | DefaultValue; type PickNamespaces<T, K extends keyof any> = { [P in K as P extends keyof T ? P : never]: T[P & keyof T]; }; /** Extracts the sub-tree returned by a selector function used as keyPrefix. */ type SelectorReturnSource<KPrefix, Fallback> = KPrefix extends (...args: any[]) => infer R ? R extends object ? R : Fallback : Fallback; type GetSource<Ns extends Namespace, KPrefix, Res = NsResource<Ns>> = KPrefix extends ( ...args: any[] ) => any ? SelectorReturnSource<KPrefix, Res> : KPrefix extends keyof Res ? Res[KPrefix] : undefined extends KPrefix ? Res : ApplyKeyPrefix<[Res], KPrefix>; type Select<T, Context> = $IsResourcesDefined extends false ? $Turtles : [_EnableSelector] extends ['optimize'] ? T : FilterKeys<T, Context>; type _HasContextVariant<T, K extends string, Context> = [ keyof T & ( | `${K}${_ContextSeparator}${Context & string}` | `${K}${_ContextSeparator}${Context & string}${_PluralSeparator}${PluralSuffix}` ), ] extends [never] ? false : true; /** Checks whether key K has **any** context variant in T (excluding pure plural suffixes). */ type _IsContextualKey<T, K extends string> = [ Exclude< keyof T & `${K}${_ContextSeparator}${string}`, | `${K}${_PluralSeparator}${PluralSuffix}` | `${K}${_PluralSeparator}ordinal${_PluralSeparator}${PluralSuffix}` >, ] extends [never] ? false : true; type FilterKeys<T, Context> = never | T extends readonly any[] ? { [I in keyof T]: FilterKeys<T[I], Context> } : $Prune< { // Mapped type 1: object-valued keys (recurse) + plain leaf keys (non-plural, non-context) [K in keyof T as T[K] extends object ? K : [Context] extends [string] ? K extends | `${string}${_ContextSeparator}${Context}` | `${string}${_ContextSeparator}${Context}${_PluralSeparator}${PluralSuffix}` ? never // context keys handled by mapped type 3 : K extends `${string}${_PluralSeparator}${PluralSuffix}` ? never // plural keys handled by mapped type 2 : K extends string ? _HasContextVariant<T, K, Context> extends true ? never // context variant exists, drop base key (type 3 handles it) : _IsContextualKey<T, K> extends true ? never // key has context variants but not for this context : K // no context variants at all, keep base key : K : K extends `${string}${_PluralSeparator}${PluralSuffix}` ? never : K]: T[K] extends object ? FilterKeys<T[K], Context> : T[K]; } & { // Mapped type 2: plural collapsing (active regardless of context) [K in keyof T as T[K] extends object ? never : [Context] extends [string] ? K extends | `${string}${_ContextSeparator}${Context}` | `${string}${_ContextSeparator}${Context}${_PluralSeparator}${PluralSuffix}` ? never // context keys handled by mapped type 3 : K extends | `${infer Prefix}${_PluralSeparator}${PluralSuffix}` | `${infer Prefix}${_PluralSeparator}ordinal${_PluralSeparator}${PluralSuffix}` ? Prefix : never : K extends | `${infer Prefix}${_PluralSeparator}${PluralSuffix}` | `${infer Prefix}${_PluralSeparator}ordinal${_PluralSeparator}${PluralSuffix}` ? Prefix : never]: T[K] extends object ? FilterKeys<T[K], Context> : PluralValue<T[K] & string>; } & { // Mapped type 3: context key collapsing [K in keyof T as T[K] extends object ? never : [Context] extends [string] ? K extends | `${infer Prefix}${_ContextSeparator}${Context}` | `${infer Prefix}${_ContextSeparator}${Context}${_PluralSeparator}${PluralSuffix}` ? Prefix : never : never]: T[K] extends object ? FilterKeys<T[K], Context> : T[K]; } >;