i18n과 typescript
Intro
현재 참여하고 있는 프로젝트에서 typescript와 i18next을 사용하는데, i18n JSON 파일의 type check를 강하게 적용했던 경험을 정리하고자 한다.
About I18n
먼저 i18n을 어떤식으로 활용하고 있었는지 소개하자면 다음과 같다.
i18n은 같은 화면에서 여러 언어로 된 Text를 보여주기 위해 사용하는 것으로, 하나의 key, 여러개의 value를 가지고 언어에 맞게 보여주는 것을 의미한다.
const i18nJSON = {
'simple-example': 'This is example text',
'values-example': 'I need to show {0} value',
'line-break-example': 'Hello. \n I am FE developer'
} as const
첫번째로 위와 같은 Key-Value 형태의 객체가 존재한다. 객체는 JSON이 될 수도 있고, typescript의 객체가 될 수도 있다.
import tFunction from 'utils';
tFunction('simple-example') // This is example text
tFunction('values-example', [15]) // I need to show 15 value
tFunction('line-break-example') // Hello <br /> I am FE developer
두번째로 tFunction
를 사용해서 i18n key를 집어 넣어서 key에 맞는 string을 가져오며, 케이스에 따라서는 values-example
과 같이 변수를 넣어 Text별로 맞는 value를 포함한 string을 리턴하는 경우도 있다.
마지막 line-break-example
에서는 \n
줄바꿈 문자를 인식하여 <br/>
태그로 변환해서 React에서 줄바꿈을 할 수 있도록 사용하고 있다.
본 article은 i18n을 적용하기 위한 방법으로 tFunction
이라는 함수를 가져다 쓴다.
JS로직이 아닌, Type 레벨에서의 내용을 주로 다루기 때문에 실제 내부 로직이 어떻게 되는지는 다루지 않을 예정이다.
i18next.t와 같은 함수와 같은 역할을 한다고 보면 될 것이다.
How about I18n return type
여기서, tFunction
에서 return 된 값이 어떤 값으로 나오는지 알 수 있다. 그렇다면 각 return값의 type은 어떻게 될까?
import tFunction from 'utils';
tFunction('simple-example')
// This is example text
// string
tFunction('values-example', [15])
// I need to show 15 value
// string
tFunction('line-break-example')
// Hello <br /> I am FE developer
// ReactElement
L3, L6는 string 타입이라고 보아도 무방하다. 하지만 L9에서는 i18n text에 \n
가 있기 때문에 ReactElement가 리턴된다.
ReactElement
로 리턴하지 않고, string을 리턴하면서 줄바꿈을 지원하기 위해서는
dangerouslySetInnerHTML를 사용해야한다.
하지만 여러 한계가 있으므로 string 대신에 ReactElement로 리턴한다.
만약 values-example
의 변수로 15
가 아니라 a tag와 같은 JSX를 넣는다면, values-example
의 리턴 타입 또한 달라진다.
import tFunction from 'utils';
tFunction('values-example', [15])
// I need to show 15 value
// string
tFunction('values-example', [<a href="/about">more</a>])
// I need to show <a href="/about">more</a> value
// ReactElement
위와 같이 같은 values-example
I18n key를 사용하더라도 values로 어떤 값이 오느냐에 따라서 타입이 달라져야한다.
a tag
또한 <br/>
과 마찬가지로 string이 아니라 ReactElement인 컴포넌트로 리턴해야 하기 때문이다.
What is matter?
자, i18n Text 타입이 상황에 따라 string, ReactElement로 달라진다는 것을 알았다. 그렇다면 어떤 것이 문제일까?
tFunction
은 위에서 이야기 한 것과 같이 똑똑하게 타입을 추론해주지 않는다는 점이다.
여기서 type이 적절히 추론되지 않는다면 어떤 문제가 있을까?
<input
type='input'
placeholder={tFunction('line-break-example')} // type error
/>
여러가지 경우가 있겠지만, 대표적으로는 HTML tag를 사용할 때 string으로 정의 된 attribute에 string이 아닌 i18n 값이 포함 될 수 있다. 이러면, placeholder에 xlt text 대신 [object Object]
가 보이게 된다.
물론, placeholder에 a tag가 들어가거나 \n 같은 줄바꿈이 들어가는 케이스가 명백히 이상하다. 하지만 i18n key를 잘못 사용하는 경우도 있고, i18n text가 잘못 등록 된 경우도 있을 수 있다.
즉, typescript를 사용하는데 타입이 제대로 추론 되지 않는 다는 점에 있어서 위와 같은 문제점이 있고 또 다른 문제를 야기 할 수 있다.
이번 article에서는 i18n text의 format 별로 타입을 정의하는 방법에 대해서 이야기 해 볼 예정이다. i18n을 예로 들었지만 기본적으로는 typescript Template Literal Types과 관련된 이야기이다.
- typescript 4.1+ 에 대한 전반적인 지식
- Template Literal Types에 대한 관심
- i18n system 사용 경험 (optional)