PHP framework CodeIgniter. Создание многоязычных сайтов

5 февраля, 2008

Локализация сайта
В этой статье речь пойдет о возможностях, которые предоставляет CodeIgniter для поддержки многоязычных сайтов.

Разберемся, как реализована поддержка языков.

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

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

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

Примечание. В этой статье речь идет о локализации фрэймворка и сайта, а не контента. Т.к. контент (текстовый) обычно находится в базе данных, то для размещения переводов вам, скорее всего, придется создать дополнительные таблицы и изменять запросы в зависимости от выбранного языка.

Создание файлов переводов

Т.к. CodeIgniter это PHP фрэймворк, то и файлы с переводами представляют собой обычные PHP скрипты, в которых объявлены массивы строк. Имена этих файлов должны обязательно содержать окончание «_lang.php».

Формат строк должен быть такой:

  1. $lang['ключ'] = "Текст, который будет показан на сайте";

с помощью «ключа» осуществляется выбор нужной строки.

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

Размещение файлов

CodeIgniter имеет две папки, предназначенных для размещения файлов переводов.

1) \system\language\ – общая папка;
2) \system\application\language\ – это папка с переводами конкретного сайта.

Разница, думаю, очевидна. Если у вас несколько сайтов используют одни и те же системные файлы, и файлы переводов должны быть доступны для всех сайтов, то нужно использовать первую папку. А если каждый сайт использует свои собственные файлы переводов, то размещать их лучше во второй папке.

При загрузке переводов поиск осуществляется в следующем порядке. Сначала – в папке приложения (\system\application\language\), а затем – в общей папке (\system\language\).

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

Например, мы создали два файла переводов для русского и английского языка, в которых разместили текст главной страницы сайта.

Что-то вроде:

  1. <?php
  2. $lang['title'] = "Hello";
  3. ?>

и

  1. <?php
  2. $lang['title'] = "Привет";
  3. ?>

Оба файла будут иметь одно и тоже имя main_lang.php, но размещены будут в разных папках:
\language\russian\main_lang.php – русский вариант;
\language\english\main_lang.php – английский вариант.

Загрузка файлов переводов

Теперь можно загружать файлы. Для этого используется метод load() объекта lang.

  1. $this->lang->load('main', 'english');

В первом параметре мы указываем имя файла (без окончания «_lang.php»), а во втором – названия языка (это название должно совпадать с именем папки, в которой находится файл перевода).

Кроме того, можно настроить CodeIgniter так, чтобы он автоматически загружал файлы выбранного языка. Для этого в файле \application\config\autoload.php находим переменную $autoload['language'] и добавляем нужный язык.

  1. $autoload['language'] = array('russian');

Для получения нужной строки используется метод line

  1. $pageData['t'] = $this->lang->line('title');

которому в первом параметре передается ключ строки.

Собираем все вместе

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

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

  1. class Welcome extends Controller {
  2.     function Welcome()
  3.     {
  4.         parent::Controller();
  5.         $userLang = $this->session->userdata('userlang');
  6.         $this->lang->load('main', $userLang);
  7.     }
  8.     function index()
  9.     {
  10.         $pageData['t'] = $this->lang->line('title');
  11.         $this->load->view('welcome_message', $pageData);
  12.     }
  13. }

Как видите, в конструкторе мы загрузили выбранный язык (строки 5, 6), а в методе index (формирует главную страницу сайта) – получили текстовую строку с ключом 'title', которую теперь можно использовать для формирования страницы.

Примечание. Это максимально упрощенный пример. В реальной ситуации вы должны будете, как минимум проверить установлен ли параметр 'userlang' и если нет – выбрать язык по-умолчанию.

Локализация стандартных библиотек

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

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

Кстати, убедитесь, что кодировка файлов с переводами совпадает с кодировкой сайта.

Удачи!

Понравилась статья? Подписывайтесь на продолжение rss link !

Или на мой твиттер twitter link

]]>

Добавьте эту страницу в google.com bobrdobr.ru del.icio.us technorati.com linkstore.ru news2.ru rumarkz.ru memori.ru moemesto.ru

]]>

Опубликовано в CodeIgniter, PHP Комментарии (44) »

]]>

Комментарии (44)

Вы можете отслеживать обсуждение записи с помощью RSS 2.0 rss link

