vue项目i18n总结

由 漆黑菌 于 2020年05月13日 发布

安装i18n依赖

使用vue-i18n进行项目国际化,此处推荐使用vue-cli中的vue-cli-plugin-i18n安装

vue add i18n

安装过程中会有一些自定义选项,其中enableInSFC要引入单独的vue-i18n-loader,我选择false。

vs code上有个插件i18n Ally,对国际化很有帮助强烈推荐安装。

i18n Ally配置

官方推荐的可选项,打开以后可以强制提高不同翻译文件格式的同步率。

  "i18n-ally.sortKeys": true,

另一个推荐项i18n-ally.keepFulfilled会导致已保存的字段被替换为空字串,没搞清楚为什么还是先关闭了。

如果是vue-cli3+直接一套生成的目录,那么理论上不配置也是可以正常使用的,然而有个项目使用用在老版cli生成的,似乎因为这个原因需要单独配置一下。第一个选项更容易一层一层找到翻译文本,第二个选项可以让插件正确读取翻译文件的位置。这两个选项推荐放在工作区配置里,因为不同项目的结构很有可能是不同的,这样可以避免不必要的冲突。

    "i18n-ally.namespace": true,
    "i18n-ally.pathMatcher": "{locale}/**/{namespace}.json",

开发时要注意的约定

  1. 语言存储在哪里,是本地还是从浏览器读取,还是从服务器读取。本次采用的是从服务器读取。
  2. 统一语言描述口径,本次开发采用了en-US和zh-CN这种语言+地区的形式。此处需要注意有可能需要宽松匹配和语言回退,比如用户语言为en是不是直接视为en-US和zh-HK也在没做繁中的情况下是匹配到zh-CN上还是匹配到en-US上这样的也要事先约定好。
  3. 语言的加载顺序,比如默认加载英文当获取到用户信息后再加载中文,还是本地先根据语言环境预加载,再根据服务器返回语言信息进行矫正。本次开发选择的顺序是服务器返回语言信息后再进行vue渲染,如果接口出错或返回值不符合预期则英文作为默认语言。

翻译文件延迟加载

