问题
There is a rather usual problem to implement i18n in node.js
web project. The problem seems even worse if you want to:
- use web components (like Polymer)
- use single translation file for server-side and client-side files
- translate some items programmaticaly (like dynamically created strings)
Thanks to brand-new L20n library developed by Mozilla team this problem can be solved rather easily.
回答1:
Project structure
First I created a project structure, that would keep my files separatedly, grouped by their purpose:
.
+-- app.js
+-- piblic
| +-- locales
| +-- app.ru.l20n
| +-- app.en.l20n
|
+-- node_models
| +-- l20n
|
+-- bower_components
| +-- Polymer libraries
|
+-- app_modules
| +-- app-l20n-node
| +-- index.js
|
+-- app_components
+-- app-l20n
+-- app-l20n.html
+-- app-custom-component
+-- app-custom-component.html
The idea is simple: app-l20n-node
is used as module to localize all server-side jobs, app-l20n
is a Polymer component for user interface l10n.
Installation
Run npm install l20n --save
Current version is 3.5.1 and it has a small bug. Main file of l20n is ./dist/compat/node/l20n.js
and it has two required variables, that are not used anywhere in code, but can crush your app on launch, as they are mentioned only in Devdependencies of the library. To avoid it I just commented them right into the library code:
//var string_prototype_startswith = require('string.prototype.startswith');
//var string_prototype_endswith = require('string.prototype.endswith');
Translation files
I created translation files in my /public/locales/
folder, named like app.ru.l20n
and app.en.l20n
. According to L20n rules, the contents of files look like:
<foo "Foo translation">
<bar "Bar translation">
<register[$variant] {
infinitive: "Register now!"
}>
Node.js + L20n
Now it's time to create a node module for L20n.
In my case the code of app_modules\app-l20n-node\index.js
looks like:
'use strict';
const L20n = require('l20n');
var path = require('path');
module.exports = function(keys, lang){
const env = new L20n.Env(L20n.fetchResource);
// Don't forget nice debug feature of L20n library
env.addEventListener('*', e => console.log(e));
// I suppose that I'll always provide locale code for translation,
// but if it would not happen, module should use preset
var langs = [];
if(!lang) {
// you should define locales here
langs = [{code: 'ru'}, {code: 'en'}]
} else {
langs = [{code: lang}]
}
// set context, using path to locale files
const ctx = env.createContext(langs,
[path.join(__dirname, '../../public/locales/app.{locale}.l20n')]);
const fv = ctx.formatValues;
return fv.apply(ctx, keys);
};
Now we can use this module in our node.js code and get translation, requested by keys and locale. Instead of hard-coded locale I use express-sessions
and store user-defined locale as session attribute req.session.locale
. But it all depends on project.
var l20n = require('../../app_modules/app-l20n-node');
l20n(['foo','bar'], 'en')
.then((t)=>{
console.log(t); // ["Foo translation", "Bar translation"]
});
Polymer + L20n
Now we should create a Polymer component for L20n.
First, add library and link to translation files to your html <head>
:
<script src="/node_modules/l20n/dist/compat/web/l20n.js"></script>
<link rel="localization" href="/locales/app.{locale}.l20n">
Now it's time to create a Polymer component app-l20n.html
with a new behavior.
<script>
/**
* Create namespace for custom behavior or use existing one.
*/
window.MB = window.MB || {};
MB.i18n = {
/**
* Use l20n.js to translate certain strings
* @param component A Polymer component, usually "this.translate(this, props);"
* @param props An array of keys to translate: strings or arrays.
*/
translate: function(component, props) {
var view = document.l10n;
var promise = view.formatValues.apply(view, props);
promise.then(function(args){
for (var i in args){
var prop = props[i];
// strings with parameters represented by arrays:
// ["string", {param: value}]
if (Array.isArray(prop)) {
// get property name - usually the same, as translation key
// so the object would have properties obj.Foo and obj.Bar
var propName = prop[0];
// if it is needed to create multiple translations of the same
// string in one component, but with different parameters,
// the best way is to use suffix:
// ["string", {param: value}, "_suffix"]
if (prop.length == 3) propName = propName + prop[2];
component.set(propName, args[i]);
}
// common strings
else component.set(prop, args[i]);
}
});
}
};
</script>
No, as the new behavior is ready, we can implement it in our custom Polymer components. You can get translation programmatically by Polymer Behavior or using L20n custom tag attributes feature.
<link rel="import" href="/bower_components/polymer/polymer.html">
<link rel="import" href="/bower_components/gold-email-input/gold-email-input.html">
<link rel="import" href="/bower_components/paper-button/paper-button.html">
<link rel="import" href="/app_components/app-l20n/app-l20n.html">
<dom-module id="app-custom-component">
<template>
<gold-email-input
auto-validate
required
name="email"
value="{{Foo}}"
label="{{Bar}}">
</gold-email-input>
<paper-button onclick="regFormSubmit(event)">
<iron-icon icon="perm-identity"></iron-icon>
<span data-l10n-id="Register" data-l10n-args='{"variant": "infinitive"}'></span>
</paper-button>
</template>
<script>
function regFormSubmit(event){}
Polymer({
is: 'app-custom-component',
behaviors: [
MB.i18n
],
ready: function(){
this.$.passwordValidator.validate = this._validatePasswords.bind(this);
// add your component properties to array. They can be simple like in this example, or
// more complex, with parameters: ["Some_key", {param: "xxx"}].
// you can even translate the same string in different properties, using custom suffix:
// ["Some_key", {param: "yyy"}, "_suffix"] and place it in template with shortcut: {{Some_key_suffix}}
var translateProps = ["Foo", "Bar"];
// now translate, using behavior
this.translate(this, translateProps);
}
});
</script>
</dom-module>
Hope this little tutorial would be helpful.
来源:https://stackoverflow.com/questions/35677198/tutorial-solution-for-node-js-polymer-i18n-based-on-l20n-library