4. 다국어 지원 (next‐i18next) - SaekKkanDa/OppaManyColorTone GitHub Wiki

프로젝트에 다국어 기능 추가하기

📈 개요

우리 사이트 이용자들의 유입경로 확인 결과

생각보다 다양한 국가에서 우리 사이트에 접속한다는 점이 발견되었고

기존 다국어 기능의 안정성을 높여 더 많은 유저 확보를 하자는 안건이 나왔다!

Untitled (2) (이미지는 구글 애널리틱스의 인구통계 - 우선은 2순위인 미국과 다른 나라들도 포용할 수 있는 영어부터!)

기존에 “react-intl” 라는 라이브러리를 사용하고 있었지만, 몇가지 불편한 점들이 있었다.

  1. 새로고침하면 선택했던 언어 설정이 날라가 다시 한국어로 돌아오는 문제
  2. Next.js를 도입하면서 생긴 서버사이드 렌더링 문제

가 발생해 이번에 새로운 라이브러리로 교체하자는 의견이 나왔고 최종적으로는 “next-i18next”가 채택되었다!

🌐 Why “next-i18next” ?

1. 우선, Next.js와 호환성이 좋다.

어떤점에서 호환성이 좋다고 하는거냐면, Next.js는 pages directory (혹은 app directory)를 따르고, SSR (server-side rendering)과 CSR (client-side rendering)을 모두 고려해줘야 하는데, next-i18next는 간단한 셋팅만으로도 이를 지원해준다.

2. Next.js의 translation 기능성을 보완

Next.js가 internationalised routing을 지원하긴 하지만, translation에 대한 기능을 따로 갖고 있지는 않다. 그저 모든 언어 파일들을 URL로 관리하고 이를 sync해 줄 뿐이다. 이를 보완하기 위해 next-i18next는 translation 파일들을 관리하고 React components를 번역해줄 components/hooks를 관리해주고 있다.

=> Next.js의 디렉토리 구조와 SSR/CSR을 고민해야하는 프로젝트에 사용하기 적합해 보인다!

🎧 그래서 어떻게 사용하는가?

next-i18next.config.js 파일을 프로젝트 최상단에 위치하고, appWithTranslationt(translate) 함수를 컴포넌트에 사용하면 next-i18next를 사용할 형태를 갖추게 된다고 한다.

그리고나서 서버 사이드 translation을 위해 getStaticProps 혹은 getServerSideProps을 page-level 컴포넌트에서 사용해주면 된다.

🔧 Setup

1. 설치

yarn add next-i18next react-i18next i18next

2. 언어별 파일 추가

기본적으로 next-i18next는 translations 파일들을 로컬 디렉토리 구조에서 로드하고, 서버 사이드에서 로드 한다. 언어 파일들은 json 형식으로 아래 디렉토리 구조를 따르면 된다.

.
└── public
    └── locales
        ├── en
        |   └── common.json
        └── ko
            └── common.json

3. 프로젝트 셋업

next-i18next.config.js 파일을 프로젝트 최상단에 위치하는데, 이는 next-i18next에게 defaultLocale과 다른 locales를 알게 해주어, 서버에서 preload 할 수 있게 해준다.

next-i18next.config.js

/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');

/** @type {import('next-i18next').UserConfig} */
module.exports = {
  i18n: {
    locales: ['en', 'ko'], // 지원하는 언어 목록
    defaultLocale: 'en', // 기본 언어 설정
  },
  localePath: path.resolve('./public/locales'), // 번역 파일 경로
  fallbackLng: 'en', // 기본 언어 설정
  ns: ['common'], // 사용하는 namespace 목록
  defaultNS: 'common', // 기본 namespace
  debug: process.env.NODE_ENV === 'development',
};

그리고, 위에서 만들어준 설정들을 next.config.js에 넘겨준다. (localised URL routing을 가능하게 해준다.)

next.config.js

const { i18n } = require('./next-i18next.config.js');

const nextConfig = {
  // ...
  i18n,
};

module.exports = withSentryConfig(
  nextConfig,
);

아래는 프로젝트에서 번역 기능을 위해 사용하게 될 next-i18next의 세가지 함수이다.

  1. appWithTranslation
import { appWithTranslation } from 'next-i18next'

const App = ({ Component, pageProps }: AppProps) => (
  <Component {...pageProps} />
)

export default appWithTranslation(MyApp);
  1. serverSideTranslations
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'

export const getServerSideProps: GetServerSideProps = async ({ locale }) => {
  return {
    props: {
      ...(await serverSideTranslations(locale ?? 'en', ['common'])),
    },
  };
};
  1. useTranslation
import { useTranslation } from 'next-i18next'

export const Footer = () => {
  const { t } = useTranslation('common')

  return (<p>{t('description')}</p>);
}

더 자세한 사용법은 공식문서를 참고 next-i18next 공식문서

👩🏻‍💻 트러블슈팅

지금 생각하면 필요 이상으로 시간을 너무 많이 썼다는 생각이 들지만, 당시에는 자바스크립트 모듈에 대한 깊은 이해가 없었기 때문에 발생한 이슈라고 생각한다.

상황은 이러했다.

  1. “next-i18next” 라이브러리 사용을 위해 next-i18next.config.js 파일 추가, next.config.js 파일 (기존에는 next.config.mjs였음)을 수정했는데, “next-i18next” 라이브러리가 파일을 찾지 못한다는 에러 발생
  2. 파일 확장자를 .cjs 혹은 .mjs로 변경하거나 내부 코드를 수정하면 파일 문법이 맞지 않는다는 에러 발생
  3. 계속 파일 확장자와 파일 내 모듈 로드 방식을 번갈아가며 변경하였지만 두 에러가 계속 반복해서 발생함

원인은 이거였다.

package.json 파일에서 type이 "module"로 되어있는데 (ESM module), 알고보니 “next-i18next”에서는 ESM module을 지원하지 않았던 것이다.

따라서 package.json의 type을 지우고 (그럼 기본값인 CommonJS로 해석), 두 파일 모두 .js 확장자로 바꿔주었다.

자바스크립트 모듈 시스템이 기본으로 CJS로 설정된다는 것과, package.jsontype으로 이를 설정해줄 수 있다는 것을 옛날에 듣고 흘렸었는데, 이번 계기로 인해 확실히 알게 되었다. 그리고 ESM module과 CJS의 차이점에 대해 단순히 import / require 을 사용한다는 점 말고도 더 찾아보고 공부하게 되었다.

공부한 내용은 블로그에 기록 자바스크립트 모듈 | CJS와 ESM의 차이