Pull to refresh

Локализация проектов написанных с использованием MVC Framework Catalyst

Reading time 5 min
Views 2.6K
Catalyst Web FrameworkПредположим ещё имеются отчаянные товарищи изучающие и доказывающие преимущество Perl перед иными средствами web-разработки. Ещё реже среди них находятся те, которые оценили фрэймворк Catalyst, не испугавшись отсутствия документации на русском. Считаю, что те, кому будет интересен этот пост уже знакомы как создать новый проект, добавить во View TT и установить с CPAN недостающие детали.

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

По скольку Perl — значит gettext, но не всё так просто. Перейдем к практике.

Подготовка проекта


Для начала создадим новый проект или откроем существующий, скажем MyApp. Возьмем скрипт update_po.sh из проекта MojoMojo и положим его в каталог проекта ./script. Для того, чтоб этот скрипт стал пригодным, необходимо над ним проделать следующую манипуляцию, надеюсь никто из авторов против не будет.

Производим во всём файле замену MOJOMOJO_DIR на PROJECT_DIR.
# Default setting
PERL_DEFAULT=`which perl`
PROJECT_DIR="."
PROJECT_NAME="MyApp"

Добавляем локальную переменную PROJECT_NAME с именем вашего проекта и делаем замену во всем файле MojoMojo на $PROJECT_NAME.

Для работы этого скрипта необходима ещё одна программа po2json.pl, ее сохраняем в ./script. Оба файла делаем исполняемыми.
UPD
Скрипт po2json входит в состав модуля Locale::Simple


Если рассмотреть код update_po.sh, то можно заметить, что сбор строк для локализации происходит в следующих файлах и каталогах:
  • ./lib/[MyApp] — каталог с исходниками проекта
  • ./lib/[MyApp].pm
  • ./root/forms — шаблоны CRUD
  • ./root/base — шаблоны ТТ, более правильно под них выделять отдельный каталог
  • ./root/static/js — скрипты JavaScript

В случае необходимости скрипт можно подправить применительно к Вашим проектам.
Создадим каталог ./lib/MyApp/I18N, в нем будут находится наши .po файлы. Если мы будем использовать JavaScript, то необходимо создать каталог ./root/static/json.

Код и настройки


Добавляем View::TT, назовем класс HTML. Если у нас существующий проект со своим представлением, можем этот пункт пропустить.
 $ ./script/myapp_create.pl view HTML TT

Данная команда нам создает файл ./lib/MyApp/View/HTML.pm. В его раздел конфигурации добавим путь, который будет указывать где будут находится шаблоны:
__PACKAGE__->config(
    TEMPLATE_EXTENSION => '.tt',
    INCLUDE_PATH => [
        MyApp->path_to('root', 'base'),
    ],
    render_die => 1,
);

Для простоты изложения в ./lib/MyApp.pm делаем это представление по умолчанию:
__PACKAGE__->config(
    name => 'MyApp',
    default_view => 'HTML',
    'View::HTML' => {
        ENCODING => 'UTF-8',
    },
...

а в объявлении плагинов добавляем I18N и Unicode:
use Catalyst qw/
    -Debug
    ConfigLoader
    Static::Simple

    I18N
    Unicode
...

Плагин I18N добавляет к проекту функцию loc() или localize(). О других возможностях можно почитать тут Catalyst::Plugin::I18N.

Переходим к оформлентю первой локализованной страницы. Попробуем два варианта вызова одновременно. Первый передает строчку из контролера в шаблон по средством stash, ее мы отобразим в заголовке страницы. Второй отражает строчку в теле страницы по средству вызова функции в шаблонизаторе.
Видоизменяем функцию, отвечающую за корневую страницу в ./lib/MyApp/Controller/Root.pm:
sub index :Path :Args(0) {
    my ( $self, $c ) = @_;

    $c->stash->{title} = $c->loc("Home Page");
}

Создаем шаблон нашей индексной страницы ./root/base/index.tt
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>[% title %]</title>
  </head>
  <body>
    [% c.loc("Hello World!") %]
  </body>
<html>

Запускаем и проверяем. Если возникли ошибки: исправляем/догружаем модули с CPAN.

Теперь переходим к переводу нашей страницы. Создаем пустые файлы для русской и скажем немецкой локализации:
 $ touch ./lib/MyApp/I18N/ru.po ./lib/MyApp/I18N/de.po

и наполним их скриптом, о котором речь шла в начале статьи:
 $ ./script/update_po.sh ru de
Если возникли ошибки — догружаем недостающие модули с CPAN.

Теперь отредактируем эти файлы на примере «родного»:
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ru\n"

#: root/base/index.tt:8
msgid "Hello World!"
msgstr "Привет Мир!"

#: lib/MyApp/Controller/Root.pm:32
msgid "Home Page"
msgstr "Домашняя страница"

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

При изменении исходного кода проекта перезапускаем update_po.sh с указаним языков, старые данные в .po файлах сохганяются.

Базы данных


Для корректного хранения и исползования во View локализованной информации необходимо драйверу при подключении к базее сообщить, что информация передается в utf8. Для этого в конфигурации подключения указываем параметр равный единице, который для каждой SQL базы данных отличается: sqlite_unicode, mysql_enable_utf8, pg_enable_utf8, odbc_utf8_on и т.д.
<Model::DB>
    <connect_info>
        dsn             dbi:mysql:database
        user            username
        password        password
        mysql_enable_utf8 1
    </connect_info>
</Model::DB>
Подробнее необходимо смотреть в документации к DBI драйверу.

Костыли


Если выводить через TT уже локализованную информацию, например из базы данных или текстового файла то мы с очень высокой долей вероятности увидим кракозябры. Вылечить нам это поможет данная функция:
sub enc {
    my ( $self, $str ) = @_;

    utf8::decode($str);
    return $str;
}

ее мы вставляем в ./lib/MyApp.pm, соответственно можем вызывать эту функцию как из perl:
    $c->stash->{field} = $c->enc($data);

так и в TT:
    [% c.enc(list.field) %]

Есть ещё один способ для TT, это создание фильтра. Для этого нужно определить FILTERS в конфигурации представления ./lib/MyApp/View/HTML.pm
__PACKAGE__->config(
    TEMPLATE_EXTENSION => '.tt',
    INCLUDE_PATH => [
        MyApp->path_to('root', 'base'),
    ],
    render_die => 1,
    FILTERS => {
        enc     => sub { utf8::decode($_[0]); $_[0]; },
    }
);

Вызывается фильтр таким способом:
    [% list.field | enc %]

Но есть с ним какой-то БАГ при использовании данных и dbi.

JavaScript


Прежде всего необходимо добавить функцию обертки, желательно в виде подгружаемого файла:
function loc(str) {
    if ( (typeof(locale) === 'undefined') || (typeof locale[str] === 'undefined') )
        return str;
    return locale[str]
}

а в заголовок страницы добавить загрузку транслируемых данных локализации
...
    <script src="[% c.uri_for_static('json/') _ c.language _ '.po.json' %]" type="text/javascript"></script>
  </head>

Редактировение файлов локализации происходит в два этапа: вызываете update_po.sh и редактируете файл локализации в ./lib/MyApp/I18N; снова вызываете update_po.sh, чтобы обновить файл ./root/static/json/[lang].po.json.

PS:


Данная статья освящает очень малую часть вопросов локализации проекта под Catalyst, но достаточную для того, чтоб начать.
Tags:
Hubs:
+13
Comments 12
Comments Comments 12

Articles