BEM internationalization for ENB
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
exlib
lib
techs
test
.bowerrc
.editorconfig
.gitignore
.jscs.js
.jshintignore
.jshintrc
.npmignore
.travis.yml
CHANGELOG.md
CONTRIBUTORS.md
LICENSE.txt
README.md
api.en.md
api.ru.md
appveyor.yml
bower.json
package.json

README.md

enb-bem-i18n

NPM version Build Status Build status Coverage Status devDependency Status

Пакет предоставляет набор ENB-технологий для сборки файлов, обеспечивающих мультиязыковую поддержку БЭМ-проектов. Под мультиязыковой поддержкой понимается интернационализация (далее по тексту также i18n).

С помощью технологий пакета enb-bem-i18n осуществляется сборка модуля для интернационализации (i18n) вашего проекта.

Технологии пакета enb-bem-i18n:

  • keysets — служебная технология для сборки исходных файлов с переводами.
  • i18n — технология для формирования бандлов с переводами для каждого языка.
  • keysets-xml — технология для локализации сервисов, использующих шаблонизатор XSLT.

Принципы работы технологий и их API описаны в документе API технологий.

Совместимость: пакет поддерживает следующие библиотеки:

Особенности реализации для каждой библиотеки описаны в разделе Различия в использовании i18n в bem-bl и bem-core.

Установка

Установите пакет enb-bem-i18n:

npm install --save-dev enb-bem-i18n

Требования: пакет enb версии 0.15.0 или выше.

Обзор документа

Быстрый старт

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

var I18NTech  = require('enb-bem-i18n/techs/i18n'),
    KeysetsTech = require('enb-bem-i18n/techs/keysets'),
    FileProvideTech = require('enb/techs/file-provider'),
    bemTechs = require('enb-bem-techs');

module.exports = function(config) {
    config.setLanguages(['en', 'ru']);

    config.node('bundle', function(node) {
        // Получаем FileList
        node.addTechs([
            [FileProvideTech, { target: '?.bemdecl.js' }],
            [bemTechs.levels, { levels: ['blocks'] }],
            [bemTechs.deps],
            [bemTechs.files]
        ]);

        // Собираем keyset-файлы для каждого языка
        node.addTech([KeysetsTech, { lang: '{lang}' }]);

        // Собираем i18n-файлы для каждого языка
        node.addTech([I18NTech, { lang: '{lang}' }]);
        node.addTarget('?.lang.{lang}.js');
    });
};

Принцип работы пакета enb-bem-i18n

В основе работы пакета enb-bem-i18n лежит библиотека для интернационализации — ядро i18n. Изначально ядро не содержит данных с переводами, оно наполняется данными (инициализируется) из keysets-файлов.

