Zend Framework и перевод
Метки: zend framework, zend_translate, zend_locale, мультиязычность
Zend Framework і переклад
Zend Framework and Translation
| ← Продвинутая отладка в JavaScript | 10 редких HTML тегов, о которых вам стоит знать → |
Мультиязычные сайты становятся все более популярными, я хотел бы показать два возможных способа перевода блоков статического текста в вашем приложении на Zend Framework. Zend Framework уже предоставляет нам несколько пакетов для упрощения жизни, например, Zend_Locale и Zend_Translate, но как свести эти компоненты вместе?
Создание приложения Zend Framework
В первую очередь нам нужно приложение Zend Framework. Вы также можете использовать свое существующее приложение, или создать новое прямо в Zend Studio for Eclipse:

Если вы не знаете как установить Zend Framework, обратитесь к соответствующему руководству Zend Framework Quickstart.
Zend_Locale и Zend_Translate
Как говорилось ранее, нам нужны сущности Zend_Locale и Zend_Translate. Я инициализирую оба объекта в классе Initializer, который был создан мастером проекта Zend Framework:
Убрать подсветку кода
Метод Initializer::initLocale() вызывается методом Initializer::_routeStartup(). Конечно, вы можете реализовать эту функциональность в файле bootstrap. Я выбрал очень простой способ инициализации объекта Zend_Locale для этого примера: я устанавливаю переменную $localeValue напрямую в методе. Конечно же, это не рекомендуется делать! Возможно вы можете взять размещение пользователя из сессии или Zend_Locale, на ваше усмотрение. Для этого мы воспользуемся Zend_Translate и Array Adapter. Это значит, что нам следует создать php-файл, который будет возвращать массив перевода.
Если у вас есть объект Zend_Locale, вы можете завершить приложение добавлением ключа "Zend_Locale" в Zend_Registry. Возможно, там уже будут некоторые компоненты ZF. На следующем шаге, мы создадим необходимые компоненты для перевода.

Это наиболее быстрый способ создания механизма перевода.
Файл en.inc.php выглядит следующим образом:
Убрать подсветку кода
Ключ массива является ключом перевода, который используется в нашем шаблоне Вида, а его значение является самим переводом.
Вернемся к нашему методу Initializer::initLocale(), мы создали объект Zend_Translate с именем адаптера, файла переводов и размещением, положив данный аналог объекта в объект Zend_Locale, который находится в Zend_Registry. Например, Zend_Form теперь будет использовать информацию для перевода меток Zend_Form_Element автоматически.
Это почти все, что мы должны подготовить перед тем как мы сможем использовать переводы в нашем view script.
Zend_View_Helper_Translate
Так как мы положили объект Zend_Translate в registry, теперь можно использовать view script в Translate View Helper. Давайте посмотрим на пример phtml:
Убрать подсветку кода
Как видите, каждый блок статического текста используется как параметр метода translate, объекта Zend_View. Сущность Zend_View_Helper_Translate будет создана объектом view автоматически. Она будет использовать объект Zend_Translate из registry, переводить параметр строку (строка должна быть ключом массива перевода) и возвращать переведенную строку, которая будет выведена во view script. Очень просто, неправда ли?
Способ <i18n>
Возможно, вам это знакомо: "Слишком много php-кода в шаблоне вида". Тогда у нас будет такое же мнение. Что вы думаете об этом шаблоне:
Убрать подсветку кода
Я включил каждый блок статического текста в тег <i18n>. На мой взгляд это легче читать и у этого способа есть больше общего с тегами HTML (возможно кто-то подскажет мне как добавлять собственные html-теги в валидатор HTML в Eclipse — без реализации нового валидатора, тогда мы сможем избавится от сообщений о некорректных тегах). Я знаю, что данные теги не валидны, но мы отфильтруем их позже — с помощью объекта Zend_Filter. У объекта Zend_View есть возможность установки Zend_Filter для фильтрации выводимого контента. Значит, нам стоит реализовать новый класс, например, Zx_View_Filter_Translate. Следуя соглашению Zend Framework про именования, вы должны создать файл library/Zx/View/Filter/Translate.php и реализовать там класс (Zx это мой личный ZendFramework-Demo-Extension-Prefix, выберите себе любой).
Давайте посмотрим на код (если вы также используете Zend_Loader, необязательно включать какие-либо файлы):
Убрать подсветку кода
Мы должны реализовать только один метод: Zx_View_Filter_Translate::filter(). Параметр $value содержит полный текст из Вида. В цикле мы проверяем каждое расположение текста, начинающееся с <i18n> и заканчивающееся на </i18n>. Затем мы извлекаем текст между этими тегами и переводим его с помощью объекта Zend_Translate (взятого из registry). В конце мы записываем переведенную строку в то же самое место, но уже без тегов <i18n>.
По причинам производительности, я не стал использовать регулярные выражения. Я согласен, с регулярными выражениями код был бы чище, но строковые операции выполняются быстрее. Это мое первое решение задачи, принимаются советы по его улучшению.
Теперь у нас есть фильтр, который не используется. Значит, нам нужно сказать Виду где его искать. Мой вариант: использовать метод Initializer::initView(). Чтобы получить Вид в этом месте приложения, нужна небольшая уловка:
Убрать подсветку кода
Создаем новый объект Zend_View, добавляем новый путь к фильтру (путь, который мы использовали к нашему фильтру Translate) и новый префикс фильтра ("Zx_View_Filter", в другом случае, по-умолчанию, "Zend_View_Filter", который использовался для создания имени класса). Дополнительно мы должны установить имя фильтра, который мы хотели бы использовать. В конце мы должны установить объект Вида в статическом action helper "ViewRenderer". Теперь настало время проверить как это работает.
Производительность
Так как я установил это демо-приложение на Zend Server 4.0 beta (VMware, Ubuntu JeOS 8.04), мне было не трудно провести тест на производительность с Zend Studio for Eclipse, а также интегрированным Profiler.
Несколько слов о моей "среде тестирования": я реализовал класс TranslateController с двумя действиями helperAction и filterAction. Оба действия не делают ничего, кроме рендера Вида с ViewHelper или Фильтром. Я также реализовал метод postDispatch(), который повторяется 150 раз и рендерит скрипт вызываемого действия. Я думаю, что будет проще понять, если вы посмотрите на код:
Убрать подсветку кода
Вызов обоих запросов (/translate/helper и /translate/filter) в Profiler дает нам следующие результаты:

/translate/helper

/translate/filter
Как вы видите, метод postDispatch() требует почти на 20% меньше общего времени для вызова фильтра, чем вызов хелпера. Я пробовал несколько раз и получал один и тот же результат. Конечно, это не научные измерения, но я думаю, что это достойно внимания.
Оригинал: Zend Framework and Translation
Создание приложения Zend Framework
В первую очередь нам нужно приложение Zend Framework. Вы также можете использовать свое существующее приложение, или создать новое прямо в Zend Studio for Eclipse:

Если вы не знаете как установить Zend Framework, обратитесь к соответствующему руководству Zend Framework Quickstart.
Zend_Locale и Zend_Translate
Как говорилось ранее, нам нужны сущности Zend_Locale и Zend_Translate. Я инициализирую оба объекта в классе Initializer, который был создан мастером проекта Zend Framework:
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /** * Инициализация Locale и Translation * * @return void */ public function initLocale() { $localeValue = 'en'; $locale = new Zend_Locale($localeValue); Zend_Registry::set('Zend_Locale', $locale); $translationFile = $this->_root . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $localeValue . '.inc.php'; $translate = new Zend_Translate('array', $translationFile, $localeValue); Zend_Registry::set('Zend_Translate', $translate); } |
Метод Initializer::initLocale() вызывается методом Initializer::_routeStartup(). Конечно, вы можете реализовать эту функциональность в файле bootstrap. Я выбрал очень простой способ инициализации объекта Zend_Locale для этого примера: я устанавливаю переменную $localeValue напрямую в методе. Конечно же, это не рекомендуется делать! Возможно вы можете взять размещение пользователя из сессии или Zend_Locale, на ваше усмотрение. Для этого мы воспользуемся Zend_Translate и Array Adapter. Это значит, что нам следует создать php-файл, который будет возвращать массив перевода.
Если у вас есть объект Zend_Locale, вы можете завершить приложение добавлением ключа "Zend_Locale" в Zend_Registry. Возможно, там уже будут некоторые компоненты ZF. На следующем шаге, мы создадим необходимые компоненты для перевода.