Вы также можете оставить комментарий, или трекбек с Вашего сайта.

]]>
  1. Sam

    Прелестно… просто прелестно :)

    Спасибо за заметку. Читать мануал CI – радость, но читать ещё более понятные заметки на родном языке – ещё большая радость.

  2. мне не очень нравится этот способ потому как

    $this->lang->line(‘title’)

    гораздо менее удобней на мой взгляд, чем

    _TITLE_

    то есть просто константы, на мой взгляд гораздо лучше – принцип такой-же, а кода меньше.

    • В вашем варианте возможны две проблемы.

      Во-первых, большое количество констант может привести к тому, что они будут повторяться. В варианте CI все строки переводов находятся внутри одного массива.

      Во-вторых, вам все равно придется написать код для загрузке нужной строки _TITLE_.
      Ведь $this->lang->line(‘title’) возвращает текст из заданного файла с переводом.

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

      P.S. В принципе ничто не мешает написать альтернативный вариант библиотеки и использовать его. Было бы интересно потестировать и сравнить.

      • система с константами на самом деле такая же как и родная CI – писать почти нифига не нужно.

        стандартный вариант:

        $lang = $this->uri->segment(1);

        switch($lang):

        case 'lv':
        $this->lang->load('main', 'latvian');
        break;

        case 'en':
        $this->lang->load('main', 'english');
        break;

        case 'ru':
        $this->lang->load('main', 'russian');
        break;

        default:
        $this->lang->load('main', 'english');
        break;

        endswitch;

        }

        вариант с константами:

        $lang = $this->uri->segment(1);

        switch($lang):

        case 'lv':
        include_once('/lang/main_latvian.php');
        break;

        case 'en':
        include_once('/lang/main_english.php');
        break;

        case 'ru':
        include_once('/lang/main_russian.php');
        break;

        default:
        include_once('/lang/main_english.php');
        break;

        endswitch;

        }

        и соответственно в языковом файле вместо
        $lang['title'] = 'Hello';
        получается
        define('title','Hello');

        вот и получается ваше во-вторых, по объёму, такое-же как и в оригинале

        ах да

        segment(1) потому что я обычно пишу route


        $route['(en|lv|ru)'] = $route['default_controller'];
        $route['(en|lv|ru)/(.+)'] = "$2";

        а как вы поступаете с роутером ?

        • Да, получается довольно компактно. Вы не тестировали быстродействие? По-идее ваш вариант должен быть быстрее. Все-таки работа идет с константами, а не ассоциативным массивом.

          По поводу роутинга…
          Дело в том, что я старался обойти этот вопрос в статье, но, похоже, не получилось :-) .
          Проблема в том, что я для себя не определился какой метод лучше, а точнее не протестировал все идеи. У меня есть три варианта.

          Первый. Посетитель сам выбирает язык, а мы сохраняем его в сессии. Параллельно пробуем прочитать Accept-Language, т.е. «угадать» выбор пользователя. Тут явный минус в том, что посетителю придется выбирать язык каждый раз при входе на сайт (если мы его не «угадали»).

          Второй. Это ваш вариант. Т.е. используются адреса вроде
          http://www.site.com/ru/controller/function/...
          http://www.site.com/en/controller/function/...
          Тут решение вы показали в своем примере. У меня получалось практически тоже самое. Разве что использовал «:any» вместо «(.+)», но это не важно. Главное, что в этом варианте посетитель может сохранить ссылку на версию сайта с нужным языком и можно не использовать сессии.

          Третий. Использовать субдомены.
          ru.site.com/….
          en.site.com/….
          Вот это вариант я как раз и не доделал. Идея простая. Раз есть три домена, то есть и три папки DOCUMENT_ROOT, в каждую их которых помещаем свой index.php, причем все они будут ссылаться на одну папку application. Но тут возникает проблема. Что делать с $config['base_url'] (из файла config.php)? Можно, конечно, ее проигнорировать и установить глобальные переменные для каждого домена в index.php. Но тогда придется игнорировать и функции типа anchor :-(

          В общем, похоже ваш вариант самый подходящий.

  3. MAX

    Согласен с mihailt. Сам подход довольно тяжеловат с точки зрения синтаксиса. Поэтому, на мой взгляд оптимальным будет использование дополнительной функции, по аналогии с WordPress _e('ключ'), которая и будет возвращать переведенную фразу. :)

    • Да, хороший вариант. Я столкнулся с этой этой функцией когда переводил на русский один из плагинов к WP. Всего два символа, очень удобно.
      Напоминает функции типа $() в Prototype :-)

  4. Спасибо за полезную информацию.

  5. MAX

    Спасибо за Automatic_configbase_url – нужная вещь. ;)

    Уж коли пошла такая "пьянка" :) спрошу, может подскажете. В CodeIgniter происходит адресация на основе контролеров/методов. Когда контролера/метод, указанного в url нет, происходит переброс на страницу error_404.php. Можно ли сделать так, чтобы при всех несуществующих методах происходил вызов своего метода (внутри своего контролера), например page_404()?

    Сам на данным момент решил эту проблему путем модицикации CodeIgniter.php – где перед вызовом show_404() проверяется метод page_404.

    Есть более элегантное решение?

  6. MAX

    Супер! Так сразу и не догадался. Вот код, если вдруг кому понадобится.


    function _remap($method)
    {
    if ( method_exists($this, $method) ) $this->$method();
    else $this->page_404();
    }

    Спасибо! :)

  7. Отдельный риспект за ссылочку на файлы с переводами ;)

  8. Есть еще вариант использования _remap(). Для авторизации.

    Принцип такой:
    1) в контроллере создаем массив с перечнем методов которые можно вызывать без входа на сайт.
    $freeAccess = array('index','login','about');

    2) в методе _remap() что-то вроде

    function _remap($method) {
        if (in_array($method, $freeAccess)) {
            $this->$method();
        }
        else if (//проверка авторизации) {
                $this->$method();
        }
        else {
                $this->login();
        }
    }

    P.S. Вообще-то собирался написать пост на эту тему :-) .

  9. LANKO

    только начинаю изучать CI, в тему вопроса многоязычности:

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

    1. основной язык(lang0): http://www.host.com/
    2. дополнительный язык (lang1): http://www.host.com/lang1/
    3. дополнительный язык (lanh2): http://www.host.com/lang2/

    если ли возможность сделать переадресацию с помощю routes или другим методом так, что бы при вызове $this->uri->segment(1); получать значение `lang0|lang1|lang2`.

    • Да, можно. В принципе метод описал mihailt (в комментариях выше).
      Просто нужно установить язык по-умолчанию (lang0) и если $this->uri->segment(1) не содержит названия языка, то использовать lang0.

      • LANKO

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

        - основной язык: http://www.host.com/articles/ $this->uri->segment(1) будет `articles`

        - дополнительный язык: http://www.host.com/lang1/articles/ $this->uri->segment(1) будет `lang1`, а $this->uri->segment(2) будет `articles`

        а нужно что бы значение $this->uri->segment(1) было одинаковым во всех языках.

        • именно для этого и нужна проверка

          Создаем массив со всеми поддерживаемыми языками
          $langs = array("ru","en","ua");

          выполняем проверку есть ли название языка в адресе

          if (in_array($this->uri->segment(1), $langs)) {
          $curLang = $this->uri->segment(1);
          }
          else {
          $curLang = "en"; //значение по-умолчанию
          }

  10. a.koposov

    Шикарная статейка. Спасибо!
    Простите, а не могли бы Вы и мне выслать книжку?
    a.koposov(собака)reneissance.org

  11. Большое Вам спасибо!

  12. a.koposov

    Огромное спасибо! Не ожидал тако доброты =) Респект вам Люди.

  13. Luiza

    да уж, мир не без добрых людей!

  14. Alex

    Огромное спасибо за статью, а можно ссылочку на исходник готового решения? дело в том что я новичек. И не совсем понятно следующее:
    #
    {
    #
    parent::Controller();
    #
    $userLang = $this->session->userdata(‘userlang’);
    #
    $this->lang->load(‘main’, $userLang);
    #
    }

    Это надо писать в каждом контроллере и для каждой необходимой строки?? Заранее благодарен за ответ.

  15. Alex

    Прошу прощения, не то вставил…

    $pageData[‘t’] = $this->lang->line(‘title’);
    $this->load->view(‘welcome_message’, $pageData);

    • Пример отправил на email (если честно, он не сильно отличается от приведенного в статье).

      $pageData[‘t’] = $this->lang->line(‘title’);

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

      $this->load->view(‘welcome_message’, $pageData);

      Это обычная загрузка представления. В большинстве случаев вызывается один раз в каждом методе.

  16. SHAMAN

    Всем привет. Друг наконец-то меня убедил начать изучать веб.
    Скинул вот эту книженцию CodeIgniter for Rapid PHP Application Development и сказал что не прочитав её – ничё не выйдет. Большая просьба к людям посещающим сей ресурс – скиньте плз на мыло shaman_13@rambler.ru где можно взять эту книжку на русском или что-то подобное (главное на русском), ибо в английском слаб для чтения таких вещей

    • У меня есть две книги о CodeIgniter (в т.ч. и эта), но обе на английском.

      И я не соглашусь с вашим другом. Если вы только начали изучать веб разработку, то нужно начинать не с библиотек и фреймворков, а с самых базовых вещей.
      Т.е. вам подойдет практически любой учебник по PHP и MySQL. Что-нибудь вроде "Освой PHP за 24 часа…"

  17. Есть ли в CI зарезервированные имена контроллеров?
    include(U:\home\1020band\www/system/application/controllers/../modules/news/frontend/controllers/news.php) нивкакую не хочет инклудицца, хотя и лежит там, и все прописано верно. Меняю имя контроллера, все грузится нормально. Я вареный….

    • Нигде в документации не встречал зарезервированных имен. И, честно говоря, не понял зачем вам подключать его вручную. Контроллер будет автоматически подключен если в строке адреса вы укажите его имя (первый параметр).

      • я не подключаю контроллер вручную. Это ошибка в CodeIgniter.php
        (155 строчка).
        if ( ! file_exists(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->fetch_class().EXT))
        {
        //show_error('Unable to load your default controller. Please make sure the controller specified in your Routes.php file is valid.');
        }

        include(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->fetch_class().EXT);

        Материться только на имя news
        http://mysite/news

        • Думал посоветовать отправить bug report, но попробовал сам создать контроллер news и он нормально заработал.
          Проверял на последней версии.
          Какая версия у вас?

  18. Иван

    Здравствуйте , Уважаемые специалисты . Хотел у вас спросить насколько это хорошо , перекрывать нормальное поведение контроллера , создавая локальный роутинг с помощью ремапа (_remap($method)).
    Смотрел контроллер maxsite там используется этот подход .
    Для не которых же приложение под загрузку определённых отображений , выделяется отдельный метод к примеру …

    function category(){
    $this->load->view("category");
    }

    function razdel(){
    $this->load->view("razdel");
    }

    с помощью remap

    function _remap($method){
    if($method=="category"){
    $this->load->view("category");
    }
    elseif($method=="razdel"){
    $this->load->view("razdel");
    }

    }

    Скажите пожалуйста в каких случаях грамотней будет использовать такой подход ?

    • На мой взляд, _remap имеет смысл использовать если нужно выполнить какие-то действия до загрузки метода контроллера.

      В вашем примере в методе _remap сразу загружается представление. Я бы так не делал.

      function _remap($method){
          if($method=="category"){
              //какие-то действия, которые нужно
              //нужно отделить от метода category
              $this->category();
          }
      }

      Переносить всю логику в _remap не самое удачное решение.

      В любом случае, это стандартная возможность, и если её использование решает ваши задачи, то её имеет смысл использовать.

  19. Насчёт быстродействия:
    нет, не тестил как то не было необходимости.

    Насчёт роутинга

    Первый вариант я не рассматриваю, минусов слишком много как вы и сказали

    второй вариант – штатное использование роутинга, в принципе дающий всё что нужно.

    Третий вариант – даже не знаю зачем такое надо, но если вдруг надо, то насчёт $config[’base_url’]
    вот вам линк в помощь:
    http://codeigniter.com/wiki/Automatic_configbase_url/

    для удобства приведу код и здесь:
    $config['base_url'] = "http://".$_SERVER['HTTP_HOST'] . str_replace(basename($_SERVER['SCRIPT_NAME']),"",$_SERVER['SCRIPT_NAME']);

    и всё же я считаю что Codeigniter'ский роутер самое лучшее решение.

    P.S. не забывайте кстати о mod_rewrite и его возможностях :)

  20. За ссылку спасибо! Попробую.

    mod_rewrite… Я о нем не забываю, но постоянно не хватает времени научиться нормально его использовать. Т.е. переделать готовые правила под свои задачи не проблема, а вот написать правила с нуля – гораздо сложнее.
    Честно говоря, я сильно сомневаюсь, что кто-то полностью представляет все его возможности и варианты использования :-)

  21. Sam

    Я представляю, хоть и не все… Поэтому мне не нравится ни один роутер в существующих фреймворках :(

  22. а чем собственно не нравятся? чего не хватает?

]]>

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

* - обязательные для заполнения поля

]]>