可以参考[vue-i18n延迟加载翻译官方文档](https://kazupon.github.io/vue-i18n/zh/guide/lazy-loading.html “延迟加载翻译 Vue I18n”)

项目中用的i18n异步加载实现

import Vue from 'vue';
import VueI18n from 'vue-i18n';
import axios from 'axios';

Vue.use(VueI18n);

const i18n = new VueI18n({
  locale: process.env.VUE_APP_I18N_LOCALE || 'en-US',
  fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en-US',
});

const loadedLanguages = [];// 'en-US','zh-CN'

function setI18nLanguage(lang) {
  i18n.locale = lang;
  axios.defaults.headers.common['Accept-Language'] = lang;
  document.documentElement.setAttribute('lang', lang);
  return lang;
}

function loadLanguageAsync(lang) {
  if (!['en-US', 'zh-CN'].includes(lang)) {
    // eslint-disable-next-line no-param-reassign
    lang = 'en-US';
  }
  if (loadedLanguages.includes(lang)) {
    return Promise.resolve(setI18nLanguage(lang));
  }
  /* eslint-disable-next-line */
  return import(/* webpackChunkName: "lang-[request]" */'@/locales/' + lang + '/index.js').then(
    (messages) => {
      i18n.setLocaleMessage(lang, messages.default);
      loadedLanguages.push(lang);
      return setI18nLanguage(lang);
    },
  );
}

export {
  i18n,
  loadLanguageAsync,
};

导出一个loadLanguageAsync方法就可以在main.js中进行国际化文件延迟加载了

async function queryUserLang() {
  return new Promise((resolve) => {
    setTimeout(() => resolve({ data: { lang: 'en-US' } }), 1000);// 模拟从服务器取回用户语言信息
  });
}

queryUserLang() //main.js是顶级作用域,在较新版本中才能使用await/async语法故使用promise
  .then(({ data }) => loadLanguageAsync(data.language))
  .catch(() => loadLanguageAsync('en-US')) //如果翻车就降级到英文
  .then(() => {
    new Vue({
      router,
      store,
      i18n,
      render: h => h(App),
    }).$mount('#app');
  });

在新版本浏览器中会使用rel="prefetch"进行预加载,所以理论上不同翻译文件还是都被下载了,不过这个资源会被标记为低优先级,当浏览器有空闲的时候才会预加载,到js执行延迟加载方法时状态才会被真正加载

<link href="/organization/js/lang-zh-CN-index-js.1060aa33.js" rel="prefetch">

实践中的教训

vue-i18n在9.x版本后更新了API

首先模板中$t并不默认注入了,需要使用启用globalInjection配置项。其次,使用$t处理复数时,参数顺序和$tc不同,而且$tc开启globalInjection后,默认也不注入。见[Component Injections Vue I18n](https://vue-i18n.intlify.dev/api/injection.html#t-key-named-plural “Component Injections Vue I18n”)
$tc('key',控制单复数num,{处理插槽的对象})
$t('key',{处理插槽的对象},控制单复数num)

翻译文件格式

翻译文件虽然要求是JSON形式,但是务必写在.js文件里,标准.json文件会执行标准JSON语法,key必须有双引号,结尾逗号语法必须严格,在编辑时会很麻烦。但是JS模式i18n Ally的支持并不好,所以第二次开发时做了权衡还是改成了JSON,靠ide自动format也还行吧。

样式调整

同样的意思中英文长度不一样(一般英文比较长),需要适当的调整元素宽度和多行对齐。

英文首字母大写

善用::first-lettertext-transform: uppercase可以使泛块级元素首字母大写(inline元素,flex元素就不行)。部分可能需要手动指定大写形式,比如组内组件库button是inline-flex就必须手动指定首字母大写

英文的单复数

使用$tc详情见相关文档[复数 Vue I18n](https://kazupon.github.io/vue-i18n/zh/guide/pluralization.html “复数 Vue I18n”)

获取当前vue-i18n语言

使用this.$i18n.locale

组件中props设置默认值

  props: {
    errorText: {
      type: String,
      default() {
        return this.$t('Page not found or no permission to access');
      },
    },
  },

在非vue中使用vue-i18n

如axios拦截器中使用,直接使用vue-i18n导出的i18n即可

import { i18n } from '../i18n';

axios.interceptors.response.use(response => response, (error) => {
  if (error.response) {
    if (error.response.status === 401 && logoutFlag) {
      logout();
      logoutFlag = false;
    }
    window.Noty.error(error.response.data.message);
  } else if (error.request) {
    window.Noty.error(i18n.t('request error'));
  }

  return Promise.reject(error);
});

提取中文需要的正则

vs code中

[\u4e00-\u9fa5]{1,}\.*[\u4e00-\u9fa5]

node中

const fs = require('fs');
const path = require('path');
const Path = path.resolve('./src');
function fileDisplay(filePath) {
  fs.readdir(filePath, function(err, files) {
    if (err) {
      console.warn(err);
    } else {
      files.forEach(function(filename) {
        var filedir = path.join(filePath, filename);
        fs.stat(filedir, function(eror, stats) {
          if (eror) {
            console.warn('获取文件stats失败');
          } else {
            var isFile = stats.isFile();
            var isDir = stats.isDirectory();
            if (isFile) {
              console.log(filedir);
              appendContentSync(filedir);
            }
            if (isDir) {
              fileDisplay(filedir);
            }
          }
        });
      });
    }
  });
}
function appendContentSync(file) {
  let content = fs.readFileSync(file, 'utf-8');
  const result = content.match(/[\x{4e00}-\x{9fa5}]{1,}.*[\x{4e00}-\x{9fa5}]/g);
  if(result) {
    fs.appendFileSync('./zh.txt', file + '\n' + Array.from(new Set(result)).join('\n') + '\n\n');
  }
}
fileDisplay(Path);

浏览器languagechange事件

在系统使用浏览器语言时,可以在浏览器切换语言时触发系统的重新设置语言逻辑。