Skip to content

Latest commit

 

History

History
161 lines (127 loc) · 9.46 KB

File metadata and controls

161 lines (127 loc) · 9.46 KB

Localization

This document describes how mifos-passcode-cmp handles user-facing text and how a consuming application controls the language shown at runtime.

Bundled locales

Both mifos-authenticator-passcode and cmp-sample-shared ship Compose Multiplatform Resource bundles for 57 locale variants plus default English. The base set matches openMF/kmp-project-template; 18 additional locales were added to align with Google Pay's supported-language list and Mifos's MENA, South Asia, and Sub-Saharan Africa microfinance markets. Default text is English (values/).

Code (Compose dir) Language
values-af Afrikaans
values-am Amharic
values-ar Arabic (RTL)
values-be Belarusian
values-bg Bulgarian
values-bn Bengali
values-ca Catalan
values-cs Czech
values-da Danish
values-de German
values-el Greek
values-en-rGB English (United Kingdom)
values-es Spanish
values-et Estonian
values-fa Persian (RTL)
values-fi Finnish
values-fil Filipino
values-fr French
values-gu Gujarati
values-he Hebrew (modern code, paired with values-iw)
values-hi Hindi
values-hr Croatian
values-hu Hungarian
values-in Indonesian (legacy code; modern is id)
values-it Italian
values-iw Hebrew (legacy code, paired with values-he for Android <13)
values-ja Japanese
values-kn Kannada
values-ko Korean
values-lt Lithuanian
values-lv Latvian
values-mk Macedonian
values-ml Malayalam
values-mr Marathi
values-nb Norwegian Bokmål
values-nl Dutch
values-pa Punjabi (Gurmukhi)
values-pl Polish
values-pt-rBR Portuguese (Brazil)
values-pt-rPT Portuguese (Portugal)
values-ro Romanian
values-ru Russian
values-si Sinhala
values-sk Slovak
values-sl Slovenian
values-sq Albanian
values-sr Serbian (Cyrillic)
values-sv Swedish
values-sw Swahili
values-ta Tamil
values-te Telugu
values-th Thai
values-tr Turkish
values-uk Ukrainian
values-ur Urdu (RTL)
values-vi Vietnamese
values-zh-rCN Chinese (Simplified)
values-zh-rTW Chinese (Traditional)

Translation status

Machine-translated first drafts. All locale files are generated by an LLM with audit-driven corrections. They are good enough for review and integration work but have not been native-speaker reviewed. Treat translations as drafts pending native review before shipping to production.

Specifically flagged for native review: Thai (th), Persian (fa), Malayalam (ml), Sinhala (si), Amharic (am) — auditor declined to certify these. All other locales should be reviewed by a fluent speaker before shipping to that market, particularly the smaller European languages (af, be, et, lv, ca, lt, sl, mk, sq).

Known sentence-fragment issue: biometric_error_lockout reads as a noun phrase ("Too many failed attempts.") in many Indic languages where the source English would read more naturally as a sentence. This is acceptable as a label but native review may want to recast it as a full sentence per language.

Style decisions follow Google Pay / Wallet conventions: informal du in German, tu imperative in Romanian, Latin "OK" in Cyrillic locales, 합쇼체 (-습니다) for Korean transactional flows.

How locale selection works

Compose Multiplatform Resources reads the OS-level locale at runtime and resolves stringResource(Res.string.…) calls against the most specific matching values-XX/ directory, falling back to the default values/. This works automatically across all targets (Android, iOS, Desktop, Web) without any per-platform setup.

The library does not ship its own language picker or persistence — that is the consumer's responsibility. Switching language at runtime requires the consumer to update the OS-reported locale.

Per-app locale switching (Android)

The recommended consumer recipe matches kmp-project-template. Persist the chosen locale in your app preferences, then on startup and on user change:

import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat

fun applyAppLocale(localeTag: String?) {
    val desiredLocales = if (localeTag != null) {
        LocaleListCompat.forLanguageTags(localeTag)
    } else {
        // System default
        LocaleListCompat.getEmptyLocaleList()
    }
    val currentLocales = AppCompatDelegate.getApplicationLocales()
    if (currentLocales != desiredLocales) {
        AppCompatDelegate.setApplicationLocales(desiredLocales)
    }
}

