Skip to content
4 changes: 4 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ android {
lint {
lintConfig = file("lint.xml")
}

androidResources {
generateLocaleConfig true
}
}

allOpen {
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,14 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>
</application>
</manifest>
115 changes: 61 additions & 54 deletions app/src/main/kotlin/com/vrem/util/LocaleUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,43 +20,27 @@ package com.vrem.util
import java.util.Locale
import java.util.SortedMap

private object SyncAvoid {
val defaultLocale: Locale = Locale.getDefault()
val countryCodes: Set<String> = Locale.getISOCountries().toSet()
val availableLocales: List<Locale> = Locale.getAvailableLocales().filter { countryCodes.contains(it.country) }

val countriesLocales: SortedMap<String, Locale> =
availableLocales
.associateBy { it.country.toCapitalize(Locale.getDefault()) }
.toSortedMap()
val supportedLocales: List<Locale> =
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<String> = Locale.getISOCountries().toSet()
private val availableLocales: List<Locale> = Locale.getAvailableLocales().filter { countryCodes.contains(it.country) }
private val countriesLocales: SortedMap<String, Locale> =
availableLocales
.associateBy { it.country.toCapitalize(currentLocale) }
.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")
Expand All @@ -65,35 +49,58 @@ val RUSSIAN: Locale = Locale.forLanguageTag("ru")
val TURKISH: Locale = Locale.forLanguageTag("tr")
val UKRAINIAN: Locale = Locale.forLanguageTag("uk")

private const val SEPARATOR: String = "_"
val baseSupportedLocales: List<Locale> =
setOf(
BULGARIAN,
DUTCH,
GREEK,
HUNGARIAN,
CHINESE_SIMPLIFIED,
CHINESE_TRADITIONAL,
ENGLISH,
FRENCH,
GERMAN,
ITALIAN,
JAPANESE,
POLISH,
PORTUGUESE_BRAZIL,
PORTUGUESE_PORTUGAL,
SPANISH,
RUSSIAN,
TURKISH,
UKRAINIAN,
).toList()

fun findByCountryCode(countryCode: String): Locale =
SyncAvoid.availableLocales.firstOrNull { countryCode.toCapitalize(Locale.getDefault()) == it.country }
?: SyncAvoid.defaultLocale
availableLocales.firstOrNull { countryCode.uppercase(Locale.ROOT) == it.country }
?: currentLocale

fun allCountries(): List<Locale> = SyncAvoid.countriesLocales.values.toList()
fun allCountries(): List<Locale> = countriesLocales.values.toList()

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
}
fun supportedLanguages(): List<Locale> = (baseSupportedLocales + currentLocale).distinct()

fun supportedLanguages(): List<Locale> = SyncAvoid.supportedLocales
fun supportedLanguageTags(): List<String> = listOf("") + baseSupportedLocales.map { it.toLanguageTag() }

fun defaultCountryCode(): String = SyncAvoid.defaultLocale.country
private fun normalizeLanguageTag(languageTag: String): String = languageTag.replace('_', '-').trim()

fun defaultLanguageTag(): String = toLanguageTag(SyncAvoid.defaultLocale)
fun findByLanguageTag(languageTag: String): Locale {
val normalizedLanguageTag = normalizeLanguageTag(languageTag)
if (normalizedLanguageTag.isEmpty()) return currentLocale

fun toLanguageTag(locale: Locale): String = locale.language + SEPARATOR + locale.country
val target = Locale.forLanguageTag(normalizedLanguageTag)
if (target.language.isEmpty()) return currentLocale

private fun fromLanguageTag(languageTag: String): Locale {
val codes: Array<String> = 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
}
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

fun currentLanguageTag(): String = currentLocale.toLanguageTag()

fun toLanguageTag(locale: Locale): String = locale.toLanguageTag()

fun Locale.toSupportedLocaleTag(): String = findByLanguageTag(this.toLanguageTag()).toLanguageTag()
2 changes: 2 additions & 0 deletions app/src/main/kotlin/com/vrem/util/StringUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ 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) }
22 changes: 15 additions & 7 deletions app/src/main/kotlin/com/vrem/wifianalyzer/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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)
Expand Down Expand Up @@ -120,6 +116,18 @@ 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()
Expand Down
16 changes: 1 addition & 15 deletions app/src/main/kotlin/com/vrem/wifianalyzer/MainReload.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -29,11 +28,8 @@ 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)
fun shouldReload(settings: Settings): Boolean = themeChanged(settings) || connectionViewTypeChanged(settings)

private fun connectionViewTypeChanged(settings: Settings): Boolean {
val currentConnectionViewType = settings.connectionViewType()
Expand All @@ -53,18 +49,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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ 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

private fun data(): List<Data> {
val currentLocale: Locale = MainContext.INSTANCE.settings.languageLocale()
val currentLocale: Locale = MainContext.INSTANCE.settings.appLocale()
return WiFiChannelCountry
.findAll()
.map { Data(it.countryCode, it.countryName(currentLocale)) }
Expand All @@ -35,4 +35,4 @@ private fun data(): List<Data> {
class CountryPreference(
context: Context,
attrs: AttributeSet,
) : CustomPreference(context, attrs, data(), defaultCountryCode())
) : CustomPreference(context, attrs, data(), currentCountryCode())
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,22 @@ package com.vrem.wifianalyzer.settings

import android.content.Context
import android.util.AttributeSet
import com.vrem.util.defaultLanguageTag
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<Data> =
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<Data> =
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(), defaultLanguageTag())
) : CustomPreference(context, attrs, data(context), "")
18 changes: 11 additions & 7 deletions app/src/main/kotlin/com/vrem/wifianalyzer/settings/Settings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.defaultCountryCode
import com.vrem.util.defaultLanguageTag
import com.vrem.util.currentCountryCode
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
Expand Down Expand Up @@ -67,12 +68,15 @@ 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)
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> =
vendorData.vendors[vendorName.uppercase(Locale.getDefault())].orEmpty()
vendorData.vendors[vendorName.uppercase(Locale.ROOT)].orEmpty()

fun findVendors(vendorName: String = String.EMPTY): List<String> {
val name = vendorName.uppercase(Locale.getDefault())
val name = vendorName.uppercase(Locale.ROOT)
return vendorData.vendors
.filterKeys { filter(it, name) }
.keys
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/resources.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
unqualifiedResLocale=en-US
Loading
Loading