简体   繁体   中英

Autocomplete in return type Typescript based on input object

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?

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!

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM