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 View Comments

]]>
  • D@RK_DIE$EL
    У меня на сайте два языка, автозагрузка не работает:
    $autoload['language'] = array('ru');
    выдает:
    An Error Was Encountered

    Unable to load the requested language file: language/ru/ru_lang.php
    ижет не в самом файле, а почему-то в папке языка, который у меня сейчас установлен.

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



    Контроллер с сылками на языки выглядит так:
    $langs = array("ru","pl");
    if (in_array($this->uri->segment(1), $langs)){
    $pageData['lang'] = $this->uri->segment(1);
    }
    else
    $pageData['lang'] = "ru";
    $pageData['fulluri'] = $this->uri->uri_string();
  • CI требует чтобы все файлы переводов находились в папке
    /application/название_языка/имя_файла_lang.php
    После того как определили язык нужно загрузить файл переводов.

    if (in_array($this->uri->segment(1), $langs)){
    $pageData['lang'] = $this->uri->segment(1);
    $this->lang->load('имя_файла', 'название_языка');
    }
  • D@RK_DIE$EL
    К сожалению то что вы мне посоветовали мне не работает. Такой способ я опробовал еще до того как задать вопрос.

    Я по умолчания гружу русский язык указав в aplicaion/config/config.php :
    $config['language'] = "ru";

    В контролере я делал так:
    $this->load->helper('form');
    $langs = array("ru","pl");
    if (in_array($this->uri->segment(1), $langs)){
    $pageData['lang'] = $this->uri->segment(1);
    }
    else
    $pageData['lang'] = "ru";
    $this->lang->load('validation', $pageData['lang']);

    но ошибки валидации мне все равно выводит на русском языке, если меня в конфиге язык, то все работает. Помогите, как можно загрузить язык валидации.
  • А если убрать автозагрузку из конфига?
    Т.е. так
    $autoload['language'] = array();
  • D@RK_DIE$EL
    Спасибо, все получилось, очень вам благодарен.

    А можно ли адрес по умолчанию загрузить в виде названиеСайта/текущийязык/?
  • Проверьте uri->segment(1) и если он пуст, поставьте редирект на
    названиеСайта/текущийязык/
  • D@RK_DIE$EL
    помогите пожалуйста, я новичек в этом деле, контролеры для страниц со сменой названий сделал и теперь хочу сделать следующее:

    создал два файла, для меню, один на русском, другой на польском. Есть главный контроллер main, он грузиться автоматом. Ссылка соответственно названиесайта/index.php/main как мне переделать контроллеры и настройки фрэймворка чтобы ссылка выглядела
    названиесайта/языкСтраницы/index.php/main а потом проверять с каким языком ссылка и грузить соответсвующий файлик?


    А еще, что означает надпись uri->segment(1), нигде пока в уроках не видел описание.



    И можно ли убрать из названия ссылки index.php? Спасибо.
  • Смотрите комментарий mihailt. Он привел практически готовую реализацию.
    Только адрес будет выглядеть не
    названиесайта/языкСтраницы/index.php/main
    а
    названиесайта/index.php/языкСтраницы/main
    Убрать index.php из названия можно с помощь .htaccess. Пример здесь. Чтобы убрать index.php из названия ссылок измените параметр $config['index_page'] в config.php.
    uri->segment(1) - возвращает первый сегмент адреса. Подробнее здесь.
  • D@RK_DIE$EL
    Да, я читал mihailt, только я не понял, куда мне добавить

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

    чтобы название ссылок всегда шло с языками. Помогите, очень надо.
  • Эти правила нужно добавить в роутер (файл config/routes.php).
    Подробнее на эту тему читайте здесь.

    А формирование ссылок выполняется методом anchor(). В его первом параметре нужно указать все сегменты адреса, и первым должен идти язык.
    Т.е. код будет примерно такой
    $lang = $this->uri->segment(1);
    echo anchor($lang.'/остальные_сегменты_адреса', 'Текст ссылки');
  • Иван
    Здравствуйте , Уважаемые специалисты . Хотел у вас спросить насколько это хорошо , перекрывать нормальное поведение контроллера , создавая локальный роутинг с помощью ремапа (_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 не самое удачное решение.

    В любом случае, это стандартная возможность, и если её использование решает ваши задачи, то её имеет смысл использовать.
  • Иван
    Большое спасибо ,Владимир.
  • Есть ли в 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 и он нормально заработал.
    Проверял на последней версии.
    Какая версия у вас?
  • SHAMAN
    Всем привет. Друг наконец-то меня убедил начать изучать веб.
    Скинул вот эту книженцию CodeIgniter for Rapid PHP Application Development и сказал что не прочитав её - ничё не выйдет. Большая просьба к людям посещающим сей ресурс - скиньте плз на мыло shaman_13@rambler.ru где можно взять эту книжку на русском или что-то подобное (главное на русском), ибо в английском слаб для чтения таких вещей
  • У меня есть две книги о CodeIgniter (в т.ч. и эта), но обе на английском.

    И я не соглашусь с вашим другом. Если вы только начали изучать веб разработку, то нужно начинать не с библиотек и фреймворков, а с самых базовых вещей.
    Т.е. вам подойдет практически любой учебник по PHP и MySQL. Что-нибудь вроде "Освой PHP за 24 часа..."
  • 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);

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

    Это надо писать в каждом контроллере и для каждой необходимой строки?? Заранее благодарен за ответ.
  • Luiza
    да уж, мир не без добрых людей!
  • a.koposov
    Огромное спасибо! Не ожидал тако доброты =) Респект вам Люди.
  • Большое Вам спасибо!
  • a.koposov
    Шикарная статейка. Спасибо!
    Простите, а не могли бы Вы и мне выслать книжку?
    a.koposov(собака)reneissance.org
  • Уже отправил.
    А пост mihailt обязательно посмотрите. Там выложено 17 очень интересных книг.
  • у меня на блоге, в последнем посте лежит ссылка.
  • LANKO
    только начинаю изучать CI, в тему вопроса многоязычности:

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

    1. основной язык(lang0): www.host.com/
    2. дополнительный язык (lang1): www.host.com/lang1/
    3. дополнительный язык (lanh2): 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"; //значение по-умолчанию
    }
  • Есть еще вариант использования _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. Вообще-то собирался написать пост на эту тему :-) .
  • Отдельный риспект за ссылочку на файлы с переводами ;)
  • MAX
    Супер! Так сразу и не догадался. Вот код, если вдруг кому понадобится.


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


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

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

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

    Есть более элегантное решение?
  • да как и сказал Sam решается при помощи _remap() ты же вроде у меня читал пост где это упомянуто?
  • Спасибо за полезную информацию.
  • MAX
    Согласен с mihailt. Сам подход довольно тяжеловат с точки зрения синтаксиса. Поэтому, на мой взгляд оптимальным будет использование дополнительной функции, по аналогии с WordPress _e('ключ'), которая и будет возвращать переведенную фразу. :)
  • Да, хороший вариант. Я столкнулся с этой этой функцией когда переводил на русский один из плагинов к WP. Всего два символа, очень удобно.
    Напоминает функции типа $() в Prototype :-)
  • мне не очень нравится этот способ потому как

    $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, т.е. «угадать» выбор пользователя. Тут явный минус в том, что посетителю придется выбирать язык каждый раз при входе на сайт (если мы его не «угадали»).

    Второй. Это ваш вариант. Т.е. используются адреса вроде
    www.site.com/ru/controller/function/...
    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 :-(

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

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

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

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

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

    mod_rewrite... Я о нем не забываю, но постоянно не хватает времени научиться нормально его использовать. Т.е. переделать готовые правила под свои задачи не проблема, а вот написать правила с нуля - гораздо сложнее.
    Честно говоря, я сильно сомневаюсь, что кто-то полностью представляет все его возможности и варианты использования :-)
  • Sam
    Я представляю, хоть и не все... Поэтому мне не нравится ни один роутер в существующих фреймворках :(
  • а чем собственно не нравятся? чего не хватает?
  • Sam
    Прелестно... просто прелестно :)

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