@@ -21,11 +21,12 @@ This project showcases **best practices** for implementing localization in moder
2121- ** 🔀 Follow System Option** : Seamlessly follow device locale settings
2222- ** 🎯 Per-App Language Settings** : Uses AndroidX AppCompat's ` setApplicationLocales() ` API (API 27+)
2323- ** 📊 Live Locale Information** : Real-time display of current locale details
24+ - ** 🌐 Accept-Language Header Demo** : HTTP request demonstration with automatic locale-aware Accept-Language headers
2425
2526## Supported Languages
2627
27- - ** English** (en, en-US )
28- - ** Vietnamese** (vi, vi -VN)
28+ - ** English** (en)
29+ - ** Vietnamese** (vi-VN)
2930
3031The app dynamically displays available languages from ` BuildConfig ` and highlights the currently selected one.
3132
@@ -56,28 +57,38 @@ Run the app and tap on a language to see instant language switching with locale-
5657- ** AndroidX AppCompat** - Per-app language preferences API
5758- ** AndroidX Lifecycle** - Lifecycle-aware components
5859- ** Java Time API** - Modern date/time handling with ICU patterns
60+ - ** Retrofit** - Type-safe HTTP client for network requests
61+ - ** Moshi** - Modern JSON library for Kotlin
62+ - ** OkHttp** - HTTP client with interceptor support
5963
6064## Project Structure
6165
6266```
6367app/src/main/
6468├── java/com/hoc081098/jetpackcomposelocalization/
6569│ ├── MainActivity.kt # Main activity with language switching
70+ │ ├── DemoAcceptLanguageHeader.kt # Accept-Language header demo
71+ │ ├── MyApplication.kt # Application class for initialization
72+ │ ├── data/
73+ │ │ ├── AcceptedLanguageInterceptor.kt # OkHttp interceptor for Accept-Language
74+ │ │ ├── ApiService.kt # Retrofit API interface
75+ │ │ └── NetworkServiceLocator.kt # Network service configuration
6676│ └── ui/
6777│ ├── locale/
68- │ │ └── currentLocale.kt # Composable to get current locale
78+ │ │ ├── AppLocaleManager.kt # Locale management and state
79+ │ │ └── currentLocale.kt # Composable to get current locale
6980│ ├── text/
70- │ │ └── DateTimeFormatterCache.kt # 🔥 Intelligent formatter caching
81+ │ │ └── DateTimeFormatterCache.kt # 🔥 Intelligent formatter caching
7182│ ├── time/
72- │ │ └── Instant.kt # Extension functions for time formatting
83+ │ │ └── Instant.kt # Extension functions for time formatting
7384│ └── theme/
74- │ ├── Color.kt # Color definitions
75- │ ├── Theme.kt # Material Theme configuration
76- │ └── Type.kt # Typography definitions
85+ │ ├── Color.kt # Color definitions
86+ │ ├── Theme.kt # Material Theme configuration
87+ │ └── Type.kt # Typography definitions
7788└── res/
78- ├── values/ # Default resources (English)
89+ ├── values/ # Default resources (English)
7990 │ └── strings.xml
80- └── values-vi/ # Vietnamese resources
91+ └── values-vi/ # Vietnamese resources
8192 └── strings.xml
8293```
8394
@@ -107,16 +118,21 @@ cd Jetpack-Compose-Localization
107118
108119### Language Switching
109120
110- The app uses AndroidX AppCompat's per-app language preferences API with support for "Follow System" mode:
121+ The app uses ` AppLocaleManager ` with AndroidX AppCompat's per-app language preferences API with support for "Follow System" mode:
111122
112123``` kotlin
113- private fun changeLanguage (language : String ) {
114- if (language == FOLLOW_SYSTEM ) {
115- // Set empty locale list to follow system
116- AppCompatDelegate .setApplicationLocales(LocaleListCompat .getEmptyLocaleList())
117- } else {
118- val locale = Locale (language)
119- AppCompatDelegate .setApplicationLocales(LocaleListCompat .create(locale))
124+ @Stable
125+ class AppLocaleManager {
126+ fun changeLanguage (locale : AppLocaleState .AppLocale ) {
127+ val target = when (locale) {
128+ AppLocaleState .AppLocale .FollowSystem ->
129+ // Set empty locale list to follow system
130+ LocaleListCompat .getEmptyLocaleList()
131+
132+ is AppLocaleState .AppLocale .Language ->
133+ LocaleListCompat .create(locale.locale)
134+ }
135+ AppCompatDelegate .setApplicationLocales(target)
120136 }
121137}
122138```
@@ -219,15 +235,27 @@ val zonedDateTime = instant.toZonedDateTime(ZoneId.systemDefault())
219235The build configuration defines supported locales:
220236
221237``` kotlin
222- val SUPPORTED_LOCALES = setOf (
223- " en" ,
224- " en-rUS" ,
225- " vi" ,
226- " vi-rVN" ,
227- )
238+ object Locales {
239+ val localeFilters = listOf (
240+ " en" ,
241+ " vi-rVN" ,
242+ )
243+
244+ val supportedLocales: String =
245+ localeFilters.joinToString(
246+ separator = " ," ,
247+ prefix = " \" " ,
248+ postfix = " \" "
249+ ) {
250+ it.replace(
251+ oldValue = " -r" ,
252+ newValue = " -"
253+ )
254+ }
255+ }
228256```
229257
230- These are automatically filtered during the build process and available via ` BuildConfig.SUPPORTED_LANGUAGE_CODES ` .
258+ These are automatically exposed via ` BuildConfig.SUPPORTED_LOCALES ` (comma-separated string: ` "en,vi-VN" ` ) .
231259
232260## Advanced Features
233261
@@ -265,23 +293,93 @@ The app provides a "Follow System" option that:
265293Supported locales are automatically exposed via BuildConfig:
266294
267295``` kotlin
268- val SUPPORTED_LOCALES = setOf (" en" , " en-rUS" , " vi" , " vi-rVN" )
269- // Available at runtime as: BuildConfig.SUPPORTED_LANGUAGE_CODES
296+ object Locales {
297+ val localeFilters = listOf (" en" , " vi-rVN" )
298+ val supportedLocales: String = localeFilters.joinToString(" ," , " \" " , " \" " ) {
299+ it.replace(" -r" , " -" )
300+ }
301+ }
302+ // Available at runtime as: BuildConfig.SUPPORTED_LOCALES = "en,vi-VN"
270303```
271304
272- This enables dynamic UI generation without hardcoding language options.
305+ The ` AppLocaleManager ` parses this string to dynamically generate language options without hardcoding.
306+
307+ ### Accept-Language Header Demo
308+
309+ The app includes a practical demonstration of sending locale-aware HTTP requests with the Accept-Language header:
310+
311+ ** Key Components:**
312+
313+ 1 . ** AcceptedLanguageInterceptor** - OkHttp interceptor that automatically adds Accept-Language header:
314+ ``` kotlin
315+ internal class AcceptedLanguageInterceptor (
316+ private val localeProvider : LocaleProvider ,
317+ ) : Interceptor {
318+ override fun intercept (chain : Interceptor .Chain ): Response {
319+ val locales = localeProvider.provide()
320+ val request = chain.request()
321+ .newBuilder()
322+ .addHeader(" Accept-Language" , locales.toLanguageTags())
323+ .build()
324+ return chain.proceed(request)
325+ }
326+ }
327+ ```
328+
329+ 2 . ** NetworkServiceLocator** - Configures OkHttp with the interceptor:
330+ ``` kotlin
331+ object NetworkServiceLocator {
332+ private val localeProvider: AcceptedLanguageInterceptor .LocaleProvider
333+ get() = AcceptedLanguageInterceptor .LocaleProvider {
334+ LocaleManagerCompat .getApplicationLocales(application)
335+ .takeIf { it.size() > 0 }
336+ ? : LocaleManagerCompat .getSystemLocales(application)
337+ }
338+
339+ private val okHttpClient: OkHttpClient by lazy {
340+ OkHttpClient .Builder ()
341+ .addInterceptor(AcceptedLanguageInterceptor (localeProvider))
342+ .build()
343+ }
344+ }
345+ ```
346+
347+ 3 . ** DemoAcceptLanguageHeader** - Composable UI that calls httpbin.org/get:
348+ - Press "GET" to make a request to httpbin.org
349+ - The server echoes back the Accept-Language header
350+ - Shows how different locales result in different Accept-Language values
351+ - Example: English → ` "en" ` , Vietnamese → ` "vi-VN" `
352+
353+ 4 . ** MyApplication** - Initializes the network service locator:
354+ ``` kotlin
355+ class MyApplication : Application () {
356+ override fun onCreate () {
357+ super .onCreate()
358+ NetworkServiceLocator .init (this )
359+ }
360+ }
361+ ```
362+
363+ ** Why this matters:**
364+ - Demonstrates real-world usage of locale information in API calls
365+ - Shows proper architecture for locale-aware networking
366+ - Useful pattern for apps that need server-side localization
367+ - The Accept-Language header helps servers return content in the user's preferred language
273368
274369## Adding New Languages
275370
276371Adding a new language is straightforward:
277372
278373** 1. Update build configuration** (` app/build.gradle.kts ` ):
279374``` kotlin
280- val SUPPORTED_LOCALES = setOf (
281- " en" , " en-rUS" ,
282- " vi" , " vi-rVN" ,
283- " fr" , " fr-rFR" , // ← Add new locale
284- )
375+ object Locales {
376+ val localeFilters = listOf (
377+ " en" ,
378+ " vi-rVN" ,
379+ " fr-rFR" , // ← Add new locale
380+ )
381+ // ...
382+ }
285383```
286384
287385** 2. Create resource directory** ` app/src/main/res/values-{lang}/ `
0 commit comments