Next.js 15 全栈开发系列之国际化

文摘   2024-11-11 08:27   湖北  


本文将详细介绍如何在Next.js项目中实现完整的国际化方案,从框架选择到具体实现,再到测试和常见问题解决,给出一个完整的落地方案。

一、国际化框架的选择

Next.js官方推荐了三个主流的国际化框架:

  • • next-i18next

  • • next-translate

  • • next-intl

经过对比分析,我们选择使用next-i18next作为解决方案,原因如下:

  1. 1. 使用灵活,支持SSR

  2. 2. 功能完善,包括多语言支持、多命名空间等

  3. 3. 社区活跃,npm下载量领先

  4. 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,
          fsfalse,
        },
      };
    }
    config.module = {
      ...config.module,
      exprContextCriticalfalse,
    };
    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 LangSelectReact.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 {
  ordertypeof order;
  commontypeof 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 getStaticPathsGetStaticPaths = 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的全栈内容请点击下面的合集

字节笔记本
专注于科技领域的分享,AIGC,全栈开发,产品运营
 最新文章