你怎么能只允许有效路径到模板文字类型中的嵌套对象?

How can you only allow valid paths to a nested object in a template literal type?

我基本上有2个对象。根据所选键,我希望字符串的第二部分成为对象中所选键值的键。在示例中,在 t("common.") 中的 . 之后,只应允许 "test" | "test2"

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.")

Link to Playground

尝试使用这种类型:

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

这分发了T,并在Tkeyof I18nNamespace[T]的基础上做了一个字符串类型。如果没有 Extract<..., string>,TypeScript 会抱怨,因为 属性 键可以是 symbol,它不能分配给模板文字中的类型(string | number | bigint | boolean)。

在这种情况下,您也不需要 UV 类型参数。

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')

Playground link