import type { $OmitArrayKeys, $PreservedValue, $Dictionary, $SpecialObject, $StringKeyPathToRecord, } from './helpers.js'; import type { TypeOptions, Namespace, FlatNamespace, DefaultNamespace, TOptions, } from './options.js'; /** @todo consider to replace {} with Record */ /* eslint @typescript-eslint/ban-types: ['error', { types: { "{}": false } }] */ // 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 _JSONFormat = TypeOptions['jsonFormat']; type _InterpolationPrefix = TypeOptions['interpolationPrefix']; type _InterpolationSuffix = TypeOptions['interpolationSuffix']; type $IsResourcesDefined = [keyof _Resources] extends [never] ? false : true; type $ValueIfResourcesDefined = $IsResourcesDefined extends true ? Value : Fallback; type $FirstNamespace = Ns extends readonly any[] ? Ns[0] : Ns; type Resources = $ValueIfResourcesDefined<_Resources, $Dictionary>; type PluralSuffix = _JSONFormat extends 'v4' ? 'zero' | 'one' | 'two' | 'few' | 'many' | 'other' : number | 'plural'; type WithOrWithoutPlural = _JSONFormat extends 'v4' | 'v3' ? Key extends `${infer KeyWithoutOrdinalPlural}${_PluralSeparator}ordinal${_PluralSeparator}${PluralSuffix}` ? KeyWithoutOrdinalPlural | Key : Key extends `${infer KeyWithoutPlural}${_PluralSeparator}${PluralSuffix}` ? KeyWithoutPlural | Key : Key : Key; type JoinKeys = `${K1 & string}${_KeySeparator}${K2 & string}`; type AppendNamespace = `${Ns & string}${_NsSeparator}${Keys & string}`; /** **************************************************** * Build all keys and key prefixes based on Resources * ***************************************************** */ type KeysBuilderWithReturnObjects = Key extends keyof Res ? Res[Key] extends $Dictionary | readonly unknown[] ? | JoinKeys>> | JoinKeys> : never : never; type KeysBuilderWithoutReturnObjects> = Key extends keyof Res ? Res[Key] extends $Dictionary | readonly unknown[] ? JoinKeys> : Key : never; type KeysBuilder = $IsResourcesDefined extends true ? WithReturnObjects extends true ? keyof Res | KeysBuilderWithReturnObjects : KeysBuilderWithoutReturnObjects : string; type KeysWithReturnObjects = { [Ns in FlatNamespace]: WithOrWithoutPlural>; }; type KeysWithoutReturnObjects = { [Ns in FlatNamespace]: WithOrWithoutPlural>; }; type ResourceKeys = WithReturnObjects extends true ? KeysWithReturnObjects : KeysWithoutReturnObjects; /** ********************************************************************** * Parse t function keys based on the namespace, options and key prefix * *********************************************************************** */ export type KeysByTOptions = TOpt['returnObjects'] extends true ? ResourceKeys : ResourceKeys; export type NsByTOptions = TOpt['ns'] extends Namespace ? TOpt['ns'] : Ns; type ParseKeysByKeyPrefix = KPrefix extends string ? Keys extends `${KPrefix}${_KeySeparator}${infer Key}` ? Key : never : Keys; type ParseKeysByNamespaces = Ns extends readonly (infer UnionNsps)[] ? UnionNsps extends keyof Keys ? AppendNamespace : never : never; type ParseKeysByFallbackNs = _FallbackNamespace extends false ? never : _FallbackNamespace extends (infer UnionFallbackNs extends string)[] ? Keys[UnionFallbackNs] : Keys[_FallbackNamespace & string]; type FilterKeysByContext = TOpt['context'] extends string ? Keys extends `${infer Prefix}${_ContextSeparator}${TOpt['context']}${infer Suffix}` ? `${Prefix}${Suffix}` : never : Keys; export type ParseKeys< Ns extends Namespace = DefaultNamespace, TOpt extends TOptions = {}, KPrefix = undefined, Keys extends $Dictionary = KeysByTOptions, ActualNS extends Namespace = NsByTOptions, > = $IsResourcesDefined extends true ? FilterKeysByContext< | ParseKeysByKeyPrefix], KPrefix> | ParseKeysByNamespaces | ParseKeysByFallbackNs, TOpt > : string; /** ******************************************************* * Parse t function return type and interpolation values * ******************************************************** */ type ParseInterpolationValues = Ret extends `${string}${_InterpolationPrefix}${infer Value}${_InterpolationSuffix}${infer Rest}` ? | (Value extends `${infer ActualValue},${string}` ? ActualValue : Value) | ParseInterpolationValues : never; type InterpolationMap = $PreservedValue< $StringKeyPathToRecord, unknown>, Record >; 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 = Val extends '' ? _ReturnEmptyString extends true ? '' : Key : Val extends null ? _ReturnNull extends true ? null : Key : Val; type ParseTReturn = ParseTReturnWithFallback< Key, Key extends `${infer K1}${_KeySeparator}${infer RestKey}` ? ParseTReturn : // Process plurals only if count is provided inside options TOpt['count'] extends number ? TOpt['ordinal'] extends boolean ? ParseTReturnPluralOrdinal : ParseTReturnPlural : // 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 = _ReturnObjects extends true ? $SpecialObject | string : TOpt['returnObjects'] extends true ? $SpecialObject : string; type DefaultTReturn = TReturnOptionalObjects | TReturnOptionalNull; export type KeyWithContext = TOpt['context'] extends string ? `${Key & string}${_ContextSeparator}${TOpt['context']}` : Key; export type TFunctionReturn< Ns extends Namespace, Key, TOpt extends TOptions, ActualNS extends Namespace = NsByTOptions, ActualKey = KeyWithContext, > = $IsResourcesDefined extends true ? ActualKey extends `${infer Nsp}${_NsSeparator}${infer RestKey}` ? ParseTReturn : ParseTReturn], TOpt> : DefaultTReturn; export type TFunctionDetailedResult = { /** * 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 & { count?: TOpt['count'] }; }; type TFunctionReturnOptionalDetails = TOpt['returnDetails'] extends true ? TFunctionDetailedResult : Ret; type AppendKeyPrefix = KPrefix extends string ? `${KPrefix}${_KeySeparator}${Key & string}` : Key; /** ************************ * T function declaration * ************************* */ export interface TFunction { $TFunctionBrand: $IsResourcesDefined extends true ? `${$FirstNamespace}` : never; < const Key extends ParseKeys | TemplateStringsArray, const TOpt extends TOptions, Ret extends TFunctionReturn, TOpt>, const ActualOptions extends TOpt & InterpolationMap = TOpt & InterpolationMap, >( ...args: | [key: Key | Key[], options?: ActualOptions] | [key: string | string[], options: TOpt & $Dictionary & { defaultValue: string }] | [key: string | string[], defaultValue: string, options?: TOpt & $Dictionary] ): TFunctionReturnOptionalDetails; } export type KeyPrefix = ResourceKeys[$FirstNamespace] | undefined;