Перейти к содержанию

Переводы админки

Новая админка Grav 2.0 (Admin2 на базе admin-next) расширяет существующую языковую цепочку Grav опциональным современным форматом сообщений, поддерживающим подстановки, правила множественного числа и select-варианты — без нарушения совместимости с Grav 1.x и классической админкой.

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

Как Admin2 читает переводы

Новая админка загружает переводы через эндпоинт GET /api/v1/translations/{lang}, предоставляемый плагином API. Этот эндпоинт возвращает результат стандартного Languages::flattenByLang() Grav, который уже объединяет languages/<lang>.yaml и languages.yaml каждого плагина в единую плоскую карту в точечной нотации. Плагинам не нужно регистрировать переводы — достаточно положить YAML-файл в languages/.

Admin2 ищет каждый ключ в двух местах по порядку:

  1. ICU.<key> — передаётся через ICU MessageFormat для подстановок, множественного числа, select-вариантов, форматирования чисел и дат.
  2. <key> — возвращается как есть, точно в том виде, в каком указан в YAML.

Если не найдено ни то, ни другое, админка создаёт удобочитаемый запасной вариант на основе самого ключа (ADMIN_NEXT.SOME_FIELD → "Some Field"), чтобы отсутствующие переводы оставались понятными.

Важно

Формат включается по пространству имён, а не по содержимому. Значение проходит через форматтер, только если его ключ начинается с ICU.. Значения без этого префикса никогда не переформатируются — даже если они содержат {...}. Это делает поведение предсказуемым и полностью сохраняет Grav 1 / классическую админку без изменений.

История совместимости

Пространство имён ICU. было спроектировано так, чтобы один релиз плагина мог поддерживать и Grav 1.7, и Grav 2.0:

Профиль плагина Grav 1.7 / классическая админка Grav 2.0 / Admin2
Старый плагин без изменений Работает как сейчас Использует плоскую строку (без множественного числа, но читаемо)
Плагин для двух версий (плоский + ICU.) Читает только плоские ключи — ICU. невидим Сначала читает ICU., получает полную поддержку множественного числа и подстановок
Плагин только для Grav 2.0 Не применимо — не загрузится на Grav 1 Использует исключительно ICU.

Language::translate() в Grav 1 ищет точные ключи, не зная о пространствах имён, поэтому блок ICU: верхнего уровня в YAML полностью игнорируется Grav 1. Не нужно менять PHP, не нужен слой совместимости и не нужно патчить классическую админку.

Примеры YAML для плагинов

Плагин только для Grav 2.0

Если плагин поддерживает только Grav 2.0, поместите всё в ICU::

ICU:
  PLUGIN_MYPLUGIN:
    TITLE: "My Plugin"
    GREETING: "Hello, {name}!"
    ITEMS_FOUND: "{n, plural, =0{No items} one{# item} other{# items}}"
    EDIT_LABEL: "{type, select, page{Edit page} post{Edit post} other{Edit item}}"

В blueprints.yaml объявите совместимость только с 2.0 (см. Совместимость плагинов):

compatibility:
  grav:
    - '2.0'
dependencies:
  - { name: grav, version: '>=2.0.0' }

Плагин для двух версий (Grav 1.7 + 2.0)

Поставьте два параллельных блока. Grav 1 читает ключи верхнего уровня; Admin2 предпочитает версии из ICU.:

# Grav 1 / классическая админка читает только этот раздел.
PLUGIN_MYPLUGIN:
  TITLE: "My Plugin"
  GREETING: "Hello"
  ITEM_FOUND: "1 item found"
  ITEMS_FOUND: "items found"

# Admin2 предпочитает этот раздел, если он есть; Grav 1 полностью его игнорирует.
ICU:
  PLUGIN_MYPLUGIN:
    TITLE: "My Plugin"
    GREETING: "Hello, {name}!"
    ITEMS_FOUND: "{n, plural, =0{No items} one{# item} other{# items}}"

Ваши PHP- и Twig-пути кода могут продолжать ссылаться на PLUGIN_MYPLUGIN.GREETING в обеих версиях Grav и продолжат работать. Код на стороне Admin2 вызывает t('PLUGIN_MYPLUGIN.GREETING', { name }) и получает отформатированную версию на Grav 2.0 с автоматическим переходом к плоской строке, если переводчик ещё не заполнил блок ICU..

Совет

