一份不错的前端国际化方案指南

14,149 阅读8分钟

本文章共3470字,预计阅读时间5-10分钟。

国际化-前言

每个开发者能希望编写的程序可以让全世界的用户使用,它要求从产品中抽离所有地域语言,国家/地区和文化相关的元素。换种说法,应用程序的功能和代码设计时考虑在不同地区运行的需要,其代码适应不同区域要求。开发这样的的过程,就称为国际化( internationalization),简称i18n。

i18n(其来源是英文单词 internationalization的首末字符i和n,18为中间的字符数)是“国际化”的简称。在资讯领域,国际化(i18n)指让产品(出版物,软件,硬件等)无需做大的改变就能够适应不同的语言和地区的需要。对程序来说,在不修改内部代码的情况下,能根据不同语言及地区显示相应的界面。

在全球化的时代,国际化尤为重要,因为产品的潜在用户可能来自世界的各个角落!

国际化-范围

国际化与本地化工作的焦点包括但不限于如下:

语言

  • 不同国家的语言;
  • 文字书写方向;(比如德语是从左到右,而波斯语、希伯来语和阿拉伯语是由右到左。)
  • 图片中包含的文字;
  • 程序中的音频;
  • 程序中的视频字幕;

文化

  • 图片和颜色:这牵涉到理解和文化适宜的议题;
  • 名字和称谓;
  • 政府给定的编码(如美国的社会安全码,英国的National Insurance number,爱沙尼亚的Isikukood及其它各国的身份证号码)和护照;
  • 电话号码前缀、地址和国际邮递区号;
  • 货币 (符号、货币标志的位置);
  • 长度、容积、重量单位;
  • 标准纸张大小;

书写习惯

  • 日期跟时间的格式,包含各式日历。
  • 时区(在国际场合会使用世界标准时间)
  • 数字格式(小数点、分隔点的位置、分隔所用的字符)

产品和服务所要面向的法规

程序的内容、运营方式及方向需要遵守当地法律、法规;

多语言翻译方案

目前,并没有非常完美,适用所有情况的多语言方案,所以找到一个比较契合自己团队的方案,并做一系列配制方法去用,是比较耗时的一项工程。是否需要花时间成本来做到前端国际化,完全取决分析自身团队的需求。

使用插件在线翻译

随着全球化网络时代的到来,语言障碍已经成为二十一世纪社会发展的重要瓶颈,实现任意时间、任意地点、任意语言的无障碍自由沟通是人类追求的一个梦想。这仅是全球化背景下的一个小缩影。在社会快速发展的进程中,在线翻译扮演越来越重要的角色。

运行规则

将单词序列(一个或多个句子)作为输入,并生成单词的输出序列,这是通过递归神经网络(RNN)实现的。具体来说,就是让两个将与某个特殊令牌一起运行的递归神经网络尝试根据前一个序列来预测后一个状态序列。

它主要由编码器和解码器两部分构成,因此有时候被称为编码器-解码器网络。

· 编码器:使用多个深度神经网络层,将输入单词转换为相应的隐藏向量。每个向量代表当前单词及其语境。

· 解码器:与编码器类似。它将编码器生成的隐藏向量、自身的隐藏状态和当前单词作为输入,从而生成下一个隐藏向量,最终预测下一个单词。

谷歌插件在线翻译

谷歌不再提供对 Google 翻译的网站翻译器的新访问。此更改不会影响网站翻译器的现有使用。

谷歌鼓励希望翻译网页的用户使用支持本地翻译的浏览器。

效果图示例:

谷歌翻译.gif

代码示例

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Translate</title>
</head>
<body>
    <p>你好 世界!</p>
    <!--谷歌翻译 -->
    <!--    如需隐藏/更改一些样式,可使用css操作-->
    <div id="google_translate_element"></div>
</body>
<script src="http://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>
<script>
  function googleTranslateElementInit() {
    new google.translate.TranslateElement({
      // includedLanguages: 'de,en,es,fr,it', 需要下拉翻译支持哪些语言,默认全部
      layout: google.translate.TranslateElement.InlineLayout.HORIZONTAL
    }, 'google_translate_element');
  }
</script>
</html>

在线示例/源码地址

在线示例:点此查看- 谷歌插件在线翻译(需要可以访问google)

源码地址:github.com/Tzlibai/Dem…

注:目前浏览器基本都内置了- 网页在线翻译功能**

PS: 谷歌翻译插件会在替换文本时修改标签(DOM结构)会导致Vue、React这种基于virtual dom的框架产生问题

问题:难以避免的误差

