Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add type-safe support to keyPrefix option #1390

Merged
merged 2 commits into from Oct 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions test/typescript/custom-types/custom-types.d.ts
Expand Up @@ -10,6 +10,9 @@ declare module 'react-i18next' {
};
alternate: {
baz: 'baz';
foobar: {
barfoo: 'barfoo';
};
};
};
}
Expand Down
17 changes: 17 additions & 0 deletions test/typescript/custom-types/useTranslation.test.tsx
Expand Up @@ -38,6 +38,11 @@ function readonlyArrayNamespace() {
);
}

function keyPrefixOption() {
const [t] = useTranslation('alternate', { keyPrefix: 'foobar' });
return <>{t('barfoo')}</>;
}

function expectErrorWhenNamespaceDoesNotExist() {
// @ts-expect-error
const [t] = useTranslation('fake');
Expand All @@ -61,3 +66,15 @@ function expectErrorWhenUsingArrayNamespaceAndWrongKey() {
// @ts-expect-error
return <>{t('custom:fake')}</>;
}

function expectErrorWhenUsingWrongKeyPrefixOption() {
// @ts-expect-error
const [t] = useTranslation('alternate', { keyPrefix: 'abc' });
return <>{t('barfoo')}</>;
}

function expectErrorWhenUsingRightKeyPrefixOptionAndWrongKey() {
const [t] = useTranslation('alternate', { keyPrefix: 'foobar' });
// @ts-expect-error
return <>{t('abc')}</>;
}
49 changes: 35 additions & 14 deletions ts4.1/index.d.ts
Expand Up @@ -149,12 +149,20 @@ type NormalizeMultiReturn<T, V> = V extends `${infer N}:${infer R}`
: never
: never;

export type TFuncKey<N extends Namespace = DefaultNamespace, T = DefaultResources> = N extends
| (keyof T)[]
| Readonly<(keyof T)[]>
type KeyPrefix<N extends Namespace> = N extends keyof DefaultResources
? Fallback<string, keyof DefaultResources[N]> | undefined
: string | undefined;

export type TFuncKey<
N extends Namespace = DefaultNamespace,
TKPrefix extends KeyPrefix<N> = undefined,
T = DefaultResources
> = N extends (keyof T)[] | Readonly<(keyof T)[]>
? NormalizeMulti<T, N[number]>
: N extends keyof T
? Normalize<T[N]>
? TKPrefix extends keyof T[N]
? Normalize<T[N][TKPrefix]>
: Normalize<T[N]>
: string;

export type TFuncReturn<N, TKeys, TDefaultResult, T = DefaultResources> = N extends (keyof T)[]
Expand All @@ -163,17 +171,20 @@ export type TFuncReturn<N, TKeys, TDefaultResult, T = DefaultResources> = N exte
? NormalizeReturn<T[N], TKeys>
: Fallback<TDefaultResult>;

export interface TFunction<N extends Namespace = DefaultNamespace> {
export interface TFunction<
N extends Namespace = DefaultNamespace,
TKPrefix extends KeyPrefix<N> = undefined
> {
<
TKeys extends TFuncKey<N> | TemplateStringsArray extends infer A ? A : never,
TKeys extends TFuncKey<N, TKPrefix> | TemplateStringsArray extends infer A ? A : never,
TDefaultResult extends TFunctionResult = string,
TInterpolationMap extends object = StringMap
>(
key: TKeys | TKeys[],
options?: TOptions<TInterpolationMap> | string,
): TFuncReturn<N, TKeys, TDefaultResult>;
<
TKeys extends TFuncKey<N> | TemplateStringsArray extends infer A ? A : never,
TKeys extends TFuncKey<N, TKPrefix> | TemplateStringsArray extends infer A ? A : never,
TDefaultResult extends TFunctionResult = string,
TInterpolationMap extends object = StringMap
>(
Expand Down Expand Up @@ -208,22 +219,32 @@ export function Trans<

export function useSSR(initialI18nStore: Resource, initialLanguage: string): void;

export interface UseTranslationOptions {
export interface UseTranslationOptions<
N extends Namespace = DefaultNamespace,
TKPrefix extends KeyPrefix<N> = undefined
> {
i18n?: i18n;
useSuspense?: boolean;
keyPrefix?: string;
keyPrefix?: TKPrefix;
}

type UseTranslationResponse<N extends Namespace> = [TFunction<N>, i18n, boolean] & {
t: TFunction<N>;
type UseTranslationResponse<N extends Namespace, TKPrefix extends KeyPrefix<N>> = [
TFunction<N, TKPrefix>,
i18n,
boolean,
] & {
t: TFunction<N, TKPrefix>;
i18n: i18n;
ready: boolean;
};

export function useTranslation<N extends Namespace = DefaultNamespace>(
export function useTranslation<
N extends Namespace = DefaultNamespace,
TKPrefix extends KeyPrefix<N> = undefined
>(
ns?: N | Readonly<N>,
options?: UseTranslationOptions,
): UseTranslationResponse<N>;
options?: UseTranslationOptions<N, TKPrefix>,
): UseTranslationResponse<N, TKPrefix>;

// Need to see usage to improve this
export function withSSR(): <Props>(
Expand Down