| Option | Type | Default | Description |
|---|---|---|---|
default_locale |
string|null |
null |
When set, used as the default source locale for fill-missing. When null, the bundle uses Symfony parameter translator.default_locale if defined, then kernel.default_locale, then en. On some Symfony versions (e.g. 8.x), only kernel.default_locale may be available. |
yaml_tree_indent |
int |
4 |
Spaces per indentation level when writing nested YAML (tree, sort, fill-missing). Allowed range: 2–12. |
yaml_tree_leaf_prefix_suffix |
string |
index |
Final segment appended to a conflicting leaf when using nowo:translation-yaml:tree --fix-leaf-prefix (e.g. key a becomes a.index). Single segment: [a-zA-Z0-9_-]+, no dots. |
machine_translator |
string |
google |
Default backend for fill-missing when machine_translator_by_locale does not match: google, deepl, or libretranslate. |
machine_translation_min_interval_ms |
int |
0 |
Minimum delay between MT HTTP calls (0 = no pacing). Use e.g. 100–250 for public LibreTranslate quotas. |
machine_translation_max_requests_per_run |
int |
0 |
Max string translations per fill-missing run (0 = unlimited). |
machine_translation_http_timeout |
float |
30 |
HTTP timeout (seconds) for Google / DeepL / LibreTranslate requests. |
machine_translator_by_locale |
array<string, string> |
{} |
Override the backend per Symfony locale (google, deepl, or libretranslate). The target locale is checked first, then the source locale, then machine_translator. Keys match like machine_translation_locale_map (pt_BR ≡ pt-br). |
deepl_endpoint |
string |
https://api.deepl.com/v2/translate |
DeepL translate URL. Use https://api-free.deepl.com/v2/translate if your key is on the Free plan. |
libretranslate_base_url |
string |
https://libretranslate.com |
Origin of the LibreTranslate server (no /translate path). Host must appear in libretranslate_allowed_hosts. Prefer self-hosting for reliability; the public demo enforces strict limits. |
libretranslate_api_key |
string |
'' |
Optional API key when your LibreTranslate instance requires one; leave empty for open public endpoints. |
libretranslate_allowed_hosts |
list<string> |
[libretranslate.com] |
Hostname allowlist for libretranslate_base_url (SSRF mitigation). Subdomains of a listed host are allowed. |
libretranslate_allow_http |
bool |
false |
When true, allows http:// LibreTranslate URLs (local/dev only). Default is HTTPS-only. |
machine_translation_locale_map |
array<string, string> |
{} |
Map Symfony locale identifiers to the exact source / target language code sent to the active backend (Google, DeepL, LibreTranslate). Keys are matched after normalizing case and - / _ (e.g. pt_BR, pt-br, PT_BR share one entry). Values are sent as-is—use the format your provider expects (e.g. LibreTranslate pt-br, DeepL PT-BR, Google pt-BR). |
missing_translation_log |
array |
see below | Runtime missing-key log (Doctrine). Use missing_translation_log: false to disable the subtree explicitly. |
missing_translation_log.enabled |
bool |
false |
When true, decorates translator, records missing keys per request, flushes on kernel.terminate. In the usual index.php flow, terminate runs after $response->send(), so the client has already received the response body; the process may still run DB or Messenger work during terminate. Requires doctrine/orm and doctrine/doctrine-bundle; the bundle prepends ORM mapping for MissingTranslationLog. |
missing_translation_log.table_prefix |
string |
nowo_translation_ |
Physical table name = {table_prefix}missing_log (only [a-z0-9_], max 40 chars). Default → nowo_translation_missing_log. |
missing_translation_log.record_call_site |
bool |
true |
When true, each flush stores call_site (absolute file:line of the first plausible caller from debug_backtrace, excluding Symfony translation/Twig bridge internals). New hits can refresh call_site. Set false to avoid backtrace overhead on hot trans() paths. |
missing_translation_log.record_request_context |
bool |
true |
When true and an HTTP Request exists, the row also stores request_route (from _route when set), request_method, and request_path (getPathInfo() when non-empty). CLI / workers have no request, so those columns stay NULL. Set false if you do not want route or path persisted (privacy). |
missing_translation_log.async_persist |
bool |
false |
When true, flush uses async_persist_strategy instead of calling persistBuffer directly in the recorder. If the chosen strategy is unavailable (no bus, no event_dispatcher), the recorder falls back to synchronous persistBuffer. |
missing_translation_log.async_persist_strategy |
string |
messenger |
Only when async_persist is true: messenger dispatches MissingTranslationBufferMessage (needs symfony/messenger + messenger.default_bus; route the message to an async transport for workers). event_dispatcher dispatches MissingTranslationBufferEvent on the app event_dispatcher; the bundle registers MissingTranslationBufferDoctrinePersistListener at priority -1024 to call persistBuffer unless a listener with higher priority called stopPropagation() (use that to enqueue the buffer and persist later without Messenger). |
missing_translation_log.web_ui.enabled |
bool |
false |
When true (and the log is enabled), registers MissingTranslationLogUiController and runs TwigPathsPass: if templates/bundles/NowoTranslationYamlToolsBundle/ exists, that path is prepended on the native Twig loader with namespace NowoTranslationYamlToolsBundle, then the bundle src/Resources/views path is appended with the same namespace so overrides win without twig.paths (see USAGE). Requires symfony/twig-bundle. Also requires symfony/security-bundle unless allow_unauthenticated: true. |
missing_translation_log.web_ui.path_prefix |
string |
/_translation_yaml_tools/missing-log |
URL prefix for the routes you import (must start with /). Use a trailing slash on the list URL if your router enforces strict trailing slashes. |
missing_translation_log.web_ui.layout_template |
string |
@NowoTranslationYamlToolsBundle/missing_translation_log/layout.html.twig |
Twig layout extended by missing_translation_log/base.html.twig. Exposed as Twig global nowo_translation_yaml_tools_missing_log_layout_template. Use .../layout_integrate_dashboard_menu.html.twig or .../layout_integrate_breadcrumb_kit.html.twig to align with those dashboards. |
missing_translation_log.web_ui.required_role |
string|null |
ROLE_ADMIN |
When the Web UI is enabled and security.authorization_checker is available, MissingLogUiAccessSubscriber requires this role for routes named nowo_translation_yaml_tools_missing_log_*. Set null to skip bundle-level checks (rely on your firewall / access_control only). |
missing_translation_log.web_ui.allow_unauthenticated |
bool |
false |
Dev/demo only. When false (default), enabling the Web UI without security.authorization_checker fails container compilation. Set true only for local demos/tests that intentionally omit SecurityBundle — never in production. |
Example:
nowo_translation_yaml_tools:
default_locale: null
yaml_tree_indent: 4
yaml_tree_leaf_prefix_suffix: index
machine_translator: google
deepl_endpoint: 'https://api.deepl.com/v2/translate'
libretranslate_base_url: 'https://libretranslate.com'
libretranslate_api_key: ''
# machine_translation_locale_map:
# pt_BR: 'pt-br' # Symfony locale => API code (see table above)
# machine_translator_by_locale:
# pt_BR: libretranslate # use LibreTranslate when translating to/from this locale (see below)
# missing_translation_log:
# enabled: true
# table_prefix: nowo_translation_
# web_ui:
# enabled: true
# path_prefix: '/_translation_yaml_tools/missing-log'
# # layout_template: '@NowoTranslationYamlToolsBundle/missing_translation_log/layout_integrate_dashboard_menu.html.twig'- Install
doctrine/ormanddoctrine/doctrine-bundlein your application (the bundle lists them undersuggest). - Enable the feature and optional table prefix / web UI:
nowo_translation_yaml_tools:
missing_translation_log:
enabled: true
table_prefix: nowo_translation_ # table: nowo_translation_missing_log
web_ui:
enabled: true
path_prefix: '/_translation_yaml_tools/missing-log'
required_role: ROLE_ADMIN
# allow_unauthenticated: false # default; never true in production-
Create the table (migrations /
doctrine:schema:updatein dev only). Entity:Nowo\TranslationYamlToolsBundle\Entity\MissingTranslationLog; table name follows{table_prefix}missing_log. The bundle metadata adds a unique constraint onmessage_id,domain, andlocale. From 0.3.2 onward,persistBufferusesINSERTand, if that row already exists,UPDATEshit_count,last_seen_at, and optional context columns (call_site,request_route,request_method,request_path), so concurrent flushes do not raise duplicate-key SQL errors. From 0.3.3 onward, those statements resolve physical column names from Doctrine class metadata (so ORM field names such ashitCountmap correctly when the naming strategy does not simply snake-case them). -
Import HTTP routes (not automatic) with the same prefix as
web_ui.path_prefix:
# config/routes/nowo_translation_yaml_tools_ui.yaml
nowo_translation_yaml_tools_missing_log_ui:
resource: '@NowoTranslationYamlToolsBundle/Resources/config/routes/missing_translation_log_ui.yaml'
type: yaml
prefix: '%nowo_translation_yaml_tools.missing_translation_log.web_ui.path_prefix%'The Flex recipe adds this under config/routes/dev/ when you install the bundle (dev only).
- Framework (for the Twig UI and CSRF on “Mark added”):
framework:
csrf_protection: trueAlso install symfony/security-csrf (and enable session if you do not already) so the csrf_token() Twig function is available.
- SecurityBundle (required for the Web UI in real apps): install
symfony/security-bundle. Without it, container compilation fails unless you explicitly setallow_unauthenticated: true(demos/tests only). Recommendedaccess_control:
# config/packages/security.yaml (fragment)
security:
access_control:
- { path: ^/_translation_yaml_tools, roles: ROLE_ADMIN }Adjust the path to match web_ui.path_prefix. Bundle-level required_role (default ROLE_ADMIN) is enforced by MissingLogUiAccessSubscriber in addition to your firewall.
-
Optional deferred flush (the response is usually already sent before
kernel.terminate; this further decouples how you persist):async_persist_strategy: messenger(default whenasync_persist: true): installsymfony/messenger, routeMissingTranslationBufferMessageto an async transport; the bundle registersPersistMissingTranslationBufferMessageHandlerwhen Messenger is available.async_persist_strategy: event_dispatcher: no Messenger required. Setasync_persist: trueandasync_persist_strategy: event_dispatcher. Subscribe toMissingTranslationBufferEvent; to only enqueue and persist in your own worker, call$event->stopPropagation()after enqueueing so the builtin Doctrine listener (priority -1024) does not run.
-
Routes (fixed names):
nowo_translation_yaml_tools_missing_log_index(GET list +?status=pending|added|validated),nowo_translation_yaml_tools_missing_log_mark_added(POST, CSRF idmissing_log_mark_added). -
Console (registered only when the log feature is enabled):
nowo:translation-yaml:missing-log-list—--status=pending|added|validated(defaultpending),--limit=…nowo:translation-yaml:missing-log-mark-added <id>— mark a row as added (string present in YAML)nowo:translation-yaml:missing-log-validate <id>— mark as validated; optional--note=
Row fields include message_id (max 500 chars), domain, locale, status, hit_count, call_site (nullable backtrace file:line, max 1024 chars), request_route (nullable, max 180), request_method (nullable, max 8), request_path (nullable pathInfo, max 2048), first_seen_at, last_seen_at, status_changed_at, notes.
The log records runtime TranslatorInterface::trans() lookups that miss the catalogue for the requested locale; it is not a full inventory of every untranslated string in the app, and call_site from Twig often points at compiled Twig under var/cache, not the .twig source line. With record_request_context (default true), HTTP rows also get request_route, request_method, and request_path. See USAGE — Missing translation log: coverage and call_site.
machine_translator_by_locale chooses which service (Google vs DeepL vs LibreTranslate) runs for a given Symfony locale. Resolution order for each translate call: target locale → source locale → machine_translator.
All three backends are always registered; ensure the env vars / keys for backends you might route to are set (e.g. DEEPL_AUTH_KEY if any locale uses deepl).
Use machine_translation_locale_map when Symfony’s locale naming does not match what your provider expects. The built-in translators apply their default normalization only for locales not listed in the map.
If two YAML keys normalize to the same canonical key (e.g. pt_BR and pt-br), the last processed entry wins.
fill-missing uses Symfony’s HttpClientInterface. Enable the Framework HTTP client in your app (as in the demos):
framework:
http_client: trueThe bundle discovers YAML files under:
translator.default_path(FrameworkBundle parameter) when defined.- Otherwise
%kernel.project_dir%/translations. - Plus
framework.translator.default_pathandframework.translator.pathsentries parsed fromconfig/packages/**/translation.yaml(or.yml).
Absolute paths in YAML are resolved with the container parameter bag (e.g. %kernel.project_dir%).
- Env var:
GOOGLE_TRANSLATE_API_KEY(required whenmachine_translatorormachine_translator_by_localeroutes togoogle). - API: Translation API v2.
- Do not commit real keys; use
.env.localor your secret manager.
- Env var:
DEEPL_AUTH_KEY(required whenmachine_translatorormachine_translator_by_localeroutes todeepl). - Set
deepl_endpointto the Free API host if your key is not valid on the Pro endpoint (see table above). - API: Translate text.
- No Google/DeepL key is required when the active backend is LibreTranslate only (
machine_translator: libretranslateormachine_translator_by_localemapping tolibretranslate) with the default public URL and an emptylibretranslate_api_key. - Public servers apply strict rate limits and may return errors when busy; for repeated or bulk
fill-missingruns, host your own LibreTranslate and pointlibretranslate_base_urlat it. - Set
libretranslate_api_keyif your instance is configured to require a key. - API: LibreTranslate API.
By default, MachineTranslatorInterface points to RoutingMachineTranslator, which delegates to the three built-in services. Register your own implementation in application DI and alias MachineTranslatorInterface to it to bypass routing and bundle backends entirely (that alias overrides the bundle default). machine_translator, machine_translator_by_locale, and machine_translation_locale_map then no longer apply unless your code uses them explicitly.