From 168aaf65bc04dd87c0e4fa1a51bb95f1e72939c1 Mon Sep 17 00:00:00 2001 From: Pedro Durek Date: Fri, 17 Feb 2023 09:26:19 -0700 Subject: [PATCH 1/3] Update types to support t function redesign --- @types/TransWithoutContext.d.ts | 43 ++++++++++ @types/helpers.d.ts | 3 + .../initReactI18next.d.ts | 2 +- TransWithoutContext.d.ts | 33 -------- icu.macro.d.ts | 56 ++++++------- index.d.ts | 79 +++++++++++-------- test/typescript/TransWithoutContext.test.tsx | 2 +- .../custom-types/TransWithoutContext.test.tsx | 2 +- tsconfig.json | 6 +- 9 files changed, 120 insertions(+), 106 deletions(-) create mode 100644 @types/TransWithoutContext.d.ts create mode 100644 @types/helpers.d.ts rename initReactI18next.d.ts => @types/initReactI18next.d.ts (50%) delete mode 100644 TransWithoutContext.d.ts diff --git a/@types/TransWithoutContext.d.ts b/@types/TransWithoutContext.d.ts new file mode 100644 index 000000000..3ee90e339 --- /dev/null +++ b/@types/TransWithoutContext.d.ts @@ -0,0 +1,43 @@ +import type { + i18n, + ParseKeys, + Namespace, + TypeOptions, + TOptions, + TFunction, + KeyPrefix, +} from 'i18next'; +import * as React from 'react'; + +type _DefaultNamespace = TypeOptions['defaultNS']; + +type TransChild = React.ReactNode | Record; +export type TransProps< + Key extends ParseKeys, + Ns extends Namespace = _DefaultNamespace, + TOpt extends TOptions = {}, + KPrefix = undefined, + E = React.HTMLProps +> = E & { + children?: TransChild | readonly TransChild[]; + components?: readonly React.ReactElement[] | { readonly [tagName: string]: React.ReactElement }; + count?: number; + context?: string; + defaults?: string; + i18n?: i18n; + i18nKey?: Key | Key[]; + ns?: Ns; + parent?: string | React.ComponentType | null; // used in React.createElement if not null + tOptions?: TOpt; + values?: {}; + shouldUnescape?: boolean; + t?: TFunction; +}; + +export function Trans< + Key extends ParseKeys, + Ns extends Namespace = _DefaultNamespace, + TOpt extends TOptions = {}, + KPrefix extends KeyPrefix = undefined, + E = React.HTMLProps +>(props: TransProps): React.ReactElement; diff --git a/@types/helpers.d.ts b/@types/helpers.d.ts new file mode 100644 index 000000000..35f865029 --- /dev/null +++ b/@types/helpers.d.ts @@ -0,0 +1,3 @@ +// Internal Helpers +export type $Tuple = readonly [T?, ...T[]]; +export type $Subtract = Omit; diff --git a/initReactI18next.d.ts b/@types/initReactI18next.d.ts similarity index 50% rename from initReactI18next.d.ts rename to @types/initReactI18next.d.ts index 57ba69275..40ff85992 100644 --- a/initReactI18next.d.ts +++ b/@types/initReactI18next.d.ts @@ -1,3 +1,3 @@ -import { ThirdPartyModule } from 'i18next'; +import type { ThirdPartyModule } from 'i18next'; export const initReactI18next: ThirdPartyModule; diff --git a/TransWithoutContext.d.ts b/TransWithoutContext.d.ts deleted file mode 100644 index de326ae52..000000000 --- a/TransWithoutContext.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { i18n, TFuncKey, Namespace, TypeOptions, TFunction, KeyPrefix } from 'i18next'; -import * as React from 'react'; - -type DefaultNamespace = TypeOptions['defaultNS']; - -type TransChild = React.ReactNode | Record; -export type TransProps< - K extends TFuncKey extends infer A ? A : never, - N extends Namespace = DefaultNamespace, - TKPrefix = undefined, - E = React.HTMLProps -> = E & { - children?: TransChild | TransChild[]; - components?: readonly React.ReactElement[] | { readonly [tagName: string]: React.ReactElement }; - count?: number; - context?: string; - defaults?: string; - i18n?: i18n; - i18nKey?: K | K[]; - ns?: N; - parent?: string | React.ComponentType | null; // used in React.createElement if not null - tOptions?: {}; - values?: {}; - shouldUnescape?: boolean; - t?: TFunction; -}; - -export function Trans< - K extends TFuncKey extends infer A ? A : never, - N extends Namespace = DefaultNamespace, - TKPrefix extends KeyPrefix = undefined, - E = React.HTMLProps ->(props: TransProps): React.ReactElement; diff --git a/icu.macro.d.ts b/icu.macro.d.ts index 762a66e23..07048e73f 100644 --- a/icu.macro.d.ts +++ b/icu.macro.d.ts @@ -1,18 +1,19 @@ import React from 'react'; import { Trans } from './'; -import { Namespace, DefaultNamespace, TFuncKey, i18n } from 'i18next'; +import type { Namespace, TypeOptions, i18n, ParseKeys } from 'i18next'; export { Trans }; +type _DefaultNamespace = TypeOptions['defaultNS']; declare module 'react-i18next/icu.macro' { export interface PluralSubProps< - K extends TFuncKey extends infer A ? A : never, - N extends Namespace = DefaultNamespace + Key extends ParseKeys, + Ns extends Namespace = _DefaultNamespace > { children?: never; - i18nKey?: K; + i18nKey?: Key; i18n?: i18n; - ns?: N; + ns?: Ns; count: number; values?: {}; zero?: string | React.ReactElement; @@ -23,14 +24,10 @@ declare module 'react-i18next/icu.macro' { other: string | React.ReactElement; } - type PluralProps< - T, - K extends TFuncKey extends infer A ? A : never, - N extends Namespace = DefaultNamespace - > = { - [P in keyof T]: P extends keyof PluralSubProps + type PluralProps, Ns extends Namespace = _DefaultNamespace> = { + [P in keyof T]: P extends keyof PluralSubProps ? // support the standard properties of Plural - PluralSubProps[P] + PluralSubProps[P] : // this supports infinite $0={..} or $123={..} // technically it also supports $-1={..} and $2.3={..} but we don't need to // worry since that's invalid syntax. @@ -48,38 +45,35 @@ declare module 'react-i18next/icu.macro' { } interface SelectRequiredProps< - K extends TFuncKey extends infer A ? A : never, - N extends Namespace = DefaultNamespace + Key extends ParseKeys, + Ns extends Namespace = _DefaultNamespace > extends NoChildren { - i18nKey?: K; + i18nKey?: Key; i18n?: i18n; - ns?: N; + ns?: Ns; other: string | React.ReactElement; } // defining it this way ensures that `other` is always defined, but allows // unlimited other select types. type SelectProps< - K extends TFuncKey extends infer A ? A : never, - N extends Namespace = DefaultNamespace - > = SelectSubProps & SelectRequiredProps; + Key extends ParseKeys, + Ns extends Namespace = _DefaultNamespace + > = SelectSubProps & SelectRequiredProps; - function Plural< - T, - K extends TFuncKey extends infer A ? A : never, - N extends Namespace = DefaultNamespace - >(props: PluralProps & NoChildren): React.ReactElement; + function Plural, Ns extends Namespace = _DefaultNamespace>( + props: PluralProps & NoChildren, + ): React.ReactElement; function SelectOrdinal< T, - K extends TFuncKey extends infer A ? A : never, - N extends Namespace = DefaultNamespace - >(props: PluralProps & NoChildren): React.ReactElement; + Key extends ParseKeys, + Ns extends Namespace = _DefaultNamespace + >(props: PluralProps & NoChildren): React.ReactElement; - function Select< - K extends TFuncKey extends infer A ? A : never, - N extends Namespace = DefaultNamespace - >(props: SelectProps): React.ReactElement; + function Select, Ns extends Namespace = _DefaultNamespace>( + props: SelectProps, + ): React.ReactElement; function date(strings: TemplateStringsArray, variable: Date): string; function time(strings: TemplateStringsArray, variable: Date): string; diff --git a/index.d.ts b/index.d.ts index 7b418ef3b..1ee93529b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,17 +1,17 @@ -import i18next, { +import type { $Subtract, $Tuple } from './@types/helpers'; +import type { ReactOptions, i18n, Resource, + FlatNamespace, Namespace, TypeOptions, TFunction, KeyPrefix, } from 'i18next'; import * as React from 'react'; -export { Trans, TransProps } from './TransWithoutContext'; -export { initReactI18next } from './initReactI18next'; - -type Subtract = Omit; +export { Trans, TransProps } from './@types/TransWithoutContext'; +export { initReactI18next } from './@types/initReactI18next'; export function setDefaults(options: ReactOptions): void; export function getDefaults(): ReactOptions; @@ -48,36 +48,45 @@ declare module 'react' { } } -type DefaultNamespace = TypeOptions['defaultNS']; +type _DefaultNamespace = TypeOptions['defaultNS']; export function useSSR(initialI18nStore: Resource, initialLanguage: string): void; -export interface UseTranslationOptions { +export interface UseTranslationOptions { i18n?: i18n; useSuspense?: boolean; - keyPrefix?: TKPrefix; + keyPrefix?: KPrefix; bindI18n?: string | false; nsMode?: 'fallback' | 'default'; // other of these options might also work: https://github.com/i18next/i18next/blob/master/index.d.ts#L127 } -export type UseTranslationResponse = [ - TFunction, - i18n, - boolean, +export type UseTranslationResponse = [ + t: TFunction, + i18n: i18n, + ready: boolean, ] & { - t: TFunction; + t: TFunction; i18n: i18n; ready: boolean; }; +// Workaround to make code completion to work when suggesting namespaces. +// This is a typescript limitation when using generics with default values, +// it'll be addressed in this issue: https://github.com/microsoft/TypeScript/issues/52516 +type FallbackNs = Ns extends undefined + ? _DefaultNamespace + : Ns extends Namespace + ? Ns + : _DefaultNamespace; + export function useTranslation< - N extends Namespace = DefaultNamespace, - TKPrefix extends KeyPrefix = undefined + Ns extends FlatNamespace | $Tuple | undefined = undefined, + KPrefix extends KeyPrefix> = undefined >( - ns?: N | Readonly, - options?: UseTranslationOptions, -): UseTranslationResponse; + ns?: Ns, + options?: UseTranslationOptions, +): UseTranslationResponse, KPrefix>; // Need to see usage to improve this export function withSSR(): ( @@ -95,10 +104,10 @@ export function withSSR(): ( }; export interface WithTranslation< - N extends Namespace = DefaultNamespace, - TKPrefix extends KeyPrefix = undefined + Ns extends FlatNamespace | $Tuple | undefined = undefined, + KPrefix extends KeyPrefix> = undefined > { - t: TFunction; + t: TFunction, KPrefix>; i18n: i18n; tReady: boolean; } @@ -109,23 +118,23 @@ export interface WithTranslationProps { } export function withTranslation< - N extends Namespace = DefaultNamespace, - TKPrefix extends KeyPrefix = undefined + Ns extends FlatNamespace | $Tuple | undefined = undefined, + KPrefix extends KeyPrefix> = undefined >( - ns?: N, + ns?: Ns, options?: { withRef?: boolean; - keyPrefix?: TKPrefix; + keyPrefix?: KPrefix; }, ): < C extends React.ComponentType & WithTranslationProps>, ResolvedProps = JSX.LibraryManagedAttributes< C, - Subtract, WithTranslationProps> + $Subtract, WithTranslationProps> > >( component: C, -) => React.ComponentType> & WithTranslationProps>; +) => React.ComponentType> & WithTranslationProps>; export interface I18nextProviderProps { children?: React.ReactNode; @@ -137,25 +146,25 @@ export const I18nextProvider: React.FunctionComponent; export const I18nContext: React.Context<{ i18n: i18n }>; export interface TranslationProps< - N extends Namespace = DefaultNamespace, - TKPrefix extends KeyPrefix = undefined + Ns extends FlatNamespace | $Tuple | undefined = undefined, + KPrefix extends KeyPrefix> = undefined > { children: ( - t: TFunction, + t: TFunction, KPrefix>, options: { i18n: i18n; lng: string; }, ready: boolean, ) => React.ReactNode; - ns?: N; + ns?: Ns; i18n?: i18n; useSuspense?: boolean; - keyPrefix?: TKPrefix; + keyPrefix?: KPrefix; nsMode?: 'fallback' | 'default'; } export function Translation< - N extends Namespace = DefaultNamespace, - TKPrefix extends KeyPrefix = undefined ->(props: TranslationProps): any; + Ns extends FlatNamespace | $Tuple | undefined = undefined, + KPrefix extends KeyPrefix> = undefined +>(props: TranslationProps): any; diff --git a/test/typescript/TransWithoutContext.test.tsx b/test/typescript/TransWithoutContext.test.tsx index bb7591187..ff5e31799 100644 --- a/test/typescript/TransWithoutContext.test.tsx +++ b/test/typescript/TransWithoutContext.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { Trans } from '../../TransWithoutContext'; +import { Trans } from '../../@types/TransWithoutContext'; function basic() { return Foo; diff --git a/test/typescript/custom-types/TransWithoutContext.test.tsx b/test/typescript/custom-types/TransWithoutContext.test.tsx index cc70aac7a..495d7edf2 100644 --- a/test/typescript/custom-types/TransWithoutContext.test.tsx +++ b/test/typescript/custom-types/TransWithoutContext.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { Trans } from '../../../TransWithoutContext'; +import { Trans } from '../../../@types/TransWithoutContext'; function defaultNamespaceUsage() { return foo; diff --git a/tsconfig.json b/tsconfig.json index f23b026cf..d9250da28 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,10 +12,8 @@ "paths": { "react-i18next": ["./"] }, "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - - "skipLibCheck": true + "allowSyntheticDefaultImports": true }, - "include": ["./src/**/*", "./test/**/*"], + "include": ["./index.d.ts", "./@types/**/*", "./icu.macro.d.ts", "./test/**/*"], "exclude": ["test/typescript/nonEsModuleInterop/**/*.ts", "test/typescript/custom-types"] } From 0a430394e260e75069f38dd9ecbe6f731148916e Mon Sep 17 00:00:00 2001 From: Adriano Raiano Date: Tue, 13 Jun 2023 08:24:45 +0200 Subject: [PATCH 2/3] rearrange d.ts files and export FallbackNs --- ...utContext.d.ts => TransWithoutContext.d.ts | 4 ++-- @types/helpers.d.ts => helpers.d.ts | 0 index.d.ts | 24 +++++++++---------- ...ReactI18next.d.ts => initReactI18next.d.ts | 0 4 files changed, 13 insertions(+), 15 deletions(-) rename @types/TransWithoutContext.d.ts => TransWithoutContext.d.ts (93%) rename @types/helpers.d.ts => helpers.d.ts (100%) rename @types/initReactI18next.d.ts => initReactI18next.d.ts (100%) diff --git a/@types/TransWithoutContext.d.ts b/TransWithoutContext.d.ts similarity index 93% rename from @types/TransWithoutContext.d.ts rename to TransWithoutContext.d.ts index 3ee90e339..827c71ea3 100644 --- a/@types/TransWithoutContext.d.ts +++ b/TransWithoutContext.d.ts @@ -17,7 +17,7 @@ export type TransProps< Ns extends Namespace = _DefaultNamespace, TOpt extends TOptions = {}, KPrefix = undefined, - E = React.HTMLProps + E = React.HTMLProps, > = E & { children?: TransChild | readonly TransChild[]; components?: readonly React.ReactElement[] | { readonly [tagName: string]: React.ReactElement }; @@ -39,5 +39,5 @@ export function Trans< Ns extends Namespace = _DefaultNamespace, TOpt extends TOptions = {}, KPrefix extends KeyPrefix = undefined, - E = React.HTMLProps + E = React.HTMLProps, >(props: TransProps): React.ReactElement; diff --git a/@types/helpers.d.ts b/helpers.d.ts similarity index 100% rename from @types/helpers.d.ts rename to helpers.d.ts diff --git a/index.d.ts b/index.d.ts index 1ee93529b..c9a23993a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,4 +1,4 @@ -import type { $Subtract, $Tuple } from './@types/helpers'; +import type { $Subtract, $Tuple } from './helpers'; import type { ReactOptions, i18n, @@ -10,8 +10,8 @@ import type { KeyPrefix, } from 'i18next'; import * as React from 'react'; -export { Trans, TransProps } from './@types/TransWithoutContext'; -export { initReactI18next } from './@types/initReactI18next'; +export { Trans, TransProps } from './TransWithoutContext'; +export { initReactI18next } from './initReactI18next'; export function setDefaults(options: ReactOptions): void; export function getDefaults(): ReactOptions; @@ -74,7 +74,7 @@ export type UseTranslationResponse = [ // Workaround to make code completion to work when suggesting namespaces. // This is a typescript limitation when using generics with default values, // it'll be addressed in this issue: https://github.com/microsoft/TypeScript/issues/52516 -type FallbackNs = Ns extends undefined +export type FallbackNs = Ns extends undefined ? _DefaultNamespace : Ns extends Namespace ? Ns @@ -82,16 +82,14 @@ type FallbackNs = Ns extends undefined export function useTranslation< Ns extends FlatNamespace | $Tuple | undefined = undefined, - KPrefix extends KeyPrefix> = undefined + KPrefix extends KeyPrefix> = undefined, >( ns?: Ns, options?: UseTranslationOptions, ): UseTranslationResponse, KPrefix>; // Need to see usage to improve this -export function withSSR(): ( - WrappedComponent: React.ComponentType, -) => { +export function withSSR(): (WrappedComponent: React.ComponentType) => { ({ initialI18nStore, initialLanguage, @@ -105,7 +103,7 @@ export function withSSR(): ( export interface WithTranslation< Ns extends FlatNamespace | $Tuple | undefined = undefined, - KPrefix extends KeyPrefix> = undefined + KPrefix extends KeyPrefix> = undefined, > { t: TFunction, KPrefix>; i18n: i18n; @@ -119,7 +117,7 @@ export interface WithTranslationProps { export function withTranslation< Ns extends FlatNamespace | $Tuple | undefined = undefined, - KPrefix extends KeyPrefix> = undefined + KPrefix extends KeyPrefix> = undefined, >( ns?: Ns, options?: { @@ -131,7 +129,7 @@ export function withTranslation< ResolvedProps = JSX.LibraryManagedAttributes< C, $Subtract, WithTranslationProps> - > + >, >( component: C, ) => React.ComponentType> & WithTranslationProps>; @@ -147,7 +145,7 @@ export const I18nContext: React.Context<{ i18n: i18n }>; export interface TranslationProps< Ns extends FlatNamespace | $Tuple | undefined = undefined, - KPrefix extends KeyPrefix> = undefined + KPrefix extends KeyPrefix> = undefined, > { children: ( t: TFunction, KPrefix>, @@ -166,5 +164,5 @@ export interface TranslationProps< export function Translation< Ns extends FlatNamespace | $Tuple | undefined = undefined, - KPrefix extends KeyPrefix> = undefined + KPrefix extends KeyPrefix> = undefined, >(props: TranslationProps): any; diff --git a/@types/initReactI18next.d.ts b/initReactI18next.d.ts similarity index 100% rename from @types/initReactI18next.d.ts rename to initReactI18next.d.ts From 781f6c8733cadaa11f7f1e80c93d5972ada3c367 Mon Sep 17 00:00:00 2001 From: Adriano Raiano Date: Tue, 13 Jun 2023 08:27:30 +0200 Subject: [PATCH 3/3] fix path --- test/typescript/TransWithoutContext.test.tsx | 2 +- test/typescript/custom-types/TransWithoutContext.test.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/typescript/TransWithoutContext.test.tsx b/test/typescript/TransWithoutContext.test.tsx index ff5e31799..bb7591187 100644 --- a/test/typescript/TransWithoutContext.test.tsx +++ b/test/typescript/TransWithoutContext.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { Trans } from '../../@types/TransWithoutContext'; +import { Trans } from '../../TransWithoutContext'; function basic() { return Foo; diff --git a/test/typescript/custom-types/TransWithoutContext.test.tsx b/test/typescript/custom-types/TransWithoutContext.test.tsx index 495d7edf2..cc70aac7a 100644 --- a/test/typescript/custom-types/TransWithoutContext.test.tsx +++ b/test/typescript/custom-types/TransWithoutContext.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { Trans } from '../../../@types/TransWithoutContext'; +import { Trans } from '../../../TransWithoutContext'; function defaultNamespaceUsage() { return foo;