| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
A production-ready Android application demonstrating advanced localization techniques including runtime language switching, locale-aware datetime formatting with ICU skeletons, and intelligent caching—all built with Jetpack Compose.
This project showcases best practices for implementing localization in modern Android applications. Beyond basic language switching, it demonstrates production-ready patterns including:
The app dynamically displays available languages from BuildConfig and highlights the currently selected one.
Jetpack-Compose-Localization-Demo.mp4git clone https://github.com/hoc081098/Jetpack-Compose-Localization.git
cd Jetpack-Compose-Localization
./gradlew installDebugRun the app and tap on a language to see instant language switching with locale-aware datetime formatting!
app/src/main/
├── java/com/hoc081098/jetpackcomposelocalization/
│ ├── MainActivity.kt # Main activity with language switching
│ ├── DemoAcceptLanguageHeader.kt # Accept-Language header demo
│ ├── MyApplication.kt # Application class for initialization
│ ├── data/
│ │ ├── AcceptedLanguageInterceptor.kt # OkHttp interceptor for Accept-Language
│ │ ├── ApiService.kt # Retrofit API interface
│ │ └── NetworkServiceLocator.kt # Network service configuration
│ └── ui/
│ ├── locale/
│ │ ├── AppLocaleManager.kt # Locale management and state
│ │ └── currentLocale.kt # Composable to get current locale
│ ├── text/
│ │ └── DateTimeFormatterCache.kt # 🔥 Intelligent formatter caching
│ ├── time/
│ │ └── Instant.kt # Extension functions for time formatting
│ └── theme/
│ ├── Color.kt # Color definitions
│ ├── Theme.kt # Material Theme configuration
│ └── Type.kt # Typography definitions
└── res/
├── values/ # Default resources (English)
│ └── strings.xml
└── values-vi/ # Vietnamese resources
└── strings.xml
# Clone the repository
git clone https://github.com/hoc081098/Jetpack-Compose-Localization.git
cd Jetpack-Compose-Localization
# Build and install
./gradlew build
./gradlew installDebugOr open in Android Studio → Sync → Run (Shift + F10)
The app uses AppLocaleManager with AndroidX AppCompat's per-app language preferences API with support for "Follow System" mode:
@Stable
class AppLocaleManager {
fun changeLanguage(locale: AppLocaleState.AppLocale) {
val target = when (locale) {
AppLocaleState.AppLocale.FollowSystem ->
// Set empty locale list to follow system
LocaleListCompat.getEmptyLocaleList()
is AppLocaleState.AppLocale.Language ->
LocaleListCompat.create(locale.locale)
}
AppCompatDelegate.setApplicationLocales(target)
}
}Key benefits:
Utility function to reactively observe locale changes in Compose:
@Composable
@ReadOnlyComposable
fun currentLocale(): Locale =
ConfigurationCompat.getLocales(LocalConfiguration.current)[0]
?: LocaleListCompat.getAdjustedDefault()[0]!!One of the coolest features is the intelligent DateTimeFormatterCache that provides:
val formatter = DateTimeFormatterCache.getFormatterFromSkeleton(
locale = locale,
skeleton = "yMMMddHmss" // Year, abbreviated month, day, hours, minutes, seconds
)
val formattedTime = formatter.formatInstant(Instant.now(), ZoneId.systemDefault())
// Example outputs:
// English: "Jan 15, 2024, 2:30:45 PM"
// Vietnamese: "15 thg 1, 2024, 14:30:45"Why ICU skeletons?
// Clear cache when locale changes (optional, for memory management)
DateTimeFormatterCache.clear()
// Remove formatters for specific locale
DateTimeFormatterCache.removeLocale(locale)// Localized date formatter
val dateFormatter = DateTimeFormatterCache.getLocalizedDateFormatter(
locale = locale,
dateStyle = FormatStyle.MEDIUM
)
// Localized time formatter
val timeFormatter = DateTimeFormatterCache.getLocalizedTimeFormatter(
locale = locale,
timeStyle = FormatStyle.SHORT
)
// Localized date-time formatter
val dateTimeFormatter = DateTimeFormatterCache.getLocalizedDateTimeFormatter(
locale = locale,
dateStyle = FormatStyle.MEDIUM,
timeStyle = FormatStyle.SHORT
)Convenient extension functions for working with Instant:
// Format an Instant with a specific zone
val formatted = formatter.formatInstant(instant, zoneId)
// Convert Instant to ZonedDateTime
val zonedDateTime = instant.toZonedDateTime(ZoneId.systemDefault())The build configuration defines supported locales:
object Locales {
val localeFilters = listOf(
"en",
"vi-rVN",
)
val supportedLocales: String =
localeFilters.joinToString(
separator = ",",
prefix = "\"",
postfix = "\""
) {
it.replace(
oldValue = "-r",
newValue = "-"
)
}
}These are automatically exposed via BuildConfig.SUPPORTED_LOCALES (comma-separated string: "en,vi-VN").
The DateTimeFormatterCache implementation demonstrates enterprise-grade patterns:
Performance benefits:
When to clear the cache:
// Optional: Clear when locale changes
AppCompatDelegate.setApplicationLocales(newLocaleList)
DateTimeFormatterCache.clear() // Free up memory if neededThe app provides a "Follow System" option that:
Supported locales are automatically exposed via BuildConfig:
object Locales {
val localeFilters = listOf("en", "vi-rVN")
val supportedLocales: String = localeFilters.joinToString(",", "\"", "\"") {
it.replace("-r", "-")
}
}
// Available at runtime as: BuildConfig.SUPPORTED_LOCALES = "en,vi-VN"The AppLocaleManager parses this string to dynamically generate language options without hardcoding.
The app includes a practical demonstration of sending locale-aware HTTP requests with the Accept-Language header:
Key Components:
internal class AcceptedLanguageInterceptor(
private val localeProvider: LocaleProvider,
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val locales = localeProvider.provide()
val request = chain.request()
.newBuilder()
.addHeader("Accept-Language", locales.toLanguageTags())
.build()
return chain.proceed(request)
}
}object NetworkServiceLocator {
private val localeProvider: AcceptedLanguageInterceptor.LocaleProvider
get() = AcceptedLanguageInterceptor.LocaleProvider {
LocaleManagerCompat.getApplicationLocales(application)
.takeIf { it.size() > 0 }
?: LocaleManagerCompat.getSystemLocales(application)
}
private val okHttpClient: OkHttpClient by lazy {
OkHttpClient.Builder()
.addInterceptor(AcceptedLanguageInterceptor(localeProvider))
.build()
}
}DemoAcceptLanguageHeader - Composable UI that calls httpbin.org/get:
MyApplication - Initializes the network service locator:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
NetworkServiceLocator.init(this)
}
}Why this matters:
Adding a new language is straightforward:
1. Update build configuration (app/build.gradle.kts):
object Locales {
val localeFilters = listOf(
"en",
"vi-rVN",
"fr-rFR", // ← Add new locale
)
// ...
}2. Create resource directory app/src/main/res/values-{lang}/
3. Add strings.xml with translated strings:
<resources>
<string name="app_name">Votre Nom d\'App</string>
<string name="current_locale_language_country">Locale actuelle: %1$s, langue: %2$s, pays: %3$s, languageTag: %4$s</string>
<string name="follow_system">Suivre le système</string>
<string name="demo_datetime_formatter">Maintenant c\'est %1s</string>
</resources>4. Rebuild → Language appears automatically in the app! ✨
The app includes a live demonstration of locale-aware datetime formatting:
@Composable
private fun DemoDateTimeFormatter(
locale: Locale,
modifier: Modifier = Modifier,
clock: Clock = Clock.systemDefaultZone(),
) {
val now: Instant = remember(clock) { Instant.now(clock) }
val timeFormatter = DateTimeFormatterCache.getFormatterFromSkeleton(
locale = locale,
skeleton = "yMMMddHmss"
)
Text(
text = stringResource(
R.string.demo_datetime_formatter,
timeFormatter.formatInstant(now, clock.zone),
),
style = MaterialTheme.typography.bodyLarge,
)
}This demonstrates how date/time formatting automatically adapts to the selected locale without any manual formatting logic.
Modern edge-to-edge display with proper window insets handling:
enableEdgeToEdge()Implements Material You design (dynamic color disabled for consistency):
JetpackComposeLocalizationTheme(dynamicColor = false) {
// Content
}The app logs lifecycle events for debugging:
lifecycle.eventFlow
.onEach { Log.d("MainActivity", ">>> lifecycle event: $it") }
.launchIn(lifecycleScope)# Unit tests
./gradlew test
# Instrumentation tests
./gradlew connectedAndroidTest./gradlew assembleRelease
# APK output: app/build/outputs/apk/release/Contributions are welcome! Please:
Available for educational and demonstration purposes. See repository for license details.
hoc081098
If you find this project helpful, please consider giving it a ⭐️ on GitHub!
For issues, questions, or suggestions, please open an issue on the GitHub repository.
| Back | FazBrowse Home | New Git URL |