Предисловие
Вообще говоря, слово «интернацинализация» для веб-разработчика очень часто означает кошмар. Чаще всего то, как это делается, понимается смутно. Так кое-кто из моих знакомых начинает вспоминать какие-то зеркала, еще что-то, какие-то словари и так далее.
Вот я и решил провести небольшое исследование на эту тему. Причем рассмотреть этот вопрос на примере того, как это делает два PHP-фреймворка — Symfony и не шибко известный Akelos.
Почему Akelos? Во-первых, потому что в недавнем времени мне пришлось писать под него приложение с поддержкой 3 человеческих языков. Akelos, как мне сообщили некие авторитетные люди, есть полный клон RoR. Не могу сказать правда это или нет, так не имею опыта работы с последним.
Теория (свободный перевод из мануала Symfony)
Слово «интернационализация» (internationalization) довольно длинное. Поэтому разработчики привыкли сокращать его до простого «i18n» (понимается как i, 18 английских букв и n в конце. Локализация (localization) точно таким же образом сводится к лаконичному «l10n». Вот эти два понятия и покрывают 2 разных аспекта разработки мультиязычных веб-приложений.
Под интернационализацией понимают несколько версий одного и того же контента на разных языках или в разных форматах. Например, интерфейс электро-почты может предлагать один и тот же сервис на разных языках. Меняются только надписи на кнопках.
Локализация же предполагает определенный контент, зависящий от страны, из который ты пришел. Возьмем для примера новостной портал. Если ты находишься в Украине, то тебе показывают последние заголовки об Украине. Если же ты находишься в России, то тебе показывают последние заголовки об Путине. Все просто. Стоит сказать, что локализация может включать в себя не только перевод контента, но и сам контент может отличаться в разных локализированных версиях.
Если все обобщить, то получится что i18n и l10n означает, что приложение умеет справляется с:
- Переводом текста на разные языки (как в интерфейсе, так и в самом содержании)
- Стандартами и форматами (даты, суммы, числа и тому подобное)
- Локализацией контента (множество версий заданного объекта в зависимости от страны)
Как же это делает Symfony?
Здесь я не собираюсь переводить целую главу из мануала Symfony. Все там достаточно толково и понятно описано. Просто остановимся вкратце на некоторых вещах.
В Symfony есть такой замечательный объект как sfUser, который инкапсулирует все рутину по работе с пользовательской сессией. По идее, про $_SESSION вы должны забыть совсем. Так что имейте это ввиду.
Итак, в этом объекте sfUser есть такой замечательный параметр как “Culture”. В настройках для приложения вы естественно можете установить его по умолчанию и переопределить, если надо, в контроллере.
Интересно, что Culture, кодируется двумя кодами – двухбуквенным кодом для языка в нижнем регистре (по ISO 639-1) и и двухбуквенным кодом для страны в верхнем регистре (по ISO 3166-1) примерно так – «fr_FR», «ru_UA». Это сделано для таких случаев, когда нам нужен один контент на одном языке, но для разных стран. Например, один контент на испанском языке для Мексики, и совершенно другой для Испании.
Что еще интересного? Поддерживается автоматическое добавление параметра Culture в URL. Для этого необходимо настроить параметры роутинга для нужных страниц и все.
Info Symfony, как и большинство приложений в таких случаях, при первом запросе определяет язык по HTTP-заголовку «Accept-Language».
Поддерживается форматирование дат, денежных сумм, чисел. с помощью хелперов.
Плюс поддерживается ввод и перевод во внутренний формат всех данных, зависящих от культурных особенностей (См. класс sfI18N).
Теперь об локализации приложений. Рассмотрим случай с существованием одно и того же контента на разных языках и хранение этой информации в базе данных.
В Symfony каждая таблица, которая содержит данные для разных языков, должна быть разделена на 2 таблицы. Первая – это та, которая не содержит никакой связанной с локализацией информации. И вторая – это та, которая содержит только локализированние данные. Эти две таблицы связаны с друг другом связью «один ко многим».
Пример задание схемы базы данных:
my_product:
_attributes: { phpName: Product, isI18N: true, i18nTable: my_product_i18n }
id: { type: integer, required: true, primaryKey: true, autoincrement: true }
price: { type: float }
my_product_i18n:
_attributes: { phpName: ProductI18n }
id: { type: integer, required: true, primaryKey: true, foreignTable: my_product, foreignReference: id }
culture: { isCulture: true, type: varchar, size: 7, required: true, primaryKey: true }
name: { type: varchar, size: 50 }
Как видим, первая таблица содержит только первичный ключ «id» и цену товара. Во второй таблице, объявлен внешний ключ на «id» из первой таблицы. К тому же присутствует дополнительно поле «culture» для задание параметра Culture, и естественно само поле?содержащее необходимый контент, «name». Symfony позволяет не описывать все так подробно. Все будет сделано само собой, если придерживаться соглашения об именах (см. мануал).
Теперь о сохранении данных. Для необходимо перед сохранением устанавливать Culture c помощью метода setCulture() класса модели. Можно автоматизировать этот процесс и делать все эти необходмые дела внутри метода hydrate(), перопределив его примерно таким образом в своей модели:
public function hydrate(ResultSet $rs, $startcol = 1)
{
parent::hydrate($rs, $startcol);
$this->setCulture(sfContext::getInstance()->getUser()->getCulture());
}
Выборка осуществляется с помощью метода doSelectWithI18n() в классах Peer’ах.
Info Для справедливости стоит сказать, что все эти выборки и set’ы справедливы для ORM-фреймворка Propel, который по умолчанию использует Symfony. Есть еще и Doctrine. Но его мы рассмотрим как-нибудь потом.
Перевод интерфейса (всяких там сообщений, лейбочек и тому подобное) сделан просто и со вкусом. Для этого всего лишь необходимо использовать хелпер __($phrase) – два символа подчеркивания, если кто не понял. Каждый раз, когда вызывается этот хелпер, Symfony смотрит перевод $phrase в словаре в зависимости от выбранного языка.
Словари написаны в c использованием XML Localization Interchange File Format (XLIFF). Первый раз встретил этот формат именно в Symfony. Вид имеет простецкий. К сожалению чего не скажешь о самом XML, который читается, честно говоря, с трудом.
Пример:
<?xml version="1.0" ?>
<xliff version="1.0">
<file orginal="global" source-language="en_US" datatype="plaintext">
<body>
<trans-unit id="1">
<source>Welcome to our website.</source>
<target>Bienvenue sur notre site web.</target>
</trans-unit>
<trans-unit id="2">
<source>Today's date is </source>
<target>La date d'aujourd'hui est </target>
</trans-unit>
</body>
</file>
</xliff>
Есть еще мелкие приятности, которые имеет Symfony. Это и перевод в зависимости от параметров (полезно для сообщений типа «1 комментарий», «2 комментария». Это и перевод предложений, содержащих переменные. И тому подобное.
Конец первой части
Хватит наверно для начала. Писалось это все в субботу утром за чашечкой чая, когда 2 чувака в квартире, где я проживаю, меняли двери, сверлили там вовсю, хлопали и топали. Пока все.