安装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",
开发时要注意的约定
- 语言存储在哪里,是本地还是从浏览器读取,还是从服务器读取。本次采用的是从服务器读取。
- 统一语言描述口径,本次开发采用了en-US和zh-CN这种语言+地区的形式。此处需要注意有可能需要宽松匹配和语言回退,比如用户语言为en是不是直接视为en-US和zh-HK也在没做繁中的情况下是匹配到zh-CN上还是匹配到en-US上这样的也要事先约定好。
- 语言的加载顺序,比如默认加载英文当获取到用户信息后再加载中文,还是本地先根据语言环境预加载,再根据服务器返回语言信息进行矫正。本次开发选择的顺序是服务器返回语言信息后再进行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-letter
和text-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事件
在系统使用浏览器语言时,可以在浏览器切换语言时触发系统的重新设置语言逻辑。