JsMultiLang — frontend-фреймворк для реализации мультиязычности на сайте

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

Когда я работал над Интернет-магазином «Шанхайский котелок», то был полностью ограничен в написании кода на стороне сервера, так как работал с SaaS-платформой InSales. Среди задач, которые поставили передо мной руководители проекта, была реализация английской версии сайта. Тогда решения реализовать мультиязычность с помощью javascript было для меня единственно возможным.

JsMultiLang я написал, когда мне нужно было реализовать мультиязычность во время написания заглушки платформы для проведения онлайн-концертов Miradas.ru. Мы изначально не нуждались в английской версии сайта, но один концерт нужно было подать в двух версиях, чтобы продавать билеты на Западе. А экономия времени разработки как на момент написания фреймворка была, так и на момент написания статьи остается достаточно актуальной проблемой. Я не буду вдаваться в подробности проекта. Прикреплю только ссылку на страницу с использованным JsMultiLang:
http://miradas.ru/events/concerts/2015/02/07/amg-king-x-maniaci/eng.

Постараюсь максимально лаконично и с наиболее понятными примерами описать функционал фреймворка в данной статье-документации. Для начала нам нужно скачать сам фреймворк (или же версию для разработчиков с комментариями формата JSDoc).

Данный фреймворк не использует сторонних библиотек и не привязан к куки, чтобы задавать язык на всем сайте, поскольку в некоторых случаях может понадобится реализация мультиязычности на одной или нескольких страницах (такая проблема, к примеру, возникла у меня при работе над Miradas).

Начинаем

html-файл, который я буду создавать в этой статье, также можно скачать на gitHub. Для начала подключим фреймворк к html-документу.


<script type="text/javascript" src="jsmultilang.min.js"></script>

Инициализация словаря

Первым шагом в использовании фреймворка является инициализация словаря с помощью метода addLangConfig, которому нужно передать 2 параметра: язык и словарь. Изначально мы не имеем ни одного языка, а добавление словаря языка создает указанный нами язык. Словарь представляет собой массив, который содержит наборы слов. Понятия набора введено в связи с необходимостью категоризации словаря при наличии большого количества слов (или фраз) для перевода. Для примера инициализируем английский и русский языки:


	JsMultiLang.addLangConfig ('eng', {
index : {
home : 'Home',
about : 'About'
},
block_titles : {
h1_1 : 'My site',
h2_1 : 'Programming',
h2_2 : 'Web design'
}
});
JsMultiLang.addLangConfig ('rus', {
index : {
home : 'Главная',
about : 'О нас'
},
block_titles : {
h1_1 : 'Мой сайт',
h2_1 : 'Программирование',
h2_2 : 'Веб дизайн'
}
});

Рекомендуется каждый набор с используемыми ключами прописывать в каждом языке. Например, если бы мы не прописали набор block_titles и ключ about в наборе index, фреймворк продолжал бы работать, но тогда могло бы возникнуть довольно странное поведение (например, пустота на месте некоторых блоков).

Демо-структура

Этот словарь привязан к следующей демо-структуре:


	<nav>
<span class="menu-item jml-home"></span>
<span class="menu-item jml-about"></span>
</nav>

<h1></h1>
<div class="block1">
<div class="category-header"></div>
</div>
<div class="block2">
<div class="category-header"></div>
</div>

А в итоге работы с фреймворком мы должны получить такой результат:


	<nav>
<span class="menu-item jml-home">Home</span>
<span class="menu-item jml-about">About</span>
</nav>

<h1>My site</h1>
<div class="block1">
<div class="category-header">Programming</div>
</div>
<div class="block2">
<div class="category-header">Web design</div>
</div>

Установка используемого языка

Текущий язык задается с помощью метода setLang ().


	JsMultiLang.setLang ('eng');

Как я уже говорил выше, он не привязан ни к куки, ни к URL. Строчкой кода выше удобно задавать язык, если вся страница статическая и конкретно на ней пресматривается использование только одного языка (в нашем файле для простоты мы будем работать именно с этим вариантом). Но язык на странице может зависеть от URL. К примеру, если предполагается, что http://lux-blog.org/portfolio/eng — английский вариант страницы контретной страницы, а http://lux-blog.org/portfolio — русский, то мы можем использовать следующий код для установки языка:


	var href = window.location.href,
// получаем идентификатор языка
lang = href.substr(href.length - 4);

//анализируем идентификатор языка
if (lang == '/eng')
JsMultiLang.setLang ('eng');
else
JsMultiLang.setLang ('rus');

Однако возможен вариант, когда ссылка типа http://lux-blog.org/.../eng должна задавать английский язык на всем сайте. То есть если пользователь раз зашел на http://lux-blog.org/.../eng, все последующие страницы должны отображаться на каком-то конкретном языке (в данном случае на eng). Тогда нам нужно использовать куки. Приведу пример кода, использующего jquery.cookie:


	var href = window.location.href,