Результатом работы технологии i18n является [функция i18n](#Обработка данных), которая общается с ядром и позволяет получить конкретное значение (строку) для указанного языка. Функция i18n может вызываться из шаблонов или клиентского JavaScript-кода.

Основные понятия

Исходные данные — keysets

keysets — исходные файлы с переводами для поддержки интернационализации. Перевод представляет собой набор ключей и их значений. Ключ определяет, какое из значений должно быть выбрано для указанного языка.

Пример для русского языка:

{
    hello: 'Привет!'
}

Пример для английского языка:

{
    hello: 'Hello!'
}

Набор данных ключ: 'значение' передается с указанием контекста (scope). Обычно контекстом служит имя блока.

Пример keysets-файла для русского языка:

module.exports = {
    greeting: {
        hello: 'Привет!'
    }
};

Расположение в файловой системе

Переводы (keysets) хранятся в файлах <lang>.js (например, en.js).

Файлы <lang>.js для каждой БЭМ-сущности находятся в отдельной директории <block-name>.i18n наряду с другими файлами технологий.

block/
    block.css
    block.js
    block.i18n/
        ru.js        # Исходный файл с переводом для русского языка.
        en.js        # Исходный файл с переводом для английского языка.

Также есть возможность объединять одинаковые для всех языков переводы в общие файлы:

  • В bem-bl — в файл all.js.
  • В bem-core — в файл <block-name>.i18n.js.
common.blocks/
    block1/
        block1.css
        block1.js
        block1.i18n.js       # Исходный файл с переводом, содержащий
                             # общие переводы.
                             # Может содержать ядро `i18n` для библиотеки ` bem-core`.
        block1.i18n/         # Директория для хранения файлов с переводами для разных языков.
            en.js
            ru.js
            all.js           # Исходный файл с переводом (для
                             # русского и английского языков).
                             # Может содержать ядро `i18n` для библиотеки `bem-bl`.

Ядро i18n

Ядро i18n — это библиотека для интернационализации. Ядро находится в keysets-файлах (<block-name>.i18n.js или <block-name>.all.js) в одной из базовых библиотек блоков:

  • В bem-bl — файл all.js.
  • В bem-core — файл <block-name>.i18n.js.

Пакет enb-bem-i18n поддерживает разные реализации ядра интернационализации для библиотек bem-bl и bem-core.

  • В bem-bl — ядро BEM.I18N.
  • В bem-core — ядро i18n.

Далее по тексту для названия ядра будет использоваться i18n.

Подробнее о расположении keysets-файлов, содержащих ядро, в файловой системе читайте в разделе Расположение в файловой системе.

Ядро i18n в библиотеках bem-core и bem-bl хранится в keysets-файлах по-разному:

  • В bem-bl (файл all.js):

    {
        all: {
            '': { // пустая строка
                /* код ядра */
            }
        }
    }
  • В bem-core (файл i18n.js):

    {
        i18n: {
            i18n: {
                /* код ядра */
            }
        }
    }

Перед использованием ядро должно быть инициализировано данными из keysets-файлов.

Важно! Для получения ядра необходимо добавить mustDeps-зависимость блокам, которые используют i18n.

  • Для bem-bl:

    ({
        mustDeps: { block: 'i-bem', elem: 'i18n' }
    })
  • Для bem-core:

    ({
        mustDeps: { block: 'i18n' }
    })

>Подробно про API использования ядра `i18n` читайте в разделе [API `i18n`](#api-i18n).

>Примеры всех вариантов использования ядра рассмотрены в [тестах к технологии](https://github.com/enb/enb-bem-i18n/blob/master/test/techs/i18n/).

## Описание работы с технологиями

Данные из keysets-файлов `<lang>.js` во время сборки проходят несколько этапов:

* [Объединение данных исходных файлов в один для указанного языка](#Объединение-данных)
* [Обработка данных из объединенного файла](#Обработка-данных)
* [Сборка шаблонов](#Сборка шаблонов)
* [Сборка только необходимых переводов](#Сборка-только-необходимых-переводов)

### Объединение данных

Технология [keysets](api.ru.md#keysets) объединяет исходные файлы `<lang>.js` для каждого языка в общий файл (`?.keysets.<lang>.js`). Набор языков, для которых будут собраны `?.keysets.<lang>.js`-файлы, задается с помощью опции [lang](api.ru.md#lang) в конфигурационном файле (`.enb/make.js`).

`?.keysets.<lang>.js`-файл — это промежуточный результат сборки, который в дальнейшем используется технологией [i18n](api.ru.md#i18n).

Например, для блоков `greeting` и `login` результирующий `?.keysets.en.js`-файл будет собран следующим образом.

Исходный файл `en.js` блока `greeting`:

```js
module.exports = {
  greeting: {
      hello: 'Hello',
      unknown: 'stranger'
  }
};

Исходный файл en.js блока login:

module.exports = {
  login: {
      login: 'Login',
      pass: 'Password'
  }
};

Результирующий ?.keysets.en.js-файл:

module.exports = {
  greeting: {
      hello: 'Hello',
      unknown: 'stranger'
  },
  login: {
      login: 'Login',
      pass: 'Password'
  }
};

Обработка данных

Данные из объединенного файла ?.keysets.<lang>.js обрабатываются технологией i18n. Результатом работы является функция i18n, которая при вызове из шаблонов или клиентского JavaScript принимает ключ и отдает значение (строку) для конкретного языка.

API взаимодействия с ядром i18n описан в разделе API i18n. В результате работы технологии i18n являются lang.<lang>.js-файлы, содержащие строки переводов, соответствующие запрошенным ключам.

Сборка шаблонов

Для сборки интернационализированных шаблонов необходимо отдельно собрать шаблоны, отдельно i18n-файлы, а потом склеить их попарно для каждого языка.

index.bemhtml.js
index.lang.en.js
index.lang.ru.js

index.en.bemhtml.js  # index.lang.en.js + index.bemhtml.js
index.ru.bemhtml.js  # index.lang.ru.js + index.bemhtml.js

После подключения BEM.I18N как сторонней библиотеки ее можно использовать:

  • в BEMHTML-шаблонах с помощью метода this.require();
  • в BH — из пространства имен bh.lib.

Подробнее о том, как подключаются сторонние библиотеки смотрите в документации к пакетам enb-bemxjst и enb-bh.

Файлы i18n нужно собирать так, чтобы i18n-функция была доступна из переменной BEM.I18N в любой среде исполнения. Для этого следует использовать опцию exports со значением { globals: 'force' }.

Пример сборки BEMHTML и BH шаблонов

var I18NTech  = require('enb-bem-i18n/techs/i18n'),
    KeysetsTech = require('enb-bem-i18n/techs/keysets'),
    BEMHTMLTech = require('enb-bemxjst/techs/bemhtml'),
    BHTech = require('enb-bh/techs/bh-bundle'),
    FileProvideTech = require('enb/techs/file-provider'),
    FileMergeTech = require('enb/techs/file-merge'),
    bemTechs = require('enb-bem-techs');

module.exports = function(config) {
    config.setLanguages(['en', 'ru']);

    config.node('bundle', function(node) {
        // Получаем FileList
        node.addTechs([
            [FileProvideTech, { target: '?.bemdecl.js' }],
            [bemTechs.levels, { levels: ['blocks'] }],
            [bemTechs.deps],
            [bemTechs.files]
        ]);

        // Собираем keyset-файлы для каждого языка
        node.addTech([KeysetsTech, { lang: '{lang}' }]);
        // Собираем i18n-файлы для каждого языка
        node.addTech([I18NTech, {
            lang: '{lang}',
            exports: { globals: 'force' }
        }]);

        // Собираем BEMHTML-шаблоны.
        // Подключаем `BEM.I18N` как стороннюю библиотеку.
        // В шаблонах `i18n`-функция будет доступна c помощью метода `this.require('i18n')`.
        node.addTech([BEMHTMLTech, {
            requires: {
                i18n: { globals: 'BEM.I18N' }
            }
        }]);

        // Объединяем скомпилированный BEMHTML-файл с i18n-файлами для каждого языка
        node.addTech([FileMergeTech, {
            target: '?.{lang}.bemhtml.js',
            lang: '{lang}',
            sources: ['?.bemhtml.js', '?.lang.{lang}.js']
        }]);
        node.addTarget('?.{lang}.bemhtml.js');


        // Собираем BH-шаблоны.
        // Подключаем `BEM.I18N` как стороннюю библиотеку.
        // В шаблонах `i18n`-функция будет доступна из `bh.lib.i18n`.
        node.addTech([BHTech, {
            requires: {
                i18n: { globals: 'BEM.I18N' }
            }
        }]);

        // Объединяем скомпилированный BH-файл с i18n-файлами для каждого языка
        node.addTech([FileMergeTech, {
            target: '?.{lang}.bh.js',
            lang: '{lang}',
            sources: ['?.bh.js', '?.lang.{lang}.js']
        }]);
        node.addTarget('?.{lang}.bh.js');
    });
};

Сборка только необходимых переводов

Если в браузере используется только часть переводов (например, когда остальные переводы применяются при шаблонизации в Node.js), то для экономии можно собрать только необходимое.

Для этого в файлах зависимостей укажите дополнительную информацию о технологиях, которые используют переводы.

  • При использовании в JavaScript-коде блоков в deps.js-файл необходимо добавить зависимость для js-технологии.

    {
        tech: 'js'
        shouldDeps: {
            tech: 'i18n'
        }
    }

    Такая запись означает, что js-технология блока зависит от технологии i18n этого же блока. Иначе говоря, в JavaScript-коде, предназначенном для работы в браузере, используются переводы.

    Важно! Если в браузер должны попасть все переводы без исключения, то такая запись не обязательна.

  • При использовании в коде шаблонов в deps.js-файл необходимо добавить зависимость для bemhtml- или bh-технологии.

    {
        tech: 'bemhtml'
        shouldDeps: {
            tech: 'i18n'
        }
    }

    Важно: если в собранные шаблоны должны попасть все переводы без исключения, то такая запись не обязательна.

На основе этой информации в процессе сборки можно составить список БЭМ-сущностей, переводы которых необходимы для работы в браузере.

Для сборки на основе зависимостей по технологиям понадобится depsByTechToBemdecl из пакета enb-bem-techs.

Пример сборки i18n для работы в браузере

var I18NTech  = require('enb-bem-i18n/techs/i18n'),
    KeysetsTech = require('enb-bem-i18n/techs/keysets'),
    FileProvideTech = require('enb/techs/file-provider'),
    bemTechs = require('enb-bem-techs');

module.exports = function (config) {
    config.setLanguages(['en', 'ru']);

    config.node('bundle', function () {
        // Получаем FileList
        node.addTechs([
            [bemTechs.levels, { levels: ['blocks'] }],
            [FileProviderTech, { target: '?.bemdecl.js' }],
            [bemTechs.deps],
            [bemTechs.files]
        ]);

        // Получаем декларацию `?.i18n.bemdecl.js`, содержащую список только необходимых БЭМ-сущностей
        // для работы i18n в браузере
        node.addTech([bemTechs.depsByTechToBemdecl, {
            target: '?.browser-i18n.bemdecl.js',
            sourceTech: 'js',
            destTech: 'i18n'
        }]);
        node.addTarget('?.browser-i18n.bemdecl.js');

        // Получаем список необходимых файлов с переводами
        node.addTechs([
            [bemTechs.deps, {
                target: '?.browser-i18n.deps.js',
                bemdeclFile: '?.browser-i18n.bemdecl.js'
            }],
            [bemTechs.files, {
                filesTarget: '?.browser-i18n.files',
                dirsTarget: '?.browser-i18n.dirs',
                depsFile: '?.browser-i18n.deps.js'
            }]
        ]);

        // Собираем keyset-файлы для каждого языка
        node.addTech([KeysetsTech, {
            filesTarget: '?.browser-i18n.files',
            lang: '{lang}'
        }]);

        // Собираем i18n-файлы для каждого языка
        node.addTech([I18NTech, { lang: '{lang}' }]);
        node.addTarget('?.lang.{lang}.js');
    });
};

Для сборки клиенских шаблонов, которые используют i18n можно воспользоваться информацией о JavaScript-зависимостях от технологии шаблонов.

{
    tech: 'js'
    shouldDeps: {
        tech: 'bemhtml' // или `bh`
    }
}

Пример сборки i18n для работы в браузере JavaScript-кода и шаблонов

var I18NTech  = require('enb-bem-i18n/techs/i18n'),
    KeysetsTech = require('enb-bem-i18n/techs/keysets'),
    FileProvideTech = require('enb/techs/file-provider'),
    bemTechs = require('enb-bem-techs');

module.exports = function (config) {
    config.setLanguages(['en', 'ru']);

    config.node('bundle', function () {
        // Получаем FileList
        node.addTechs([
            [bemTechs.levels, { levels: ['blocks'] }],
            [FileProviderTech, { target: '?.bemdecl.js' }],
            [bemTechs.deps],
            [bemTechs.files]
        ]);

        // Получаем декларацию, содержащую список только необходимых БЭМ-сущностей
        // для работы в браузере BEMHTML, который использует i18n.
        node.addTechs([
            // декларация для работы i18n в браузере
            [bemTechs.depsByTechToBemdecl, {
                target: '?.browser-i18n.bemdecl.js',
                sourceTech: 'js',
                destTech: 'i18n'
            }],
            // декларация для работы BEMHTML в браузере
            [bemTechs.depsByTechToBemdecl, {
                target: '?.browser-bemhtml.bemdecl.js',
                sourceTech: 'js',
                destTech: 'bemhtml'
            }],
            // объединяем декларации
            [bemTechs.mergeBemdecl, {
                sources: ['?.browser-bemhtml.bemdecl.js', '?.browser-i18n.bemdecl.js'],
                target: '?.browser-bemhtml+i18n.bemdecl.js'
            }]
        ]);

        // Получаем список необходимых файлов с переводами
        node.addTechs([
            [bemTechs.deps, {
                target: '?.browser-bemhtml+i18n.deps.js',
                bemdeclFile: '?.browser-bemhtml+i18n.bemdecl.js'
            }],
            [bemTechs.files, {
                filesTarget: '?.browser-bemhtml+i18n.files',
                dirsTarget: '?.browser-bemhtml+i18n.dirs',
                depsFile: '?.browser-bemhtml+i18n.deps.js'
            }]
        ]);

        // Собираем keyset-файлы для каждого языка
        node.addTech([KeysetsTech, {
            filesTarget: '?.browser-bemhtml+i18n.files',
            lang: '{lang}'
        }]);

        // Собираем i18n-файлы для каждого языка
        node.addTech([I18NTech, { lang: '{lang}' }]);
        node.addTarget('?.lang.{lang}.js');
    });
};

Использование

Функция i18n может использоваться:

В JavaScript

Cпособы использования i18n в JavaScript зависят от наличия модульной системы в проекте и ее типа. Файлы могут подключаться как в Node.js, так и в браузере, независимо от используемой библиотеки (bem-bl или bem-core).

Использование в Node.js

Скомпилированный файл можно подключить как модуль в формате CommonJS.

var i18n = require('bundle.lang.en.js'); // Путь до скомпилированного файла

i18n('scope', 'key'); // 'val'

Использование в браузере

В браузере применение скомпиллированных ?.lang.<lang>.js-файлов зависит от наличия модульной системы:

  • В браузере без YModules как BEM.I18N

    BEM.I18N('greeting', 'hello'); // Ядро `i18n` предоставляется в глобальную видимость в переменную `BEM.I18N`.
  • В браузере с YModules как i18n-модуль

    modules.require('i18n', function (i18n) {
        i18n('scope', 'key'); // 'val'
    });

Важно! В проект с модульной системой ядро библиотеки интернационализации подключаются, как модуль i18n, вне зависимости от используемой библиотеки bem-core или bem-bl.

В шаблонах

Способы использования i18n-функции зависят от сборки шаблонов.

BEMHTML

После подключения BEM.I18N как сторонней библиотеки ее можно использовать в шаблонах с помощью метода this.require.

block('button').elem('tooltip').content()(function () {
    var i18n = this.require('i18n'),  // Библиотека `BEM.I18N`

    // Локализованное значение для ключа `tooltip`
    return i18n('button', 'tooltip');
});

BH

После подключения BEM.I18N как сторонней библиотеки ее можно использовать в шаблонах из пространства имен bh.lib.

bh.match('block', function (ctx) {
    ctx.content({
        elem: 'tooltip',
        content: bh.lib.i18n('block', 'tooltip');
    });
});

API i18n

Описание взаимодействия с ядром i18n, результатом которого являются локализованные строки.

Инициализация

В разделе рассмотрены примеры инициализации ядра на абстрактных файлах для библиотек bem-bl и bem-core.

  • В bem-bl

    var core = /* ... */,
        keyset = {
            hello: 'Привет!'
        },
        lang = 'ru';
    
    core.decl('greeting', keyset, lang);
    core.lang(lang);
    
    core('greeting', 'hello'); // Привет!
  • В bem-core

    var core = /* ... */,
        keysets = {
            greeting: {
                hello: "Привет!"
            }
        };
    
    var i18n = core().decl(keysets);
    
    i18n('greeting', 'hello'); // Привет!

Передаваемые параметры функции i18n

Функция i18n принимает следующие параметры:

  • scope — область видимости ключа. Обычно имя блока.
  • key — ключ, соответствующий значению для указанного языка.
  • params — входные данные для функции (дополнительный параметр).

Параметризация значений позволяет задавать дополнительный параметр params, который будет обработан функцией i18n и может повлиять на результат перевода. Например, в этом параметре может передаваться число от 1 до 12, при обработке которого результатом будет месяц, соответствующий указанному числу.

Результат выполнения: строка, содержащая перевод.

  • В bem-bl

    Задавать значения ключей можно не только как строку. Также возможность параметризации значений реализована через XML.

    {
        'scope-1': {
            key: 'val'
        },
        'scope-2': {
            key: 'Hello <i18n:param>who</i18n:param>!'
        }
    }
  • В bem-core

    Задавать значения ключей можно не только как строку. Параметризация значений реализована через функцию.

    Пример:

    {
        'scope-1': {
            key: 'val'
        },
        'scope-2': {
            key: function (params, i18n) {
                return i18n(params.scope, params.key);
            }
        }
    }
    modules.require('i18n', function (i18n) {
        i18n('scope-2', 'key', { scope: 'scope-1', key: 'key' }); // Значение
    });

Различия в использовании i18n в bem-bl и bem-core

Переключение между языками в runtime

  • В bem-bl реализована возможность переключения между языками в runtime.
  • В bem-core такой возможности нет.

Хранение общих keysets-файлов с переводами

  • В bem-bl переводы, одинаковые для всех языков, хранятся в файле <block-name>.i18n/all.js.
  • В bem-core — в файле <block-name>.i18n.js.

Расположение ядра i18n

  • В bem-bl — элемент i18n блока i-bem.
  • В bem-core — блок i18n.

© 2014 YANDEX LLC. Код лицензирован Mozilla Public License 2.0.