机器翻译其误差在所难免,原因在于,机器翻译运用语言学原理,机器自动识别语法,调用存储的词库,自动进行对应翻译,但是因语法、词法、句法发生变化或者不规则,出现错误是难免的,比如《大话西游》中 “给我一个杀你的理由,先” 中,“先”字意义上其实是起修饰限制作用,但在机器翻译时就会有不同的意思。

机器毕竟是机器,没有人对语言的特殊感情。它怎么会感受“万里悲秋常作客,百年多病独登台”的无限悲凉之意?毕竟汉语因其词法、语法、句法的变化及其语境的更换,其意思大相径庭。

维护多套页面/语言代码

顾名思义:不同语言编写不同的页面。 假设需要支持3种语言,此时需要编写三种不同的页面,这样的弊端是当页面需要维护修改时,需要对不同的页面进行更改

效果图示例:

多套网页维护.gif

在线示例/源码地址

在线示例:点此查看- 维护多套页面/语言代码

源码地址:github.com/Tzlibai/Dem…

语言包配置文件

将所有的语言资源放在独立的文件夹下,以每个字段唯一标识,去找到不同语言相对应的字段,以显示来完成前端国际化。 这样在html我们只需要输出标识符,在js中配置好功能、路径,我们就可以让它自行去语言资源包中找到对应语言字段以显示。

jQuery - 多语言翻译

代码示例

主要步骤如下:

  • jquery.js文件

  • jquery.i18n.properties.js文件

  • 各个国家语言包(.properties格式文件,每个国家语言包目录下都会有该文件,只是配置不同)

    // commion.properties 文件格式如下
    // (key) = (value)
    ​
    hi=Hello world!
    ​
    
  • index 编写逻辑,读写cookie、引入语言包、根据class渲染文案

整理完成后 目录如下图:

文件目录解读-pro.jpg html文件如下:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
<!--    这里是设置id为i18n_pagename, 引入了资源文件common-->
    <meta id="i18n_pagename" content="common">
    <title>jQuery - 多语言翻译</title>
</head>
<body>
<!--  这里面class=”i18n”写法,下边在js里面我们可以根据类选择器获取需要国际化的地方,-->
<!--  然后name=”hi”这里面的 hi 跟我们语言包里面资源文件里面的key保持一致-->
    <label class="i18n" name="hi"></label>
    <br>
<!--切换按钮-->
    <select id="language">
        <option value="zh-CN">中文</option>
        <option value="en">英语</option>
        <option value="idn">印尼语语</option>
    </select>
</body>
<script src="./i18n/jquery.js"></script>
<script src="./i18n/jquery.i18n.properties.js.js"></script>
<script>
  /**
   * cookie操作
   */
  var getCookie = function(name, value, options) {
    if (typeof value != 'undefined') { // name and value given, set cookie
      options = options || {};
      if (value === null) {
        value = '';
        options.expires = -1;
      }
      var expires = '';
      if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
        var date;
        if (typeof options.expires == 'number') {
          date = new Date();
          date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
        } else {
          date = options.expires;
        }
        expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
      }
      var path = options.path ? '; path=' + options.path : '';
      var domain = options.domain ? '; domain=' + options.domain : '';
      var s = [cookie, expires, path, domain, secure].join('');
      var secure = options.secure ? '; secure' : '';
      var c = [name, '=', encodeURIComponent(value)].join('');
      var cookie = [c, expires, path, domain, secure].join('')
      document.cookie = cookie;
    } else { // only name given, get cookie
      var cookieValue = null;
      if (document.cookie && document.cookie != '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
          var cookie = jQuery.trim(cookies[i]);
          // Does this cookie string begin with the name we want?
          if (cookie.substring(0, name.length + 1) == (name + '=')) {
            cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
            break;
          }
        }
      }
      return cookieValue;
    }
  };
​
  /**
   * 获取浏览器语言类型
   * @return {string} 浏览器国家语言
   */
  var getNavLanguage = function(){
    if(navigator.appName == "Netscape"){
      var navLanguage = navigator.language;
      return navLanguage.substr(0,2);
    }
    return false;
  }
​
  /**
   * 设置语言类型: 默认为中文
   */
  var i18nLanguage = "zh-CN";
  /*
  设置一下网站支持的语言种类
   */
  var webLanguage = ['zh-CN', 'idn', 'en'];
