feat(i18n): data-driven language picker + Weblate translation guard #5

Merged
makiolaj merged 4 commits from feat/translations into main 2026-06-18 08:41:47 +00:00
Owner

Enables community-driven translations end to end.

App

  • New res/xml/locales_config.xml (en, de) as the single source of truth for shipped languages, wired via android:localeConfig → also gives the Android 13+ per-app language entry in system settings.
  • AppLanguage is now data-driven: it parses locales_config.xml for supported BCP-47 tags and renders autonyms ("Deutsch", "English", …); the hardcoded LanguagePref enum is gone and the Settings picker is built from that list.
  • Removed the now-unused settings_language_german/english strings.

Adding a language is now just: drop in values-<tag>/strings.xml + add one <locale> line.

CI

  • scripts/check_translations.py + a lightweight Translations workflow (no Android SDK) for fast feedback on translation PRs. Fails on stale keys and on translating translatable="false" entries; missing keys are coverage-only.
  • Lint MissingTranslation downgraded to informational (partial translations fall back to English); ExtraTranslation stays fatal.

Verification

  • :app:assembleDebug, :app:testDebugUnitTest, :app:lintDebug all pass locally.
  • Parity script verified on a positive (de 100%) and a negative (stale + fixed-key) case.

⚠️ Not yet reviewed on-device — the Settings → App language picker should be eyeballed on a real device before merge/release.

Paired with the self-hosted Weblate component (calendula/strings, Gitea-PR push) now wired to this repo.

🤖 Generated with Claude Code

Enables community-driven translations end to end. ## App - New `res/xml/locales_config.xml` (en, de) as the **single source of truth** for shipped languages, wired via `android:localeConfig` → also gives the Android 13+ per-app language entry in system settings. - `AppLanguage` is now **data-driven**: it parses `locales_config.xml` for supported BCP-47 tags and renders **autonyms** ("Deutsch", "English", …); the hardcoded `LanguagePref` enum is gone and the Settings picker is built from that list. - Removed the now-unused `settings_language_german/english` strings. **Adding a language** is now just: drop in `values-<tag>/strings.xml` + add one `<locale>` line. ## CI - `scripts/check_translations.py` + a lightweight **Translations** workflow (no Android SDK) for fast feedback on translation PRs. Fails on stale keys and on translating `translatable="false"` entries; missing keys are coverage-only. - Lint `MissingTranslation` downgraded to informational (partial translations fall back to English); `ExtraTranslation` stays fatal. ## Verification - `:app:assembleDebug`, `:app:testDebugUnitTest`, `:app:lintDebug` all pass locally. - Parity script verified on a positive (de 100%) and a negative (stale + fixed-key) case. ⚠️ Not yet reviewed on-device — the Settings → App language picker should be eyeballed on a real device before merge/release. Paired with the self-hosted Weblate component (`calendula/strings`, Gitea-PR push) now wired to this repo. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
makiolaj added 2 commits 2026-06-18 06:43:46 +00:00
Make the supported-language list a single source of truth so community
translations show up with no code change: add res/xml/locales_config.xml
(en, de) and reference it via android:localeConfig, which also surfaces the
per-app language entry in Android 13+ system settings.

Rewrite AppLanguage to parse locales_config.xml for the supported BCP-47
tags and expose currentTag/apply/displayName (autonyms), dropping the
hardcoded LanguagePref enum; the Settings picker is now built from that list.
Remove the now-unused settings_language_german/english strings.

Adding a language is now: drop in values-<tag>/strings.xml and add one
<locale> line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ci(i18n): translation parity guard + allow partial translations
Some checks failed
CI / ci (push) Failing after 17m45s
Translations / check (push) Successful in 7s
cf380b6eab
Add scripts/check_translations.py and a lightweight Translations workflow
that runs it (no Android SDK needed) so Weblate PRs get fast feedback. The
script fails on stale keys (present in a translation but not the base) and on
translating translatable="false" entries; missing keys are reported as
coverage only.

Downgrade lint's MissingTranslation to informational: partial community
translations are expected and fall back to the English base at runtime.
Stale/extra keys (ExtraTranslation) remain fatal in lintDebug.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
makiolaj added 2 commits 2026-06-18 08:34:13 +00:00
The in-app language picker silently did nothing: AppCompatDelegate.set
ApplicationLocales only syncs to the system from an AppCompatActivity, but
MainActivity was a plain ComponentActivity (with a platform theme). Switch
MainActivity to AppCompatActivity and base Theme.Calendula on
Theme.AppCompat.DayNight.NoActionBar.

Changing the locale recreates the activity; set android:windowBackground to a
DayNight colour matching the Compose background (light #FBFCFE / dark #101316)
so the recreation no longer flashes a contrasting backdrop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge remote-tracking branch 'origin/main' into worktree-feat+translations
All checks were successful
Translations / check (push) Successful in 4s
CI / ci (push) Successful in 5m11s
788ca3906e
Author
Owner

Update — on-device review fixes + merge with main

On-device review (Pixel 10, Android 16) surfaced a real bug, now fixed:

  • The picker silently did nothing — AppCompatDelegate.setApplicationLocales only syncs to the system from an AppCompatActivity, but MainActivity was a plain ComponentActivity. Switched it to AppCompatActivity + based Theme.Calendula on Theme.AppCompat.DayNight.NoActionBar. Verified: system per-app locale now flips correctly on tap.
  • Locale change recreates the activity; set android:windowBackground to a DayNight colour matching the Compose background (light #FBFCFE / dark #101316) so the recreation no longer flashes a lighter backdrop.

Merged main: reconciled with the new full-screen picker (ui/common/Picker.kt). The language row now feeds the data-driven tag list + autonyms into main's OptionPicker (the redesigned picker is preserved, not reverted). Parity guard green at 244/244; assembleDebug + unit tests pass on the merged tree.

Still pending: final on-device sign-off on the merged build before merge/release.

### Update — on-device review fixes + merge with main **On-device review (Pixel 10, Android 16) surfaced a real bug, now fixed:** - The picker silently did nothing — `AppCompatDelegate.setApplicationLocales` only syncs to the system from an `AppCompatActivity`, but `MainActivity` was a plain `ComponentActivity`. Switched it to `AppCompatActivity` + based `Theme.Calendula` on `Theme.AppCompat.DayNight.NoActionBar`. Verified: system per-app locale now flips correctly on tap. - Locale change recreates the activity; set `android:windowBackground` to a DayNight colour matching the Compose background (light `#FBFCFE` / dark `#101316`) so the recreation no longer flashes a lighter backdrop. **Merged `main`:** reconciled with the new full-screen picker (`ui/common/Picker.kt`). The language row now feeds the data-driven tag list + autonyms into main's `OptionPicker` (the redesigned picker is preserved, not reverted). Parity guard green at 244/244; `assembleDebug` + unit tests pass on the merged tree. Still pending: final on-device sign-off on the merged build before merge/release.
makiolaj merged commit 7285e274df into main 2026-06-18 08:41:47 +00:00
makiolaj deleted branch feat/translations 2026-06-18 08:41:47 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: makiolaj/calendula#5