Skip to content

feat(i18n): translatable UI in 12 languages with browser language detection - #9

Merged
Blackwellcode merged 5 commits into
masterfrom
feat/i18n
Nov 3, 2026
Merged

Blackwellcode merged 5 commits into
masterfrom
feat/i18n

Conversation

@Blackwellcode

Copy link
Copy Markdown
Owner

Summary

The web UI is now translatable and follows the browser language.

  • 12 UI languages: English (source), Deutsch, Français, Español, Italiano, Português (Brasil), Nederlands, Polski, Türkçe, 日本語, 简体中文, 한국어.
  • Language selection:
    • Order: sv_lang cookie, then the best Accept-Language match (a region falls back to its base language, e.g. de-AT → de), then English.
    • There is a switcher on the login page and in the sidebar. The "Automatic" option clears the cookie.
    • The new endpoint POST /language only redirects to a safe next URL.
  • Legal texts: terms, privacy policy and house rules are available in German (binding) and English.
    • The English version is used only when it carries the same version number as the German one, so switching languages never forces users to accept again.
    • Every other UI language falls back to English.
    • The rules acceptance accepts both AKZEPTIERE and ACCEPT.
  • Implementation:
    • Flask-Babel 4 (gettext) with English source strings: 1,083 messages, translated to 100 % in all 11 target languages.
    • JavaScript strings are served to the client as a small catalog (window.SV_I18N, svT()).
    • The compiled .mo files are committed.
  • CI: scripts/check_translations.py checks placeholders, %% escapes and HTML tags in every locale, and CI verifies that the compiled catalogs are up to date.
  • Layout fix: .task-queue-stats now wraps for long translations on phones.

Operator notes

  • New dependency: Flask-Babel>=4.0.0,<5.
  • No new environment variables and no database migrations.

Known limitations

  • Only the German and English texts are reviewed. The other translations have had no native-speaker review, which the README states.
  • Messages from background threads are always in English.
  • Some German values stay on purpose: values the app compares as keys or stores in the database, such as stream-title sentinels, storage last_error texts and the default group descriptions.
  • Numbers are not yet formatted per locale.

Test plan

  • python -m unittest discover -s tests: 174 tests, OK (skipped=1)
  • ruff check .
  • python scripts/check_translations.py: 0 issues
  • Render smoke test: 12 languages × 90 requests, no server errors, correct <html lang>
  • Browser layout scan: 240 renders (12 languages × 2 widths × 10 pages) for container and text overflow; the one finding is fixed
  • CI on 3.10 / 3.11 / 3.12

🤖 Generated with Claude Code

Pierre and others added 5 commits November 3, 2026 19:00
- Flask-Babel with English as the source language
- language per request: chosen language (sv_lang cookie), else the
  best Accept-Language match (regional tags fall back to the base
  language), else English; 12 supported locales
- POST /language stores the choice ("Automatic" clears it), with a
  safe same-site redirect back
- reusable language switcher; <html lang> follows the locale
- JS strings: N_() marks them for extraction, components/js_i18n.html
  exposes window.svT() backed by the current catalog
- login page converted as the reference pattern
- babel.cfg for pybabel extraction

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The German documents stay legally binding. German UI shows them; every
other UI language shows the English translation (content/*.en.md and
tube_rules.RULES_EN). A translation is only used when it carries the
same termsVersion/privacyVersion as the original, so the version users
accept never depends on their language. Discord output and version
checks keep using the German original.

The consent phrase is shown per language (AKZEPTIERE / ACCEPT); either
is accepted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All user-visible text now goes through gettext with English msgids:
36 templates (text, attributes, inline script strings via |tojson),
344 Python messages (gettext at request time, lazy_gettext for module
constants), and 52 static JS strings (svT() + JS_MESSAGES). The German
catalog restores the previous UI wording: 1083/1083 messages.

- WEBUI/translations: messages.pot plus compiled de catalog (.mo is
  committed so installs need no build step)
- scripts/check_translations.py: placeholders must match the source and
  literal % must be %% (Flask-Babel formats every message)
- mobile VOD page counter is found via data-vod-page-counter instead of
  matching the German word "Seite"
- values compared as keys or stored in the database (stream-title
  sentinel, clip_engine status checks, seeded group descriptions,
  storage last_error texts asserted by tests) intentionally stay German
- tests: the storage page test now requests Accept-Language: de, so its
  German assertions (incl. assertNotIn) keep their meaning; no assertion
  was changed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
French, Spanish, Italian, Brazilian Portuguese, Dutch, Polish, Turkish,
Japanese, Simplified Chinese and Korean, 1083/1083 messages each,
validated for matching placeholders, escaped percent signs, HTML tags
and the locale's plural form count. These translations have not been
reviewed by native speakers yet; the README says so and CONTRIBUTING
explains how to correct them.

CI runs scripts/check_translations.py and fails when the committed
.mo files do not match their .po sources.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The admin task queue used four fixed columns (about 60px each on a
phone). Spanish, Italian, Portuguese and Polish labels pushed the
counts out of their chips. The grid now fits as many 96px+ columns as
there is room for: one row on desktop, two on phones.

Found by a layout scan of 10 pages x 2 widths in all 12 languages
(240 renders); after this fix it reports no clipped content.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Blackwellcode
Blackwellcode merged commit 2671124 into master Nov 3, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant