TypeScript로 check i18n 사전을 타이핑하는 방법은 무엇입니까?
react-i18 next 사전에 기존 키 확인을 타이핑할 수 있습니까?키가 존재하지 않을 경우 TS가 컴파일 시간 동안 경고를 표시하도록 합니다.
예.
다음과 같은 사전이 있다고 가정해 보겠습니다.
{
"footer": {
"copyright": "Some copyrights"
},
"header": {
"logo": "Logo",
"link": "Link",
},
}
존재하지 않는 키를 제공하면 TS가 폭발합니다.
const { t } = useTranslation();
<span> { t('footer.copyright') } </span> // this is OK, because footer.copyright exists
<span> { t('footer.logo') } </span> // TS BOOM!! there is no footer.logo in dictionary
이 기술의 올바른 이름은 무엇입니까?저만 이런 행동을 요구하는 것은 아니라고 확신합니다.
에서 구현됩니까?react-i18next
개봉?API가 있습니까?react-i18next
어떻게든 도서관을 확장해서 그것을 가능하게 할 수 있을까요?래퍼 함수를 만드는 것을 피하고 싶습니다.
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.)을 제공하는 방법.
키 조회: 반환 유형
// 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 */ }
키 조회: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"
자세한 내용은 중첩된 개체의 유형 스크립트: 딥 키를 참조하십시오.
조합이 복잡하고 사전 개체 모양에 따라 컴파일러 재귀 깊이 한계에 도달할 수 있습니다.보다 가벼운 대안: 전류 입력에 따라 점진적으로 다음 키 경로에 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
// fix issue allowed last dot
? T[I1] extends object
? `${I1}.${DeepKeys<T[I1], I2>}`
: keyof T & string
: 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 */ }
// IntelliSense suggestions and compile errors!
// Press Ctrl+Space just outside the string, inside parentheses
t("f"); // error, suggests "footer" | "header"
t("footer"); // OK
t("footer."); // error, suggests "footer.copyright"
t("footer.copyright"); // OK
t("header.") // error, suggests "header.logo" | "header.link"
t("footer.copyright."); // error, suggests "footer.copyright"
보간
// 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
예:
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
또는 독립적으로.
구답
(PRETS 4.1) 강력한 입력 키를 사용할 수 없는 두 가지 이유가 있습니다.react-i18next
:
1.) TypeScript는 다음과 같은 동적 또는 계산 문자열 식을 평가할 수 있는 방법이 없습니다.'footer.copyright'
,하도록footer
그리고.copyright
변환 개체 계층 구조의 핵심 요소로 식별할 수 있습니다.
2.)는 정의된 사전/설명에 형식 제약 조건을 적용하지 않습니다.대신 함수t
에는 기본적으로 " " " " " "으로 설정된 유형 변수가 되어 있습니다.string
수동으로 지정하지 않은 경우.
다음은 Rest 매개 변수/튜플을 사용하는 대체 솔루션입니다.
을 입력했습니다.t
함수:
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 ...
}
을 입력했습니다.useTranslation
설정:
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(".")) } }
}
수품useTypedTranslation
다른 모듈의 경우
import { useTypedTranslation } from "./useTypedTranslation"
const App = () => {
const { t } = useTypedTranslation()
return <div>{t("footer", "copyright")}</div>
}
테스트:
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
여러 오버로드 서명(플레이그라운드) 대신 이러한 유형을 자동으로 추론할 수 있습니다.TS 4.1까지는 핵심 개발자가 생산할 때 이러한 재귀 유형을 권장하지 않습니다.
이제 React-i18next는 이를 지원합니다.공식 문서를 찾지 못했지만 소스 코드에 도움이 되는 설명이 있습니다.
당신의 번역이 다음과 같다고 가정합니다.public/locales/[locale]/translation.json
그리고 당신의 주요 언어는 영어입니다.
// src/i18n-resources.d.ts
import 'react-i18next'
declare module 'react-i18next' {
export interface Resources {
translation: typeof import('../public/locales/en/translation.json')
}
}
여러 개의 변환 파일을 사용하는 경우 네임스페이스별로 키를 지정하여 리소스 인터페이스에 모두 추가해야 합니다.
설해야합니로 설정해주세요."resolveJsonModule": true
의 신의에tsconfig.json
filejson 파일에서 .
이 동작을 수행하는 또 다른 방법은 translationKey 유형을 생성하여 useThook 및 사용자 지정 Trans 구성 요소에서 사용하는 것보다 사용하는 것입니다.
- translation.json 파일 생성
{
"PAGE_TITLE": "Product Status",
"TABLES": {
"COUNTRY": "Country",
"NO_DATA_AVAILABLE": "No price data available"
}
}
- generateTranslationKey를 사용하여 typeTranslationKey를 생성합니다.유형.js
/**
* This script generates the TranslationKey.ts types that are used from
* useT and T components
*
* to generate type run this command
*
* ```
* node src/i18n/generateTranslationTypes.js
* ```
*
* or
* ```
* npm run generate-translation-types
* ```
*/
/* eslint-disable @typescript-eslint/no-var-requires */
const translation = require("./translation.json")
const fs = require("fs")
// console.log("translation", translation)
function extractKeys(obj, keyPrefix = "", separator = ".") {
const combinedKeys = []
const keys = Object.keys(obj)
keys.forEach(key => {
if (typeof obj[key] === "string") {
if (key.includes("_plural")) {
return
}
combinedKeys.push(keyPrefix + key)
} else {
combinedKeys.push(...extractKeys(obj[key], keyPrefix + key + separator))
}
})
return combinedKeys
}
function saveTypes(types) {
const content = `// generated file by src/i18n/generateTranslationTypes.js
type TranslationKey =
${types.map(type => ` | "${type}"`).join("\n")}
`
fs.writeFile(__dirname + "/TranslationKey.ts", content, "utf8", function(
err
) {
if (err) {
// eslint-disable-next-line no-console
console.log("An error occurred while writing to File.")
// eslint-disable-next-line no-console
return console.log(err)
}
// eslint-disable-next-line no-console
console.log("file has been saved.")
})
}
const types = extractKeys(translation)
// eslint-disable-next-line no-console
console.log("types: ", types)
saveTypes(types)
- useThook 유사한 ThookTranslationKey 유형을 사용하는 Translation
import { useTranslation } from "react-i18next"
import { TOptions, StringMap } from "i18next"
function useT<TInterpolationMap extends object = StringMap>() {
const { t } = useTranslation()
return {
t(key: TranslationKey, options?: TOptions<TInterpolationMap> | string) {
return t(key, options)
},
}
}
export default useT
- T 구성 요소는 Trans 구성 요소와 유사합니다.
import React, { Fragment } from "react"
import useT from "./useT"
import { TOptions, StringMap } from "i18next"
export interface Props<TInterpolationMap extends object = StringMap> {
id: TranslationKey
options?: TOptions<TInterpolationMap> | string
tag?: keyof JSX.IntrinsicElements | typeof Fragment
}
export function T<TInterpolationMap extends object = StringMap>({
id,
options,
tag = Fragment,
}: Props<TInterpolationMap>) {
const { t } = useT()
const Wrapper = tag as "div"
return <Wrapper>{t(id, options)}</Wrapper>
}
export default T
- useT 및 T를 유형 확인된 ID와 함께 사용합니다.
const MyComponent = () => {
const { t } = useT()
return (
<div>
{ t("PAGE_TITLE", {count: 1})}
<T id="TABLES.COUNTRY" options={{count: 1}} />
</div>
)
}
훌륭한 답변 @ford04, 하지만 키와 인터폴 유형에 약간의 문제가 있습니다. 만약 당신이 이런 식으로 사용하고 문자열 끝에 변수가 없다면 인터폴은 그것을 식별하지 못할 것입니다.이 문제를 해결하려면 다음과 같은 방법으로 수행할 수 있습니다.
export type Keys<S extends string> =
S extends `${string}{{${infer B}}}${infer C}`
? C extends `${string}{{${string}}}${string}`
? [B, ...Keys<C>]
: [B]
: never;
type Interpolate<
S extends string,
I extends Record<Keys<S>[number], string>,
> = S extends ''
? ''
: S extends `${infer A}{{${infer B}}}${infer C}`
? C extends `${string}{{${string}}}${string}`
? `${A}${I[Extract<B, keyof I>]}${Interpolate<C, I>}`
: `${A}${I[Extract<B, keyof I>]}`
: never;
예를 따릅니다.놀이터
여러 json 구성에서 dts 유형 정의 파일 생성을 지원하는 CLI를 작성했습니다.드셔보세요.현재 고급 타입의 ts 4는 i18 next의 기능을 완전히 지원하지 않아서 코드 생성을 선택했습니다.
https://www.npmjs.com/package/ @liuli-slot/i18 next-slot-gen
언급URL : https://stackoverflow.com/questions/58277973/how-to-type-check-i18n-dictionaries-with-typescript
'programing' 카테고리의 다른 글
소방서:다중 조건부 where 절 (0) | 2023.06.15 |
---|---|
레일 마이그레이션을 사용하여 열을 내리는 방법 (0) | 2023.06.15 |
Oracle: 방금 삽입한 행의 시퀀스 번호를 가져오려면 어떻게 해야 합니까? (0) | 2023.06.15 |
iPad/iPhone에서 CSS 제출 버튼 이상한 렌더링 (0) | 2023.06.15 |
UIImagePickerController 오류: 렌더링되지 않은 보기를 스냅샷하면 iOS 7에서 스냅샷이 비어 있습니다. (0) | 2023.06.15 |