前端 i18n 最佳实践:在 React 中使用 i18next

简介: 前端 i18n 最佳实践:在 React 中使用 i18next

本文将会介绍 i18n 的业界最佳实践。

如果你对 i18n 的基本概念不够了解,可以参考我的上一篇文章。

聊聊什么是 i18n?JavaScript 中的 i18n 基本概念

在上一章节中介绍了 JavaScript 原生的 i18n API,但是它并没有被广泛采用。这一篇文章我来聊聊业界使用广泛的解决方案。

目前业界最流行的 i18n 方案都需要处理两个东西:

  • 资源文件
  • 路由


资源文件


i18n 这个问题在前后端未分离的时代就一直存在。

实现 i18n,可以分为后端 runtime 的策略和前端 runtime 的策略。

先讲一下后端 runtime 的策略。

比如在 JSP 和 PHP 模板引擎中就有大量基于这种策略实现的 i18n。

它会把网页中要显示的内容提前翻译成多种语言。

当用户请求网页时,由用户自己选择语言,或者在第一次用户没选择语言时使用服务端接收到的 accept-language 请求头字段来确定用户的语言。

之后去加载写死的模板字符串资源文件,去替换文本内容。

拿代码举个大概的例子。

这是原来没有做国际化的代码。


<div>这是一段简单的文本</div>

做完国际化后。


<div>{t('simpleContent')}</div>

其实就是提供了一个 t 函数(就是 translation 的缩写),它可以翻译文本。

资源文件可以是 JSON,或者是其他格式的文件,内容大概如下:


{
  "en": {
    "simpleContent": "Just simple content"
  },
  "zh": {
    "simpleContent": "这是一段简单的文本"
  }
}

前端 runtime 的策略原理和上面的类似,就是提前定义一堆 JavaScript 对象,然后去动态获取它的值而已。

这么看来似乎前端 runtime 策略似乎更加简单,但在实际上几乎没有什么人会用这种方案,因为它有一个致命缺点,就是不支持 SEO。

国际化的目的本来就是服务更多的用户群体,考虑做国际化的站点,基本上也会考虑做 SEO。


路由


早期的网站很多都没有考虑路由的问题,所有国家的用户都访问同一个网站。但是当中国的用户分享了他老婆网址,他老婆精通中英双语,但是她老婆的浏览器默认是英语,对方想直接看到中文的内容,还需要手动切换语言,很麻烦。而且有些语言的书写和排列都和英语、汉语这类语言不一样,需要单独调整样式。

基于这些需求,现代的网站在做国际化时都喜欢区分路由,比如 web.dev 的网址,中文访问是这个地址:web.dev/i18n/zh/web…,英文访问是这个地址:web.dev/i18n/en/web…

甚至还可以转发网址,比如中国用户访问 xxx.cn,美国用户访问 xxx.com。


最流行的 i18n 库


从 npm 周下载量上看,i18next 无疑是最流行的 i18n 库。

image.png

i18next 之所以如此受欢迎,是因为 i18next 不仅仅是针对 React 或者某个框架而设计的,甚至都不是针对 Web 这个平台而设计的。它可以用在 Node.js、Deno、.NET、php、ruby、IOS、Android 等一系列平台上。

而且 i18next 也足够老,从 2011 年末就开源了,比 React、Vue 这些前端框架都要老,经过了很长时间的考验,基本上没有什么 i18n 领域的问题是它解决不了的。


在 React 中使用 i18next


为了更好的适配 React 这个框架,社区又基于 i18n 开发了 react-i18next 这个库。

接下来我会完成一个简单的案例,带大家快速学习 react-i18next。


基本用法


首先创建项目。


npx create-react-app react-i18n

然后安装 i18-next 的依赖项。


npm i i18next react-i18next i18next-browser-languagedetector
  • i18next 提供了翻译的基本能力。
  • react-i18next 是 i18next 的一个插件,用来降低 react 的使用成本。
  • i18next-browser-languagedetector 是用来检测浏览器语言的插件。

