本文将详细介绍如何在Next.js项目中实现完整的国际化方案,从框架选择到具体实现,再到测试和常见问题解决,给出一个完整的落地方案。
一、国际化框架的选择
Next.js官方推荐了三个主流的国际化框架:
• next-i18next
• next-translate
• next-intl
经过对比分析,我们选择使用next-i18next
作为解决方案,原因如下:
1. 使用灵活,支持SSR
2. 功能完善,包括多语言支持、多命名空间等
3. 社区活跃,npm下载量领先
4. 基于成熟的i18next库,适合大型项目
特别提醒:如果你使用Next.js 13+的app router,建议直接使用i18next和react-i18next,无需使用next-i18next。
二、项目配置步骤
1. 安装依赖
pnpm add next-i18next react-i18next i18next
这三个包各自的作用和关系如下:
i18next
• 这是最基础的国际化核心库
• 提供了最基本的国际化功能,包括:
• 翻译文本的加载和管理
• 语言切换
• 复数处理
• 格式化日期、数字等
• 命名空间管理
• 它是与框架无关的,可以在任何 JavaScript 环境中使用
react-i18next
• 这是 i18next 的 React 集成库
• 提供了在 React 应用中使用 i18next 的专用组件和hooks:
•
useTranslation
hook:用于在组件中获取翻译函数•
Trans
组件:用于处理包含 HTML 或组件的复杂翻译•
withTranslation
HOC:用于为类组件添加翻译功能• 处理 React 特定的生命周期和状态管理
next-i18next
• 这是专门为 Next.js 开发的国际化解决方案
• 在
i18next
和react-i18next
的基础上增加了 Next.js 特定的功能:• 服务端渲染(SSR)支持
• 与 Next.js 路由系统的集成
• 自动语言检测和路由处理
• 针对 Next.js 的性能优化
这三个包的关系可以这样理解:
next-i18next (提供 Next.js 集成)
↓
react-i18next (提供 React 集成)
↓
i18next (核心国际化功能)
使用示例:
// 1. 首先需要配置 next-i18next
// next-i18next.config.js
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'zh'],
},
}
// 2. 在组件中使用
import { useTranslation } from 'next-i18next'; // 这实际上是从 react-i18next 重导出的
const MyComponent = () => {
// useTranslation hook 来自 react-i18next
const { t } = useTranslation('common');
return (
<div>
{/* t 函数实际上是调用 i18next 的翻译功能 */}
<h1>{t('welcome')}</h1>
<p>{t('description')}</p>
</div>
);
};
// 3. 在页面中使用 next-i18next 的特定功能
export const getStaticProps = async ({ locale }) => ({
props: {
...(await serverSideTranslations(locale, ['common'])),
},
})
2. 添加配置文件
创建next-i18next.config.js
:
const path = require('path');
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'zh'],
localePath: path.resolve('./public/locales'),
},
};
3. 修改Next.js配置
在next.config.js
中添加国际化配置:
const { i18n } = require('./next-i18next.config');
const nextConfig = {
i18n,
webpack: (config, {isServer}) => {
if (!isServer) {
config.resolve = {
...config.resolve,
fallback: {
...config.resolve.fallback,
fs: false,
},
};
}
config.module = {
...config.module,
exprContextCritical: false,
};
return config;
},
};
module.exports = nextConfig;
三、翻译文件组织
1. 目录结构
在public/locales
下创建对应的语言目录:
public/
└── locales/
├── en/
│ ├── common.json
│ └── order.json
└── zh/
├── common.json
└── order.json
2. 翻译文件示例
{
"components": {
"SubscribeFormModal": {
"onOK": {
"title": "Confirm",
"message": "Are you sure?"
}
}
}
}
四、实现语言切换器
import {
Text,
Menu,
MenuButton,
MenuItem,
MenuList,
MenuButtonProps,
} from '@chakra-ui/react';
import { useRouter } from 'next/router';
const LANG_MAP = {
en: {
label: 'English',
icon: '🇺🇸',
},
zh: {
label: '中文',
icon: '🇨🇳',
},
} as const;
const LangSelect: React.FC<MenuButtonProps> = (props) => {
const router = useRouter();
const { pathname, asPath, query, locale } = router;
return (
<Menu autoSelect={false}>
<MenuButton p="12px" {...props}>
<LanguageIcon />
</MenuButton>
<MenuList w="max-content" minW="120px">
{Object.entries(LANG_MAP).map(([key, lang]) => (
<MenuItem
key={key}
display="flex"
alignItems="center"
fontSize="sm"
{...(key === locale ? { bg: 'A7Gray.200' } : {})}
onClick={() => {
document.cookie = `NEXT_LOCALE=${key}; max-age=31536000; path=/`;
router.push({ pathname, query }, asPath, { locale: key });
}}
>
<Text mr="8px">{lang.icon}</Text>
<Text>{lang.label}</Text>
</MenuItem>
))}
</MenuList>
</Menu>
);
};
export default LangSelect;
五、开发环境配置
1. VSCode插件配置
安装i18n-ally
插件,在.vscode/settings.json
中添加:
{
"i18n-ally.localesPaths": "public/locales",
"i18n-ally.keystyle": "nested"
}
2. 类型支持
创建types/i18next.d.ts
:
import 'i18next';
import order from '../../public/locales/en/order.json';
import common from '../../public/locales/en/common.json';
interface I18nNamespaces {
order: typeof order;
common: typeof common;
}
declare module 'i18next' {
interface CustomTypeOptions {
defaultNS: 'common';
resources: I18nNamespaces;
}
}
六、在页面中使用翻译
1. 包装App组件
import { appWithTranslation } from 'next-i18next'
const MyApp = ({ Component, pageProps }) => (
<Component {...pageProps} />
)
export default appWithTranslation(MyApp)
2. 创建获取本地化props的工具函数
import { GetStaticProps } from 'next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { I18nNamespaces } from '@/types/i18next';
const getLocaleProps =
(namespaces: (keyof I18nNamespaces)[]): GetStaticProps =>
async ({ locale }) => ({
props: {
...(await serverSideTranslations(locale!, namespaces)),
},
});
export default getLocaleProps;
3. 在页面中使用
import { GetStaticPaths } from 'next';
import { useTranslation } from 'next-i18next';
import { getLocaleProps } from '@/helper/utils';
const Page = () => {
const { t } = useTranslation('order');
return (
<div>
{t('components.SubscribeFormModal.onOK.title')}
</div>
);
};
export const getStaticPaths: GetStaticPaths = async () => ({
paths: [],
fallback: 'blocking',
});
export const getStaticProps = getLocaleProps(['common', 'order']);
export default Page;
4.路由持久化
通过设置NEXT_LOCALE
cookie来实现语言选择的持久化,cookie设置示例:
document.cookie = `NEXT_LOCALE=${lang}; max-age=31536000; path=/`;
七、测试配置
1. E2E测试配置
import orderLocale from '../../public/locales/en/order.json';
import commonLocale from '../../public/locales/en/common.json';
describe('Test My Order', () => {
it('Should visit my order', () => {
cy.visit('/order');
cy.contains(orderLocale.Tab.order.title).should('be.visible');
});
});
2. Jest测试配置
创建createI18nMock.ts
:
import i18next from 'i18next';
import { initReactI18next } from 'react-i18next';
import common from '../../public/locales/en/common.json';
i18next.use(initReactI18next).init({
lng: 'en',
fallbackLng: 'en',
ns: ['common'],
defaultNS: 'common',
resources: {
en: {
common,
},
},
});
export default i18next;
在jest.setup.ts
中配置:
import i18next from './src/test-utils/createI18nMock';
jest.mock('next-i18next', () => {
return {
useTranslation: () => {
return {
t: (key, option) =>
i18next.getResource('en', option?.ns || 'common', key),
i18n: {
changeLanguage: () => new Promise(() => {}),
},
};
},
};
});
更多关于 Next.js的全栈内容请点击下面的合集