Skip to content

Add language settings, normalized language in app (#1004) - #1181

Merged
ryanbr merged 1 commit into
mainfrom
feat/app-language-1004
Aug 9, 2026
Merged

ryanbr merged 1 commit into
mainfrom
feat/app-language-1004

Conversation

@ryanbr

@ryanbr ryanbr commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Rebase of #1004 by @lukezielke onto current main — the original conflicted heavily (20/28 files) on the theming/accent/HSV-picker merges, epicentre being the Settings appearance card. Feature content is unchanged; I resolved the conflicts (Language now sits above Preset/Theme on both platforms) and added two small follow-ons noted below.

Feature

In-app UI-language selector on Apple + Android: System / English / Deutsch / Español / Français / Português / 中文, applied consistently to localized copy and locale-sensitive date/number formatting. Units and time zone stay independently configured.

  • Apple — writes the standard AppleLanguages bundle override (takes effect after reopening NOOP, per the note in Settings); AppLanguage.activeLocale follows the running bundle (not the pending picker) and combines the app language with the device region, fed to SwiftUI format styles at both app roots.
  • Android — AppLanguagePrefs.wrap sets the process locale in Application + Activity attachBaseContext; the picker recreates the Activity so composable + non-composable lookups switch together.
  • Full de/es/fr/pt-PT/zh translations for the new strings (iOS xcstrings carries all 8 locales); extracts several previously-hardcoded Android appearance labels into resources.

Follow-ons I added while landing it

  1. Day-key Locale.ROOT hardening (parity fix). The audit flagged that Android's AnalyticsEngine.isoDay + AppleHealthImporter.civilFromDays day-key writers relied on the default locale. Safe for the six shipped languages (all Latin digits), but the Swift AnalyticsEngine.isoDay already pins en_US_POSIX — so this closes a real Swift↔Kotlin gap rather than adding behavior, and future-proofs against a non-Latin-digit language.
  2. Test fix. AppLanguageTests.testExplicitLanguageWritesAndSystemRemovesAppleOverride asserted defaults.object(forKey: "AppleLanguages") == nil after removing the override — but object(forKey:) falls through to NSGlobalDomain, where CI has the system language set. Changed to read the suite's own persistent domain. (This is why the first app-build's macOS test leg failed; feature code was fine.)

Verification

  • app-build green on both legs: NOOPiOS (iOS, macos-26) ✅ and Strand (macOS, incl. StrandTests) ✅ — the real gate, since app-target Swift has no default CI.
  • Android ./gradlew compileFullDebugKotlin ✅ locally.
  • Locale coverage verified complete (all 8 iOS locales / all 5 Android non-default locales); no duplicate resource names.
  • Locale.setDefault corruption audit: every persisted/hashed/day-key path is locale-pinned or ASCII-safe for the six shipped languages.

