Skip to content

Commit

Permalink
add 'normalize' method for Locales
Browse files Browse the repository at this point in the history
In {{tts-voices:}} we require a method which converts from
a locale provided from the TTS Engine, and produce an Anki-compatible
Two Letter ISO Code

spa-MEX => es-MX

This is not possible within `Locale`

In Android 24+, this can be handled by the ULocale class
In Android 23:
We we have a list of system locales in the correct format
Use the ISO-3 code to map to the system locales, and use their data

Issue 14358
  • Loading branch information
david-allison authored and mikehardy committed Oct 31, 2023
1 parent 90d9870 commit 9615918
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2023 David Allison <[email protected]>
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.ichi2.anki.compat

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.ichi2.anki.tests.InstrumentedTest
import com.ichi2.compat.CompatHelper
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.*
import org.junit.Test
import org.junit.runner.RunWith
import java.util.Locale

@RunWith(AndroidJUnit4::class)
class CompatNormalizeTest : InstrumentedTest() {
@Test
fun normalize() {
fun assertEqual(l: Locale, str: String) {
val normalized = CompatHelper.compat.normalize(l)
assertThat(normalized.toLanguageTag(), equalTo(str))
}

assertEqual(Locale("en", "GB"), "en-GB")
assertEqual(Locale("es", "MX"), "es-MX")
assertEqual(Locale("spa", "MEX"), "es-MX")
assertEqual(Locale("fil", "PH"), "fil-PH")
// TBC
assertEqual(Locale("ar", ""), "ar")
}
}
8 changes: 8 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/compat/Compat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import android.media.MediaRecorder
import android.net.Uri
import android.os.Bundle
import android.view.View
import androidx.annotation.CheckResult
import java.io.*
import java.util.*

Expand Down Expand Up @@ -199,6 +200,13 @@ interface Compat {
@Throws(IOException::class)
fun contentOfDirectory(directory: File): FileStream

/**
* Converts a locale to a 'two letter' code (ISO-639-1 + ISO 3166-1 alpha-2)
* Locale("spa", "MEX", "001") => Locale("es", "MX", "001")
*/
@CheckResult
fun normalize(locale: Locale): Locale

companion object {
/* Mock the Intent PROCESS_TEXT constants introduced in API 23. */
const val ACTION_PROCESS_TEXT = "android.intent.action.PROCESS_TEXT"
Expand Down
35 changes: 35 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/compat/CompatV23.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.io.Serializable
import java.util.Locale

/** Baseline implementation of [Compat]. Check [Compat]'s for more detail. */
@KotlinCleanup("add extension method logging file.delete() failure" + "Fix Deprecation")
Expand Down Expand Up @@ -269,4 +270,38 @@ open class CompatV23 : Compat {
key: String,
clazz: Class<T>
): T? = bundle.getSerializable(key) as? T?

override fun normalize(locale: Locale): Locale {
// normalises to "spa_MEX"
val iso3Code = getIso3Code(locale) ?: return locale
// convert back from this key to a two-letter mapping
return twoLetterSystemLocaleMapping[iso3Code] ?: locale
}
companion object {
/**
* Maps from the ISO 3 code of a locale to the locale in
*/
private val twoLetterSystemLocaleMapping: Map<String, Locale>

fun getIso3Code(locale: Locale): String? {
try {
if (locale.country.isBlank()) {
return locale.isO3Language
}
return "${locale.isO3Language}_${locale.isO3Country}"
} catch (e: Exception) {
// MissingResourceException can be thrown, in which case return null
return null
}
}
init {
val locales = Locale.getAvailableLocales()
val validLocales = mutableMapOf<String, Locale>()
for (locale in locales) {
val code = getIso3Code(locale) ?: continue
validLocales.putIfAbsent(code, locale)
}
twoLetterSystemLocaleMapping = validLocales
}
}
}
20 changes: 19 additions & 1 deletion AnkiDroid/src/main/java/com/ichi2/compat/CompatV24.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,25 @@
package com.ichi2.compat

import android.annotation.TargetApi
import android.icu.util.ULocale
import com.ichi2.utils.isRobolectric
import timber.log.Timber
import java.util.Locale

/** Implementation of [Compat] for SDK level 24 and higher. Check [Compat]'s for more detail. */
@TargetApi(24)
open class CompatV24 : CompatV23(), Compat
open class CompatV24 : CompatV23(), Compat {
override fun normalize(locale: Locale): Locale {
// ULocale isn't currently handled by Robolectric
if (isRobolectric) {
return super.normalize(locale)
}
return try {
val uLocale = ULocale(locale.language, locale.country, locale.variant)
Locale(uLocale.language, uLocale.country, uLocale.variant)
} catch (e: Exception) {
Timber.w("Failed to normalize locale %s", locale, e)
locale
}
}
}

0 comments on commit 9615918

Please sign in to comment.