-
-
Notifications
You must be signed in to change notification settings - Fork 27
fix(mobile): localize Just Lift Echo-mode UI for Italian (issue #540) #542
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
9thLevelSoftware
merged 1 commit into
main
from
fix/phoenix-issue-540-italian-eccentric-locale
Jun 14, 2026
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
11 changes: 11 additions & 0 deletions
11
...d/src/androidMain/kotlin/com/devil/phoenixproject/domain/model/CurrentLanguage.android.kt
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.devil.phoenixproject.domain.model | ||
|
|
||
| import java.util.Locale | ||
|
|
||
| /** | ||
| * Android actual for [currentLanguageCode]. Returns the JVM `Locale`'s | ||
| * lowercased language code, e.g. `"en"` / `"it"`. Returns `""` if the | ||
| * default locale is `ROOT` or otherwise has no language component. | ||
| */ | ||
| actual fun currentLanguageCode(): String = | ||
| Locale.getDefault().language.orEmpty() |
33 changes: 33 additions & 0 deletions
33
shared/src/commonMain/composeResources/values-it/strings.xml
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <!-- Italian (italiano) — Beta AI-generated translation --> | ||
| <!-- Fitness terminology context: | ||
| "set" = serie (un gruppo di ripetizioni) | ||
| "rep" = ripetizione (un singolo movimento) | ||
| "1RM" = 1RM (one-rep max, universale) | ||
| "AMRAP" = AMRAP (as many reps as possible, universale) | ||
| "TUT" = TUT (time under tension, universale) | ||
| "PR" = PR (record personale) | ||
| Technical terms like "Echo", "TUT", "AMRAP", "1RM" stay in English. | ||
| --> | ||
| <resources> | ||
| <string name="app_name">Phoenix</string> | ||
|
|
||
| <!-- ==================== Workout Modes ==================== --> | ||
| <string name="echo_level">Livello Echo</string> | ||
| <string name="echo_level_hard">Difficile</string> | ||
| <string name="echo_level_harder">Più difficile</string> | ||
| <string name="echo_level_hardest">Difficilissimo</string> | ||
| <string name="echo_level_epic">Epico</string> | ||
| <string name="eccentric_load">Carico eccentrico</string> | ||
| <string name="eccentric_load_helper">Carico durante la fase eccentrica (discesa)</string> | ||
| <string name="rep_count_timing">Tempismo conteggio ripetizioni</string> | ||
| <string name="rep_count_timing_top">Conta in cima (picco concentrico)</string> | ||
| <string name="rep_count_timing_bottom">Conta in basso (fase eccentrica)</string> | ||
|
|
||
| <!-- ==================== Rest Timer ==================== --> | ||
| <string name="rest_eccentric_load">CARICO ECCENTRICO</string> | ||
| <string name="rest_echo_level">LIVELLO ECHO</string> | ||
|
|
||
| <!-- ==================== Configuration ==================== --> | ||
| <string name="config_echo_level">LIVELLO ECHO</string> | ||
| </resources> | ||
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
21 changes: 21 additions & 0 deletions
21
shared/src/commonMain/kotlin/com/devil/phoenixproject/domain/model/CurrentLanguage.kt
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package com.devil.phoenixproject.domain.model | ||
|
|
||
| /** | ||
| * Returns the BCP-47 language subtag of the active app locale, lowercased | ||
| * (e.g. `"en"`, `"it"`, `"de"`). Returns `""` when the language cannot be | ||
| * determined. | ||
| * | ||
| * This is a multiplatform-friendly alternative to reading | ||
| * `LocalConfiguration.current.locales`, which is not exposed in the | ||
| * Compose Multiplatform iOS klib (CMP 1.10.3) and would not compile for | ||
| * `iosArm64`. We delegate to platform-native APIs: | ||
| * | ||
| * - Android: `java.util.Locale.getDefault().language` | ||
| * - iOS: `NSUserDefaults.standardUserDefaults` first entry of `AppleLanguages` | ||
| * (the user-selected preferred language list, which is exactly the | ||
| * Italian iPhone setting from issue #540). | ||
| * | ||
| * The returned value is the *language* subtag only — no region, no script. | ||
| * Tests for the Italian NBSP branch check `equals("it", ignoreCase = true)`. | ||
| */ | ||
| expect fun currentLanguageCode(): String |
86 changes: 86 additions & 0 deletions
86
shared/src/commonMain/kotlin/com/devil/phoenixproject/domain/model/EccentricLoadLabels.kt
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| package com.devil.phoenixproject.domain.model | ||
|
|
||
| import androidx.compose.runtime.Composable | ||
| import org.jetbrains.compose.resources.stringResource | ||
| import vitruvianprojectphoenix.shared.generated.resources.Res | ||
| import vitruvianprojectphoenix.shared.generated.resources.echo_level_epic | ||
| import vitruvianprojectphoenix.shared.generated.resources.echo_level_hard | ||
| import vitruvianprojectphoenix.shared.generated.resources.echo_level_harder | ||
| import vitruvianprojectphoenix.shared.generated.resources.echo_level_hardest | ||
|
|
||
| /** | ||
| * Locale-aware label helpers for the Echo-mode UI block in | ||
| * [com.devil.phoenixproject.presentation.screen.JustLiftScreen]. | ||
| * | ||
| * Background (issue #540): on Italian iPhone the system font (SF Pro) renders | ||
| * the literal sequence `110%` with effectively zero left-advance on the `%` | ||
| * glyph, so the EccentricLoad dropdown value reads as `11U%` / `11 U%`. | ||
| * Italian typography inserts a hard non-breaking space (U+00A0) between the | ||
| * digits and the percent sign — `110 %` — and iOS SF Pro renders that form | ||
| * without a glyph collision. This file emits that form for any `it-*` | ||
| * language and the back-compat ASCII `110%` form for every other locale. | ||
| * | ||
| * The pure formatter [formatEccentricLoad] is `internal` so the unit test in | ||
| * `commonTest` can exercise the per-locale behaviour without needing a | ||
| * Compose runtime or a platform-specific locale implementation. The | ||
| * `@Composable` [eccentricLoadLabel] wrapper uses the expect/actual | ||
| * [currentLanguageCode] helper to get the active app locale without pulling | ||
| * in any Compose-Multiplatform-specific locale API. | ||
| */ | ||
|
|
||
| /** | ||
| * Non-Composable pure formatter used by the `@Composable` [eccentricLoadLabel] | ||
| * wrapper. Exposed as `internal` for unit testing. | ||
| * | ||
| * Behaviour: | ||
| * * For Italian (the language code `it`, case-insensitive) this returns the | ||
| * digits followed by U+00A0 NBSP and `%` — e.g. `"110\u00A0%"`. This is | ||
| * the canonical Italian typographic form and resolves the iOS SF Pro | ||
| * `%`-glyph collision. | ||
| * * For every other locale this returns the digits followed by `%` — e.g. | ||
| * `"110%"` (byte-identical to the legacy `EccentricLoad.displayName` so | ||
| * the existing English UI is unchanged). | ||
| * | ||
| * @param load the [EccentricLoad] enum entry whose | ||
| * [EccentricLoad.percentage] is formatted. | ||
| * @param language the lowercased language code of the active locale, e.g. | ||
| * `"en"`, `"it"`, `"de"`. Pass `""` or a non-`"it"` value to | ||
| * get the ASCII form. | ||
| */ | ||
| internal fun formatEccentricLoad(load: EccentricLoad, language: String): String { | ||
| return if (language.equals("it", ignoreCase = true)) { | ||
| "${load.percentage}\u00A0%" | ||
| } else { | ||
| "${load.percentage}%" | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Composable wrapper that resolves the active language code via | ||
| * [currentLanguageCode] and delegates to [formatEccentricLoad]. No | ||
| * Compose-Multiplatform-specific locale API is required, so this helper | ||
| * compiles cleanly for both the Android and iOS targets. | ||
| * | ||
| * Returns the locale-formatted percentage for the dropdown value cell in | ||
| * [com.devil.phoenixproject.presentation.screen.JustLiftScreen] (line ~528) | ||
| * and the matching dropdown item text (line ~544). | ||
| */ | ||
| @Composable | ||
| fun eccentricLoadLabel(load: EccentricLoad): String { | ||
| return formatEccentricLoad(load, currentLanguageCode()) | ||
| } | ||
|
|
||
| /** | ||
| * Returns the [EchoLevel] label via the [stringResource] lookup so the | ||
| * FilterChip and SegmentedButton rows pick up the per-locale translation. | ||
| * | ||
| * Resource key convention: `echo_level_<lowercase name>` → `echo_level_hard`, | ||
| * `echo_level_harder`, `echo_level_hardest`, `echo_level_epic`. | ||
| */ | ||
| @Composable | ||
| fun echoLevelLabel(level: EchoLevel): String = when (level) { | ||
| EchoLevel.HARD -> stringResource(Res.string.echo_level_hard) | ||
| EchoLevel.HARDER -> stringResource(Res.string.echo_level_harder) | ||
| EchoLevel.HARDEST -> stringResource(Res.string.echo_level_hardest) | ||
| EchoLevel.EPIC -> stringResource(Res.string.echo_level_epic) | ||
| } |
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
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding this
values-itlocale and advertisingitinCFBundleLocalizationsmakes Italian a shipped locale, but the app’s own language dropdown still constructs onlyen,nl,de,es, andfroptions inSettingsTab.kt(checked thelanguageOptionslist at lines 1123-1128). In contexts where the user does not have system Italian selected, or has previously chosen another in-app language that is persisted via the existing locale helpers, these new Italian strings and the NBSP percent fix are not reachable from the app’s language setting. Please add theitoption and its display string alongside this locale.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@codex
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary
"it"option inlanguageOptions. shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/SettingsTab.ktL173-L178 shared/src/commonMain/kotlin/com/devil/phoenixproject/presentation/screen/SettingsTab.ktL1123-L1131language_italianstring and the Italian resource file’s language-name entries so the picker can render Italian consistently. shared/src/commonMain/composeResources/values/strings.xmlL158-L164 shared/src/commonMain/composeResources/values-it/strings.xmlL15-L21values-itforecho_levelandeccentric_load. shared/src/commonMain/composeResources/values-it/strings.xmlL23-L30currentLanguageCode()to cache the AppleLanguages lookup withlazyand usestringArrayForKey, avoiding repeatedNSUserDefaultsreads during recomposition. shared/src/iosMain/kotlin/com/devil/phoenixproject/domain/model/CurrentLanguage.ios.ktL15-L29c82d543and created the follow-up PR viamake_pr.Testing
./gradlew -Pskip.supabase.check=true :shared:compileKotlinIosArm64python3 - <<'PY' from pathlib import Path import xml.etree.ElementTree as ET for p in Path('shared/src/commonMain/composeResources').glob('values*/strings.xml'): ET.parse(p) print('parsed compose resource strings.xml files') PYgit diff --check./gradlew :shared:compileKotlinIosArm64 :shared:compileAndroidHostTest(warning due to missing Supabase credentials in this environment)./gradlew -Pskip.supabase.check=true :shared:compileKotlinIosArm64 :shared:compileAndroidHostTest(warning due to missing Android SDK /ANDROID_HOMEin this environment)View task →