Back to home

A Comprehensive Guide to i18n Integration in Next.js- Framework Selection, Implementation, and Best Practices

133 min read

Introduction

Implementing internationalization (i18n) in Next.js applications is a crucial step for reaching a global audience. This guide will walk you through the process of selecting and implementing the right i18n solution, with practical examples and best practices.

Comparing i18n Frameworks

Next.js officially supports three main internationalization libraries:

1. next-i18next

  • Most popular solution based on npm downloads
  • Built on i18next and react-i18next
  • Flexible with comprehensive features
  • Supports SSR, multiple languages, and namespaces
  • Ideal for large-scale projects
  • Offers both static file-based and API-based internationalization

2. next-intl

  • Built-in Next.js solution
  • Simpler implementation
  • Based on React Intl
  • Best for smaller projects
  • Limited but focused feature set

3. next-translate

  • Similar to next-i18next
  • Simple configuration
  • Supports JSON and YML formats
  • Uses useTranslation hook
  • Good for basic translation needs

Important Note: If you're using Next.js 13+ with the app router, you should use i18next and react-i18next directly instead of next-i18next.

Implementing next-i18next

1. Installation

yarn add next-i18next react-i18next i18next

2. Configuration Setup

Create next-i18next.config.js:

const path = require('path');

module.exports = {
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'zh'],
    localePath: path.resolve('./public/locales'),
  },
};

Update next.config.js:

const { i18n } = require('./next-i18next.config');

const nextConfig = {
  i18n,
  // other configurations...
};

module.exports = nextConfig;

Translation File Structure

Organization

Place translation files in /public/locales/[language]/[namespace].json

Example structure:

public/
  locales/
    en/
      common.json
      order.json
    zh/
      common.json
      order.json

Translation File Format

{
  "components": {
    "header": {
      "title": "Welcome",
      "subtitle": "Select your language"
    }
  }
}

Creating a Language Switcher

Here's a clean implementation of a language switcher component:

import { useRouter } from 'next/router';
import { Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react';

const LANG_MAP = {
  en: {
    label: 'English',
    icon: '🇺🇸',
  },
  zh: {
    label: '中文',
    icon: '🇨🇳',
  },
};

const LanguageSwitcher = () => {
  const router = useRouter();
  const { pathname, asPath, query, locale } = router;

  const handleLanguageChange = (newLocale: string) => {
    document.cookie = `NEXT_LOCALE=${newLocale}; max-age=31536000; path=/`;
    router.push({ pathname, query }, asPath, { locale: newLocale });
  };

  return (
    <Menu>
      <MenuButton>🌍</MenuButton>
      <MenuList>
        {Object.entries(LANG_MAP).map(([key, lang]) => (
          <MenuItem
            key={key}
            onClick={() => handleLanguageChange(key)}
            selected={key === locale}
          >
            {lang.icon} {lang.label}
          </MenuItem>
        ))}
      </MenuList>
    </Menu>
  );
};

export default LanguageSwitcher;

Development Environment Setup

VSCode Configuration

  1. Install the i18n Ally extension
  2. Add to .vscode/settings.json:
{
  "i18n-ally.localesPaths": "public/locales",
  "i18n-ally.keystyle": "nested"
}

Type Support

Create types/i18next.d.ts:

import 'i18next';
import common from '../public/locales/en/common.json';
import order from '../public/locales/en/order.json';

interface I18nNamespaces {
  common: typeof common;
  order: typeof order;
}

declare module 'i18next' {
  interface CustomTypeOptions {
    defaultNS: 'common';
    resources: I18nNamespaces;
  }
}

Using Translations in Pages

Setup App Component

import { appWithTranslation } from 'next-i18next';

const MyApp = ({ Component, pageProps }) => (
  <Component {...pageProps} />
);

export default appWithTranslation(MyApp);

Implementing in Pages

import { useTranslation } from 'next-i18next';
import { getLocaleProps } from '@/helpers/i18n';

const Page = () => {
  const { t } = useTranslation('common');

  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('description')}</p>
    </div>
  );
};

export const getStaticProps = getLocaleProps(['common', 'order']);

export default Page;

Testing with i18n

E2E Testing

import commonLocale from '../../public/locales/en/common.json';

describe('Homepage', () => {
  it('should display correct translations', () => {
    cy.visit('/');
    cy.contains(commonLocale.title).should('be.visible');
  });
});

Jest Testing

Create a mock i18n instance:

import i18next from 'i18next';
import { initReactI18next } from 'react-i18next';
import common from '../../public/locales/en/common.json';

const i18n = i18next.use(initReactI18next).init({
  lng: 'en',
  fallbackLng: 'en',
  ns: ['common'],
  defaultNS: 'common',
  resources: {
    en: { common }
  }
});

export default i18n;

Common Issues and Solutions

Resolving 'fs' Module Error

Add to next.config.js:

webpack: (config, { isServer }) => {
  if (!isServer) {
    config.resolve.fallback = {
      ...config.resolve.fallback,
      fs: false
    };
  }
  config.module.exprContextCritical = false;
  return config;
}

Best Practices

  1. Use namespaces to organize translations logically
  2. Maintain consistent key naming conventions
  3. Implement type checking for translation keys
  4. Use the i18n Ally extension for better development experience
  5. Implement proper error handling for missing translations
  6. Consider translation file size and loading performance

Conclusion

Implementing i18n in Next.js requires careful consideration of your project's needs and scale. The next-i18next library provides a robust solution for most cases, while next-intl and next-translate offer simpler alternatives for smaller projects. With proper setup and tooling, you can create a maintainable internationalized application that serves a global audience effectively.