— Измени надпись на кнопке.
— Опять?
— Да, опять. Измени на «Подтвердить».
— Но мы уже делали это на прошлой неделе!
— Да, за последние несколько дней клиент просил изменить кнопку «отправить» несколько раз , а сегодня попросил «отменить все это обратно».

Ситуация в эпиграфе не типичная, но я наблюдал ее по крайней мере несколько раз. Не так уж это и сложно — измените перевод ключа в yaml/xlffile, увеличьте номер версии, зафиксируйте изменения, отправьте его на CI / CD-сервер и вуаля! Новый перевод! Но я думаю, что это слишком много действий, чтобы заменить «Подтвердить» на «Отправить» (и, возможно, вернуть «Подтвердить» на следующей неделе).

Платформа Symfony предлагает прекрасный способ хранения сообщений i18n: вы можете хранить его в простом файле yaml как key: value, в xlf-файле, который можно обновлять в специальных приложениях, po/mo-файлах для gettext-движка и так далее — но все это не предполагает редактирования сообщений в интерфейсе администрирования веб-сайта, и обычно хранится в исходном коде. И естественно, что исходный код (код приложения) не может быть отредактирован пользователем (или даже администратором).

С точки зрения хранения и редактирования лучший способ — хранить сообщения в месте для динамического пользовательского контента, тоесть в базе данных. Конечно это повлияет на производительность приложения, но не сильно — если вы не поддерживаете действительно большое приложение, это не будет иметь значения. Компонент Symfony Translator может быть расширен и работать с любыми источниками — включая базу данных, разумеется.

Для этого нужно создать класс, который реализует Symfony\Component\Translation\Loader\LoaderInterface, добавить его конфигурацию в контейнер и все — на первый взгляд.
Но такая конфигурация не заработает — Symfony ищет переводы в файлах, а наличие файла является сигналом «для этого translation-домена существует перевод». Поэтому, чтобы транслятор (лоадер) был загружен, необходимо создать «фейковые», пустые файлы перевода с расширениями из вашей конфигурации.
Например, класс загрузчика:

Класс DbLoader реализует LoaderInterface:

И конфигурация:

В этой конфигурации у нас есть тег с alias: db, это значит, что symfony будет искать файлы с расширением db в каталоге с переводами. То есть нужно создать <domain>.<locale>.db-файл в translation-каталоге, чтобы запустить загрузчик. messages.en_US.db, например.

Сущность — это простая стандартная сущность Doctrine:

а репозиторий имеет вспомогательные функции

Благодаря такой конструкции, переводы терминов приложения будут храниться в базе данных в виде простой key-value таблицы.
Вы можете добавить свою собственную (удобную для вас) форму, контроллер и view-файлы для редактирования сообщений.

Я сделал простой бандл для использования в своих проектах — пожалуйста, не стесняйтесь использовать / форкать его.
Обратите внимание — интерфейс для редактирования не включен в этот пакет, предполагается, что он будет разрабатываться в каждом отдельном случае свой.

Думаю, это несложно.