nextjs 다국어 로컬라이제이션(localization) 지원하기 next-i18next 사용

nextjs 다국어 로컬라이제이션(localization) 지원하기 next-i18next 사용

2022-08-28
수정

기존 개인 프로젝트에서 영어와 한국어를 섞어서 사용하고 있었고, 게다가 텍스트를 그냥 코드 중간에 삽입하는 형식으로 관리하고 있었다. 사실상 관리는 아닌 것이다.

nextjs에서 사용하는 다국어 패키지를 찾아보았고, 그중 괜찮은 것을 사용하기로 하였다.

기본 설정

next-i18next 설치

npm install next-i18next

npmyarn등으로 쉽게 설치할 수 있다.

next-i18next 경로 예시

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

json형식으로 사용하는 텍스트를 관리할 수 있다. nextjs가 pagesrouter형태로 관리하다보니 기본적으로 페이지 이름별로 json파일을 만들고 공통으로 사용하는 common이나 footer을 따로 만들어서 관리하였다.

next-i18next.config.js

module.exports = {
  debug: process.env.NODE_ENV === 'development',
  i18n: {
    defaultLocale: 'ko',
    locales: ['ko', 'en'],
  },
  reloadOnPrerender: process.env.NODE_ENV === 'development',
};

next-i18next의 설정 파일이다. 프로젝트의 root경로에 두어 관리하였다.

자세한 설정은 docs를 참고하면 좋을 것 같다.

next.config.js

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

const nextConfig = {
  i18n,
};

module.exports = nextConfig;

위의 next-i18next.config.js파일에서 설정을 해준 것을 적용하려면, next.config.js파일에서 적용시켜주어야 한다.

_app.tsx

import React from 'react';
import { AppProps } from 'next/app';
import { appWithTranslation } from 'next-i18next';

function MyApp({ Component, pageProps }: AppProps) {
  return (
      <Component {...pageProps} />
  );
}

export default appWithTranslation(MyApp);

또한 적용시켜주려면 _app.tsx에서 appWithTranslation로 감싸주어야 한다.

사용 방법

공통사항

import { serverSideTranslations } from 'next-i18next/serverSideTranslations';

export async function getStaticProps({ locale }) {
  return {
    props: {
      ...(await serverSideTranslations(locale, ['common', 'footer'])),
      // Will be passed to the page component as props
    },
  };
}

nextjs에서 서버사이드로 next-i18next를 적용시키려면, 위와 같이 모든 페이지에 getStaticProps에서 텍스트 정보들을 가져와 주어야 한다. 이것이 귀찮다면, 이처럼 사용하는 고도화 방법도 있다.

페이지에서 사용하기

import Link from 'next/link'
import { useRouter } from 'next/router'

import { useTranslation, Trans } from 'next-i18next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'

import { Header } from '../components/Header'
import { Footer } from '../components/Footer'

const Homepage = () => {

  const router = useRouter()
  const { t } = useTranslation('common')

  return (
    <>
      <main>
        <Header heading={t('h1')} title={t('title')} />
        <div style={{ display: 'inline-flex', width: '90%' }}>
          <div style={{ width: '50%' }}>
            <h3 style={{ minHeight: 70 }}>{t('blog.optimized.question')}</h3>
            <p>
              <Trans i18nKey='blog.optimized.answer'>
                Then you may have a look at <a href={t('blog.optimized.link')}>this blog post</a>.
              </Trans>
            </p>
            <a href={t('blog.optimized.link')}>
              <img style={{ width: '50%' }} src='https://locize.com/blog/next-i18next/next-i18next.jpg' />
            </a>
          </div>
          <div style={{ width: '50%' }}>
            <h3 style={{ minHeight: 70 }}>{t('blog.ssg.question')}</h3>
            <p>
              <Trans i18nKey='blog.ssg.answer'>
                Then you may have a look at <a href={t('blog.ssg.link')}>this blog post</a>.
              </Trans>
            </p>
            <a href={t('blog.ssg.link')}>
              <img style={{ width: '50%' }} src='https://locize.com/blog/next-i18n-static/title.jpg' />
            </a>
          </div>
        </div>
        <hr style={{ marginTop: 20, width: '90%' }} />
        <div>
          <Link
            href='/'
            locale={router.locale === 'en' ? 'de' : 'en'}
          >
            <button>
              {t('change-locale', { changeTo: router.locale === 'en' ? 'de' : 'en' })}
            </button>
          </Link>
          <Link href='/second-page'>
            <button
              type='button'
            >
              {t('to-second-page')}
            </button>
          </Link>
        </div>
      </main>
      <Footer />
    </>
  )
}

export const getStaticProps = async ({ locale }) => ({
  props: {
    ...await serverSideTranslations(locale, ['common', 'footer']),
  },
})

export default Homepage

위처럼 json objectobject[key]처럼 사용하는 것이 아니라, t(’object.key’)의 형식으로 접근할 수 있다.

t('object.key', { returnObjects: true })

이전의 방식은 텍스트를 불러오는 방법이고, 위처럼 사용하면, object나, array형식도 불러올 수 있다.

컴포넌트에서의 사용

혹시나 컴포넌트에서 사용하려면, props로 전달해야 하는가 궁금하였다면, 걱정하지 않아도 된다.

page단에서 이미 getStaticProps로 불러왔다면, 추가 방식 없이 t(’object.key’)처럼 그냥 사용할 수 있다.

결과

 할인율, 세일, 비율, 일부 값, 퍼센트 계산기 - 개발자 유도혁

개인 프로젝트에 적용해본 결과이다. opengraph도 언어별로 설정하여서 seo에 더 좋은 결과가 있으면 좋겠다.

참고 링크

 next-i18next

 GitHub - i18next/next-i18next

KO/EN