Skip to content

Commit

Permalink
feat(types): allow key separator augmentation (#1367)
Browse files Browse the repository at this point in the history
Co-authored-by: Marcos Gomez Castillo <marcos.gomez.castillo@tecsisa.com>
  • Loading branch information
ImADrafter and Marcos Gomez Castillo committed Oct 28, 2021
1 parent b81cf8f commit fadddff
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 6 deletions.
31 changes: 30 additions & 1 deletion test/typescript/returnTypes.test.ts
@@ -1,4 +1,4 @@
import { NormalizeByTypeOptions } from 'react-i18next';
import { AppendKeys, NormalizeByTypeOptions, NormalizeReturn, TFuncReturn } from 'react-i18next';

// Test cases for TypeOptions['returnNull']: true
type ReturnNull = NormalizeByTypeOptions<null, { returnNull: true }>; // Returns null
Expand Down Expand Up @@ -31,3 +31,32 @@ const emptyStringValue2: ReturnEmptyString = '';

// @ts-expect-error: '"non-empty-string"' is not assignable to type '""'
const nonEmptyStringValue2: ReturnEmptyString = 'non-empty-string';

// Test cases for TypeOptions['keySeparator']: '.' (default)
type DefaultCase = AppendKeys<'namespace', 'key' | 'key2'>;
const defaultCaseExpectedResult = 'namespace.key';
const defaultCaseExpectedResult2 = 'namespace.key2';
const defaultCase: DefaultCase = defaultCaseExpectedResult;
const defaultCase2: DefaultCase = defaultCaseExpectedResult2;

// Test cases for TypeOptions['keySeparator']: '>>>' (arbitrary separator)
type ArbitrarySeparatorCase = AppendKeys<'namespace', 'key' | 'key2', '>>>'>;
const arbitrarySeparatorExpectedResult = 'namespace>>>key';
const arbitrarySeparatorExpectedResult2 = 'namespace>>>key2';
const arbitrarySeparatorCase: ArbitrarySeparatorCase = arbitrarySeparatorExpectedResult;
const arbitrarySeparatorCase2: ArbitrarySeparatorCase = arbitrarySeparatorExpectedResult2;

// Test cases for TypeOptions['keySeparator']: false (nesting not supported)
interface MockDictionary { key: { nested: 'value' }; notNested: 'value' };

type ReturnGivenKey = NormalizeReturn<MockDictionary, 'key.nested', false>;
const shouldBeGivenKey: ReturnGivenKey = 'key.nested';

type ReturnGivenKey2 = NormalizeReturn<MockDictionary, 'keyfalsenested', false>;
const shouldBeGivenKey2: ReturnGivenKey2 = 'keyfalsenested';

type ReturnValue = NormalizeReturn<MockDictionary, 'notNested', false>;
const shouldBeTranslationValue: ReturnValue = 'value';

type ReturnValue2 = NormalizeReturn<MockDictionary, 'key//nested', '//'>;
const shouldBeTranslationValue2: ReturnValue2 = 'value';
19 changes: 14 additions & 5 deletions ts4.1/index.d.ts
Expand Up @@ -54,6 +54,7 @@ type TypeOptions = MergeBy<
{
returnNull: true;
returnEmptyString: true;
keySeparator: '.';
defaultNS: 'translation';
resources: Resources;
},
Expand Down Expand Up @@ -92,8 +93,10 @@ declare module 'i18next' {
}

// Normalize single namespace
type AppendKeys<K1, K2> = `${K1 & string}.${K2 & string}`;
type AppendKeys2<K1, K2> = `${K1 & string}.${Exclude<K2, keyof any[]> & string}`;
type AppendKeys<K1, K2, S extends string = TypeOptions['keySeparator']> = `${K1 & string}${S}${K2 &
string}`;
type AppendKeys2<K1, K2, S extends string = TypeOptions['keySeparator']> = `${K1 &
string}${S}${Exclude<K2, keyof any[]> & string}`;
type Normalize2<T, K = keyof T> = K extends keyof T
? T[K] extends Record<string, any>
? T[K] extends readonly any[]
Expand Down Expand Up @@ -135,12 +138,18 @@ export type NormalizeByTypeOptions<
R = TypeOptionsFallback<TranslationValue, Options['returnEmptyString'], ''>
> = TypeOptionsFallback<R, Options['returnNull'], null>;

type NormalizeReturn<T, V> = V extends `${infer K}.${infer R}`
type NormalizeReturn<
T,
V,
S extends string | false = TypeOptions['keySeparator']
> = V extends keyof T
? NormalizeByTypeOptions<T[V]>
: S extends false
? V
: V extends `${infer K}${S}${infer R}`
? K extends keyof T
? NormalizeReturn<T[K], R>
: never
: V extends keyof T
? NormalizeByTypeOptions<T[V]>
: never;

type NormalizeMultiReturn<T, V> = V extends `${infer N}:${infer R}`
Expand Down

0 comments on commit fadddff

Please sign in to comment.