This document describes how mifos-passcode-cmp handles user-facing text and how a consuming application controls the language shown at runtime.
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) |
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_lockoutreads 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
duin German,tuimperative in Romanian, Latin "OK" in Cyrillic locales,합쇼체(-습니다) for Korean transactional flows.
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.
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— theobserveLanguage/UpdateAppLocaleplumbingcmp-android/src/main/kotlin/cmp/android/app/AndroidApp.kt:restoreSavedLanguage()— the per-app-locale call sitecore/model/src/commonMain/kotlin/org/mifos/core/model/LanguageConfig.kt— the supported-locales enumfeature/settings/src/commonMain/kotlin/org/mifos/feature/settings/LanguageDialog.kt— the picker UI
in openMF/kmp-project-template.
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.preferredLocalizationsor shippingLSPreferredLocalesin the consumer app'sInfo.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.languageis read-only; per-app switching requires aCompositionLocal-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.
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.localizedReason — title 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.
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.
- Copy
mifos-authenticator-passcode/src/commonMain/composeResources/values/strings.xmltovalues-XX/strings.xml. - Translate every
<string>value while preserving thename="…"keys verbatim. - Repeat for
cmp-sample-shared/src/commonMain/composeResources/values/strings.xml(the sample app keys). - Build to verify Compose Resources picks up the new locale:
./gradlew :mifos-authenticator-passcode:assemble :cmp-sample-shared:assemble. - Submit for native-speaker review.
- Add the
<string>tovalues/strings.xmlin the relevant module first (the English source of truth). - Add the same key with a placeholder or auto-translated value to all
values-XX/strings.xmlin that module — Compose Resources falls back to the default file for any missing key, but mismatched key sets across locales are a code smell. - Reference it via
stringResource(Res.string.your_key)from a@Composablecontext.
- Strings used by
PasscodeScreenand 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-templateecosystem 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.