Создание многоязычных сайтов с помощью CodeIgniter

5 февраля, 2008

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Code (php)
  1. <?php
  2. $lang[‘title’] = "Hello";
  3. ?>

и

Code (php)
  1. <?php
  2. $lang[‘title’] = "Привет";
  3. ?>

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

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

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

Code (php)
  1. $this->lang->load(‘main’, ‘english’);

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

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

Code (php)
  1. $autoload[‘language’] = array(‘russian’);

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

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

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

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

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

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

Code (php)
  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 !

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

Опубликовано в CodeIgniter, PHP

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

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

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

  1. Sam 05.02.2008 в 14:54 (Ответить)

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

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

    1. Владимир 05.02.2008 в 15:35 (Ответить)

      Спасибо за отзыв!
      Мануал в CI прекрасный (наверное один из лучших), но хотелось бы еще хорошую книжку (хотя бы на английском) :-)

      1. mihailt 05.02.2008 в 15:44 (Ответить)

        насчёт книжки могу поделится
        Packt Publishing - CodeIgniter for Rapid PHP Application Development 2007
        по моему единственная книга о CI правда 1.6 в ней нету ;)

        1. Владимир 05.02.2008 в 15:46 (Ответить)

          Если не сложно, вышлите.
          Заранее спасибо.

          1. mihailt 05.02.2008 в 16:22 (Ответить) (Достигнут максимальный уровень вложенности комментариев)

            выслал на gala.net ;)

  2. mihailt 05.02.2008 в 14:56 (Ответить)

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

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

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

    _TITLE_

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

    1. Владимир 05.02.2008 в 15:44 (Ответить)

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

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

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

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

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

      1. mihailt 05.02.2008 в 16:17 (Ответить)

        система с константами на самом деле такая же как и родная 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";

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

        1. Владимир 06.02.2008 в 21:32 (Ответить)

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

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

          Первый. Посетитель сам выбирает язык, а мы сохраняем его в сессии. Параллельно пробуем прочитать 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 :-(

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

          1. mihailt 07.02.2008 в 00:48 (Ответить) (Достигнут максимальный уровень вложенности комментариев)

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

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

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

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

            Третий вариант - даже не знаю зачем такое надо, но если вдруг надо, то насчёт $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 и его возможностях :)

          2. Владимир 07.02.2008 в 17:54 (Ответить)

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

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

          3. Sam 07.02.2008 в 18:08 (Ответить)

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

          4. mihailt 07.02.2008 в 19:56 (Ответить)

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

  3. MAX 05.02.2008 в 16:26 (Ответить)

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

    1. Владимир 06.02.2008 в 21:36 (Ответить)

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

  4. ВАГ 06.02.2008 в 16:38 (Ответить)

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

  5. MAX 07.02.2008 в 19:08 (Ответить)

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

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

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

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

    1. mihailt 07.02.2008 в 19:54 (Ответить)

      да как и сказал Sam решается при помощи _remap() ты же вроде у меня читал пост где это упомянуто?

  6. Sam 07.02.2008 в 19:15 (Ответить)

    Можно:
    http://codeigniter.com/user_guide/general/controllers.html#remapping

  7. MAX 07.02.2008 в 19:53 (Ответить)

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


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

    Спасибо! :)

  8. ACID Jesus 08.02.2008 в 08:56 (Ответить)

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

  9. Владимир 08.02.2008 в 12:40 (Ответить)

    Есть еще вариант использования _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. Вообще-то собирался написать пост на эту тему :-) .

  10. LANKO 28.02.2008 в 09:52 (Ответить)

    только начинаю изучать 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`.

    1. Владимир 28.02.2008 в 18:28 (Ответить)

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

      1. LANKO 28.02.2008 в 18:37 (Ответить)

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

        - основной язык: 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) было одинаковым во всех языках.

        1. Владимир 28.02.2008 в 20:21 (Ответить)

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

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

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

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

  11. a.koposov 02.03.2008 в 04:55 (Ответить)

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

    1. mihailt 02.03.2008 в 15:24 (Ответить)

      у меня на блоге, в последнем посте лежит ссылка.

    2. Владимир 02.03.2008 в 21:07 (Ответить)

      Уже отправил.
      А пост mihailt обязательно посмотрите. Там выложено 17 очень интересных книг.

  12. selma 17.03.2008 в 07:58 (Ответить)

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

  13. a.koposov 19.03.2008 в 15:44 (Ответить)

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

  14. Luiza 07.04.2008 в 12:20 (Ответить)

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

  15. Alex 16.04.2008 в 13:47 (Ответить)

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

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

  16. Alex 16.04.2008 в 13:48 (Ответить)

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

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

    1. Владимир 16.04.2008 в 17:44 (Ответить)

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

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

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

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

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

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

Введите ваш комментарий

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

Quicktags: