Мультиязычность проектов

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

Немного теоретических размышлений
Изучая одну открытую платформу, обратил внимание на отличный подход к локализации (переводу) данных и решил "поковыряться поглубже". Если говорить о локализации, стоит выделить три основных типа данных, которые следует локализовать:
1. Сущности - то есть объекты, с которыми мы работаем. Сюда можно отнести такие понятия как login - логин; password - пароль; rule - право доступа и т.д. Другими словами, речь идет о классах и их свойствах, которые фигурируют в системе;
2. Компоненты пользовательского интерфейса - когда работает программист, записи вида msg://helloMessage это приемлемо, но когда за работу берется дизайнер, подобного рода записи, часто необходимые для локализации, начинают сильно мешать. Посмотрите сами, какой из шаблонов страницы лучше:
<div>
<h1>msg://TitleMessage</h1>

<p>%Content%</p>
</div>

или

<div>
<h1 dLocal="text">Hello people!</h1>

<p dInit="contentLoad">Test content. More text...</p>
</div>
Мне нравиться второй вариант. По моему мнению, здесь содержание шаблона отделяется от его форматирования. В первом случае при запуске такой страницы в браузере, мы увидим ненужную для нас, программную информацию, что частенько раздражает дизайнеров, и я их понимаю, мне тоже не хотелось бы задумываться обо всех этих программных примочках, а просто писать дизайн и пример страниц.
3. Динамический, "тяжелый контент" - к примеру сообщения пользователей или новостные сообщения, локализация которых часто очень сложна или занимает много времени (данный тип мы рассматривать не будем).

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

Как я это вижу
При написании модулей локализации в своей платформе, я задался вопросом - как это лучше сделать?
Выделил следующие обязательные условия, которым должна соответствовать система локализации:
1. При локализации сущностей, следует беспокоиться о размерах файлов локализации, дабы не перебирать огромные массивы данных с целью поиска нужной нам записи;
2. Не следует смешивать процесс локализации с процессом программирования сущностей. Программисту и так в голове нужно держать целую проектную модель, на кой ему еще и беспокоится о написании локализации? Нужно сделать так, чтобы отдельные люди могли заниматься локализацией, при этом они не обязаны знать программирование!
3. То же самое, что и п. 2, только относительно дизайнеров!
4. Следует обеспечить дизайнерам возможность писать примеры страниц с тестовым содержимым, чтобы заказчик мог оценить результат как он есть, вместо записей на экране вида %contentID%!
5. При работе с пользовательским интерфейсом, вопрос объемов данных для локализации стоит еще более остро чем в п. 1;
6. Сделать все это удобным для работы!

Как видите, условия довольно строгие, но справедливые!

Еще немного теории перед началом
Для того, чтобы вы могли понять подход, применяемый мной для мультиязычности, следует немного сказать о платформе. Платформа имеет модульную структуру, это означает, что ее функциональность зависит от модулей, которые в ней устанавливаются. Так существуют модули Users - пользователи; Access - права доступа; TapeNews - лента новостей и т.д. Каждый модуль включает сущности и логику, строго ограниченную задачами данного модуля.
Помимо модулей, в систему можно устанавливать экраны. Это специальные компоненты, отвечающие за визуализацию модулей. Каждый экран состоит из html, css и js файлов, в первом содержится разметка, во втором дизайн, в третьем логика. Используется PHP (для модулей) и JS (для экранов), а так же объектно-ориентированный подход.
Все что мне нужно, это реализовать локализацию модулей, при чем так, чтобы любой сторонний разработчик мог без проблем использовать ее для своих модулей, а так же реализовать локализацию экранов с теми же условиями.

Локализация модулей
Для понимание принципов локализации модулей рассмотрим пример модуля Access. Данный модуль отвечает за предоставление прав доступа к другим модулям пользователям и имеет следующую структуру:
1. Controller - стандартный программный интерфейс доступа к модулю. Нам он не интересен так как нечего локализовывать;
2. AccessManager, EssenceManager - бизнес-логика модуля. Так же нечего локализовывать;
3. Role - сущность! Представляет роль пользователя типа: админ, модератор, пользователь, гость. Каждому пользователю может быть назначено сколько угодно ролей, они и определяют что пользователь может, а что нет. Есть простор для локализации;
4. Rule - тоже сущность! Право доступа (или точнее не доступа), которое определяет модуль и его метод, к которому не может получить доступ с данным правом доступа. Можно локализовать.

