如何解决前端多语言选型和实现难题?

16,171 阅读9分钟

国际化演示地址: H5-Dooring国际化
注: ⚠️本文为掘金社区首发签约文章,未获授权禁止转载

多语言(i18n)支持 是企业项目走向国际化的必经之路,也是前端工程师最佳实践的内容之一。不过,多语言框架众多,会带来一系列选型问题,相信大家在平时对项目进行多语言支持时,也往往会遇到如下几个问题:

  • 针对不同的技术栈,我该如何选择多语言方案?
  • 如果不借助第三方库,如何独立实现对项目的多语言支持?
  • 在实践多语言方案的过程中,我因该考虑那些问题? 如何更高效的实现多语言?

其实,多语言框架虽多,但是从传统的 jquery 时代到目前流行的 MVVM 框架,多语言方案一直在演进和优化,最终目的是将其普适化最简化。理解了这一点,学习多语言技术就容易多了。

我在设计和实践低代码/零代码搭建平台 Dooring 的过程中,也遇到了多语言方案的技术选型,目前方案基本完成,接下来我将带大家一步步分析多语言在不同技术栈中的实现方案,并以实际的项目让大家掌握多言语技术,在文章最后我也会提出对多语言未来演进的一些方向,供大家研究和探索。

image.png

按照我一向的写作风格,我会在下面列出文章的大纲,以便大家有选择且高效率的阅读和学习:

  • 目前常用的多语言方案介绍和实践
    • 原生js/jquery方案实现多语言
    • vue项目中的多语言方案
    • react项目中的多语言方案
  • 使用 @umijs/plugin-locale 落地国际化项目
  • 智能国际化方案畅想

目前常用的多语言方案介绍和实践

目前常用的多语言方案基本都是人工翻译,然后通过动态替换来实现语言的切换,但是不同的技术框架模式稍有不同, 接下来我们就逐个分析一下。

1. 原生js/jquery方案实现多语言

在传统方案中最容易想到的就是dom替换。我们通过提前定义好多语言文件(或文案Map),并在html标签中做语言映射,最后通过切换函数来动态的切换网站语言。其基本的模式如下:

// 语言库,我们有两种方式来定义
// 1. 单文件Map模式, lang.js
const lang = {
  zh: {
        'title''H5编辑器',
        'userLogin''用户登录',
        'usernameError': '请输入用户名'
    },
  en: {
        'title''H5 editor',
        'userLogin''The user logs on',
        'usernameError': 'Please enter your username'
    },
}

// 2. 多语言包模式
lang/cn.json 
lang/en.json

html标签结构如下:

<select id="langControl">
  <option value="cn">中文</option>
  <option value="en">English</option>
</select>

<div lang="title">H5编辑器</div>
<div lang="userLogin">用户登录</div>

最后我们通过 javascript 遍历 [lang] 属性并通过映射关系来替换语言。当然我们还可以通过 template.js 这样的模版引擎来优化我们dom的渲染替换方式,但是以上方案在落地过程中仍然需要考虑很多问题。如下:

  • 语言持久化需要单独处理
  • 无法动态添加语言文本
  • 无法引入变量以及读取语言文本
  • 缺乏灵活性和配置化

在传统方案中我们为了解决以上问题并支持更复杂系统,我们不得不考虑插件化,当然 Jquery-I18n 就是一个非常不错的解决方案。它可以帮助我们轻松地国际化 Web 应用程序,并且支持链式调用, 且可以无刷新切换语言。接下来我就带大家使用 Jquery-I18n 实现一个简单的demo,让大家更好的掌握该方案。

  1. 引入资源
<script src="https://cdn.bootcss.com/jquery/3.10.2/jquery.min.js"></script> 
<script src="https://cdn.bootcss.com/js-cookie/latest/js.cookie.min.js"></script> 
<script src="./js/jquery.i18n.js"></script>
  1. 创建并配置多语言文件

image.png

image.png

  1. 编写页面内容
<select id="langControl">
  <option value="cn">中文</option>
  <option value="en">English</option>
</select>

<div lang="title">H5编辑器</div>
<div lang="userLogin">用户登录</div>
  1. 初始化 i18n 配置并实现语言切换逻辑
