
Однажды ночью мне написал знакомый и попросил взяться за проект на symfony, который он не успевал доделать, т.к. срочно уезжал. Срок выполнения — 2 дня. Первым пунктом в задании значилась поддержка следующих языков: английский, русский, польский, французский, немецкий. Как раз этот пункт прежний разработчик отложил «на потом». В итоге, в срок я не уложился, так как с интернационализацией до этого дела не имел, а в интернете не было ответов на все мои вопросы.
Далее рассмотрим процесс интернационализации, а также подводные камни, которые при этом могут встретиться.
Интернационализация (англ. internationalization) — процесс адаптации продукта, такого как программное или аппаратное обеспечение, к языковым и культурным особенностям региона (регионов), отличного от того, в котором разрабатывался продукт. В английском языке для слова «internationalization» принято сокращение «i18n». При этом число 18 означает количество пропущенных между «i» и «n» букв.
1. Определяемся с тем, какие языки будет поддерживать разрабатываемое приложение. В нашем случае это английский и русский, язык по умолчанию — русский.
2. В settings.yml приложения прописываем:
all:
settings:
i18n: on
charset: utf-8
default_culture: ru
standard_helpers: [Partial, Form, I18N]
3. В routing.yml прописываем:
default:
url: /:sf_culture/:module/:action/*
param: { module:news, action: index }
requirements: { sf_culture: (?:ru|en) }
4. Создаём модель:
news:
id: ~
date: { type: date }
news_i18n:
title: { type: longvarchar, required: true }
body: { type: longvarchar, required: true }
5. В /lib/News.php добавляем метод:
/**
* Handle I18n DB fields in Admin Generator
*
* @param string $method
* @param mixed $arguments
* @return mixed
*/
public function __call($method, $arguments)
{
$data = split('I18n', $method, 2);
if( count($data) != 2 )
{
// original call for support sfPropelBehavior
return parent::__call($method, $arguments);
}
list( $method, $culture ) = $data;
if (4 == strlen($culture))
{
$culture = strtolower(substr($culture, 0, 2)) . '_' . strtoupper(substr($culture, 2, 2));
}
else
{
$culture = strtolower($culture);
}
$this->setCulture( $culture );
return call_user_func_array(array($this, $method), $arguments);
}
6. Текущий язык админки может переключаться на непредусмотренный разработчиком, вследствие чего перестаёт выводиться информация, зависящая от языка. Возможные причины:
- На странице происходит обращение к несуществующему JavaScript файлу, изображению и т.д. В этом случае язык переключается на js и uploads соответственно.
- По какой-то причине (сбой в БД или человеческий фактор) в таблице news_i18n отсутствуют записи для соответствующих записей из таблицы news
- Symfony пытается получить текстовое представление новости, но в файле /lib/News.php отсутствует метод __toString()
Чтобы работа админки не нарушалась, проверяем текущий язык в обработчиках List, Save, Create, Edit и, если он некорректный, то устанавливаем язык по умолчанию:
public function executeList(sfWebRequest $request)
{
if ( !in_array($this->getUser()->getCulture(), array('ru', 'en') ) ) {
$this->getUser()->setCulture('ru');
}
parent::executeList($request);
}
7. Чтобы получить возможность переводить TITLE модулей, прописанный в view.yml, вносим изменения в /lib/symfony/helper/AssetHelper.php:
function include_title()
{
//$title = sfContext::getInstance()->getResponse()->getTitle();
//echo content_tag('title', $title)."\n";
$context = sfContext::getInstance();
if (sfConfig::get('sf_i18n')) {
$i18n = $context->getI18N();
$title = sfContext::getInstance()->getResponse()->getTitle();
echo content_tag('title', $i18n->__($title))."\n";
} else {
$title = sfContext::getInstance()->getResponse()->getTitle();
echo content_tag('title', $title)."\n";
}
}
8. Этот пункт рассмотрим на примере. При добавлении новости через админку в таблицу news добавляется запись новости и в news_i18n добавляется 2 записи для русского и английского языка. Допустим, у пользователей сайта есть возможность добавлять новости. Пользователь добавляет новость на английском языке, при этом добавляется одна запись в таблицу news и одна запись в news_i18n для английского языка.
Теперь зайдя в админку на русском языке мы эту новость не увидим в списке новостей.
Идём в файл actions.class.php новости в кэше админки и комментируем строчку в методе executeList($request):
//$this->pager->setPeerMethod('doSelectWithI18n');
9. Допустим, что в списке новостей необходимо выводить только новости, у которых прописан текст на текущем языке (например, не все новости ещё переведены на русский язык).
Использовать плагин sfPropelPager для постраничной навигации в этом случае не получится, т.к. если даже мы установим:
$pager->setPeerMethod('doSelectWithI18n');
то общее количество новостей будет неправильно вычисляться плагином.
Поэтому используем плагин sfPropelPagerI18n:
http://tigor.com.ua/blog/2008/08/24/symfony-sfpropelpageri18n-propel-pager-with-i18n/
Пример получения списка новостей:
public function executeIndex(sfWebRequest $request)
{
$c = new Criteria();
$c->add( NewsI18nPeer::BODY, '', Criteria::NOT_EQUAL );
$pager = new sfPropelPagerI18n('News', NewsPeer::NEWS_PER_PAGE);
$pager->setCriteriaI18n($c);
$pager->setPage($this->getRequestParameter('page', 1));
$pager->init();
$this->pager = $pager;
}
P.S.
1) Возможно, какие-то из упомянутых проблем имеют более элегантное решение.
2) Будем надеяться, что кому-то данный пост поможет сэкономить время и усилия.