2016-02-28 10 views
0

Существует довольно обычная проблема для реализации i18n в веб-проекте node.js. Проблема, кажется, еще хуже, если вы хотите:Учебное пособие: решение для node.js/Polymer i18n на основе библиотеки L20n

  1. использовать веб-компоненты (как Polymer)
  2. использование одного файла перевод на стороне сервера и на стороне клиента файлы
  3. перевести некоторые элементы programmaticaly (например, динамически создаваемые строки)

Благодаря совершенно новому L20n library, разработанному командой Mozilla, эта проблема может быть решена довольно легко.

ответ

0

Структуры проекта

Сначала я создал структуру проекта, который будет держать мои файлы separatedly, сгруппированный по их назначению:

. 
+-- 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 

Идея проста: app-l20n-node используются в качестве модуля для локализации всех серверов -side jobs, app-l20n является полимерным компонентом для пользовательского интерфейса l10n.

Установка

Run npm install l20n --save
Текущая версия 3.5.1 и имеет небольшую ошибку. Основной файл l20n - ./dist/compat/node/l20n.js, и он имеет две обязательные переменные, которые не используются нигде в коде, но могут раздавить ваше приложение при запуске, поскольку они упоминаются только в Devdependencies библиотеки. Чтобы избежать этого, я просто прокомментировал их прямо в код библиотеки:

//var string_prototype_startswith = require('string.prototype.startswith'); 
//var string_prototype_endswith = require('string.prototype.endswith'); 

Перевод файлы

Я создал файлы перевод в моих /public/locales/ папку, названных как app.ru.l20n и app.en.l20n. Согласно L20n правил, содержимое файлов выглядит следующим образом:

<foo "Foo translation"> 
<bar "Bar translation"> 
<register[$variant] { 
    infinitive: "Register now!" 
}> 

Node.js + L20n

Теперь пришло время для создания модуля узла для L20n. В моем случае код app_modules\app-l20n-node\index.js выглядит следующим образом:

'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); 
}; 

Теперь мы можем использовать этот модуль в нашем Node.js коде и получить перевод, запрашиваемые ключами и локал. Вместо жестко кодированного языка я использую express-sessions и сохраняю пользовательский локаль как атрибут сеанса req.session.locale. Но все зависит от проекта.

var l20n = require('../../app_modules/app-l20n-node'); 

l20n(['foo','bar'], 'en') 
    .then((t)=>{ 
     console.log(t); // ["Foo translation", "Bar translation"] 
    }); 

Полимер + L20n

Теперь мы должны создать полимерный компонент для L20n.
Во-первых, добавить библиотеку и ссылку на файлы перевода на ваш HTML <head>:

<script src="/node_modules/l20n/dist/compat/web/l20n.js"></script> 
<link rel="localization" href="/locales/app.{locale}.l20n"> 

Теперь пришло время, чтобы создать полимерный компонент app-l20n.html с новым поведением.

<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> 

Нет, поскольку новое поведение готово, мы можем реализовать его в наших пользовательских компонентах Polymer. Вы можете получить перевод программным путем с помощью Polymer Behavior или с помощью атрибутов атрибутов пользовательских тегов L20n.

<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> 

Надеюсь, что этот небольшой учебник был бы полезен.