创建 i18n.js,具体的注释都在代码中。


import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
  // 检测用户当前使用的语言
  // 文档: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // 注入 react-i18next 实例
  .use(initReactI18next)
  // 初始化 i18next
  // 配置参数的文档: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false,
    },
    resources: {
      en: {
        translation: {
          // 这里是我们的翻译文本
        }
      }
    }
  });
export default i18n;

在 index.js 中导入 i18n.js。


import './i18n.js'

重新编写 App.js 中的内容。


import { useTranslation, Trans } from 'react-i18next';
function App() {
  const { t } = useTranslation();
  return (
    <div >
      <main>
        <p>
          {t('welcome')}
        </p>
        <Trans i18nKey="author">
          作者是: <code>{Date.now()}</code>
        </Trans>
      </main>
    </div>
  );
}
export default App;

其中使用到了 useTranslation 这个 Hooks,以及 Trans 这个组件。

useTranslation 返回的对象包含一个 t 方法,这个方法可以翻译文本。

Trans 可以翻译一个组件树。

打开浏览器,这时显示下面的内容:

image.png

我们去 i18n.js 中添加翻译。


import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
  // 检测用户当前使用的语言
  // 文档: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // 注入 react-i18next 实例
  .use(initReactI18next)
  // 初始化 i18next
  // 配置参数的文档: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false,
    },
    resources: {
      en: {
        translation: {
          // 这里是我们的翻译文本
          welcome: 'Welcome to my website',
          author: `Author is:<1>code and beast</1>`,
        }
      },
      zh: {
        translation: {
          welcome: '欢迎来到我的网站',
          author: `作者是:<1>代码与野兽</1>`,
        }
      }
    }
  });
export default i18n;

现在显示正常了。

image.png

接下来我们再来测试浏览器是英文的情况。

我用的是 Chrome 浏览器,其他浏览器操作步骤类似。

在浏览器设置中搜索 languages。

image.png

点击 add languages,选择 English。

image.png

然后需要 Move to the top,把 English 设置为首选语言。

image.png

重启浏览器。

这时就被翻译成英语了。

image.png


语言翻译器


接下来我们实现一个语言切换的功能。


import { useTranslation, Trans } from 'react-i18next';
const lngs = {
  en: { nativeName: 'English' },
  zh: { nativeName: '中文' }
};
function App() {
  const { t, i18n } = useTranslation();
  return (
    <div >
      <header>
        <select onChange={(evt) => {
          i18n.changeLanguage(evt.target.value)
        }}>
          {Object.keys(lngs).map((lng) => (
            <option key={lng} value={lng} label={lngs[lng].nativeName}
              style={{ fontWeight: i18n.resolvedLanguage === lng ? 'bold' : 'normal' }} />
          ))}
        </select>
      </header>
      <main>
        <p>
          {t('welcome')}
        </p>
        <Trans i18nKey="author">
          作者是: <code>{Date.now()}</code>
        </Trans>
      </main>
    </div >
  );
}
export default App;

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/117da67925b2476fb3592b2790182a5f~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp

核心代码是 useTranslation 返回的 i18n.changeLanguage 方法,这个方法可以修改用户的当前语言。

不过 i18next-browser-languagedetector 会自动尝试检测浏览器的默认语言,我们可以把用户上次手动选择的语言存储到 localStorage 中,下次访问页面时使用上次存储的语言作为首选语言。


插值表达式


这时你可能会问了,当我们碰到要翻译的内容中存在变量时该怎么办?

i18next 提供了插值的用法。

我们在 t 函数中传递第二个参数,它是一个对象。

比如我们要显示当前时间。

首先安装 dayjs。


npm i day.js

添加一个元素。


<p>
  {t('currentTime', { time: dayjs().format('MM/DD/YYYY') })}
</p>

然后在 i18n.js 中添加翻译的文本。


