Сайт на нескольких языках. Часть 1. Статический текст.

cakephp, Программирование Добавить коментарий

Данная статья предназначена для того, чтобы показать, насколько просто создавать мультиязычные сайты с использованием CakePHP.

Итак - каждый сайт содержит текст в том или ином виде. Текст может быть “статическим” и “динамическим”.

Для начала определимся с тем, что такое “статический текст”. Статический текст - это текст, который не меняется на протяжении существования сайта. То есть он жестко прописан в html-коде страницы. Примерами такого текста могут быть подписи к полям формы и так далее.

Существует много различных подходов к тому, как сделать несколько копий текстовых данных на разных языках и как их потом выводить. Но способ, который предлагает CakePHP, кажется мне наиболее логичным и удобным для использования.

Заранее договоримся, что пользоваться будем CakePHP v1.2.0.5427alpha

Итак - задача: Есть страница, которая доступна по адресу

http://mysite/staticpages/simplepage

Соответственно имеем контроллер

StaticpagesController

метод в нем

function simplepage()

и вид для него

simplepage.ctp

Вид содержит статический текст:

  1. <h1><?php echo “Hello, World!”; ?></h1>

Необходимо, чтобы строка “hello” выводилась на разных языках, в завасимости от настроек. Для примера пусть будет английский и русский языки.

Приступим.

Для начала нам необходимо в папке “app/locale” создать две папки с названиями “eng” и “rus”. Далее в каждой из этих папок создать папку “LC_MESSAGES”, где создать текстовый файл “staticpages.po”. Схема именования файла такая же, как и для контроллера, только без слова “_controller”.

Данные, которые находятся в этом файле, будут доступны только в контроллене Staticpages. Если есть какие-то данные, которые общие для разных контроллеров, их нужно поместить в файл “default.po”

В файл нужно записать (в папке “app/locale/eng/LC_MESSAGES/staticpages.po”):

  1. "Project-Id-Version: CakePHP language demon\n"
  2. "POT-Creation-Date: \n"
  3. "PO-Revision-Date: 2007-09-10 11:01+0200\n"
  4. "Last-Translator: Vyacheslav Enis <venis@difane.com>\n"
  5. "Language-Team: Difane team <support@difane.com>\n"
  6. "MIME-Version: 1.0\n"
  7. "Content-Type: text/plain; charset=windows-1251\n"
  8. "Content-Transfer-Encoding: 8bit\n"
  9. msgid "hello"
  10. msgstr "Hello, World!"

Здесь msgid - строка, которая выступает как идентификатор. По нему мы будем обращаться к данной строке.
msgstr - строка, которая является текстом, который соответствует msgid. Остальные пункты определяют кодировку файла, так что при использовании UTF-8 необходимо поменять

  1. "Content-Type: text/plain; charset=windows-1251\n"
  2. "Content-Transfer-Encoding: 8bit\n"

на

  1. "Content-Type: text/plain; charset=utf-8\n"
  2. "Content-Transfer-Encoding: 16bit\n"

Аналогично для файла “app/locale/rus/LC_MESSAGES/staticpages.po”:

  1. "Project-Id-Version: CakePHP language demon\n"
  2. "POT-Creation-Date: \n"
  3. "PO-Revision-Date: 2007-09-10 11:01+0200\n"
  4. "Last-Translator: Vyacheslav Enis <venis@difane.com>\n"
  5. "Language-Team: Difane team <support@difane.com>\n"
  6. "MIME-Version: 1.0\n"
  7. "Content-Type: text/plain; charset=windows-1251\n"
  8. "Content-Transfer-Encoding: 8bit\n"
  9.  
  10. msgid "hello"
  11. msgstr "Привет, Мир!"

Пол дела сделано.

Теперь открываем файл вида (simplepage.ctp), и меняем строку

  1. <h1><?php echo “Hello, World!”; ?></h1>

на

  1. <h1><?php echo __(“hello”); ?></h1>

Как видно, вместо строки “hello”, мы вызываем функцию “__” (да-да, именно так, два подчеркивания) и передаем ей в качестве параметра идентификатор из соответствующего файла (msgid, который в нашем случае равен “hello”).

Все, открываем страницу “http://mysite/staticpages/simplepage” в броузере и видим на экране

Hello, World!

