JohnCMS | Переход на Gettext, мануал для разработчиков

2.8K
.
AlkatraZ
╭∩╮ (`-`) ╭∩╮
Как Вы знаете, JohnCMS сменил формата языков, то есть той системы, которая реализует многоязычность.
Ранее в JohnCMS для реализации многоязычности использовались .ini файлы, в которых хранились языки в формате ключ=>фраза. Эти файлы потом считываются системой и парсятся в PHP массив, который и доступен для применения в виде переменной $lng.
Мультиязычность была реализована еще в далеком 2010 году.

Однако время идет и появились отличные онлайн сервисы (к примеру Crowdin, Transifex и другие) и программы, которые очень сильно помогают разработчикам с переводом, систематизируют и упорядочивают их работу, позволяют избежать ошибок.
Работа с ключами массивов, несмотря на свою очевидность, в большом проекте превращается в реальный геморой.
Требовалось какое-то решение проблемы.

Прежде всего я исследовал возможности онлайн сервисов, программ, почитал кучу статей на тему как сегодня решается вопрос интернационализации (i18n) в современных программах.
Да, формат PHP array поддерживается многими сервисами перевода (в частности Crowdin), но хотелось чего-то более удобного и очевидного.
Сразу же натолкнулся на наиболее растространенное средство (для всех языков программирования) интернационализации - это GNU Gettext.
Данное расширение существует и для PHP, формат Gettext используется во многих популярных проектах, например Wordpress.
Несмотря на кажущуюся (по сравнению с PHP array) сложность самих языковых файлов, на деле данный формат дает большое удобство и предоставляет много возможностей для разработчика.
---
ВНИМАНИЕ!
Начиная с JohnCMS 9.0 используется значительно усовершенствованная и радикально упрощенная система работы с языками.
Данные инструкции считаются устаревшими.
Пожалуйста смотрите новые инструкции.
.
AlkatraZ
╭∩╮ (`-`) ╭∩╮
КРАТКОЕ ЗНАКОМСТВО С GETTEXT
Я не буду описывать подробности, статей на эту тему есть куча, поищите в интернете на предмет "gettext".
Можете так же поискать мануалы для переводчиков Wordpress, там тоже используется данный формат и многие мануалы могут быть полезными для понимания системы.

Расскажу как хранятся языки.

Если Вы загляните в папку с языками любого модуля, то увидите там файлы с расширениями .PO и .MO
Это и есть наше хранилище фраз с переводами.

Файлы .PO представляют из себя обычные текстовые файлы, в которых хранятся фразы в исходном виде (исходник).
В файле есть заголовок со служебными инструкциями и собственно фразы.
msgid - это фраза на исходном языке (в нашем случае на Английском)
msgstr - это уже переведенная фраза
В файле так же много комментариев с указанием, где в PHP исходнике встречается данная фраза. Именно с этими файлами и работают переводчики.
Однако сегодня их никто вручную не переводит, для этого есть удобные онлайн-сервисы и специальные редакторы (к примеру PoEdit)

Файлы .MO это скомпилированный в машинный формат .PO файл. Данные хнанятся в бинарном виде, потому посмотреть содержимое в текстовом редакторе невозможно.

Файлы .POT Еще Вы можете заметить, что у нас имеются файлы с расширением .POT
Это шаблоны с исходными фоарами. Перевода там нет.
Данные файлы формируются автоматически. Фразы извлекаются из исходного РНР куода с помощью специавльных утилит (опишу ниже).
После, данный файл загружается на онлайн-сервис перевода, или открывается в редакторе PoEdit и на его основе формируются .PO заготовки для всех языков.
.
AlkatraZ
╭∩╮ (`-`) ╭∩╮
Я понимаю, что всем инересно почитать о том, как на практике работать Gettext. Инструкции будут, но позже. Перед этим надо рассказать про реализацию мультиязычности в JohnCMS.

РЕАЛИЗАЦИЯ
Тут все довольно просто.
Вы пишите свой скрипт, там где в браузер должна выводиться какая-то информация, которую потом нужно переводить на другие языки, Вы применяете функцию _t() в которую в качестве аргумента вставляете нужную фразу.
Пример:
echo _t('Welcome');
Функция _t() займется переводом переданного ей текста. Если перевода не существует (ну не успели еще сделать), то будет показана исходная фраза.

