다국어 설정 변경작업

개요

다국적 기업의 웹페이지는 여러 언어로 되어 있다. 한개의 언어를 고정으로 단순하게 string을 넣고 있다면 다른 언어를 지원할 때 같은 페이지를 또 만들어야하는 문제가 발생한다. 이 문제를 해결하기 위해 다국어 지원을 위한 라이브러리가 있다.

  1. i18n : 일반 react 프로젝트
  2. next-intl : nextjs 프로젝트

위와같은 라이브러리로 지원된다. 위 라이브러리의 중 Nextjs 의 다국어 프로젝트 설정을 알아보자

코드

next.config.js

다국어 모듈을 적용하기 해서 플러그인을 선 적용해준다.

const createNextIntlPlugin = require('next-intl/plugin');
const withNextIntl = createNextIntlPlugin();
/** @type {import('next').NextConfig} */

const nextConfig = {
    // 기존 설정들 생략...
};

module.exports = withNextIntl(nextConfig); 

src/i18n/request.ts

어떤 설정을 가져올지 정할 수 있다. cookie 및 header에서 설정이 가능하다,

가장 간단한 방법은 경로로 한번 감싸는 것이다. origin/ko,/~ origin/en/~ 같이 변경하면 좋지만 내 블로그 글들은 어차피 다 한글이기 때문에 연습삼아 적용한 것이고 url까지 나누진 않도록 하겠다.

import {getRequestConfig} from 'next-intl/server';
import {hasLocale} from 'next-intl';
import {routing} from './routing';

// messages import (정적 import)
import ko_main_menu from './messages/ko/main-menu.json';
import en_main_menu from './messages/en/main-menu.json';
import ko_note_edit from './messages/ko/note-edit.json';
import en_note_edit from './messages/en/note-edit.json';

// messages 객체로 통합
const messages: Record<string, Record<string, any>> = {
  ko: {
    main_menu: ko_main_menu,
    note_edit: ko_note_edit,
  },
  en: {
    main_menu: en_main_menu,
    note_edit: en_note_edit,
  },
};

export default getRequestConfig(async (param) => {
  // Typically corresponds to the `[locale]` segment
  const requested = await param.requestLocale;
  const locale = hasLocale(routing.locales, requested)
    ? requested
    : routing.defaultLocale;
  return {
    locale,
    messages: messages[locale] || messages[routing.defaultLocale],
  };
});

src/i18n/navigation.ts

Link나 redirect 같은 부분에서 locale 을 사용해서 움직일 수 있도록 하기 위해서 next-intl에서 제공하는 Link, redirect를 사용한다.

import {createNavigation} from 'next-intl/navigation';
import {routing} from './routing';
 
// Lightweight wrappers around Next.js' navigation
// APIs that consider the routing configuration
export const {Link, redirect, usePathname, useRouter, getPathname} =
  createNavigation(routing);

실사용 component

import { useTranslations } from 'next-intl';
import { Link } from '@/i18n/navigation';
import { MenuItem } from '@/types/menu';
import { DrawerClose } from './ui/drawer';

export function SidebarMenu({ menu }: { menu: MenuItem[] }) {
  const t = useTranslations('main_menu');
  return (
    <ul className="flex flex-col gap-2 p-4">
      {menu.map((item) => (
        <li key={item.href}>
          <DrawerClose asChild>
            <Link
              href={item.href}
              className="block rounded px-3 py-2 hover:bg-gray-200 dark:hover:bg-gray-700"
            >
              {item.icon}
              {t(item.labelKey)}
            </Link>
          </DrawerClose>
          {item.children && item.children.length > 0 && (
            <SidebarMenu menu={item.children} />
          )}
        </li>
      ))}
    </ul>
  );
}

언어저장용 messages/ko/main-menu.json

{
    "title": "메인 메뉴",
    "dashboard": "대시보드",
    "blog": "블로그",
    "game": "게임",
    "note": "노트",
    "rag": "RAG",
    "about": "소개",
    "blogResources": "코딩자료",
    "blogTest": "test",
    "boardgame": "보드게임",
    "mincraft": "미니크래프트",
    "services": "서비스",
    "contact": "연락처",
    "sign-in": "로그인",
    "sign-up": "회원가입"
}

참고사항

1. next-intl의 Link asChild문제

next-intl의 Link는 serverComponent에서 사용시 asChild가 적용되지 않는 문제가 있을 수 있다.

// 작동안함.
import { Link } from '@/i18n/navigation';

<Button asChild>
    <Link href="/">
        {t('sign-in')}
    </Link>
</Button>
  • scadn ui 에서는 다음과 같이 button class 에 스타일을 적용해서 같은 스타일을 적용할 수 있다.
<Link
    href="/sign-up"
    className={buttonVariants({ variant: 'default', size: 'sm' })}> 
    {t('sign-up')}
</Link>