// получаем идентификатор языка
lang = href.substr(href.length - 4),
// устанавливаем украинский язык по-умолчанию
defaultLang = 'ukr',
usedLang = undefined;

//анализируем идентификатор языка
switch (lang) {
case '/eng' : usedLang = 'eng'; break;
case '/rus' : usedLang = 'rus'; break;
}

// если пользователь зашел впервые и не устанавливает язык,
// то мы выбираем значение по-умолчанию
if ($.cookie('lang') == undefined && usedLang == undefined) {
usedLang = defaultLang;
}
$.cookie('lang', usedLang, { path: '/' });

// устанавливаем язык сайта для фреймворка JsMultiLang
JsMultiLang.setLang ($.cookie('lang'));

Использование фреймворка для перевода

Выборка слов осуществляется с помощью метода getWords (setName, wordId). Чтобы получить нужное слово, необходимо указать параметрами название набора и ключ слова. Чтобы заполнить нашу демо-структуру, необходимо прописать следующий код (с использованием jQuery он будет короче):


	document.getElementsByClassName ("jml-home") [0].innerHTML = 
JsMultiLang.getWords('index', 'home');

document.getElementsByClassName ("jml-about") [0].innerHTML =
JsMultiLang.getWords('index', 'about');

document.getElementsByTagName ("h1") [0].innerHTML =
JsMultiLang.getWords('block_titles', 'h1_1');

document.getElementsByClassName ("block1") [0]
.getElementsByClassName ("category-header") [0]
.innerHTML =
JsMultiLang.getWords('block_titles', 'h2_1');

document.getElementsByClassName ("block2") [0]
.getElementsByClassName ("category-header") [0]
.innerHTML =
JsMultiLang.getWords('block_titles', 'h2_2');

Чистым javascript бывает не всегда удобно переводить и не на каждой странице используется jQuery (или подобные фреймворки). Для таких редких случаев в JsMultiLang предусмотрено 3 метода:

  • setForId (elementId, setName, wordId) устанавливает фразу со словаря элементу с заданным идентификатором
  • setForFirstClassElement (elementClassName, setName, wordId) — первому или единственному элементу с заданным именем класса
  • setForClassElements (elementsClassName, setName, wordId) — всем элементам с заданным именем класса

Альтернативные функции работы с DOM

Для демонстрации работы функций воспользуется следующей демо-структурой:


	<div id="JMLTestEmpty"></div>
<div id="JMLTestNonEmpty">JMLTestNonEmpty</div>
<div class="JMLTest1">JMLTest1</div>
<div class="JMLTest1">JMLTest1</div>
<div class="JMLTest2">JMLTest2</div>
<div class="JMLTest2">JMLTest2</div>
<div class="JMLTest3">JMLTest3</div>

Следующий код демонстрирует работу описаных выше 3-х функций:


	JsMultiLang.setForId ('JMLTestNonEmpty', 'index', 'home');
// Первый элемент должен получить значение Home
JsMultiLang.setForId ('JMLTestEmpty', 'index', 'home');
// Второй элемент должен получить значение Home
JsMultiLang.setForFirstClassElement ('JMLTest1', 'index', 'home');
// Третий элемент (первый элемент с классом JMLTest1)
// должен получить значение Home
JsMultiLang.setForClassElements ('JMLTest2', 'index', 'home');
// Пятый и шестой элемент должны получить значение Home

Для любителей jQuery, прилагаю аналог с использованием этой библиотеки:


	$('#JMLTestNonEmpty').text (JsMultiLang.getWords('index', 'home'));
// Первый элемент должен получить значение Home
$('#JMLTestEmpty').text (JsMultiLang.getWords('index', 'home'));
// Второй элемент должен получить значение Home
$('.JMLTest1').first ().text (JsMultiLang.getWords('index', 'home'));
// Третий элемент (первый элемент с классом JMLTest1)
// должен получить значение Home
$('.JMLTest2').text (JsMultiLang.getWords('index', 'home'));
// Пятый и шестой элемент должны получить значение Home

Для установки нового значения заголовка страницы (значения тега title) предусмотрен метод setPageTitle:


	JsMultiLang.setPageTitle ('index', 'home');

Добавление новых фраз в словарь

После того, как был задан словарь для определенного языка, его можно расширить, добавив новые фразы. Для этого предусмотрен метод addWords, которому нужно передать следующие параметры:

  • расширяемый язык
  • расширяемый набор
  • ключ фразы
  • фраза на указанном языке

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


	JsMultiLang.addWords ('eng', 'locations', 'denwer', 'Denwer');
JsMultiLang.addWords ('rus', 'locations', 'denwer', 'Денвер');

Альтернативой множественному вызову метода addWords является метод addWordsSet, которому в качестве параметров нужно передать имя набора, ключ слова и массив, в котором ключ соответствует языку, а значение — слову (фразе) в этом языке. Лично мне этот вариант предпочтительнее.


	JsMultiLang.addWordsSet ('locations', 'denwer', {
eng : 'Denwer',
rus : 'Денвер'
});