Теперь взглянем на сущности:
class Role{
  /**
   * Наименование роли.
   * @var string
   */
  protected $name;

  /**
   * Множество правил доступа, связанных с данной ролью.
   * @var \PPHP\tools\patterns\database\associations\LongAssociation
   */
  protected $rules;
}
class Rule{
  /**
   * Имя модуля, на которого распространяется правило.
   * @var string
   */
  protected $module;

  /**
   * Имя запрещаемого метода.
   * @var string
   */
  protected $action;
}

Что же тут локализовывать? Представьте что вы админ и вам нужно добавить новую роль в систему, на пример "VIP-пользователь". Перед вами таблица, которая называется Role (а вам бы хотелось Роли), в которой поля key, name и rules (а вам бы хотелось Ключ, Имя роли, Права) и список уже существующих ролей. Есть что локализовать, не правда ли? При чем локализовать нам нужно имя класса и его свойств.

Реализуется это у меня следующим образом. Так как каждый класс находится в отдельном файле, а каждый файл одного модуля в каталоге этого модуля, то системе не составляет проблем найти файл локализации класса, она просто ищет файл, имя которого равно имени класса с суффиксом локали (en, ru и т.д.). В данном файле в виде ini структуры записывается локализация класса. Пример для класса Role:
Role_ru.ini
Role=Роли
key=Ключ
name=Имя роли
rules=Права доступа
Очень маленький и простой файл. Правила локализации модулей поймет даже не программист. Нужно локализовать класс? Создай файл с тем же именем, что и класс, добавь через нижнее подчеркивание имя локали и записывай в файл имя локализуемого свойства и его локализацию через знак равенства. Нужно локализовать имя самой сущности? Пиши имя класса в этот файл и локализуй!
Как же выполняется локализация? Когда мы передаем объект некоторого класса пользователю (на уровень экранов), система в тайне(!) от программистов предварительно ищет файл локализации, считывает из него данные и локализует объект, после чего передает его пользователю, потому на уровне модуля мы работаем с объектами, а экраны получают уже локализованные данные (ведь если пользователь открывает сайт для ru, зачем ему получать от сервера данные на инглише, а потом их локализовывать? Зачем я спрашиваю?!).

Локализация экранов
С локализацией экранов немного сложнее. Говорю я значит своему дизайнеру:
- Хочешь локализовать заголовок страницы, запиши в этот тег msg://имяЗаписи и потом запишешь локализацию этой записи в этот вот файл указав имя записи и ее значение! - а он на меня смотрит и... все!
Понял что дело не ладно и решил, что дизайнеры не должны локализовывать ничего! Пусть пишут по старинке:
<h1>Тестовый заголовок</h1>
<p>Какое то там содержимое. йух!</p>
а система пусть сама все локализовывать при помощи специальных файлов. Заметьте, что чаще всего локализовывать нужно либо:
1. Содержимое тегов;
2. Содержимое атрибутов тегов (как на пример атрибут value у тега <input type="text").

Экраны у нас тоже содержаться в отдельных каталогах экранов, потому файлы локализации можно писать в них и никаких огромных файлов с текстом не нужно (да и искать легко).
Вот пример экрана TapeNews:
TapeNews/browse
browse.html
browse.css
browse.js
Сюда же помещаем файл, имя которого соответствует имени экрана (browse) + суффикс локали (en, ru и т.д.): browse_ru.ini
Теперь дизайнер пишет структуру экрана в html файле:
<div>
<h1>Test</h1>
<p>Какое то там содержимое.</p>
</div>
После добавляем управляющие атрибуты (дизайнеру о них знать и не нужно):
<div>
<h1 dLocal="text">Test</h1>
<p dInit="contentLoader">Какое то там содержимое.</p>
</div>
dLocal - следует локализовать содержимое тега (то есть текст "Test");
dInit - следует обратиться к методу contentLoader контроллера экрана, и получить от него нужные данные для тега (уже локализованные).

Теперь остается записать в файл локализации следующее:
Test=Тестовый заголовок
Все! Теперь перед показом экрана пользователю, система автоматически выберет все теги с атрибутом dLocal, найдет файлы локализации, переведет данные и покажет пользователю результат.

Делов то?!
Краткая модель уровневой мультиязычности мной расписана. Думаю получилось довольно просто и понятно, а главное удобно! Заниматься локализацией могут даже отдельные специалисты, которым не нужно углубляться не в программирование, не в верстку и дизайн.
Удачи в программировании!

URL: https://visavi.net/articles/488