i18next.t() の引数をTemplate Literal Types で縛る
Yuji Matsumoto / April 30 2021
i18next とは
i18nextは国際化対応を行うためのライブラリです。init 時に対応したい locale の辞書を読み込ませた上で、i18n.t()
に辞書の key を渡すと、locale に基づいた value を返却してくれます。
import i18next from 'i18next';
const en = {
address: 'address',
animals: {
cat: 'cat',
},
};
const ja = {
address: '住所',
animals: {
cat: '猫',
},
};
const resources = {
'en-US': {
translation: en,
},
ja: {
translation: ja,
},
};
i18next.init({
lng: 'ja',
resources,
});
i18next.t('address');
// => ja だと住所
// => en-US だとaddress
i18next.t('animals.cat');
// => ja だと猫
// => en-US だとcat
i18next.t()
が受けとる key の型は string|string[]
になっています。今回はここの型を実際に存在する辞書の key のみに縛り、辞書に存在しない key を指定する事故を防ぐ & IDE 上で補完が効く状態を目指します。
string の value を持った key を取り出す型を定義する
type RecursiveRecord = {
[key in string]: string | RecursiveRecord;
};
type PickKeys<T extends RecursiveRecord, K = keyof T> = K extends string
? T[K] extends string
? K
: `${K}.${PickKeys<Extract<T[K], RecursiveRecord>>}`
: never;
RecursiveRecord
はstring
かRecursiveRecord
自身を value として保持する object の型で、辞書ファイルの型を想定しています。PickKeys
はRecursiveRecord
から string の value を持った key を取り出す型です。T[K]
が string の場合は、そのまま key を返し、T[K]
がRecursiveRecord
の場合は、key と、value の object をPickKeys
に通したものを .
で join するような template literal を返します。このPickKeys
型に辞書ファイルの型を generics で渡してあげれば、i18next.t()
の引数として有効な key の union を得ることができます。
type I18nDictionary = {
address: string;
animals: {
cat: string;
dog: string;
};
};
type I18nKey = PickKeys<I18nDictionary>;
wrapper function を作成する
先程定義したI18nKey
型を使って、i18next.t()
の wrapper function を定義します。
import i18n, { TFunctionResult, TOptions } from 'i18next';
const translate = (key: I18nKey | I18nKey[], options?: TOptions) => {
return i18next.t<TFunctionResult, I18nKey>(key, options);
};
translate()
の引数を入力する際に、key
の型が補完されるようになりました。