Skip to content

Commit

Permalink
types(TFunction): make return not inferrable and use defaultValue as …
Browse files Browse the repository at this point in the history
…return when provided (#2234)
  • Loading branch information
marcalexiei authored Sep 10, 2024
1 parent e5170ad commit 7739788
Show file tree
Hide file tree
Showing 9 changed files with 40 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ describe('i18next.t', () => {
});

it('should accept any key if default value is provided', () => {
const str: string = i18next.t('unknown-ns:unknown-key', 'default value');
assertType<string>(str);
assertType<'default value'>(i18next.t('unknown-ns:unknown-key', 'default value'));
});

it('should work with plurals', () => {
Expand Down
8 changes: 4 additions & 4 deletions test/typescript/custom-types-default-ns-as-array/t.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,12 @@ describe('t', () => {
assertType<string[]>(result);
});

it('should not throw an error when `defaultValue` is provided', () => {
it('should not throw an error when `defaultValue` is provided and value should be equal to DefaultValue', () => {
expectTypeOf(
t('foobar.barfoo', { defaultValue: 'some default value' }),
).toMatchTypeOf<unknown>();
expectTypeOf(t('new.key', { defaultValue: 'some default value' })).toEqualTypeOf<unknown>();
expectTypeOf(t('new.key', 'some default value')).toEqualTypeOf<unknown>();
).toMatchTypeOf<string>();
expectTypeOf(t('new.key', { defaultValue: 'some default value' })).toMatchTypeOf<string>();
expectTypeOf(t('new.key', 'some default value')).toMatchTypeOf<string>();
});
});

Expand Down
13 changes: 6 additions & 7 deletions test/typescript/custom-types-json-v3/i18nextT.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,17 @@ describe('i18next.t', () => {
it('should work with default value', () => {
expectTypeOf(
i18next.t('custom:bar', { defaultValue: 'some default value' }),
).toMatchTypeOf<unknown>();
expectTypeOf(i18next.t('custom:bar', 'some default value')).toMatchTypeOf<unknown>();
).toMatchTypeOf<string>();
expectTypeOf(i18next.t('custom:bar', 'some default value')).toMatchTypeOf<string>();
expectTypeOf(
i18next.t('bar', { ns: 'custom', defaultValue: 'some default value' }),
).toMatchTypeOf<unknown>();
expectTypeOf(i18next.t('bar', { defaultValue: 'some default value' })).toMatchTypeOf<unknown>();
expectTypeOf(i18next.t('bar', 'some default value')).toMatchTypeOf<unknown>();
).toMatchTypeOf<string>();
expectTypeOf(i18next.t('bar', { defaultValue: 'some default value' })).toMatchTypeOf<string>();
expectTypeOf(i18next.t('bar', 'some default value')).toMatchTypeOf<string>();
});

it('should accept any key if default value is provided', () => {
const str: string = i18next.t('unknown-ns:unknown-key', 'default value');
assertType<string>(str);
assertType<'default value'>(i18next.t('unknown-ns:unknown-key', 'default value'));
});

it('should work with plurals', () => {
Expand Down
4 changes: 2 additions & 2 deletions test/typescript/custom-types-json-v3/t.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ describe('t', () => {
expectTypeOf(
t('foobar.barfoo', { defaultValue: 'some default value' }),
).toMatchTypeOf<unknown>();
expectTypeOf(t('new.key', { defaultValue: 'some default value' })).toEqualTypeOf<unknown>();
expectTypeOf(t('new.key', 'some default value')).toEqualTypeOf<unknown>();
expectTypeOf(t('new.key', { defaultValue: 'some default value' })).toMatchTypeOf<string>();
expectTypeOf(t('new.key', 'some default value')).toMatchTypeOf<string>();
});
});

Expand Down
3 changes: 1 addition & 2 deletions test/typescript/custom-types/i18nextT.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ describe('i18next.t', () => {
});

it('should accept any key if default value is provided', () => {
const str: string = i18next.t('unknown-ns:unknown-key', 'default value');
assertType<string>(str);
assertType<'default value'>(i18next.t('unknown-ns:unknown-key', 'default value'));
});

it('should fallback for null translations with unset returnNull in config', () => {
Expand Down
8 changes: 4 additions & 4 deletions test/typescript/custom-types/t.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,12 @@ describe('t', () => {
assertType<string[]>(result);
});

it('should not throw an error when `defaultValue` is provided', () => {
it('should not throw an error when `defaultValue` is provided and value should be equal to DefaultValue', () => {
expectTypeOf(
t('foobar.barfoo', { defaultValue: 'some default value' }),
).toMatchTypeOf<unknown>();
expectTypeOf(t('new.key', { defaultValue: 'some default value' })).toEqualTypeOf<unknown>();
expectTypeOf(t('new.key', 'some default value')).toEqualTypeOf<unknown>();
).toMatchTypeOf<string>();
expectTypeOf(t('new.key', { defaultValue: 'some default value' })).toMatchTypeOf<string>();
expectTypeOf(t('new.key', 'some default value')).toMatchTypeOf<string>();
});
});

Expand Down
6 changes: 3 additions & 3 deletions test/typescript/misc/t.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,12 +188,12 @@ describe('t', () => {
});

it('`returnObjects`', () => {
expectTypeOf(t('tree', { returnObjects: true, something: 'gold' })).toEqualTypeOf<
expectTypeOf(t('tree', { returnObjects: true, something: 'gold' })).toMatchTypeOf<
object | Array<string | object>
>();
// -> { res: 'added gold' }

expectTypeOf(t('array', { returnObjects: true })).toEqualTypeOf<
expectTypeOf(t('array', { returnObjects: true })).toMatchTypeOf<
object | Array<string | object>
>();
// -> ['a', 'b', 'c']
Expand All @@ -214,7 +214,7 @@ describe('t', () => {
it('`returnObjects` + `returnDetails`', () => {
expectTypeOf(t('test', { returnObjects: true, returnDetails: true }))
.toHaveProperty('res')
.toEqualTypeOf<object | Array<string | object>>();
.toMatchTypeOf<object | Array<string | object>>();
});

it('should provide information when `returnDetails` is `true`', () => {
Expand Down
8 changes: 8 additions & 0 deletions typescript/helpers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,11 @@ type $StringKeyPathToRecordUnion<
export type $StringKeyPathToRecord<TPath extends string, TValue> = $UnionToIntersection<
$StringKeyPathToRecordUnion<TPath, TValue>
>;

/**
* We could use NoInfer typescript build-in utility,
* however this project still supports ts < 5.4.
*
* @see https://github.com/millsp/ts-toolbelt/blob/master/sources/Function/NoInfer.ts
*/
export type $NoInfer<A> = [A][A extends any ? 0 : never];
14 changes: 11 additions & 3 deletions typescript/t.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
$Dictionary,
$SpecialObject,
$StringKeyPathToRecord,
$NoInfer,
} from './helpers.js';
import type {
TypeOptions,
Expand Down Expand Up @@ -260,6 +261,12 @@ export type TFunctionDetailedResult<T = string, TOpt extends TOptions = {}> = {
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;
Expand All @@ -278,12 +285,13 @@ export interface TFunction<Ns extends Namespace = DefaultNamespace, KPrefix = un
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: string }]
| [key: string | string[], defaultValue: string, options?: TOpt & $Dictionary]
): TFunctionReturnOptionalDetails<Ret, TOpt>;
| [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;

0 comments on commit 7739788

Please sign in to comment.