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
- Install the i18n Ally extension
- 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
- Use namespaces to organize translations logically
- Maintain consistent key naming conventions
- Implement type checking for translation keys
- Use the i18n Ally extension for better development experience
- Implement proper error handling for missing translations
- 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.