Kohana 3.2: организация мультиязычности

На моём сайте в определенный момент очень остро встал вопрос организации мультиязычности, причём речь шла не о 2-3 языках, а о том, чтобы перевод сайта на другой, абсолютно любой язык мог быть осуществлен за считанные минуты. При этом у каждого языка должен был быть уникальный адрес, то есть домен/ru/, домен/en/ и т.п. Информации в сети на эту тему не так много(может я плохо искал?) и мне пришлось думать самому. Хотя моё решение довольно простое и многим может показаться очевидным, я всё равно приведу его, надеюсь кому-нибудь оно будет полезно. Сразу предупреждаю, что необходимо мало-мальское представление о фреймворке kohana 3.

Итак, какие вообще могут быть варианты организации мультиязычности на сайте? Самый простой и очевидный вариант, это создание для каждого языка папки «ru», «en» и т.п. Но это значило бы копирование всех файлов, а если поменяли что-то, меняем во всех 30 папках… О ужас, развивать эту тему может только мазохист, поэтому, естественно, я стал искать другой путь, который бы позволил менять только языковые файлы с переводом.

Очевидно, что необходимо было ввести в роутинг параметр lang, который бы шёл в начале каждого правила:

Route::set('auth', '<lang>/<action>(/<param>)', array('action' => 'login|logout|registration|forgot|start'))
	->defaults(array(
                'directory'  => 'index',
		'controller' => 'auth',
	)); 	
Route::set('static', '<lang>/<action>(/<uri>)', array('action' => 'page|blog|news|forms|polls'))
	->defaults(array(
                'directory'  => 'index',
                'controller' => 'static',
                'action' => 'page',
	));   	
Route::set('default', '(<lang>(/<controller>(/<action>(/<param>))))', array('param' => '.+'))
	->defaults(array(
	        'directory' => 'index',
		'controller' => 'static',
		'action'     => 'index',
                'lang'        => 'ru'
	));


Обратите внимание, что последнем роуте я установил lang по дефолту, чтобы можно было открыть домен без указания языка домен, если бы я этого не сделал, тогда бы пришлось бы всегда дописывать язык домен/ru, ну или каждый раз выполнять перенаправление.

В основном контроллере в методе before (может это же лучше делать в bootstrap?) я определил константу LANG:

if ( !defined('LANG') ) {
       define('LANG', $this->lang());
}

// какой язык в адресе?
$lang_uri = $this->request->param('lang');

// если не совпадает перенаправляем
if ( $lang_uri != LANG ) {
     $this->request->redirect(LANG);
}

// подключаем файл перевода 
i18n::lang(LANG . '-' . $this->request->controller());


В методе lang я определяю язык пользователя по кукам или ip пользователя. Файл перевода лежит в папке application/i18n/LANG/controller_name.php. Я дробил перевод по контроллерам, можно это сделать по экшенам или вообще всё засунуть в один файл, это уж как вашей душе и потребностям проекта угодно.

Остается только переопределить метод helper'а HTML anchor. Для этого я создал в папке classes файл html с содержимым:
<?php defined('SYSPATH') or die('No direct script access.');

class HTML extends Kohana_HTML {

	public static function anchor($uri, $title = NULL, array $attributes = NULL, $protocol = NULL, $index = TRUE)
	{
                $uri = LANG . '/'. $uri;                
 
                 return parent::anchor($uri, $title, array $attributes, $protocol, $index);
        }
}


Я ВСЕГДА подаю в этот метод адрес без домена и начальной косой черты, если вы не всегда так делаете, то необходимо обработать входящий $uri правильным способом!

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

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

Комментарии 12

    0
    А чем не устроил стандартный вариант?
    Зачем плодить целую кучу одинаковых контроллеров, если можно использовать __() и менять язык через I18n::lang()?
      0
      Извините, но Вы видимо вообще не читали статью, так что даже не знаю, что ответить))
        0
        Извиняюсь, видимо устал и не сразу понял некоторые моменты…

        Тогда еще вопрос
        // если не совпадает перенаправляем
        if ( $lang_uri != LANG ) {
             $this->request->redirect(LANG);
        }
        

        Не кажется ненужным?

        Если я зашел через прокси Германии, то я теперь на русском не смогу посмотреть сайт?
        Ведь даже если я попытаюсь перейти на ru-версию, меня перенаправит обратно на немецкую.
          0
          // если не совпадает перенаправляем
          if ( $lang_uri != LANG ) {
               $this->request->redirect(LANG);
          }
          

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

          Конечно можно, это пользователь может сам уже может выбрать — обычно в шапке есть выпадающий список, где каждый язык написан по родному: Русский, English, Deutsch. При изменении записывается кука и тогда метод lang() выдает выбранный язык для пользователя.

          Но при первом посещении сайта, пользователь из Германии, конечно, увидит сайт на немецком.
      0
      Неужели в кохане нельзя установить базовый префикс для всех роутов?
        +1
        Конечно можно. Задается через base_url в Kohana::init.
          0
          Ну а там же нельзя, в таком случае, определить язык для модуля перевода и не писать его в каждом роуте, как ТС?
            +1
            Язык можно определять до вызова Kohana::init.
        0
        Язык пользователя также можно определить через $_SERVER['HTTP_ACCEPT_LANGUAGE']. Метод не 100%, но при этом не нужна база ip.
          0
          Ну, да это уже дело каждого, как определять язык пользователя, я выделил это в метод lang(), где каждый может это делать по своему усмотрению.
          0
          Почему для action ставятся ограничения, а для lang нет? Например чтобы он был например не больше двух символов или 'lang' => 'ru|en|de'
            0
            Не совсем понял, почему I18n::lang() должен устанавливать язык в зависимости от контроллера. То есть перевод одной и той же кнопки в шапке будет разным для разных страниц (точнее, он будет продублирован в разных файлах)?

            Если говорить о роутинге, вероятно лучше было бы создать отдельные классы (Route_I18n, HTML_I18n) для работы с такими адресами, и в них завернуть всю необходимую функциональность. Константа LANG тоже неправославна, хотя бы из-за возможного конфликта с подключенными вендорными библиотеками.

            Ну и, конечно, вопрос перевода контента не раскрыт. По сути, все вышеописанное позволяет делать легкий перевод интерфейса. И то, иногда бывает удобнее создать отдельные шаблоны для разных языков, чем дергать постоянно __() для отдельных элементов), тут напрашивается класс View_I18n, который бы проверял наличие шаблона для текущего языка. И тд

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

            Самое читаемое