...

четверг, 22 августа 2013 г.

[Из песочницы] Локализация шаблонов на клиенте в AngularJS

image

При разработке мультиязычного веб-приложения на AngularJS вам скорее всего понадобится так или иначе решать вопрос с переводом. Сегодня я хотел бы поделиться одним из способов, с помощью которого это можно реализовать.



Допустим у нас уже есть некий сервис, предоставляющий возможность осуществлять перевод, используя простой словарь строк.



/**
* @param {Settings} Settings
* @constructor
*/
function Language(Settings) {
this.Settings = Settings;
}
Language.$inject = ['Settings'];
app.service('Language', Language);

/**
* @param {String} string
* @returns {String}
*/
Language.prototype.translate = function(string) {

var translation = __lang[this.Settings.getValue('lang')];

if (typeof translation[string] !== 'undefined') {
string = translation[string];
}

return string;
};


Зная AngularJS, первая же идея, которая приходит в голову — это использовать стандартные возможности выражений с фильтрами для перевода строк «на лету» вида:



<span>{{ 'some_english_string' | translate }}</span>




Но этот подход имеет свои недостатки:

— во-первых, все-таки увеличивается количество кода, исполняемого в каждом $digest цикле и если у вас сложное приложение, одновременно на странице отображается большое количество строк и/или ваш алгоритм перевода уже более сложный, чем просто поставление строки из словаря по ключу — то производительность будет снижаться.

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

Второй вариант, который рекомендуют использовать — это переводить шаблоны на сервере и с клиентской стороны уже иметь доступ к локализованным копиям вида some_partial_en.html, some_partial_ru.html. В таком случае для смены языка необходимо будет перезагружать приложение. К тому же, не всегда есть такая возможность: например, на моем прошлом проекте приложение использовало данные из стороннего сервиса, а написание бекенда вообще не предполагалось.


Но есть еще один способ — а именно переводить шаблоны на клиенте, но уже не средствами AngularJS. Например, можно использовать шаблонизатор из Lo-Dash / underscorejs и использовать конструкции вида:



<span><%= t('some_english_string') %></span>




Плюс в том, что при этом можно будет как использовать angular-выражения внутри перевода строки в словаре

__lang.ru =
{
"some_english_string": "Русский перевод строки номер {{string.number}}"
};




так и наоборот, подставлять эту конструкцию внутрь других angular-выражений, допустим, в какой-нибудь своей директиве:

<div class="input-text-right">
<input type="text"
my-num-format="00000.00"
my-validate="[
{
type: 'notGreaterThen',
compareTo: 'somecondition()',
message: '<%= t('some_validation_error_message') %>'
}
]"
ng-model="model.value">
</div>


Для этого мы допишем метод для перевода шаблонов в наш сервис Language:



.....

/**
* @param {String} template
* @returns {String}
*/
Language.prototype.translateTemplate = function(template) {
// подставляем наш метод перевода this.translate() в область видимости шаблона из Lo-Dash как t()
return _.template(template, {
t: angular.bind(this, this.translate)
});
};


Теперь нам нужно перехватывать шаблоны, используемые angular-ом и переводить их до того, как он начнет их компиляцию. Отдельного сервиса для получения шаблонов в AngularJS нет, но везде, где это происходит (роутинг, директива ngInclude и т.д.) — используется $templateCache для кеширования. Вот его-то мы и переопределим:



// создаем angular-фабрику с таким же именем, как и у стандартной реализации фреймворка
app.factory('$templateCache', [
'$cacheFactory',
'Language',

function($cacheFactory, Language) {

/**
* @constructor
*/
function MyTemplateCache() {

/**
* @param {String} key
* @param {*} value
*/
this.put = function(key, value) {

// если значение - это promise-объект, который возвращает сервис $http
if (typeof value.then !== 'undefined') {
// заменяем его своим promise-ом, который будет зарезолвен уже переведенным шаблоном
value = value.then(function(response) {
response.data = Language.translateTemplate(response.data);
return response;
});
}
// если значение - это массив-результат, который angular записывает в кеш при резолве promise-а $http
else if (value instanceof Array) {
value[1] = Language.translateTemplate(value[1]);
}
// если значение - это сам шаблон
// (в случае, когда шаблоны берутся со страницы из тегов <script type="text/ng-template"></script>
// или вставляются вручную)
else if (typeof value === 'string') {
value = Language.translateTemplate(value);
}

// вызываем родительский метод put()
MyTemplateCache.prototype.put(key, value);
};
}

// наследуемся от стандартного объекта $templateCache
MyTemplateCache.prototype = $cacheFactory('templates');

return new MyTemplateCache();
}
]);


Вот, собственно и все. Теперь для смены языка без перезагрузки страницы будет достаточно очистить кеш и вызвать перезагрузку роута:



$templateCache.removeAll();
$route.reload();




Единственное замечание: при перезагрузке роута будут сброшены все изменения, не сохраненные в состоянии, поскольку при перекомпиляции шаблонов будут пересозданы заново все скоупы.

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends: 'You Say What You Like, Because They Like What You Say' - http://www.medialens.org/index.php/alerts/alert-archive/alerts-2013/731-you-say-what-you-like-because-they-like-what-you-say.html


Комментариев нет:

Отправить комментарий