pierrejeambrun opened a new pull request, #72648:
URL: https://github.com/apache/airflow/pull/72648
Plugins can now contribute UI translations, letting a deployment localize
the Airflow UI into a language it does not ship, or override specific strings,
without forking the frontend.
A plugin declares a `ui_translations` list whose entries are either a
`<language>/<namespace>.json` directory tree or an inline `{language:
{namespace: {key: value}}}` mapping (the two can be mixed). Each entry is
deep-merged over the built-in translations, so an override replaces only the
keys it names — at any nesting depth — and everything else falls back to the
built-in language, and ultimately English.
```python
from pathlib import Path
from airflow.plugins_manager import AirflowPlugin
class TranslationsPlugin(AirflowPlugin):
name = "translations"
ui_translations = [
Path(__file__).parent / "locales", #
<language>/<namespace>.json tree
{"en": {"dags": {"dag_one": "Pipeline"}}}, # inline override of
specific keys
]
```
### Robustness (translations are not versioned in lockstep with Airflow)
- A broken translation source (unreadable file, malformed JSON, a bad
`ui_translations` value) is skipped with a warning. It never stops the API
server from starting or keeps other plugins from loading.
- English is the reference for which keys exist. At consolidation time, a
plugin key that is not present in the English file for its namespace is logged
as a warning (it was likely renamed or removed upstream) and otherwise ignored.
### Right-to-left languages
Text direction is derived from the locale (the browser's CLDR data), so a
custom RTL language flips the whole UI to right-to-left automatically, with no
extra configuration.
### How it works
- **Backend:** a route serves each locale file with the plugin overrides
deep-merged in. It stays unauthenticated because translations must load before
login (the login page is localized too), and is only registered when a plugin
actually contributes translations, so deployments without such a plugin keep
serving the bundled files straight from the static mount. A small manifest
route lists the plugin languages.
- **Frontend:** the extra languages extend i18next's `supportedLngs` and
appear in the language selector (names resolved via `Intl.DisplayNames`).
### Screenshots
Demonstrated with a local plugin adding **Esperanto** (LTR, from a directory
tree), **Persian** (RTL, from a directory tree), and **English** overrides
(inline Python dict).
Inline Python override — the Admin menu shows "Pools!" instead of "Pools":

Custom LTR language (Esperanto) from full files — the Admin menu shows
Naĝejoj / Variabloj, with English fallback for untranslated items:

Custom RTL language (Persian) from full files — the whole UI flips to
right-to-left:

The language selector lists the plugin languages (Esperanto, فارسی)
alongside the built-ins:

---
##### Was generative AI tooling used to co-author this PR?
- [X] Yes — Claude Code (Opus 4.8)
Generated-by: Claude Code (Opus 4.8) following [the
guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]