function toggleLang(lang){
   $("[lang]").i18n({
       defaultLang: lang,  // 默认语言
       filePath: "/lang/", // 语言文件所在的目录
       filePrefix: "",   // 语言文件前缀
       fileSuffix: "",   // 语言文件后缀
       forever: true,
       callback: function(res) {}  // 初始化后的回调
   });
}

// 语言切换
$('#langControl').change(val => {
   toggleLang(val)
})

当然 Jquery-I18n 有更多强大的配置,大家可以参考文档进行配置,相关库 github 地址如下:

当然以上方案还只是手动切换语言,更多的需求场景是要我们基于用户当前的浏览器环境或者网站链接地址的参数不同来自动切换对应的语言。对于通过链接参数来改变系统语言,这个我们只需要通过解析参数并进行对应的处理即可,比如解析 http://xxx.xxx?lan=cnhttp://xxx.xxx?lan=en。当然浏览器也提供了对应的 api 可以获取当前用户浏览的环境:navigator.language ,我们在浏览器控制台输入该脚本的结果如下:

image.png

所以我们可以根据这个信息来自动匹配用户当前的语言模式。

2. vue项目中的多语言方案

基于 Vue 的多语言方案网上也有很多,毕竟国内大部分企业都在使用 Vue 开发项目,所以我简单列举几个成熟的方案给大家,并对其中一个方案给出具体的实践:

当然大家如果做的不是很复杂的项目,也可以直接采用 simplest-i18n,因为其更简单轻量。

接下来我会以一个完整的例子来说明如何使用 vue-i18n 来做 Vue 项目的国际化。

  1. 定义语言文件

image.png

  1. 引入依赖并注册语言包
import Vue from "vue";
import VueI18n from "vue-i18n";

Vue.use(VueI18n);

// 加载所有语言环境并记住上下文
function loadMessages() {
  const context = require.context("./lang", true, /[a-z0-9-_]+.json$/i);

  const messages = context
    .keys()
    .map((key) => ({ key, locale: key.match(/[a-z0-9-_]+/i)[0] }))
    .reduce(
      (messages, { key, locale }) => ({
        ...messages,
        [locale]: context(key),
      }),
      {}
    );

  return { context, messages };
}

const { context, messages } = loadMessages();

// VueI18n 实例
const i18n = new VueI18n({
  locale: navigator.language, // 根据浏览器环境设置网站语言
  messages,
});

// 运行程序
const app = new Vue({
  i18n,
  // ...
}).$mount('#app');

// 切换语言(在组件内也可以使用$i18n.locale来切换语言环境)
i18n.locale = 'en-US'

// 热更新支持
if (module.hot) {
  module.hot.accept(context.id, () => {
    const { messages: newMessages } = loadMessages();

    Object.keys(newMessages)
      .filter((locale) => messages[locale] !== newMessages[locale])
      .forEach((locale) => {
        messages[locale] = newMessages[locale];
        i18n.setLocaleMessage(locale, messages[locale]);
      });
  });
}

当然在项目中我们还可以延迟加载翻译,原理类似 webpack 的异步加载文件,参考如下:

export function loadLangAsync(lang) {
  // 如果语言相同
  if (i18n.locale === lang) {
    return Promise.resolve(...)
  }

  // 如果语言已经加载
  if (loadedLanguages.includes(lang)) {
    return Promise.resolve(...)
  }

  // 如果尚未加载语言
  return import(/* webpackChunkName: "lang-[request]" */ `@/lang/${lang}.json`).then(
    messages => {
      // ... 处理逻辑
    }
  )
}

同时 vue-cli 还提供了对应的插件 vue-cli-plugin-i18n 来支持通过配置化的方式开启多语言。

3. react项目中的多语言方案

首先其实多语言在 VueReact 项目中都有一个非常简单的方案,

image.png

其基本流程如下:

  1. 定义多语言文件
  2. 引入到应用
  3. 应用根据优先级设置当前语言环境,并写入 cookie
  4. 当切换语言时,重写 cookie 语言信息并刷新页面

当然市场上也有很多成熟的解决方案,如下:

接下来笔者将结合自己的案例,采用 react-i18next + i18next 方案来实现一个完整的 demo

  1. 安装依赖
# npm
$ npm install react-i18next i18next --save
  1. 定义语言包(这里和之前一样,这里不在重述)

  2. 代码实现

    3.1 全局配置i18n