Credit to @lukezielke (#1004). Closes #1004.

In-app UI-language selector on Apple + Android (System / English / Deutsch /
Español / Français / Português / 中文), applied consistently to localized copy
and locale-sensitive date/number formatting. Units and time zone stay
independently configured.

- Apple: writes the standard `AppleLanguages` bundle override and takes effect
  after reopening NOOP; `AppLanguage.activeLocale` follows the running bundle
  (not the pending picker) and combines the app language with the device region,
  fed to SwiftUI format styles via `.environment(\.locale,)` at both app roots.
  Formatters across the day/sleep/workout screens switch from `Locale.current`
  to `AppLanguage.activeLocale`.
- Android: `AppLanguagePrefs.wrap` sets the process locale in both
  `Application` and `Activity` `attachBaseContext`; the picker recreates the
  Activity so composable + non-composable resource lookups switch together.
- Full de/es/fr/pt-PT/zh translations for the new settings strings on both
  platforms (iOS xcstrings carries all 8 locales); extracts several previously
  hardcoded Android appearance labels into resources.

Rebased onto current main (theming/accent/HSV-picker merges): the Settings
appearance card now shows Language above Preset/Theme on both platforms.

Day-key hardening (parity + #1004 safety): pin `Locale.ROOT` on the Kotlin
`AnalyticsEngine.isoDay` formatter and `AppleHealthImporter.civilFromDays`
`String.format`, so the primary stored `DailyMetric.day` writer stays ASCII
yyyy-MM-dd under any app-language selection. The Swift `AnalyticsEngine.isoDay`
already pins `en_US_POSIX`; this closes that Swift↔Kotlin gap rather than
introducing new behavior (all six shipped languages already emit Latin digits).
@ryanbr
ryanbr merged commit cb5f775 into main Aug 9, 2026
5 checks passed
@ryanbr
ryanbr deleted the feat/app-language-1004 branch August 9, 2026 06:38
DX23876 added a commit to DX23876/noop that referenced this pull request Aug 12, 2026
85 commits from ryanbr/noop (a26bd3a..d94d568, 6–9 Aug): Oura decode fixes
(0x5D bucket order and pairing, RSA gating), a batch of BLE work (5/MG bond
watchdog, empty-offload backoff, MTU settle, battery poll throttle, DIS-based
4.0/5.0 correction), sleep-card reordering, the stress motion gate, the ryanbr#103
SpO₂ @82 candidate, language settings, and the ryanbr#1068 visual-system pass.

24 files conflicted. Resolved so that upstream wins on behaviour and this fork
wins on its own visual identity:

- Liquid Today, the Sleep hero, ScreenScaffold, StrandCard/Typography/Components
  keep the fork's design. Upstream's ryanbr#1068 rewrite (NoopPanelSurface everywhere,
  SF-Rounded roles, native tab bar) is deliberately not adopted — it targets a
  screen layout this fork replaced.
- Features upstream re-mounted in ryanbr#980 are taken: HealthAlertBanner and
  AutoWorkoutCard now render on Liquid Today, which is the default screen here,
  so a raised health alert finally has a home surface. `dataSourcesSection` is
  NOT re-mounted — the reorderable card block already renders it.
- Also taken: the ryanbr#1112 Sleep card reordering (all six of this fork's cards are
  covered by upstream's section enum), ryanbr#989 hydration, ryanbr#804 Fix A's provided
  hypnogram, the accent-colour storage, SyncChipState, and ryanbr#103's SpO₂ candidate
  — the last one rerouted through `windowedSpark` so the tile still honours the
  Detailed-tiles window picker.
- The string catalog was merged structurally, per key, rather than textually:
  4352 keys, 35 new from upstream, every focus locale complete.

Two silent regressions from upstream's rewrite were caught and restored:
ScreenScaffold's `overSky` title contrast (fork-only, deleted by ryanbr#1068) and the
`icon:` argument that ktile gained. Both heroes now share `LiquidScoreGauge`
instead of each inlining a vessel.

Verified: Strand (macOS) and NOOPiOS build; StrandTests 1864 pass; WhoopProtocol
558, WhoopStore 367, StrandAnalytics 1302 pass; i18n audit clean. One upstream
test (AppLanguageTests, new in ryanbr#1181) asserted through NSArgumentDomain, which
this fork's test scheme pins to `en` — rewritten to read the suite's own domain,
matching what the test already does for its second assertion.

NOT verified: Android does not compile here (no SDK on this machine), and none
of the BLE changes have been near a strap.
simoncad7 pushed a commit to simoncad7/noop that referenced this pull request Aug 17, 2026
…br#1181)

In-app UI-language selector on Apple + Android (System / English / Deutsch /
Español / Français / Português / 中文), applied consistently to localized copy
and locale-sensitive date/number formatting. Units and time zone stay
independently configured.

- Apple: writes the standard `AppleLanguages` bundle override and takes effect
  after reopening NOOP; `AppLanguage.activeLocale` follows the running bundle
  (not the pending picker) and combines the app language with the device region,
  fed to SwiftUI format styles via `.environment(\.locale,)` at both app roots.
  Formatters across the day/sleep/workout screens switch from `Locale.current`
  to `AppLanguage.activeLocale`.
- Android: `AppLanguagePrefs.wrap` sets the process locale in both
  `Application` and `Activity` `attachBaseContext`; the picker recreates the
  Activity so composable + non-composable resource lookups switch together.
- Full de/es/fr/pt-PT/zh translations for the new settings strings on both
  platforms (iOS xcstrings carries all 8 locales); extracts several previously
  hardcoded Android appearance labels into resources.

Rebased onto current main (theming/accent/HSV-picker merges): the Settings
appearance card now shows Language above Preset/Theme on both platforms.

Day-key hardening (parity + ryanbr#1004 safety): pin `Locale.ROOT` on the Kotlin
`AnalyticsEngine.isoDay` formatter and `AppleHealthImporter.civilFromDays`
`String.format`, so the primary stored `DailyMetric.day` writer stays ASCII
yyyy-MM-dd under any app-language selection. The Swift `AnalyticsEngine.isoDay`
already pins `en_US_POSIX`; this closes that Swift↔Kotlin gap rather than
introducing new behavior (all six shipped languages already emit Latin digits).

Co-authored-by: Luke Zielke <luke.bjarne@icloud.com>
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