Zend Framework и многоязычность

    Возникла у меня надобность создать многоязычный сайт используя ZF. Создал. Под катом расскажу как.

    Значит, есть у нас условный сайт mysite.com, который переведен тремя зыками: украинским (uk), русским (ru) и английским (en). И тут приходит на сайт пользователь. Что мы обычно делаем? Или сразу показываем ему сайт на дефолтном языке, или сначала пытаемся определить, какой язык для него будет лучшим. Вот о втором варианте я хотел бы рассказать более подробно.

    Современные браузеры (не берусь говорить о старых) передают серверу параметр HTTP_ACCEPT_LANGUAGE. У меня он равен “uk,ru;q=0.8,en-us;q=0.5,en;q=0.3″. Т.е. лучше всего я воспринимаю сайты на украинском или русском языке, а потом уже на английском, при этом отдаю предпочтение «американскому».

    В ZF для того, чтобы определить какой же язык будет наиболее уместен можно использовать Zend_Locale. Например:

    $locale = new Zend_Locale('auto');
    $lang = $locale->getLanguage();


    Но что если наш сайт не поддерживает этот язык? Нужно сделать проверку. Для этого в конфигурационном файле (я использую Zend_Config_Ini) сохраним набор локалей которые поддерживаются сайтом (с них потом можно будет вытянуть язык).

    ; allowed locales (first locale - default)
    locales.uk = uk_UA
    locales.ru = ru_RU
    locales.en = en_GB


    И пишем проверку (в bootstrap файле).

    $locales = $config->locales->toArray();
    $locale = new Zend_Locale('auto');

    $lang = array_key_exists($locale->getLanguage(), $locales)
    ? $locale->getLanguage() : $config->locales->key();


    Ок. Это работает. А как теперь сделать, чтобы mysite.com/uk/user/auth отображался на украинском, mysite.com/ru/user/auth — на русском, а mysite.com/user/auth — на том языке, который более всего подходит пользователю (или на дефолтном)? Для этого нужно переопределить основной роутер (для того чтобы он передавал параметр lang) и создать ещё один, который будет вытягивать язык из url (если он там есть):

    // change default router
    $frontController->getRouter()->addRoute('default',
    new Zend_Controller_Router_Route(
    ':module/:controller/:action/*',
    array(
    'module' => 'default',
    'controller' => 'index',
    'action' => 'index',
    'lang' => $lang
    )
    )
    );

    // add multilingual route
    $frontController->getRouter()->addRoute('default_multilingual',
    new Zend_Controller_Router_Route(
    ':lang/:module/:controller/:action/*',
    array(
    'module' => 'default',
    'controller' => 'index',
    'action' => 'index',
    'lang' => $lang
    ),
    array(
    'lang' => '\w{2}'
    )
    )
    );


    Но и это ещё не все. Пользователь может попытаться открыть страницу mysite.com/de/user/auth, а наш сайт нихт шпрехен зе дойч… Вот засада. Что же делать? В общем для того, чтобы уже окончательно определиться на каком языке показывать сайт, создаём специальный helper (подсмотрено в книге «Zend Framework in Action»).

    <?php

    class Site_Controller_Action_Helper_Language extends Zend_Controller_Action_Helper_Abstract {

    protected $_sDefaultLanguage;
    protected $_aLocales;
    protected $_sLanguagesDirectoryPath;

    /**
    * @param array $aLocales - Available locales
    * @param string $sLanguagesDirectoryPath
    */
    public function __construct(array $aLocales, $sLanguagesDirectoryPath) {
    $this->_sLanguagesDirectoryPath = $sLanguagesDirectoryPath;
    $this->_aLocales = $aLocales;
    $this->_sDefaultLanguage = key($aLocales); // get first language
    }

    public function init() {
    // try get current language from url
    $sLang = $this->getRequest()->getParam('lang');

    if(! array_key_exists($sLang, $this->_aLocales)) {
    $sLang = $this->_sDefaultLanguage;
    }

    // generate path to the gettext language file
    $sLanguageFilePath = $this->_sLanguagesDirectoryPath . '/'. $sLang . '/LC_MESSAGES/' . $sLang . '.mo';
    if(! file_exists($sLanguageFilePath)) {
    $sLanguageFilePath = $this->_sLanguagesDirectoryPath . '/' . $this->_sDefaultLanguage . '/LC_MESSAGES/' . $this->_sDefaultLanguage . '.mo';
    $sLang = $this->_sDefaultLanguage;
    }

    // get current locale by current language
    $sLocale = $this->_aLocales[$sLang];

    // setup translate object
    $oTranslate = new Zend_Translate('gettext', $sLanguageFilePath, $sLang);
    Zend_Form::setDefaultTranslator($oTranslate); // translated Zend_Form

    $this->_actionController->_locale = $sLocale;
    $this->_actionController->_lang = $sLang;
    $this->_actionController->_translate = $oTranslate;

    $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
    $viewRenderer->view->locale = $sLocale;
    $viewRenderer->view->lang = $sLang;
    $viewRenderer->view->translate = $oTranslate;
    }
    }


    Этот helper делает последнюю проверку и пытается подгрузить нужный языковый файл. По адресу mysite.com/de/user/auth будет показываться украинская версия сайта. Ну и для того чтобы этот helper заработал, достаточно добавить две строки в наш bootstrap файл.

    $languageHelper = new Site_Controller_Action_Helper_Language($locales, APPLICATION_PATH . '/languages');
    Zend_Controller_Action_HelperBroker::addHelper($languageHelper);


    В принципе все. Благодарю за внимание. Следующий топик будет посвящен Zend_Translate.
    Share post

    Comments 14

      0
      Возьму на вооружение. Спасибо.
        0
        имхо лажа отдавать по /de дефолтовый язык — что будет, когда яндекс погуляет? — куча ссылок с якобы немецким языком. имхо отдать заглушку со списком имеющихся языков и фсё.
          0
          Яндекс не сможет туда зайти, ибо нигде нет такой ссылки (автор указал, что предполагает, что пользователь сам ввел de). А даже если и есть, то откуда Яндекс знает, что mysite.com/de/user/auth означает немецкий язык?
          0
          можно и так.

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

          если же такого языка не было, тогда с какой радости яндекс полезет туда гулять?
            0
            Как то уж сильно массивно для такой простейшей задачи.

            И, кстати, тогда уж либо нужно подставлять язык в КАЖДЫЙ url (если не указан, то делать перенаправление на язык по умолчанию), либо сделать модуль, который ставить как язык по умолчанию, так и по обращении к нему меняет на указанный, возвращая пользователя обратно на ту же страницу, где он и был.
              0
              // Как то уж сильно массивно для такой простейшей задачи.
              предложите решение попроще :)
              к сожалению здесь двумя строчками кода не обойтись

              // И, кстати, тогда уж либо нужно подставлять язык в КАЖДЫЙ url (если не указан, то делать перенаправление на язык по умолчанию)

              это можно сделать при помощи стандартного url хелпера

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

              не уверен что до конца понял суть. можете написать более развёрнуто?
                0
                Ну я не использую пока ZF, в моей системе есть просто 2 точки — метод, который устанавливает язык для приложения при запросе, он же ставит язык по умолчанию, если от пользователя не пришёл какой язык ему нужен из Сookies, а второй метод меняет язык про запросу пользователя на определённый урл по типу htpp://site.com/change_lang/ru.html
                  0
                  тут все то же самое, только нет жёсткой привязки к дефолтному языку. ну и сразу инстанциируется Zend_Translate. без этого код будет раза в три короче :)
              0
              Мне кажется лутше для того кто хочет увидеть сайт на немецком всетаки показывать английский а не украинский вариант.
              А что если хранить язык в кукисах? Как раз нужно реализовать чтото подобное, только на cakephp.
                0
                думаю Вам правильно кажется — украинский язык я взял просто для примера :).

                т. е. если человек выбрал язык сохранять его в куки? можно и так. неплохая мысль!
                  0
                  Спасибо за статью.

                  Когда пользователь выбрал язык, можно хранить его в сессиях, или, как предлагали, в кукиз, если юзер зарегистрированый то в его настроках хранить… впрочем это очевидно)
                  Постоянные ссылки на страницы формировать с :lang в УРЛ тогда можно будет давать ссылки на странички на любом языке.
                  Вкратце свой подход описал)
                  Для меня сейчас актуально определение Страны и Города посетителя может вы решали подобную задачу с использованием ZF?
                    0
                    Когда я решал такую задачу ZF ещё в помине не было :). Страну и город можно определить использую базу GeoIP от www.maxmind.com
                0
                Интересно — сразу же попробовал применить и сразу же не сработало :/
                У вас переопределен дефолтный роутер и все запросы все равно отправляются на него: т.е. я всегда буду видеть титульную страницу по дефолтному индекс-контроллеру и дефолтному индекс-экшену. Есть ли какой-либо красивый способ сделать это правильно?
                  0
                  синтаксическая ошибка — пардон все кошерно ))))

                Only users with full accounts can post comments. Log in, please.