{
  resources: {
      en: {
        translation: {
          // 这里是我们的翻译文本
          welcome: 'Welcome to my website',
          author: `Author is:<1>code and beast</1>`,
          currentTime: 'Current time is {{time}}',
        }
      },
      zh: {
        translation: {
          welcome: '欢迎来到我的网站',
          author: `作者是:<1>代码与野兽</1>`,
          currentTime: '当前时间是 {{time}}',
        }
      }
    }
}

插值表达式的语法是使用两个大花括号包裹属性名。


格式化


如果你想对插值进行格式化,比如中文的日期显示 2022-09-05,英文日期显示05/09/22。该怎么做呢?

i18next 提供了格式化的能力。

首先差值表达式支持第二个值,它是格式化器的名字。


{
  resources: {
  en: {
    translation: {
      // 这里是我们的翻译文本
      welcome: 'Welcome to my website',
      author: `Author is:<1>code and beast</1>`,
      currentTime: 'Current time is {{time, DD/MM/YY}}',
    }
  },
  zh: {
    translation: {
      welcome: '欢迎来到我的网站',
      author: `作者是:<1>代码与野兽</1>`,
      currentTime: '当前时间是 {{time, YYYY-MM-DD}}',
    }
  }
}
}

然后添加两个格式化器。


i18n.services.formatter.add('DD/MM/YY', (value, lng, options) => {
  return dayjs(value).format('DD/MM/YY')
});
i18n.services.formatter.add('YYYY-MM-DD', (value, lng, options) => {
  return dayjs(value).format('YYYY-MM-DD')
});

这样就实现了插值文本的格式化。

除了上面介绍的用法外,i18next 还有更多的功能,比如语境、复数、命名空间等。这里就不多做介绍了,详细的内容可以参考 i18next 的文档


将翻译文件与代码进行拆分


通常来说,翻译的工作不是程序员干的,而是有专业的业务团队去负责翻译,因为他们对业务术语等更加了解。

为了方便维护,我们可以选择使用 json 文件来作为保存翻译文本的资源文件。

首先需要安装一个库:i18next-http-backend。


npm install i18next-http-backend

然后修改 i18n.js 的内容,将这个插件用上,并且删除掉原来的 resources 字段。


import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import dayjs from 'dayjs'
import Backend from 'i18next-http-backend';
i18n
  .use(Backend)
  // 检测用户当前使用的语言
  // 文档: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // 注入 react-i18next 实例
  .use(initReactI18next)
  // 初始化 i18next
  // 配置参数的文档: https://www.i18next.com/overview/configuration-options
  .init({
    debug: true,
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false,
    },
  });
// new usage
i18n.services.formatter.add('DD/MM/YY', (value, lng, options) => {
  return dayjs(value).format('DD/MM/YY')
});
i18n.services.formatter.add('YYYY-MM-DD', (value, lng, options) => {
  return dayjs(value).format('YYYY-MM-DD')
});
export default i18n;

然后在 public 下面创建 locales 目录,在这个目录下创建和语言缩写对应的文件夹,其中放置 translation.json 文件。这个命名是约定好的,backend 插件会去按照这个路径请求资源文件。

image.png

翻译的内容和 resources 的内容一模一样。

比如 zh/translation.json 的内容如下:


{
  "welcome": "欢迎来到我的网站",
  "author": "作者是:<1>代码与野兽</1>",
  "currentTime": "当前时间是 {{time, YYYY-MM-DD}}"
}

但是这样会让资源文件以异步的方式进行请求,如果网络存在波动,那么有可能请求资源文件失败。

所以我们还要使用 Suspense 组件处理加载出错。


import { Suspense } from 'react';
function App() {
  // ...
}
export default function WrappedApp() {
  return (
    <Suspense fallback="loading">
      <App />
    </Suspense>
  );
}


locize 平台


i18next 有自己的翻译平台 locize.app/

这个平台可以帮助我们整合翻译服务、追踪翻译的更改、版本、机器翻译等一系列功能,它会让我们管理翻译资源文件就像管理代码一样。

当我们的全球化业务非常庞大时,采用这个平台来处理 i18n 问题会更加轻松。