Запасной вариант применяется для каждого ключа, а не для каждой локали. Переводчики могут переносить строки из устаревшего блока в блок ICU. в своём темпе — любой ключ, которого ещё нет в ICU., просто передаётся в плоскую версию.

Вызов переводов из кода Admin2

Из компонентов Svelte, поставляемых в admin-next

import { i18n } from '$lib/stores/i18n.svelte';

const label = i18n.t('PLUGIN_MYPLUGIN.GREETING', { name: user.name });
const exists = i18n.has('PLUGIN_MYPLUGIN.GREETING');

Из веб-компонента плагина

Сборки полей плагинов, загружаемые в Admin2 (например, пользовательские типы полей схем, поставляемые как веб-компоненты), не собираются под среду выполнения admin-next. Вместо этого они используют стабильный глобальный объект только для чтения:

const { t, has, locale, subscribe } = window.__GRAV_I18N;

const label = t('PLUGIN_EDITOR_PRO.TOOLBAR_BOLD');
const items = t('PLUGIN_MYPLUGIN.ITEMS_FOUND', { n: count });

// Реагировать на изменение локали (например, переключение языка админки)
const unsubscribe = subscribe((newLocale) => {
  rerender();
});

Глобальный объект устанавливается при загрузке Admin2 и заморожен — плагины могут рассчитывать на его наличие и структуру во всех релизах Grav 2.x.

Краткий справочник ICU MessageFormat

