i18next
Version:
i18next internationalization framework
300 lines (263 loc) • 10.5 kB
TypeScript
import type {
$OmitArrayKeys,
$PreservedValue,
$Dictionary,
$SpecialObject,
$StringKeyPathToRecord,
$NoInfer,
} from './helpers.js';
import type {
TypeOptions,
Namespace,
FlatNamespace,
DefaultNamespace,
TOptions,
} from './options.js';
/** @todo consider to replace {} with Record<string, never> */
/* 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 _UnescapePrefix = TypeOptions['unescapePrefix'];
type _UnescapeSuffix = TypeOptions['unescapeSuffix'];
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 = _JSONFormat extends 'v4'
? 'zero' | 'one' | 'two' | 'few' | 'many' | 'other'
: number | 'plural';
type WithOrWithoutPlural<Key> = _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, 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;
/** ****************************************************
* 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;
type ParseInterpolationValues<Ret> =
Ret extends `${string}${_InterpolationPrefix}${infer Value}${_InterpolationSuffix}${infer Rest}`
?
| (Value extends `${infer ActualValue},${string}`
? ParseActualValue<ActualValue>
: ParseActualValue<Value>)
| ParseInterpolationValues<Rest>
: never;
type InterpolationMap<Ret> = $PreservedValue<
$StringKeyPathToRecord<ParseInterpolationValues<Ret>, unknown>,
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 TOptions> = _ReturnObjects extends true
? $SpecialObject | string
: TOpt['returnObjects'] extends true
? $SpecialObject
: string;
type DefaultTReturn<TOpt extends TOptions> = TReturnOptionalObjects<TOpt> | TReturnOptionalNull;
export type KeyWithContext<Key, TOpt extends TOptions> = TOpt['context'] extends string
? `${Key & string}${_ContextSeparator}${TOpt['context']}`
: Key;
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>
: ParseTReturn<ActualKey, Resources[$FirstNamespace<ActualNS>], TOpt>
: 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;
/** ************************
* T function declaration *
************************* */
export interface TFunction<Ns extends Namespace = DefaultNamespace, KPrefix = undefined> {
$TFunctionBrand: $IsResourcesDefined extends true ? `${$FirstNamespace<Ns>}` : never;
<
const Key extends ParseKeys<Ns, TOpt, KPrefix> | TemplateStringsArray,
const TOpt extends TOptions,
Ret extends TFunctionReturn<Ns, AppendKeyPrefix<Key, KPrefix>, TOpt>,
const ActualOptions extends TOpt & InterpolationMap<Ret> = TOpt & InterpolationMap<Ret>,
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>;
}
export type KeyPrefix<Ns extends Namespace> = ResourceKeys<true>[$FirstNamespace<Ns>] | undefined;