Как видим - работает. И более того - попробуйте изменить язык, который броузер передает, как язык по умолчанию (в опере это “Tools->Preferences”, закладка “General”, поле “Language” внизу.) В зависимости от выбранного языка будет использоваться текст на правильном языке.

Автоопределение языка это конечно хорошо, но должны же быть возможность установить его вручную. И эта возможность есть. Для установки языка нужно вызвать следующую функцию:

  1. Configure::write(‘Config.language’, ‘ru’);

Здесь “ru” - идентификатор требуемого языка.

Предугадывая вопрос о том, откуда взять название для директории и название языка для установки, вот таблица для всех языков поддерживаемых данной версией CakePHP. Данные взяты из файла “\cake\libs\l10n.php”.

Языки

Название языка Сокращение (используется как имя каталога в LC_MESSAGES) Сокращение (используется при установке языка)
Albanian alb sq
Arabic ara ar
Basque baq eu
Bulgarian bul bg
Byelorussian bel be
Catalan cat ca
Chinese chi zh
Chinese zho zh
Croatian hrv hr
Croatian scr hr
Czech ces cs
Czech cze cs
Danish dan da
Dutch (Standard) dut nl
Dutch (Standard) nld nl
English eng en
Estonian est et
Faeroese fao fo
Farsi fas fa
Farsi per fa
Finnish fin fi
French (Standard) fra fr
French (Standard) fre fr
Gaelic (Scots) gla gd
Galician glg gl
German (Standard) deu de
German (Standard) ger de
Greek ell el
Greek gre el
Hebrew heb he
Hindi hin hi
Hungarian hun hu
Icelandic ice is
Icelandic isl is
Indonesian ind id
Irish gle ga
Italian ita it
Japanese jpn ja
Korean kor ko
Latvian lav lv
Lithuanian lit lt
Macedonian mac mk
Macedonian mkd mk
Malaysian may ms
Malaysian msa ms
Maltese mlt mt
Norwegian nor no
Norwegian Bokmal nob nb
Norwegian Nynorsk nno nn
Polish pol pl
Portuguese (Portugal) por pt
Rhaeto-Romanic roh rm
Romanian ron ro
Romanian rum ro
Russian rus ru
Sami (Lappish) smi sz
Scots Gaelic gla gd
Serbian scc sr
Serbian srp sr
Slovack slk sk
Slovack slo sk
Slovenian slv sl
Sorbian wen sb
Spanish (Spain - Traditional) spa es
Swedish swe sv
Thai tha th
Tsonga tso ts
Tswana tsn tn
Turkish tur tr
Ukrainian ukr uk
Urdu urd ur
Venda ven ve
Vietnamese vie vi
Xhosa xho xh
Yiddish yid yi
Zulu zul zu

Вот в принципе и все, если бы не одно “НО”. Иногда необходимо в контроллере установить значение некоторой переменной. И это значение тоже должно быть на нескольких языках. Сразу напрашивается способ:

  1. $this->set(‘test’, __($domain, “hello”));

и в виде:

  1. <h1><?php echo $test; ?></h1>

В этом случае получим немного непредсказуемое поведение - строка с идентификатором “hello” будет выведена на экран. Стобы этого избежать, необходимо добавить второй параметр и сделать его равным true

  1. $this->set(‘test’, __($domain, “hello”, true));

Но на этом подводные камни не заканчиваются. Таким способом можно установить только текст, который содержится в файлах “default.po”. То есть данный текст должен быть общим для всех доменов.

Попытка использовать текст из “staticpages.po” завершится тем, что в качестве текста будет использована строка “hello”.

Это не ошибка, такое поведение вполне ожидаемо. Функция “__” предназначена для работы со “статическим” текстом. А он в 99,9% случаев содержится в виде. В контроллере же мы работаем с данными, которые получаем из базы (в большинстве случаев), а не жестко прописываем в коде. А для этого есть свой универсальный способ ( о нем разговор пойдет в отдельной статье).

А пока, если все-же жизненно необходимо в контроллере установить значение переменной и идентификатор находится не в “default.po”, а например в “staticpages.po”, то можно воспользоваться следующим способом:

  1. $domain = Inflector::underscore(substr(get_class($this),
  2.        0, strlen(get_class($this))-10));$this->set(‘test’,
  3.        __d($domain, “hello”, true));

Данный код позвольте оставить без комментариев.

Ниже представлены исходные коды всех необходимых файлов:

