{"id":31778028,"url":"https://github.com/hoc081098/jetpack-compose-localization","last_synced_at":"2025-10-10T06:22:42.035Z","repository":{"id":318017265,"uuid":"1069043496","full_name":"hoc081098/Jetpack-Compose-Localization","owner":"hoc081098","description":"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.","archived":false,"fork":false,"pushed_at":"2025-10-04T15:11:03.000Z","size":142,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-04T15:26:34.060Z","etag":null,"topics":["android-compose","android-jetpack-compose","android-localization","compose-localization","jetpack-compose","jetpack-compose-localization"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hoc081098.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-03T10:08:14.000Z","updated_at":"2025-10-04T15:11:06.000Z","dependencies_parsed_at":null,"dependency_job_id":"4b47042a-e192-49de-aceb-1c26b5b8a4b8","html_url":"https://github.com/hoc081098/Jetpack-Compose-Localization","commit_stats":null,"previous_names":["hoc081098/jetpack-compose-localization"],"tags_count":2,"template":true,"template_full_name":null,"purl":"pkg:github/hoc081098/Jetpack-Compose-Localization","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hoc081098%2FJetpack-Compose-Localization","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hoc081098%2FJetpack-Compose-Localization/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hoc081098%2FJetpack-Compose-Localization/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hoc081098%2FJetpack-Compose-Localization/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hoc081098","download_url":"https://codeload.github.com/hoc081098/Jetpack-Compose-Localization/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hoc081098%2FJetpack-Compose-Localization/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279002973,"owners_count":26083489,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android-compose","android-jetpack-compose","android-localization","compose-localization","jetpack-compose","jetpack-compose-localization"],"created_at":"2025-10-10T06:22:30.682Z","updated_at":"2025-10-10T06:22:42.026Z","avatar_url":"https://github.com/hoc081098.png","language":"Kotlin","readme":"# Jetpack Compose Localization\n\nA 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.\n\n[![Android CI](https://github.com/hoc081098/Jetpack-Compose-Localization/actions/workflows/android.yml/badge.svg)](https://github.com/hoc081098/Jetpack-Compose-Localization/actions/workflows/android.yml)\n\n## Overview\n\nThis project showcases **best practices** for implementing localization in modern Android applications. Beyond basic language switching, it demonstrates production-ready patterns including:\n- ⚡ **Zero-restart language switching** using AndroidX AppCompat's per-app language preferences API\n- 🕐 **Locale-aware datetime formatting** with ICU skeleton patterns and intelligent caching\n- 🎨 **Material 3** design with edge-to-edge display\n- 🔄 **Follow system locale** option for seamless integration with device settings\n\n## Features\n\n- **🚀 Runtime Language Switching**: Change app language instantly without restarting\n- **⚡ DateTimeFormatter Caching**: Production-ready cache system for optimal performance\n- **🌍 ICU Skeleton Support**: Locale-aware date/time formatting using ICU skeleton patterns\n- **📱 Modern Jetpack Compose UI**: Pure Compose implementation with Material 3\n- **🔀 Follow System Option**: Seamlessly follow device locale settings\n- **🎯 Per-App Language Settings**: Uses AndroidX AppCompat's `setApplicationLocales()` API (API 27+)\n- **📊 Live Locale Information**: Real-time display of current locale details\n- **🌐 Accept-Language Header Demo**: HTTP request demonstration with automatic locale-aware Accept-Language headers\n\n## Supported Languages\n\n- **English** (en)\n- **Vietnamese** (vi-VN)\n\nThe app dynamically displays available languages from `BuildConfig` and highlights the currently selected one.\n\nhttps://github.com/user-attachments/assets/1dfaab2c-747b-4974-a1d9-96c1a57fe52a\n\n## Quick Start\n\n```bash\ngit clone https://github.com/hoc081098/Jetpack-Compose-Localization.git\ncd Jetpack-Compose-Localization\n./gradlew installDebug\n```\n\nRun the app and tap on a language to see instant language switching with locale-aware datetime formatting!\n\n## Requirements \u0026 Tech Stack\n\n### Core Requirements\n- **Min SDK**: API 27 (Android 8.1)\n- **Target/Compile SDK**: API 36 (Android 14)\n- **Kotlin**: 2.0.21\n- **Gradle**: 8.13.0\n- **Java**: 11+\n\n### Key Technologies\n- **Jetpack Compose** - Modern declarative UI\n- **Material 3** - Material Design 3 components\n- **AndroidX AppCompat** - Per-app language preferences API\n- **AndroidX Lifecycle** - Lifecycle-aware components\n- **Java Time API** - Modern date/time handling with ICU patterns\n- **Retrofit** - Type-safe HTTP client for network requests\n- **Moshi** - Modern JSON library for Kotlin\n- **OkHttp** - HTTP client with interceptor support\n\n## Project Structure\n\n```\napp/src/main/\n├── java/com/hoc081098/jetpackcomposelocalization/\n│   ├── MainActivity.kt                          # Main activity with language switching\n│   ├── DemoAcceptLanguageHeader.kt              # Accept-Language header demo\n│   ├── MyApplication.kt                         # Application class for initialization\n│   ├── data/\n│   │   ├── AcceptedLanguageInterceptor.kt       # OkHttp interceptor for Accept-Language\n│   │   ├── ApiService.kt                        # Retrofit API interface\n│   │   └── NetworkServiceLocator.kt             # Network service configuration\n│   └── ui/\n│       ├── locale/\n│       │   ├── AppLocaleManager.kt              # Locale management and state\n│       │   └── currentLocale.kt                 # Composable to get current locale\n│       ├── text/\n│       │   └── DateTimeFormatterCache.kt        # 🔥 Intelligent formatter caching\n│       ├── time/\n│       │   └── Instant.kt                       # Extension functions for time formatting\n│       └── theme/\n│           ├── Color.kt                         # Color definitions\n│           ├── Theme.kt                         # Material Theme configuration\n│           └── Type.kt                          # Typography definitions\n└── res/\n    ├── values/                                  # Default resources (English)\n    │   └── strings.xml\n    └── values-vi/                               # Vietnamese resources\n        └── strings.xml\n```\n\n## Setup and Installation\n\n### Prerequisites\n\n- Android Studio (latest version)\n- JDK 11 or higher\n- Android emulator or physical device\n\n### Build \u0026 Run\n\n```bash\n# Clone the repository\ngit clone https://github.com/hoc081098/Jetpack-Compose-Localization.git\ncd Jetpack-Compose-Localization\n\n# Build and install\n./gradlew build\n./gradlew installDebug\n```\n\n**Or** open in Android Studio → Sync → Run (Shift + F10)\n\n## How It Works\n\n### Language Switching\n\nThe app uses `AppLocaleManager` with AndroidX AppCompat's per-app language preferences API with support for \"Follow System\" mode:\n\n```kotlin\n@Stable\nclass AppLocaleManager {\n  fun changeLanguage(locale: AppLocaleState.AppLocale) {\n    val target = when (locale) {\n      AppLocaleState.AppLocale.FollowSystem -\u003e\n        // Set empty locale list to follow system\n        LocaleListCompat.getEmptyLocaleList()\n\n      is AppLocaleState.AppLocale.Language -\u003e\n        LocaleListCompat.create(locale.locale)\n    }\n    AppCompatDelegate.setApplicationLocales(target)\n  }\n}\n```\n\n**Key benefits:**\n- ✅ Works on Android 8.1+ (API 27)\n- ✅ Persists preference across app restarts\n- ✅ Integrates with Android 13+ system language settings\n- ✅ No app restart required\n- ✅ Seamlessly follows system locale when user prefers\n\n### Getting Current Locale in Compose\n\nUtility function to reactively observe locale changes in Compose:\n\n```kotlin\n@Composable\n@ReadOnlyComposable\nfun currentLocale(): Locale =\n  ConfigurationCompat.getLocales(LocalConfiguration.current)[0]\n    ?: LocaleListCompat.getAdjustedDefault()[0]!!\n```\n\n### 🔥 DateTimeFormatter Caching (Production-Ready)\n\nOne of the **coolest features** is the intelligent `DateTimeFormatterCache` that provides:\n- **Thread-safe caching** of immutable DateTimeFormatter instances\n- **ICU skeleton support** for locale-aware patterns (e.g., \"yMd\", \"jm\", \"yMMMdjm\")\n- **Automatic 12h/24h normalization** based on user preference\n- **Localized styles** (SHORT, MEDIUM, LONG, FULL)\n- **Per-locale cache management** for optimal memory usage\n\n#### Using ICU Skeletons\n\n```kotlin\nval formatter = DateTimeFormatterCache.getFormatterFromSkeleton(\n  locale = locale,\n  skeleton = \"yMMMddHmss\"  // Year, abbreviated month, day, hours, minutes, seconds\n)\n\nval formattedTime = formatter.formatInstant(Instant.now(), ZoneId.systemDefault())\n// Example outputs:\n// English: \"Jan 15, 2024, 2:30:45 PM\"\n// Vietnamese: \"15 thg 1, 2024, 14:30:45\"\n```\n\n**Why ICU skeletons?**\n- 🌍 Automatically adapt to locale conventions\n- 🎯 More flexible than rigid patterns\n- 🔒 Safer than `DateTimeFormatter.ofPattern()` for user-facing text\n- ⚡ Cached for optimal performance\n\n#### Cache Management\n\n```kotlin\n// Clear cache when locale changes (optional, for memory management)\nDateTimeFormatterCache.clear()\n\n// Remove formatters for specific locale\nDateTimeFormatterCache.removeLocale(locale)\n```\n\n#### Additional Formatter Options\n\n```kotlin\n// Localized date formatter\nval dateFormatter = DateTimeFormatterCache.getLocalizedDateFormatter(\n  locale = locale,\n  dateStyle = FormatStyle.MEDIUM\n)\n\n// Localized time formatter\nval timeFormatter = DateTimeFormatterCache.getLocalizedTimeFormatter(\n  locale = locale,\n  timeStyle = FormatStyle.SHORT\n)\n\n// Localized date-time formatter\nval dateTimeFormatter = DateTimeFormatterCache.getLocalizedDateTimeFormatter(\n  locale = locale,\n  dateStyle = FormatStyle.MEDIUM,\n  timeStyle = FormatStyle.SHORT\n)\n```\n\n### Time Formatting Extensions\n\nConvenient extension functions for working with `Instant`:\n\n```kotlin\n// Format an Instant with a specific zone\nval formatted = formatter.formatInstant(instant, zoneId)\n\n// Convert Instant to ZonedDateTime\nval zonedDateTime = instant.toZonedDateTime(ZoneId.systemDefault())\n```\n\n### Locale Configuration\n\nThe build configuration defines supported locales:\n\n```kotlin\nobject Locales {\n  val localeFilters = listOf(\n    \"en\",\n    \"vi-rVN\",\n  )\n\n  val supportedLocales: String =\n    localeFilters.joinToString(\n      separator = \",\",\n      prefix = \"\\\"\",\n      postfix = \"\\\"\"\n    ) {\n      it.replace(\n        oldValue = \"-r\",\n        newValue = \"-\"\n      )\n    }\n}\n```\n\nThese are automatically exposed via `BuildConfig.SUPPORTED_LOCALES` (comma-separated string: `\"en,vi-VN\"`).\n\n## Advanced Features\n\n### 🔥 Why DateTimeFormatterCache is Production-Ready\n\nThe `DateTimeFormatterCache` implementation demonstrates enterprise-grade patterns:\n\n1. **Thread-Safety**: Uses `ConcurrentHashMap` for safe concurrent access\n2. **Immutability**: DateTimeFormatter instances are immutable and thread-safe\n3. **Smart Key Generation**: Combines locale + descriptor + flags for precise caching\n4. **Memory Management**: Per-locale removal for granular cache control\n5. **ICU Skeleton Normalization**: Automatically handles 12h/24h preferences\n\n**Performance benefits:**\n- Avoids repeated expensive pattern compilation\n- Reduces garbage collection pressure\n- Ideal for apps with frequent datetime formatting\n\n**When to clear the cache:**\n```kotlin\n// Optional: Clear when locale changes\nAppCompatDelegate.setApplicationLocales(newLocaleList)\nDateTimeFormatterCache.clear()  // Free up memory if needed\n```\n\n### Follow System Locale\n\nThe app provides a \"Follow System\" option that:\n- Sets empty locale list: `AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList())`\n- Automatically adopts system locale changes\n- Integrates seamlessly with Android 13+ per-app language settings\n\n### BuildConfig Integration\n\nSupported locales are automatically exposed via BuildConfig:\n\n```kotlin\nobject Locales {\n  val localeFilters = listOf(\"en\", \"vi-rVN\")\n  val supportedLocales: String = localeFilters.joinToString(\",\", \"\\\"\", \"\\\"\") { \n    it.replace(\"-r\", \"-\") \n  }\n}\n// Available at runtime as: BuildConfig.SUPPORTED_LOCALES = \"en,vi-VN\"\n```\n\nThe `AppLocaleManager` parses this string to dynamically generate language options without hardcoding.\n\n### Accept-Language Header Demo\n\nThe app includes a practical demonstration of sending locale-aware HTTP requests with the Accept-Language header:\n\n**Key Components:**\n\n1. **AcceptedLanguageInterceptor** - OkHttp interceptor that automatically adds Accept-Language header:\n```kotlin\ninternal class AcceptedLanguageInterceptor(\n  private val localeProvider: LocaleProvider,\n) : Interceptor {\n  override fun intercept(chain: Interceptor.Chain): Response {\n    val locales = localeProvider.provide()\n    val request = chain.request()\n      .newBuilder()\n      .addHeader(\"Accept-Language\", locales.toLanguageTags())\n      .build()\n    return chain.proceed(request)\n  }\n}\n```\n\n2. **NetworkServiceLocator** - Configures OkHttp with the interceptor:\n```kotlin\nobject NetworkServiceLocator {\n  private val localeProvider: AcceptedLanguageInterceptor.LocaleProvider\n    get() = AcceptedLanguageInterceptor.LocaleProvider {\n      LocaleManagerCompat.getApplicationLocales(application)\n        .takeIf { it.size() \u003e 0 }\n        ?: LocaleManagerCompat.getSystemLocales(application)\n    }\n\n  private val okHttpClient: OkHttpClient by lazy {\n    OkHttpClient.Builder()\n      .addInterceptor(AcceptedLanguageInterceptor(localeProvider))\n      .build()\n  }\n}\n```\n\n3. **DemoAcceptLanguageHeader** - Composable UI that calls httpbin.org/get:\n   - Press \"GET\" to make a request to httpbin.org\n   - The server echoes back the Accept-Language header\n   - Shows how different locales result in different Accept-Language values\n   - Example: English → `\"en\"`, Vietnamese → `\"vi-VN\"`\n\n4. **MyApplication** - Initializes the network service locator:\n```kotlin\nclass MyApplication : Application() {\n  override fun onCreate() {\n    super.onCreate()\n    NetworkServiceLocator.init(this)\n  }\n}\n```\n\n**Why this matters:**\n- Demonstrates real-world usage of locale information in API calls\n- Shows proper architecture for locale-aware networking\n- Useful pattern for apps that need server-side localization\n- The Accept-Language header helps servers return content in the user's preferred language\n\n## Adding New Languages\n\nAdding a new language is straightforward:\n\n**1. Update build configuration** (`app/build.gradle.kts`):\n```kotlin\nobject Locales {\n  val localeFilters = listOf(\n    \"en\",\n    \"vi-rVN\",\n    \"fr-rFR\",  // ← Add new locale\n  )\n  // ...\n}\n```\n\n**2. Create resource directory** `app/src/main/res/values-{lang}/`\n\n**3. Add `strings.xml`** with translated strings:\n```xml\n\u003cresources\u003e\n    \u003cstring name=\"app_name\"\u003eVotre Nom d\\'App\u003c/string\u003e\n    \u003cstring name=\"current_locale_language_country\"\u003eLocale actuelle: %1$s, langue: %2$s, pays: %3$s, languageTag: %4$s\u003c/string\u003e\n    \u003cstring name=\"follow_system\"\u003eSuivre le système\u003c/string\u003e\n    \u003cstring name=\"demo_datetime_formatter\"\u003eMaintenant c\\'est %1s\u003c/string\u003e\n\u003c/resources\u003e\n```\n\n**4. Rebuild** → Language appears automatically in the app! ✨\n\n## Key Features Implementation\n\n### Live DateTime Demo\n\nThe app includes a live demonstration of locale-aware datetime formatting:\n\n```kotlin\n@Composable\nprivate fun DemoDateTimeFormatter(\n  locale: Locale,\n  modifier: Modifier = Modifier,\n  clock: Clock = Clock.systemDefaultZone(),\n) {\n  val now: Instant = remember(clock) { Instant.now(clock) }\n  val timeFormatter = DateTimeFormatterCache.getFormatterFromSkeleton(\n    locale = locale,\n    skeleton = \"yMMMddHmss\"\n  )\n\n  Text(\n    text = stringResource(\n      R.string.demo_datetime_formatter,\n      timeFormatter.formatInstant(now, clock.zone),\n    ),\n    style = MaterialTheme.typography.bodyLarge,\n  )\n}\n```\n\nThis demonstrates how date/time formatting automatically adapts to the selected locale without any manual formatting logic.\n\n### Edge-to-Edge Display\n\nModern edge-to-edge display with proper window insets handling:\n\n```kotlin\nenableEdgeToEdge()\n```\n\n### Material 3 Theming\n\nImplements Material You design (dynamic color disabled for consistency):\n\n```kotlin\nJetpackComposeLocalizationTheme(dynamicColor = false) {\n  // Content\n}\n```\n\n### Lifecycle Awareness\n\nThe app logs lifecycle events for debugging:\n\n```kotlin\nlifecycle.eventFlow\n  .onEach { Log.d(\"MainActivity\", \"\u003e\u003e\u003e lifecycle event: $it\") }\n  .launchIn(lifecycleScope)\n```\n\n## Testing\n\n```bash\n# Unit tests\n./gradlew test\n\n# Instrumentation tests\n./gradlew connectedAndroidTest\n```\n\n## Building for Release\n\n```bash\n./gradlew assembleRelease\n# APK output: app/build/outputs/apk/release/\n```\n\n## Contributing\n\nContributions are welcome! Please:\n1. Open an issue first for major changes\n2. Follow Kotlin conventions\n3. Add tests for new features\n4. Update documentation\n5. Ensure tests pass before submitting PR\n\n## License\n\nAvailable for educational and demonstration purposes. See repository for license details.\n\n## Acknowledgments\n\n- Built with [Jetpack Compose](https://developer.android.com/jetpack/compose)\n- [AndroidX AppCompat](https://developer.android.com/jetpack/androidx/releases/appcompat) for per-app language preferences\n- [Material Design 3](https://m3.material.io/) guidelines\n- [ICU](https://developer.android.com/guide/topics/resources/internationalization) for locale-aware patterns\n\n## Resources\n\n- [Android Localization Guide](https://developer.android.com/guide/topics/resources/localization)\n- [Per-app language preferences](https://developer.android.com/guide/topics/resources/app-languages)\n- [Jetpack Compose Documentation](https://developer.android.com/jetpack/compose/documentation)\n- [Material Design 3](https://m3.material.io/)\n\n## Author\n\n**hoc081098**\n\n- GitHub: [@hoc081098](https://github.com/hoc081098)\n\n## Support\n\nIf you find this project helpful, please consider giving it a ⭐️ on GitHub!\n\nFor issues, questions, or suggestions, please open an issue on the [GitHub repository](https://github.com/hoc081098/Jetpack-Compose-Localization/issues).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhoc081098%2Fjetpack-compose-localization","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhoc081098%2Fjetpack-compose-localization","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhoc081098%2Fjetpack-compose-localization/lists"}