Tutorial: solution for node.js / Polymer i18n based on L20n library

荒凉一梦 提交于 2019-12-20 07:04:16

问题


There is a rather usual problem to implement i18n in node.js web project. The problem seems even worse if you want to:

  1. use web components (like Polymer)
  2. use single translation file for server-side and client-side files
  3. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!