From 99f1b5a88e7e5381f2f17feda4723930a8dc2e02 Mon Sep 17 00:00:00 2001 From: Akihiro Nagai <77012577+314systems@users.noreply.github.com> Date: Fri, 26 Dec 2025 23:07:20 +0900 Subject: [PATCH 01/12] enable locale config generation --- app/build.gradle | 4 ++++ app/src/main/res/resources.properties | 1 + 2 files changed, 5 insertions(+) create mode 100644 app/src/main/res/resources.properties diff --git a/app/build.gradle b/app/build.gradle index 64bebb1a..f2e4c15f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -126,6 +126,10 @@ android { lint { lintConfig = file("lint.xml") } + + androidResources { + generateLocaleConfig true + } } allOpen { diff --git a/app/src/main/res/resources.properties b/app/src/main/res/resources.properties new file mode 100644 index 00000000..467b3efe --- /dev/null +++ b/app/src/main/res/resources.properties @@ -0,0 +1 @@ +unqualifiedResLocale=en-US From 943a4dd0df7afdc3abb97e2b24981a7355699f58 Mon Sep 17 00:00:00 2001 From: Akihiro Nagai <77012577+314systems@users.noreply.github.com> Date: Fri, 26 Dec 2025 23:11:27 +0900 Subject: [PATCH 02/12] add AppLocalesMetadataHolderService to manifest --- app/src/main/AndroidManifest.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 672e2624..e2c13b7c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -55,5 +55,14 @@ + + + + From 83771c16ee56877f5b59e3ea5c2350da21c39b39 Mon Sep 17 00:00:00 2001 From: Akihiro Nagai <77012577+314systems@users.noreply.github.com> Date: Sat, 27 Dec 2025 13:23:12 +0900 Subject: [PATCH 03/12] refactor LocaleUtils and update settings references --- .../main/kotlin/com/vrem/util/LocaleUtils.kt | 82 +++++++++---------- .../settings/CountryPreference.kt | 4 +- .../settings/LanguagePreference.kt | 4 +- .../vrem/wifianalyzer/settings/Settings.kt | 10 +-- .../kotlin/com/vrem/util/LocaleUtilsTest.kt | 8 +- .../wifianalyzer/settings/SettingsTest.kt | 8 +- 6 files changed, 57 insertions(+), 59 deletions(-) diff --git a/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt b/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt index 2fba582a..29cf2f2f 100644 --- a/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt +++ b/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt @@ -20,38 +20,13 @@ package com.vrem.util import java.util.Locale import java.util.SortedMap -private object SyncAvoid { - val defaultLocale: Locale = Locale.getDefault() - val countryCodes: Set = Locale.getISOCountries().toSet() - val availableLocales: List = Locale.getAvailableLocales().filter { countryCodes.contains(it.country) } - - val countriesLocales: SortedMap = - availableLocales - .associateBy { it.country.toCapitalize(Locale.getDefault()) } - .toSortedMap() - val supportedLocales: List = - setOf( - BULGARIAN, - DUTCH, - GREEK, - HUNGARIAN, - Locale.SIMPLIFIED_CHINESE, - Locale.TRADITIONAL_CHINESE, - Locale.ENGLISH, - Locale.FRENCH, - Locale.GERMAN, - Locale.ITALIAN, - Locale.JAPANESE, - POLISH, - PORTUGUESE_BRAZIL, - PORTUGUESE_PORTUGAL, - SPANISH, - RUSSIAN, - TURKISH, - UKRAINIAN, - defaultLocale, - ).toList() -} +private val currentLocale: Locale get() = Locale.getDefault() +private val countryCodes: Set = Locale.getISOCountries().toSet() +private val availableLocales: List = Locale.getAvailableLocales().filter { countryCodes.contains(it.country) } +private val countriesLocales: SortedMap = + availableLocales + .associateBy { it.country.toCapitalize(currentLocale) } + .toSortedMap() val BULGARIAN: Locale = Locale.forLanguageTag("bg") val DUTCH: Locale = Locale.forLanguageTag("nl") @@ -65,27 +40,50 @@ val RUSSIAN: Locale = Locale.forLanguageTag("ru") val TURKISH: Locale = Locale.forLanguageTag("tr") val UKRAINIAN: Locale = Locale.forLanguageTag("uk") +val baseSupportedLocales: List = + setOf( + BULGARIAN, + DUTCH, + GREEK, + HUNGARIAN, + Locale.SIMPLIFIED_CHINESE, + Locale.TRADITIONAL_CHINESE, + Locale.ENGLISH, + Locale.FRENCH, + Locale.GERMAN, + Locale.ITALIAN, + Locale.JAPANESE, + POLISH, + PORTUGUESE_BRAZIL, + PORTUGUESE_PORTUGAL, + SPANISH, + RUSSIAN, + TURKISH, + UKRAINIAN, + ).toList() + private const val SEPARATOR: String = "_" fun findByCountryCode(countryCode: String): Locale = - SyncAvoid.availableLocales.firstOrNull { countryCode.toCapitalize(Locale.getDefault()) == it.country } - ?: SyncAvoid.defaultLocale + availableLocales.firstOrNull { countryCode.toCapitalize(Locale.getDefault()) == it.country } + ?: currentLocale -fun allCountries(): List = SyncAvoid.countriesLocales.values.toList() +fun allCountries(): List = countriesLocales.values.toList() + +fun supportedLanguages(): List = + (baseSupportedLocales + currentLocale).distinct() fun findByLanguageTag(languageTag: String): Locale { val languageTagPredicate: (Locale) -> Boolean = { val locale: Locale = fromLanguageTag(languageTag) it.language == locale.language && it.country == locale.country } - return SyncAvoid.supportedLocales.firstOrNull(languageTagPredicate) ?: SyncAvoid.defaultLocale + return supportedLanguages().firstOrNull(languageTagPredicate) ?: currentLocale } -fun supportedLanguages(): List = SyncAvoid.supportedLocales - -fun defaultCountryCode(): String = SyncAvoid.defaultLocale.country +fun currentCountryCode(): String = currentLocale.country -fun defaultLanguageTag(): String = toLanguageTag(SyncAvoid.defaultLocale) +fun currentLanguageTag(): String = toLanguageTag(currentLocale) fun toLanguageTag(locale: Locale): String = locale.language + SEPARATOR + locale.country @@ -93,7 +91,7 @@ private fun fromLanguageTag(languageTag: String): Locale { val codes: Array = languageTag.split(SEPARATOR).toTypedArray() return when (codes.size) { 1 -> Locale.forLanguageTag(codes[0]) - 2 -> Locale.forLanguageTag("${codes[0]}-${codes[1].toCapitalize(Locale.getDefault())}") - else -> SyncAvoid.defaultLocale + 2 -> Locale.forLanguageTag("${codes[0]}-${codes[1].toCapitalize(currentLocale)}") + else -> currentLocale } } diff --git a/app/src/main/kotlin/com/vrem/wifianalyzer/settings/CountryPreference.kt b/app/src/main/kotlin/com/vrem/wifianalyzer/settings/CountryPreference.kt index 6aaf8ed2..7be4ce88 100644 --- a/app/src/main/kotlin/com/vrem/wifianalyzer/settings/CountryPreference.kt +++ b/app/src/main/kotlin/com/vrem/wifianalyzer/settings/CountryPreference.kt @@ -19,7 +19,7 @@ package com.vrem.wifianalyzer.settings import android.content.Context import android.util.AttributeSet -import com.vrem.util.defaultCountryCode +import com.vrem.util.currentCountryCode import com.vrem.wifianalyzer.MainContext import com.vrem.wifianalyzer.wifi.band.WiFiChannelCountry import java.util.Locale @@ -35,4 +35,4 @@ private fun data(): List { class CountryPreference( context: Context, attrs: AttributeSet, -) : CustomPreference(context, attrs, data(), defaultCountryCode()) +) : CustomPreference(context, attrs, data(), currentCountryCode()) diff --git a/app/src/main/kotlin/com/vrem/wifianalyzer/settings/LanguagePreference.kt b/app/src/main/kotlin/com/vrem/wifianalyzer/settings/LanguagePreference.kt index 4abf77fd..fe5727b3 100644 --- a/app/src/main/kotlin/com/vrem/wifianalyzer/settings/LanguagePreference.kt +++ b/app/src/main/kotlin/com/vrem/wifianalyzer/settings/LanguagePreference.kt @@ -19,7 +19,7 @@ package com.vrem.wifianalyzer.settings import android.content.Context import android.util.AttributeSet -import com.vrem.util.defaultLanguageTag +import com.vrem.util.currentLanguageTag import com.vrem.util.supportedLanguages import com.vrem.util.toCapitalize import com.vrem.util.toLanguageTag @@ -35,4 +35,4 @@ private fun map(it: Locale): Data = Data(toLanguageTag(it), it.getDisplayName(it class LanguagePreference( context: Context, attrs: AttributeSet, -) : CustomPreference(context, attrs, data(), defaultLanguageTag()) +) : CustomPreference(context, attrs, data(), currentLanguageTag()) diff --git a/app/src/main/kotlin/com/vrem/wifianalyzer/settings/Settings.kt b/app/src/main/kotlin/com/vrem/wifianalyzer/settings/Settings.kt index 8dc3b772..91f4ffb3 100644 --- a/app/src/main/kotlin/com/vrem/wifianalyzer/settings/Settings.kt +++ b/app/src/main/kotlin/com/vrem/wifianalyzer/settings/Settings.kt @@ -20,8 +20,8 @@ package com.vrem.wifianalyzer.settings import android.content.SharedPreferences.OnSharedPreferenceChangeListener import com.vrem.annotation.OpenClass import com.vrem.util.buildMinVersionQ -import com.vrem.util.defaultCountryCode -import com.vrem.util.defaultLanguageTag +import com.vrem.util.currentCountryCode +import com.vrem.util.currentLanguageTag import com.vrem.util.findByLanguageTag import com.vrem.util.findOne import com.vrem.util.findSet @@ -67,11 +67,11 @@ class Settings( fun wiFiBand(wiFiBand: WiFiBand): Unit = repository.save(R.string.wifi_band_key, wiFiBand.ordinal) - fun countryCode(): String = repository.string(R.string.country_code_key, defaultCountryCode()) + fun countryCode(): String = repository.string(R.string.country_code_key, currentCountryCode()) fun languageLocale(): Locale { - val defaultLanguageTag = defaultLanguageTag() - val languageTag = repository.string(R.string.language_key, defaultLanguageTag) + val currentLanguageTag = currentLanguageTag() + val languageTag = repository.string(R.string.language_key, currentLanguageTag) return findByLanguageTag(languageTag) } diff --git a/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt b/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt index c615260c..8ab3807f 100644 --- a/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt +++ b/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt @@ -123,12 +123,12 @@ class LocaleUtilsTest { } @Test - fun currentDefaultCountryCode() { - assertThat(defaultCountryCode()).isEqualTo(Locale.getDefault().country) + fun currentCurrentCountryCode() { + assertThat(currentCountryCode()).isEqualTo(Locale.getDefault().country) } @Test - fun currentDefaultLanguageTag() { - assertThat(defaultLanguageTag()).isEqualTo(toLanguageTag(Locale.getDefault())) + fun currentCurrentLanguageTag() { + assertThat(currentLanguageTag()).isEqualTo(toLanguageTag(Locale.getDefault())) } } diff --git a/app/src/test/kotlin/com/vrem/wifianalyzer/settings/SettingsTest.kt b/app/src/test/kotlin/com/vrem/wifianalyzer/settings/SettingsTest.kt index 9ba01f56..bd0c4280 100644 --- a/app/src/test/kotlin/com/vrem/wifianalyzer/settings/SettingsTest.kt +++ b/app/src/test/kotlin/com/vrem/wifianalyzer/settings/SettingsTest.kt @@ -20,8 +20,8 @@ package com.vrem.wifianalyzer.settings import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.os.Build import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.vrem.util.defaultCountryCode -import com.vrem.util.defaultLanguageTag +import com.vrem.util.currentCountryCode +import com.vrem.util.currentLanguageTag import com.vrem.util.ordinals import com.vrem.util.toLanguageTag import com.vrem.wifianalyzer.R @@ -353,7 +353,7 @@ class SettingsTest { @Test fun countryCode() { // setup - val defaultValue = defaultCountryCode() + val defaultValue = currentCountryCode() val expected = "WW" doReturn(expected).whenever(repository).string(R.string.country_code_key, defaultValue) // execute @@ -366,7 +366,7 @@ class SettingsTest { @Test fun languageLocale() { // setup - val defaultValue = defaultLanguageTag() + val defaultValue = currentLanguageTag() val expected = Locale.FRENCH doReturn(toLanguageTag(expected)).whenever(repository).string(R.string.language_key, defaultValue) // execute From 96ec463d57301c1a53ff2a5c112d8ce57802150c Mon Sep 17 00:00:00 2001 From: Akihiro Nagai <77012577+314systems@users.noreply.github.com> Date: Sat, 27 Dec 2025 14:58:14 +0900 Subject: [PATCH 04/12] add system_default string to translations --- app/src/main/res/values-bg/strings.xml | 1 + app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-el/strings.xml | 1 + app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values-hu/strings.xml | 1 + app/src/main/res/values-it/strings.xml | 1 + app/src/main/res/values-ja/strings.xml | 1 + app/src/main/res/values-nl/strings.xml | 1 + app/src/main/res/values-pl/strings.xml | 1 + app/src/main/res/values-pt-rBR/strings.xml | 1 + app/src/main/res/values-pt/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values-tr/strings.xml | 1 + app/src/main/res/values-uk/strings.xml | 1 + app/src/main/res/values-zh-rCN/strings.xml | 1 + app/src/main/res/values-zh-rTW/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 18 files changed, 18 insertions(+) diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 5265d93f..49b11f26 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -103,4 +103,5 @@ "Изключи Wi-Fi при изход" "Регулирането на Wi-Fi сканирането е изключено" "Регулирането на Wi-Fi сканирането е включено" + Стандартно за системата diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index a448b204..44c9fa39 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -103,4 +103,5 @@ "WLAN beim Verlassen deaktivieren" "Die WLAN-Scan-Drosselung ist deaktiviert" "Die WLAN-Scan-Drosselung ist aktiviert" + Standardeinstellung des Systems diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 6eb67f2e..918fb8fe 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -103,4 +103,5 @@ "Απενεργοποίηση Wi-Fi κατά την έξοδο" "Ο περιορισμός σάρωσης Wi-Fi είναι απενεργοποιημένος" "Ο περιορισμός σάρωσης Wi-Fi είναι ενεργοποιημένος" + Προεπιλογή συστήματος diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index e5035666..874a3b16 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -103,4 +103,5 @@ "Apagar Wi-Fi al salir" "La aceleración del escaneo de Wi-Fi está deshabilitada" "La aceleración del escaneo de Wi-Fi está habilitada" + Predeterminado del sistema diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 829afd73..84cf463b 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -102,4 +102,5 @@ "Arrêter le Wi-Fi en quittant" "La limitation de l'analyse Wi-Fi est désactivée" "La limitation de l'analyse Wi-Fi est activée" + Paramètre système par défaut diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 2a0b5fcc..692549f2 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -102,5 +102,6 @@ "Wi‑Fi kikapcsolása kilépéskor" "A Wi‑Fi szkennelés korlátozása tiltva" "A Wi‑Fi szkennelés korlátozása engedélyezve" + Rendszerbeállítás diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index afff650e..12c6958f 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -103,4 +103,5 @@ "Spegni Wi-Fi all\'uscita" "La limitazione della scansione Wi-Fi è disabilitata" "La limitazione della scansione Wi-Fi è abilitata" + Predefinita di sistema diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index cb375412..551cbbc5 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -103,4 +103,5 @@ "閉じる時 Wi-Fi をオフにする" "Wi-Fi スキャンのスロットリングが無効になっています" "Wi-Fi スキャンのスロットルが有効になっています" + システムのデフォルト diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index aa7a1ff6..0d9fdc12 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -102,4 +102,5 @@ "Wifi uitsch. bij afsluiten" "Wifi-scanbeperking is uitgeschakeld" "Wifi-scanbeperking is ingeschakeld" + Systeemstandaard diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index fcd16e6a..12340e68 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -103,4 +103,5 @@ "Wyłączanie Wi-Fi przy wyjściu" "Ograniczanie skanowania Wi-Fi jest wyłączone" "Ograniczanie skanowania Wi-Fi jest włączone" + Ustawienie domyślne systemu diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index b14b11cb..037d4794 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -103,4 +103,5 @@ "Desativar o Wi-Fi ao sair" "A limitação da busca por Wi-Fi está desativada" "A limitação da busca por Wi-Fi está ativada" + Padrão do sistema diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index d5a7000c..df86f795 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -103,4 +103,5 @@ "Desligar o Wi-Fi ao sair" "A limitação da procura de Wi-Fi está desativada" "A limitação da procura de Wi-Fi está ativada" + Padrão do sistema diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 8f5704f5..64b9634a 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -103,4 +103,5 @@ "Wi-Fi выключен при выходе" "Регулирование сканирования Wi-Fi отключено" "Регулирование сканирования Wi-Fi включено" + Язык системы diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 5d9352bc..c92bbb4c 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -103,4 +103,5 @@ "Çıkışta Wi-Fi'yi kapat" "Wi-Fi tarama kısıtlaması devre dışı" "Wi-Fi tarama kısıtlaması etkin" + Sistem varsayılanı diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index a713d2f9..572020e6 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -114,4 +114,5 @@ "Wi-Fi вимкнений при виході" "Регулювання сканування Wi-Fi вимкнено" "Регулювання сканування Wi-Fi увімкнено" + Налаштування системи за умовчанням diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index cf09e803..ff868aed 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -103,4 +103,5 @@ "退出时关闭 Wi-Fi" "WLAN 扫描调节已禁用" "WLAN 扫描调节已启用" + 系统默认设置 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 8b432bbc..3f064206 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -103,4 +103,5 @@ "退出時關閉 Wi-Fi" "Wi-Fi 掃描限制已停用" "Wi-Fi 掃描限制已啟用" + 系統預設 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d2301aed..3fa5a579 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -217,4 +217,5 @@ "80 MHz" "160 MHz" "320 MHz" + System default From 6d5cc0acd2834271d2e24464d1f09f8b7423fd6c Mon Sep 17 00:00:00 2001 From: Akihiro Nagai <77012577+314systems@users.noreply.github.com> Date: Sat, 27 Dec 2025 15:18:15 +0900 Subject: [PATCH 05/12] update locale utils and language preference --- .../main/kotlin/com/vrem/util/LocaleUtils.kt | 26 +++++++------------ .../main/kotlin/com/vrem/util/StringUtils.kt | 3 +++ .../settings/LanguagePreference.kt | 24 +++++++++-------- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt b/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt index 29cf2f2f..8858804f 100644 --- a/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt +++ b/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt @@ -62,8 +62,6 @@ val baseSupportedLocales: List = UKRAINIAN, ).toList() -private const val SEPARATOR: String = "_" - fun findByCountryCode(countryCode: String): Locale = availableLocales.firstOrNull { countryCode.toCapitalize(Locale.getDefault()) == it.country } ?: currentLocale @@ -73,25 +71,19 @@ fun allCountries(): List = countriesLocales.values.toList() fun supportedLanguages(): List = (baseSupportedLocales + currentLocale).distinct() +fun supportedLanguageTags(): List = + listOf("") + baseSupportedLocales.map { it.toLanguageTag() } + fun findByLanguageTag(languageTag: String): Locale { - val languageTagPredicate: (Locale) -> Boolean = { - val locale: Locale = fromLanguageTag(languageTag) + if (languageTag.isEmpty()) return currentLocale + val locale = Locale.forLanguageTag(languageTag) + return baseSupportedLocales.firstOrNull { it.language == locale.language && it.country == locale.country - } - return supportedLanguages().firstOrNull(languageTagPredicate) ?: currentLocale + } ?: currentLocale } fun currentCountryCode(): String = currentLocale.country -fun currentLanguageTag(): String = toLanguageTag(currentLocale) +fun currentLanguageTag(): String = currentLocale.toLanguageTag() -fun toLanguageTag(locale: Locale): String = locale.language + SEPARATOR + locale.country - -private fun fromLanguageTag(languageTag: String): Locale { - val codes: Array = languageTag.split(SEPARATOR).toTypedArray() - return when (codes.size) { - 1 -> Locale.forLanguageTag(codes[0]) - 2 -> Locale.forLanguageTag("${codes[0]}-${codes[1].toCapitalize(currentLocale)}") - else -> currentLocale - } -} +fun toLanguageTag(locale: Locale): String = locale.toLanguageTag() diff --git a/app/src/main/kotlin/com/vrem/util/StringUtils.kt b/app/src/main/kotlin/com/vrem/util/StringUtils.kt index e96df0c2..37cfda7b 100644 --- a/app/src/main/kotlin/com/vrem/util/StringUtils.kt +++ b/app/src/main/kotlin/com/vrem/util/StringUtils.kt @@ -27,3 +27,6 @@ fun String.Companion.nullToEmpty(value: String?): String = value ?: String.EMPTY fun String.specialTrim(): String = this.trim { it <= ' ' }.replace(" +".toRegex(), String.SPACE_SEPARATOR) fun String.toCapitalize(locale: Locale): String = this.replaceFirstChar { word -> word.uppercase(locale) } + +fun String.titlecaseFirst(locale: Locale): String = + replaceFirstChar { it.titlecase(locale) } diff --git a/app/src/main/kotlin/com/vrem/wifianalyzer/settings/LanguagePreference.kt b/app/src/main/kotlin/com/vrem/wifianalyzer/settings/LanguagePreference.kt index fe5727b3..e94d8215 100644 --- a/app/src/main/kotlin/com/vrem/wifianalyzer/settings/LanguagePreference.kt +++ b/app/src/main/kotlin/com/vrem/wifianalyzer/settings/LanguagePreference.kt @@ -19,20 +19,22 @@ package com.vrem.wifianalyzer.settings import android.content.Context import android.util.AttributeSet -import com.vrem.util.currentLanguageTag -import com.vrem.util.supportedLanguages -import com.vrem.util.toCapitalize -import com.vrem.util.toLanguageTag +import com.vrem.util.supportedLanguageTags +import com.vrem.util.titlecaseFirst +import com.vrem.wifianalyzer.R import java.util.Locale -private fun data(): List = - supportedLanguages() - .map { map(it) } - .sorted() - -private fun map(it: Locale): Data = Data(toLanguageTag(it), it.getDisplayName(it).toCapitalize(Locale.getDefault())) +private fun data(context: Context): List = + supportedLanguageTags().map { tag -> + if (tag.isEmpty()) { + Data("", context.getString(R.string.system_default)) + } else { + val locale = Locale.forLanguageTag(tag) + Data(tag, locale.getDisplayName(locale).titlecaseFirst(locale)) + } + } class LanguagePreference( context: Context, attrs: AttributeSet, -) : CustomPreference(context, attrs, data(), currentLanguageTag()) +) : CustomPreference(context, attrs, data(context), "") From 4758f5db924e110b422278b27726c37346a0ad01 Mon Sep 17 00:00:00 2001 From: Akihiro Nagai <77012577+314systems@users.noreply.github.com> Date: Sun, 28 Dec 2025 00:08:37 +0900 Subject: [PATCH 06/12] refactor locale handling to use AppCompatDelegate for application locales --- .../main/kotlin/com/vrem/util/LocaleUtils.kt | 39 +++++++++++++------ .../com/vrem/wifianalyzer/MainActivity.kt | 21 ++++++---- .../com/vrem/wifianalyzer/MainReload.kt | 15 +------ .../settings/CountryPreference.kt | 2 +- .../vrem/wifianalyzer/settings/Settings.kt | 15 ++++--- .../ChannelAvailableFragment.kt | 2 +- .../kotlin/com/vrem/util/LocaleUtilsTest.kt | 14 +++---- .../com/vrem/wifianalyzer/MainReloadTest.kt | 24 ------------ .../wifianalyzer/settings/SettingsTest.kt | 2 +- .../ChannelAvailableFragmentTest.kt | 4 +- 10 files changed, 65 insertions(+), 73 deletions(-) diff --git a/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt b/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt index 8858804f..ad0ca037 100644 --- a/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt +++ b/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt @@ -29,9 +29,18 @@ private val countriesLocales: SortedMap = .toSortedMap() val BULGARIAN: Locale = Locale.forLanguageTag("bg") + +val CHINESE_SIMPLIFIED: Locale = Locale.forLanguageTag("zh-Hans") + +val CHINESE_TRADITIONAL: Locale = Locale.forLanguageTag("zh-Hant") val DUTCH: Locale = Locale.forLanguageTag("nl") +val ENGLISH: Locale = Locale.forLanguageTag("en") +val FRENCH: Locale = Locale.forLanguageTag("fr") +val GERMAN: Locale = Locale.forLanguageTag("de") val GREEK: Locale = Locale.forLanguageTag("el") val HUNGARIAN: Locale = Locale.forLanguageTag("hu") +val ITALIAN: Locale = Locale.forLanguageTag("it") +val JAPANESE: Locale = Locale.forLanguageTag("ja") val POLISH: Locale = Locale.forLanguageTag("pl") val PORTUGUESE_PORTUGAL: Locale = Locale.forLanguageTag("pt-PT") val PORTUGUESE_BRAZIL: Locale = Locale.forLanguageTag("pt-BR") @@ -46,13 +55,13 @@ val baseSupportedLocales: List = DUTCH, GREEK, HUNGARIAN, - Locale.SIMPLIFIED_CHINESE, - Locale.TRADITIONAL_CHINESE, - Locale.ENGLISH, - Locale.FRENCH, - Locale.GERMAN, - Locale.ITALIAN, - Locale.JAPANESE, + CHINESE_SIMPLIFIED, + CHINESE_TRADITIONAL, + ENGLISH, + FRENCH, + GERMAN, + ITALIAN, + JAPANESE, POLISH, PORTUGUESE_BRAZIL, PORTUGUESE_PORTUGAL, @@ -76,10 +85,15 @@ fun supportedLanguageTags(): List = fun findByLanguageTag(languageTag: String): Locale { if (languageTag.isEmpty()) return currentLocale - val locale = Locale.forLanguageTag(languageTag) - return baseSupportedLocales.firstOrNull { - it.language == locale.language && it.country == locale.country - } ?: currentLocale + + val target = Locale.forLanguageTag(languageTag) + if (target.language.isEmpty()) return currentLocale + + return baseSupportedLocales.find { it == target } + ?: baseSupportedLocales.find { it.language == target.language && it.script == target.script } + ?: baseSupportedLocales.find { it.language == target.language && it.country == target.country } + ?: baseSupportedLocales.find { it.language == target.language } + ?: currentLocale } fun currentCountryCode(): String = currentLocale.country @@ -87,3 +101,6 @@ fun currentCountryCode(): String = currentLocale.country fun currentLanguageTag(): String = currentLocale.toLanguageTag() fun toLanguageTag(locale: Locale): String = locale.toLanguageTag() + +fun Locale.toSupportedLocaleTag(): String = + findByLanguageTag(this.toLanguageTag()).toLanguageTag() diff --git a/app/src/main/kotlin/com/vrem/wifianalyzer/MainActivity.kt b/app/src/main/kotlin/com/vrem/wifianalyzer/MainActivity.kt index 7c59a14d..9c77a6d6 100644 --- a/app/src/main/kotlin/com/vrem/wifianalyzer/MainActivity.kt +++ b/app/src/main/kotlin/com/vrem/wifianalyzer/MainActivity.kt @@ -17,7 +17,6 @@ */ package com.vrem.wifianalyzer -import android.content.Context import android.content.SharedPreferences import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.res.Configuration @@ -26,18 +25,17 @@ import android.view.Menu import android.view.MenuItem import android.view.View import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.app.AppCompatDelegate +import androidx.core.os.LocaleListCompat import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.GravityCompat import androidx.drawerlayout.widget.DrawerLayout import com.google.android.material.navigation.NavigationView import com.vrem.annotation.OpenClass -import com.vrem.util.createContext import com.vrem.wifianalyzer.navigation.NavigationMenu import com.vrem.wifianalyzer.navigation.NavigationMenuControl import com.vrem.wifianalyzer.navigation.NavigationMenuController import com.vrem.wifianalyzer.navigation.options.OptionMenu -import com.vrem.wifianalyzer.settings.Repository -import com.vrem.wifianalyzer.settings.Settings import com.vrem.wifianalyzer.wifi.accesspoint.ConnectionView import com.vrem.wifianalyzer.wifi.scanner.ScannerService @@ -52,15 +50,13 @@ class MainActivity : internal lateinit var optionMenu: OptionMenu internal lateinit var connectionView: ConnectionView - override fun attachBaseContext(newBase: Context) = - super.attachBaseContext(newBase.createContext(Settings(Repository(newBase)).languageLocale())) - override fun onCreate(savedInstanceState: Bundle?) { val mainContext = MainContext.INSTANCE mainContext.initialize(this, largeScreen) val settings = mainContext.settings settings.initializeDefaultValues() + settings.syncLanguage() setTheme(settings.themeStyle().themeNoActionBar) mainReload = MainReload(settings) @@ -120,6 +116,17 @@ class MainActivity : sharedPreferences: SharedPreferences, key: String?, ) { + val languageKey = getString(R.string.language_key) + if (key == languageKey) { + val languageTag = sharedPreferences.getString(languageKey, "") + val locales = languageTag + ?.takeIf { it.isNotEmpty() } + ?.let(LocaleListCompat::forLanguageTags) + ?: LocaleListCompat.getEmptyLocaleList() + + AppCompatDelegate.setApplicationLocales(locales) + } + val mainContext = MainContext.INSTANCE if (mainReload.shouldReload(mainContext.settings)) { MainContext.INSTANCE.scannerService.stop() diff --git a/app/src/main/kotlin/com/vrem/wifianalyzer/MainReload.kt b/app/src/main/kotlin/com/vrem/wifianalyzer/MainReload.kt index 33caa01f..a8d087c2 100644 --- a/app/src/main/kotlin/com/vrem/wifianalyzer/MainReload.kt +++ b/app/src/main/kotlin/com/vrem/wifianalyzer/MainReload.kt @@ -20,7 +20,6 @@ package com.vrem.wifianalyzer import com.vrem.wifianalyzer.settings.Settings import com.vrem.wifianalyzer.settings.ThemeStyle import com.vrem.wifianalyzer.wifi.accesspoint.ConnectionViewType -import java.util.Locale class MainReload( settings: Settings, @@ -29,11 +28,9 @@ class MainReload( private set var connectionViewType: ConnectionViewType private set - var languageLocale: Locale - private set fun shouldReload(settings: Settings): Boolean = - themeChanged(settings) || connectionViewTypeChanged(settings) || languageChanged(settings) + themeChanged(settings) || connectionViewTypeChanged(settings) private fun connectionViewTypeChanged(settings: Settings): Boolean { val currentConnectionViewType = settings.connectionViewType() @@ -53,18 +50,8 @@ class MainReload( return themeChanged } - private fun languageChanged(settings: Settings): Boolean { - val settingLanguageLocale = settings.languageLocale() - val languageLocaleChanged = languageLocale != settingLanguageLocale - if (languageLocaleChanged) { - languageLocale = settingLanguageLocale - } - return languageLocaleChanged - } - init { themeStyle = settings.themeStyle() connectionViewType = settings.connectionViewType() - languageLocale = settings.languageLocale() } } diff --git a/app/src/main/kotlin/com/vrem/wifianalyzer/settings/CountryPreference.kt b/app/src/main/kotlin/com/vrem/wifianalyzer/settings/CountryPreference.kt index 7be4ce88..b1bda240 100644 --- a/app/src/main/kotlin/com/vrem/wifianalyzer/settings/CountryPreference.kt +++ b/app/src/main/kotlin/com/vrem/wifianalyzer/settings/CountryPreference.kt @@ -25,7 +25,7 @@ import com.vrem.wifianalyzer.wifi.band.WiFiChannelCountry import java.util.Locale private fun data(): List { - val currentLocale: Locale = MainContext.INSTANCE.settings.languageLocale() + val currentLocale: Locale = MainContext.INSTANCE.settings.appLocale() return WiFiChannelCountry .findAll() .map { Data(it.countryCode, it.countryName(currentLocale)) } diff --git a/app/src/main/kotlin/com/vrem/wifianalyzer/settings/Settings.kt b/app/src/main/kotlin/com/vrem/wifianalyzer/settings/Settings.kt index 91f4ffb3..311cbb6c 100644 --- a/app/src/main/kotlin/com/vrem/wifianalyzer/settings/Settings.kt +++ b/app/src/main/kotlin/com/vrem/wifianalyzer/settings/Settings.kt @@ -18,14 +18,15 @@ package com.vrem.wifianalyzer.settings import android.content.SharedPreferences.OnSharedPreferenceChangeListener +import androidx.appcompat.app.AppCompatDelegate import com.vrem.annotation.OpenClass import com.vrem.util.buildMinVersionQ import com.vrem.util.currentCountryCode -import com.vrem.util.currentLanguageTag import com.vrem.util.findByLanguageTag import com.vrem.util.findOne import com.vrem.util.findSet import com.vrem.util.ordinals +import com.vrem.util.toSupportedLocaleTag import com.vrem.wifianalyzer.R import com.vrem.wifianalyzer.navigation.MAIN_NAVIGATION import com.vrem.wifianalyzer.navigation.NavigationMenu @@ -69,10 +70,14 @@ class Settings( fun countryCode(): String = repository.string(R.string.country_code_key, currentCountryCode()) - fun languageLocale(): Locale { - val currentLanguageTag = currentLanguageTag() - val languageTag = repository.string(R.string.language_key, currentLanguageTag) - return findByLanguageTag(languageTag) + fun appLocale(): Locale = + findByLanguageTag(AppCompatDelegate.getApplicationLocales().toLanguageTags()) + + fun syncLanguage() { + val appLocaleTag = appLocale().toSupportedLocaleTag() + if (appLocaleTag != repository.string(R.string.language_key, "")) { + repository.save(R.string.language_key, appLocaleTag) + } } fun sortBy(): SortBy = settingsFind(SortBy.entries, R.string.sort_by_key, SortBy.STRENGTH) diff --git a/app/src/main/kotlin/com/vrem/wifianalyzer/wifi/channelavailable/ChannelAvailableFragment.kt b/app/src/main/kotlin/com/vrem/wifianalyzer/wifi/channelavailable/ChannelAvailableFragment.kt index f0063ea7..a9bf4b67 100644 --- a/app/src/main/kotlin/com/vrem/wifianalyzer/wifi/channelavailable/ChannelAvailableFragment.kt +++ b/app/src/main/kotlin/com/vrem/wifianalyzer/wifi/channelavailable/ChannelAvailableFragment.kt @@ -44,7 +44,7 @@ class ChannelAvailableFragment : Fragment() { private fun update() { val settings = MainContext.INSTANCE.settings val countryCode = settings.countryCode() - val languageLocale = settings.languageLocale() + val languageLocale = settings.appLocale() binding.apply { val textViews = listOf( diff --git a/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt b/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt index 8ab3807f..ddcfc2e4 100644 --- a/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt +++ b/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt @@ -97,13 +97,13 @@ class LocaleUtilsTest { DUTCH, GREEK, HUNGARIAN, - Locale.SIMPLIFIED_CHINESE, - Locale.TRADITIONAL_CHINESE, - Locale.ENGLISH, - Locale.FRENCH, - Locale.GERMAN, - Locale.ITALIAN, - Locale.JAPANESE, + CHINESE_SIMPLIFIED, + CHINESE_TRADITIONAL, + ENGLISH, + FRENCH, + GERMAN, + ITALIAN, + JAPANESE, POLISH, PORTUGUESE_BRAZIL, PORTUGUESE_PORTUGAL, diff --git a/app/src/test/kotlin/com/vrem/wifianalyzer/MainReloadTest.kt b/app/src/test/kotlin/com/vrem/wifianalyzer/MainReloadTest.kt index bbb0191b..3d50b18d 100644 --- a/app/src/test/kotlin/com/vrem/wifianalyzer/MainReloadTest.kt +++ b/app/src/test/kotlin/com/vrem/wifianalyzer/MainReloadTest.kt @@ -27,7 +27,6 @@ import org.mockito.kotlin.atLeastOnce import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever -import java.util.Locale class MainReloadTest { private val settings = MainContextHelper.INSTANCE.settings @@ -37,7 +36,6 @@ class MainReloadTest { fun setUp() { whenever(settings.themeStyle()).thenReturn(ThemeStyle.DARK) whenever(settings.connectionViewType()).thenReturn(ConnectionViewType.COMPLETE) - whenever(settings.languageLocale()).thenReturn(Locale.UK) fixture = MainReload(settings) } @@ -45,7 +43,6 @@ class MainReloadTest { fun tearDown() { verify(settings, atLeastOnce()).themeStyle() verify(settings, atLeastOnce()).connectionViewType() - verify(settings, atLeastOnce()).languageLocale() verifyNoMoreInteractions(settings) MainContextHelper.INSTANCE.restore() } @@ -91,25 +88,4 @@ class MainReloadTest { assertThat(actual).isTrue assertThat(fixture.connectionViewType).isEqualTo(expected) } - - @Test - fun shouldNotReloadWithNoLanguageLocaleChanges() { - // execute - val actual = fixture.shouldReload(settings) - // validate - assertThat(actual).isFalse - assertThat(fixture.languageLocale).isEqualTo(Locale.UK) - } - - @Test - fun shouldReloadWithLanguageLocaleChange() { - // setup - val expected = Locale.US - whenever(settings.languageLocale()).thenReturn(expected) - // execute - val actual = fixture.shouldReload(settings) - // validate - assertThat(actual).isTrue - assertThat(fixture.languageLocale).isEqualTo(expected) - } } diff --git a/app/src/test/kotlin/com/vrem/wifianalyzer/settings/SettingsTest.kt b/app/src/test/kotlin/com/vrem/wifianalyzer/settings/SettingsTest.kt index bd0c4280..81f3a6fa 100644 --- a/app/src/test/kotlin/com/vrem/wifianalyzer/settings/SettingsTest.kt +++ b/app/src/test/kotlin/com/vrem/wifianalyzer/settings/SettingsTest.kt @@ -370,7 +370,7 @@ class SettingsTest { val expected = Locale.FRENCH doReturn(toLanguageTag(expected)).whenever(repository).string(R.string.language_key, defaultValue) // execute - val actual = fixture.languageLocale() + val actual = fixture.appLocale() // validate assertThat(actual).isEqualTo(expected) verify(repository).string(R.string.language_key, defaultValue) diff --git a/app/src/test/kotlin/com/vrem/wifianalyzer/wifi/channelavailable/ChannelAvailableFragmentTest.kt b/app/src/test/kotlin/com/vrem/wifianalyzer/wifi/channelavailable/ChannelAvailableFragmentTest.kt index 27e3ea49..ff88c4bc 100644 --- a/app/src/test/kotlin/com/vrem/wifianalyzer/wifi/channelavailable/ChannelAvailableFragmentTest.kt +++ b/app/src/test/kotlin/com/vrem/wifianalyzer/wifi/channelavailable/ChannelAvailableFragmentTest.kt @@ -62,13 +62,13 @@ class ChannelAvailableFragmentTest { @Before fun setUp() { whenever(settings.countryCode()).thenReturn(locale.country) - whenever(settings.languageLocale()).thenReturn(Locale.US) + whenever(settings.appLocale()).thenReturn(Locale.US) } @After fun tearDown() { verify(settings, atLeastOnce()).countryCode() - verify(settings, atLeastOnce()).languageLocale() + verify(settings, atLeastOnce()).appLocale() INSTANCE.restore() } From 730dc4f1290cef088f79f2d36ab826532cc308e9 Mon Sep 17 00:00:00 2001 From: Akihiro Nagai <77012577+314systems@users.noreply.github.com> Date: Sun, 28 Dec 2025 14:00:09 +0900 Subject: [PATCH 07/12] update LocaleUtils and refactor language preference tests --- .../main/kotlin/com/vrem/util/LocaleUtils.kt | 10 ++++--- .../vendor/model/VendorService.kt | 4 +-- .../kotlin/com/vrem/util/LocaleUtilsTest.kt | 19 ++++++++++---- .../settings/LanguagePreferenceTest.kt | 26 +++++++++++-------- .../wifianalyzer/settings/SettingsTest.kt | 17 +++++------- .../wifi/band/WiFiChannelCountryTest.kt | 12 ++++++--- 6 files changed, 52 insertions(+), 36 deletions(-) diff --git a/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt b/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt index ad0ca037..652d39c0 100644 --- a/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt +++ b/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt @@ -72,7 +72,7 @@ val baseSupportedLocales: List = ).toList() fun findByCountryCode(countryCode: String): Locale = - availableLocales.firstOrNull { countryCode.toCapitalize(Locale.getDefault()) == it.country } + availableLocales.firstOrNull { countryCode.uppercase(Locale.ROOT) == it.country } ?: currentLocale fun allCountries(): List = countriesLocales.values.toList() @@ -83,10 +83,14 @@ fun supportedLanguages(): List = fun supportedLanguageTags(): List = listOf("") + baseSupportedLocales.map { it.toLanguageTag() } +private fun normalizeLanguageTag(languageTag: String): String = + languageTag.replace('_', '-').trim() + fun findByLanguageTag(languageTag: String): Locale { - if (languageTag.isEmpty()) return currentLocale + val normalizedLanguageTag = normalizeLanguageTag(languageTag) + if (normalizedLanguageTag.isEmpty()) return currentLocale - val target = Locale.forLanguageTag(languageTag) + val target = Locale.forLanguageTag(normalizedLanguageTag) if (target.language.isEmpty()) return currentLocale return baseSupportedLocales.find { it == target } diff --git a/app/src/main/kotlin/com/vrem/wifianalyzer/vendor/model/VendorService.kt b/app/src/main/kotlin/com/vrem/wifianalyzer/vendor/model/VendorService.kt index fba8fa11..09d1ad7f 100644 --- a/app/src/main/kotlin/com/vrem/wifianalyzer/vendor/model/VendorService.kt +++ b/app/src/main/kotlin/com/vrem/wifianalyzer/vendor/model/VendorService.kt @@ -34,10 +34,10 @@ class VendorService( fun findVendorName(address: String = String.EMPTY): String = vendorData.macs[address.clean()].orEmpty() fun findMacAddresses(vendorName: String = String.EMPTY): List = - vendorData.vendors[vendorName.uppercase(Locale.getDefault())].orEmpty() + vendorData.vendors[vendorName.uppercase(Locale.ROOT)].orEmpty() fun findVendors(vendorName: String = String.EMPTY): List { - val name = vendorName.uppercase(Locale.getDefault()) + val name = vendorName.uppercase(Locale.ROOT) return vendorData.vendors .filterKeys { filter(it, name) } .keys diff --git a/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt b/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt index ddcfc2e4..21c3c238 100644 --- a/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt +++ b/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt @@ -69,8 +69,10 @@ class LocaleUtilsTest { @Test fun toLanguageTagWithKnownCode() { - assertThat(toLanguageTag(Locale.US)).isEqualTo(Locale.US.language + "_" + Locale.US.country) - assertThat(toLanguageTag(Locale.ENGLISH)).isEqualTo(Locale.ENGLISH.language + "_") + assertThat(toLanguageTag(Locale.US)).isEqualTo("en-US") + assertThat(toLanguageTag(ENGLISH)).isEqualTo("en") + assertThat(toLanguageTag(CHINESE_SIMPLIFIED)).isEqualTo("zh-Hans") + assertThat(toLanguageTag(CHINESE_TRADITIONAL)).isEqualTo("zh-Hant") } @Test @@ -83,9 +85,16 @@ class LocaleUtilsTest { @Test fun findByLanguageTagWithKnownTag() { - assertThat(findByLanguageTag(toLanguageTag(Locale.SIMPLIFIED_CHINESE))).isEqualTo(Locale.SIMPLIFIED_CHINESE) - assertThat(findByLanguageTag(toLanguageTag(Locale.TRADITIONAL_CHINESE))).isEqualTo(Locale.TRADITIONAL_CHINESE) - assertThat(findByLanguageTag(toLanguageTag(Locale.ENGLISH))).isEqualTo(Locale.ENGLISH) + // BCP-47 format (new) + assertThat(findByLanguageTag("zh-Hans")).isEqualTo(CHINESE_SIMPLIFIED) + assertThat(findByLanguageTag("zh-Hant")).isEqualTo(CHINESE_TRADITIONAL) + assertThat(findByLanguageTag("en")).isEqualTo(ENGLISH) + assertThat(findByLanguageTag("en-US")).isEqualTo(ENGLISH) + + // Backward compatibility: underscore format (old) + assertThat(findByLanguageTag("zh_Hans")).isEqualTo(CHINESE_SIMPLIFIED) + assertThat(findByLanguageTag("zh_Hant")).isEqualTo(CHINESE_TRADITIONAL) + assertThat(findByLanguageTag("en_US")).isEqualTo(ENGLISH) } @Test diff --git a/app/src/test/kotlin/com/vrem/wifianalyzer/settings/LanguagePreferenceTest.kt b/app/src/test/kotlin/com/vrem/wifianalyzer/settings/LanguagePreferenceTest.kt index 32b90514..dd2ab5be 100644 --- a/app/src/test/kotlin/com/vrem/wifianalyzer/settings/LanguagePreferenceTest.kt +++ b/app/src/test/kotlin/com/vrem/wifianalyzer/settings/LanguagePreferenceTest.kt @@ -19,9 +19,8 @@ package com.vrem.wifianalyzer.settings import android.os.Build import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.vrem.util.supportedLanguages -import com.vrem.util.toCapitalize -import com.vrem.util.toLanguageTag +import com.vrem.util.supportedLanguageTags +import com.vrem.util.titlecaseFirst import com.vrem.wifianalyzer.R import com.vrem.wifianalyzer.RobolectricUtil import org.assertj.core.api.Assertions.assertThat @@ -35,7 +34,7 @@ import java.util.Locale @Config(sdk = [Build.VERSION_CODES.BAKLAVA]) class LanguagePreferenceTest { private val mainActivity = RobolectricUtil.INSTANCE.activity - private val languages = supportedLanguages() + private val languageTags = supportedLanguageTags() private val attributeSet = Robolectric.getAttributeSetFromXml(R.xml.test_attrs) private val fixture = LanguagePreference(mainActivity, attributeSet) @@ -44,9 +43,16 @@ class LanguagePreferenceTest { // execute val actual: Array = fixture.entries // validate - assertThat(actual).hasSize(languages.size) - languages.forEach { - assertThat(actual).contains(it.getDisplayName(it).toCapitalize(Locale.getDefault())) + assertThat(actual).hasSize(languageTags.size) + + // Check system default entry + assertThat(actual[0]).isEqualTo(mainActivity.getString(R.string.system_default)) + + // Check language entries + languageTags.drop(1).forEachIndexed { index, tag -> + val locale = Locale.forLanguageTag(tag) + val displayName = locale.getDisplayName(locale).titlecaseFirst(locale) + assertThat(actual[index + 1]).isEqualTo(displayName) } } @@ -55,9 +61,7 @@ class LanguagePreferenceTest { // execute val actual: Array = fixture.entryValues // validate - assertThat(actual).hasSize(languages.size) - languages.forEach { - assertThat(actual).contains(toLanguageTag(it)) - } + assertThat(actual).hasSize(languageTags.size) + assertThat(actual).containsExactlyElementsOf(languageTags) } } diff --git a/app/src/test/kotlin/com/vrem/wifianalyzer/settings/SettingsTest.kt b/app/src/test/kotlin/com/vrem/wifianalyzer/settings/SettingsTest.kt index 81f3a6fa..cfb36c71 100644 --- a/app/src/test/kotlin/com/vrem/wifianalyzer/settings/SettingsTest.kt +++ b/app/src/test/kotlin/com/vrem/wifianalyzer/settings/SettingsTest.kt @@ -21,9 +21,7 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.os.Build import androidx.test.ext.junit.runners.AndroidJUnit4 import com.vrem.util.currentCountryCode -import com.vrem.util.currentLanguageTag import com.vrem.util.ordinals -import com.vrem.util.toLanguageTag import com.vrem.wifianalyzer.R import com.vrem.wifianalyzer.navigation.NavigationMenu import com.vrem.wifianalyzer.wifi.accesspoint.AccessPointViewType @@ -46,7 +44,6 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever import org.robolectric.annotation.Config -import java.util.Locale @RunWith(AndroidJUnit4::class) @Config(sdk = [Build.VERSION_CODES.BAKLAVA]) @@ -364,16 +361,14 @@ class SettingsTest { } @Test - fun languageLocale() { - // setup - val defaultValue = currentLanguageTag() - val expected = Locale.FRENCH - doReturn(toLanguageTag(expected)).whenever(repository).string(R.string.language_key, defaultValue) + fun appLocale() { + // Note: appLocale() uses AppCompatDelegate.getApplicationLocales() which is system API + // and returns the current application locale (device default in test environment) // execute val actual = fixture.appLocale() - // validate - assertThat(actual).isEqualTo(expected) - verify(repository).string(R.string.language_key, defaultValue) + // validate: should return a valid Locale (default in test) + assertThat(actual).isNotNull + assertThat(actual.language).isNotEmpty() } @Test diff --git a/app/src/test/kotlin/com/vrem/wifianalyzer/wifi/band/WiFiChannelCountryTest.kt b/app/src/test/kotlin/com/vrem/wifianalyzer/wifi/band/WiFiChannelCountryTest.kt index 42272a22..4082ca97 100644 --- a/app/src/test/kotlin/com/vrem/wifianalyzer/wifi/band/WiFiChannelCountryTest.kt +++ b/app/src/test/kotlin/com/vrem/wifianalyzer/wifi/band/WiFiChannelCountryTest.kt @@ -30,10 +30,14 @@ class WiFiChannelCountryTest { @Test fun find() { - val expected = Locale.US - val actual = WiFiChannelCountry.find(expected.country) - assertThat(actual.countryCode).isEqualTo(expected.country) - assertThat(actual.countryName(expected)).isEqualTo(expected.displayCountry) + val countryLocale = Locale.US + val displayLocale = Locale.getDefault() + + val actual = WiFiChannelCountry.find(countryLocale.country) + + assertThat(actual.countryCode).isEqualTo(countryLocale.country) + assertThat(actual.countryName(displayLocale)) + .isEqualTo(countryLocale.getDisplayCountry(displayLocale)) } @Test From 402dcca72306a44d617c0cd9b73d6c422f2530ed Mon Sep 17 00:00:00 2001 From: Akihiro Nagai <77012577+314systems@users.noreply.github.com> Date: Mon, 29 Dec 2025 17:17:10 +0900 Subject: [PATCH 08/12] reformat --- app/src/main/kotlin/com/vrem/util/LocaleUtils.kt | 12 ++++-------- app/src/main/kotlin/com/vrem/util/StringUtils.kt | 3 +-- .../kotlin/com/vrem/wifianalyzer/MainActivity.kt | 9 +++++---- .../main/kotlin/com/vrem/wifianalyzer/MainReload.kt | 3 +-- .../com/vrem/wifianalyzer/settings/Settings.kt | 3 +-- 5 files changed, 12 insertions(+), 18 deletions(-) diff --git a/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt b/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt index 652d39c0..17395ae8 100644 --- a/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt +++ b/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt @@ -77,14 +77,11 @@ fun findByCountryCode(countryCode: String): Locale = fun allCountries(): List = countriesLocales.values.toList() -fun supportedLanguages(): List = - (baseSupportedLocales + currentLocale).distinct() +fun supportedLanguages(): List = (baseSupportedLocales + currentLocale).distinct() -fun supportedLanguageTags(): List = - listOf("") + baseSupportedLocales.map { it.toLanguageTag() } +fun supportedLanguageTags(): List = listOf("") + baseSupportedLocales.map { it.toLanguageTag() } -private fun normalizeLanguageTag(languageTag: String): String = - languageTag.replace('_', '-').trim() +private fun normalizeLanguageTag(languageTag: String): String = languageTag.replace('_', '-').trim() fun findByLanguageTag(languageTag: String): Locale { val normalizedLanguageTag = normalizeLanguageTag(languageTag) @@ -106,5 +103,4 @@ fun currentLanguageTag(): String = currentLocale.toLanguageTag() fun toLanguageTag(locale: Locale): String = locale.toLanguageTag() -fun Locale.toSupportedLocaleTag(): String = - findByLanguageTag(this.toLanguageTag()).toLanguageTag() +fun Locale.toSupportedLocaleTag(): String = findByLanguageTag(this.toLanguageTag()).toLanguageTag() diff --git a/app/src/main/kotlin/com/vrem/util/StringUtils.kt b/app/src/main/kotlin/com/vrem/util/StringUtils.kt index 37cfda7b..afc68e67 100644 --- a/app/src/main/kotlin/com/vrem/util/StringUtils.kt +++ b/app/src/main/kotlin/com/vrem/util/StringUtils.kt @@ -28,5 +28,4 @@ fun String.specialTrim(): String = this.trim { it <= ' ' }.replace(" +".toRegex( fun String.toCapitalize(locale: Locale): String = this.replaceFirstChar { word -> word.uppercase(locale) } -fun String.titlecaseFirst(locale: Locale): String = - replaceFirstChar { it.titlecase(locale) } +fun String.titlecaseFirst(locale: Locale): String = replaceFirstChar { it.titlecase(locale) } diff --git a/app/src/main/kotlin/com/vrem/wifianalyzer/MainActivity.kt b/app/src/main/kotlin/com/vrem/wifianalyzer/MainActivity.kt index 9c77a6d6..c0a9d2e3 100644 --- a/app/src/main/kotlin/com/vrem/wifianalyzer/MainActivity.kt +++ b/app/src/main/kotlin/com/vrem/wifianalyzer/MainActivity.kt @@ -119,10 +119,11 @@ class MainActivity : val languageKey = getString(R.string.language_key) if (key == languageKey) { val languageTag = sharedPreferences.getString(languageKey, "") - val locales = languageTag - ?.takeIf { it.isNotEmpty() } - ?.let(LocaleListCompat::forLanguageTags) - ?: LocaleListCompat.getEmptyLocaleList() + val locales = + languageTag + ?.takeIf { it.isNotEmpty() } + ?.let(LocaleListCompat::forLanguageTags) + ?: LocaleListCompat.getEmptyLocaleList() AppCompatDelegate.setApplicationLocales(locales) } diff --git a/app/src/main/kotlin/com/vrem/wifianalyzer/MainReload.kt b/app/src/main/kotlin/com/vrem/wifianalyzer/MainReload.kt index a8d087c2..67b10edf 100644 --- a/app/src/main/kotlin/com/vrem/wifianalyzer/MainReload.kt +++ b/app/src/main/kotlin/com/vrem/wifianalyzer/MainReload.kt @@ -29,8 +29,7 @@ class MainReload( var connectionViewType: ConnectionViewType private set - fun shouldReload(settings: Settings): Boolean = - themeChanged(settings) || connectionViewTypeChanged(settings) + fun shouldReload(settings: Settings): Boolean = themeChanged(settings) || connectionViewTypeChanged(settings) private fun connectionViewTypeChanged(settings: Settings): Boolean { val currentConnectionViewType = settings.connectionViewType() diff --git a/app/src/main/kotlin/com/vrem/wifianalyzer/settings/Settings.kt b/app/src/main/kotlin/com/vrem/wifianalyzer/settings/Settings.kt index 311cbb6c..05563b27 100644 --- a/app/src/main/kotlin/com/vrem/wifianalyzer/settings/Settings.kt +++ b/app/src/main/kotlin/com/vrem/wifianalyzer/settings/Settings.kt @@ -70,8 +70,7 @@ class Settings( fun countryCode(): String = repository.string(R.string.country_code_key, currentCountryCode()) - fun appLocale(): Locale = - findByLanguageTag(AppCompatDelegate.getApplicationLocales().toLanguageTags()) + fun appLocale(): Locale = findByLanguageTag(AppCompatDelegate.getApplicationLocales().toLanguageTags()) fun syncLanguage() { val appLocaleTag = appLocale().toSupportedLocaleTag() From c3f3466dd210afb0d61bb4682608e1e46c2fa5b5 Mon Sep 17 00:00:00 2001 From: Akihiro Nagai <77012577+314systems@users.noreply.github.com> Date: Thu, 1 Jan 2026 13:16:23 +0900 Subject: [PATCH 09/12] reorder and update supported locales in LocaleUtils and LocaleUtilsTest --- .../main/kotlin/com/vrem/util/LocaleUtils.kt | 18 ++++++++---------- .../kotlin/com/vrem/util/LocaleUtilsTest.kt | 8 ++++---- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt b/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt index 17395ae8..8b5ce78f 100644 --- a/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt +++ b/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt @@ -29,9 +29,7 @@ private val countriesLocales: SortedMap = .toSortedMap() val BULGARIAN: Locale = Locale.forLanguageTag("bg") - val CHINESE_SIMPLIFIED: Locale = Locale.forLanguageTag("zh-Hans") - val CHINESE_TRADITIONAL: Locale = Locale.forLanguageTag("zh-Hant") val DUTCH: Locale = Locale.forLanguageTag("nl") val ENGLISH: Locale = Locale.forLanguageTag("en") @@ -42,34 +40,34 @@ val HUNGARIAN: Locale = Locale.forLanguageTag("hu") val ITALIAN: Locale = Locale.forLanguageTag("it") val JAPANESE: Locale = Locale.forLanguageTag("ja") val POLISH: Locale = Locale.forLanguageTag("pl") -val PORTUGUESE_PORTUGAL: Locale = Locale.forLanguageTag("pt-PT") val PORTUGUESE_BRAZIL: Locale = Locale.forLanguageTag("pt-BR") -val SPANISH: Locale = Locale.forLanguageTag("es") +val PORTUGUESE_PORTUGAL: Locale = Locale.forLanguageTag("pt-PT") val RUSSIAN: Locale = Locale.forLanguageTag("ru") +val SPANISH: Locale = Locale.forLanguageTag("es") val TURKISH: Locale = Locale.forLanguageTag("tr") val UKRAINIAN: Locale = Locale.forLanguageTag("uk") val baseSupportedLocales: List = - setOf( + listOf( BULGARIAN, - DUTCH, - GREEK, - HUNGARIAN, CHINESE_SIMPLIFIED, CHINESE_TRADITIONAL, + DUTCH, ENGLISH, FRENCH, GERMAN, + GREEK, + HUNGARIAN, ITALIAN, JAPANESE, POLISH, PORTUGUESE_BRAZIL, PORTUGUESE_PORTUGAL, - SPANISH, RUSSIAN, + SPANISH, TURKISH, UKRAINIAN, - ).toList() + ) fun findByCountryCode(countryCode: String): Locale = availableLocales.firstOrNull { countryCode.uppercase(Locale.ROOT) == it.country } diff --git a/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt b/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt index 21c3c238..d5849b85 100644 --- a/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt +++ b/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt @@ -103,21 +103,21 @@ class LocaleUtilsTest { val expected: Set = setOf( BULGARIAN, - DUTCH, - GREEK, - HUNGARIAN, CHINESE_SIMPLIFIED, CHINESE_TRADITIONAL, + DUTCH, ENGLISH, FRENCH, GERMAN, + GREEK, + HUNGARIAN, ITALIAN, JAPANESE, POLISH, PORTUGUESE_BRAZIL, PORTUGUESE_PORTUGAL, - SPANISH, RUSSIAN, + SPANISH, TURKISH, UKRAINIAN, Locale.getDefault(), From 8ce2941fe88e49350f06398e14cec8d78dcb0ef1 Mon Sep 17 00:00:00 2001 From: Akihiro Nagai <77012577+314systems@users.noreply.github.com> Date: Thu, 1 Jan 2026 14:17:13 +0900 Subject: [PATCH 10/12] rename variable --- .../wifi/channelavailable/ChannelAvailableFragment.kt | 4 ++-- app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/vrem/wifianalyzer/wifi/channelavailable/ChannelAvailableFragment.kt b/app/src/main/kotlin/com/vrem/wifianalyzer/wifi/channelavailable/ChannelAvailableFragment.kt index a9bf4b67..a78b030c 100644 --- a/app/src/main/kotlin/com/vrem/wifianalyzer/wifi/channelavailable/ChannelAvailableFragment.kt +++ b/app/src/main/kotlin/com/vrem/wifianalyzer/wifi/channelavailable/ChannelAvailableFragment.kt @@ -44,7 +44,7 @@ class ChannelAvailableFragment : Fragment() { private fun update() { val settings = MainContext.INSTANCE.settings val countryCode = settings.countryCode() - val languageLocale = settings.appLocale() + val appLocale = settings.appLocale() binding.apply { val textViews = listOf( @@ -61,7 +61,7 @@ class ChannelAvailableFragment : Fragment() { Triple(channelsAvailable6GHz320MHz, WiFiBand.GHZ6, WiFiWidth.MHZ_320), ) channelsAvailableCountryCode.text = countryCode - channelsAvailableCountryName.text = WiFiChannelCountry.find(countryCode).countryName(languageLocale) + channelsAvailableCountryName.text = WiFiChannelCountry.find(countryCode).countryName(appLocale) textViews.forEach { (textView, wiFiBand, wiFiWidth) -> textView.text = wiFiBand.wiFiChannels.availableChannels(wiFiWidth, wiFiBand, countryCode).joinToString(", ") diff --git a/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt b/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt index d5849b85..f6e4cf31 100644 --- a/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt +++ b/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt @@ -132,12 +132,12 @@ class LocaleUtilsTest { } @Test - fun currentCurrentCountryCode() { + fun currentCountryCodeReturnsDefault() { assertThat(currentCountryCode()).isEqualTo(Locale.getDefault().country) } @Test - fun currentCurrentLanguageTag() { + fun currentLanguageTagReturnsDefault() { assertThat(currentLanguageTag()).isEqualTo(toLanguageTag(Locale.getDefault())) } } From feaa2c816ecf57abd949a876fdecf8c4fac30611 Mon Sep 17 00:00:00 2001 From: Akihiro Nagai <77012577+314systems@users.noreply.github.com> Date: Thu, 1 Jan 2026 14:17:30 +0900 Subject: [PATCH 11/12] remove createContext from CompatUtils and CompatUtilsTest --- .../main/kotlin/com/vrem/util/CompatUtils.kt | 10 ---------- .../kotlin/com/vrem/util/CompatUtilsTest.kt | 19 ------------------- 2 files changed, 29 deletions(-) diff --git a/app/src/main/kotlin/com/vrem/util/CompatUtils.kt b/app/src/main/kotlin/com/vrem/util/CompatUtils.kt index 97fde6cc..4873c617 100644 --- a/app/src/main/kotlin/com/vrem/util/CompatUtils.kt +++ b/app/src/main/kotlin/com/vrem/util/CompatUtils.kt @@ -20,19 +20,9 @@ package com.vrem.util import android.content.Context import android.content.pm.PackageInfo import android.content.pm.PackageManager.PackageInfoFlags -import android.content.res.Configuration -import android.content.res.Resources import android.net.wifi.ScanResult import android.os.Build import androidx.annotation.RequiresApi -import java.util.Locale - -fun Context.createContext(newLocale: Locale): Context { - val resources: Resources = resources - val configuration: Configuration = resources.configuration - configuration.setLocale(newLocale) - return createConfigurationContext(configuration) -} fun Context.packageInfo(): PackageInfo = if (buildMinVersionT()) { diff --git a/app/src/test/kotlin/com/vrem/util/CompatUtilsTest.kt b/app/src/test/kotlin/com/vrem/util/CompatUtilsTest.kt index 3500053a..adb149c3 100644 --- a/app/src/test/kotlin/com/vrem/util/CompatUtilsTest.kt +++ b/app/src/test/kotlin/com/vrem/util/CompatUtilsTest.kt @@ -79,25 +79,6 @@ class CompatUtilsTest { verifyNoMoreInteractions(wifiSsid) } - @Test - fun createContext() { - // setup - whenever(context.resources).thenReturn(resources) - whenever(resources.configuration).thenReturn(configuration) - whenever(context.createConfigurationContext(configuration)).thenReturn(contextWrapper) - whenever(contextWrapper.baseContext).thenReturn(context) - // execute - val actual: Context = context.createContext(newLocale) - // validate - assertThat(actual).isEqualTo(contextWrapper) - assertThat((actual as ContextWrapper).baseContext).isEqualTo(context) - verify(configuration).setLocale(newLocale) - verify(context).createConfigurationContext(configuration) - verify(context).resources - verify(contextWrapper).baseContext - verify(resources).configuration - } - @Test fun contextPackageInfo() { // setup From 286c209c59ef5e4859e14ace98003666f341b0cc Mon Sep 17 00:00:00 2001 From: Akihiro Nagai <77012577+314systems@users.noreply.github.com> Date: Thu, 1 Jan 2026 17:46:35 +0900 Subject: [PATCH 12/12] add support for Chinese language tags and update tests --- app/src/main/kotlin/com/vrem/util/LocaleUtils.kt | 15 +++++++++++++++ .../test/kotlin/com/vrem/util/LocaleUtilsTest.kt | 13 ++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt b/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt index 8b5ce78f..68f0ef1e 100644 --- a/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt +++ b/app/src/main/kotlin/com/vrem/util/LocaleUtils.kt @@ -29,6 +29,7 @@ private val countriesLocales: SortedMap = .toSortedMap() val BULGARIAN: Locale = Locale.forLanguageTag("bg") +val CHINESE: Locale = Locale.forLanguageTag("zh") val CHINESE_SIMPLIFIED: Locale = Locale.forLanguageTag("zh-Hans") val CHINESE_TRADITIONAL: Locale = Locale.forLanguageTag("zh-Hant") val DUTCH: Locale = Locale.forLanguageTag("nl") @@ -81,6 +82,15 @@ fun supportedLanguageTags(): List = listOf("") + baseSupportedLocales.ma private fun normalizeLanguageTag(languageTag: String): String = languageTag.replace('_', '-').trim() +private val chineseCountryToLocale: Map = + mapOf( + "CN" to CHINESE_SIMPLIFIED, + "SG" to CHINESE_SIMPLIFIED, + "TW" to CHINESE_TRADITIONAL, + "HK" to CHINESE_TRADITIONAL, + "MO" to CHINESE_TRADITIONAL, + ) + fun findByLanguageTag(languageTag: String): Locale { val normalizedLanguageTag = normalizeLanguageTag(languageTag) if (normalizedLanguageTag.isEmpty()) return currentLocale @@ -88,6 +98,11 @@ fun findByLanguageTag(languageTag: String): Locale { val target = Locale.forLanguageTag(normalizedLanguageTag) if (target.language.isEmpty()) return currentLocale + if (target.language == "zh" && target.script.isEmpty()) { + if (target.country.isEmpty()) return CHINESE + return chineseCountryToLocale[target.country] ?: CHINESE + } + return baseSupportedLocales.find { it == target } ?: baseSupportedLocales.find { it.language == target.language && it.script == target.script } ?: baseSupportedLocales.find { it.language == target.language && it.country == target.country } diff --git a/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt b/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt index f6e4cf31..cba2dd97 100644 --- a/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt +++ b/app/src/test/kotlin/com/vrem/util/LocaleUtilsTest.kt @@ -86,15 +86,22 @@ class LocaleUtilsTest { @Test fun findByLanguageTagWithKnownTag() { // BCP-47 format (new) - assertThat(findByLanguageTag("zh-Hans")).isEqualTo(CHINESE_SIMPLIFIED) - assertThat(findByLanguageTag("zh-Hant")).isEqualTo(CHINESE_TRADITIONAL) assertThat(findByLanguageTag("en")).isEqualTo(ENGLISH) assertThat(findByLanguageTag("en-US")).isEqualTo(ENGLISH) + assertThat(findByLanguageTag("zh")).isEqualTo(CHINESE) + assertThat(findByLanguageTag("zh-CN")).isEqualTo(CHINESE_SIMPLIFIED) + assertThat(findByLanguageTag("zh-Hans")).isEqualTo(CHINESE_SIMPLIFIED) + assertThat(findByLanguageTag("zh-Hant")).isEqualTo(CHINESE_TRADITIONAL) + assertThat(findByLanguageTag("zh-TW")).isEqualTo(CHINESE_TRADITIONAL) + assertThat(findByLanguageTag("zh-XX")).isEqualTo(CHINESE) // Backward compatibility: underscore format (old) + assertThat(findByLanguageTag("en_US")).isEqualTo(ENGLISH) + assertThat(findByLanguageTag("zh_CN")).isEqualTo(CHINESE_SIMPLIFIED) assertThat(findByLanguageTag("zh_Hans")).isEqualTo(CHINESE_SIMPLIFIED) assertThat(findByLanguageTag("zh_Hant")).isEqualTo(CHINESE_TRADITIONAL) - assertThat(findByLanguageTag("en_US")).isEqualTo(ENGLISH) + assertThat(findByLanguageTag("zh_TW")).isEqualTo(CHINESE_TRADITIONAL) + assertThat(findByLanguageTag("zh_XX")).isEqualTo(CHINESE) } @Test