MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Node.js国际化i18n解决方案

2021-11-082.7k 阅读

1. 理解国际化(i18n)的概念

在全球化的今天,应用程序需要适应不同地区和语言的用户。国际化(Internationalization,通常缩写为 i18n,因为在单词 “Internationalization” 中,“I” 和 “n” 之间有 18 个字符)是使软件能够在不同语言和地区设置下工作的过程。本地化(Localization,缩写为 l10n)则是针对特定语言和地区定制软件的过程,例如翻译文本、调整日期和数字格式等。

在前端开发中,Node.js 提供了多种方式来实现国际化。这不仅有助于提升用户体验,还能扩大应用程序的受众范围。

2. Node.js 中的国际化模块

2.1 i18n 模块

  • 安装: 首先,我们需要通过 npm 安装 i18n 模块。在项目目录下运行以下命令:

    npm install i18n
    
  • 基本使用: 假设我们有一个简单的 Node.js 应用,我们想要根据用户的语言偏好显示不同的问候语。

    const i18n = require('i18n');
    i18n.configure({
      locales: ['en', 'zh'],
      directory: __dirname + '/locales',
      defaultLocale: 'en'
    });
    
    const express = require('express');
    const app = express();
    
    app.get('/', function (req, res) {
      // 根据请求头中的 Accept - Language 字段设置语言
      i18n.setLocale(req, req.headers['accept - language'].split(',')[0]);
      res.send(i18n.__('hello'));
    });
    
    const port = 3000;
    app.listen(port, function () {
      console.log(`Server running on port ${port}`);
    });
    

    在上述代码中,我们首先配置了 i18n 模块,指定了支持的语言(enzh)以及存储翻译文件的目录(locales 目录)。然后在 Express 应用中,根据请求头中的 Accept - Language 字段设置语言,并通过 i18n.__('hello') 获取翻译后的问候语。

    我们需要在 locales 目录下创建 en.jsonzh.json 文件,内容如下:

    • en.json
    {
      "hello": "Hello"
    }
    
    • zh.json
    {
      "hello": "你好"
    }
    

2.2 node - polyglot 模块

  • 安装: 通过 npm 安装 node - polyglot 模块,命令如下:
    npm install node - polyglot
    
  • 基本使用
    const Polyglot = require('node - polyglot');
    const express = require('express');
    const app = express();
    
    const en = {
      hello: 'Hello'
    };
    const zh = {
      hello: '你好'
    };
    
    const polyglotEn = new Polyglot({ phrases: en });
    const polyglotZh = new Polyglot({ phrases: zh });
    
    app.get('/', function (req, res) {
      const lang = req.headers['accept - language'].split(',')[0];
      let polyglot;
      if (lang.startsWith('zh')) {
        polyglot = polyglotZh;
      } else {
        polyglot = polyglotEn;
      }
      res.send(polyglot.t('hello'));
    });
    
    const port = 3000;
    app.listen(port, function () {
      console.log(`Server running on port ${port}`);
    });
    
    在这个例子中,我们直接在代码中定义了英文和中文的翻译对象,然后创建了两个 Polyglot 实例。根据请求头中的语言信息选择相应的实例,并通过 polyglot.t('hello') 获取翻译后的文本。

3. 处理复数形式

在许多语言中,名词和动词的形式会根据数量而变化。例如,在英语中,“1 book” 和 “2 books” 中 “book” 的形式不同。Node.js 的国际化模块提供了处理复数形式的功能。

3.1 i18n 模块处理复数

  • 配置和使用: 在 i18n 模块中,我们可以在翻译文件中定义复数形式。例如,在 en.json 中:
    {
      "books": {
        "one": "1 book",
        "other": "{{count}} books"
      }
    }
    
    zh.json 中:
    {
      "books": {
        "one": "1 本书",
        "other": "{{count}} 本书"
      }
    }
    
    在代码中,我们可以这样使用:
    const i18n = require('i18n');
    i18n.configure({
      locales: ['en', 'zh'],
      directory: __dirname + '/locales',
      defaultLocale: 'en'
    });
    
    const express = require('express');
    const app = express();
    
    app.get('/books/:count', function (req, res) {
      const count = parseInt(req.params.count);
      i18n.setLocale(req, req.headers['accept - language'].split(',')[0]);
      res.send(i18n.__n('books', count, { count }));
    });
    
    const port = 3000;
    app.listen(port, function () {
      console.log(`Server running on port ${port}`);
    });
    
    这里通过 i18n.__n('books', count, { count }) 方法,根据数量 count 来获取正确的复数形式翻译。

