> 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/developer-guide-en/pjt-prepare/publish-your-docs/store-front-framework-next.js/02.-coding-guide-and-essential-packages/4.-internationalization-and-i18n.md).

# 4. Internationalization and i18n

This document explains how to develop a multilingual site in a Next.js project.

It explains using middleware to extract the language, managing JSON files to manage multilingual content, and how to configure i18n and handle language switching and navigation.

***

Next.js lets you configure routing and content rendering to support multiple languages. Building a site that supports multiple locales involves translated content (localization) and internationalized routes.

## Middleware (/src/middleware.ts)

The middleware extracts the locale from the client's request and performs the appropriate redirect or rewrite accordingly. If a client makes a request to /about and the locale is set to en, the middleware redirects or rewrites this to /en/about.

Through this processing, users are redirected or rewritten to the page translated into the appropriate language, so they can effectively be served internationalized content.

{% 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)

Messages can be served locally or loaded from a remote data source. The simplest option is to add JSON files based on locale to the project.

Example JSON:

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

For multilingual JSON files, you place the JSON file in a folder for each language under the i18n folder, and to map each language to its JSON file, you specify the corresponding JSON path in the i18n file.

### /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&#x26;token=4e7f8013-08c2-42ed-bff0-776eed98bd7c" alt=""><figcaption></figcaption></figure></div>

Since the functions provided on the server side and the client side differ for using messages, in order to use a common function, we branch the logic and return the appropriate function.

{% code title="messageClient (Example)" %}

```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 %}

***

## How to Use on the Server

{% code title="Server Component (Example)" %}

```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 %}

## How to Use on the Client

{% code title="Client Component (Example)" %}

```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 provides a solution for a common Next.js navigation API that automatically handles the user's locale.

{% 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 %}

You can use Navigation to implement locale switching and page navigation. The example source below uses route and Link from the navigation configured above to switch locales and navigate pages.

{% code title="Locale Switching Example (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 %}

***