文中介绍的代码部分均为客户端 runtime 翻译,在更多的实际情况下我们会选择服务端 runtime 翻译,这时可能会使用 nextjs 这种 SSR 框架来处理 i18n。

更多的内容我会在后续的文章中讲解。



相关文章
|
1月前
|
前端开发 JavaScript 开发者
颠覆传统:React框架如何引领前端开发的革命性变革
【10月更文挑战第32天】本文以问答形式探讨了React框架的特性和应用。React是一款由Facebook推出的JavaScript库,以其虚拟DOM机制和组件化设计,成为构建高性能单页面应用的理想选择。文章介绍了如何开始一个React项目、组件化思想的体现、性能优化方法、表单处理及路由实现等内容,帮助开发者更好地理解和使用React。
77 9
|
25天前
|
监控 前端开发 数据可视化
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
@icraft/player-react 是 iCraft Editor 推出的 React 组件库,旨在简化3D数字孪生场景的前端集成。它支持零配置快速接入、自定义插件、丰富的事件和方法、动画控制及实时数据接入,帮助开发者轻松实现3D场景与React项目的无缝融合。
94 8
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
|
24天前
|
存储 前端开发 JavaScript
前端中对象的深度应用与最佳实践
前端对象应用涉及在网页开发中使用JavaScript等技术创建和操作对象,以实现动态交互效果。通过定义属性和方法,对象可以封装数据和功能,提升代码的组织性和复用性,是现代Web开发的核心技术之一。
|
28天前
|
前端开发 JavaScript 开发者
使用React和Redux构建高效的前端应用
使用React和Redux构建高效的前端应用
33 1
|
1月前
|
前端开发 数据管理 测试技术
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第27天】本文介绍了前端自动化测试中Jest和Cypress的实战应用与最佳实践。Jest适合React应用的单元测试和快照测试,Cypress则擅长端到端测试,模拟用户交互。通过结合使用这两种工具,可以有效提升代码质量和开发效率。最佳实践包括单元测试与集成测试结合、快照测试、并行执行、代码覆盖率分析、测试环境管理和测试数据管理。
57 2
|
1月前
|
前端开发 JavaScript Android开发
前端框架趋势:React Native在跨平台开发中的优势与挑战
【10月更文挑战第27天】React Native 是跨平台开发领域的佼佼者,凭借其独特的跨平台能力和高效的开发体验,成为许多开发者的首选。本文探讨了 React Native 的优势与挑战,包括跨平台开发能力、原生组件渲染、性能优化及调试复杂性等问题,并通过代码示例展示了其实际应用。
63 2
|
1月前
|
前端开发 JavaScript 数据可视化
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第26天】前端自动化测试在现代软件开发中至关重要,Jest和Cypress分别是单元测试和端到端测试的流行工具。本文通过解答一系列问题,介绍Jest与Cypress的实战应用与最佳实践,帮助开发者提高测试效率和代码质量。
43 2
|
1月前
|
前端开发 JavaScript 开发者
React与Vue:前端框架的巅峰对决与选择策略
【10月更文挑战第23天】React与Vue:前端框架的巅峰对决与选择策略
|
1月前
|
前端开发 JavaScript 开发者
“揭秘React Hooks的神秘面纱:如何掌握这些改变游戏规则的超能力以打造无敌前端应用”
【10月更文挑战第25天】React Hooks 自 2018 年推出以来,已成为 React 功能组件的重要组成部分。本文全面解析了 React Hooks 的核心概念,包括 `useState` 和 `useEffect` 的使用方法,并提供了最佳实践,如避免过度使用 Hooks、保持 Hooks 调用顺序一致、使用 `useReducer` 管理复杂状态逻辑、自定义 Hooks 封装复用逻辑等,帮助开发者更高效地使用 Hooks,构建健壮且易于维护的 React 应用。
35 2
|
1月前
|
前端开发 JavaScript 数据管理
React与Vue:两大前端框架的较量与选择策略
【10月更文挑战第23天】React与Vue:两大前端框架的较量与选择策略