3.2 node - polyglot 模块处理复数

  • 配置和使用: 在 node - polyglot 中,同样可以处理复数。首先定义翻译对象:
    const Polyglot = require('node - polyglot');
    
    const en = {
      books: {
        one: '1 book',
        other: '{{count}} books'
      }
    };
    const zh = {
      books: {
        one: '1 本书',
        other: '{{count}} 本书'
      }
    };
    
    const polyglotEn = new Polyglot({ phrases: en });
    const polyglotZh = new Polyglot({ phrases: zh });
    
    const express = require('express');
    const app = express();
    
    app.get('/books/:count', function (req, res) {
      const count = parseInt(req.params.count);
      const lang = req.headers['accept - language'].split(',')[0];
      let polyglot;
      if (lang.startsWith('zh')) {
        polyglot = polyglotZh;
      } else {
        polyglot = polyglotEn;
      }
      res.send(polyglot.t('books', { count, _count: count }));
    });
    
    const port = 3000;
    app.listen(port, function () {
      console.log(`Server running on port ${port}`);
    });
    
    这里通过 polyglot.t('books', { count, _count: count }) 来处理复数形式,_count 用于指定数量,以便 node - polyglot 选择正确的复数形式。

4. 日期和数字格式化

除了文本翻译,国际化还涉及到日期和数字的格式化。不同地区使用不同的日期格式(如 MM/dd/yyyydd - MM - yyyy)和数字格式(如千位分隔符的使用)。

4.1 使用 Intl.NumberFormat

  • 基本数字格式化
    const number = 12345.678;
    const enUS = new Intl.NumberFormat('en - US');
    const zhCN = new Intl.NumberFormat('zh - CN');
    
    console.log(enUS.format(number)); // 输出: 12,345.678
    console.log(zhCN.format(number)); // 输出: 12,345.678
    
    上述代码展示了如何使用 Intl.NumberFormat 对数字进行格式化,根据不同的语言标签(en - USzh - CN),数字会以不同的格式显示。
  • 货币格式化
    const amount = 12345.678;
    const enUS = new Intl.NumberFormat('en - US', {
      style: 'currency',
      currency: 'USD'
    });
    const zhCN = new Intl.NumberFormat('zh - CN', {
      style: 'currency',
      currency: 'CNY'
    });
    
    console.log(enUS.format(amount)); // 输出: $12,345.68
    console.log(zhCN.format(amount)); // 输出: ¥12,345.68
    
    这里通过设置 style'currency' 并指定 currency,可以将数字格式化为货币形式。

4.2 使用 Intl.DateTimeFormat

  • 日期格式化
    const date = new Date();
    const enUS = new Intl.DateTimeFormat('en - US');
    const zhCN = new Intl.DateTimeFormat('zh - CN');
    
    console.log(enUS.format(date)); // 例如输出: 11/25/2023
    console.log(zhCN.format(date)); // 例如输出: 2023/11/25
    
    上述代码展示了如何使用 Intl.DateTimeFormat 对日期进行格式化,不同的语言标签会导致不同的日期格式。
  • 完整日期和时间格式化
    const date = new Date();
    const enUS = new Intl.DateTimeFormat('en - US', {
      year: 'numeric',
      month: 'long',
      day: 'numeric',
      hour: 'numeric',
      minute: 'numeric',
      second: 'numeric'
    });
    const zhCN = new Intl.DateTimeFormat('zh - CN', {
      year: 'numeric',
      month: 'long',
      day: 'numeric',
      hour: 'numeric',
      minute: 'numeric',
      second: 'numeric'
    });
    
    console.log(enUS.format(date)); // 例如输出: November 25, 2023, 10:30:00 AM
    console.log(zhCN.format(date)); // 例如输出: 2023年11月25日 上午10:30:00
    
    通过设置不同的选项,可以定制日期和时间的详细格式。

5. 整合前端和后端国际化