{name}                                         простая подстановка
{n, plural, one{# minute} other{# minutes}}    категории множественного числа для локали
{type, select, page{Page} post{Post} other{Item}}    варианты select
{n, number, percent}                           форматированное число
{when, date, short}                            форматированная дата

Используйте =0, =1 и т. д. для точных совпадений перед категориями множественного числа:

{n, plural, =0{No results} one{1 result} other{# results}}

Категории множественного числа CLDR (zero, one, two, few, many, other) применяются для каждой локали автоматически. Польский, чешский, русский, арабский и другие языки с богатой системой множественного числа получают правильную форму без отдельного кода для каждого языка в плагине. Всегда включайте other как универсальный вариант — он обязателен в формате.

Инструменты перевода, понимающие ICU MessageFormat (Crowdin, Lokalise, Phrase и т. д.), сохраняют синтаксис при переводе. Для YAML, редактируемого вручную, предпочитайте более простые формы; используйте множественное число и select только в случаях, когда композиция по правилам английской грамматики действительно ломается в другом языке.

Рекомендации по миграции

  • Для новых строк только для Admin2 с первого дня помещайте их в ICU. — даже если в значении нет подстановок. Затраты нулевые (форматтер пропускает простые строки без изменений), и вы избежите миграции позже, когда добавится подстановка.
  • Для существующих строк плагина, которые уже работают на Grav 1, оставьте их на месте. Добавляйте блок ICU: только когда вам действительно нужны возможности форматирования или лучшая поддержка множественного числа в Admin2.
  • Не определяйте автоматически. Формат включается по пространству имён, а не путём анализа значения. Сохраняйте контракт очевидным, чтобы отладка была простой.

Аудит схем

Файлы схем плагинов — крупнейший источник текста интерфейса в любом плагине Grav. Каждое поле label, title, description, help, placeholder и hint в blueprints.yaml (и любом файле в blueprints/) отображается в интерфейсе админки. Чтобы их можно было переводить, эти значения должны быть ссылками на ключи перевода (PLUGIN_FOO.SOMETHING), а не жёстко заданными английскими строками.

Плагин Grav Devtools поставляется с CLI-командой, которая проверяет любой плагин или тему на жёстко заданные значения схем и может переписать их в ключи перевода за вас.

Установка

bin/gpm install devtools

Аудит

Запустите без аргументов внутри сайта Grav, чтобы проверить каждый установленный плагин и тему:

bin/plugin devtools i18n-blueprints

Вывод (сокращённо):

Plugin/Theme                                         Hardcoded   Keys   Ratio
--------------------------------------------------  ----------  -----  ------
admin2                                                       5      0    100%
api                                                         35      0    100%
editor-pro                                                  10      1     91%
flex-objects                                                24      6     80%
ai-pro                                                      40     46     47%
admin                                                        6    192      3%
--------------------------------------------------  ----------  -----  ------
TOTAL                                                      818   1286     39%

Столбец ratio — процент полей label/title/description/help, которые по-прежнему жёстко заданы на английском (красный ≥ 50%, жёлтый ≥ 20%, зелёный — иначе). Чем ниже, тем лучше.

Область сканирования можно ограничить несколькими способами:

# Один плагин или тема
bin/plugin devtools i18n-blueprints user/plugins/my-plugin

# Папка плагинов (например, только плагины, без тем)
bin/plugin devtools i18n-blueprints user/plugins

# Корень конкретной установки Grav
bin/plugin devtools i18n-blueprints /path/to/another/grav-site

Добавьте --details, чтобы перечислить каждое отдельное жёстко заданное значение с файлом и строкой:

bin/plugin devtools i18n-blueprints user/plugins/my-plugin --details
### my-plugin
  blueprints.yaml:38  label: Default for All Users
  blueprints.yaml:49  title: Editor Configuration
  blueprints.yaml:55  label: Toolbar Items
  blueprints.yaml:56  help: Configure which tools appear in the toolbar
  ...

Добавьте --threshold 0.5, чтобы скрыть плагины с долей жёстко заданных строк ниже 50% — полезно при аудите всей установки, когда нужно увидеть только худшие случаи.

Автоисправление с --fix

Флаг --fix атомарно делает две вещи:

  1. Переписывает YAML схемы на месте, заменяя каждое жёстко заданное значение сгенерированным ключом перевода (например, label: Default for All Users → label: PLUGIN_MYPLUGIN.DEFAULT_FOR_ALL_USERS).
  2. Обновляет языковой файл плагина, добавляя новые записи KEY: "Original string" в соответствующий блок с префиксом.

Обновление языкового файла учитывает стиль хранения, который уже использует плагин:

Существующий файл Поведение
languages.yaml Добавить записи в существующий блок en: → PLUGIN_FOO:
languages/en.yaml Добавить записи в существующий блок верхнего уровня PLUGIN_FOO:
Ни одного Создать languages/en.yaml с нуля с новым блоком

Если блока с префиксом ещё нет в файле, недостающая структура создаётся на месте — команда никогда не создаёт дублирующиеся ключи верхнего уровня.

Важно

--fix изменяет ваши файлы схем и языковой файл. Всегда сначала запускайте с --dry-run, чтобы просмотреть изменения, и убедитесь, что работа закоммичена в git, чтобы откат был на расстоянии одной команды, если что-то выглядит неправильно.

# Предварительный просмотр — файлы не изменяются
bin/plugin devtools i18n-blueprints user/plugins/my-plugin --fix --dry-run

# Применить — перезаписывает схемы И обновляет языковой файл плагина
bin/plugin devtools i18n-blueprints user/plugins/my-plugin --fix

Пример вывода:

=== i18n blueprint fix ===

### my-plugin  (prefix: PLUGIN_MYPLUGIN, lang file: languages/en.yaml [per-locale-file])
Blueprint changes: 1 file(s), 4 line(s)
  blueprints.yaml  (4 lines)
Lang file changes: Append to languages/en.yaml (+4 entries)
# Lang file additions:
PLUGIN_MYPLUGIN:
  DEFAULT_FOR_ALL_USERS: "Default for All Users"
  EDITOR_CONFIGURATION: "Editor Configuration"
  TOOLBAR_ITEMS: "Toolbar Items"
  CONFIGURE_WHICH_TOOLS_APPEAR_IN_THE_TOOLBAR: "Configure which tools appear in the toolbar"

После запуска --fix ваш blueprints.yaml выглядит так:

default_for_all:
  type: toggle
  label: PLUGIN_MYPLUGIN.DEFAULT_FOR_ALL_USERS

editor_section:
  type: section
  title: PLUGIN_MYPLUGIN.EDITOR_CONFIGURATION

…а четыре новые записи добавляются под PLUGIN_MYPLUGIN: в languages/en.yaml. Админка сразу разрешит их через обычную цепочку переводов Grav. Повторный запуск --fix идемпотентен — уже существующие ключи (с тем же значением) пропускаются.

Интерактивная проверка с --interactive

Для более осторожных сценариев добавьте --interactive (или -i), чтобы просмотреть каждое жёстко заданное значение отдельно перед преобразованием. Команда останавливается на каждой находке, показывает файл/строку/поле/значение и ждёт одно нажатие клавиши:

bin/plugin devtools i18n-blueprints user/plugins/my-plugin --fix --interactive
### my-plugin  (4 hardcoded values to review)

  [1/4] blueprints.yaml:5  description:
     Provides webhook and health check endpoints for Grav's Modern Scheduler
   Translate? [Y/n/a/s/q/?]: y

  [2/4] blueprints.yaml:35  label:
     Enable CORS
   Translate? [Y/n/a/s/q/?]: n

  [3/4] blueprints.yaml:36  help:
     Allow cross-origin requests to webhook endpoints
   Translate? [Y/n/a/s/q/?]: a

  3 accepted, 1 skipped

Доступные действия:

Клавиша Действие
y Да — перевести это значение (по умолчанию)
n Нет — оставить жёстко заданным
a Да для всех оставшихся в этом плагине
s Пропустить все оставшиеся в этом плагине
q Полностью выйти из этого плагина (не применять изменения)
? Показать эту справку

При аудите нескольких плагинов q завершает только текущий плагин — следующий плагин всё равно запросит подтверждение заново.

Фильтрация значений, похожих на данные

Классификатор намеренно пропускает несколько шаблонов, похожих на данные, а не на текст для пользователя — они всё равно учитываются в итогах как identifier, но не отображаются как жёстко заданные:

  • URL (https://example.com, mailto:a@b.com)
  • адреса электронной почты (user@example.com)
  • имена файлов (logo.svg, theme.css, app.js, …)
  • строки в формате телефона (+1 (555) 123-4567)
  • маркеры API-ключей с последовательностями xxxx
  • списки CSS-классов (несколько токенов в нижнем регистре через дефис, без заглавных букв)
  • только пунктуация/символы (===, ---)

Значение пропускается, только если вся строка соответствует непереводимому шаблону. Предложения, которые содержат URL или имя файла («For example: myimage.jpg»), остаются переводимыми.

Как выбираются ключи и префиксы

Команда выбирает префикс плагина в таком порядке:

  1. Если у плагина есть существующий languages/en.yaml, используется любой найденный там ключ верхнего уровня PLUGIN_* (или THEME_*). Это сохраняет уже установленное соглашение.
  2. Иначе он выводится из имени папки — editor-pro → PLUGIN_EDITOR_PRO, quark2 → THEME_QUARK2. Выбор PLUGIN_ или THEME_ зависит от того, находится ли путь в user/plugins/ или user/themes/.

Каждое уникальное жёстко заданное значение получает ключ, производный от содержимого ("Default for All Users" → DEFAULT_FOR_ALL_USERS). Длинные значения обрезаются по последней границе подчёркивания и по умолчанию ограничиваются 40 символами. Переопределите с помощью --key-length=N (минимум 10), если нужны более длинные или короткие ключи:

bin/plugin devtools i18n-blueprints user/plugins/foo --fix --key-length=30

Если два разных значения дают одинаковый ключ, второе получает суффикс _2 (и так далее). Существующие ключи в языковом файле используются повторно, когда совпадают и ключ, и его значение — поэтому повторный запуск --fix после редактирования переводов идемпотентен.

Настройка Admin2 с --icu

По умолчанию сгенерированные ключи попадают под устаревший префикс верхнего уровня (PLUGIN_FOO:), поэтому работают и в админке Grav 1, и в Grav 2. Для плагинов, поддерживающих только Grav 2.0 / Admin2, добавьте --icu, чтобы выводить их в блок ICU. и получить доступ к фичам ICU MessageFormat:

bin/plugin devtools i18n-blueprints user/plugins/my-plugin --fix --icu

Тогда вывод выглядит так:

ICU:
  PLUGIN_MYPLUGIN:
    DEFAULT_FOR_ALL_USERS: "Default for All Users"
    EDITOR_CONFIGURATION: "Editor Configuration"

Совет

Если ваш плагин должен поддерживать и Grav 1.7, и Grav 2.0 в одном релизе, сначала запустите без --icu (чтобы ключи остались в устаревшем блоке, где их найдёт Grav 1), а затем вручную добавьте параллельный блок ICU: для ключей, которые хотите обогатить подстановками или правилами множественного числа.

Вывод JSON

Для интеграции с инструментами --json выводит машиночитаемую сводку вместо таблицы:

bin/plugin devtools i18n-blueprints --json

Предыстория

Эта схема была введена для Grav 2.0 в ответ на давние запросы сообщества о полноценной поддержке множественного числа и контекста в переводах админки (getgrav/grav#4064, на основе #2947). Словарная структура существующей системы Grav 1 работала достаточно хорошо для английского, но вынуждала к неестественным переводам во многих других языках. Пространство имён ICU. позволяет экосистеме двигаться вперёд в своём темпе, плагин за плагином, не ломая того, что уже работает.