Мне потребовалось сделать мультиязычный сайт аж на 5 языках. В целом тут нет ничего сложного, кроме того, что пользователю надо будет заполнять 5 форм на разных языках. Логичный вывод - сделать обязательным только один язык, например английский. Остальные заполняются по желанию, а для незаполненных показывается на том же английском. Промучившись некоторое время с рецептами от Календаря Адвента, и не добившись успеха, я нашел пост [], который мне помог. Я считаю, что полезным будет сделать его перевод. Так же я добавлю некоторые свои комментарии. (Перевод вольный, эстеты идут лесом)
Некоторые люди утверждают, что Symfony - это подарок богов. Другие считают, что это преувеличение. Но как ни крути, в версиях 1.3/1.4 добавилось много нового и полезного, что может сэкономить вам вермя.
Представим проект, где есть разные заметки (новости, интервью, и т.д.). И они могут быть на разных языках. И это довольно просто сделать с actAs: I18n. Но есть небольшая сложность: переводы должны быть опциональны, чтобы можно было написать статью только на французском, английском или немецком.
Некоторые материалы по теме: , , , , .
Итак, начнем со схемы.
Article:
actAs:
Timestampable: ~
I18n:
fields: [ title, body ]
actAs:
Sluggable: { fields: [ title ], uniqueBy: [ lang, title ] }
columns:
title: { type: string(255), notnull: true }
body: { type: clob, notnull: true }
author: { type: string(255), notnull: false }
News:
inheritance:
extends: Article
type: concreteА так же фикстуры:
News:
n1:
author: 'Lenta.ru'
Translation:
ru:
title: 'Нет вестей с Титана'
body: |
Титан – это шестой и самый крупный спутник Сатурна.
n2:
author: 'Bash.org'
Translation:
en:
title: '#921792'
body: |
<Thomas> if women think they arent meant to cook
<Thomas> why do they have milk and eggs inside them?Загружаем все это добро в базу данных:
php symfony doctrine:build --all --and-load php symfony generate:app backend php symfony doctrine:generate-admin backend News
Загляните в только что построенный модуль админки. Нажмите кнопку "редактировать" и оп.. а где все наши переводы? Если вы еще не в курсе, actAs:I18n разделяет таблицу на 2 части, в первой содержатся общие поля, не зависящие от перевода, а во второй те, которые требуют перевода.
mysql> SELECT * FROM news; +----+-------------+---------------------+---------------------+ | id | author | created_at | updated_at | +----+-------------+---------------------+---------------------+ | 1 | Lenta.ru | 2010-01-29 12:14:46 | 2010-01-29 12:14:46 | | 2 | Bash.org | 2010-01-29 12:14:46 | 2010-01-29 12:14:46 | +----+-------------+---------------------+---------------------+ mysql> SELECT id, lang, title FROM news_translation; +----+------+-----------------------------------------------------+ | id | lang | title | +----+------+-----------------------------------------------------+ | 1 | ru | Титан – это шестой и самый крупный спутник Сатурна. | | 2 | en | <Thomas> if women think they arent meant to cook ...| +----+------+-----------------------------------------------------+
Чтобы нам было доступно редактирование переводов - надо воспользоваться функцией embedI18n.
// lib/form/doctrine/NewsForm.class.php class NewsForm extends BaseNewsForm { /** * @see ArticleForm */ public function configure() { parent::configure(); $this->embedI18n(array('ru', 'en')); } }
Вуа ля. Редактировать можно!
Немного приберемся
Наш код сейчас не так хорош, как мог бы быть:
Каждый раз, когда мы будем добавлять новый тип статьи - нам надо будет менять метод configure;
Каждый раз, когда мы будем добавлять/удалять новый язык для статей - нам надо будет менять все формы.
К счастью, с Symfony 1.3, наследование форм повторяет наследование моделей. Смотрите, NewsForm наследует BaseNewsForm, которая в свою очередь наследует ArticleForm.
# config/app.yml
all:
cultures:
enabled:
ru: Russian
en: English// lib/form/doctrine/NewsForm.class.php // Revert the changes we added there class NewsForm extends BaseNewsForm { /** * @see ArticleForm */ public function configure() { parent::configure(); } } // lib/form/doctrine/ArticleForm.class.php class ArticleForm extends BaseArticleForm { /** * Available languages * * @var array $languages **/ protected $langages; public function configure() { $this->languages = sfConfig::get('app_cultures_enabled'); $langs = array_keys($this->languages); $this->embedI18n($langs); foreach($this->languages as $lang => $label) { $this->widgetSchema[$lang]->setLabel($label); } } }
Перезагрузите теперь страницу. Вы можете теперь добавлять/удалять языки независимо от количества типов статей.
Редактируем переводы
Давайте теперь попробуем нашу форму для статей. Когда вы попробуете редактировать какую-нибудь новость, то... постыдная неудача. Нельзя сохранить, потому что нет английского или русского перевода. Давайте добавим условие, что если в форме есть пустые поля - мы эти формы не сохраняем.
Для этого мы перекроем метод doBind.
// lib/form/doctrine/ArticleForm.class.php class ArticleForm extends BaseArticleForm { /** * Available languages * * @var array $languages **/ protected $langages; public function configure() { $this->languages = sfConfig::get('app_cultures_enabled'); $langs = array_keys($this->languages); $this->embedI18n($langs); foreach($this->languages as $lang => $label) { $this->widgetSchema[$lang]->setLabel($label); } } /** * Cleans and binds values to the current form * * Ignore i18n forms when all their fields are empty * * @see sfForm::doBind **/ protected function doBind(array $values) { foreach($this->languages as $lang => $label) { if($this->embeddedI18nFormIsEmpty($values[$lang])) { unset( $values[$lang], $this[$lang] ); } } parent::doBind($values); } /** * Check if every fields, except for id and lang, are empty **/ protected function embeddedI18nFormIsEmpty(array $values) { foreach($values as $key => $value) { if(in_array($key, array('id', 'lang'))) continue; if('' !== trim($value)) { return false; } } return true; } }
В перекрытом методе doBind, мы проверяем каждую I18n форму, и, если надо делаем ей unset. Теперь все сохраняется как надо, однако же...
Загляните в базу данных, вас там ждет сюрприз.
mysql> SELECT id, lang, slug FROM news_translation; +----+------+------------------------------------------------------+ | id | lang | slug | +----+------+------------------------------------------------------+ | 1 | en | | | 1 | ru | Титан – это шестой и самый крупный спутник Сатурна. | | 2 | en | <Thomas> IF women think they arent meant TO cook ... | +----+------+------------------------------------------------------+
Где-то в процессе сохранения, Symfony создала пустой объект перевода. И вот решение для этой проблемы.
// lib/form/doctrine/ArticleForm.class.php // Add this at the beginnig of the class: /** * I18n ignored forms **/ protected $I18nFormsIgnored = array(); // update the doBind method: /** * Unset i18n forms values when every field is empty **/ protected function doBind(array $values) { foreach($this->languages as $lang => $label) { if($this->embeddedI18nFormEmpty($values[$lang])) { $this->I18nFormsIgnored[] = $lang; unset( $values[$lang], $this[$lang] ); } } parent::doBind($values); } // And override the doUpdateObject method: /** * Updates the values of the object with the cleaned up values. * * @param array $values An array of values * * @see sfFormDoctrine::doUpdateObject() */ protected function doUpdateObject($values) { parent::doUpdateObject($values); foreach($this->I18nFormsIgnored as $lang) { unset($this->object->Translation[$lang]); unset($values[$lang]); } }
На этот раз все сохраняется как надо!
(Здесь я пропускаю абзац про убирание поля Slug. Там есть сложность с тем, что формы перевода не наследуются. Если вам интересно - посмотрите в оригинальной статье. Я считаю, что основное здесь - как раз перекрытие методов doBind и doUpdateObject.
Тесты, приведенные автором можно посмотреть у него в статье, а можно и ).
На этом все. Надеюсь вам пригодится мой перевод.
А еще я писал про:









