Навигация
Главная »  Xml 

Как добавить интернациональную поддержку в ваши PHP-приложения (исходники)


Источник: IBM developerWorks Россия
Роберт Брэдли

Оценка приложения

Требования к локализации могут быть довольно туманными, например "подготовить это приложение для Германии". Но даже когда кажется, что требования подробны, вы можете обнаружить вещи, которые менеджер по продукту упустил из виду.

Возьмём, к примеру, приложение Yahoo! RSS news reader, показанное в Листинге 1. Когда страница вызывается в первый раз, отображается список заголовков по умолчанию, а также поле формы, в которое можно впечатать выбранную категорию новостей перед повторной отправкой страницы. Приложение проверяет введённую категорию и либо выводит сообщение об ошибке, если категория введена неправильно, либо отображает заголовки запрошенной категории.

Листинг 1. RSS news от Yahoo!
    RSS Feed from Yahoo News   

RSS Feed from Yahoo News

Добро пожаловать в Yahoo RSS news reader. Для просмотра заголовков различных новостей введите тип новости в поле внизу, а затем отправьте форму. Допустимые типы новостей - 'tech' и 'world'.

' . $error . '
'; } ?>


parse(); echo "
    \n"; foreach ($rss->getItems() as $item) { echo "
  • " . $item['title'] . "
  • \n"; } echo "
\n"; ?>

Даже в таком простом виде, это приложение иллюстрирует многие проблемы, возникающие в ходе локализации. Оно также отражает некоторые преимущества и недостатки любого скриптового языка.

Всегда можно сделать продукт быстро, но за это придётся заплатить свою цену. Пользовательский интерфейс (UI), бизнес-логика и конфигурация, или же их отсутствие, смешиваются вместе, безо всякой мысли о поддержке или расширении страницы новыми функциям.

При локализации этой страницы для немецкого, и, возможно, других языков, становятся очевидными несколько препятствий:

  • Следует провести рефакторинг кода для выделения различных уровней приложения.
  • Текст пользовательского интерфейса, например, заголовки, содержание и сообщения об ошибках, нужно извлечь и перевести.
  • Элементы управления формами, например, кнопку Submit (отправить), нужно локализовать.
  • В приложении должен быть предусмотрен способ определения целевой локали (target locale).
  • Требования к пользовательскому вводу и его проверка изменяются в зависимости от локали.
  • Рассмотрите бизнес-правила, зависящие от места действия, например, URL для RSS-каналов Yahoo!; места назначения и синтаксис меняются от страны к стране.
  • Увеличивается потребность в среде, поддерживающей настройку и управление поведением области приложения для каждой локали.
Усилия, затраченные на всё это, могут быть такими же, как при создании собственно приложения, или превосходить их - горькая пилюля для всех, кого это касается. В итоге ваша команда должна быть готова взять этот барьер - до или после завершения работы над приложением.

Терминология по локализации

Если в ходе проекта вам придётся иметь дело с переводчиками, хорошо бы убедиться, что вы понимаете некоторые термины, которые вы, вероятно, встретите, общаясь с ними. В Таблице 1 даны определения нескольких общеупотребительных терминов, используемых лингвистами и специалистами по переводу.

Таблица 1. Терминология по локализации

Термин

Значение

Локаль Комбинация языка и страны/региона - например, en_US, en_GB, de_DE
Интернационализация (I18N) Планирование, проектирование и подготовка приложений к локализации
Локализация (L10N) Определение и перевод (T9N) пользовательского интерфейса приложения для целевой локали
Экстернализация (извлечение строк) Процесс извлечения строк программы для перевода


Встраивание локализации

Листинг 2 показывает, как выглядит приложение RSS после рефакторинга и модернизации для поддержки локализации. Функциональная логика и среда локализации абстрагируются в класс обработчика (handler class). Всё, что остаётся в основном коде - это конструирование объекта обработчика страниц, шаблон HTML и обращения к методам accessor обработчика для наполнения динамических фрагментов шаблона HTML.

Листинг 2. Локализация RSS news
      <?php echo $handler->getTitle() ?>   

