react-i18next & i18next 둘의 관계
i18n 구현시 대표되는 framework인 i18next 생태계에 대해 분석해보자.
react-i18next & i18next 둘의 관계
i18n 구현시 대표되는 framework인 i18next 생태계에 대해 분석해보자.
Why “react”?
i18next는, react와 독립돼 어느 생태계(ex vanillaJS)에서 동작하는 로직을 담고 있다. 따라서, react-i18next가 필요하다.
i18next 사용시, 다음과 같이 사용한다.
export const MyComponent = () => {
const {t} = useTranslation();
return <p>{t("hi")}</p>
}
왜 이 “훅"이 필요한 것일까?
- 리액트 컴포넌트 트리와 호환되는 i18n 인스턴스
const { i18n: i18nFromProps } = props;
const { i18n: i18nFromContext, defaultNS: defaultNSFromContext } =
useContext(I18nContext) || {};
const i18n = i18nFromProps || i18nFromContext || getI18n();
기본적인 설정으론 geti18n() 를 통해 싱글톤인스턴스를 사용하나, context와 props(사실 hook이므로 params가 적당하겠다.)를 통해 i18n 인스턴스를 주입하는 것을 허용하기 위함이다.
- 최적화
const i18nOptions = useMemo(
() => ({ ...getDefaults(), ...i18n?.options?.react, ...props }),
[i18n, props],
);
리액트 컴포넌트 생명주기상 발생하는 병목을 해소하기 위해 일부 로직에서 useCallback , useMemo 를 사용한다.
- suspense 연동
if (i18n && useSuspense && !ready) {
throw new Promise((resolve) => {
const onLoaded = () => resolve();
if (props.lng) {
loadLanguages(i18n, props.lng, namespaces, onLoaded);
} else {
loadNamespaces(i18n, namespaces, onLoaded);
}
});
}
옵션에 따라 Promise를 throw하는 로직을 만들어 Suspense와 호환한다.
반대로, useSuspense를 disable했을 땐
useEffect(() => {
if (i18n && !ready && !useSuspense) {
const onLoaded = () => setLoadCount((c) => c + 1);
if (props.lng) {
loadLanguages(i18n, props.lng, namespaces, onLoaded);
} else {
loadNamespaces(i18n, namespaces, onLoaded);
}
}
}, [i18n, props.lng, namespaces, ready, useSuspense, loadCount]);
useEffect내에서 이를 처리해 loadLanguages(loadNamespaces)로직을 리액트 생명주기와 동기화한다.
- i18next의 내부 상태를 react와 동기화
무엇보다, i18next는 react 밖의 로직이므로, 이를 react render cycle과 연동시켜야한다. useSyncExternalStore은 외부 store를 react 세계로 끌어온다.
const { t, ready } = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
i18next의 역할에 대해선 추후 살펴보자.
- 그 외
이외 많은 역할이 있으나, 내 이해력과 시간으로 인해 남겨두겠다. 소스코드는 여기서 확인할 수 있다.
Core Responsibilities
그럼, 코어 로직을 담는 i18next가 뭐하는 것일까?
useAPI를 통한 플러그인 연동
use(module) {
if (!module) throw new Error('You are passing an undefined module! Please check the object you are passing to i18next.use()')
if (!module.type) throw new Error('You are passing a wrong module! Please check the object you are passing to i18next.use()')
if (module.type === 'backend') {
this.modules.backend = module;
}
if (module.type === 'logger' || (module.log && module.warn && module.error)) {
this.modules.logger = module;
}
if (module.type === 'languageDetector') {
this.modules.languageDetector = module;
}
if (module.type === 'i18nFormat') {
this.modules.i18nFormat = module;
}
if (module.type === 'postProcessor') {
postProcessor.addPostProcessor(module);
}
if (module.type === 'formatter') {
this.modules.formatter = module;
}
if (module.type === '3rdParty') {
this.modules.external.push(module);
}
return this;
}
- 인스턴스 초기화
설정 옵션으로 i18next 인스턴스를 초기화한다.
- 리소스 관리
this.store = new ResourceStore(this.options.resources, this.options);
loadNamespaces, loadLanguages와 같은 API를 제공하고,
init 메서드의 option으로 설정한 resource를 ResourceStore에 등록한다.
- 번역 인터페이스 제공
react와 독립적인 t, getFixedT function을 제공한다.
- 이벤트
changeLanguages와 같은 이벤트 API를 제공한다.
- 그외
결론
- i18n 로직 자체는 i18next의 API로 제공된다.
- i18next는 내부 자체 스토어로 동작한다.
- i18next의 로직과 스토어를 react 컴포넌트 트리 & 생명주기와 연동하며, 최적화를 할 필요가 존재해 react-i18next를 사용한다.
메타데이터
- post_id
- 8dff2ec23943
- slug
- react-i18next-i18next-둘의-관계-8dff2ec23943
- url
- https://medium.com/@nayounsang722/react-i18next-i18next-%EB%91%98%EC%9D%98-%EA%B4%80%EA%B3%84-8dff2ec23943
- canonical_url
- https://medium.com/@nayounsang722/react-i18next-i18next-%EB%91%98%EC%9D%98-%EA%B4%80%EA%B3%84-8dff2ec23943
- author_url
- https://medium.com/@nayounsang722
- status
- ok
- fetched_at
- 2026-07-11 17:44:30