Еще есть возможность плюрализации, то есть использования множественных чисел, например: 1 файл, 2 файла, 5 файлов.
Но об этом потом.

Примечание
Хотя в качестве исходного "системного" языка можно использовать любой, международный стандарт подразумевает, что для Gettext исходным языком является Английский.
В JohnCMS в качестве исходного тоже используется Английский. Это необходимо учитывать при разработке мультиязычных модулей.
.
AlkatraZ
╭∩╮ (`-`) ╭∩╮
ПРАКТИЧЕСКОЕ РУКОВОДСТВО: СТРУКТУРА
Каждый модуль JohnCMS имеет свои языковые файлы. Несмотря на некоторое дублирование фраз, достигается полная языковая независимость.
Несмотря на то, что вы можете располагать языковые файлы так, как Вам удобно, в JohnCMS существуют свои рекомендацити для этого:
/папка_с_модулем/locale/двухбуквенный_ISO_код/default.po (ну и там же скомпилированный default.mo)

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


ПРАКТИЧЕСКОЕ РУКОВОДСТВО: ИНСТРУМЕНТЫ
Для начала, нам понадобится специальный инструмент (читай редактор) для работы с Gettext.
Для этого лезем сюда: https://poedit.net и скачиваем бесплатный редактор PoEdit.
Устанавливаем его, благо, что он существует для Windows, Linux и MacOs.

У PoEdit есть много полезных наворотов, помогающих переводчикам. С этим разберетесь сами.
Сейчас мы поговорим о другой его возможности: автоматическое извлечение фраз из исходного кода.
Но об этом ниже.

Желающие разумеется могут установить нужные библиотеки и использовать интерфейс коммандной строки утилит Gettext, но данное направление тут рассматривать не будем, кому нужно сами разберутся.
.
AlkatraZ
╭∩╮ (`-`) ╭∩╮
ПРАКТИЧЕСКОЕ РУКОВОДСТВО: НАПИСАНИЕ КОДА
Для примера будем рассматривать модуль альбомов.
Подразумевается, что у нас пороцедурный код. Любители ООП могут данную часть подробно не читать, у них есть свои возможности прямой связи с контейнером и пакетом i18n.

В первую очередь Вам надо сообщить движку, откуда в Вашем модуле берется перевод и каков его формат. Поддерживаются форматы gettext, phparray, ini (нужное значение передается в виде аргумента).
Для этого вставляем в свой код ПОСЛЕ(!!!) подключения require('../incfiles/core.php') следующие строки:
/** @var Interop\Container\ContainerInterface $container */
$container = App::getContainer();
/** @var Zend\I18n\Translator\Translator $translator */
$translator = $container->get(Zend\I18n\Translator\Translator::class);
$translator->addTranslationFilePattern('gettext', __DIR__ . '/locale', '/%s/default.mo');

В данном коде мы в начале получаем объект контейнера $container, а далее от него требуем объект транслятора, чтоб потом сообщить ему откуда брать файлы пеервода.
Последняя строка нашего куска кода (метод addTranslationFilePattern()) как раз этим и занимается. Первым аргументом мы передаем тип файлов с фразами (gettext, phparray, ini), а вторым аргументом патерн поиска файлов с фразами.
Если используете формат Gettext и следуете рекомендациям по структуре, то просто скопируйте код как есть.

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

Ну а далее все просто...
Пишете свой код в Вашем любимом РНР редакторе, все фразы, которые нужно выводить в браузер и которые нужно перевести на другие языки, заключаете в функцию _t()
Про плюрализацию и варианты множественных чисел напишу отдельно.
.
AlkatraZ
╭∩╮ (`-`) ╭∩╮
ПРАКТИЧЕСКОЕ РУКОВОДСТВО: ПОДГОТОВКА К ПЕРЕВОДУ
Вот мы и подошли к главной части нашего мануала.
Если выше все сделали правильно, то у Вас код должен работать без проблем и фразы будут выводиться на исходном (Английском) языке, даже если Вы пока еше не создали никаких файлов с фразами перевода.
Вот этим мы сейчас и займемся.

