avatar

Personal Maps: локализация и интернационализация. Часть 10

Опубликовал в блог Веб разработка
0
Приветствую! Это заключительная статья цикла о разработке web приложения с использованием фреймворков Yii и AngularJS. На данный момент у нас есть полностью работающее приложение, и остаётся добавить возможность перевода интерфейса на разные языки.

Примечание. Ссылки на все предыдущие статьи вы найдёте в конце этой страницы.
Вообще создание многоязычного интерфейса – задача довольно тривиальная. В большинство фреймворков (и Yii здесь не исключение) входят соответствующие библиотеки. Но в нашем случае ситуация немного сложнее из-за того, что приложение состоит из клиентской и серверной частей. При этом для обоих фреймворков (Yii и ) есть собственные средства для работы с переводами.

В принципе, можно работать с двумя библиотеками. Но обычно удобнее, собрать все переводы в одном месте, либо на клиенте, либо на сервере. Т.к. передача данных от сервера клиенту (браузеру) проще, то мы будем использовать библиотеку Yii в качестве основной. А при формировании главной страницы приложения, передадим переводы AngularJS.
Напоминаю. Вы можете посмотреть исходный код приложения на GitHub и поэкспериментировать с демо-версией.
Source
Demo
Интернационализация серверной части (Yii)
В официальном руководстве есть подробная статья на эту тему, повторять её я не буду, а остановлюсь только на тех моментах, которые относятся к нашему приложению.
Прежде всего, необходимо выбрать тип источника сообщений. Yii поддерживает три типа таких источников:
  • обычные PHP массивы (CPhpMessageSource);
  • файлы формата GNU Gettext (CGettextMessageSource);
  • базу данных (CDbMessageSource).
Я остановился на первом варианте ( массивы), но принципиальной разницы нет.
Важно другое. Для передачи переводов клиентской части нам очень желательно передать сразу все переводы, иначе их придётся загружать AJAX запросами, а это занимает время и не лучшим образом скажется на внешнем виде приложения. Но класс CPhpMessageSource не позволяет получить все переводы сразу, точнее нужный нам метод
<code>loadMessages</code>
объявлен защищённым (protected).
Поэтому мы создадим компонент, который наследует
<code>CPhpMessageSource</code>
и будем использовать его в качестве источника сообщений.
<code>protected/components/PhpMessageSource.php</code>
class PhpMessageSource extends CPhpMessageSource {
    public function getAllMessages($category, $lang) {
        return $this->loadMessages($category, $lang);
    }
}
Мы объявили один метод
<code>getAllMessages</code>
, который просто вызывает
<code>loadMessages</code>
, т.е. возвращает массив переводов для указанного языка.
Подключаем наш компонент в
<code>config/main.php</code>
return array(
	...
    'language'=>'ru',

	...
	// application components
	'components'=>array(
		...
        'messages'=>array(
            'class'=>'PhpMessageSource',
        ),
	),
	...
);
Файлы с переводами находятся в папке
<code>protected/messages</code>
.
messages/
	en/
		frontend.php
		...
	ru/
		frontend.php
		...
Сами переводы выглядят следующим образом:
return array(
    'CREATE_PLACE' => 'Create place',
    'UPDATE_PLACE' => 'Update place',
	...
);

return array(
    'CREATE_PLACE' => 'Создать объект',
    'UPDATE_PLACE' => 'Изменить объект',
	...
);
Интернационализация клиентской части (AngularJS)
Прежде всего, нам нужно передать переводы браузеру. Для этого в представление, которое создаёт главную страницу приложения (
<code>protected/views/places/index.php</code>
), добавим следующий код:
Yii::app()->clientScript->registerScriptFile(Yii::app()->baseUrl.'/js/angular-translate.min.js', CClientScript::POS_END);
Yii::app()->clientScript->registerScript(
    'langScript'
    , '
    var lang = "'.Yii::app()->getLanguage().'";
    var translations = '.CJSON::encode(Yii::app()->messages->getAllMessages('frontend', Yii::app()->getLanguage())).';'
    , CClientScript::POS_HEAD
);
В первой строке мы подключаем Angular translate. Это модуль, предназначенный для интернационализации приложений на Angular.
Затем мы создаём две JS переменные:
<code>lang</code>
– содержит название языка;
<code>translations</code>
– содержит массив с переводами.
Этих данных нам достаточно для того, чтобы настроить приложение
<code>public_html/js/app.js</code>
Мы указываем модуль
<code>pascalprecht.translate</code>
в списке зависимостей приложения.
var app = angular.module('personalmaps', ['ui.bootstrap', 'pascalprecht.translate'])
    .value('lang', lang);
