TS 4.1
наконец, поддерживает поиск по типу строковых ключей и интерполяцию с помощью типы литералов шаблона.
Теперь мы можем использовать аргумент строки с точками для глубокого доступа к ключам словаря / пути к объекту:
t("footer"); // ✅ { copyright: "Some copyrights"; }
t("footer.copyright"); // ✅ "Some copyrights"
t("footer.logo"); // ❌ should trigger compile error
Давайте посмотрим 1.) на подходящий тип возвращаемого значения для функции перевода t
2.) как мы можем выдать ошибку компиляции при несовпадении ключевых аргументов и предоставить IntelliSense 3.) на примере строковой интерполяции.
1. Поиск ключа: возвращаемый тип
// returns property value from object O given property path T, otherwise never
type GetDictValue<T extends string, O> =
T extends `${infer A}.${infer B}` ?
A extends keyof O ? GetDictValue<B, O[A]> : never
: T extends keyof O ? O[T] : never
function t<P extends string>(p: P): GetDictValue<P, typeof dict> { /* impl */ }
площадка а>
2. Поиск ключа: IntelliSense и ошибки компиляции
Может быть достаточно просто вызвать ошибки компиляции на неправильных ключах:
// returns the same string literal T, if props match, else never
type CheckDictString<T extends string, O> =
T extends `${infer A}.${infer B}` ?
A extends keyof O ? `${A}.${Extract<CheckDictString<B, O[A]>, string>}` :never
: T extends keyof O ? T : never
function t<P extends string>(p: CheckDictString<P, typeof dict>)
: GetDictValue<P, typeof dict> { /* impl */ }
Продолжайте читать, если вам также нужна IntelliSense. Следующий тип будет запрашивать все возможные перестановки ключевых путей словаря, обеспечивать автозаполнение и помогать подсказками об ошибках для несовпадающих ключей:
// get all possible key paths
type DeepKeys<T> = T extends object ? {
[K in keyof T]-?: `${K & string}` | Concat<K & string, DeepKeys<T[K]>>
}[keyof T] : ""
// or: only get leaf and no intermediate key path
type DeepLeafKeys<T> = T extends object ?
{ [K in keyof T]-?: Concat<K & string, DeepKeys<T[K]>> }[keyof T] : "";
type Concat<K extends string, P extends string> =
`${K}${"" extends P ? "" : "."}${P}`
function t<P extends DeepKeys<typeof dict>>(p: P) : GetDictValue<P, typeof dict>
{ /* impl */ }
type T1 = DeepKeys<typeof dict>
// "footer" | "header" | "footer.copyright" | "header.logo" | "header.link"
type T2 = DeepLeafKeys<typeof dict>
// "footer.copyright" | "header.logo" | "header.link"
Детская площадка
Дополнительные сведения см. В разделе Typescript: deep keyof вложенного объекта.
Из-за комбинаторной сложности и в зависимости от формы объекта словаря вы можете нажать compiler ограничения глубины рекурсии. Более легкая альтернатива: предоставить IntelliSense для следующего ключевого пути постепенно на основе текущего ввода:
// T is the dictionary, S ist the next string part of the object property path
// If S does not match dict shape, return its next expected properties
type DeepKeys<T, S extends string> =
T extends object
? S extends `${infer I1}.${infer I2}`
? I1 extends keyof T
? `${I1}.${DeepKeys<T[I1], I2>}`
: keyof T & string
: S extends keyof T
? `${S}`
: keyof T & string
: ""
function t<S extends string>(p: DeepKeys<typeof dict, S>)
: GetDictValue<S, typeof dict> { /* impl */ }
t("f"); // error, suggests "footer"
t("footer"); // OK
t("footer."); // error, suggests "footer.copyright"
t("footer.copyright"); // OK
t("header.") // error, suggests "header.logo" | "header.link"
площадка
3. Интерполяция
Вот пример использования строки интерполяция.
// retrieves all variable placeholder names as tuple
type Keys<S extends string> = S extends '' ? [] :
S extends `${infer _}{{${infer B}}}${infer C}` ? [B, ...Keys<C>] : never
// substitutes placeholder variables with input values
type Interpolate<S extends string, I extends Record<Keys<S>[number], string>> =
S extends '' ? '' :
S extends `${infer A}{{${infer B}}}${infer C}` ?
`${A}${I[Extract<B, keyof I>]}${Interpolate<C, I>}`
: never
Example:
type Dict = { "key": "yeah, {{what}} is {{how}}" }
type KeysDict = Keys<Dict["key"]> // type KeysDict = ["what", "how"]
type I1 = Interpolate<Dict["key"], { what: 'i18next', how: 'great' }>;
// type I1 = "yeah, i18next is great"
function t<
K extends keyof Dict,
I extends Record<Keys<Dict[K]>[number], string>
>(k: K, args: I): Interpolate<Dict[K], I> { /* impl */ }
const ret = t('key', { what: 'i18next', how: 'great' } as const);
// const ret: "yeah, i18next is great"
площадка а>
Примечание. Все фрагменты можно использовать в сочетании с react-i18next
или независимо.
Старый ответ
(PRE TS 4.1) Есть две причины, по которым ключи со строгой типизацией невозможны в react-i18next
:
1.) TypeScript не имеет возможности оценивать динамические или вычисляемые строковые выражения, такие как 'footer.copyright'
, поэтому что footer
и copyright
могут быть определены как ключевые части в иерархии объектов переводов.
2.) useTranslation
не применяет ограничения типа к определенному вами словарь / переводы. Вместо этого функция t
содержит параметры универсального типа, по умолчанию равные string
, если они не указаны вручную.
Вот альтернативное решение, в котором используется Остальные параметры / кортежи.
Typed
t
function:
type Dictionary = string | DictionaryObject;
type DictionaryObject = { [K: string]: Dictionary };
interface TypedTFunction<D extends Dictionary> {
<K extends keyof D>(args: K): D[K];
<K extends keyof D, K1 extends keyof D[K]>(...args: [K, K1]): D[K][K1];
<K extends keyof D, K1 extends keyof D[K], K2 extends keyof D[K][K1]>(
...args: [K, K1, K2]
): D[K][K1][K2];
// ... up to a reasonable key parameters length of your choice ...
}
Typed
useTranslation
Hook:
import { useTranslation } from 'react-i18next';
type MyTranslations = {/* your concrete type*/}
// e.g. via const dict = {...}; export type MyTranslations = typeof dict
// import this hook in other modules instead of i18next useTranslation
export function useTypedTranslation(): { t: TypedTFunction<typeof dict> } {
const { t } = useTranslation();
// implementation goes here: join keys by dot (depends on your config)
// and delegate to lib t
return { t(...keys: string[]) { return t(keys.join(".")) } }
}
Import
useTypedTranslation
in other modules:
import { useTypedTranslation } from "./useTypedTranslation"
const App = () => {
const { t } = useTypedTranslation()
return <div>{t("footer", "copyright")}</div>
}
Test it:
const res1 = t("footer"); // const res1: { "copyright": string;}
const res2 = t("footer", "copyright"); // const res2: string
const res3 = t("footer", "copyright", "lala"); // error, OK
const res4 = t("lala"); // error, OK
const res5 = t("footer", "lala"); // error, OK
Вы потенциально можете Infer тех типов автоматически вместо подписи множественной перегрузки ( Детская площадка < / а>). Имейте в виду, что эти рекурсивные типы являются не рекомендуется для рабочей среды разработчиками ядра до TS 4.1.
person
ford04
schedule
09.10.2019