Это наиболее быстрый способ создания механизма перевода.
Файл en.inc.php выглядит следующим образом:
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 | return array( 'Berlin' => 'Berlin', 'Hamburg' => 'Hamburg', 'München' => 'Munich', 'Köln' => 'Cologne', 'Stuttgart' => 'Stuttgart', 'Hauptstadt' => 'capital', 'Hafen' => 'harbor', // ... |
Ключ массива является ключом перевода, который используется в нашем шаблоне Вида, а его значение является самим переводом.
Вернемся к нашему методу Initializer::initLocale(), мы создали объект Zend_Translate с именем адаптера, файла переводов и размещением, положив данный аналог объекта в объект Zend_Locale, который находится в Zend_Registry. Например, Zend_Form теперь будет использовать информацию для перевода меток Zend_Form_Element автоматически.
Это почти все, что мы должны подготовить перед тем как мы сможем использовать переводы в нашем view script.
Zend_View_Helper_Translate
Так как мы положили объект Zend_Translate в registry, теперь можно использовать view script в Translate View Helper. Давайте посмотрим на пример phtml:
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $this->headTitle('Translate with Filter'); $this->placeholder('title')->set($this->translate('Das höchste Gut und Uebel')); <h2>Cicero</h2> <small> $this->translate('Absatz') 1.10.32 - 1.10.33</small> <p> $this->translate('Damit Ihr indess erkennt, woher dieser ganze Irrthum gekommen ist, und weshalb man die Lust anklagt und den Schmerz lobet, so will ich Euch Alles eröffnen und auseinander setzen, was jener Begründer der Wahrheit und gleichsam Baumeister des glücklichen Lebens selbst darüber gesagt hat.') <!-- … --> |
Как видите, каждый блок статического текста используется как параметр метода translate, объекта Zend_View. Сущность Zend_View_Helper_Translate будет создана объектом view автоматически. Она будет использовать объект Zend_Translate из registry, переводить параметр строку (строка должна быть ключом массива перевода) и возвращать переведенную строку, которая будет выведена во view script. Очень просто, неправда ли?
Способ <i18n>
Возможно, вам это знакомо: "Слишком много php-кода в шаблоне вида". Тогда у нас будет такое же мнение. Что вы думаете об этом шаблоне:
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 11 12 13 | $this->headTitle('Translate with Filter'); $this->placeholder('title')->set('<i18n>Das höchste Gut und Uebel</i18n>'); <h2>Cicero</h2> <small><i18n>Absatz</i18n> 1.10.32 - 1.10.33</small> <p> <i18n>Damit Ihr indess erkennt, woher dieser ganze Irrthum gekommen ist, und weshalb man die Lust anklagt und den Schmerz lobet, so will ich Euch Alles eröffnen und auseinander setzen, was jener Begründer der Wahrheit und gleichsam Baumeister des glücklichen Lebens selbst darüber gesagt hat.</i18n> |
Я включил каждый блок статического текста в тег <i18n>. На мой взгляд это легче читать и у этого способа есть больше общего с тегами HTML (возможно кто-то подскажет мне как добавлять собственные html-теги в валидатор HTML в Eclipse — без реализации нового валидатора, тогда мы сможем избавится от сообщений о некорректных тегах). Я знаю, что данные теги не валидны, но мы отфильтруем их позже — с помощью объекта Zend_Filter. У объекта Zend_View есть возможность установки Zend_Filter для фильтрации выводимого контента. Значит, нам стоит реализовать новый класс, например, Zx_View_Filter_Translate. Следуя соглашению Zend Framework про именования, вы должны создать файл library/Zx/View/Filter/Translate.php и реализовать там класс (Zx это мой личный ZendFramework-Demo-Extension-Prefix, выберите себе любой).
Давайте посмотрим на код (если вы также используете Zend_Loader, необязательно включать какие-либо файлы):
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | class Zx_View_Filter_Translate implements Zend_Filter_Interface { /** * Начальный разделить блока перевода в Виде * */ const I18N_DELIMITER_START = '<i18n>'; /** * Заключающий разделить блока в Виде * */ const I18N_DELIMITER_END = '</i18n>'; /** * Фильтрация значения для текста внутри тегов i18n и перевод * * @param string $value * @return string */ public function filter($value) { $startDelimiterLength = strlen(self::I18N_DELIMITER_START); $endDelimiterLength = strlen(self::I18N_DELIMITER_END); $translator = Zend_Registry::get('Zend_Translate'); $offset = 0; while (($posStart = strpos($value, self::I18N_DELIMITER_START, $offset)) !== false) { $offset = $posStart + $startDelimiterLength; if (($posEnd = strpos($value, self::I18N_DELIMITER_END, $offset)) === false) { throw new Zx_Exception("No ending tag after position [$offset] found!"); } $translate = substr($value, $offset, $posEnd - $offset); $translate = $translator->_($translate); $offset = $posEnd + $endDelimiterLength; $value = substr_replace($value, $translate, $posStart, $offset - $posStart); $offset = $offset - $startDelimiterLength - $endDelimiterLength; } return $value; } } |
Мы должны реализовать только один метод: Zx_View_Filter_Translate::filter(). Параметр $value содержит полный текст из Вида. В цикле мы проверяем каждое расположение текста, начинающееся с <i18n> и заканчивающееся на </i18n>. Затем мы извлекаем текст между этими тегами и переводим его с помощью объекта Zend_Translate (взятого из registry). В конце мы записываем переведенную строку в то же самое место, но уже без тегов <i18n>.
По причинам производительности, я не стал использовать регулярные выражения. Я согласен, с регулярными выражениями код был бы чище, но строковые операции выполняются быстрее. Это мое первое решение задачи, принимаются советы по его улучшению.
Теперь у нас есть фильтр, который не используется. Значит, нам нужно сказать Виду где его искать. Мой вариант: использовать метод Initializer::initView(). Чтобы получить Вид в этом месте приложения, нужна небольшая уловка:
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /** * Инициализация view * * @return void */ public function initView() { // … $view = new Zend_View(); $view->addFilterPath('Zx/View/Filter', 'Zx_View_Filter'); $view->setFilter('Translate'); $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('ViewRenderer'); $viewRenderer->setView($view); } |
Создаем новый объект Zend_View, добавляем новый путь к фильтру (путь, который мы использовали к нашему фильтру Translate) и новый префикс фильтра ("Zx_View_Filter", в другом случае, по-умолчанию, "Zend_View_Filter", который использовался для создания имени класса). Дополнительно мы должны установить имя фильтра, который мы хотели бы использовать. В конце мы должны установить объект Вида в статическом action helper "ViewRenderer". Теперь настало время проверить как это работает.
Производительность
Так как я установил это демо-приложение на Zend Server 4.0 beta (VMware, Ubuntu JeOS 8.04), мне было не трудно провести тест на производительность с Zend Studio for Eclipse, а также интегрированным Profiler.
Несколько слов о моей "среде тестирования": я реализовал класс TranslateController с двумя действиями helperAction и filterAction. Оба действия не делают ничего, кроме рендера Вида с ViewHelper или Фильтром. Я также реализовал метод postDispatch(), который повторяется 150 раз и рендерит скрипт вызываемого действия. Я думаю, что будет проще понять, если вы посмотрите на код:
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | /** * TranslateController * * @author $LastChangedBy: $ * @version $Id: TranslateController.php 148 2009-03-27 09:48:37Z $ */ class TranslateController extends Zend_Controller_Action { const ITERATIONS = 150; private static $cnt = 0; private $_script; private $_currentActionName; /** * построение имени view script, которое рендерит postDispatch() * */ public function init() { $this->_currentActionName = $this->getRequest() ->getActionName(); $this->_script = $this->getRequest()->getControllerName() . DIRECTORY_SEPARATOR . $this->_currentActionName . '.phtml'; } /** * Построение цепочки действий с повторением self::ITERATIONS. * В каждой итерации будет рендериться соответствующий скрипт * */ public function postDispatch() { if (++self::$cnt >= self::ITERATIONS) return; $this->view->render($this->_script); $this->_forward($this->_currentActionName); } /** * использование Translate ViewHelper Видом, для перевода * */ public function helperAction() { } /** * использование фильтра Видом, для перевода * */ public function filterAction() { } } |
Вызов обоих запросов (/translate/helper и /translate/filter) в Profiler дает нам следующие результаты:

/translate/helper

/translate/filter
Как вы видите, метод postDispatch() требует почти на 20% меньше общего времени для вызова фильтра, чем вызов хелпера. Я пробовал несколько раз и получал один и тот же результат. Конечно, это не научные измерения, но я думаю, что это достойно внимания.
Оригинал: Zend Framework and Translation
Рейтинг:




<< Вы можете поставить оценку этой статьеПодобные статьи:
Интеграция FCKeditor в Zend_Form
Автоматизированное тестирование с использованием Zend Framework
Паттерн кэширования для моделей
Два (или более) проекта Zend Framework на общем хостинге
Memcached в PHP - просто с Zend Framework