Repository navigation
Add language settings, normalized language in app (#1004) - #1181
Merged
Merged
Conversation
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).
7 of 12 tasks
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
AppleLanguagesbundle override (takes effect after reopening NOOP, per the note in Settings);AppLanguage.activeLocalefollows 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.AppLanguagePrefs.wrapsets the process locale inApplication+ActivityattachBaseContext; the picker recreates the Activity so composable + non-composable lookups switch together.Follow-ons I added while landing it
Locale.ROOThardening (parity fix). The audit flagged that Android'sAnalyticsEngine.isoDay+AppleHealthImporter.civilFromDaysday-key writers relied on the default locale. Safe for the six shipped languages (all Latin digits), but the SwiftAnalyticsEngine.isoDayalready pinsen_US_POSIX— so this closes a real Swift↔Kotlin gap rather than adding behavior, and future-proofs against a non-Latin-digit language.AppLanguageTests.testExplicitLanguageWritesAndSystemRemovesAppleOverrideasserteddefaults.object(forKey: "AppleLanguages") == nilafter removing the override — butobject(forKey:)falls through toNSGlobalDomain, 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
NOOPiOS(iOS, macos-26) ✅ andStrand(macOS, incl.StrandTests) ✅ — the real gate, since app-target Swift has no default CI../gradlew compileFullDebugKotlin✅ locally.Locale.setDefaultcorruption audit: every persisted/hashed/day-key path is locale-pinned or ASCII-safe for the six shipped languages.Credit to @lukezielke (#1004). Closes #1004.