getTitle() ?>

getGreeting() ?>

showerror() ?>

showHeadlines() ?>

Преимущество рефакторинга состоит в том, что, возможно, программистам легче осуществлять поддержку кода. При этом вызывает сомнение, что пользовательский интерфейс стало легче поддерживать при отсутствии лингвистического контекста и встраивании значительных фрагментов HTML в PHP-код.

Доверят ли PHP-программисты выполнять подобные изменения Web-разработчику? Какую цену придётся платить за то, что разработчикам внутреннего интерфейса приходится иметь дело с второстепенными запросами на изменение в пользовательском интерфейсе? Позднее я покажу вам, каким образом язык Extensible Stylesheet Language Transformation (XSLT) может помочь в разграничении уровней приложения.

Конструктор

Начнём с конструктора для класса обработчика (см. Листинг 3), чтобы увидеть, каким образом страница была изменена для локализации. При создании экземпляра обработчика страницы конструктор определяет соответствующую локаль, извлекает параметры конфигурации для этой локали, проверяет ввод пользователя, вычисляет весь некогда статический контент, руководствуясь текущей локалью пользователя, а затем проверяет любой пользовательский ввод. (Исходный код см. в разделе Downloads.)

Листинг 3. RSSHandler.php
 public function __construct() {  # настройка локали putenv("LANG=de_DE");       # для конфигурации setlocale(LC_ALL, 'de_DE');   # для gettext  # Получение зависящей от локали конфигурации $this->conf = new RSSConfig; $this->configdecorator = new Configdecorator($this->conf);  # установка окружения извлечения текста bindtextdomain($this->conf->getTextdomainmessage(), $this->conf->getTextdomainpath()); textdomain($this->conf->getTextdomainmessage());      # Извлечение текста для перевода. $errormsg = gettext("News type is not valid."); $this->title = gettext("Yahoo RSS News"); $this->submitbutton = gettext("Get the news."); $lgreeting = gettext("Welcome to the Yahoo RSS news reader. ") . " " . gettext("Enter a news type, then submit the form.") . " " . gettext ("Valid news types are: %s."); $decoratedtypes = $this->configdecorator->getNewsTypes(); $this->greeting = sprintf($lgreeting, $decoratedtypes);      # Нормализация и проверка $this->newstype = $this->normalize(); $this->error = $this->validate() ? ' : $errormsg; } 

В данном примере ради простоты локаль жёстко закодирована на de_DE. В реальной жизни вам потребуется настраивать локаль в зависимости от бизнес-требований. Два общепринятых способа определения правильной локали - посмотреть URL запроса (например, www.ebay.de, amazon.fr), или же просто спросить пользователя и установить в его браузере cookie-файл с предпочтениями.

Извлечение параметров конфигурации

После определения локали обработчик извлекает параметры конфигурации для Германии. В данном примере конфигурация предполагает использование пакета PEAR (Config.php), конфигурационный XML-файл (RSSConfig.xml), надстройку, относящуюся к области приложения (RSSConfig.php) и простой класс decorator для добавления HTML-разметки для некоторых параметров конфигурации (Configdecorator.php).

Конфигурационный файл, показанный в Листинге 4, хранит информацию в XML-формате. Он определяет окружение извлечения строки (//conf/textdomain) - местонахождение зависимых от локали компонентов и имя файла сообщений.

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

Конфигурационные файлы также сообщают приложению, как сконструировать правильный URL новостного канала Yahoo! (//conf/de_DE/baseurl), как проверить выбор пользователем типа новостного канала (//conf/de_DE/newtypes) и какой тип новостей должен использоваться по умолчанию, если пользователь не сделал выбора.

Листинг 4. Configuration.xml
    /home/bbradley/Sites messages   http://rss.news.yahoo.com/rss/   true tech tech    world world     http://de.news.yahoo.com/   true Ausland politik/ausland.html.xml   Technik technik/index.html.xml     

Конфигурацию можно сделать более устойчивой к сбоям, добавив локаль "по умолчанию". В этом случае, если приложение пытается выполнить недопустимую локаль, всегда можно гарантировать какое-то разумное поведение приложения. Также можно расширить конфигурацию для задействования других возможностей приложения, например кобрендинга или ограничений на основе прав пользователей.

Остальная часть объекта обработчика отвечает в основном за порождение текстовых фрагментов страницы соответствующим образом для каждой локали и обеспечение публичных методов accessor, которые делают эти кусочки переведённого текста доступными HTML-шаблону.

Среда извлечения gettext

Приложение использует среду извлечения строк с открытым исходным кодом под названием gettext для фактуализации переведённых строк. Эта среда имеет несколько преимуществ:

  • Она бесплатная.
  • Она работает на множестве платформ.
  • Она работает со многими языками программирования, в том числе и с PHP.
  • Она особенно подходит для модернизации (retrofitting).
Чтобы использовать gettext:

  1. Создайте структуру каталогов для всех локалей, поддержку которых необходимо обеспечить.
  2. Разметьте строки, передвигая их в аргументы специальной функции gettext().
  3. Запустите утилиту командной строки xgettext для извлечения строк в файл сообщений.
  4. Скопируйте получившийся файл сообщений в каждую директорию для локали.
  5. Отредактируйте локальные версии файла сообщений и переведите текст.
  6. Запустите утилиту командной строки msgfmt для создания локализованных баз данных сообщений, которые будут использоваться во время выполнения.
Важно понимать, что функция gettext() служит очевидной цели извлечения локализованного текста во время выполнения. Но также важно и то, что она является ещё и маркером для извлечения текста. Утилита xgettext ищет строки, которые отмечает эта функция.

Все программные компоненты примера приложения, в том числе структура каталогов, определённая в конфигурации приложения, приведены в Листинге 5. Для каждой реализации gettext требуется аналогичная структура каталогов. Не требуется создавать дерево каталогов или базу данных сообщений для локали по умолчанию; строки для этой локали уже внедрены в код. Однако вам потребуется отредактировать файл RSSConfig.xml, точно указав полный путь к этим файлам для среды gettext.

Листинг 5. Структура каталогов
 ConfigDecorator.php - класс Decorator de_DE - директория для Германии LC_MESSAGES - поддиректория для сообщений messages.mo - база данных скомпилированных сообщений messages.po - переведённые сообщения messages.po - создаётся xgettext news2.php - главное приложение / HTML-шаблон RSSConfig.php - класс конфигурации приложения RSSConfig.xml - конфигурационный файл приложения RSSHandler.php - обработчик HTTP-запросов / бизнес-логика 

Перевод файла сообщений

В немецком файле сообщений жирным шрифтом отмечены те части, которые должен предоставить переводчик. Другими словами, переводчик должен заполнить пустые поля. Когда переводчик в первый раз открывает файл сообщений, эти части будут пустыми, а переводчик должен указать кодировку, которую он или она будет использовать, в данном случае, Latin-1, а также сделать переводы. ID сообщения (msgid) - это оригинальный текст, отмеченный для извлечения с помощью вызова функции gettext(). Строка сообщения (msgstr) - это та строка, которая должна быть в языке, на который делается перевод. В Листинге 6 показан перевод сообщения об ошибке, которое выдаётся, когда пользователь указывает неправильный тип новостей.

Листинг 6. Немецкий файл сообщений, messages.po.de
 # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid " msgstr " "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2006-11-05 11:05-0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-1\n" "Content-Transfer-Encoding: 8bit\n"  #: RSSHandler.php:31 msgid "News type is not valid." msgstr "Die Nachrichtkategorie ist falsch." 

При каком-либо изменении текста любой извлечённой строки, во время следующего создания файла сообщений будет создаваться и новое ID сообщения. Всякий раз, когда добавляются или изменяются строки, файл сообщений следует генерировать заново, иначе в целевых локалях новый текст будет отображаться непереведённым.

В производственной среде все эти задачи (помимо подготовки исходного кода) обычно выполняет группа L10N. Но это не означает, что вы можете совершенно не уделять внимание лингвистическим проблемам. При локализации предложения не забывайте о следующих моментах:

  • Не внедряйте в строки функциональную информацию.
  • Используйте маркеры printf для динамического содержимого в целом предложении, вместо того, чтобы разрывать предложение на несколько строк. Помните, что порядок слов в предложении в различных языках разный; не нужно лишать переводчика возможности менять порядок слов. Если строка содержит более одного динамического элемента, msgfmt поддерживает расширенный синтаксис разметки, позволяющий указывать положение каждого динамического элемента в строке. При переводе порядок элементов легко изменить. Более подробно о позиционном синтаксисе можно узнать из любого онлайнового руководства по gettext (см. Ресурсы).
  • Убедитесь, что по контексту строки можно однозначно понять её значение. Имейте в виду, что переводчики видят только строки, а не работающее приложение.
Примечание: для получения более подробной информации по лингвистическим аспектам локализации ПО см. Ресурсы.

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

Проектирование локализации

Описанный выше подход к локализации вполне пригоден во многих случаях. Но с увеличением масштаба, имеет смысл подумать о проектировании приложений с помощью конструктивных шаблонов. Один из наиболее широко используемых шаблонов - Модель-Представление-Контроллер (MVC). Этот шаблон предлагает рассматривать приложение как совокупность различных уровней - презентации, управляющей логики и доступа к данным. Ещё одно преимущество MVC состоит в том, что с его помощью легче расширять контроллер и представление для обработки других типов HTTP-запросов либо как SOAP-сервисов, либо как XML API.

Шаблон RSS MVC

Struts, Ruby on Rails и Zend Framework основаны на такой многоуровневой философии проектирования. Если вы находите подход MVC привлекательным, изучите Zend Framework for PHP. Однако, чтобы уменьшить количество дополнительных пакетов, необходимых для примера приложения, я использую свой собственный MVC-шаблон, показанный в Листинге 7.

Размер основного кода стал теперь ещё меньше. Он работает как контроллер, занимаясь по большей части обслуживанием HTTP-запросов. Он конструирует объект домена, задействующий бизнес-правила, которые мы уже обсуждали, извлекает модель данных сессии из домена, а затем передаёт модель классу представления для её трансформации в HTML, который отправляется обратно клиенту.

Листинг 7. Контроллер RSS news, news3.php
 getModel(); $view = new View($domain->getTemplate(), $model); echo $view->asHTML(); ?> 

Та же используемая ранее схема конфигурации приложения по-прежнему управляет поведением бизнес-правил. Изменился способ обработки пользовательского интерфейса нового уровня презентации. Этот уровень больше не использует поддержку HTML-шаблонов PHP. Вместо этого он полагается на XSL-трансформации. Модель домена - это рекурсивная структура данных вложенных объектов, которые можно реализовать как XML. Представление заставляет шаблон XSL трансформировать XML-представление данных сессии в локализованный HTML, возвращаемый браузеру клиента.

Уровень презентации

XML-модель и XSL-шаблон, формирующие ядро уровня презентации показаны в Листинге 8. Модель содержит информацию о конфигурации, например типы новостей, в особенности то, как они могут отображаться для пользователя (//rss/newstypes/displaytypes). Модель также возвращает выбор пользователем типа новостей (//rss/userinput/newstype), а также доставленные заголовки новостей и URL (//rss/headlinelist/headline). Если пользовательский выбор типа новостей был недопустимым, вместо заголовков модель возвратит код ошибки. Шаблон XSL получает доступ к фрагментам возвращённой модели данных для конструирования HTML, который отображается в браузере клиента.

Листинг 8. Образец модели XML и шаблона XSL (RSSView.xsl)
   Ausland, Technik  Technik Ausland    Ausland    http://de.news.yahoo.com/01112006/12/guatemala.html Guatemala und Venezuela halten Besprechungen   http://de.news.yahoo.com/01112006/12/mandat.html Mandat einer Regierung in Afrika     ---------------------------------------------------------------    Yahoo Schlagzeilen Hol Schlagzeilen             <xsl:value-of select="$pagetitle"/>    

Willkommen! Um den Yahoo Nachrichtleser zu gebrauchen, gib einen von den folgenden Nachrichttypen ein: !

Falscher Typ






При таком подходе локализованные строки не извлекаются инструментом типа gettext. Вместо этого, каждая локаль имеет собственный XSL-шаблон. Поскольку теперь имеется понятный языковой контекст, распределять рабочую нагрузку при поддержке приложения должно быть легче. Web-разработчики, группа по локализации и даже менеджеры по продуктам могут взять на себя ответственность за реализацию самых незначительных связанных с пользовательским интерфейсом исправлений ошибок и запросов на создание функции.

Для конструирования HTML класс представления использует поддержку XSL, идущую с PHP V5, модель данных и шаблон XSL. PHP V4 делает это по-другому. Данный подход также демонстрируется для тех ситуаций, когда необходимо локализовать старое приложение. В большинстве случаев XSLT включено по умолчанию, однако может потребоваться включить обработку XSLT в вашей установке PHP, чтобы всё это заработало. В Листинге 9 показана XSL-трансформация.

Листинг 9. XSL-трансформация (View.php)
 public function asHTML() { $xml = new DomDocument; $modelxml = $this->model->asXML(); #system('echo " ' . $modelxml . ' " > /tmp/l.txt'); $xml->loadXML($modelxml); $xsl = new DomDocument; $xsl->load($this->template); $proc = new xsltprocessor; $proc->importStyleSheet($xsl); $html = $proc->transformToXML($xml); 		 #      Following is XSL transformation syntax for PHP4 #		$xh = xslt_create(); #		$arguments = array ('/_xml' => $this->model->asXML(),  '/_xsl' => $this->template); #		$html = xslt_process($xh, 'arg:/_xml', $this->template, #                                          NULL, $arguments, $this->parameters); #		xslt_free($xh);  return $html; } 

Если в проекте задействованы более крупные команды, всё равно имеет смысл извлекать строки и генерировать XSL-шаблоны из главного шаблона. Это обычно выполняется при помощи системы управления глобализацией (globalization management system - GMS). Затем Web-разработчики имеют дело только с XSL-шаблонами по умолчанию на предпочитаемом на предприятии языке, а переводчики используют функции системы управления, и, возможно, системы машинного перевода типа SYSTRAN.

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

  • GMS извлекает весь текст из основных XSL-шаблонов и обновляет базу данных переводов.
  • Группа L10N использует базу данных переводов для перевода новых строк и повторного перевода изменившихся строк.
  • GMS реконструирует локализованные XSL-шаблоны для каждой локали из базы данных переводов.
Хотя использование GMS и несёт очевидные выгоды, вероятно, не следует применять её при работе в небольшой группе или со зрелым приложением, которое не так уж сильно изменяется. При работе со всеми упомянутыми инструментами следует взвесить как выгоды, так и затраты на получение, реализацию и поддержку того из них, который вы решили использовать.

Заключение

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

Тем не менее, представленные приёмы позволят вам выполнять 80 процентов того, что необходимо сделать для локализации приложения. Если вы работаете с уже существующим приложением и имеются временные и бюджетные ограничения, сконцентрируйтесь на менее амбициозном подходе - модернизации. Если же вы разрабатываете новое приложение или имеете достаточные ресурсы для локализации существующего, не бойтесь быть более агрессивными, если это окупится. В результате вы получите не только безупречно локализованный продукт, но и значительно облегчите работу людям, которые будут осуществлять поддержку приложения, что бы им не пришлось делать - исправлять ошибку или добавлять новую функцию.



 

 Web-сервисы Java, часть 3: Связывание данных в Axis2 (исходники).
 Не повторяйте DAO!.
 Организация контента с помощью категорий Atom (исходники).
 XML и Java: Возвращение к основам (исходники).
 Введение в WML (исходники, документация).


Главная »  Xml 

© 2022 Team.Furia.Ru.
Частичное копирование материалов разрешено.