회사에서 다국어 설정 라이브러리로 next-translate를 사용하고 있었다.
next-translate 는 번역 리소스 파일을 번들링하지 않고 런타임에 동적으로 로드한다는 특징이 있는데, 이러한 점을 활용하지 않고 모든 페이지에서 매번 전체 json 파일을 불러오고 있었다.
따라서 json 파일을 페이지 별로 분리하고, 페이지마다 사용하는 json만 불러오면 성능을 개선할 수 있을 거라는 생각이 들어 작업을 진행하게 되었다.
다국어 json 파일을 관리하는 방법에는 여러 가지가 있겠지만, 우리는 spreadsheet를 통해 관리하였다.
spreadsheet에서 '페이지' 라는 column을 추가하여 페이지 별로 사용하는 번역을 정리하였고, 모든 페이지에서 존재하는 Header, Footer, Modal 등에 사용되는 번역 문구는 'common' 이라는 이름으로 따로 빼 두었다.
이렇게 분리하고, i18n.js 를 다음과 같이 작성해주었다.
// i18n.js
module.exports = {
locales: ['en'],
default: 'en',
localeDetection: false, // true로 변경 시 locale을 자동으로 감지하여 ko로 설정됨
pages: { // 여기에 지정!
'*': ['common'], // '*' : 모든 페이지
'/gift': ['gift'],
'/itemList': ['itemList', 'payment'],
}
}
pages 안에 어떤 페이지에서 어떤 json 파일을 불러올지 배열 형식으로 작성해주면 된다.
여기서 지정해주지 않고 뷰단에서 useTranslation('jsonFileName')을 해도 적용되지 않기 때문에 주의해야 한다..!
위와 같이 설정을 마치고, 번역을 사용할 파일에서는 useTranslation 키워드를 사용하면 된다.
const { t } = useTranslation('jsonFileName');
...
<div>{t('key')}</div>
const jsonFileName1T = useTranslation('jsonFileName1').t;
const jsonFileName2T = useTranslation('jsonFileName2').t;
...
<div>{jsonFileName1T('key1')}</div>
<div>{jsonFileName2T('key2')}</div>
or
const { t } = useTranslation('jsonFileName'); // default로 설정
...
<div>{t('key1')}</div> // default로 지정된 json 파일을 사용할 때 (=== jsonFileName:key1)
<div>{t('jsonFileName2:key2')}</div> // 다른 json 파일을 사용할 때
성능은 개선되었지만,
추후 다른 언어가 추가되었을 때 언어를 포함한 페이지별 다국어 처리에는 적합하지 않았다.
따라서 next-i18next 라이브러리로 마이그레이션을 진행했다.
// next-i18next.config.js
module.exports = {
i18n: {
locales: ['default', 'en', 'ko'], // 언어 추가되면 여기에 추가
defaultLocale: 'default',
localeDetection: false, // true로 변경 시 locale을 자동으로 감지하여 ko로 설정됨
},
};
// next.config.js
const { i18n } = require('./next-i18next.config');
module.exports = {
...,
i18n
}
// _app.tsx
import { appWithTranslation } from 'next-i18next';
const MyApp = () => {
...
}
export default appWithTranslation(MyApp);
// middleware.ts
import { NextRequest, NextResponse } from 'next/server'
const PUBLIC_FILE = /\.(.*)$/
export async function middleware(req: NextRequest) {
if (
req.nextUrl.pathname.startsWith('/_next') ||
req.nextUrl.pathname.includes('/api/') ||
PUBLIC_FILE.test(req.nextUrl.pathname)
) {
return
}
if (req.nextUrl.locale === 'default') {
const locale = req.cookies.get('NEXT_LOCALE')?.value || 'en'
return NextResponse.redirect(
new URL(`/${locale}${req.nextUrl.pathname}${req.nextUrl.search}`, req.url)
)
}
}
// pages/index.tsx
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
...
export async function getServerSideProps(context: any) {
...
return {
props: {
isReferer,
...(await serverSideTranslations(context.locale ?? 'en', Locale.MAIN)), // 여기에 지정!
},
};
}
// public/locales/_constants/index.ts
export const Locale = {
// 각 페이지에서 로드할 json 파일을 정의
MAIN: ['common', 'main'],
}
나는 각 페이지 별로 로드 할 json 파일을 next-translate 에서와 같이 하나의 파일에서 관리하고 싶어서 위와 같이 상수 파일을 하나 만들었다.
useTranslation 을 통해 번역 사용 (next-translate와 유사)a. 한 파일에서 하나의 언어 파일만 사용할 때
const { t } = useTranslation('jsonFileName');
...
<div>{t('key')}</div>
b. 한 파일에서 두 개이상의 언어 파일을 사용할 때
const commonT = useTranslation('common').t; // common.json
const mainT = useTranslation('main').t; // main.json
...
<div>{commonT('key1')}</div>
<div>{mainT('key2')}</div>
or
const { t } = useTranslation('main'); // default로 설정
...
<div>{t('key1')}</div> // default로 지정된 json 파일을 사용할 때 (=== main:key1)
<div>{t('common:key2')}</div> // 다른 json 파일을 사용할 때
c. 번역에 실패할 경우 나타낼 문구를 설정하고 싶을 때
t('key', 'defaultValue')
// EX) gnb_daily 라는 키가 json 파일에 존재하지 않을 때
// t('gnb_daily') => gnb_daily로 노출
// t('gnb_daily', 'Daily') => Daily로 노출
.
.
이와 같이 코드를 변경해주었으며, next-i18next 특성상 URL 뒤에 /[locale] 이 붙는 형태가 된다.
(예시)
- example.com/ko
- example.com/en