В результате через систему внедрения зависимостей (Dependency injection — DI) станет доступен сервис
<code>$translateProvider</code>
, которому мы передаём массив с переводами.
app.config(['$translateProvider', function($translateProvider) {
    // add translation table
    $translateProvider.translations(translations);
}]);
Также через DI будет доступна переменная
<code>lang</code>
. Вообще мы можем обойтись без неё, но я решил просто показать пример использования переменных. Т.е. получить к ней доступ можно, например, так:
app.controller('PlacesListController'
    , ['$scope', '$rootScope', 'Places', '$dialog', 'lang'
    , function($scope, $rootScope, Places, $dialog, lang) {

    $scope.curLang = lang;

	...
}]);
Возвращаемся к модулю Angular translate.
На официальном сайте предлагается загрузить все варианты переводов.
app.config(function ($translateProvider) {
  $translateProvider.translations('en', {
    TITLE: 'Hello',
	...
  });
  $translateProvider.translations('de', {
    TITLE: 'Hallo',
	...
  });
  $translateProvider.preferredLanguage('en');
});
Такой способ имеет смысл использовать, если вы хотите предоставить пользователю возможность переключать языки прямо из интерфейса приложения, т.е. выполнять следующий код.
$scope.changeLanguage = function (key) {
	$translate.uses(key);
};
Но в нашем приложении такая возможность не предусматривается. Язык приложения указывается в конфигурационном файле
<code>main.php</code>
, поэтому отправлять браузеру все переводы нет смысла. Мы просто один раз вызываем метод
<code>translations</code>
и передаём ему массив с переводами на выбранный язык.
$translateProvider.translations(translations);
Для использования переводов в модуль angular translate входит специальный фильтр –
<code>translate</code>
. Т.е. теперь в шаблонах мы можем написать что-то вроде (
<code>partials/list.</code>
):
<span ng-show="isEmpty()">{{ 'NO_PLACES' | translate }}</span>

<div>
    <a href="places/index#/add" class="btn btn-success">{{ 'ADD_PLACE' | translate }}</a>
</div>
В фигурных скобках мы указываем имя сообщения (ключ в массиве
<code>translations</code>
) и через вертикальную черту – название фильтра. В результате мы получим значение из массива
<code>translations</code>
, т.е. сообщение на нужном языке.
Заключение
Этот цикл получился довольно объемным, и публикация растянулась на два месяца. Но мне кажется, что такой формат полезнее, чем отдельные статьи, потому что в них сложно рассмотреть взаимодействие нескольких технологий между собой. Даже в этом цикле многие вещи пришлось упростить, чтобы не перегружать код различными проверками и дополнительными функциями. Всё-таки основная цель заключалась в том, чтобы показать, как компоненты взаимодействуют между собой, а не создать приложение для продакшена (хотя приложение вполне можно использовать).
И отдельно хочу поблагодарить всех читателей, которые присылали вопросы и замечания. Вы помогли сделать этот цикл лучше.
Успехов!
1 комментарий RSS
avatar
Добрый день! Если Вы заинтересованы в локализации web-ПО, ПО для персональных компьютеров, ПО для мобильных устройств либо иного вида программного обеспечения, я рекомендую Вам использовать этот инструмент на базе web:
poeditor.com

POEditor является интуитивным, хорошо проработанным инструментом, обладающим рядом полезных свойств, которые помогают организовать процесс управления переводом. Он поддерживает множество популярных форматов файлов и обладает собственным API, что обеспечивает лучшую автоматизацию. Желаю Вам больших успехов в Ваших проектах!
Комментарий отредактирован 2014-02-19 01:14:03 пользователем admin
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.