Впервые я столкнулся с необходимостью обеспечить перевод сайта совсем недавно. Этот пост о моем исследовании проблемы, инструментах и решениях в контексте Zend Framework 2.
Возможности и реализация перевода в Zend Framework 2
В ZF2 перевод реализует класс Translator (полный путь Zend\I18n\Translator\Translator). Этот гибкий инструмент имеет несколько способов конфигурирования.
Самый простой это метод addTranslationFile() содержит следующий набор параметров:
- $type – это тип файла содержащего перевод из перечисленных ниже.
- $filename – полный путь к файлу. альтернативный вариант это задать паттерн, о нем дальше.
- $textDomain – контекст. по умолчанию ‘default’
- $locale – локаль языка в виде ru_RU, en_US
Пример использования:
1 2 3 4 |
$translator = new Translator(); $translator->addTranslationFile($type, $filename, $textDomain, $locale); |
Так же вместо указания одного файла можно задать паттерн, который будет искать все файлы с заданным расширением по заданному пути. Например:
1 2 3 4 |
$translator = new Translator(); $translator->addTranslationFilePattern($type, $baseDir, $pattern, $textDomain); |
Пример паттерна – /var/messages/%s/messages.mo
Список поддерживаемых форматов перевода:
- PHP arrays
- Gettext
- INI
Содержание файла PHP очевидно будет иметь вид ключ-значение, где ключем будет переводимая строка, а в значении – перевод. Это самый простой формат. Например перевод на немецкий:
1 2 3 4 5 6 |
<?php return array( 'Skeleton Application' => 'das Skelett Applikation' ); |
Содержимое INI файла будет выглядеть как набор строк message и translation. Так же перед каждым переводом можно указать группу (это и есть тот самый контекст из параметров выше).
1 2 3 4 5 |
[Skeleton Application] message = Skeleton Application translation = das Skelett Applikation |
Формат INI уже больше похож на Gettext по своей структуре, но все таки он слишком спецализирован и уступает Gettext по возможностям и популярности. Например, популярная CMS WordPress переводится с помощью Gettext. Рассмотрим Gettext формат:
1 2 3 4 5 |
#: ../view/error/404.phtml:1 msgid "A 404 error occurred" msgstr "Es trat ein 404 Fehler auf" |
Тут видно, что в комментарии можно указать произвольный текст, например путь к файлу. Далее идет переводимая строка msgid и перевод msgstr. Преимущество Gettext в том, что PO файлы компилируются в MO файлы, которые сжаты и легче читаются программой. Но с Gettext сложнее работать, если переводы можно редактировать просто в редакторе, то все равно нужен компилятор MO.
Я не рассматриваю перевод для множественного числа, скажу лишь, что для этого есть translatePlural() и так же существуют правила для каждого языка, как единственное число преобразуется в множественное, выраженные в формулах. Эти формулы очень полезны для PO редакторов, найти их можно тут – http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html
Для настройки использования Gettext в Zend нужно добавить следующий конфиг в модуль:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
'translator' => [ 'locale' => 'ru_RU', 'translation_file_patterns' => [ [ 'type' => 'gettext', 'base_dir' => __DIR__ . '/../language', 'pattern' => '%s.mo', ], ], 'cache' => [ 'adapter' => [ 'name' => 'Filesystem', 'options' => [ 'cache_dir' => __DIR__ . '/../../../data/cache', 'ttl' => '3600' ] ], 'plugins' => [ [ 'name' => 'serializer', 'options' => [] ], 'exception_handler' => [ 'throw_exceptions' => true ] ] ], ], |
Тут указывается настройки сервиса, а именно локаль по умолчанию, тип файлов перевода и шаблон пути к файлам. Параметры cache не обязательные, но лучше их указывать для производительности.
По умолчанию вместе с Zend Skeleton идет модуль Application в котором можно увидеть множество PO и MO файлов. Взяв за основу предлагаемый ru_RU.po файл, я решил расширить его нужными мне переводами. Для этого я поискал инструмент для редактирвоания, т.к. редактировать в IDE мне не понравилось. Самый популярный редактор для PO это PoEdit и он мне не понравился. Может быть он хорош для перевода PO, но я так и не нашел в нем возможность добавлять новые не вереведенные фразы. Я исследовал еще несколько редакторов, включая GTranslator, Babel, Virtaal и poeditor и ни один из них не показался мне достаточно удобным и завершенным.
В итоге я решил использовать онлайн сервис, в котором можно добавлять фразы, переводить, сохрнанять и компилировать MO – https://localise.biz/free/poeditor
Более подробная информация по Translator в документации – http://framework.zend.com/manual/current/en/modules/zend.i18n.translating.html
Практическое применение
Для начала несколько примеров использования Translator в разных местах фреймворка. Эти примеры будут корректными при условии, что в конфигурации route текущего запроса есть параметр locale.
В конфигурации:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
'default' => array(
'type' => 'Segment',
'options' => array(
'route' => '/[:controller[/:action][/:locale]]',
'constraints' => array(
'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
'locale' => '[a-zA-Z]{2}_[a-zA-Z]{2}',
),
'defaults' => array(
'locale' => 'en_US'
),
),
), |
В модуле
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public function onBootstrap(MvcEvent $e)
{
$app = $e->getApplication();
$app->getEventManager()->attach(
'dispatch',
function($e) {
$routeMatch = $e->getRouteMatch();
if ($routeMatch->getParam('locale') != '') {
$this->serviceManager = $e->getApplication()->getServiceManager();
$translator = $this->serviceManager->get('translator');
$translator->setLocale($routeMatch->getParam('locale'));
}
},
100
);
} |
В экшине контроллера:
1 2 3 4 5 6 7 8 |
public function indexAction()
{
$this->getServiceLocator()->get('translator')->setLocale( $this->params()->fromRoute('locale')
);
return new ViewModel();
} |
Но самое интересное и удобное, что есть у Zend Framework 2 по переводам это View хелперы:
- Currency Format Helper
- Date Format Helper
- Number Format Helper
- Plural Helper
- Translate Helper
- Translate Plural Helper
Многие из этих хелперов (если не все) используют php расширение intl.
Из них самые часто используемые должны быть Date и Translate, их я и использовал. Учитывая сконфигурированный путь к MO файлам, Translator подхватывает переводы автоматически.
Например этот код
1 2 3 4 5 |
<a href="/page/privacy-policy"> <?php echo $this->translate("Privacy Policy") ?> </a> |
выдаст мне анкор “Политика конфиденциальности”, т.к. я добавил перевод в ru_RU.po и добавил скомпилированный ru_RU.mo
1 2 3 4 |
msgid "Privacy Policy" msgstr "Политика Конфиденциальности" |
Если с Translator все просто, то вот с Date Formatter есть нюансы. Он сделан не очень гибко и поддерживает форматирвоание только с использованием преодпределенных констант расширения intl.
Например
1 2 3 4 5 6 7 8 9 10 |
// Date and Time echo $this->dateFormat( new DateTime(), IntlDateFormatter::MEDIUM, // date IntlDateFormatter::MEDIUM, // time "en_US" ); // This returns: "Jul 2, 2012 6:44:03 PM" |
Для того, чтобы задать произвольный формат, например получить только месяц текстом с переводом, мне пришлось добавить метод в свой PageHelper
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
/** * @param string $format * @return string * @throws \Exception */ public function dateFormat($format = Date::STANDARD) { $date = $this->getCurrentPage()->getDate(); return $date->format($format); } /** * @param string $format * @return string * @throws \Exception */ public function dateFormatIntl($format = Date::STANDARD) { $formatter = new \IntlDateFormatter('ru_RU', \IntlDateFormatter::MEDIUM, \IntlDateFormatter::MEDIUM); $formatter->setPattern($format); $date = $this->getCurrentPage()->getDate(); return $formatter->format($date); } |
Я привожу так же метод с форматированием \DateTime, чтобы была разница. Используется мой ViewHelper так
1 2 3 4 5 |
<span class="newsMonth"> <h3><?php echo $this->pageHelper()->dateFormatIntl("MMM"); ?></h3> </span> |
Получаю 19 Марта вместо 19 March. Список доступных форматов дат, которые использует intl – http://userguide.icu-project.org/formatparse/datetime
Документация по остальным View Helper – http://framework.zend.com/manual/current/en/modules/zend.i18n.view.helpers.html
Так же в Zend 2 на основе intl сделаны мощные фильтры и валидаторы, информацию о которых можно найти по ссылке выше.
В завершение хочу порекомендовать отличный модуль, который автоматичеки встраивает Twitter Bootstrap в рендеринг ZF2 форм. Просто создав класс MyForm и вызвав его рендеринг можно сразу же получить форму в разметке Bootstrap, а для более гибкой верстки у модуля существует множество методов. Я упоминаю этот модуль тут, т.к он автоматически переводит Label к полям формы (при наличии перевода в файле перевода и конфигурации) и так же Value в кнопках и другое.
Модуль Zf2-twb-bundle