app\locale\eng\LC_MESSAGES\staticpages.po

  1. "Project-Id-Version: CakePHP language demo\n"
  2. "POT-Creation-Date: \n"
  3. "PO-Revision-Date: 2007-09-10 11:01+0200\n"
  4. "Last-Translator: Vyacheslav Enis <venis@difane.com>\n"
  5. "Language-Team: Difane team <support@difane.com>\n"
  6. "MIME-Version: 1.0\n"
  7. "Content-Type: text/plain; charset=windows-1251\n"
  8. "Content-Transfer-Encoding: 8bit\n"
  9.  
  10. msgid "hello"
  11. msgstr "Hello, World!"

app\locale\rus\LC_MESSAGES\staticpages.po

  1. "Project-Id-Version: CakePHP language demo\n"
  2. "POT-Creation-Date: \n"
  3. "PO-Revision-Date: 2007-09-10 11:01+0200\n"
  4. "Last-Translator: Vyacheslav Enis <venis@difane.com>\n"
  5. "Language-Team: Difane team <support@difane.com>\n"
  6. "MIME-Version: 1.0\n"
  7. "Content-Type: text/plain; charset=windows-1251\n"
  8. "Content-Transfer-Encoding: 8bit\n"
  9.  
  10. msgid "hello"
  11. msgstr "Привет, Мир!"

app\controllers\staticpages_controller.php

  1. <?php
  2. class StaticpagesController extends AppController {     
  3.         var $uses = array();           
  4.         function simplepage()
  5.         {
  6.                 Configure::write(‘Config.language’, ‘ru’);
  7.         }
  8. }
  9. ?>

app\views\staticpages\simplepage.ctp

  1. <h1><?php echo __(“hello”); ?></h1>

Также вы можете скачать исходные коды одним архивом здесь

С Уважением,
Difane-team

