Add multilingual support across 58 locales#79
Open
TheKalpeshPawar wants to merge 26 commits into
Open
Conversation
…nd cleaning up dead resources - Remove 22 unused string keys from passcode library and fix the 2 placeholder values for the external auth dialog. - Replace the sample app's orphaned strings.xml with screen-organized keys and rewire 7 .kt files to use stringResource() instead of hardcoded English.
…benchmark Adds values-XX/strings.xml under both mifos-authenticator-passcode and cmp-sample-shared for: af, be, bg, ca, cs, da, de, el, en-rGB, es, et, fa, fi, fr, hi, hr, hu, in, it, iw, ja, ko, lv, ml, nb, nl, pl, pt-rBR, pt-rPT, ro, ru, sk, sv, th, tr, uk, vi, zh-rCN, zh-rTW. Translations are machine-generated first drafts, audited and corrected for clear errors (terminology overloading in pt-rBR/tr, missing postpositions in hi, case error in et, ambiguous app_name in es/af, wrong sense in vi). Native-speaker review still recommended before production — register choices (T-V form, formal vs informal imperative) intentionally not auto-corrected and need stakeholder decision per locale.
- Latin "OK" (not Cyrillic "ОК") in ru/bg/be — matches Google + Apple - Romanian: tu-form throughout (informal imperative), removing the two voi-form outliers (Activați→Activează, Introduceți→Introdu) — Google Pay Romanian standardizes on tu - Korean: -습니다 register for the dialog confirmation, normalizing the -어요/-습니다 mix — Google Pay Korean uses 합쇼체 for transactional flows
…dd LOCALIZATION.md
The biometrics module previously hardcoded English in three places: the Android
prompt subtitle, the iOS authentication-failed fallback, and the "Register
yourself" registration title. Errors were also surfaced as English-prefixed
prose ("Authentication failed: ...") which broke the platform's own locale
mapping when the message portion was already localized by the OS.
This refactor:
- Adds title/subtitle/description/negativeButtonText parameters to
PlatformAuthenticator.authenticate() and registerUser(), threaded through
PlatformAuthenticationProvider.
- Replaces AuthenticationResult.Error(message: String) and
RegistrationResult.Error(message: String) with sealed BiometricError type
carrying categorical cases (Lockout, LockoutPermanent, HardwareUnavailable,
NotEnrolled, Timeout, NoSpace, Unknown). Consumer maps cases to localized
strings from its own resources.
- Drops the "Authentication failed: " / "Registration failed: " English
prefixes that previously concatenated with platform messages.
- Updates sample app to pass stringResource(...) for all prompt strings and
introduces rememberBiometricErrorMessages() helper to map BiometricError to
localized strings outside @composable context.
- Adds 6 new biometric_error_* keys to default English strings.xml; locale
variants for the 39 already-bundled languages are pending follow-up.
- Adds LOCALIZATION.md documenting bundled locales, the per-app-locale
recipe (Android), biometric-prompt string convention, and adding new
locales/strings; README links to it.
Breaking change: Result.Error.message → Result.Error.error: BiometricError.
…p locale Android 13+ AppCompatDelegate.setApplicationLocales(...) auto-converts the legacy "iw" tag to the modern "he" tag when storing per-app locales. Compose Multiplatform Resources then looks for values-he/ at runtime and falls back to the default English bundle when only values-iw/ exists, even though the Hebrew translation file is present. Verified on a OnePlus device running Android 16: setting per-app locale to "iw-IL" via adb stored as "he-IL", and Hebrew strings now render correctly after this change. Both directories are kept (identical content) so devices on older Android versions that still pass "iw" to Compose Resources continue to work.
…markets Not bundled in openMF/kmp-project-template's LanguageConfig benchmark, but added here since Arabic-speaking markets (Egypt, Jordan, Morocco, etc.) are core Mifos microfinance footprint. RTL layout was already verified working via Persian/Hebrew tests. Verified on a OnePlus device (Android 16): per-app locale ar-SA renders "أدخل رمز المرور" / "نسيت رمز المرور؟" with mirrored RTL layout, the `?` positioned on the visual left in the forgot-passcode link. Note: consumers using kmp-project-template's LanguageConfig enum will need to add ARABIC(localeName = "ar", text = "العربية") to expose this in their in-app language picker.
…kets
New locales (all 17 with both passcode-library and sample-app strings.xml):
South Asia (8): bn (Bengali), ta (Tamil), te (Telugu), mr (Marathi),
gu (Gujarati), kn (Kannada), pa (Punjabi/Gurmukhi),
ur (Urdu/RTL), si (Sinhala)
Africa (2): sw (Swahili), am (Amharic)
SE Asia (1): fil (Filipino)
Europe (5): lt (Lithuanian), sl (Slovenian), sr (Serbian/Cyrillic),
mk (Macedonian), sq (Albanian)
Combined with the existing 39-locale set + ar (added previously) + he (paired
with iw), the library now ships 57 locale variants plus default English.
Audit-driven mistranslation fixes applied alongside this batch:
sw: home_screen_title "Skrini ya Mwanzo" (Start Screen) → "Skrini ya Nyumbani"
enable_external_auth_dialog_description: "stakabadhi" (receipts) →
"vitambulisho" (credentials)
mk: biometric_setup_title "Поставки за биометрија" (settings for...)
→ "Поставување на биометрија" (setting up of...)
sq: biometric_error_hardware_unavailable: "Pajisja biometrike" (device)
→ "Hardueri biometrik" (hardware)
am: log_out: "ውጣ" (bare imperative) → "ይውጡ" (polite, matches rest of file)
ta/te/kn: biometric_prompt_subtitle "pattern" — replaced literal
translations (வடிவம்/నమూనా/ಮಾದರಿ "shape/sample/model") with the
Android-conventional transliteration (பேட்டர்ன்/ప్యాటర్న్/ಪ್ಯಾಟರ್ನ್).
te/kn version also resolved a same-word collision with "sample" in
app_name.
mr/gu/pa: confirm_*_passcode genitive postposition — added missing ची/ની/ਦੀ
markers per audit.
gu: use_biometrics — fixed stray space ("બાયોમેટ્રિક નો" → "બાયોમેટ્રિકનો").
LOCALIZATION.md updated with the full 57-locale table and an expanded list of
languages flagged for native-speaker review (now also si and am).
Verified on a OnePlus device: per-app locale `bn-IN` renders Bengali strings
correctly. Build green across all targets.
Adds digit_0..digit_9 string keys to the passcode-library default values/strings.xml (Western "0"-"9") and overrides them in the 13 bundled locales whose native numeral system differs from Western: ar — Arabic-Indic (٠-٩, U+0660..U+0669) fa — Persian (۰-۹, U+06F0..U+06F9) ur — Persian (Urdu) (۰-۹, same set as fa) hi — Devanagari (०-९, U+0966..U+096F) mr — Devanagari (०-९, same set as hi) bn — Bengali (০-৯, U+09E6..U+09EF) ta — Tamil (௦-௯, U+0BE6..U+0BEF) te — Telugu (౦-౯, U+0C66..U+0C6F) gu — Gujarati (૦-૯, U+0AE6..U+0AEF) kn — Kannada (೦-೯, U+0CE6..U+0CEF) pa — Gurmukhi (Punjabi) (੦-੯, U+0A66..U+0A6F) ml — Malayalam (൦-൯, U+0D66..U+0D6F) th — Thai (๐-๙, U+0E50..U+0E59) PasscodeKey gains a `keyDisplay: String = keyTitle` parameter that the keypad uses to render the localized glyph; `keyTitle` (the storage value emitted via onClick) continues to be ASCII "0"-"9" so PasscodeManager stores a locale-independent passcode regardless of which glyph the user saw on the keypad. Defaulting keyDisplay to keyTitle preserves backward compatibility for existing call sites that don't decouple display from storage. Locales without overrides (am, iw/he, si, all Latin-script locales) fall back to the Western default. Hostile audit: Codepoint-by-codepoint check across all 14 files passed — no transpositions, no wrong-script glyphs, no duplicates, no missing keys. Verified on device: ar-SA renders Arabic-Indic, fa-IR renders Persian, hi-IN renders Devanagari, plus bn-IN, ta-IN, te-IN, gu-IN, kn-IN, mr-IN all render correctly. es-ES regression confirms Western digits unchanged.
LoginScreen + HomeScreen had no horizontal padding and the title Text
expanded to fill width without textAlign — Spanish strings ("Pantalla de
inicio de sesión", "Pantalla de inicio") wrapped to multiple lines that
appeared left-aligned and visually overlapped due to the default 48sp
font's tight line height. English wasn't affected because shorter strings
didn't force the Text to expand.
Fixes for both screens:
- Add `padding(horizontal = 24.dp)` to the Column root.
- Add `textAlign = TextAlign.Center` to the title Text so wrapped lines
center within the Text bounds.
- Add `lineHeight = 56.sp` for proper line spacing at 48sp.
Verified on a OnePlus device with es-ES per-app locale: title now
properly wraps and centers, no edge-touching, no overlap.
Separately localizes PasscodeLengthSwitch:
- Replaces hardcoded "4 digits" / "6 digits" in PasscodeLengthSwitch.kt
(lines 96, 118, 150, 152) with stringResource(passcode_length_4_digits)
and (..._6_digits).
- Adds the two new keys to library default values/strings.xml plus all 57
locale variants (Spanish: "4 dígitos"/"6 dígitos", and so on across
ar, fa, hi, bn, mr, gu, kn, pa, ur, ml, te, ta, th, fr, de, it, ja, ko,
ru, etc.). Verified on-device: Create Passcode toggle now reads
"4 dígitos" / "6 dígitos" in Spanish.
…eral locales The keypad already renders locale-native digits in non-Latin numeral system locales (commit fe2fb0f), but the PasscodeLengthSwitch toggle below it kept showing Western "4 अंक" / "6 अंक" — a visual inconsistency between the digit on the toggle and the digit on the key the user actually presses. Updates the leading numeral in passcode_length_4_digits and passcode_length_6_digits for the 12 affected locales: ar ٤ أرقام / ٦ أرقام Arabic-Indic hi ४ अंक / ६ अंक Devanagari mr ४ अंक / ६ अंक Devanagari bn ৪ অঙ্ক / ৬ অঙ্ক Bengali ta ௪ இலக்கங்கள் / ௬ இலக்கங்கள் Tamil te ౪ అంకెలు / ౬ అంకెలు Telugu gu ૪ અંક / ૬ અંક Gujarati kn ೪ ಅಂಕಿ / ೬ ಅಂಕಿ Kannada pa ੪ ਅੰਕ / ੬ ਅੰਕ Gurmukhi ml ൪ അക്ക / ൬ അക്ക Malayalam th ๔ หลัก / ๖ หลัก Thai ur ۴ ہندسے / ۶ ہندسے Persian (Urdu adopts the set) Persian (fa) was already correct (۴/۶) from the original commit; not modified. All Latin-script locales (es, fr, de, ja, ko, zh-rCN, etc.) keep Western 4/6 — modern UI convention in those locales is Western digits with native counter words ("4 dígitos", "4桁", "4자리", "4位"). Verified on a OnePlus Android 16 device: hi-IN renders "४ अंक" / "६ अंक", ar-SA renders "٤ أرقام" / "٦ أرقام", both with their respective native-digit keypads. Toggle and keypad are now visually consistent.
Refactor biometric error handling to provide more granular feedback for invalid registration data and invalid arguments, particularly for the desktop implementation.
* **Biometrics Library**:
* Add `InvalidRegistrationData` and `InvalidArguments(stage: AuthStage)` to the `BiometricError` sealed interface.
* Introduce `AuthStage` enum to distinguish between failures during the Registration and Authentication phases.
* Update `WindowsHelloAuthenticator` and associated desktop logic to emit these specific error types instead of generic `Unknown` errors with platform-specific string messages.
* Simplify internal Windows authenticator responses by removing custom error strings in favor of typed objects.
* **Sample App and Localization**:
* Add localized strings for `biometric_error_invalid_registration_data`, `biometric_error_invalid_arguments_auth`, and `biometric_error_invalid_arguments_registration` across all supported locales (ca, ar, zh, es, fr, hi, etc.).
* Update `BiometricKey.kt` in the sample project to map the new `BiometricError` variants to their respective localized string resources.
P0 of L10N_FIX_PLAN.md. Adds biometric_error_lockout, _hardware_unavailable, _not_enrolled, _timeout, _no_space, _unknown across 40 locales that were missing them (would otherwise fall back to English at runtime). Translations sourced from l10n-review/batch_*.json suggested_fix payloads except he/iw which were translated manually using formal masculine register matching existing Hebrew strings. Also translates 3 untranslated English strings in values-fil/ (login_screen_title, home_screen_title, biometric_setup_title) per l10n-review/REPORT.md:501-503. Verified: all 58 locales now have all 6 biometric_error_* keys. Builds green on android-debug and desktop targets.
P1a (af): cd_* TalkBack labels used "Stawe met X" — "stawe" is the legal-attestation verb in Afrikaans, completely wrong for UI auth. Replaced with "Verifieer met X" across 4 cd_* keys. P1b (ko): Standardized passcode terminology on 비밀번호 (Google Pay KR convention). The mixed 암호 / 비밀번호 split was internally inconsistent. 7 occurrences fixed across both passcode lib and sample-shared. P1c (ca): "Desbloca" is a non-standard MT calque from Spanish/English; normative Catalan imperative of *desbloquejar* is "Desbloqueja". Fixed 3 occurrences. Also fixed "Configureu" → "Configura" and "Torneu-ho" → "Torna-ho" — the 2nd-person plural forms were inconsistent with the rest of the file's singular register. P1d (hi): 3 newer biometric_error_invalid_* strings used ASCII period "." as sentence terminator; fixed to Devanagari purna viram "।" to match the rest of the hi locale. References: l10n-review/REPORT.md G-3 (af:529, ko:130, ca:654, hi:250). Verification: all 4 grep checks in L10N_FIX_PLAN.md pass; android-debug + desktop builds green.
Deliberately overrides l10n-review/REPORT.md's case-by-case recommendation (which suggested informal register for ~14 of these locales per Google Pay convention). Per user request, applies formal "you" register everywhere instead — uniform across all 16 locales. Locales: de(Sie), hu(Ön), ru(вы), uk(ви), bg(вие), be(вы), sr(Ви), hr(Vi), sl(Vi), sq(Ju), ro(Dvs), lt(unchanged - already formal), lv(Jūs), mk(Вие), el(εσείς), tr(siz). 228 string substitutions applied. Each was a register-marker-only edit (pronouns, possessives, polite-imperative endings); content was not otherwise rewritten. Build green on android-debug, sample-shared compile, passcode-lib compile, and desktop targets. Caveat: native-speaker review still recommended before final ship, especially for low-resource locales (be, mk, sq, sl, lv, lt, sr, hr). References: L10N_FIX_PLAN.md P2.
In `WindowsHelloAuthenticator`, remove the `Logger.e` call when an exception occurs during the registration or verification process. The method continues to return `WindowsAuthenticatorResponse.Registration.Error` as the failure result.
Refactors the component to use `IntrinsicSize.Max` for width calculation, ensuring that the container expands to fit longer localized strings while maintaining a minimum width of 150.dp for shorter labels. * Rename the `width` parameter to `minWidth` to accurately reflect its new behavior. * Replace absolute button positioning (`Alignment.CenterStart/End`) with a `Row` using `weight(1f)` to ensure equal space distribution between the two options. * Add horizontal padding to the buttons to prevent text from clipping against the edges. * Update `AnimatedContent` to `fillMaxSize` to match the new container constraints.
Refactor UI components to use a `PasscodeStrings` abstraction instead of direct resource references. This allows callers to provide custom localized strings or override default text across the passcode library.
* **API Changes**:
* Add a `strings` parameter to `PasscodeScreen` and its internal components (`PasscodeHeader`, `PasscodeKeys`, `PasscodeLengthSwitch`, `PasscodeForgotButton`, etc.).
* Use `defaultPasscodeStrings()` as the default value for the new `strings` parameter to maintain existing behavior.
* **UI Logic**:
* Replace `stringResource(Res.string.*)` calls with properties from the `PasscodeStrings` instance.
* Decouple the keypad's localized digits and accessibility content descriptions from static resources.
* Update dialogs (`SystemAuthConfirmDialog`, `PasscodeMismatchedDialog`) to support dynamic string injection.
* **Cleanup**:
* Remove unused imports for generated resource classes in multiple component files.
Library now exposes a `PasscodeStrings` data class so consumers supply their own translations instead of relying on the bundled defaults. - `PasscodeStrings.kt` (new) — 17-field data class covering every user-visible string + 10-element digit glyph list, plus a memoized `defaultPasscodeStrings()` factory wrapped in `remember(...)` keyed on the underlying string values (one allocation per locale change, not per recomposition). - `PasscodeScreen` retains a top-level `strings: PasscodeStrings = defaultPasscodeStrings()` default and threads `strings = strings` to every leaf it composes. - Leaf composables (`PasscodeHeader`, `PasscodeKeys`, `PasscodeLengthSwitch`, `PasscodeMismatchedDialog`, `PasscodeForgotButton`, `SystemAuthSetupConfirmDialog`) now require explicit `strings: PasscodeStrings` — no per-leaf default. This matches Stage 2's end state (when the lib stops bundling strings entirely, defaults won't be possible) and surfaces missing dependencies at compile time rather than via a hidden runtime fallback. Dead-code cleanup: removed the unused `PasscodeSkipButton` composable and its associated `skipButtonStyle()` text style. Trimmed the now- orphaned `skipButtonTextStyle` field from `PasscodeButtonConfig`, the commented-out construction line in `PasscodeScreen`, and the commented `isSkipButtonVisible` placeholder. Verified zero remaining references repo-wide before deletion. Build green: passcode lib (android-debug + desktop) + sample-android APK assembly all succeed.
Adds comprehensive localized string resources for the passcode library across multiple locales (including Arabic, Bengali, Chinese, French, Hindi, Spanish, and more) within the sample app. **Key changes:** * **Localization**: Populates `strings.xml` across 30+ language variants with `mifos_passcode_*` keys to support internationalization of the passcode UI. * **Component Integration**: Updates `PasscodeScreenWithBiometrics` to accept a `PasscodeStrings` parameter, allowing the library to use caller-provided localized text. * **Resource Mapping**: Introduces `rememberPasscodeStringsFromResources()`, a helper composable that resolves localized strings from the app's resources and memoizes them into the library's `PasscodeStrings` data class. This includes headers, error messages, digit labels, and accessibility content descriptions.
Stage 2.2 of the i18n migration. The library no longer ships its own translations; consumers supply a `PasscodeStrings` instance constructed from their app's resources. - Deleted `mifos-authenticator-passcode/src/commonMain/composeResources/ values-*/strings.xml` (58 locale variants + base English) — 59 files. The library's published artifact no longer carries embedded translation data. Drawables (mifos_logo) and fonts (Lato_*) stay bundled. - Removed `defaultPasscodeStrings()` factory from `PasscodeStrings.kt`. The data class itself stays. With no bundled resources to read, the bridge factory is unimplementable; the explicit-strings contract is now load-bearing. - Made `strings: PasscodeStrings` a required parameter on `PasscodeScreen(...)` (was a default-valued bridge previously). All current internal call sites already pass `strings = strings` explicitly so this only constrains future external consumers. - New `l10n-templates/passcode/values-*/strings.xml` (59 files) holds the canonical translations consumers can copy into their own modules. Every key is prefixed `mifos_passcode_*` so it can be appended to a consumer's existing `strings.xml` without collision. - New `l10n-templates/passcode/README.md` documents the integration paths (manual copy now; planned Gradle plugin later) and the resource-key → `PasscodeStrings`-field mapping table. Verified: `:clean` then full multi-target compile (android-debug, desktop, iOS-arm64, JS, wasmJS) plus sample-android APK assemble all green. Compose Resources codegen handles the lib's no-`values/` state cleanly — the auto-generated `Res` accessor exposes only `Res.drawable.*` and `Res.font.*` after this commit.
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.
#78
Summary
Comprehensive localization pass across
mifos-authenticator-passcodeandcmp-sample-shared.BiometricErrorsealed type — categorical error states (Lockout,LockoutPermanent,HardwareUnavailable,NotEnrolled,Timeout,NoSpace,InvalidRegistrationData,InvalidArguments(stage),Unknown) replace prose error strings; consumers map each case to localized messages via theBiometricErrorMessagesmapper in the sample app.digit_0..digit_9overrides for non-Latin numeral locales (ar, fa, ur, hi, mr, bn, ta, te, gu, kn, pa, ml, th).l10n-review/REPORT.md):Stawe met X(legal-attestation verb) →Verifieer met Xfor 4 cd_* labels.비밀번호(Google Pay KR convention) — replaced 7암호occurrences across both modules.Desbloca→Desbloqueja(3×); plural→singularConfigureu→Configura,Torneu-ho→Torna-ho..→ Devanagari purna viram।in 3 newer error strings.stringResource(cd_fingerprint_icon,cd_face_scan_icon,cd_iris_scan_icon,cd_device_credential_icon); same for passcode-keypad action keys (visibility toggle, delete).L10N_FIX_PLAN.mddocuments prioritization, decisions, and verification commands for resumption by future contributors.Test plan
./gradlew :cmp-sample-android:assembleDebug— green./gradlew :cmp-sample-shared:compileDebugKotlinAndroid :mifos-authenticator-passcode:compileDebugKotlinAndroid :cmp-sample-shared:compileKotlinDesktop— green./gradlew :mifos-authenticator-biometrics:compileKotlinJs :compileKotlinWasmJs— green (KMP non-android targets)./gradlew :cmp-sample-android:lintDebug— 0 errors; 147 pre-existing baseline issues incidentally resolvedgrep -L "biometric_error_lockout" cmp-sample-shared/src/commonMain/composeResources/values-*/strings.xml | wc -l→0(all 58 locales have all 6 legacy biometric error keys; no English fallback at runtime).Supported languages (58 bundled)
58 locale variants in
values-XX/strings.xmlplus the basevalues/strings.xml(English source). Locales selected to match the openMF/kmp-project-template benchmark plus Mifos-priority microfinance markets and Google Pay's coverage list.Full locale list — click to expand
afamarbebgbncacsdadeelen-rGBesetfafifilfrguhehihrhuinitiwjaknkoltlvmkmlmrnbnlpaplpt-rBRpt-rPTrorusiskslsqsrsvswtatethtrukurvizh-rCNzh-rTWNative numeral overrides (locale-specific
digit_0..digit_9glyphs on the keypad):ar,fa,ur,hi,mr,bn,ta,te,gu,kn,pa,ml,th. All other locales fall back to base ASCII digits.RTL locales:
ar,fa,ur,he,iw.Region-qualified locales:
en-rGB,pt-rBR,pt-rPT,zh-rCN,zh-rTW. Note that the resource folder convention usesras a region prefix (pt-rBR) while runtime BCP-47 tags drop it (pt-BR); Android's resource resolver bridges between the two.Click a thumbnail to view full-size.
de)fr)es)pt-rBR)pt-rPT)ar)he)hi)bn)ta)te)ko)ja)ru)th)