Added language selection (bitfireAT/davx5#137)

* added language selection

* PermissionsFragment: Hide notification permission switch on Android < 13

* Retrieve locales with a function

* Added locales flavoring

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Moved locales functions to `locales.gradle`

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Added `locales_config.xml` generation

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Now gets generated automatically

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Added `android:localeConfig`

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Updated submodules

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

* Using `logger` instead of `println`

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>

Signed-off-by: Arnau Mora <arnyminer.z@gmail.com>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
Co-authored-by: Arnau Mora <arnyminer.z@gmail.com>
This commit is contained in:
Patrick Lang 2022-12-19 18:22:10 +01:00 committed by Ricki Hirner
parent 0521f4bcf0
commit 1f818ee3b4
No known key found for this signature in database
GPG key ID: 79A019FCAAEDD3AA
8 changed files with 184 additions and 2 deletions

View file

@ -8,6 +8,8 @@ apply plugin: 'dagger.hilt.android.plugin'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply from: 'locales.gradle'
android {
compileSdkVersion 33
buildToolsVersion '33.0.0'
@ -33,6 +35,16 @@ android {
arg("room.schemaLocation", "$projectDir/schemas")
}
}
applicationVariants.all { variant ->
def locales = getLocales(variant.flavorName)
variant.buildConfigField "String[]", "TRANSLATION_ARRAY", "new String[]{\"" + locales.join("\",\"") + "\"}"
variant.mergedFlavor.resourceConfigurations.clear()
variant.mergedFlavor.resourceConfigurations.addAll(locales)
generateLocalesConfig(variant.flavorName, locales)
}
}
compileOptions {
@ -62,6 +74,9 @@ android {
}
sourceSets {
ose.res.srcDirs += "build/generated/res/locale-ose"
androidTest.java.srcDirs = [ "src/androidTest/java" ]
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
@ -110,7 +125,7 @@ dependencies {
implementation "com.google.dagger:hilt-android:${versions.hilt}"
kapt "com.google.dagger:hilt-android-compiler:${versions.hilt}"
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'androidx.appcompat:appcompat:1.6.0-rc01'
implementation 'androidx.browser:browser:1.4.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.concurrent:concurrent-futures-ktx:1.1.0'

105
app/locales.gradle Normal file
View file

@ -0,0 +1,105 @@
import groovy.xml.MarkupBuilder
import static groovy.io.FileType.DIRECTORIES
/**
* Obtains a list of all the available locales for an specific flavor
* @since 20221123
* @param flavorDir The base directory of the flavor inside `/app/src`
* @return A list with the language codes of the locales available.
*/
Set<String> getLocalesForFlavor(File flavorDir) {
def dir = new File(flavorDir, "res")
if (!flavorDir.exists()) {
logger.warn("Tried to get locales for non-existing flavor. Directory: $flavorDir")
return new LinkedHashSet()
}
if (!dir.exists()) {
logger.warn("Tried to get locales for a flavor without strings. Directory: $dir")
return new LinkedHashSet()
}
// Initialize the list English, since it's available by default
Set<String> locales = new LinkedHashSet(['en'])
// Get all directories inside resources
logger.trace("Getting locales values directories from $dir")
dir.traverse(type: DIRECTORIES, maxDepth: 0) { file ->
// Get only values directories
def fileName = file.name
if (!fileName.startsWith("values-")) return
// Take only the values directories that contain strings
def stringsFile = new File(file, "strings.xml")
if (!stringsFile.exists()) return
// Add to the list the locale of the strings file
def langCode = fileName.substring(fileName.indexOf('-') + 1)
locales.add(langCode)
}
// Log the available locales
logger.info('Supported locales: ' + locales.join(", "))
// Return the built list
return locales
}
/**
* Obtains a list of all the available locales
* @since 20220928
* @return A list with the language codes of the locales available.
*/
Set<String> getLocales(String flavor) {
// Get all the flavor directories
def dir = new File(projectDir, "src")
// Get a list of locales for the base flavor
def mainDir = new File(dir, 'main')
logger.trace("Getting main locales ($mainDir)...")
def mainLocales = getLocalesForFlavor(mainDir)
// Get the current flavor
def flavorDir = new File(dir, flavor)
logger.trace("Getting locales for flavor $flavor ($flavorDir)...")
def flavorLocales = getLocalesForFlavor(flavorDir)
// Build the locales list
// We use Set for avoiding duplicates
Set<String> locales = new LinkedHashSet()
locales.addAll(mainLocales)
locales.addAll(flavorLocales)
// Log the available locales
logger.trace("Supported locales for flavor $flavor: " + locales.join(', '))
return locales
}
def generateLocalesConfig(String flavor, Set<String> locales) {
def outputDir = new File(projectDir, "build/generated/res/locale-$flavor/xml")
mkdir outputDir
logger.trace("Generating locales_config.xml...")
new File(outputDir, "locales_config.xml").withWriter { writer ->
def destXml = new MarkupBuilder(new IndentPrinter(writer, " ", true, true))
destXml.setDoubleQuotes(true)
def destXmlMkp = destXml.getMkp()
destXmlMkp.xmlDeclaration(version: "1.0", encoding: "utf-8")
destXmlMkp.comment("Generated at ${new Date()}")
destXmlMkp.yield "\r\n"
destXml."locale-config"(['xmlns:android': "http://schemas.android.com/apk/res/android"]) {
locales.forEach { locale ->
destXml."locale"("android:name": locale)
}
}
}
}
// Export getLocales and generateLocalesConfig
ext {
getLocales = this.&getLocales
generateLocalesConfig = this.&generateLocalesConfig
}

View file

@ -48,7 +48,8 @@
android:theme="@style/AppTheme"
android:resizeableActivity="true"
tools:ignore="UnusedAttribute"
android:supportsRtl="true">
android:supportsRtl="true"
android:localeConfig="@xml/locales_config">
<!-- required for Hilt/WorkManager integration -->
<provider
@ -300,6 +301,17 @@
android:resource="@xml/debug_paths" />
</provider>
<!-- Support language selection on Android 12 and lower -->
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>
</application>
<!-- package visiblity which apps do we need to see? -->

View file

@ -34,6 +34,9 @@ object Settings {
const val PREFERRED_THEME = "preferred_theme"
const val PREFERRED_THEME_DEFAULT = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
const val LANGUAGE = "language"
const val LANGUAGE_SYSTEM = "language_system"
const val PREFERRED_TASKS_PROVIDER = "preferred_tasks_provider"
/** whether detected collections are selected for synchronization for default */

View file

@ -15,6 +15,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.UiThread
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat
import androidx.preference.*
import at.bitfire.cert4android.CustomCertManager
import at.bitfire.davdroid.BuildConfig
@ -32,6 +33,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.net.URI
import java.net.URISyntaxException
import java.util.*
import javax.inject.Inject
import kotlin.math.roundToInt
@ -227,6 +229,38 @@ class AppSettingsActivity: AppCompatActivity() {
false
}
}
findPreference<ListPreference>(Settings.LANGUAGE)!!.apply {
val languageOptions = mutableListOf<Locale?>(null)
for (language in BuildConfig.TRANSLATION_ARRAY) {
languageOptions.add(Locale.forLanguageTag(language))
}
this.entries = languageOptions.map { language -> language?.displayLanguage ?: context.getText(R.string.app_settings_language_system_default) }.toTypedArray()
this.entryValues = languageOptions.map { language -> language?.language ?: Settings.LANGUAGE_SYSTEM }.toTypedArray()
val appCompatLocales = AppCompatDelegate.getApplicationLocales()
var currentLocale: Locale? = null
if(!appCompatLocales.isEmpty) {
for (i in 0 until appCompatLocales.size()) {
val locale = appCompatLocales[i] ?: continue
if (languageOptions.contains(appCompatLocales[i]!!)) {
currentLocale = locale
break
}
}
}
setValueIndex(entryValues.indexOf(currentLocale?.language ?: Settings.LANGUAGE_SYSTEM))
summary = entry
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
if(newValue.toString() == Settings.LANGUAGE_SYSTEM)
AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList())
else {
val newLanguage = Locale.forLanguageTag(newValue.toString())
AppCompatDelegate.setApplicationLocales(LocaleListCompat.create(newLanguage))
}
false
}
}
// integration settings
findPreference<Preference>(Settings.PREFERRED_TASKS_PROVIDER)!!.apply {

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM18.92,8h-2.95c-0.32,-1.25 -0.78,-2.45 -1.38,-3.56 1.84,0.63 3.37,1.91 4.33,3.56zM12,4.04c0.83,1.2 1.48,2.53 1.91,3.96h-3.82c0.43,-1.43 1.08,-2.76 1.91,-3.96zM4.26,14C4.1,13.36 4,12.69 4,12s0.1,-1.36 0.26,-2h3.38c-0.08,0.66 -0.14,1.32 -0.14,2 0,0.68 0.06,1.34 0.14,2L4.26,14zM5.08,16h2.95c0.32,1.25 0.78,2.45 1.38,3.56 -1.84,-0.63 -3.37,-1.9 -4.33,-3.56zM8.03,8L5.08,8c0.96,-1.66 2.49,-2.93 4.33,-3.56C8.81,5.55 8.35,6.75 8.03,8zM12,19.96c-0.83,-1.2 -1.48,-2.53 -1.91,-3.96h3.82c-0.43,1.43 -1.08,2.76 -1.91,3.96zM14.34,14L9.66,14c-0.09,-0.66 -0.16,-1.32 -0.16,-2 0,-0.68 0.07,-1.35 0.16,-2h4.68c0.09,0.65 0.16,1.32 0.16,2 0,0.68 -0.07,1.34 -0.16,2zM14.59,19.56c0.6,-1.11 1.06,-2.31 1.38,-3.56h2.95c-0.96,1.65 -2.49,2.93 -4.33,3.56zM16.36,14c0.08,-0.66 0.14,-1.32 0.14,-2 0,-0.68 -0.06,-1.34 -0.14,-2h3.38c0.16,0.64 0.26,1.31 0.26,2s-0.1,1.36 -0.26,2h-3.38z"/>
</vector>

View file

@ -206,6 +206,8 @@
<string name="app_settings_notification_settings">Notification settings</string>
<string name="app_settings_notification_settings_summary">Manage notification channels and their settings</string>
<string name="app_settings_theme_title">Select theme</string>
<string name="app_settings_language_title">Select language</string>
<string name="app_settings_language_system_default">System default</string>
<string-array name="app_settings_theme_names">
<item>System default</item>
<item>Light</item>

View file

@ -95,6 +95,12 @@
android:entryValues="@array/app_settings_theme_values"
android:persistent="false" />
<ListPreference
android:key="language"
android:icon="@drawable/ic_language"
android:title="@string/app_settings_language_title"
android:persistent="false" />
<Preference
android:key="reset_hints"
android:title="@string/app_settings_reset_hints"