Комментариев - 25 на “Сайт на нескольких языках. Часть 1. Статический текст.”

  1. demon.mhm говорит:

    Отличная статья, спасибо большое! А как просто реализовать переключение языковой среды пользователем? Делать отдельные lang_controller с одной функцией set_lang()? Или можно как-то более щтатными методами обойтись?

  2. Вячеслав (Difane-team) говорит:

    По поводу переключения языка - следующий код

    1. Configure::write(’Config.language’, ‘ru’);

    Можно вызывать в любом месте (в принципе). Как вариант можно сделать так:

    1) В AppController добавить функцию beforeFilter:

    1. function beforeFilter()
    2. {
    3.     $locale = “en”;
    4.     if(isset($this->params[’locale’]))
    5.     {
    6.         $locale = $this->params[’locale’];
    7.         Configure::write(’Config.language’, $locale);
    8.     }
    9. }

    В “config/routes.php” в начале добавить:

    1. $Route->connect(’/:locale/:controller/:action/*’);

    Теперь на все ссылки вида:

    http://mysite/ru/controller/action будет устанавливаться рус. язык

    http://mysite/en/controller/action будет устанавливаться англ. язык

    и т.д.

  3. Влад говорит:

    Отличная статья и хорошо оформлена. Надеюсь ты на этом не остановишься и статьи будут появляться регулярно.

    Ещё раз спасибо за статью :)

    И ни одного упоминания об i18n? ;-))

  4. Вячеслав (Difane-team) говорит:

    Спасибо за теплые слова. Будем стараться. Есть еще много о чем рассказать касательно CakePHP.

    А по поводу i18n - не все сразу. Статья расчитана на то, чтобы человек, не знакомый с тонкостями, сразу смог получить результат. Подробнее обо всех механизмах - в следующих статьях.

    P.S. Кстати, может есть вопросы либо темы, которые будут интересны - пишите, предлагайте. Постараемся осветить все.

  5. Сергей говорит:

    Программа poEdit (http://www.poedit.net/) сильно облегчит создание .po и .mo файлов.

  6. Вячеслав (Difane-team) говорит:

    Спасибо за упоминание “poEdit”.

    Программа несомненно заслуживает внимания, но работа с ней может показаться немного нетривиальной. И к сожалению есть небольшие нестабильности в ее работе.

    Возможно позже будет выложен инструментарий, которым мы пользуемся для своих нужд.

  7. Алексей (Difane-team) говорит:

    А вот небольшое изменение в коде cake, которое позволит тем же механизмом переводить сообщения об ошибках:

    в файле /cake/libs/view/helpers/form.php, в функции error, перед строкой (у меня она 341) :

    1. if ($options[‘wrap’] === true) {

    вставляем следующий код:

    1. $domain = Inflector::underscore(substr(get_class($this), 0, strlen(get_class($this))-10));
    2. $error = __d($domain, $error, true);

    в модели валидация выглядит так:

    1. “uFamilyname” => array (
    2.    ‘required’ => VALID_NOT_EMPTY,
    3.    ’simpleText’ => array ( ‘rule’ => ‘validateSimpleText’ ),
    4.    ‘minLength’ => array ( ‘rule’ => array(‘minLength’,2) ),
    5.    ‘maxLength’ => array ( ‘rule’ => array(‘maxLength’,50) )
    6. )

    В нашем .po файле вставляем

    1. msgid “minLength”
    2. msgstr “Текст должен быть более длинным”

    и в результате при использовании функции $form->error (а она же вызывается при всех ошибках валидации в хелпере form) вместо minLength получаем необходимую строку :)

  8. Ghost говорит:

    Что-то у меня после добавления в “config/routes.php” строки
    $Route->connect(’/:locale/:controller/:action/*’);

    появилась ошибка “You are seeing this error because controller Controller could not be found.” :(

  9. Вячеслав (Difane-team) говорит:

    Если вы о том, что было сказано в комментарии выше - то там нужно название контроллера заменить на свое.

  10. Ghost говорит:

    Не помогло…
    Я о коментарии №2

  11. Ghost говорит:

    Разобрался. Извините, тупил (:

  12. Вячеслав (Difane-team) говорит:

    Не надо извиняться. Не во всем удается разобраться с первого раза. Если у Вас будут возникать какие-либо вопросы - не стесняйтесь, пишите. Постараемся помочь в меру сил.

  13. Ghost говорит:

    А можно ли сделать так, чтобы значение msgstr бралось из default.po, если соответствующего msgid нету в файле “контроллер”.po?

  14. Вячеслав (Difane-team) говорит:

    Это так и происходит.

  15. Ghost говорит:

    У меня почему-то так происходит лишь при отсутствии файла “контроллер”.po…

  16. Вячеслав (Difane-team) говорит:

    Да, действительно. Изначально проверяется файл “controller”.po и только если его нет - сообщение берется из default.po

    Так как данный момент действительно необходим - постараемся выложить решение проблемы.

  17. VolCh говорит:

    Не забывайте про вывод языка текущего в атрибутах lang (и xml:lang для xHTML) тега . Например, чтобы Google знал на каком языке страница :). Или вообще их не используйте, а то странно выглядит страница набранная на русском с атрибутами установленными в “en”. И уж совсем хорошо, если другой язык будете выделять теми же тегами, например на русской странице ссылку на английский вариант делать так

    1. <a href=“/” title=“View this page in English” rel=“nofollow”>English</a>
  18. VolCh говорит:

    Чёрт, теги съелись :(

  19. Вячеслав (Difane-team) говорит:

    VolCh, Спасибо за дополнение. Комментарий поправлен.

  20. wialy говорит:

    Уважаемый, когда продолжения ждать? Очень нетерпится,) За статью спаибо!

  21. Вячеслав (Difane-team) говорит:

    Спасибо за то, что вы все еще с нами. Постараемся при первой же возможности выкладывать интересные статьи. К сожалению после праздников времени на написание не настолько много, как хотелось бы.

  22. wialy говорит:

    Да, знакомая ситуация, со времением. Удачи вам!

  23. sprint говорит:

    Привет!
    Использую кейк 1.2. Не читаются сообщения из файлов “контроллер”.ро а только из дефолтного. Кто знает в чем может быть проблема?
    Спасибо!

  24. Вячеслав (Difane-team) говорит:

    Странно. Должно работать. Предоставить фрагмент примера, где это наблюдается (описать здесь) или выслать минимальный проект. постараемся помочь.

  25. sprint говорит:

    Не работает. И нигде не читал такого. Только что пробовал с нуля с чистым кейком. не работает. мне тут подсказали юзать метод __d(’ФАЙЛ’, ‘КЛЮЧ’); Это работает… Буду искать дальше… я юзаю кейк версии 1.2.0.6311. Использую все стандартно, даже не представляю что бы тут публиковать…

Оставить комментарий

WP Theme & Icons by N.Design Studio
RSS Коментарии RSS Войти