Reputation: 2068
Update: thanks to https://stackoverflow.com/users/5575595/drag13 completed version can be found here: https://github.com/web-ridge/react-ridge-translations/blob/main/src/index.ts
I'm working on a translation library for React / React Native but I can't get the types to work.
https://github.com/web-ridge/react-ridge-translations
You can create a translation in the following way
// first describe which languages are allowed/required (Typescript)
type TranslationLanguages = {
nl: string
fr: string
en: string
}
// create a translation object with your translations
export default const translate = createTranslations<TranslationLanguages>({
homeScreen:{
yesText: {
nl: 'Ja',
fr: 'Oui',
be: 'Yes',
},
welcomeText: ({ firstName }: { firstName: string }) => ({
nl: `Hoi ${firstName}`,
fr: `Hello ${firstName}`,
be: `Hello ${firstName}`,
}),
}
}, {
language: 'nl',
fallback: 'en',
})
The library changes the object to the following
{
homeScreen:{
yesText: 'Ja',
welcomeText: ({ firstName }: { firstName: string }) => `Hoi ${firstName}`,
}
}
In your component you will use the types in this way
const {yesText,welcomeText} = translate.use().appScreen
It won't autocomplete the types.
Library code (simplified)
type val<T> = (...params: any[]) => T
type val1<T> = T
type Translations<T> = {
[group: string]: {
[key: string]: val<T> | val1<T>
},
}
type TranslationsObject<T> = {
translations: Translations<string>,
use: () => Translations<string>;
}
export function createTranslations<T>(t: Translations<T>): TranslationsObject<T>
How can I let Typescript understand that it needs to autocomplete?
Upvotes: 2
Views: 2099
Reputation: 5988
So, you want to allow TypeScript to define interface himself. In this case, you need to use more generics. Something like this:
type ValueOf<T> = T[keyof T];
type Translations<TGroup> = {
[group in keyof TGroup]: { [key in keyof ValueOf<TGroup>]: ValueOf<TGroup>[key] }
}
type TranslationsObject<TGroup> = {
translations: Translations<TGroup>,
use: () => Translations<TGroup>;
}
export function createTranslations<TGroup>(data: TGroup): TranslationsObject<TGroup> { }
const test = createTranslations({
homeScreen:{
yesText: {
nl: 'Ja',
fr: 'Oui',
be: 'Yes',
},
welcomeText: ({ firstName }: { firstName: string }) => ({
nl: `Hoi ${firstName}`,
fr: `Hello ${firstName}`,
be: `Hello ${firstName}`,
}),
}
});
const x = test.use().homeScreen.yesText
Type checking should work fine for now. Does this look good?
Updated with code from comment
Upvotes: 1
Reputation: 5988
Issue is here
type Translations<T> = {
[group: string]: {
[key: string]: val<T> | val1<T>
},
}
Because of the indexer declaration, TypeScript doesn't know anything about the inner structure. If you want to have more strong types, here is the option:
type val<T> = (...params: any[]) => T
type val1<T> = T
type TransltionGroup = 'homeScreen' | 'personalCabinet';
type Translations<T> = {
[group in TransltionGroup]: {
[key: string]: val<T> | val1<T>
}
}
type TranslationsObject<T> = {
translations: Translations<string>,
use: () => Translations<string>;
}
export function createTranslations<T>(t: Translations<T>): TranslationsObject<T> { }
const test = createTranslations({ homeScreen: { test: '4' }, personalCabinet: { test: '6' } });
const x = test.use().personalCabinet
And of course, you may declare the type for the subgroup
Hope this helps!
Upvotes: 1