zfconf 2010: zend framework and multilingual
DESCRIPTION
TRANSCRIPT
Zend Framework и мультиязычность
Степан Танасийчук[email protected]
27 марта 2010 г.
Санкт-Петербург
Чем я занимаюсь?
Web разработкой занялся в 2003 году
С Zend Framework начал работать в 2008 году
Руковожу собственной веб-студией с 2009 года
Активный участник сообщества zendframework.ru
Люблю прикольные смайлы :]
Содержание доклада
Простейшее подключение Zend_Translate Работа с view хелпером translate Plural forms или формы множественного числа Почему я отдаю предпочтение gettext? Работаем с poedit Хаки для работы с gettext Перевод сообщений валидаторов Сравнение различных схем передачи языка в URL Zend_Translate и кеширование
Самый простой вариант подключения Zend_Translate
Добавляем в application.ini следующие настройки:resources.translate.data = APPLICATION_PATH "/languages"
resources.translate.adapter = "array"
resources.translate.locale = "auto"
resources.translate.options.scan = "directory"
resources.translate.options.disableNotices = true
Редактриуем IndexAction() в дефолтном контроллере:./application/controllers/IndexController.php
public function indexAction()
{
echo $this->view->translate('Hello');
}
Создаем файл переводов для русского языка
Структура каталога languages:./application/languages/
`-- ru
`-- application.php
Файл переводов:./application/languages/ru/application.php
<?php
return array(
'Hello' => 'Привет',
);
Результат:Привет
Почему ”Привет”, а не ”Hello”?
Потому что в моих настройках браузера русский язык по приоритету выше английского:
Отдельные можно выводить сообщения на указанном языке
Для этого нужно указать язык или локаль в последнем аргументе view хелпера translate():
./application/controllers/IndexController.php
public function indexAction() {
echo $this->view->translate('Hello', 'en_GB');
// или
echo $this->view->translate('Hello', 'en');
}
Результат:Hello
Эту возможность удобно использовать при формировании текстов для многоязычной рассылки.
Plural forms или формы множественного числа
Поддержка plural forms есть в адаптерах: Array Csv Gettext
Разберем на примере:./application/controllers/IndexController.php
public function indexAction()
{
echo $this->view->translate('Hello') . '! ';
$count = 5;
echo sprintf($this->view->translate(array('%s day', '%s days', $count)), $count) . ' ' . $this->view->translate('left before the conference');
}
Plural Forms (продолжение)
Обновим файл переводов для русского языка:./application/languages/ru/application.php
<?php
return array(
'Hello' => 'Привет',
'left before the conference' => 'осталось до начала конференции',
'%s day' => array(
'%s день',
'%s дня',
'%s дней'
)
);
Результат:Привет! 5 дней осталось до начала конференции
Почему я отдаю предпочтение gettext?
В коде отображаются оригиналы сообщений. Пример сообщения в view шаблоне:
<h2><?php echo $this->translate('Create new brand'); ?>:</h2>
Не нужно искать где и какие строки были добавлены или удалены в исходном коде — gettext сам найдет все изменения.
Есть готовые программы для работы с файлами переводов (особенно актуально для НЕпрограммистов). Достаточно предоставить заказчику или перводчику .po файл и программу для его редактирования (например кроссплатформенная Poedit).
Работаем с poedit. Настройки каталога
Создаем новый каталог Файл→Создать каталог. Указываем необходимые настройки:
Работаем с poedit. Настройка каталога (продолжение)
Обязательно указывайте формы множественного числа для каждого перевода!
Полный список форм можно найти на странице http://translate.sourceforge.net/wiki/l10n/pluralforms
Например для русского:
nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 or n%100>=20) ? 1 : 2)
для немецкого:
nplurals=2; plural=(n != 1)
Работаем с poedit. Пути
На вкладке «Пути» указываем путь к каталогу проекта или пути к каталогам которые нужно сканировать:
Работаем poedit. Ключевые слова
Также добавляем названия функций, строковые аргументы которых должны добавлятся в языковый файл (вкладка «Ключевые слова»):
Для чего я добавил plural:1,2?
Я не знаю как заставить парсер xgettext доставать строки из такой конструкции:
$this->view->translate(array('%s day', '%s days', $count)
Но он успешно достает их из конструкции вида:$this->view->translate()->getTranslator()->plural('%s day', '%s days',
$count)
Работаем с Poedit. Обновляем каталог из исходных файлов
Сохраняем каталог ./application/languages/ru/application.po И обовляем его Каталог→Обновить из исходного кода Получаем следующую картину:
Работаем с Poedit. Добавляем переводы и сохраняем каталог
Сохраняем результат. В параметрах должна быть отмечена опция ”При сохранении автоматически компилировать файл .mo”.
В application.ini меняем адаптер на:
resources.translate.adapter = "gettext"
Запускаем:Привет! 5 дней осталось до начала конференции
Ньюансы и хаки для работы с gettext
Лейблы формы нужно оборачивать в _(). Пример:$username = $form->createElement('text', 'username');
$username->setLabel(_('Имя пользователя'));
Только что подумал о том, что setLabel тоже можно добавить в ключевые слова :).
Та же ситуация с названиями пунктов меню для Zend_Navigation:
array(
'controller' => 'users',
'action' => 'list',
'resource' => 'mvc:users',
'privilege' => 'list',
'label' => _('Users'),
'route' => 'default',
)
Перевод сообщений валидаторов для адаптеров != array
Раньше мы делали отдельный файлик, который содержал все сообщения валидаторов обернутые в _():
_("A record matching \"%value%\" was found");
_("Password and confirmed password do not match.");
xgettext этот файл парсил, а мы переводили и компилировали .mo файл, который уже подключали ко всем проектам.
Теперь в ZF появилась папочка resources, в которой лежат переводы сообщений валидаторов. Но как ихподключить если для основного сайта используется адаптер отличный от array я пока не разобрался. Думаю, что вскоре разберусь и решение будет опубликовано на сайте или форуме http://zendframework.ru
Перевод сообщений валидаторов для адаптера array
Для array все намного проще. Добавляем в Bootstrap.php такой метод:
/**
* Init translator to Zend_Validate
* @return Zend_Translate
*/
public function _initZendValidateTranslator()
{
$this->bootstrap('translate');
$translate = $this->getResource('translate');
$translate->addTranslation(APPLICATION_PATH . '/../resources/languages');
return $translate;
}
Варианты передачи языка в URL
Язык на поддомене (Zend_Controller_Router_Route_Hostname): en.wikipedia.org ru.wikipedia.org
Язык в поддиректории: 1й вариант (CyEngine_Controller_Router_Route_Multilingual):
mota.ru – русский mota.ru/en/ – английский
2й вариант (переопределяем Zend_Controller_Router_Route): preorder.it – ”auto” или по базе GeoIP preorder.it/ru/ – русский preorder.it/en/ – английский
Язык на поддомене +/-
+ Ускоренная индексация. Для доменов первого уровня, которые не имеют
географической привязки можно настроить разные географические цели для каждой языковой версии сайта (в Google Webmaster Tools).
Вес с основного домена передается на поддомены. Сайты можно разнести на разные сервера и делать
независимые изменения в коде.
- Бюджет на продвижение и эффект от него будет
дробиться на все сайты.
Язык в поддиректории +/-
+ Достаточно просто реализовать в ZF. Хорошо подходит для сайтов-услуг. Идет продвижения одного домена, т.е. тот же бюджет
что и в первом варианте даст больший эффект.
- Более медленная индексация. Нельзя сделать разные географические цели для
разных языковых версий сайта. С технической стороны будет проблематично разнести
разные языковые версии сайта на разные сервера.
Несколько строчек кода, которые делают приложение быстрее :)
Если есть возможность что-то закешировать, значит нужно её использовать:
$cache = Zend_Cache::factory(
'Core',
'File',
array(
'caching' => true,
'automatic_serialization' => true,
'lifetime' => 3600,
),
array(
'cache_dir' => realpath(APPLICATION_PATH . '/../tmp'),
)
);
Zend_Translate::setCache($cache);