СОЗДАЕМ ШАБЛОН ДЛЯ ПЕРЕВОДА
Так, как у нас подразумевается большое к-во различных языков и использование онлайн сервисов перевода, удобнее будет создать шаблон Gettext, как вы помните, это файл с расширением .pot
Чтоб не заморачиваться с созданием нового файла, проще скопировать его с другого модуля и настроить под себя.
Так, как у нас исходный язык Английский, в его папке (en) у нас и будет храниться шаблон.

1) В папке с Вашим модулем создаем подпапки ./locale/en/
Полный путь от корня движка может выглядеть так /mymodule/locale/en/

2) так, как в качестве примера мы рассматривали альбомы, лезем в папку /album/locale/en/ и видим там файл default.pot
Это и есть шаблон, копируем его в свою папку /mymodule/locale/en/, что мы создали выше.

3) Запускаем редактор PoEdit и открываем наш файл шаблона /mymodule/locale/en/default.pot
Вы сразу же увидите набор фраз исходного языка. Однако, так, как мы скопировали шаблон из другого модуля, там пока не наши фразы, а чужие.
Чтоб там появились именно наши фразы, нам надо настроить наш шаблон на извлечение фраз по нужному нам пути (из папки с нашим модулем).

4) Заходим в меню poEdit "Каталог -> Свойства"
Откроется окно, где будет 3 вкладки.
Первая вкладка нас не интересует, если Вы пишете не оф. модуль, а какой-то свой, можете вписать туда свои реквизиты. Главное не трогайте настройки кодировки. Должно быть UTF-8.

Нас интересует вторая вкладка "Папки с исходными файлами", переходим на нее.
Так, как мы скопировали наш шаблон из другого модуля, там будут прописаны именно его пути. Нужно их удалитьл и добавить свой.
В поле "Папки" Вы увидите точку. Выделите ее и ниже нажмите минус, чтоб удалить.
Далее, рядом жмите плюс и выберите пункт "Добавить папки". Укажите там корневую папку своего модуля.

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

На этом все. Жмем ОК и окно свойств закроется.

5) В панели редактора жмите кнопку "Сохранить", чтоб зафиксировать изменения в нашем шаблоне.
Наш шаблон успешно подготовлен к извлечению фраз.

6) На панели инструментов poEdit жмем кнопку "Обновить".
Запустится тот самый волшебный и удобный процесс по извлечению фраз из исходного кода.
Все старые фразы от чужого модуля удалятся.
Если вы уже успели написать что-то свое и сделали это правильно, в окне редактора появятся фразы именно вашего модуля.

7) Сохраняем наш шаблон.
.
AlkatraZ
╭∩╮ (`-`) ╭∩╮
ПРАКТИЧЕСКОЕ РУКОВОДСТВО: ПРОВЕРКА И АКТУАЛИЗАЦИЯ
Если выше все сделали правильно, у нас в папке /mymodule/locale/en/ будет находиться файл default.pot с фразами нашего модуля.
Еще раз откройте его в редакторе PoEdit и внимательно просмотрите.
Если заметили какие-то грамматические ошибки, или дублирующийся код (к примеру File и file рассматриваются как разные фразы) исправьте.

ИСПРАВЛЕНИЕ ОШИБОК
УЧТИТЕ, что сам шаблон нельзя редактировать, он у нас обновляется автоматом, любые изменения потом потеряются.
Если заметили какую-то ошибку, жмите на этой строке правой клавишей.
В открывшемся контекстном меню будет пункт "Ссылки:" где будут указаны все строки вашего кода, где встречается данная фраза.
Откройте свой РНР редактор и исправьте фразы в найденных выше строках.

АКТУАЛИЗАЦИЯ
Шаблон с фразами у нас уже был создан. Но сам исходный код постоянно меняется, дополняется и исправляются найденные ошибки.
Шаблон надо постоянно держать в актуальном состоянии с самыми свежими фразами.
Это делается легко. Открываем шаблон и жмем кнопку "Обновить", потом "Сохранить" и все.