// i18n.js
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import en from 'locales/en-US.js';
import cn from 'locales/zh-CN.js';


const resources = {
  en: {
    translation: en
  },
  cn: {
    translation: cn
  }
};

i18n
  .use(initReactI18next) // 注册
  .init({
    resources,
    lng: "cn", // 默认语言
    interpolation: {
      escapeValue: false // xss安全开关
    }
  });

  export default i18n;

3.2 配置入口文件

// index.js
import React, { Component } from "react";
import ReactDOM from "react-dom";
// ...其他库
import './i18n';
import App from './App';

ReactDOM.render(
  <App />,
  document.getElementById("root")
);

3.3 在react组件中使用:

import React from 'react';
import { useTranslation } from 'react-i18next';

function Home () {
  const { t, i18n } = useTranslation();
  return <h1>{t('title')}</h1>
}

以上就是一个完整的使用流程,当然大家也可以使用该库实现更多有意思的功能。

使用 @umijs/plugin-locale 落地国际化项目

在介绍完不同技术栈实现的多语言方案之后,我们以 H5-Dooring 为实际案例,来将其国际化一下。因为 H5-Dooring 编辑器端主要采用 React 开发,并采用 umi 做工程化方案,所以我自然选择 umi 对应的生态来实现,好在其提供了开箱即用的 @umijs/plugin-locale 插件,我们可以轻松的实现国际化,接下来我将采用该方案来带大家实现国际化,如果大家也在使用 umi,可以参考一下。

先上张效果图:

chrome-capture.gif

首先我们需要配置一下 umirc.ts 文件,添加如下配置:

locale: {
    default: 'zh-CN', // 默认的语言
    antd: true,  // antd是否也支持国际化
    title: true, // 页面标题是否支持国际化
    baseNavigator: true,  // 开启浏览器语言检测
},

其次我们需要定义多语言文件:

image.png

语言文件内容如下:

image.png

在做好基本准备工作之后,我们看看如何在项目中使用多语言。我们已入口页面为例来说明:

image.png

首先我们需要引入对应的插件,如下:

import { useIntl, setLocale } from 'umi';

其次在 hooks 组件里使用 hook

const Home = ({ location }) => {
  // ... 其他逻辑状态
  const [lang, setLang] = useState('En');
  const intl = useIntl();
  
  return <div>
      <span className={styles.btnControl} onClick={toggleLang}>{ lang }
      </span>
      <h3>{ intl.formatMessage({id: 'dr.friendHelp'}) }</h3>
      <h3>{ intl.formatMessage({id: 'dr.personHelp'}) }</h3>
      <Form.Item label={ intl.formatMessage({id: 'dr.cpEnName'}) } name="cpField" rules={[{ required: true, message: intl.formatMessage({id: 'dr.cpInfoError'}, { text: intl.formatMessage({id: 'dr.cpName'})}) }]}>
         <Input placeholder={ intl.formatMessage({id: 'dr.cpInfoError'}, { text: intl.formatMessage({id: 'dr.cpEnName'})}) } />
      </Form.Item>
  </div>
}

以上就是基本的实现,我们可以在语言文件中使用变量,并在项目中动态指定变量的值,是不是很强大呢? 对于多语言切换,企业提供了对应的 API,并支持无刷新切换和刷新切换两种模式,使用方式如下:

const toggleLang = () => {
    if(lang === 'En') {
      setLocale('en-US', false);
      setLang('中文')
      return
    }
    setLocale('zh-CN', false);
    setLang('En');
  }

由上可以看出我们使用setLocale 这个api来切换语言,第一个参数为语言文本,第二个参数表示切换语言时是否刷新页面。

智能国际化方案畅想

以上探索的方案都是人工来做翻译,我在翻译的过程中耗费了大量的精力和时间,如果网站越来越复杂, 会投入更多的精力,所以说通用的智能化翻译方案在未来将是迫切需要解决的问题,也是国际化 i18n 演进的必经之路,虽然国内已经有大公司内部在做对应的事情了,甚至已经有了一定的解决方案积累,在这里我大致给出一个基本的思路设想:

image.png

这样的话我们只需要以一种语言为基础来进行正常开发,其他语言会通过我们的工具自动翻译并转化为对应的语言文件。后续笔者会落实该方案,大家也可以尝试不同的智能化方案,一起探索前端效能提升之路。

往期文章