enb-bem-i18n
Пакет предоставляет набор 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 может вызываться из шаблонов или клиентского 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описан в разделе APIi18n. В результате работы технологии 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.