feat(mobile): complete EN/PL localization and locale-aware presentation - #2189
Conversation
|
Re: #2189 (comment) Thanks — these are now addressed.
Validation: typecheck PASS; lint PASS with 0 errors/0 warnings; i18n audit PASS; full Jest 345 suites / 5597 tests / 0 failures; final CI is green. |
|
Re: #2189 (comment) Thanks — these are now addressed.
|
|
Could you take a look at the Claude comments once. Blocking:
Non-blocking: |
…e-localization # Conflicts: # SparkyFitnessMobile/AGENTS.md # SparkyFitnessMobile/__tests__/screens/MedicationScheduleFormScreen.test.tsx
…lendarSheet month/year nav
Blocker 1 — MealDetailScreen: replace manual count === 1 ? singular : plural
with i18next count pluralization for servingsLabel/ingredientsLabel (EN _one/_other,
PL _one/_few/_many/_other). Fixes "3 porcji" -> "3 porcje", "5 składniki" -> "5 składników".
Blocker 2 — WorkoutReorderList: replace manual count === 1 ? set : sets ternary
with t('workoutReorder.sets', { count }) using a full plural family (EN _one/_other,
PL _one/_few/_many/_other). Fixes "1 serii" -> "1 seria", "3 serii" -> "3 serie".
Blocker 3 — CalendarSheet: restore quick month/year navigation removed by hideHeader
by remounting the picker with initialView ('day'|'month'|'year') and onYearChange
(react-native-ui-datepicker 3.1.2 contract). Localized month/year caption buttons,
year-step prev/next inside the grids, firstDayOfWeek preserved across language switches.
Blocker 4 — merge with upstream/main (telemetry/health-sync changes retained).
F1 — Silent EN copy drift: split reused compact keys into context-specific semantic
keys so English UX copy matches pre-i18n upstream (Sat. Fat -> Saturated Fat full label,
Cal/kcal and cal/kcal unit labels, Clear workout alert button -> Clear).
F2 — FoodPhotoImproveScreen: replace length > 1 ? photos : photo ternary with
i18next count pluralization for subjectLabel (PL _one/_few/_many/_other).
G — Broader audit: WorkoutCompleteScreen allSets now uses count pluralization
instead of a manual allSetsOne/allSets ternary.
H — shared/cycle/predictions.ts: optional params + upcoming_period_today are consumed
by mobile cycleLocalization and fall back safely for server/frontend (.message).
Regression tests: MealDetail, WorkoutReorder, FoodPhotoImprove pluralization tests
against the real EN/PL catalogs (PL one/few/many for 1/2/3/5/12/22/25). CalendarSheet
tests cover month/year quick-jump, prev/next, locale-aware labels, firstDayOfWeek.
useScreenHeader duplicate-press mock returns defaultValue so localized Save resolves.
validate (typecheck+lint+i18n:audit): 0 errors, 0 warnings.
test:ci: 352 suites / 5759 tests PASS.
|
Thanks for the review — all blocking and non-blocking findings are addressed in the latest push (HEAD 1. MealDetailScreen pluralization — fixed. Removed the 2. WorkoutReorderList pluralization — fixed. Removed the 3. CalendarSheet month/year navigation — fixed. 4. Merge conflict / current main — resolved. Branch merged with 5. English-copy drift — fixed. Split reused compact keys into context-specific semantic keys so English matches pre-i18n upstream: 6. FoodPhotoImproveScreen pluralization — fixed. Replaced 7. shared/cycle/predictions.ts — verified and retained. Added a note to the PR body: Broader plural-pattern audit — scanned the mobile source for the Final HEAD: The exact final-HEAD build was not re-tested on a physical Android/iOS device in this round; the earlier device recording in the PR body is marked as historical. Device-level verification of the new HEAD is left to your environment. I won't request re-review until the new CI confirms green — which it now has. |
|
Awesome. Thank you. Two things left.
Also: the shared-key pattern you fixed for activeWorkout.bar.clear is still live |
|
I will merge this for now. you can fix these in the next PR. |
Update — 2026-08-24 (review round 2)
This update addresses all blocking and non-blocking findings from the latest CodeWithCJ/Claude review and re-integrates
upstream/main.Final PR HEAD:
6e1c08b33ec9a014aa0321eca50b3d77e291704f(ordinary push, no rebase/force-push).Integrated
upstream/mainSHA:da63d72f87041db7dc39283508ce615bc0456215. Merge-base equalsupstream/main, so GitHub reports the PR mergeable with no conflicts. Upstream telemetry/health-sync changes are retained.Blocking findings fixed:
count === 1 ? singular : pluralternary forservingLabel/ingredientLabel; both now uset('mealDetail.servingsLabel'/'ingredientsLabel', { count })with a full plural family (EN_one/_other, PL_one/_few/_many/_other). Fixes3 porcji→3 porcje,5 składniki→5 składników.count === 1 ? set : setsternary;t('workoutReorder.sets', { count })now drives selection with a full plural family. Fixes1 serii→1 seria,3 serii→3 serie.hideHeaderhad removed access to thereact-native-ui-datepickermonth/year selector. Restored quick-jump via the picker'sinitialView('day' | 'month' | 'year') andonYearChange(verified against the 3.1.2 type contract:initialView?: CalendarViews,onYearChange?: (year: number) => void). Localized month/year caption buttons switch to year-step prev/next inside the grids; selecting a month/year returns to the day grid.firstDayOfWeekis preserved across language switches; locale-aware month/weekday labels still follow the app locale; PL ↔ EN runtime switch still updates without restart; selected date stays correct.upstream/main; all upstream changes (telemetry cache, health-sync, Garmin, CI actions) are retained. No conflicts remain.Non-blocking findings fixed:
nutrients.saturatedFatLabel("Saturated Fat" full label) instead of the compactSat. Fat;*.caloriesUnit("Cal"/"cal") instead of the sharedkcal;activeWorkout.bar.clear("Clear") for the destructive alert button instead ofclearWorkout("Clear workout"). PL values remain correct ("kcal", "Wyczyść"). Existing*.caloriesShort= "kcal" stays for the contexts that already used "kcal" upstream.images.length > 1 ? photos : photowitht('foodPhotoImprove.subjectLabel', { count })and a full PL family (zdjęcie/zdjęcia/zdjęć). Fixes5 zdjęcia→5 zdjęć.Broader plural-pattern audit (G): scanned the mobile source for manual
count === 1 ? singular : plural/> 1 ? plural : singular/ singular-plural sibling keys. Found and fixedWorkoutCompleteScreenallSets(was using a legacyallSetsOnefor count=1 alongside a count-basedallSets; now usest('workoutComplete.labels.allSets', { count })consistently).healthDataDisplay.unit()andfoodUnitLocalizationalready passcountto i18next with full families and were left as-is. The mechanical i18n audit cannot statically detect this antipattern without a brittle AST parser; targeted regression tests cover the fixed call sites.Supporting
shared/src/cycle/predictions.tschange (H): the optionalparamsfield and the newupcoming_period_todayalert are consumed bySparkyFitnessMobile/src/utils/cycleLocalization.ts(localizes bykey+params, with generic fallbacks whenparamsis absent). Server/frontend consumers read.messageas the fallback and ignore the optionalparams, so the additive contract does not break them. Thediff === 0vsdiff >= -3 && diff <= 0split only changesupcoming_period→upcoming_period_todayfor the same-day case; other ranges are unaffected. This change is needed for semantically correct localized cycle messages and is retained.Regression tests (I): added/extended tests against the real EN/PL catalogs (not just inline
defaultValue):MealDetailScreen: EN singular/plural + PL one/few/many for servings/ingredients (1, 2, 3, 5, 12, 22, 25).WorkoutReorderList: EN singular/plural + PL one/few/many for sets (1, 2, 3, 5, 12, 22, 25).FoodPhotoImproveScreen: PL one/few/many forsubjectLabel(1, 2, 3, 5, 12, 22).CalendarSheet: month/year quick-jump reachable, returns to day grid, year-step prev/next labels, localized caption,firstDayOfWeekpreserved across language switch, prev/next month still works.Full validation (J): from
SparkyFitnessMobile/:pnpm run validate(typecheck + lint + i18n:audit): PASS — 0 errors, 0 warnings.pnpm run i18n:audit: PASS — 0 locale structural errors, 0 missing static keys, 0 placeholder errors, 0 plural errors, 0 missing English fallbacks, 0 dynamic keys, 0 source scan errors.jest --ci --coverage(full mobile test suite): 352 suites / 5759 tests PASS.git diff --check: PASS.GitHub CI (final HEAD
6e1c08b33): run 32770751200 — SUCCESS. Mobile Tests PASS (5m20s), Server Tests PASS, Detect Changes PASS, Validate & Label PASS, Auto-Merge Translations PASS. No force-push; ordinarygit push origin feat/mobile-complete-localization.The exact final-HEAD build was not re-tested on a physical Android/iOS device in this round; the prior physical-device evidence below (recorded from an earlier SHA) remains representative of the UI layout, but it is not an exact-final-HEAD recording. Device-level verification of the new HEAD is left to the maintainer's environment.
Description
What problem does this PR solve?
SparkyFitnessMobile previously did not have complete localization coverage. Large parts of the React Native UI, controlled domain values, native Android/iOS surfaces, widgets, accessibility text, and locale-sensitive date, number, and unit presentation were English-only or not consistently tied to the selected application language.
This PR completes the mobile i18n refactor and introduces a reusable localization foundation for the mobile app, with full English and Polish coverage across the current mobile product and supported native/runtime surfaces.
How did you implement the solution?
The mobile app now uses bundled semantic i18next catalogs with deterministic English fallback, system/per-app language integration, a manual language selector, and reactive runtime language switching initialized before localized UI presentation. The architecture was introduced in merged PR #2069, which added the mobile i18n bootstrap, bundled catalogs, fallback contract, and platform language integration. Merged PR #2072 extended that foundation to native widgets and Live Activity. This PR completes the application-wide migration by localizing the remaining React Native/mobile presentation surfaces and making locale-sensitive display consistent across the product.
Localization is applied across React Native screens and components, native Android/iOS resources, widgets/activity surfaces, permission and accessibility text, alerts/notifications where applicable, and controlled values received from shared/server contracts. Dates, times, numbers, decimal separators, measurements, units, charts/tooltips, and runtime calendar presentation now follow the selected SparkyFitness app language while preserving the configured first day of week. Canonical raw API/storage values remain stable; custom and user-provided text remains literal.
Coverage spans dashboard/diary, nutrition and foods, meal builder/types, workouts and active-workout timers, fasting, medications, health synchronization/presentation, cycle tracking, TTC, pregnancy (including Baby Development and safety content), settings, accessibility, and native widget/activity surfaces. The change also adds localization audits and regression coverage for catalog structure, static keys, placeholders, plurals, hardcoded UI text, dynamic keys, semantic/linguistic behavior, and locale-sensitive presentation.
Linked Issue: #1774
Implements the mobile app-language functionality requested in #1490.
Builds on merged PRs #2069 and #2072.
How to Test
SparkyFitnessMobileAPK from final PR HEADe97926fb9cba13a6faba4a39dcbc9d5d87a9a03f, or install Build Test APK #42 / artifact 9498301607.Physical device evidence
Final Android physical-device retest after all review fixes: PASS.
e97926fb9cba13a6faba4a39dcbc9d5d87a9a03ffeat/mobile-complete-localizatione97926fb9cba13a6faba4a39dcbc9d5d87a9a03fdevreleasearm64-v8aorg.SparkyApps.SparkyFitnessMobile1.devSparkyFitness-e97926fb-dev-releaseSparkyFitness-feat-mobile-complete-localization-e97926fb-dev-release.apk1442c180d5c0b76a2ebaf5083e77c20dc6309197def2ab92914795b6d089e420Physical iOS testing was not performed because an iOS device was not available.
PR Type
Checklist
All PRs:
New features only:
Frontend changes (
SparkyFitnessFrontend/):pnpm run validateand it passes.en) translation file.Backend changes (
SparkyFitnessServer/):rls_policies.sqlfor any new user-specific tables.UI changes (components, screens, pages):
Mobile changes (
SparkyFitnessMobile/):Screenshots
Final Android physical-device test
The recording below predates the final review-cleanup commits and is retained as representative visual evidence because the subsequent changes did not materially alter the UI layout or appearance. It was recorded from earlier SHA
1067c4b579910d5d7219a3cecaeff887876ab084; it is not an exact-final-SHA recording. The exact final SHA (at the time) was separately re-tested on a real Android device using the final APK documented above; that SHA is now historical (see the round-2 update at the top of this body).SparkyFitness-PR2189-device-test.mp4
Notes for Reviewers
feat(mobile): add i18n infrastructure and language settings #2069 introduced the core mobile i18n and language-settings/platform-language contract; feat(mobile): localize native widgets and live activity #2072 extended it to native widgets and Workout Live Activity; feat(mobile): complete EN/PL localization and locale-aware presentation #2189 completes the application-wide EN/PL migration and locale-aware presentation. The architecture discussion is in [Feature]: Add localization (i18n) support to the mobile app #1774, and this implements the mobile app-language functionality requested in [Feature]: Mobile App - Allow to change App Language #1490. RTL remains outside the scope of this initial mobile-localization contribution.
English and Polish are included here; the bundled mobile catalog is structured for a dedicated Weblate component that can reuse the project glossary/translation memory later. Application-owned controlled values are localized at the UI boundary; custom/user-generated values remain literal.
Native iOS localization/static coverage is included, but full physical iOS/Xcode validation was not available in the Linux/Android test environment.
(Historical, review round 1) Final PR HEAD:
e97926fb9cba13a6faba4a39dcbc9d5d87a9a03f— superseded by the round-2 HEAD6e1c08b33documented in the update section at the top of this PR body. Final local validation:pnpm run validatePASS; typecheck PASS; lint PASS with 0 errors and 0 warnings; i18n audit PASS; locale structural errors: 0; missing static keys: 0; placeholder errors: 0; plural errors: 0; missing English fallbacks: 0; dynamic i18n keys: 0; source scan errors: 0; hardened hardcoded UI strings: 0;git diff --checkPASS. Final exact upstream CI Tests #3077 / 32658109951 SUCCESS, including Mobile Tests, Mobile validation, Mobile tests with coverage, Server Tests, Server validation, and Server tests with coverage.Since the last review: the blocking plural regression was removed; the branch was updated against
mainand the merge conflict was resolved; the i18n audit was rerun after the update; catalog/defaultValue drift and plural families are mechanically guarded; runtime localization helpers now use the injectedTFunctioncontract with corrected memo dependencies; the convention is documented inSparkyFitnessMobile/AGENTS.md; the reviewedas anycases were removed/fixed;LOCALIZATION_WORK_LEDGER.mdis not part of the final deliverable; and the later CalendarSheet regression was found and fixed.The final guard validates EN
defaultValue/catalog parity and plural families (EN_one/_other; PL_one/_few/_many/_other). Final CI is green.Final audit improvements include controlled exercise taxonomy presentation localized through a shared injected-
TFunctionhelper, removal of remaining application-owned hardcoded UI literals, and a hardened hardcoded-UI scanner covering conditional/logical presentation expressions and Unicode text. Unknown/custom/user/server content remains literal; the final hardened hardcoded UI count is zero.Final Android physical-device runtime test: PASS after all final review fixes, using the exact
e97926fbfinal-HEAD build documented in Physical device evidence above. Physical iOS testing was not performed because an iOS device was not available.