i18nPic

Guide for Using i18n in NextJS

Background

My current project is using NextJS App Router and SSG/ISR for internationalization.
This guide explains how to set up and use i18next in this environment.

Installation Requirements

Install the following packages in your project:

  1. i18next: Core internationalization framework
  2. react-i18next: React bindings
  3. i18next-browser-languagedetector: For detecting browser language
  4. i18next-resources-to-backend: For dynamically loading translation resources
  5. react-cookie: For managing language preference cookies

Installation command:

npm install i18next react-i18next i18next-browser-languagedetector i18next-resources-to-backend react-cookie

Configuration Steps

1. Path and Folder Setup

Set up a dynamic route folder [lng] in the app folder. All other paths (folders) will be stored under this folder.

app
β”œβ”€β”€ [lng]
β”‚   └── All path folders
└── i18n
    β”œβ”€β”€ locales
    β”œβ”€β”€ client.ts  // Configuration and useTranslation function for client components
    β”œβ”€β”€ index.ts   // Configuration and useTranslation function for server components
    β”œβ”€β”€ setting.ts
    └── lang.ts    // Get current language from URL

2. Create i18n Configuration Files

Create configuration files for client-side and server-side i18n setup. (The content of these files is omitted for brevity, but includes setup for i18next, language detection, and custom useTranslation hooks.)

3. Create Translation Files

Create translation files for each language under the app/i18n/locales/ directory, for example:

  • app/i18n/locales/en/common.json
  • app/i18n/locales/zh/common.json

4. Using Translations

In Client Components:

"use client";
import { useTranslation } from "../i18n/client";
import { CurrentLang } from "../i18n/lang";

export default function ClientComponent() {
  const lang = CurrentLang(); // Get current language
  const { t } = useTranslation(lang, "common");

  return <h1>{t('welcome')}</h1>; // Use translation
}

In Server Components:

import { useTranslation } from "../i18n";

export default async function ServerComponent({ params: { lng } }) {
  const { t } = await useTranslation(lng, "common");

  return <h1>{t('welcome')}</h1>; // Use translation
}

Notes

  • Ensure i18n settings are correctly configured in next.config.js.
  • For dynamic routes, make sure to handle language settings correctly in the app/[lng] folder.
  • To get the current language setting: It can be passed down from params, or use the CurrentLang() function if it can't be passed from params.
  • Use different useTranslation import methods in Client Components and Server Components.
  • The i18next configurations for client-side and server-side are slightly different, pay attention to distinguish their usage.

Code Snippets

client.ts

"use client";

import { useEffect, useState } from "react";
import i18next from "i18next";
import {
    initReactI18next,
    useTranslation as useTranslationOrg,
} from "react-i18next";
import { useCookies } from "react-cookie";
import resourcesToBackend from "i18next-resources-to-backend";
import LanguageDetector from "i18next-browser-languagedetector";
import { getOptions, languages, cookieName } from "./setting";

// Check if the code is running on the server side
const runsOnServerSide = typeof window === "undefined";

// Initialize i18next
i18next
    .use(initReactI18next) // Passes i18n down to react-i18next
    .use(LanguageDetector) // Use language detector
    .use(
        resourcesToBackend(
            (lng: string, ns: string) => import(`./locales/${lng}/${ns}.json`)
        )
    ) // Load translations dynamically
    .init({
        ...getOptions(),
        lng: undefined, // Let detect the language on client side
        detection: {
            order: ["path", "htmlTag", "cookie", "navigator"],
        },
        preload: runsOnServerSide ? languages : [], // Preload all languages on server side
    });

// Custom useTranslation hook
export function useTranslation(lng?: string, ns?: string, options?: {}) {
    const [cookies, setCookie] = useCookies([cookieName]);
    const ret = useTranslationOrg(ns, options);
    const { i18n } = ret;

    if (runsOnServerSide && lng && i18n.resolvedLanguage !== lng) {
        i18n.changeLanguage(lng);
    } else {
        // Client-side language handling
        const [activeLng, setActiveLng] = useState(i18n.resolvedLanguage);
        
        // Update active language when resolved language changes
        useEffect(() => {
            if (activeLng === i18n.resolvedLanguage) return;
            setActiveLng(i18n.resolvedLanguage);
        }, [activeLng, i18n.resolvedLanguage]);
        
        // Change language when lng prop changes
        useEffect(() => {
            if (!lng || i18n.resolvedLanguage === lng) return;
            i18n.changeLanguage(lng);
        }, [lng, i18n]);
        
        // Update cookie when language changes
        useEffect(() => {
            if (cookies.i18next === lng) return;
            setCookie(cookieName, lng, { path: "/" });
        }, [lng, cookies.i18next, setCookie]);
    }
    return { ...ret };
}

index.ts

import { createInstance } from "i18next";
import resourcesToBackend from "i18next-resources-to-backend";
import { initReactI18next } from "react-i18next/initReactI18next";
import { getOptions } from "./setting"

// Initialize i18next instance
const initI18next = async (lng: string, ns: string) => {
  const i18nInstance = createInstance();
  await i18nInstance
    .use(initReactI18next)
    .use(
      resourcesToBackend(
        (lng: string, ns: string) => import(`./locales/${lng}/${ns}.json`)
      )
    )
    .init(getOptions(lng, ns));
  return i18nInstance;
};

// useTranslation hook for server components
export async function useTranslation(lng: string, ns: string, options = {}) {
  const i18nextInstance = await initI18next(lng, ns);
  return {
    t: i18nextInstance.getFixedT(lng, Array.isArray(ns) ? ns[0] : ns),
    i18n: i18nextInstance,
  };
}

setting.js

// Default language
export const fallbackLng = "en";

// Supported languages
export const languages = [fallbackLng, "zh-Hant"];

// Cookie name for storing language preference
export const cookieName = 'i18next'

// Default namespace
export const defaultNS = "translation";

// Function to get i18next options
export function getOptions(lng = fallbackLng, ns = defaultNS) {
    return {
        debug: true,
        supportedLngs: languages,
        fallbackLng,
        lng,
        fallbackNS: defaultNS,
        defaultNS,
        ns,
    };
}

Code Explanation

client.ts: This file sets up i18next for client-side use. It initializes i18next with various plugins, including language detection and dynamic resource loading. The custom useTranslation hook manages language changes and syncs the language preference with cookies.

index.ts: This file provides a server-side implementation of i18next. It creates a new i18next instance for each request, ensuring that server-side rendering uses the correct language.

setting.js: This file contains configuration settings for i18next, including supported languages, default language, and options for i18next initialization. These settings are used by both client-side and server-side implementations.