Nextjs 다국어 처리

라이브러리 선정하기

  1. 검색 nextjs 13 app router를 구성할때 다국어 지원 라이브러리로 검색을 하였고 두가지 정도의 라이브러리를 뽑았다
  1. NPM Trend 비교

1번에서 선정한 이 두가지 npm trend를 봤을때 nextjs가 pages router일때는 next-18next가 매우 높았지만 next-intl이 점점 따라가는 추세처럼 보였다.

  1. 최종 선정 i18next의 공식 페이지 https://next.i18next.com/ 에서 https://locize.com/blog/next-app-dir-i18n/ 를 보면 i18next라이브러리 자체가 nextjs를 위한 라이브러리가 아니기때문에 기존에는 next에서 사용할때에 SSR이나 SSG를 사용하기 위해서 i18next및 여러 라이브러리를 사용하여 직접 세팅을 하여 구성을 한다. (hook, middleware 등)

next-18next의 참고 코드 : https://github.com/hotsunchip/nextjs-app-router

복잡한 서비스를 구성을 하는 거면 이 라이브러리를 사용했겠지만, 그렇지 않기 때문에 next전용 라이브러리로 개발되고 13버전부터 변경된 app-router를 지원하며 초기 세팅이 편한 next-intl을 사용하기로 했다.



next-intl 사용하기

  1. 설치

    pnpm isntall next-intl
    
  2. i18n.ts

    i18n.ts
    import { notFound } from 'next/navigation';
    import { getRequestConfig } from 'next-intl/server';
    
    const locales = ['ko', 'en'];
    
    export default getRequestConfig(async ({ locale }) => {
      if (!locales.includes(locale as any)) notFound();
    
      return {
        messages: (await import(`../../locale/${locale}.json`)).default,
      };
    });
    
    
  3. next.config.mjs

    next.config.mjs
    import createNextIntlPlugin from 'next-intl/plugin';
    
    const withNextIntl = createNextIntlPlugin('./src/utils/server/i18n.ts');
    
    /** @type {import('next').NextConfig} */
    const nextConfig = {};
    
    export default withNextIntl(nextConfig);
    

    → i18n 세팅 파일 경로를 지정해준다.

  4. locale 파일생성

    • i18n.ts에서 지정한 [local].json 형식으로 만든다.
  5. middleware.ts

    middleware.ts
    import createMiddleware from 'next-intl/middleware';
    
    export default createMiddleware({
      locales: ['ko', 'en'],
      defaultLocale: 'ko',
    });
    
    export const config = {
      matcher: ['/', '/(de|en)/:path*']
    };
    

    matcher는 항상 locale경로로 이동하게 해준다.

    이때 matcher: ['/((?!api|_next/static|_next/image|assets/imgs|favicon.ico).*)'] 로설정하면 next/image의 경우 image 파일을 ssr로 최적화해서 화면에 보여주어서 경로가 _next/image로 보내주게 되는데 이때는 locale경로를 사용하지 않고, api또한 마찬가지로 이때에는 local경로를 사용하지 않게 한다.

    같은 이유로 이미지 파일이 있는 경로인 assets/imgs도 추가한다.

  6. router 세팅

    ├── app
         ├── [locale]
         │   ├── layout.tsx
         │   └── page.tsx
         └── api
    

    기존 파일들을 다이나믹 라우팅으로 [locale]로 만들어서 넣는다.

  7. provider

    • Root layout.tsx : server 컴포넌트

      layout.tsx
      import { Providers } from '@/system';
      import '@/styles/globals.css';
      import { getMessages } from 'next-intl/server';
      
      export default async function RootLayout({
        children,
        params: { locale },
      }: Readonly<{
        children: React.ReactNode;
        params: { locale: string };
      }>) {
        const messages = await getMessages();
      
        return (
          <html lang={locale}>
            <body>
              <Providers locale={locale} messages={messages}>
                {children}
              </Providers>
            </body>
          </html>
        );
      }
      
      
    • providers.tsx : client 컴포넌트

      providers.tsx
      'use client';
      import { NextIntlClientProvider, useMessages } from 'next-intl';
      
      interface ProvidersProps {
        children: React.ReactNode;
        locale: string;
        messages: ReturnType<typeof useMessages>;
      }
      
      export function Providers({ children, locale, messages }: ProvidersProps) {
        return (
          <NextIntlClientProvider locale={locale} messages={messages}>
            {children}
          </NextIntlClientProvider>
        );
      }
      
      
  8. 사용하기

    • locale 파일

      ko.json
      {
        "home": {
          "title": "i18n 예제"
        }
      }
      
      en.json
      {
        "home": {
          "title": "i18n Example"
        }
      }
      
    page.tsx
    import { useTranslations } from 'next-intl';
    
    export interface HomeProps {}
    
    export const Home = ({}: HomeProps) => {
      const { data: session, status } = useSession();
    
      const t = useTranslations('home');
      console.log('t', t('title'));
    
      return ...
    }
    

    다음과 같이 출력되게 된다.

    • localhost:3000/ko : “i18n 예제”
    • localhost:3000/en : “i18n Example”