Поставляем переводы собственных модулей на продакшен.
Когда я пишу кастомные модули для проектов, там естественным образом оказывается множество различных лейблов, описаний и т.д. Это поднимает вопрос, а на каком языке писать эти самые метки?
По стандартам, весь код должен быть на английском языке, даже если ваш сайт моноязычный. Если проект моноязычный, в принципе, очень легко частично убегать от проблемы. Но она будет переодически о себе трубить, если её не игнорировать, получается достаточнно странная ситуация.
Если забить на факт стандартов (что уже аргумент в пользу подхода), что метки, описания и прочие пользовательские строки должны быть англоязычными, остаются другие проблемы. А именно, не везде можно игнорировать TranslatableMarkup
. Например, в маршрутах по умолчанию заголовок автоматически проходит через переводы, но это решается кастомным методом для заголовка, который вернет строку на нужном языке в обход переводов. Но это требует дополнительных действий и кода.
Есть места, где игнорирование в принципе невозможно. Например, в *.links.menu.yml
и подобных файлах, вообще не предусметрено что лейблом может быть отличный от английского язык.
Да, можно писать на русском, но это равносильно new TranslatableMarkup('Привет Мир!')
— bad practice в довесок к игнорированию стандарта.
Можно миксовать, там где нельзя подлезть, писать на английском, а где можно, на русском. Но это полумера всеравно поднимет вопрос, а как же тогда переводить то что написано на английском. Да и вообще, сильно ударяет по "целостности проекта", и получается тяп-ляп.
В итоге, вместо того, чтобы воевать и уворачиваться от стандартов, я пришёл к тому, что нужно просто им следовать и не париться. И тут возникает резонный вопрос, если проект кастомный, как доставлять переводы? Ведь после деплоя заходить и переводить — полнешний бред и пустая трата времени.
Как хорошо что и тут позаботились в ядре и добавили спец. настройки для модулей и тем! Всего 2 строки в *.info.yml
файле модуля позволят забыть про деплой переводов.
Как подключить и использовать ¶
Для подключения поддержки переводов модулем (или темой), который не находится на drupal.org и не имеет переводов на localize.drupal.org, можно добавить в его объявление две строки:
-
'interface translation project'
: (обязательно) Содержит название проекта, к которому относятся переводы. В контексте кастомного модуля — это машинное имя модуля. -
'interface translation server pattern'
: (обязательно) Путь до "сервера", откуда забирать переводы для импорта.
Как уже написал выше, в 'interface translation project'
просто указывается машинное имя модуля где это описывается.
В случае с 'interface translation server pattern'
, нужно указать путь до .po файла с переводами, которые необходимо импортировать для данного модуля.
Данный параметр имеет шаблоны подстановки:
-
%core
: Версия ядра указанная вcore
*.info.yml
файла. -
%project
: Машинное имя проекта. -
%version
: Версия модуля изversion
. Для кастомов это имеет мало смысла, так как version задавать руками не принято, его добавляет drupal.org для своих проектов. Но если указан, можно задействовать. -
%language
: Язык, для которого ищется перевод на импорт.
Эти плейсхолдеры опциональны. В пути можно указать как локальный путь, так и удаленный, например:
- Локально с использованием StreamWrapper:
public://ru.po
,translations://ru.po
и т.д. - Локально по относительному пути:
modules/custom/%project/translations/%language.po
. В данном случае, путь должен быть относитель docroot проекта. - На удалённом сервере
https://my-company.ru/translations/%core/%project/%language.po
.
В своих проектах я использую второй подход, все переводы я храню в translations/ru.po
файле модуля. В пути я использую подставноку %project
, чтобы не писать название второй раз.
Далее остаётся дело за малым, писать на английском в коде, и добавлять попутно переводы в файлик. Может показаться что это слишком замороченно, но на деле даже не напрягает, делается крайне шустро.
Для того чтобы переводы подхватились нужно обновить локаль drush locale:update
, которая и подхватит файл указанный в настройках, а затем, сразу импортирует его на проект. После данных манипуляций необходимо также сбросить кэш, чтобы переводы актуализировались.
Так, у меня в деплой добавилась просто одна строка перед сбросом кэша, и у меня всегда актуальные переводы модулей, не только с drupal.org, но и кастомные.
name: Dummy
type: module
description: 'Some example'
package: Custom
core: 8.x
'interface translation project': dummy
'interface translation server pattern': modules/custom/%project/translations/%language.po
Разбираемся с .po файлом ¶
Как я и написал, придётся ручками добавлять переводы по мере нобходимости. Проще всего не пытаться найти прогу для этого, а просто запомнить пару условий и конструкций, а затем писать их через ваш же редактор кода, чтобы не тратить время на доп. софт.
Файл .po стандартизован, я лишь напишу шпаргалку:
- В каждом
.po
файле первый перевод идет для мета-информации, он полностью игнорируется. Если использовать софт, он туда добавляет информацию об авторе, проекте и т.д. Для Русского языка нужно указать формулу склонений, иначе не будет работать третья форма (5 яблок).
Ранее тут была рекомендация использовать в качестве «заголовка» пустые msgid
и msgstr
. В случае с русским языком и тремя склонениями, также необходимо указывать формулу, которая представлена ниже. Если переводы вашего модуля будут загружены последними, то будет всего два склонения!
msgid ""
msgstr ""
"Plural-Forms: nplurals=3; plural=((((n%10)==1)&&((n%100)!=11))?(0):(((((n%10)>=2)&&((n%10)<=4))&&(((n%100)<10)||((n%100)>=20)))?(1):2));\n"
-
msgid
— для оригинала,msgstr
— для перевода. Должны идти друг за другом именно в таком порядке. -
#
для комментариев. - Для переводов с
context
, передmsgid
указывается контекст при помощиmsgctxt
. Напримерmsgctxt "Drupal commerce"
. - Для множественных форм используется следующая конструкция:
# Оригинал в единственном числе.
msgid "1 minute"
# Оригинал во множественном числе.
msgid_plural "@count minutes"
# Перевод единственного числа.
msgstr[0] "1 минута"
# Первая форма множественного перевода.
msgstr[1] "@count минуты"
# Вторая форма множественного перевода.
msgstr[2] "@count минут"
- Для многострочных значений, первая строка должна быть
""
, затем, каждая строка должна содержать необходимый текст.
msgid ""
"First paragraph.\n"
"Second paragraph."
msgstr ""
"Первый абзац.\n"
"Второй абзац.\n"
Автоматическая генерация .po файла ¶
Если вы хотите автоматизировать генерацию оригинальных строк, то можно воспользовать проектом drupal/potx. Он сырой, но рабочий.
Вам необходимо загрузить модуль при помощи композера composer require kgaut/potx
, а затем включить его drusn en potx
. После чего, у вас появится новая команда для генерации .po файлов. Там есть способ в обход установки модуля, для этого читайте README.md проекта.
Пример экспорта всех строк из указанных модулей. Строки будут экспортированы в корень проекта в файл general.pot
.
drush potx single --include=modules/contrib/potx --modules=foo,bar --api=8
Аналогичная команда, но с multiple
вместо single
сделает по .pot файлу, для каждого файла где найдётся строка на перевод. В прямом смысле, если в файле modules/custom/dummy/src/Plugin/EntityReferenceSelection/ProductVariationWithSkuSelection.php
будут найдены строки, то он создаст файл modules-custom-dummy-src-Plugin-EntityReferenceSelection.pot
(и опять в корне сайта).
drush potx multiple --include=modules/contrib/potx --modules=foo,bar --api=8
Такие команды сгенерируют примерно следующий результат:
# $Id$
#
# LANGUAGE translation of Drupal (general)
# Copyright YEAR NAME <EMAIL@ADDRESS>
# Generated from files:
# modules/custom/dummy/dummy.info.yml: n/a
# modules/custom/dummy/src/Plugin/EntityReferenceSelection/ProductVariationWithSkuSelection.php: n/a
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"POT-Creation-Date: 2019-09-02 14:22+0000\n"
"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n"
"Last-Translator: NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <EMAIL@ADDRESS>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
#: modules/custom/dummy/dummy.info.yml:0
msgid "Dummy"
msgstr ""
#: modules/custom/dummy/dummy.info.yml:0
msgid "EntityReferenceSelection example"
msgstr ""
#: modules/custom/dummy/dummy.info.yml:0
msgid "Custom"
msgstr ""
#: modules/custom/dummy/src/Plugin/EntityReferenceSelection/ProductVariationWithSkuSelection.php:8
msgid "Order Item selection with SKU"
msgstr ""
Удобен он тем, что часть работы берет на себя модуль. Он также указывает где он нашёл данную строку, и когда она пропадёт, он её удалит (хоть и глючит для yml файлов определение строки, но работает вроде корректно).
Но данный способ мне не прижился, так как экспорт в 1 здоровенный файл, даже без возможности указать куда выгружать — для меня перекрывает абсолютно все плюсы. Я хочу чтобы переводы были только там, где они используются, а не в одном спагетти-файле. Плюс, как видно из сгенерированного примера, а также явно указано на странице проекта — он сырой.
Также у него не очень приятное поведение если поменялась оригинальная строка. Он заменяет как строку, так и перевод. С одной стороны это логично, с другой, если вы добавили запятую, он откатит перевод целиком, но в этом нет необходимости. В общем, спорный помошник, но может вам подойдёт.
Комментарии
Спасибо за статью!
Для кастомного проекта удобно положить все переводы в один файл и импортировать при деплое через drush locale:import
.
drusn en potx --> drush en potx
well done!
Все таки деплоть перевод лучше так:
drush locale-check && drush locale-update && drush cr
Без locale-check у меня не подхватывался.
Добрый день, Никита. А если, к примеру, такая ситуация - описал в своём модуле po-файлы с переводами, далее модуль установил, он успешно работает и использует переводы. А потом возникла необходимость доработать этот модуль и при этом что-то в po-файлы дописать, например, новые переводы и т.д., то каким образом эти обновления переводов подгружать на этапе update модуля? Есть ли какие-нибудь методы, чтобы их использовать в hook_update? Или как вообще эти обновления правильно делать?
Качественно. Спасибо!