> For the complete documentation index, see [llms.txt](https://tech.x2bee.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tech.x2bee.com/dev-guide/pjt-prepare/publish-your-docs/store-front-framewok-next.js/02./4.-internationalization-i18n.md).

# 4. Internationalization와 i18n

이 문서는 Nextjs 프로젝트에서 다국어 사이트를 개발하는 방법에 대해 설명합니다.

미들웨어를 사용하여 언어를 추출하고, 다국어 콘텐츠를 관리하기 위해 JSON 파일을 관리, i18n 설정과 언어 변경 및 이동 방법을 설명합니다.

***

Next.js는 여러 언어를 지원하기 위해 라우팅 및 콘텐츠 렌더링을 구성할 수 있게 해줍니다. 여러 locale에 대응하는 사이트를 만들기 위해서는 번역된 콘텐츠(localization)와 internationalized routes가 포함됩니다.

## Middleware (/src/middleware.ts)

미들웨어는 클라이언트의 요청에서 locale 을 추출하고, 이에 따라 적절한 redirect 또는 rewrite를 수행합니다. 만약 클라이언트가 /about에 대한 요청을 하고 locale이 en으로 설정되어 있다면, 미들웨어는 이를 /en/about으로 redirect하거나 rewrite합니다.

이러한 처리를 통해 사용자는 적절한 언어로 번역된 페이지로 redirect되거나 rewrite되어 효과적으로 국제화된 콘텐츠를 제공받을 수 있습니다.

{% code title="/src/middleware.ts" %}

```ts
import createMiddleware from 'next-intl/middleware'
import { NextRequest } from 'next/server'
import { locales } from './navigation'

/**
 * 국제화 (i18n)
 * ko-KR : Korean (Korea)
 * en-US : English (United States)
 * zh-CN : Chinese (S)
 * zh-TW : Chinese (T)
 * ja-JP : Japanese (Japan)
 */
export default async function middleware(request: NextRequest) {
  const acceptLanguage = request.headers.get('accept-language') || process.env.DEFAULT_LOCALE

  const defaultLocales = locales.filter((locale) => {
    if (acceptLanguage.startsWith(locale)) return true
    else return false
  })

  const defaultLocale = defaultLocales[0] || process.env.DEFAULT_LOCALE

  const handleI18nRouting = createMiddleware({
    locales,
    defaultLocale,
    localePrefix: process.env.LOCALE_PREFIX || ('always' as any),
    localeDetection: true
  })

  const response = handleI18nRouting(request)

  if (response.cookies.get('NEXT_LOCALE' as any)) {
    response.cookies.delete('NEXT_LOCALE' as any)
  }

  return response
}

export const config = {
  matcher: ['/((?!api|_next|.*\\..*).*)']
}
```

{% endcode %}

***

## message (/src/data/i18n)

message는 로컬에서 제공하거나 원격 데이터 소스에서 로드할 수 있습니다. 가장 간단한 옵션은 locale을 기반으로 한 JSON 파일을 프로젝트에 추가하는 것입니다.

예시 JSON:

```json
{
  "common-page": {
    "loggedIn": "Logged In",
    "loggedOut": "Logged Out",
    "login": "Sign In",
    "logout": "Log Out",
    "Settings": "Settings",
    "title": "Account"
  }
}
```

다국어 Json파일은 i18n폴더에 언어별 폴더에 json 파일을 넣으면 되며, 각 언어와 json 파일을 매핑하기 위해서는 i18n파일에 해당 json 경로를 명시해주면 됩니다.

### /src/i18n.ts

{% code title="/src/i18n.ts" %}

```ts
import { getRequestConfig } from 'next-intl/server'

export default getRequestConfig(async ({ locale }) => {
  // messages json 파일들 경로를 명시
  const combinedMessages = {
    ...(await import(`/src/data/i18n/${locale}/common.json`)),
    ...(await import(`/src/data/i18n/${locale}/display.json`)),
    ...(await import(`/src/data/i18n/${locale}/event.json`)),
    ...(await import(`/src/data/i18n/${locale}/goods.json`)),
    ...(await import(`/src/data/i18n/${locale}/member.json`)),
    ...(await import(`/src/data/i18n/${locale}/order.json`)),
    ...(await import(`/src/data/i18n/${locale}/promotion.json`)),
    ...(await import(`/src/data/i18n/${locale}/search.json`))
  }

  return { messages: combinedMessages }
})
```

{% endcode %}

### /src/data

<div align="left"><figure><img src="https://200425-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVEZx3rsZsIv89GPS3d2J%2Fuploads%2FSfJldPPtAqfDdmhgMS20%2Fimg%20(2).png?alt=media&amp;token=4e7f8013-08c2-42ed-bff0-776eed98bd7c" alt=""><figcaption></figcaption></figure></div>

message를 사용하기 위해 server와 client단에서 제공해주는 함수가 다른데 공통된 함수로 사용하기 위해 분기처리하여 해당하는 함수를 return 해 줍니다.

{% code title="messageClient (예시)" %}

```ts
import { getTranslations } from 'next-intl/server'
import { useTranslations } from 'next-intl';
import { isNull } from '@/lib/x2bee-core';

export function getMessage(namespace: string): any {
  if (isNull(namespace)) {
    throw new Error('Message Client namespace value is not null.');
  }

  const isServerComponent: () => (boolean) = () => {
    return typeof window === 'undefined' ? true : false;
  };

  // server와 client 분기처리
  if (isServerComponent()) {
    let localeTranslations = {}
    try {
      localeTranslations = getTranslations(namespace);
    } catch(error) {
      localeTranslations = useTranslations(namespace);
    }
    return localeTranslations;
  } else {
    return useTranslations(namespace);
  }
}
```

{% endcode %}

***

## Server에서 사용 방법

{% code title="Server Component (예시)" %}

```tsx
import { getMessage } from '@/lib/common/plugins/messageClient'

const TestPage = async () => {
  const t = await getMessage('account-page')
  console.log(t)

  return (
    <>
      <h1>{t('loggedIn')}</h1>
    </>
  )
}

export default TestPage
```

{% endcode %}

## Client에서 사용 방법

{% code title="Client Component (예시)" %}

```tsx
'use client'
import { getMessage } from '@/lib/common/plugins/messageClient'

const TestPage = () => {
  const t = getMessage('account-page')
  console.log(t)

  return (
    <>
      <h1>{t('loggedIn')}</h1>
    </>
  )
}

export default TestPage
```

{% endcode %}

***

## Navigation

next-intl은 사용자 locale을 자동으로 처리하는 공통 Next.js 네비게이션 API에 대한 솔루션을 제공합니다.

{% code title="/src/navigation.ts" %}

```ts
import { createLocalizedPathnamesNavigation, Pathnames } from 'next-intl/navigation'

export const locales = ['ko', 'en'] as const
export const localePrefix = 'always'

// Default export
export const pathnames = {} satisfies Pathnames<typeof locales>

export const { Link, redirect, usePathname, useRouter, getPathname } =
  createLocalizedPathnamesNavigation({
    locales,
    localePrefix,
    pathnames
  })
```

{% endcode %}

Navigation을 사용하여 locale 변경 및 페이지 이동을 구현할 수 있습니다. 아래 예시 소스는 위에 설정한 navigation에서 route와 Link를 사용하여 locale 변경 및 페이지 이동하는 소스입니다.

{% code title="Locale 변경 예시 (Client Component)" %}

```tsx
'use client'
import { getMessage } from '@/lib/common/plugins/messageClient'
// next-intl navigation 가져오기
import { Link, useRouter, usePathname } from '@/navigation'

const TestPage = () => {
  const t = getMessage('common-page')
  const pathname = usePathname()
  const router = useRouter()

  // router를 사용한 방식
  const handleChange = (event) => {
    router.replace(pathname, { locale: event.target.value })
  }

  return (
    <>
      <h1>{t('loggedIn')}</h1>

      <select onChange={handleChange}>
        <option>ko</option>
        <option>en</option>
      </select>

      {/* Link를 사용한 방식 */}
      <Link href="/goods" locale="en"> Switch to German </Link>
    </>
  )
}

export default TestPage
```

{% endcode %}

***