(продолжение следует)
.
AlkatraZ
╭∩╮ (`-`) ╭∩╮
ПРАКТИЧЕСКОЕ РУКОВОДСТВО: ПЕРЕВОД
Ну и теперь наконец мы подошли собственно к переводу.
Дальнейшие шаги зависят от того, как вы собираетесь переводить Ваш модуль?

ПЕРЕВОД С ПОМОЩЬЮ ОНЛАЙН СЕРВИСОВ
Мы берем наш готовый и проверенный .pot файл и пересылаем его менеджеру проекта, он добавляет его в список файлов для перевода.
после этого все желающие (если проект открытый) могут его переводить.
Готовый результат (.po файлы для всех языков) можно будет скачать исходя из правил конкретного сервиса.

ПЕРЕВОД С ПОМОЩЬЮ POEDIT
Если Вы пишете свой собственный модуль, который не входит в оф. пакет и не желаете пользоваться онлайн сервисами, хорошим решением будет использование для перевода нашего редактора PoEdit.
Для примера, делаем перевод с Английского (исходник) на Русский

1) Подготавливаем папку для нашего языка.
Все языки у нас хранятся в папке /mymodule/locale/, там уже есть папка /en с шаблоном.
Создаем еще одну (локаль), для нашего языка /ru.
ВНИМАНИЕ!!!
Имена папок локалей должны соответствовать двухбуквенному стандарту ISO 639-1
Любая отсебятина обречена на провал, ваш язык просто не будет опознаваться системой.

2) Открываем пустой PoEdit (не открывая никакой файл)

3) В центре окна редактора жмем кнопку "Создать новый перевод" и выбираем файл с нашим шаблоном, что мы создали выше /mymodule/locale/en/default.pot

4) После выбора файла и нажатия кнопки "Открыть", появится окошко с выбором нужного языка для перевода.
Выбираем нужный язык и жмем "ОК"

5) Сохраняем наш перевод в папке для выбранного языка /mymodule/locale/ru/ которую мы создали выше.
УЧТИТЕ, что по умолчанию PoEdit будет предлагать сохранить файл с именем ru.pot Вам же надо сменить имя у сохраняемого файла на default.pot
Если помните, выше мы добавляли некоторый код, где была строка
$translator->addTranslationFilePattern('gettext', __DIR__ . '/locale', '/%s/default.mo');

Так вот, имя файла должно быть как там, только с расширением .po

6) Ну и далее, можно переводить. Если вы подключены к Интернету, то в PoEdit справа сразу же сможете увидеть варианты перевода.
.
AlkatraZ
╭∩╮ (`-`) ╭∩╮
ПРАКТИЧЕСКОЕ РУКОВОДСТВО: КОМПИЛЯЦИЯ .MO
Итак, в примере выше Вы перевели свой модуль на Русский.
Однако в браузере Ваш модуль почему то до сих пор разговаривает на Английском, хотя весь остальной движок исправно общается на Русском.
Заключительным этапом нам надо скомпилировать .mo файл, из которгого собственно и берется перевод.

1) Открываем с помощью PoEdit .po файл выбранного языка (файлы шаблонов .pot компилироватьнельзя).
В нашем уроке - это будет созданный выше /mymodule/locale/ru/default.po

2) В меню "Файл" выбираем команду "Компилировать в формат MO..."
Сохраняем файл в той же папке, где лежал исходный .po файл, выбранный для компиляции.
В нашем случае должен получиться /mymodule/locale/ru/default.mo

И все.
Если теперь из браузера откроете свой модуль, он будет говорить на Русском (разумеется те фразы. которые перевели).

ПРИМЕЧАНИЕ
Во время компиляции, PoEdit может показать какие-то ошибки, допущенные при переводе.
Обычно это или лишние переводы строк, или пропущенные плейсхолдеры, или что-то еще.
В этом случае исправляете перевод нужных строк (не исходный текст, а именно перевод) и пробуете скомпилировать еще раз.
Повторяете процесс до тех пор. пока компиляция не будет проходить без ошибок.
.
AlkatraZ
╭∩╮ (`-`) ╭∩╮
На этом все.
Тему открываю, если есть вопросы, можете задавать ниже.
За флуд, холивар, или вопрос не по теме - сразу отпуск.
Для трепа по соседству есть другая тема
Тут только мануал.
Всего: 34