在一个完整的应用程序中,前端和后端都需要处理国际化。通常,后端负责提供翻译后的文本和格式化的数据,前端负责展示。

5.1 前端使用翻译后的文本

假设后端通过 API 返回翻译后的文本。在前端(使用 React 作为示例),我们可以这样处理:

import React, { useEffect, useState } from'react';

function App() {
  const [greeting, setGreeting] = useState('');

  useEffect(() => {
    fetch('/api/greeting')
    .then(response => response.json())
    .then(data => setGreeting(data.greeting));
  }, []);

  return (
    <div>
      <p>{greeting}</p>
    </div>
  );
}

export default App;

在后端(使用 Node.js 和 Express),我们可以这样提供翻译后的问候语:

const i18n = require('i18n');
i18n.configure({
  locales: ['en', 'zh'],
  directory: __dirname + '/locales',
  defaultLocale: 'en'
});

const express = require('express');
const app = express();

app.get('/api/greeting', function (req, res) {
  i18n.setLocale(req, req.headers['accept - language'].split(',')[0]);
  const greeting = i18n.__('hello');
  res.json({ greeting });
});

const port = 3000;
app.listen(port, function () {
  console.log(`Server running on port ${port}`);
});

这样,前端可以根据用户的语言偏好从后端获取相应的翻译文本并展示。

5.2 前端格式化日期和数字

前端也可以直接进行日期和数字的格式化。例如,在 React 中使用 Intl.NumberFormatIntl.DateTimeFormat

import React from'react';

function App() {
  const number = 12345.678;
  const date = new Date();

  const enUSNumber = new Intl.NumberFormat('en - US').format(number);
  const zhCNNumber = new Intl.NumberFormat('zh - CN').format(number);

  const enUSDate = new Intl.DateTimeFormat('en - US').format(date);
  const zhCNDate = new Intl.DateTimeFormat('zh - CN').format(date);

  return (
    <div>
      <p>Number in en - US: {enUSNumber}</p>
      <p>Number in zh - CN: {zhCNNumber}</p>
      <p>Date in en - US: {enUSDate}</p>
      <p>Date in zh - CN: {zhCNDate}</p>
    </div>
  );
}

export default App;

通过这种方式,前端可以根据用户的语言环境进行日期和数字的格式化,提升用户体验。

6. 动态加载翻译文件

在实际应用中,可能需要动态加载翻译文件,而不是在启动时一次性加载所有。这对于大型应用或需要实时更新翻译的场景非常有用。

6.1 i18n 模块动态加载

  • 配置动态加载
    const i18n = require('i18n');
    i18n.configure({
      locales: ['en', 'zh'],
      autoReload: true,
      directory: __dirname + '/locales',
      defaultLocale: 'en'
    });
    
    const express = require('express');
    const app = express();
    
    app.get('/', function (req, res) {
      i18n.setLocale(req, req.headers['accept - language'].split(',')[0]);
      res.send(i18n.__('hello'));
    });
    
    const port = 3000;
    app.listen(port, function () {
      console.log(`Server running on port ${port}`);
    });
    
    通过设置 autoReload: truei18n 模块会在翻译文件发生变化时自动重新加载。

6.2 node - polyglot 模块动态加载

  • 手动加载翻译文件: 我们可以编写一个函数来手动加载翻译文件。假设翻译文件存储在 locales 目录下。
    const fs = require('fs');
    const path = require('path');
    const Polyglot = require('node - polyglot');
    
    function loadTranslations(lang) {
      const filePath = path.join(__dirname, 'locales', `${lang}.json`);
      const data = fs.readFileSync(filePath, 'utf8');
      const phrases = JSON.parse(data);
      return new Polyglot({ phrases });
    }
    
    const express = require('express');
    const app = express();
    
    app.get('/', function (req, res) {
      const lang = req.headers['accept - language'].split(',')[0];
      const polyglot = loadTranslations(lang);
      res.send(polyglot.t('hello'));
    });
    
    const port = 3000;
    app.listen(port, function () {
      console.log(`Server running on port ${port}`);
    });
    
    这里通过 loadTranslations 函数根据语言动态加载翻译文件并创建 Polyglot 实例。

7. 处理语言变体

有些语言存在多种变体,例如英语有美式英语(en - US)和英式英语(en - GB)。Node.js 的国际化模块可以处理这些变体。