Этот код создаст набор locations (поскольку раньше у нас не было) и вставит в него слово с ключем denwer. После этого мы можем использовать новую фразу для установки текста на наш сайт:


	JsMultiLang.setForFirstClassElement ('JMLTest3', 'locations', 'denwer');

Транслейтеры

В JsMultiLang присутствуют транслейтеры — функции с каким-то определенным набором команд. Схема работы довольно проста: сначала мы инициализируем несколько функций, которые выполняют некоторые действия, а потом вызываем их в нужном месте кода (может быть при каком-то условии). Рассмотрим на примере. Сначала пропишем следующую структуру:


	<div id="JMLTranslatorsTestIndex">JMLTranslatorsTestIndex</div>

<div class="JMLTranslatorsTestBlock1">JMLTranslatorsTestBlock1</div>
<div class="JMLTranslatorsTestBlock2">JMLTranslatorsTestBlock2</div>

Транслейтеры задаются с помощью функции addTranslators в следующем виде:


	JsMultiLang.addTranslators ({
index : function () {
JsMultiLang.setForId ('JMLTranslatorsTestIndex', 'index', 'home');
},
sthBlock : function () {
JsMultiLang.setForFirstClassElement ('JMLTranslatorsTestBlock1', 'index', 'home');
JsMultiLang.setForFirstClassElement ('JMLTranslatorsTestBlock2', 'index', 'home');
}
});

Вызвать транслейтер можно в любом месте кода после инициализации с помощью метода translate с параметром, который содержит имя транслейтера.


	JsMultiLang.translate ('index');

Глобальный словарь

Последний механизм, которую я опишу в этой статье, — глобальный словарь. Схема его работы строится на том, что сначала разработчик определяет прописанные на сайте фразы, которые необходимо перевести, их перевод, а потом язык, к которому фразы прикрепляются. То есть перевод осуществляется, если установлен данный язык. Фреймворк сканирует каждый элемент DOM и, если обрезанное с помощью trim содержимое совпадает с одной из фраз для перевода, заменяет содержимое элемента. Для примера возьмем следующий html-код:


	<div>
<span>Главная</span>
<span>О системе</span>
<span>Концерты</span>
</div>
<div> Гарантии </div>

<div>
Для организаторов
<span class="hint-btn"></span>
</div>

<div>Контакты:
<div>no-reply@lux-blog.org</div>
</div>

Вот пример JS-кода, который прописывает параметры перевода для конструкции выше:


	// JsMultiLang.setLang ('eng');
// JsMultiLang.addLangConfig ('eng');
// JsMultiLang.addLangConfig ('rus');

JsMultiLang.setDictionary ({
index: 'Главная',
about: 'О системе',
concerts: 'Концерты',
garant: 'Гарантии',
for_managers: 'Для организаторов',
contacts: 'Контакты:'
}, {
index: 'Home',
about: 'About',
concerts: 'Concerts',
garant: 'Warranty',
for_managers: 'For managers',
contacts: 'Contacts: '
},
'eng'
);

Аналогично словарь можно задать с помощью следующей конструкции:


	JsMultiLang.setDictionary( [
[ 'Главная', 'Home' ],
[ 'О системе', 'About' ],
[ 'Концерты', 'Concerts' ],
[ 'Гарантии', 'Warranty' ],
[ 'Для организаторов', 'For managers' ],
[ 'Контакты:', 'Contacts: ' ],
], 'eng' );

Обратите внимание, что глобальный словарь можно использовать, инициализировав всего лишь имя словаря с помощью метода addLangConfig. Чтобы перевести наш текст, после инициализации глобального словаря нужно использовать метод translateAll.


	JsMultiLang.translateAll ();

Хоть и метод «просматривает» все элементы DOM, скорость его работы с заданными 50-тью фразами в словаре можно сравнить с выборкой одного элемента с помощью класса в библиотеке jQuery (приблизительно 4 миллисекунды). В качестве аргумента метод может принимать DOM-элемент, внутри которого будет происходить поиск и замена фраз. Если элемент не задан, поиск начинается с <body>.


	JsMultiLang.translateAll ( document.getElementsByTagName("html") [0] );
JsMultiLang.translateAll ( document.getElementsByClassName ("my-class") [0] );

В данном примере мы получим следующий результат:


	<div>
<span>Home</span>
<span>About</span>
<span>Concerts</span>
</div>
<div> Warranty </div>

<div>
For managers
<span class="hint-btn"></span>
</div>

<div>Contacts:
<div>no-reply@lux-blog.org</div>
</div>

Не буду врать — не знал, как озаглавить заключение

Напомню, что все коды, которые мы прописывали в данной статье можно скачать на gitHub. Если у вас будут какие-то предложения и пожелания, можете стучаться на почту den@lux-blog.org.

Поделиться в соцсетях: Вконтакте Facebook Twitter Mail.ru Google plus
Комментарии:
Подождите, пожалуйста. Загружаются комментарии...