Skip to content

Localize the Home experience on Android and iOS - #1490

Open
bhelm wants to merge 11 commits into
ryanbr:mainfrom
bhelm:feature/home-localization-upstream
Open

Localize the Home experience on Android and iOS#1490
bhelm wants to merge 11 commits into
ryanbr:mainfrom
bhelm:feature/home-localization-upstream

Conversation

@bhelm

@bhelm bhelm commented Aug 20, 2026

Copy link
Copy Markdown

Summary

  • localize the Android and iOS Home/Today surfaces, including indirect engine/helper copy, accessibility labels, empty states, provenance, readiness, metrics, workout prompts, and score explanation screens
  • keep analytics models UI-independent by exposing semantic values and resolving localized copy at the UI boundary
  • add locale-aware formatting, plural handling, and focused regression coverage for the Home localization closure
  • align the German score glossary around Energie, Belastung, and Erholung across the Home and explanation copy
  • improve Android's localized score-bubble and section-header layouts so longer German labels fit without clipping or unnecessary wrapping

Translation notes

Android in German was manually tested, including the adjusted Home layout. The additional locale content was translated automatically and is covered by catalog completeness, placeholder, echo, and semantic regression checks. Native-speaker corrections are welcome; having complete localized surfaces should make contextual issues easier to spot and report than the previous English fallbacks.

Validation

  • python3 Tools/test_home_i18n.py — 27 tests pass
  • python3 Tools/test_i18n_audit.py — 42 tests pass
  • python3 Tools/i18n_audit.py --ci origin/main — pass
  • ./gradlew :app:assembleFullDebug --no-parallel --max-workers=1 --no-daemon — pass
  • Android German Home flow and layout manually tested
  • two independent code/guideline reviews completed; findings resolved and delta-reviewed

Platform note

The Apple catalog and semantic localization paths are covered by the focused automated tests and audits. An iOS/macOS Xcode app build could not be run on the Linux host used for this work and still needs CI or macOS verification.

signal-2026-08-20-14-16-16-116 signal-2026-08-20-11-40-01-997_003

@ryanbr

ryanbr commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Gave this its own pass rather than folding it into the smaller ones. Mechanically it's in good shape — the
part I'd want fixed is one missing catalogue key, and the reason it's invisible.

What I verified as safe

  • Localizable.xcstrings: 3,562 → 3,598 keys, zero removed, and no entry lost a locale. The +527/-186
    line churn is entry rewrites, not lost translations.
  • Android: 273 new keys present in all seven locales. No new drift; values-zh actually improves
    (43 → 42 missing vs default). Both remaining gaps predate this.
  • lintVitalFullRelease -PstagingRelease clean — the fatal ExtraTranslation/MissingTranslation gate
    that plain compiles miss. i18n_audit --ci, doc_comment_lint, and 4,151 Android tests all green.
  • The semantic-enum refactor on IllnessSignalEngine and ReadinessEngine is additive — the English
    copy/suppressedBy payloads stay for existing callers, and IllnessSignal isn't persisted or carried
    in .noopbak, so the new Codable enums aren't a wire format.

The one live bug: a verdict that will render in English

ChargeDrivers.swift can emit this, and it is not in Localizable.xcstrings:

below baseline, limiting recovery, though low resting HR suggests this may be parasympathetic saturation rather than fatigue

I enumerated all 13 verdicts the engine can produce; 12 are present with German, this one isn't.

It's invisible because of how iOS localizes here — ChargeBreakdownFormat.swift:70:

let verdict = String(localized: String.LocalizationValue(d.verdict))

The engine's English string is the lookup key, resolved at runtime. A missing key doesn't fail; it
returns the English. So a German user who hits the HRV-saturation branch gets one English sentence inside
otherwise-German copy, and nothing reports it — not the compiler, not i18n_audit, not lintVital, not
the 28 new test_home_i18n.py tests. Every check on this PR is green with the hole in place.

Naturally it's the longest, rarest branch that's missing, which is exactly what manual catalogue upkeep
drops.

Why the two halves differ in robustness

Android iOS
mechanism typed enums → when English string → runtime key lookup
a new or reworded verdict compile error silent English fallback

RecoveryDrivers.kt moves to ChargeDriverLabel / ChargeDriverUnit / ChargeDriverVerdict;
ChargeDrivers.swift is untouched and still emits English. That's a legitimate design choice per platform,
but it means the iOS side has no compile-time safety by construction — and the PR removes the doc line
calling ChargeDriverRow a "SHARED CONTRACT… field names byte-identical across platforms", which is
honest, but leaves the divergence undocumented.

What I'd ask for

  1. Add the missing key with its German. One-line catalogue fix for a live gap.
  2. A test that enumerates every verdict ChargeDrivers.swift can emit and asserts each has a catalogue
    entry.
    That turns a silent runtime fallback into a build failure, and it's the only thing that stops
    this recurring — I found this one by writing that check ad hoc, and it should live in the repo.
  3. Optionally a note in ChargeDrivers.swift that its literals are localization keys, so the next person
    rewording a verdict knows the catalogue has to move with it.

Two things that are yours to decide, not defects

  • 126 existing German strings are rewritten (plus 14 pt-PT and a handful elsewhere) — the
    Ladungswert → Energiewert, Anstrengung → Belastung glossary realignment. That's a product-voice call
    about shipped copy; I can't evaluate German and neither can any gate here. Worth a German speaker's eye,
    and arguably separable from the coverage work.
  • Tools/test_home_i18n.py isn't wired into CI. 588 lines, 28 tests, passes locally, nothing runs it.
    It'll rot within a release or two.

Also flagging that I can't verify the Android layout changes for longer German labels — that needs a device.

@bhelm

bhelm commented Aug 21, 2026

Copy link
Copy Markdown
Author

Thanks for the detailed pass — the missing verdict and the silent runtime-key failure mode are both valid findings.

Fixed in 60a2ec05:

  • added the missing parasympathetic-saturation verdict to the Strand catalog for all currently maintained catalog locales
  • added a regression test that extracts every verdict return path from ChargeDrivers.swift (13 paths / 12 unique keys) and requires every unique key to exist with all focus-locale translations
  • documented directly in ChargeDrivers.swift that these English literals are runtime localization keys and must move together with the catalog

One clarification: Tools/test_home_i18n.py is already wired into CI through the Tools Python CI workflow's python3 -m unittest discover -p "test_*.py". The latest successful job ran all 28 Home tests as part of its 87 collected Tools/ tests. The new verdict-contract test will therefore run there as well.

On translation quality and scope: I manually tested the Android German Home flow, including the localized layout adjustments. I intend to do a separate content-quality and consistency pass over the German translations; there are also pre-existing translations outside this change that need improvement. Expanding this PR into a general German copy rewrite would make an already broad localization-enablement change larger still. The primary goal here is to make the Home strings localizable instead of leaving English hardcoded/fallback copy. Translation quality can then be iterated with native-speaker reports rather than being blocked on strings that cannot be translated at all.

Likewise, making missing iOS localization keys compile-time failures is worthwhile follow-up work, but not part of this PR. I already have separate parity-validation work that can compile iOS code on Linux; if that lands, it should improve this situation for future cross-platform development and validation.

@bhelm
bhelm force-pushed the feature/home-localization-upstream branch from 60a2ec0 to c9c7ede Compare August 21, 2026 12:30
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.

2 participants