​
  /**
   * 执行页面i18n方法
   * @return
   */
  var execI18n = function(){
    /*
    获取一下资源文件名
     */
    var optionEle = $("#i18n_pagename");
    if (optionEle.length < 1) {
      console.log("未找到页面名称元素,请在页面写入\n <meta id="i18n_pagename" content="页面名(对应语言包的语言文件名)">");
      return false;
    };
    var sourceName = optionEle.attr('content');
    sourceName = sourceName.split('-');
    /*
    首先获取用户浏览器设备之前选择过的语言类型
     */
    if (getCookie("userLanguage")) {
      i18nLanguage = getCookie("userLanguage");
    } else {
      // 获取浏览器语言
      var navLanguage = getNavLanguage();
      if (navLanguage) {
        // 判断是否在网站支持语言数组里
        var charSize = $.inArray(navLanguage, webLanguage);
        if (charSize > -1) {
          i18nLanguage = navLanguage;
          // 存到缓存中
          getCookie("userLanguage",navLanguage);
        };
      } else{
        console.log("not navigator");
        return false;
      }
    }
    /* 需要引入 i18n 文件*/
    if ($.i18n == undefined) {
      console.log("请引入i18n js 文件")
      return false;
    };
​
    /*
    这里需要进行i18n的翻译
     */
    jQuery.i18n.properties({
      name : sourceName, //资源文件名称
      path : 'i18n/' + i18nLanguage +'/', //资源文件路径
      mode : 'map', //用Map的方式使用资源文件中的值
      language : i18nLanguage,
      callback : function() {//加载成功后设置显示内容
        var insertEle = $(".i18n");
        console.log(".i18n 写入中...");
        insertEle.each(function() {
          // 根据i18n元素的 name 获取内容写入
          $(this).html($.i18n.prop($(this).attr('name')));
        });
        console.log("写入完毕");
​
        console.log(".i18n-input 写入中...");
        var insertInputEle = $(".i18n-input");
        insertInputEle.each(function() {
          var selectAttr = $(this).attr('selectattr');
          if (!selectAttr) {
            selectAttr = "value";
          };
          $(this).attr(selectAttr, $.i18n.prop($(this).attr('selectname')));
        });
        console.log("写入完毕");
      }
    });
  }
​
  /*页面执行加载执行*/
  $(function(){
    /*执行I18n翻译*/
    execI18n();
    /*将语言选择默认选中缓存中的值*/
    $("#language option[value="+i18nLanguage+"]").attr("selected",true);
    /* 选择语言 */
    $("#language").on('change', function() {
      var language = $(this).children('option:selected').val()
      console.log(language);
      getCookie("userLanguage",language,{
        expires: 30,
        path:'/'
      });
      location.reload();
    });
  });
</script>
</html>

如打开文件遇到跨域、403、第三方组件需要国际化问题可参考:jquery.i18n国际话及问题整理 - 隔壁丿老苏

在线示例/源码地址

在线示例:点此查看- jQuery - 多语言翻译

源码地址:github.com/Tzlibai/Dem…

Vue - 多语言翻译

Vue I18n 是 Vue.js 的国际化插件。它可以轻松地将一些本地化功能集成到你的 Vue.js 应用程序中。

由于官方文档表述的已经十分详细,这里就不做赘述;

该插件的解决方案也是基于多个国家配置不同的语言文案,通过对应key动态渲染。

vue.jpg

React - 多语言翻译

i18next 不仅仅是提供标准的 i18n 功能,例如(复数、上下文、插值、格式)。它为您提供了一个完整的解决方案,将您的产品从 Web 本地化到移动设备和桌面。

由于官方文档表述的已经十分详细,这里也就不做赘述;

该插件的解决方案也是基于多个国家配置不同的语言文案,通过对应key动态渲染。

react.jpg

番外

:lang选择器

:lang() CSS 伪类基于元素语言来匹配页面元素。:lang() 伪类选择器并不那么出名。但是,此伪类选择器非常酷,因为即使在元素外部声明了语言,它也可以根据 lang 属性识别内容的语言。

// html
<div lang="en"><q>This English quote has a <q>nested</q> quote inside.</q></div>
<div lang="fr"><q>This French quote has a <q>nested</q> quote inside.</q></div>
<div lang="de"><q>This German quote has a <q>nested</q> quote inside.</q></div>
​
// css
:lang(en) > q { color: red; }
:lang(fr) > q { color: blue; }
:lang(de) > q { color: pink; }
​

参考资料

结尾

好了,以上就是本篇全部文章内容啦。

如果遇到问题或者有其他意见可以在下方评论区贴出!

码字不易。如果觉得本篇文章对你有帮助的话,希望你可以留言点赞支持一下,非常感谢~

只要有树叶飞舞的地方,火就会燃烧,火的影子照耀着村子,新的树叶就会发芽。