国际化(i18n)的原理
国际化(Internaltionalization)经常被用i18n简称,这是因为从国际化这个词从i到n之间有18个字母,为了避免打过多字就被简化为了i18n。国际化使得任何网站文本不是hard code在代码之中,因此在日后国际化的过程中不需要对原先的代码进行修改而只需要添加对应的文本翻译即可。
国际化的实现一种常用的方法是根据用户的语言设置偏好,将网页模版中的占位字符串替换成对应的语言翻译。比如可以在项目的根目录下设置/locales这样一个文件夹,里面放入以键值对形式存储的json string。如果用户的语言偏好设置是:zh_CN (中文),那么遇到模版的welcome占位符的时候,就替换为“欢迎”,此外还可以有其他不同语言的翻译。
一个通常的locale json文件格式如下:
//zh_CN.json:
{
"welcome": "欢迎!!"
}
基于用户登录状态、系统偏好设置的4层备选(Fallback)结构
刚才提到了用户的语言偏好设置为中文,那么偏好设置如何获取呢?假定一个具有用户账户系统的网站,一般来说有以下4个来源,依次作为上一个层次失效时的备选方案(Fallback):
- 登录用户账户的设置
- 用户cookie中存储的locale信息
- 用户浏览器的默认语言
- 网站的默认语言
在用户登录的情况下提取用户的语言偏好。如果检查到用户没有登录,但是cookie中保存了用户上次访问时设置的语言偏好,那么使用cookie中的语言偏好。如果用户的cookie中并没有关于locale的信息(一般来说是用户第一次访问或者清除了cookie数据),那么退一步使用用户浏览器的语言偏好,这一信息可以从请求头的Accept-language中提取。最后作为不得已在用户未发送任何信息的情况下,使用网站默认语言作为用户的偏好设置。
其中,使用1、3、4的情况下,服务器端应在最后一步尝试设置浏览器的Cookie数据,以保证用户在下一次访问、或者切换登录状态时仍然能够按照之前的偏好设置获取网站服务。
使用i18n-node和handlebars的实现
nodejs的i18n-node提供了非常简单的i18n中间件,并且支持多种的模版实现。如果大家搜索可能还会看到i18n-node2这个package。它是对于早期i18n-node的一个扩展,由Jquery的John Resig实现,目前的i18n-node已经基本包含了这些内容。
下面的代码展示了如何使用i18n-node module结合handlebars的helper函数来实现基本的i18n。
这里的一个基本假设是,服务器处理i18n是在获取了用户登录信息之后。关于如何使用express-session保存用户登录状态以提供连续的服务,可以参考《Session原理、安全以及最基本的Express和Redis实现》这篇文章。
//server.js
var express = require('express'),
session = require('express-session'),
handlebars = require('express-handlebars'),
i18n = require('i18n'),
app = express();
//配置i18n
i18n.configure({
locales:['en', 'zh-CN'], //声明包含的语言
directory: __dirname + '/locales', //翻译json文件的路径
defaultLocale: 'en' //默认的语言,即为上述标准4
});
// 设置app的模版引擎
app.engine('hbs', handlebars.create({
layoutsDir: 'templates/layouts',
partialsDir: 'templates/partials',
defaultLayout: 'default',
helpers: new require('helpers')(),
extname: '.hbs'
}).engine);
// 添加session处理的中间件
app.use(session({
secret: 'express is powerful'
}));
// 添加setLocale中间件,注意必须在session之后
app.use(setLocale);
// 定义setLocale中间件
function setLocale(req, res, next){
var locale;
// 当req进入i18n中间件的时候,已经通过sessionId信息获取了用户数据
// 获取用户数据中的locale数据
if(req.user){
locale = req.user.locale;
}
// 获取cookie中的locale数据
else if(req.signedCookies['locale']){
locale = req.signedCookies['locale'];
}
// 获取浏览器第一个偏好语言,这个函数是express提供的
else if(req.acceptsLanguages()){
locale = req.acceptsLanguages();
}
// 没有语言偏好的时候网站使用的语言为中文
else{
locale = 'zh_CN';
}
// 如果cookie中保存的语言偏好与此处使用的语言偏好不同,更新cookie中的语言偏好设置
if(req.signedCookies['locale'] !== locale){
res.cookie('locale', locale, { signed: true, httpOnly: true });
}
// 设置i18n对这个请求所使用的语言
req.setLocale(locale);
next();
};
// 启动http server...省略部分代码
//helpers.js
var i18n = require('i18n');
module.exports = function() {
var _helpers = {};
// 声明handlebar中的i18n helper函数
// __函数不考虑单复数
_helpers.__ = function () {
return i18n.__.apply(this, arguments);
};
// __n函数考虑单复数
_helpers.__n = function () {
return i18n.__n.apply(this, arguments);
};
return _helpers;
};
// templates/index.hbs
{{{__ "welcome"}}}
这样就可以渲染出有“欢迎”两个字的页面了。这只是i18n-node最简单的用法,大家可以去i18n-node的Github页面看更多API信息
另外给大家推荐偶尔发现的一个网站Transifex,它使用众包的方式帮助你的网站做国际化,比起自己找朋友翻译,这个网站提供的方式是收集世界各地有兴趣来翻译的爱好群体帮你翻译,可以获得的语种和可靠性都还不错的样子。
https://www.transifex.com/