After this call, all Compose Resources lookups in the library and your app code resolve to the requested locale automatically. No further wiring is needed in the library itself.

For a complete reference integration including a language picker UI and persistence layer, see:

  • cmp-navigation/src/commonMain/kotlin/cmp/navigation/AppViewModel.kt — the observeLanguage / UpdateAppLocale plumbing
  • cmp-android/src/main/kotlin/cmp/android/app/AndroidApp.kt:restoreSavedLanguage() — the per-app-locale call site
  • core/model/src/commonMain/kotlin/org/mifos/core/model/LanguageConfig.kt — the supported-locales enum
  • feature/settings/src/commonMain/kotlin/org/mifos/feature/settings/LanguageDialog.kt — the picker UI

in openMF/kmp-project-template.

Per-app locale switching (iOS / Desktop / Web)

Compose Multiplatform Resources picks up the OS locale on these platforms automatically, but per-app overrides are not yet wired up by either this library or kmp-project-template:

  • iOS: Override requires manipulating NSBundle.preferredLocalizations or shipping LSPreferredLocales in the consumer app's Info.plist. Not yet provided.
  • Desktop (JVM): java.util.Locale.setDefault(Locale.forLanguageTag(tag)) plus a Compose recomposition trigger works in practice. Not yet abstracted.
  • Web (wasmJs / js): navigator.language is read-only; per-app switching requires a CompositionLocal-based override or URL-param scheme. Not yet provided.

Consumers needing cross-platform locale switching should treat this as an open task and contribute back.

Biometric prompt strings

The biometrics module's PlatformAuthenticator.authenticate(...) and PlatformAuthenticator.registerUser(...) accept title, subtitle, description, and negativeButtonText parameters that are passed through to the platform's biometric prompt UI. The library does not bundle translations for these strings — the consumer is expected to pass already-localized strings sourced from its own resources (e.g. via stringResource(Res.string.your_biometric_prompt_title)).

This applies on Android (BiometricPrompt.PromptInfo) and iOS (LAContext.localizedReasontitle only; subtitle/description/negativeButtonText are silently ignored). Desktop and Web ignore all four.

The system-rendered chrome inside the prompt (cancel / "Use PIN" / "Touch sensor" / recoverable error toasts) is rendered by the OS using the device locale, not the app locale — this is unavoidable on both Android and iOS.

Biometric error mapping

Authentication and registration errors come back as BiometricError sealed cases (Lockout, LockoutPermanent, HardwareUnavailable, NotEnrolled, Timeout, NoSpace, Unknown). The consumer maps these to localized strings from its own resources — the library deliberately does not surface platform error prose because those strings are device-locale, not app-locale.

See cmp-sample-shared/src/commonMain/kotlin/cmp/sample/shared/platformAuthentication/BiometricKey.kt for the rememberBiometricErrorMessages() pattern used by the sample app.

Adding a new locale

  1. Copy mifos-authenticator-passcode/src/commonMain/composeResources/values/strings.xml to values-XX/strings.xml.
  2. Translate every <string> value while preserving the name="…" keys verbatim.
  3. Repeat for cmp-sample-shared/src/commonMain/composeResources/values/strings.xml (the sample app keys).
  4. Build to verify Compose Resources picks up the new locale: ./gradlew :mifos-authenticator-passcode:assemble :cmp-sample-shared:assemble.
  5. Submit for native-speaker review.

Adding a new string

  1. Add the <string> to values/strings.xml in the relevant module first (the English source of truth).
  2. Add the same key with a placeholder or auto-translated value to all values-XX/strings.xml in that module — Compose Resources falls back to the default file for any missing key, but mismatched key sets across locales are a code smell.
  3. Reference it via stringResource(Res.string.your_key) from a @Composable context.

Notes for library consumers

  • Strings used by PasscodeScreen and friends ship inside the library's resources. Consumers do not override them via constructor parameters; the standard Compose Resources locale-resolution mechanism above is the only override path.
  • This is a deliberate design choice matching the kmp-project-template ecosystem convention. If your app needs to override library strings (rare), contribute the strings upstream rather than monkey-patching.
  • Strings rendered as part of the biometric prompt UI come from your app's resources, not the library's. See above.