7.1 i18n 模块处理语言变体

  • 配置变体: 我们可以在 i18n 模块的配置中添加变体。例如:
    const i18n = require('i18n');
    i18n.configure({
      locales: ['en - US', 'en - GB', 'zh'],
      directory: __dirname + '/locales',
      defaultLocale: 'en - US'
    });
    
    const express = require('express');
    const app = express();
    
    app.get('/', function (req, res) {
      i18n.setLocale(req, req.headers['accept - language'].split(',')[0]);
      res.send(i18n.__('color'));
    });
    
    const port = 3000;
    app.listen(port, function () {
      console.log(`Server running on port ${port}`);
    });
    
    locales 目录下,我们需要创建 en - US.jsonen - GB.jsonzh.json 文件。例如,en - US.json 中:
    {
      "color": "color"
    }
    
    en - GB.json 中:
    {
      "color": "colour"
    }
    
    这样,根据请求头中的语言信息,i18n 模块会选择正确的变体翻译。

7.2 node - polyglot 模块处理语言变体

  • 分别创建实例
    const Polyglot = require('node - polyglot');
    const express = require('express');
    const app = express();
    
    const enUS = {
      color: 'color'
    };
    const enGB = {
      color: 'colour'
    };
    const zh = {
      color: '颜色'
    };
    
    const polyglotEnUS = new Polyglot({ phrases: enUS });
    const polyglotEnGB = new Polyglot({ phrases: enGB });
    const polyglotZh = new Polyglot({ phrases: zh });
    
    app.get('/', function (req, res) {
      const lang = req.headers['accept - language'].split(',')[0];
      let polyglot;
      if (lang.startsWith('en - US')) {
        polyglot = polyglotEnUS;
      } else if (lang.startsWith('en - GB')) {
        polyglot = polyglotEnGB;
      } else {
        polyglot = polyglotZh;
      }
      res.send(polyglot.t('color'));
    });
    
    const port = 3000;
    app.listen(port, function () {
      console.log(`Server running on port ${port}`);
    });
    
    通过分别创建不同语言变体的 Polyglot 实例,根据请求头中的语言信息选择正确的实例进行翻译。

8. 最佳实践和注意事项

8.1 翻译文件的管理

  • 结构化:保持翻译文件的结构清晰,对于大型项目,可以按照功能模块划分翻译文件。例如,将用户界面相关的翻译放在 ui.json 中,将业务逻辑相关的翻译放在 business.json 中。
  • 版本控制:使用版本控制系统(如 Git)管理翻译文件,这样可以跟踪翻译的变化历史,方便团队协作和回滚。

8.2 性能优化

  • 缓存:对于频繁使用的翻译文本和格式化对象,可以进行缓存。例如,在 Node.js 中,可以使用内存缓存来存储已经加载的翻译文件和 Intl.NumberFormatIntl.DateTimeFormat 实例。
  • 延迟加载:对于不常用的语言或翻译文件,可以采用延迟加载的方式,只有在用户实际需要时才加载,减少初始加载时间。

8.3 测试

  • 单元测试:对翻译函数进行单元测试,确保翻译结果的正确性。例如,使用 Mocha 和 Chai 等测试框架,测试不同语言和数量下的翻译是否准确。
  • 集成测试:进行集成测试,确保前端和后端在国际化方面的协作正常。例如,测试前端能否正确获取后端提供的翻译文本并展示。

通过遵循这些最佳实践和注意事项,可以构建一个高效、可靠且易于维护的 Node.js 国际化解决方案。在全球化的市场中,为用户提供更好的多语言体验,从而提升应用程序的竞争力。同时,不断关注国际化标准和模块的更新,以适应不断变化的需求和语言环境。无论是小型项目还是大型企业级应用,合理的国际化策略都能为项目的成功奠定基础。在实际开发中,结合项目的特点和需求,选择合适的国际化模块和方法,并不断优化和完善,以满足用户多样化的需求。在处理日期和数字格式化时,要充分考虑不同地区的习惯差异,确保格式化结果符合当地用户的认知。在动态加载翻译文件和处理语言变体等方面,要确保系统的灵活性和稳定性,为用户提供无缝的国际化体验。