J'ai essentiellement 2 objets. Selon la clé sélectionnée, je veux que la deuxième partie de ma chaîne soit une clé de la valeur de la clé sélectionnée dans l'objet. Dans l'exemple, après le . dans t("common."), seul "test" | "test2" devrait être autorisé.

const common = {
  "test": "Test",
  "test2": "Test2"
}

const greetings = {
  "hello": "Hello"
}

export type I18nMap = Record<typeof locales[number], I18nNamespace>;
export interface I18nNamespace {
    common: typeof common;
    greetings: typeof greetings;
}
export const locales = (["en", "nl"] as const);

type Interpolation = Record<string, string | number>

export function useTranslation<
  T extends keyof I18nNamespace,
  U extends T extends T ? keyof I18nNamespace[T] : never,
  V extends `${T}.${U}`
>(namespace: T | T[]): {t: (key: V, interpolation?: Interpolation) => void} {
  // ...
}

const { t } = useTranslation(["common", "greetings"])

// Only allow common.test | common.test2
t("common.")

Lien vers Playground

0
user14315589 12 nov. 2020 à 13:01

1 réponse

Meilleure réponse

Essayez d'utiliser ce type :

type GetI18nKeys<T extends keyof I18nNamespace> = T extends unknown
  ? `${T}.${Extract<keyof I18nNamespace[T], string>}`
  : never;

Cela distribue T et crée un type de chaîne basé sur T et keyof I18nNamespace[T]. Sans le Extract<..., string>, TypeScript se plaindrait car les clés de propriété peuvent être un symbol, qui n'est pas assignable aux types qui peuvent être dans un modèle littéral (string | number | bigint | boolean).

Vous n'avez pas non plus besoin des paramètres de type U et V dans cette situation.

export function useTranslation<T extends keyof I18nNamespace>(
  namespace: T | T[]
): {t: (key: GetI18nKeys<T>, interpolation?: Interpolation) => void} {
  // ...
}

const { t } = useTranslation(["common", "greetings"])

// Works
t('common.test')
t('common.test2')
t('greetings.hello')

// Fails
t('common.hello')
t('greetngs.test')

Lien aire de jeux

0
cherryblossom 12 nov. 2020 à 11:26