core-locale: locale-aware date formatting (currentLocale, localizedDateFormatter)

Both functions lived in Calendula's ui/common/LocaleSupport.kt. Neither is
calendar-specific — "lay this date out the way this language writes dates" is
family plumbing — so they move here, where Agendula can reach them too.

localizedDateFormatter(locale, skeleton) takes a field list ("LLLLy", "dMMMy")
and lets the locale arrange it, which is the only way to render a date correctly
in a language you didn't hand-write a template for. Dropping a field from the
skeleton drops it from the output, so callers ask for "no year" by omitting it
rather than post-processing a longer pattern.

currentLocale() reads the display locale from LocalConfiguration. This module's
own AppLanguage.apply() changes the language inside a running process, so a
locale captured once goes stale; keying on the configuration makes the UI follow
the switch.

core-locale gains Compose (runtime + ui only) for currentLocale(). Its build file
previously declared itself Compose-free, but the alternative — parking the hook
in a Compose module that depends back on this one — splits one concern across two
modules to honour a comment. core-crash is the precedent for a core-* module
carrying Compose.

Leaves core-time's Instant.formatDate() family alone despite the overlap: it is
unused by Calendula but visible to Agendula, so reconciling it is that app's
call, not a drive-by here. Noted in the changelog.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-17 09:47:36 +02:00
parent 2124227a7f
commit 45ca3243fe
5 changed files with 121 additions and 6 deletions

View File

@@ -1,10 +1,20 @@
// core-locale — per-app language plumbing shared across the family: read the
// shipped languages from res/xml/locales_config.xml, get/set the applied
// language via AppCompatDelegate, and render each language's autonym. App-
// agnostic — the consuming app passes its own locales_config resource id; no
// Compose, no DataStore.
// core-locale — everything "what language are we in", shared across the family:
// read the shipped languages from res/xml/locales_config.xml, get/set the applied
// language via AppCompatDelegate, render each language's autonym, observe the
// current locale from Compose, and format dates in that locale's own field order.
// App-agnostic — the consuming app passes its own locales_config resource id; no
// DataStore.
//
// Compose is here for currentLocale() alone: applying a language mid-process is
// this module's own API, so the hook that makes the UI notice belongs beside it
// rather than in a Compose module that would have to depend back on this one.
// (core-crash is the precedent for a core-* module carrying Compose.) Deliberately
// lean: runtime + ui only, no material3, no foundation.
plugins {
// AGP 9.x provides built-in Kotlin compilation, so only the Compose plugin is
// applied alongside the Android library plugin.
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.compose)
}
android {
@@ -20,6 +30,10 @@ android {
targetCompatibility = JavaVersion.VERSION_17
}
buildFeatures {
compose = true
}
testOptions {
unitTests {
all { it.useJUnitPlatform() }
@@ -39,6 +53,16 @@ dependencies {
// annotation (XmlRes) transitives.
implementation(libs.androidx.appcompat)
// ConfigurationCompat for currentLocale(). Reachable transitively via
// appcompat, but declared because this module uses it directly.
implementation(libs.androidx.core.ktx)
// currentLocale() only needs the runtime (@Composable, remember) and ui
// (LocalConfiguration) — api() so consumers can use the returned Locale in
// their own composables without re-declaring Compose themselves.
api(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
testImplementation(libs.junit.jupiter.api)
testRuntimeOnly(libs.junit.jupiter.engine)
testRuntimeOnly(libs.junit.platform.launcher)