From ac7fb752dfb1c20c4d0470ae6afb56ee47938884 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 28 Jan 2024 10:42:42 +0100 Subject: [PATCH] Use language tags (#445) WARNING: due to renames, your existing user history and blacklist files might not be used after this commit. If you build the app with this commit, backup and restore settings ot fix it. Use language tags for identifying a string locale, not Locale.toString. This allows to avoid issues with non-default scripts, e.g. we can now use `sr-Latn` instead of the `sr_ZZ` workaround. Existing files are not renamed, but rename will happen when restoring backups. Most of the occurrences of a locale string have been replaced with Locale where possible. One notable exception is in user dictionary settings, where the locale string must be used to retrieve contents from system personal dictionary. Internal script IDs are switched to string as used in language tags, e.g. Latn for latin. This allows for correct interpretation of a Locale with explicitly specified script. --- .../{main_en_gb.dict => main_en-GB.dict} | Bin .../{main_en_us.dict => main_en-US.dict} | Bin .../{main_pt_br.dict => main_pt-BR.dict} | Bin .../{main_pt_pt.dict => main_pt-PT.dict} | Bin .../{bn_bd.txt => bn-BD.txt} | 0 .../{bn_in.txt => bn-IN.txt} | 0 .../{de_ch.txt => de-CH.txt} | 0 .../{de_de.txt => de-DE.txt} | 0 .../{hi_zz.txt => hi-Latn.txt} | 0 .../{sr_zz.txt => sr-Latn.txt} | 0 .../language_key_texts/{fil.txt => tl.txt} | 0 .../inputmethod/compat/ConfigurationCompat.kt | 12 + .../compat/InputMethodSubtypeCompatUtils.kt | 26 -- .../keyboard/KeyboardLayoutSet.java | 8 +- .../keyboard/KeyboardSwitcher.java | 4 +- .../keyboard/MainKeyboardView.java | 5 +- .../keyboard_parser/LocaleKeyTexts.kt | 7 +- .../latin/BinaryDictionaryGetter.java | 318 ------------------ .../latin/DictionaryFacilitatorImpl.java | 4 +- .../inputmethod/latin/DictionaryFactory.java | 99 ------ .../inputmethod/latin/DictionaryFactory.kt | 94 ++++++ .../latin/ExpandableBinaryDictionary.java | 65 +--- .../openboard/inputmethod/latin/LatinIME.java | 13 +- .../latin/RichInputConnection.java | 18 +- .../latin/RichInputMethodManager.java | 31 +- .../latin/RichInputMethodSubtype.java | 42 +-- .../latin/UserBinaryDictionary.java | 19 +- .../inputmethod/latin/common/LocaleUtils.java | 204 ----------- .../inputmethod/latin/common/LocaleUtils.kt | 190 +++++++++++ .../latin/inputlogic/InputLogic.java | 42 +-- .../latin/makedict/DictionaryHeader.kt | 3 +- .../settings/AdvancedSettingsFragment.kt | 40 ++- .../latin/settings/LanguageFilterList.kt | 3 +- .../latin/settings/LanguageSettingsDialog.kt | 74 ++-- .../settings/LanguageSettingsFragment.kt | 66 ++-- .../inputmethod/latin/settings/Settings.java | 22 +- .../latin/settings/SettingsValues.java | 6 +- .../settings/SpacingAndPunctuations.java | 3 +- .../UserDictionaryAddWordContents.java | 87 ++--- .../UserDictionaryAddWordFragment.java | 22 +- .../settings/UserDictionaryListFragment.java | 45 +-- .../settings/UserDictionarySettings.java | 39 +-- .../AndroidSpellCheckerService.java | 34 +- .../AndroidSpellCheckerSession.java | 11 +- .../AndroidWordLevelSpellCheckerSession.java | 32 +- .../latin/utils/AdditionalSubtypeUtils.java | 51 ++- .../latin/utils/CustomLayoutUtils.kt | 8 +- .../latin/utils/DictionaryInfoUtils.java | 104 +++--- .../latin/utils/DictionaryUtils.kt | 22 +- .../latin/utils/LanguageOnSpacebarUtils.java | 2 +- .../latin/utils/NewDictionaryAdder.kt | 35 +- .../inputmethod/latin/utils/ScriptUtils.java | 236 ------------- .../inputmethod/latin/utils/ScriptUtils.kt | 187 ++++++++++ .../latin/utils/SubtypeLocaleUtils.java | 98 +++--- .../latin/utils/SubtypeSettings.kt | 101 +++--- .../inputmethod/latin/utils/SubtypeUtils.kt | 14 + app/src/main/res/values-af/strings.xml | 8 +- app/src/main/res/values-am/strings.xml | 8 +- app/src/main/res/values-ar/strings.xml | 8 +- app/src/main/res/values-az/strings.xml | 8 +- app/src/main/res/values-b+sr+Latn/strings.xml | 8 +- app/src/main/res/values-be/strings.xml | 8 +- app/src/main/res/values-bg/strings.xml | 8 +- app/src/main/res/values-bn/strings.xml | 9 +- app/src/main/res/values-bs/strings.xml | 8 +- app/src/main/res/values-ca/strings.xml | 8 +- app/src/main/res/values-cs/strings.xml | 8 +- app/src/main/res/values-da/strings.xml | 8 +- app/src/main/res/values-de/strings.xml | 8 +- app/src/main/res/values-el/strings.xml | 8 +- app/src/main/res/values-en-rAU/strings.xml | 8 +- app/src/main/res/values-en-rCA/strings.xml | 8 +- app/src/main/res/values-en-rGB/strings.xml | 8 +- app/src/main/res/values-en-rIN/strings.xml | 8 +- app/src/main/res/values-en-rXC/strings.xml | 8 +- app/src/main/res/values-es-rUS/strings.xml | 8 +- app/src/main/res/values-es/strings.xml | 8 +- app/src/main/res/values-et/strings.xml | 8 +- app/src/main/res/values-eu/strings.xml | 8 +- app/src/main/res/values-fa/strings.xml | 8 +- app/src/main/res/values-fi/strings.xml | 8 +- app/src/main/res/values-fr-rCA/strings.xml | 8 +- app/src/main/res/values-fr/strings.xml | 8 +- app/src/main/res/values-gl/strings.xml | 8 +- app/src/main/res/values-gu/strings.xml | 8 +- app/src/main/res/values-hi/strings.xml | 8 +- app/src/main/res/values-hr/strings.xml | 8 +- app/src/main/res/values-hu-rZZ/cm_strings.xml | 19 -- app/src/main/res/values-hu-rZZ/strings.xml | 8 +- app/src/main/res/values-hu/strings.xml | 8 +- app/src/main/res/values-hy/strings.xml | 8 +- app/src/main/res/values-in/strings.xml | 8 +- app/src/main/res/values-is/strings.xml | 8 +- app/src/main/res/values-it/strings.xml | 8 +- app/src/main/res/values-iw/strings.xml | 8 +- app/src/main/res/values-ja/strings.xml | 8 +- app/src/main/res/values-ka/strings.xml | 8 +- app/src/main/res/values-kk/strings.xml | 8 +- app/src/main/res/values-km/strings.xml | 8 +- app/src/main/res/values-kn/strings.xml | 8 +- app/src/main/res/values-ko/strings.xml | 8 +- app/src/main/res/values-ky/strings.xml | 8 +- app/src/main/res/values-lo/strings.xml | 8 +- app/src/main/res/values-lt/strings.xml | 8 +- app/src/main/res/values-lv/strings.xml | 8 +- app/src/main/res/values-mk/strings.xml | 8 +- app/src/main/res/values-ml/strings.xml | 8 +- app/src/main/res/values-mn/strings.xml | 8 +- app/src/main/res/values-mr/strings.xml | 8 +- app/src/main/res/values-ms/strings.xml | 8 +- app/src/main/res/values-my/strings.xml | 8 +- app/src/main/res/values-nb/strings.xml | 8 +- app/src/main/res/values-ne/strings.xml | 8 +- app/src/main/res/values-nl/strings.xml | 8 +- app/src/main/res/values-pa-rPK/strings.xml | 2 +- app/src/main/res/values-pa/strings.xml | 8 +- app/src/main/res/values-pl/strings.xml | 8 +- app/src/main/res/values-pt-rBR/strings.xml | 8 +- app/src/main/res/values-pt-rPT/strings.xml | 8 +- app/src/main/res/values-pt/strings.xml | 8 +- app/src/main/res/values-ro/strings.xml | 8 +- app/src/main/res/values-ru/strings.xml | 8 +- app/src/main/res/values-si/strings.xml | 8 +- app/src/main/res/values-sk/strings.xml | 8 +- app/src/main/res/values-sl/strings.xml | 8 +- app/src/main/res/values-sq/strings.xml | 8 +- app/src/main/res/values-sr/strings.xml | 8 +- app/src/main/res/values-sv/strings.xml | 8 +- app/src/main/res/values-sw/strings.xml | 8 +- app/src/main/res/values-ta/strings.xml | 8 +- app/src/main/res/values-te/strings.xml | 8 +- app/src/main/res/values-th/strings.xml | 8 +- app/src/main/res/values-tl/strings.xml | 8 +- app/src/main/res/values-tr/strings.xml | 8 +- app/src/main/res/values-uk/strings.xml | 8 +- app/src/main/res/values-ur/strings.xml | 8 +- app/src/main/res/values-uz/strings.xml | 8 +- app/src/main/res/values-vi/strings.xml | 8 +- app/src/main/res/values-zh-rCN/strings.xml | 8 +- app/src/main/res/values-zh-rHK/strings.xml | 8 +- app/src/main/res/values-zh-rTW/strings.xml | 8 +- app/src/main/res/values-zu/strings.xml | 8 +- app/src/main/res/values/cm_strings.xml | 19 -- app/src/main/res/values/donottranslate.xml | 19 +- app/src/main/res/values/strings.xml | 19 +- app/src/main/res/xml/method.xml | 8 +- app/src/main/res/xml/spellchecker.xml | 4 - .../inputmethod/latin/LocaleUtilsTest.kt | 17 + .../openboard/inputmethod/latin/ParserTest.kt | 9 +- .../inputmethod/latin/ScriptUtilsTest.kt | 20 ++ 150 files changed, 1446 insertions(+), 1909 deletions(-) rename app/src/main/assets/dicts/{main_en_gb.dict => main_en-GB.dict} (100%) rename app/src/main/assets/dicts/{main_en_us.dict => main_en-US.dict} (100%) rename app/src/main/assets/dicts/{main_pt_br.dict => main_pt-BR.dict} (100%) rename app/src/main/assets/dicts/{main_pt_pt.dict => main_pt-PT.dict} (100%) rename app/src/main/assets/language_key_texts/{bn_bd.txt => bn-BD.txt} (100%) rename app/src/main/assets/language_key_texts/{bn_in.txt => bn-IN.txt} (100%) rename app/src/main/assets/language_key_texts/{de_ch.txt => de-CH.txt} (100%) rename app/src/main/assets/language_key_texts/{de_de.txt => de-DE.txt} (100%) rename app/src/main/assets/language_key_texts/{hi_zz.txt => hi-Latn.txt} (100%) rename app/src/main/assets/language_key_texts/{sr_zz.txt => sr-Latn.txt} (100%) rename app/src/main/assets/language_key_texts/{fil.txt => tl.txt} (100%) create mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/compat/ConfigurationCompat.kt delete mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/compat/InputMethodSubtypeCompatUtils.kt delete mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/latin/BinaryDictionaryGetter.java delete mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/latin/DictionaryFactory.java create mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/latin/DictionaryFactory.kt delete mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/latin/common/LocaleUtils.java create mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/latin/common/LocaleUtils.kt delete mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/ScriptUtils.java create mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/ScriptUtils.kt create mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/SubtypeUtils.kt delete mode 100644 app/src/main/res/values-hu-rZZ/cm_strings.xml delete mode 100644 app/src/main/res/values/cm_strings.xml create mode 100644 app/src/test/java/org/dslul/openboard/inputmethod/latin/LocaleUtilsTest.kt create mode 100644 app/src/test/java/org/dslul/openboard/inputmethod/latin/ScriptUtilsTest.kt diff --git a/app/src/main/assets/dicts/main_en_gb.dict b/app/src/main/assets/dicts/main_en-GB.dict similarity index 100% rename from app/src/main/assets/dicts/main_en_gb.dict rename to app/src/main/assets/dicts/main_en-GB.dict diff --git a/app/src/main/assets/dicts/main_en_us.dict b/app/src/main/assets/dicts/main_en-US.dict similarity index 100% rename from app/src/main/assets/dicts/main_en_us.dict rename to app/src/main/assets/dicts/main_en-US.dict diff --git a/app/src/main/assets/dicts/main_pt_br.dict b/app/src/main/assets/dicts/main_pt-BR.dict similarity index 100% rename from app/src/main/assets/dicts/main_pt_br.dict rename to app/src/main/assets/dicts/main_pt-BR.dict diff --git a/app/src/main/assets/dicts/main_pt_pt.dict b/app/src/main/assets/dicts/main_pt-PT.dict similarity index 100% rename from app/src/main/assets/dicts/main_pt_pt.dict rename to app/src/main/assets/dicts/main_pt-PT.dict diff --git a/app/src/main/assets/language_key_texts/bn_bd.txt b/app/src/main/assets/language_key_texts/bn-BD.txt similarity index 100% rename from app/src/main/assets/language_key_texts/bn_bd.txt rename to app/src/main/assets/language_key_texts/bn-BD.txt diff --git a/app/src/main/assets/language_key_texts/bn_in.txt b/app/src/main/assets/language_key_texts/bn-IN.txt similarity index 100% rename from app/src/main/assets/language_key_texts/bn_in.txt rename to app/src/main/assets/language_key_texts/bn-IN.txt diff --git a/app/src/main/assets/language_key_texts/de_ch.txt b/app/src/main/assets/language_key_texts/de-CH.txt similarity index 100% rename from app/src/main/assets/language_key_texts/de_ch.txt rename to app/src/main/assets/language_key_texts/de-CH.txt diff --git a/app/src/main/assets/language_key_texts/de_de.txt b/app/src/main/assets/language_key_texts/de-DE.txt similarity index 100% rename from app/src/main/assets/language_key_texts/de_de.txt rename to app/src/main/assets/language_key_texts/de-DE.txt diff --git a/app/src/main/assets/language_key_texts/hi_zz.txt b/app/src/main/assets/language_key_texts/hi-Latn.txt similarity index 100% rename from app/src/main/assets/language_key_texts/hi_zz.txt rename to app/src/main/assets/language_key_texts/hi-Latn.txt diff --git a/app/src/main/assets/language_key_texts/sr_zz.txt b/app/src/main/assets/language_key_texts/sr-Latn.txt similarity index 100% rename from app/src/main/assets/language_key_texts/sr_zz.txt rename to app/src/main/assets/language_key_texts/sr-Latn.txt diff --git a/app/src/main/assets/language_key_texts/fil.txt b/app/src/main/assets/language_key_texts/tl.txt similarity index 100% rename from app/src/main/assets/language_key_texts/fil.txt rename to app/src/main/assets/language_key_texts/tl.txt diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/compat/ConfigurationCompat.kt b/app/src/main/java/org/dslul/openboard/inputmethod/compat/ConfigurationCompat.kt new file mode 100644 index 000000000..4c122c091 --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/compat/ConfigurationCompat.kt @@ -0,0 +1,12 @@ +package org.dslul.openboard.inputmethod.compat + +import android.content.res.Configuration +import android.os.Build +import java.util.Locale + +fun Configuration.locale(): Locale = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + locales[0] + } else { + @Suppress("Deprecation") locale + } diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/compat/InputMethodSubtypeCompatUtils.kt b/app/src/main/java/org/dslul/openboard/inputmethod/compat/InputMethodSubtypeCompatUtils.kt deleted file mode 100644 index 5251409ec..000000000 --- a/app/src/main/java/org/dslul/openboard/inputmethod/compat/InputMethodSubtypeCompatUtils.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * modified - * SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only - */ - -package org.dslul.openboard.inputmethod.compat - -import android.os.Build -import android.os.Build.VERSION_CODES -import android.view.inputmethod.InputMethodSubtype -import org.dslul.openboard.inputmethod.latin.common.LocaleUtils -import org.dslul.openboard.inputmethod.latin.utils.locale -import java.util.* - -object InputMethodSubtypeCompatUtils { - @JvmStatic - fun getLocaleObject(subtype: InputMethodSubtype): Locale { // Locale.forLanguageTag() is available only in Android L and later. - if (Build.VERSION.SDK_INT >= VERSION_CODES.N) { - val languageTag = subtype.languageTag - if (languageTag.isNotEmpty()) - return Locale.forLanguageTag(languageTag) - } - return LocaleUtils.constructLocaleFromString(subtype.locale()) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/KeyboardLayoutSet.java b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/KeyboardLayoutSet.java index ba8f06cca..d19dad665 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/KeyboardLayoutSet.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/KeyboardLayoutSet.java @@ -98,7 +98,7 @@ public final class KeyboardLayoutSet { boolean mIsSpellChecker; int mKeyboardWidth; int mKeyboardHeight; - int mScriptId = ScriptUtils.SCRIPT_LATIN; + String mScript = ScriptUtils.SCRIPT_LATIN; // Indicates if the user has enabled the split-layout preference // and the required ProductionFlags are enabled. boolean mIsSplitLayoutEnabled; @@ -202,8 +202,8 @@ public final class KeyboardLayoutSet { return keyboard; } - public int getScriptId() { - return mParams.mScriptId; + public String getScript() { + return mParams.mScript; } public static final class Builder { @@ -298,7 +298,7 @@ public final class KeyboardLayoutSet { public KeyboardLayoutSet build() { if (mParams.mSubtype == null) throw new RuntimeException("KeyboardLayoutSet subtype is not specified"); - mParams.mScriptId = ScriptUtils.getScriptFromSpellCheckerLocale(mParams.mSubtype.getLocale()); + mParams.mScript = ScriptUtils.script(mParams.mSubtype.getLocale()); // todo: the whole parsing stuff below should be removed, but currently // it simply breaks when it's not available // for emojis, moreKeys and moreSuggestions there are relevant parameters included diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/KeyboardSwitcher.java b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/KeyboardSwitcher.java index c3d419fd7..39d0b3c9c 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/KeyboardSwitcher.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/KeyboardSwitcher.java @@ -594,11 +594,11 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } } - public int getCurrentKeyboardScriptId() { + public String getCurrentKeyboardScript() { if (null == mKeyboardLayoutSet) { return ScriptUtils.SCRIPT_UNKNOWN; } - return mKeyboardLayoutSet.getScriptId(); + return mKeyboardLayoutSet.getScript(); } public void switchToSubtype(InputMethodSubtype subtype) { diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/MainKeyboardView.java b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/MainKeyboardView.java index e8c9ec898..8232dc6bc 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/MainKeyboardView.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/MainKeyboardView.java @@ -18,7 +18,6 @@ import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Typeface; import android.util.AttributeSet; -import org.dslul.openboard.inputmethod.latin.utils.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -31,6 +30,7 @@ import androidx.appcompat.view.ContextThemeWrapper; import org.dslul.openboard.inputmethod.accessibility.AccessibilityUtils; import org.dslul.openboard.inputmethod.accessibility.MainKeyboardAccessibilityDelegate; import org.dslul.openboard.inputmethod.annotations.ExternallyReferenced; +import org.dslul.openboard.inputmethod.compat.ConfigurationCompatKt; import org.dslul.openboard.inputmethod.keyboard.internal.DrawingPreviewPlacerView; import org.dslul.openboard.inputmethod.keyboard.internal.DrawingProxy; import org.dslul.openboard.inputmethod.keyboard.internal.GestureFloatingTextDrawingPreview; @@ -55,6 +55,7 @@ import org.dslul.openboard.inputmethod.latin.settings.DebugSettings; import org.dslul.openboard.inputmethod.latin.settings.Settings; import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils; import org.dslul.openboard.inputmethod.latin.utils.LanguageOnSpacebarUtils; +import org.dslul.openboard.inputmethod.latin.utils.Log; import org.dslul.openboard.inputmethod.latin.utils.TypefaceUtils; import java.util.ArrayList; @@ -800,7 +801,7 @@ public final class MainKeyboardView extends KeyboardView implements DrawingProxy final List secondaryLocalesToUse = withoutDuplicateLanguages(secondaryLocales, subtype.getLocale().getLanguage()); if (secondaryLocalesToUse.size() > 0) { StringBuilder sb = new StringBuilder(subtype.getMiddleDisplayName()); - final Locale displayLocale = getResources().getConfiguration().locale; + final Locale displayLocale = ConfigurationCompatKt.locale(getResources().getConfiguration()); for (Locale locale : secondaryLocales) { sb.append(" - "); sb.append(locale.getDisplayLanguage(displayLocale)); diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/keyboard_parser/LocaleKeyTexts.kt b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/keyboard_parser/LocaleKeyTexts.kt index fe7e28f6b..c3dcf7104 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/keyboard_parser/LocaleKeyTexts.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/keyboard_parser/LocaleKeyTexts.kt @@ -10,6 +10,7 @@ import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.floris. import org.dslul.openboard.inputmethod.latin.common.splitOnFirstSpacesOnly import org.dslul.openboard.inputmethod.latin.common.splitOnWhitespace import org.dslul.openboard.inputmethod.latin.settings.Settings +import org.dslul.openboard.inputmethod.latin.utils.SubtypeLocaleUtils import java.io.InputStream import java.util.Locale import kotlin.math.round @@ -244,11 +245,11 @@ private fun createLocaleKeyTexts(context: Context, params: KeyboardParams, moreK private fun getStreamForLocale(locale: Locale, context: Context) = try { - if (locale.toString() == "zz") context.assets.open("$LANGUAGE_TEXTS_FOLDER/more_more_keys.txt") - else context.assets.open("$LANGUAGE_TEXTS_FOLDER/${locale.toString().lowercase()}.txt") + if (locale.toLanguageTag() == SubtypeLocaleUtils.NO_LANGUAGE) context.assets.open("$LANGUAGE_TEXTS_FOLDER/more_more_keys.txt") + else context.assets.open("$LANGUAGE_TEXTS_FOLDER/${locale.toLanguageTag()}.txt") } catch (_: Exception) { try { - context.assets.open("$LANGUAGE_TEXTS_FOLDER/${locale.language.lowercase()}.txt") + context.assets.open("$LANGUAGE_TEXTS_FOLDER/${locale.language}.txt") } catch (_: Exception) { null } diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/BinaryDictionaryGetter.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/BinaryDictionaryGetter.java deleted file mode 100644 index 28372f84c..000000000 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/BinaryDictionaryGetter.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * modified - * SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only - */ - -package org.dslul.openboard.inputmethod.latin; - -import static org.dslul.openboard.inputmethod.latin.settings.LanguageSettingsFragmentKt.USER_DICTIONARY_SUFFIX; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.res.AssetFileDescriptor; -import org.dslul.openboard.inputmethod.latin.utils.Log; - -import org.dslul.openboard.inputmethod.latin.common.FileUtils; -import org.dslul.openboard.inputmethod.latin.common.LocaleUtils; -import org.dslul.openboard.inputmethod.latin.define.DecoderSpecificConstants; -import org.dslul.openboard.inputmethod.latin.utils.DictionaryInfoUtils; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Locale; - -/** - * Helper class to get the address of a mmap'able dictionary file. - */ -final public class BinaryDictionaryGetter { - - /** - * Used for Log actions from this class - */ - private static final String TAG = BinaryDictionaryGetter.class.getSimpleName(); - - /** - * Used to return empty lists - */ - private static final File[] EMPTY_FILE_ARRAY = new File[0]; - - /** - * Name of the common preferences name to know which word list are on and which are off. - */ - private static final String COMMON_PREFERENCES_NAME = "LatinImeDictPrefs"; - - private static final boolean SHOULD_USE_DICT_VERSION = - DecoderSpecificConstants.SHOULD_USE_DICT_VERSION; - - // Name of the category for the main dictionary - public static final String MAIN_DICTIONARY_CATEGORY = "main"; - public static final String ID_CATEGORY_SEPARATOR = ":"; - - public static final String ASSETS_DICTIONARY_FOLDER = "dicts"; - - // The key considered to read the version attribute in a dictionary file. - private static final String VERSION_KEY = "version"; - - // Prevents this from being instantiated - private BinaryDictionaryGetter() {} - - /** - * Generates a unique temporary file name in the app cache directory. - */ - public static String getTempFileName(final String id, final Context context) - throws IOException { - final String safeId = DictionaryInfoUtils.replaceFileNameDangerousCharacters(id); - final File directory = new File(DictionaryInfoUtils.getWordListTempDirectory(context)); - if (!directory.exists()) { - if (!directory.mkdirs()) { - Log.e(TAG, "Could not create the temporary directory"); - } - } - // If the first argument is less than three chars, createTempFile throws a - // RuntimeException. We don't really care about what name we get, so just - // put a three-chars prefix makes us safe. - return File.createTempFile("xxx" + safeId, null, directory).getAbsolutePath(); - } - - /** - * Returns a file address from a resource, or null if it cannot be opened. - */ - public static AssetFileAddress loadFallbackResource(final Context context, - final int fallbackResId) { - AssetFileDescriptor afd = null; - try { - afd = context.getResources().openRawResourceFd(fallbackResId); - } catch (RuntimeException e) { - Log.e(TAG, "Resource not found: " + fallbackResId); - return null; - } - if (afd == null) { - Log.e(TAG, "Resource cannot be opened: " + fallbackResId); - return null; - } - try { - return AssetFileAddress.makeFromFileNameAndOffset( - context.getApplicationInfo().sourceDir, afd.getStartOffset(), afd.getLength()); - } finally { - try { - afd.close(); - } catch (IOException ignored) { - } - } - } - - private static final class DictPackSettings { - final SharedPreferences mDictPreferences; - public DictPackSettings(final Context context) { - mDictPreferences = null == context ? null - : context.getSharedPreferences(COMMON_PREFERENCES_NAME, - Context.MODE_MULTI_PROCESS); - } - public boolean isWordListActive(final String dictId) { - if (null == mDictPreferences) { - // If we don't have preferences it basically means we can't find the dictionary - // pack - either it's not installed, or it's disabled, or there is some strange - // bug. Either way, a word list with no settings should be on by default: default - // dictionaries in LatinIME are on if there is no settings at all, and if for some - // reason some dictionaries have been installed BUT the dictionary pack can't be - // found anymore it's safer to actually supply installed dictionaries. - return true; - } - // The default is true here for the same reasons as above. We got the dictionary - // pack but if we don't have any settings for it it means the user has never been - // to the settings yet. So by default, the main dictionaries should be on. - return mDictPreferences.getBoolean(dictId, true); - } - } - - /** - * Utility class for the {@link #getCachedWordLists} method - */ - private static final class FileAndMatchLevel { - final File mFile; - final int mMatchLevel; - public FileAndMatchLevel(final File file, final int matchLevel) { - mFile = file; - mMatchLevel = matchLevel; - } - } - - /** - * Returns the list of cached files for a specific locale, one for each category. - * - * This will return exactly one file for each word list category that matches - * the passed locale. If several files match the locale for any given category, - * this returns the file with the closest match to the locale. For example, if - * the passed word list is en_US, and for a category we have an en and an en_US - * word list available, we'll return only the en_US one. - * Thus, the list will contain as many files as there are categories. - * - * @param locale the locale to find the dictionary files for, as a string. - * @param context the context on which to open the files upon. - * @return an array of binary dictionary files, which may be empty but may not be null. - */ - public static File[] getCachedWordLists(final String locale, final Context context, final boolean weakMatchAcceptable) { - final File[] directoryList = DictionaryInfoUtils.getCachedDirectoryList(context); - if (null == directoryList) return EMPTY_FILE_ARRAY; - Arrays.sort(directoryList); - final HashMap cacheFiles = new HashMap<>(); - for (File directory : directoryList) { - if (!directory.isDirectory()) continue; - final String dirLocale = - DictionaryInfoUtils.getWordListIdFromFileName(directory.getName()).toLowerCase(Locale.ENGLISH); - final int matchLevel = LocaleUtils.getMatchLevel(dirLocale, locale.toLowerCase(Locale.ENGLISH)); - if (weakMatchAcceptable ? LocaleUtils.isMatchWeak(matchLevel) : LocaleUtils.isMatch(matchLevel)) { - final File[] wordLists = directory.listFiles(); - if (null != wordLists) { - for (File wordList : wordLists) { - final String category = - DictionaryInfoUtils.getCategoryFromFileName(wordList.getName()); - final FileAndMatchLevel currentBestMatch = cacheFiles.get(category); - if (null == currentBestMatch || currentBestMatch.mMatchLevel <= matchLevel) { - // todo: not nice, related to todo in getDictionaryFiles - // this is so user-added main dict has priority over internal main dict - // actually any user-added dict has priority, but there aren't any other built-in types - if (wordList.getName().endsWith(USER_DICTIONARY_SUFFIX) || currentBestMatch == null) - cacheFiles.put(category, new FileAndMatchLevel(wordList, matchLevel)); - } - } - } - } - } - if (cacheFiles.isEmpty()) return EMPTY_FILE_ARRAY; - final File[] result = new File[cacheFiles.size()]; - int index = 0; - for (final FileAndMatchLevel entry : cacheFiles.values()) { - result[index++] = entry.mFile; - } - return result; - } - - /** - * Returns a list of file addresses for a given locale, trying relevant methods in order. - * - * Tries to get binary dictionaries from various sources, in order: - * - Uses a content provider to get a public dictionary set, as per the protocol described - * in BinaryDictionaryFileDumper. - * If that fails: - * - Gets a file name from the built-in dictionary for this locale, if any. - * If that fails: - * - Returns null. - * @return The list of addresses of valid dictionary files, or null. - */ - // todo: the way of using assets and cached lists should be improved, so that the assets file - // doesn't need to be in cached dir just for checking whether it's a good match - public static ArrayList getDictionaryFiles(final Locale locale, - final Context context, final boolean weakMatchAcceptable) { - loadDictionaryFromAssets(locale.toString(), context, weakMatchAcceptable); // will copy dict to cached word lists if not existing - final File[] cachedWordLists = getCachedWordLists(locale.toString(), context, weakMatchAcceptable); - final String mainDictId = DictionaryInfoUtils.getMainDictId(locale); - final DictPackSettings dictPackSettings = new DictPackSettings(context); - - boolean foundMainDict = false; - final ArrayList fileList = new ArrayList<>(); - // cachedWordLists may not be null, see doc for getCachedDictionaryList - for (final File f : cachedWordLists) { - final String wordListId = DictionaryInfoUtils.getWordListIdFromFileName(f.getName()); - final boolean canUse = f.canRead(); - if (canUse && DictionaryInfoUtils.isMainWordListId(wordListId)) { - foundMainDict = true; - } - if (!dictPackSettings.isWordListActive(wordListId)) continue; - if (canUse) { - final AssetFileAddress afa = AssetFileAddress.makeFromFileName(f.getPath()); - if (null != afa) fileList.add(afa); - } else { - Log.e(TAG, "Found a cached dictionary file for " + locale + " but cannot read or use it"); - } - } - - if (!foundMainDict && dictPackSettings.isWordListActive(mainDictId)) { - final File dict = loadDictionaryFromAssets(locale.toString(), context, weakMatchAcceptable); - if (dict != null) { - final AssetFileAddress fallbackAsset = AssetFileAddress.makeFromFileName(dict.getPath()); - if (fallbackAsset != null) - fileList.add(fallbackAsset); - } - } - - return fileList; - } - - /** - * Returns the best matching main dictionary from assets. - * - * Actually copies the dictionary to cache folder, and then returns that file. This allows - * the dictionaries to be stored in a compressed way, reducing APK size. - * On next load, the dictionary in cache folder is found by getCachedWordLists - * - * Returns null on IO errors or if no matching dictionary is found - */ - public static File loadDictionaryFromAssets(final String locale, final Context context, final boolean weakMatchAcceptable) { - final String[] dictionaryList = getAssetsDictionaryList(context); - if (null == dictionaryList) return null; - String bestMatchName = null; - int bestMatchLevel = 0; - for (String dictionary : dictionaryList) { - final String dictLocale = - extractLocaleFromAssetsDictionaryFile(dictionary); - if (dictLocale == null) continue; - // assets files may contain the locale in lowercase, but dictionary headers usually - // have an upper case country code, so we compare lowercase here - final int matchLevel = LocaleUtils.getMatchLevel(dictLocale.toLowerCase(Locale.ENGLISH), locale.toLowerCase(Locale.ENGLISH)); - if ((weakMatchAcceptable ? LocaleUtils.isMatchWeak(matchLevel) : LocaleUtils.isMatch(matchLevel)) && matchLevel > bestMatchLevel) { - bestMatchName = dictionary; - bestMatchLevel = matchLevel; - } - } - if (bestMatchName == null) return null; - - // we have a match, now copy contents of the dictionary to cached word lists folder - final String bestMatchLocale = extractLocaleFromAssetsDictionaryFile(bestMatchName); - if (bestMatchLocale == null) return null; - File dictFile = DictionaryInfoUtils.getMainDictFile(locale, context); - if (dictFile.exists()) - return dictFile; - try { - FileUtils.copyStreamToNewFile( - context.getAssets().open(ASSETS_DICTIONARY_FOLDER + File.separator + bestMatchName), - dictFile); - return dictFile; - } catch (IOException e) { - Log.e(TAG, "exception while looking for locale " + locale, e); - return null; - } - } - - /** - * Returns the locale for a dictionary file name stored in assets. - * - * Assumes file name main_[locale].dict - * - * Returns the locale, or null if file name does not match the pattern - */ - public static String extractLocaleFromAssetsDictionaryFile(final String dictionaryFileName) { - if (dictionaryFileName.startsWith(DictionaryInfoUtils.MAIN_DICT_PREFIX) - && dictionaryFileName.endsWith(".dict")) { - return dictionaryFileName.substring( - DictionaryInfoUtils.MAIN_DICT_PREFIX.length(), - dictionaryFileName.lastIndexOf('.') - ); - } - return null; - } - - public static String[] getAssetsDictionaryList(final Context context) { - final String[] dictionaryList; - try { - dictionaryList = context.getAssets().list(ASSETS_DICTIONARY_FOLDER); - } catch (IOException e) { - return null; - } - return dictionaryList; - } -} diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/DictionaryFacilitatorImpl.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/DictionaryFacilitatorImpl.java index 8f208d189..a87916f26 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/DictionaryFacilitatorImpl.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/DictionaryFacilitatorImpl.java @@ -424,7 +424,7 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator { // load blacklist if (noExistingDictsForThisLocale) { - newDictGroup.blacklistFileName = context.getFilesDir().getAbsolutePath() + File.separator + "blacklists" + File.separator + locale.toString().toLowerCase(Locale.ENGLISH) + ".txt"; + newDictGroup.blacklistFileName = context.getFilesDir().getAbsolutePath() + File.separator + "blacklists" + File.separator + locale.toLanguageTag() + ".txt"; if (!new File(newDictGroup.blacklistFileName).exists()) new File(context.getFilesDir().getAbsolutePath() + File.separator + "blacklists").mkdirs(); newDictGroup.blacklist.addAll(readBlacklistFile(newDictGroup.blacklistFileName)); @@ -494,7 +494,7 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator { mainDicts[i] = null; continue; } - mainDicts[i] = DictionaryFactory.createMainDictionaryFromManager(context, dictionaryGroup.mLocale); + mainDicts[i] = DictionaryFactoryKt.createMainDictionary(context, dictionaryGroup.mLocale); } synchronized (mLock) { diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/DictionaryFactory.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/DictionaryFactory.java deleted file mode 100644 index d967a79bf..000000000 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/DictionaryFactory.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * modified - * SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only - */ - -package org.dslul.openboard.inputmethod.latin; - -import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.widget.Toast; - -import androidx.annotation.NonNull; - -import org.dslul.openboard.inputmethod.latin.makedict.DictionaryHeader; -import org.dslul.openboard.inputmethod.latin.utils.DictionaryInfoUtils; - -import java.io.File; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.Locale; - -/** - * Factory for dictionary instances. - */ -public final class DictionaryFactory { - - /** - * Initializes a main dictionary collection from a dictionary pack, with explicit flags. - *

- * This searches for a content provider providing a dictionary pack for the specified - * locale. If none is found, it falls back to the built-in dictionary - if any. - * @param context application context for reading resources - * @param locale the locale for which to create the dictionary - * @return an initialized instance of DictionaryCollection - */ - public static DictionaryCollection createMainDictionaryFromManager(final Context context, @NonNull final Locale locale) { - final LinkedList dictList = new LinkedList<>(); - ArrayList assetFileList = - BinaryDictionaryGetter.getDictionaryFiles(locale, context, false); - - boolean mainFound = false; - for (AssetFileAddress fileAddress : assetFileList) { - if (fileAddress.mFilename.contains("main")) { - mainFound = true; - break; - } - } - if (!mainFound) // try again and allow weaker match - assetFileList = BinaryDictionaryGetter.getDictionaryFiles(locale, context, true); - - for (final AssetFileAddress f : assetFileList) { - final DictionaryHeader header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(new File(f.mFilename), f.mOffset, f.mLength); - String dictType = Dictionary.TYPE_MAIN; - if (header != null) { - // make sure the suggested words dictionary has the correct type - dictType = header.mIdString.split(":")[0]; - } - final ReadOnlyBinaryDictionary readOnlyBinaryDictionary = - new ReadOnlyBinaryDictionary(f.mFilename, f.mOffset, f.mLength, - false /* useFullEditDistance */, locale, dictType); - if (readOnlyBinaryDictionary.isValidDictionary()) { - if(locale.getLanguage().equals("ko")) { - // Use KoreanDictionary for Korean locale - dictList.add(new KoreanDictionary(readOnlyBinaryDictionary)); - } else { - dictList.add(readOnlyBinaryDictionary); - } - } else { - readOnlyBinaryDictionary.close(); - // Prevent this dictionary to do any further harm. - killDictionary(context, f); - } - } - - // If the list is empty, that means we should not use any dictionary (for example, the user - // explicitly disabled the main dictionary), so the following is okay. dictList is never - // null, but if for some reason it is, DictionaryCollection handles it gracefully. - return new DictionaryCollection(Dictionary.TYPE_MAIN, locale, dictList); - } - - /** - * Kills a dictionary so that it is never used again, if possible. - * @param context The context to contact the dictionary provider, if possible. - * @param f A file address to the dictionary to kill. - */ - public static void killDictionary(final Context context, final AssetFileAddress f) { - if (f.pointsToPhysicalFile()) { - f.deleteUnderlyingFile(); - // notify the user if possible (toast not showing up on Android 13+ when not in settings) - // but not that important, as the not working dictionary should be obvious - final String wordlistId = DictionaryInfoUtils.getWordListIdFromFileName(new File(f.mFilename).getName()); - new Handler(Looper.getMainLooper()).post(() -> - Toast.makeText(context, "dictionary "+wordlistId+" is invalid, deleting", Toast.LENGTH_LONG).show() - ); - } - } -} diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/DictionaryFactory.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/DictionaryFactory.kt new file mode 100644 index 000000000..c772bcf3e --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/DictionaryFactory.kt @@ -0,0 +1,94 @@ +/* +* Copyright (C) 2011 The Android Open Source Project +* modified +* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only +*/ +package org.dslul.openboard.inputmethod.latin + +import android.content.Context +import org.dslul.openboard.inputmethod.latin.common.FileUtils +import org.dslul.openboard.inputmethod.latin.common.LocaleUtils +import org.dslul.openboard.inputmethod.latin.common.LocaleUtils.constructLocale +import org.dslul.openboard.inputmethod.latin.settings.USER_DICTIONARY_SUFFIX +import org.dslul.openboard.inputmethod.latin.utils.DictionaryInfoUtils +import org.dslul.openboard.inputmethod.latin.utils.Log +import java.io.File +import java.util.LinkedList +import java.util.Locale + +/** + * Initializes a main dictionary collection from a dictionary pack, with explicit flags. + * + * + * This searches for a content provider providing a dictionary pack for the specified + * locale. If none is found, it falls back to the built-in dictionary - if any. + * @param context application context for reading resources + * @param locale the locale for which to create the dictionary + * @return an initialized instance of DictionaryCollection + */ +fun createMainDictionary(context: Context, locale: Locale): DictionaryCollection { + val cacheDir = DictionaryInfoUtils.getCacheDirectoryForLocale(locale, context) + val dictList = LinkedList() + // get cached dict files + val (userDicts, extractedDicts) = DictionaryInfoUtils.getCachedDictsForLocale(locale, context) + .partition { it.name.endsWith(USER_DICTIONARY_SUFFIX) } + // add user dicts to list + userDicts.forEach { checkAndAddDictionaryToListIfNotExisting(it, dictList, locale) } + // add extracted dicts to list (after userDicts, to skip extracted dicts of same type) + extractedDicts.forEach { checkAndAddDictionaryToListIfNotExisting(it, dictList, locale) } + if (dictList.any { it.mDictType == Dictionary.TYPE_MAIN }) + return DictionaryCollection(Dictionary.TYPE_MAIN, locale, dictList) + + // no main dict found -> check assets + val assetsDicts = DictionaryInfoUtils.getAssetsDictionaryList(context) + // file name is _.dict + val dictsByType = assetsDicts?.groupBy { it.substringBefore("_") } + // for each type find the best match + dictsByType?.forEach { (dictType, dicts) -> + val bestMatch = LocaleUtils.getBestMatch(locale, dicts) { it.substringAfter("_") + .substringBefore(".").constructLocale() } ?: return@forEach + // extract dict and add extracted file + val targetFile = File(cacheDir, "$dictType.dict") + FileUtils.copyStreamToNewFile( + context.assets.open(DictionaryInfoUtils.ASSETS_DICTIONARY_FOLDER + File.separator + bestMatch), + targetFile + ) + checkAndAddDictionaryToListIfNotExisting(targetFile, dictList, locale) + } + // If the list is empty, that means we should not use any dictionary (for example, the user + // explicitly disabled the main dictionary), so the following is okay. dictList is never + // null, but if for some reason it is, DictionaryCollection handles it gracefully. + return DictionaryCollection(Dictionary.TYPE_MAIN, locale, dictList) +} + +/** + * add dictionary created from [file] to [dicts] + * if [file] cannot be loaded it is deleted + * if the dictionary type already exists in [dicts], the [file] is skipped + */ +private fun checkAndAddDictionaryToListIfNotExisting(file: File, dicts: MutableList, locale: Locale) { + if (!file.isFile) return + val header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(file) ?: return killDictionary(file) + val dictType = header.mIdString.split(":").first() + if (dicts.any { it.mDictType == dictType }) return + val readOnlyBinaryDictionary = ReadOnlyBinaryDictionary( + file.absolutePath, 0, file.length(), false, locale, dictType + ) + + if (readOnlyBinaryDictionary.isValidDictionary) { + if (locale.language == "ko") { + // Use KoreanDictionary for Korean locale + dicts.add(KoreanDictionary(readOnlyBinaryDictionary)) + } else { + dicts.add(readOnlyBinaryDictionary) + } + } else { + readOnlyBinaryDictionary.close() + killDictionary(file) + } +} + +private fun killDictionary(file: File) { + Log.e("DictionaryFactory", "could not load dictionary ${file.parentFile?.name}/${file.name}, deleting") + file.delete() +} diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/ExpandableBinaryDictionary.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/ExpandableBinaryDictionary.java index 3e4507725..d46cc7141 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/ExpandableBinaryDictionary.java @@ -14,7 +14,6 @@ import androidx.annotation.Nullable; import com.android.inputmethod.latin.BinaryDictionary; -import org.dslul.openboard.inputmethod.annotations.UsedForTesting; import org.dslul.openboard.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import org.dslul.openboard.inputmethod.latin.common.ComposedData; import org.dslul.openboard.inputmethod.latin.common.FileUtils; @@ -27,14 +26,12 @@ import org.dslul.openboard.inputmethod.latin.settings.SettingsValuesForSuggestio import org.dslul.openboard.inputmethod.latin.utils.AsyncResultHolder; import org.dslul.openboard.inputmethod.latin.utils.CombinedFormatUtils; import org.dslul.openboard.inputmethod.latin.utils.ExecutorUtils; -import com.android.inputmethod.latin.utils.WordInputEventForPersonalization; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.Locale; import java.util.Map; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; @@ -44,7 +41,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; * Abstract base class for an expandable dictionary that can be created and updated dynamically * during runtime. When updated it automatically generates a new binary dictionary to handle future * queries in native code. This binary dictionary is written to internal storage. - * + *

* A class that extends this abstract class must have a static factory method named * getDictionary(Context context, Locale locale, File dictFile, String dictNamePrefix) */ @@ -96,8 +93,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { private final ReentrantReadWriteLock mLock; - private Map mAdditionalAttributeMap = null; - /* A extension for a binary dictionary file. */ protected static final String DICT_FILE_EXTENSION = ".dict"; @@ -151,7 +146,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { public static String getDictName(final String name, final Locale locale, final File dictFile) { - return dictFile != null ? dictFile.getName() : name + "." + locale.toString(); + return dictFile != null ? dictFile.getName() : name + "." + locale.toLanguageTag(); } private void asyncExecuteTaskWithWriteLock(final Runnable task) { @@ -191,9 +186,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { protected Map getHeaderAttributeMap() { HashMap attributeMap = new HashMap<>(); - if (mAdditionalAttributeMap != null) { - attributeMap.putAll(mAdditionalAttributeMap); - } attributeMap.put(DictionaryHeader.DICTIONARY_ID_KEY, mDictName); attributeMap.put(DictionaryHeader.DICTIONARY_LOCALE_KEY, mLocale.toString()); attributeMap.put(DictionaryHeader.DICTIONARY_VERSION_KEY, @@ -343,41 +335,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { }); } - /** - * Used by Sketch. - * {@see https://cs.corp.google.com/#android/vendor/unbundled_google/packages/LatinIMEGoogle/tools/sketch/ime-simulator/src/com/android/inputmethod/sketch/imesimulator/ImeSimulator.java&q=updateEntriesForInputEventsCallback&l=286} - */ - @UsedForTesting - public interface UpdateEntriesForInputEventsCallback { - void onFinished(); - } - - /** - * Dynamically update entries according to input events. - * - * Used by Sketch. - * {@see https://cs.corp.google.com/#android/vendor/unbundled_google/packages/LatinIMEGoogle/tools/sketch/ime-simulator/src/com/android/inputmethod/sketch/imesimulator/ImeSimulator.java&q=updateEntriesForInputEventsCallback&l=286} - */ - @UsedForTesting - public void updateEntriesForInputEvents( - @NonNull final ArrayList inputEvents, - final UpdateEntriesForInputEventsCallback callback) { - reloadDictionaryIfRequired(); - asyncExecuteTaskWithWriteLock(() -> { - try { - final BinaryDictionary binaryDictionary = getBinaryDictionary(); - if (binaryDictionary == null) { - return; - } - binaryDictionary.updateEntriesForInputEvents(inputEvents.toArray(new WordInputEventForPersonalization[0])); - } finally { - if (callback != null) { - callback.onFinished(); - } - } - }); - } - @Override public ArrayList getSuggestions(final ComposedData composedData, final NgramContext ngramContext, final long proximityInfoHandle, @@ -608,24 +565,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { return result.get(null /* defaultValue */, TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS); } - @UsedForTesting - public void waitAllTasksForTests() { - final CountDownLatch countDownLatch = new CountDownLatch(1); - asyncExecuteTaskWithWriteLock(countDownLatch::countDown); - try { - countDownLatch.await(); - } catch (InterruptedException e) { - Log.e(TAG, "Interrupted while waiting for finishing dictionary operations.", e); - } - } - - @UsedForTesting - public void clearAndFlushDictionaryWithAdditionalAttributes( - final Map attributeMap) { - mAdditionalAttributeMap = attributeMap; - clear(); - } - public void dumpAllWordsForDebug() { reloadDictionaryIfRequired(); final String tag = TAG; diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/LatinIME.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/LatinIME.java index 9a27a8f88..92e5d95ed 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/LatinIME.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/LatinIME.java @@ -41,6 +41,7 @@ import android.view.inputmethod.InputMethodSubtype; import org.dslul.openboard.inputmethod.accessibility.AccessibilityUtils; import org.dslul.openboard.inputmethod.annotations.UsedForTesting; +import org.dslul.openboard.inputmethod.compat.ConfigurationCompatKt; import org.dslul.openboard.inputmethod.compat.EditorInfoCompatUtils; import org.dslul.openboard.inputmethod.compat.InsetsOutlineProvider; import org.dslul.openboard.inputmethod.compat.ViewOutlineProviderCompatUtils; @@ -281,7 +282,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen case MSG_RESUME_SUGGESTIONS: latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor( latinIme.mSettings.getCurrent(), - latinIme.mKeyboardSwitcher.getCurrentKeyboardScriptId()); + latinIme.mKeyboardSwitcher.getCurrentKeyboardScript()); break; case MSG_REOPEN_DICTIONARIES: // We need to re-evaluate the currently composing word in case the script has @@ -698,7 +699,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // case, we are about to go down but we still don't know it, however the system tells // us there is no current subtype. Log.e(TAG, "System is reporting no current subtype."); - subtypeLocale = getResources().getConfiguration().locale; + subtypeLocale = ConfigurationCompatKt.locale(getResources().getConfiguration()); } else { subtypeLocale = subtypeSwitcherLocale; } @@ -1467,7 +1468,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mInputLogic.finishInput(); int newPosition = mInputLogic.mConnection.mExpectedSelStart + moveSteps; mInputLogic.mConnection.setSelection(newPosition, newPosition); - mInputLogic.restartSuggestionsOnWordTouchedByCursor(mSettings.getCurrent(), mKeyboardSwitcher.getCurrentKeyboardScriptId()); + mInputLogic.restartSuggestionsOnWordTouchedByCursor(mSettings.getCurrent(), mKeyboardSwitcher.getCurrentKeyboardScript()); } @Override @@ -1606,7 +1607,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final InputTransaction completeInputTransaction = mInputLogic.onCodeInput(mSettings.getCurrent(), event, mKeyboardSwitcher.getKeyboardShiftMode(), - mKeyboardSwitcher.getCurrentKeyboardScriptId(), mHandler); + mKeyboardSwitcher.getCurrentKeyboardScript(), mHandler); updateStateAfterInputTransaction(completeInputTransaction); mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState()); } @@ -1763,7 +1764,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final InputTransaction completeInputTransaction = mInputLogic.onPickSuggestionManually( mSettings.getCurrent(), suggestionInfo, mKeyboardSwitcher.getKeyboardShiftMode(), - mKeyboardSwitcher.getCurrentKeyboardScriptId(), + mKeyboardSwitcher.getCurrentKeyboardScript(), mHandler); updateStateAfterInputTransaction(completeInputTransaction); } @@ -1915,7 +1916,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mInputLogic.onCodeInput(mSettings.getCurrent(), event, mKeyboardSwitcher.getKeyboardShiftMode(), // TODO: this is not necessarily correct for a hardware keyboard right now - mKeyboardSwitcher.getCurrentKeyboardScriptId(), + mKeyboardSwitcher.getCurrentKeyboardScript(), mHandler); return true; } diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/RichInputConnection.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/RichInputConnection.java index 0427cba42..0181f0060 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/RichInputConnection.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/RichInputConnection.java @@ -645,10 +645,10 @@ public final class RichInputConnection implements PrivateCommandPerformer { mIC.performContextMenuAction(android.R.id.selectAll); } - public void selectWord(final SpacingAndPunctuations spacingAndPunctuations, final int scriptId) { + public void selectWord(final SpacingAndPunctuations spacingAndPunctuations, final String script) { if (!isConnected()) return; if (mExpectedSelStart != mExpectedSelEnd) return; // already something selected - final TextRange range = getWordRangeAtCursor(spacingAndPunctuations, scriptId, false); + final TextRange range = getWordRangeAtCursor(spacingAndPunctuations, script, false); if (range == null) return; mIC.setSelection(mExpectedSelStart - range.getNumberOfCharsInWordBeforeCursor(), mExpectedSelStart + range.getNumberOfCharsInWordAfterCursor()); } @@ -726,23 +726,23 @@ public final class RichInputConnection implements PrivateCommandPerformer { } private static boolean isPartOfCompositionForScript(final int codePoint, - final SpacingAndPunctuations spacingAndPunctuations, final int scriptId) { + final SpacingAndPunctuations spacingAndPunctuations, final String script) { // We always consider word connectors part of compositions. return spacingAndPunctuations.isWordConnector(codePoint) // Otherwise, it's part of composition if it's part of script and not a separator. || (!spacingAndPunctuations.isWordSeparator(codePoint) - && ScriptUtils.isLetterPartOfScript(codePoint, scriptId)); + && ScriptUtils.isLetterPartOfScript(codePoint, script)); } /** * Returns the text surrounding the cursor. * * @param spacingAndPunctuations the rules for spacing and punctuation - * @param scriptId the script we consider to be writing words, as one of ScriptUtils.SCRIPT_* + * @param script the script we consider to be writing words, as one of ScriptUtils.SCRIPT_* * @return a range containing the text surrounding the cursor */ public TextRange getWordRangeAtCursor(final SpacingAndPunctuations spacingAndPunctuations, - final int scriptId, final boolean justDeleted) { + final String script, final boolean justDeleted) { mIC = mParent.getCurrentInputConnection(); if (!isConnected()) { return null; @@ -764,7 +764,7 @@ public final class RichInputConnection implements PrivateCommandPerformer { // we need text before, and text after is either empty or a separator or similar if (justDeleted && before.length() > 0 && (after.length() == 0 - || !isPartOfCompositionForScript(Character.codePointAt(after, 0), spacingAndPunctuations, scriptId) + || !isPartOfCompositionForScript(Character.codePointAt(after, 0), spacingAndPunctuations, script) ) ) { // issue: @@ -786,7 +786,7 @@ public final class RichInputConnection implements PrivateCommandPerformer { int endIndexInAfter = -1; while (startIndexInBefore > 0) { final int codePoint = Character.codePointBefore(before, startIndexInBefore); - if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, scriptId)) { + if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, script)) { if (Character.isWhitespace(codePoint) || !spacingAndPunctuations.mCurrentLanguageHasSpaces) break; // continue to the next whitespace and see whether this contains a sometimesWordConnector @@ -815,7 +815,7 @@ public final class RichInputConnection implements PrivateCommandPerformer { if (endIndexInAfter == -1) { while (++endIndexInAfter < after.length()) { final int codePoint = Character.codePointAt(after, endIndexInAfter); - if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, scriptId)) { + if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, script)) { if (Character.isWhitespace(codePoint) || !spacingAndPunctuations.mCurrentLanguageHasSpaces) break; // continue to the next whitespace and see whether this contains a sometimesWordConnector diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/RichInputMethodManager.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/RichInputMethodManager.java index 050906714..9cdff2614 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/RichInputMethodManager.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/RichInputMethodManager.java @@ -11,19 +11,20 @@ import android.content.SharedPreferences; import android.inputmethodservice.InputMethodService; import android.os.AsyncTask; import android.os.IBinder; -import org.dslul.openboard.inputmethod.latin.utils.Log; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; import org.dslul.openboard.inputmethod.annotations.UsedForTesting; -import org.dslul.openboard.inputmethod.compat.InputMethodSubtypeCompatUtils; +import org.dslul.openboard.inputmethod.compat.ConfigurationCompatKt; import org.dslul.openboard.inputmethod.latin.settings.Settings; import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils; import org.dslul.openboard.inputmethod.latin.utils.LanguageOnSpacebarUtils; +import org.dslul.openboard.inputmethod.latin.utils.Log; import org.dslul.openboard.inputmethod.latin.utils.ScriptUtils; import org.dslul.openboard.inputmethod.latin.utils.SubtypeLocaleUtils; import org.dslul.openboard.inputmethod.latin.utils.SubtypeSettingsKt; +import org.dslul.openboard.inputmethod.latin.utils.SubtypeUtilsKt; import java.util.Collections; import java.util.HashMap; @@ -293,14 +294,14 @@ public class RichInputMethodManager { return keyboardCount > 1; } - public InputMethodSubtype findSubtypeByLocaleAndKeyboardLayoutSet(final String localeString, + public InputMethodSubtype findSubtypeByLocaleAndKeyboardLayoutSet(final Locale locale, final String keyboardLayoutSetName) { final InputMethodInfo myImi = getInputMethodInfoOfThisIme(); final int count = myImi.getSubtypeCount(); for (int i = 0; i < count; i++) { final InputMethodSubtype subtype = myImi.getSubtypeAt(i); final String layoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); - if (localeString.equals(subtype.getLocale()) + if (locale.equals(SubtypeUtilsKt.locale(subtype)) && keyboardLayoutSetName.equals(layoutName)) { return subtype; } @@ -316,7 +317,7 @@ public class RichInputMethodManager { // search for exact match for (int i = 0; i < count; ++i) { final InputMethodSubtype subtype = subtypes.get(i); - final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype); + final Locale subtypeLocale = SubtypeUtilsKt.locale(subtype); if (subtypeLocale.equals(locale)) { return subtype; } @@ -324,7 +325,7 @@ public class RichInputMethodManager { // search for language + country + variant match for (int i = 0; i < count; ++i) { final InputMethodSubtype subtype = subtypes.get(i); - final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype); + final Locale subtypeLocale = SubtypeUtilsKt.locale(subtype); if (subtypeLocale.getLanguage().equals(locale.getLanguage()) && subtypeLocale.getCountry().equals(locale.getCountry()) && subtypeLocale.getVariant().equals(locale.getVariant())) { @@ -334,7 +335,7 @@ public class RichInputMethodManager { // search for language + country match for (int i = 0; i < count; ++i) { final InputMethodSubtype subtype = subtypes.get(i); - final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype); + final Locale subtypeLocale = SubtypeUtilsKt.locale(subtype); if (subtypeLocale.getLanguage().equals(locale.getLanguage()) && subtypeLocale.getCountry().equals(locale.getCountry())) { return subtype; @@ -344,7 +345,7 @@ public class RichInputMethodManager { final SharedPreferences prefs = DeviceProtectedUtils.getSharedPreferences(mContext); for (int i = 0; i < count; ++i) { final InputMethodSubtype subtype = subtypes.get(i); - final String subtypeLocale = subtype.getLocale(); + final Locale subtypeLocale = SubtypeUtilsKt.locale(subtype); final List secondaryLocales = Settings.getSecondaryLocales(prefs, subtypeLocale); for (final Locale secondaryLocale : secondaryLocales) { if (secondaryLocale.equals(locale)) { @@ -355,7 +356,7 @@ public class RichInputMethodManager { // search for language match for (int i = 0; i < count; ++i) { final InputMethodSubtype subtype = subtypes.get(i); - final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype); + final Locale subtypeLocale = SubtypeUtilsKt.locale(subtype); if (subtypeLocale.getLanguage().equals(locale.getLanguage())) { return subtype; } @@ -363,7 +364,7 @@ public class RichInputMethodManager { // search for secondary language match for (int i = 0; i < count; ++i) { final InputMethodSubtype subtype = subtypes.get(i); - final String subtypeLocale = subtype.getLocale(); + final Locale subtypeLocale = SubtypeUtilsKt.locale(subtype); final List secondaryLocales = Settings.getSecondaryLocales(prefs, subtypeLocale); for (final Locale secondaryLocale : secondaryLocales) { if (secondaryLocale.getLanguage().equals(locale.getLanguage())) { @@ -374,12 +375,12 @@ public class RichInputMethodManager { // extra: if current script is not compatible to current subtype, search for compatible script // this is acceptable only because this function is only used for switching to a certain locale using EditorInfo.hintLocales - final int script = ScriptUtils.getScriptFromSpellCheckerLocale(locale); - if (script != ScriptUtils.getScriptFromSpellCheckerLocale(getCurrentSubtypeLocale())) { + final String script = ScriptUtils.script(locale); + if (!script.equals(ScriptUtils.script(getCurrentSubtypeLocale()))) { for (int i = 0; i < count; ++i) { final InputMethodSubtype subtype = subtypes.get(i); - final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype); - if (ScriptUtils.getScriptFromSpellCheckerLocale(subtypeLocale) == script) { + final Locale subtypeLocale = SubtypeUtilsKt.locale(subtype); + if (ScriptUtils.script(subtypeLocale).equals(script)) { return subtype; } } @@ -418,7 +419,7 @@ public class RichInputMethodManager { final RichInputMethodSubtype richSubtype = mCurrentRichInputMethodSubtype; final boolean implicitlyEnabledSubtype = checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled( richSubtype.getRawSubtype()); - final Locale systemLocale = mContext.getResources().getConfiguration().locale; + final Locale systemLocale = ConfigurationCompatKt.locale(mContext.getResources().getConfiguration()); LanguageOnSpacebarUtils.onSubtypeChanged( richSubtype, implicitlyEnabledSubtype, systemLocale); LanguageOnSpacebarUtils.setEnabledSubtypes(getMyEnabledInputMethodSubtypeList( diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/RichInputMethodSubtype.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/RichInputMethodSubtype.java index ece27493d..04f52d09d 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/RichInputMethodSubtype.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/RichInputMethodSubtype.java @@ -6,17 +6,15 @@ package org.dslul.openboard.inputmethod.latin; -import android.os.Build; import android.view.inputmethod.InputMethodSubtype; -import org.dslul.openboard.inputmethod.compat.InputMethodSubtypeCompatUtils; import org.dslul.openboard.inputmethod.latin.common.Constants; import org.dslul.openboard.inputmethod.latin.common.LocaleUtils; import org.dslul.openboard.inputmethod.latin.utils.CustomLayoutUtilsKt; import org.dslul.openboard.inputmethod.latin.utils.Log; import org.dslul.openboard.inputmethod.latin.utils.SubtypeLocaleUtils; +import org.dslul.openboard.inputmethod.latin.utils.SubtypeUtilsKt; -import java.util.HashMap; import java.util.Locale; import static org.dslul.openboard.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE; @@ -33,30 +31,18 @@ import androidx.annotation.Nullable; public class RichInputMethodSubtype { private static final String TAG = RichInputMethodSubtype.class.getSimpleName(); - // todo: remove this map when switching (rich input) subtype to use language tag - private static final HashMap sLocaleMap = initializeLocaleMap(); - private static HashMap initializeLocaleMap() { - final HashMap map = new HashMap<>(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - // Locale#forLanguageTag is available on API Level 21+. - // TODO: Remove this workaround once when we become able to deal with "sr-Latn". - map.put(Locale.forLanguageTag("sr-Latn"), new Locale("sr", "ZZ")); - } - return map; - } - @NonNull private final InputMethodSubtype mSubtype; @NonNull private final Locale mLocale; - @NonNull - private final Locale mOriginalLocale; + // The subtype is considered RTL if the language of the main subtype is RTL. + // Cached because it might get read frequently, e.g. when moving pointer with space bar + private final boolean mIsRtl; public RichInputMethodSubtype(@NonNull final InputMethodSubtype subtype) { mSubtype = subtype; - mOriginalLocale = InputMethodSubtypeCompatUtils.getLocaleObject(mSubtype); - final Locale mappedLocale = sLocaleMap.get(mOriginalLocale); - mLocale = mappedLocale != null ? mappedLocale : mOriginalLocale; + mLocale = SubtypeUtilsKt.locale(mSubtype); + mIsRtl = LocaleUtils.isRtlLanguage(mLocale); } // Extra values are determined by the primary subtype. This is probably right, but @@ -71,7 +57,7 @@ public class RichInputMethodSubtype { } public boolean isNoLanguage() { - return SubtypeLocaleUtils.NO_LANGUAGE.equals(mSubtype.getLocale()); + return SubtypeLocaleUtils.NO_LANGUAGE.equals(mLocale.getLanguage()); } public boolean isCustom() { @@ -105,7 +91,7 @@ public class RichInputMethodSubtype { if (isNoLanguage()) { return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype); } - return SubtypeLocaleUtils.getSubtypeLocaleDisplayName(mSubtype.getLocale()); + return SubtypeLocaleUtils.getSubtypeLocaleDisplayName(mLocale); } // Get the RichInputMethodSubtype's middle display name in its locale. @@ -114,7 +100,7 @@ public class RichInputMethodSubtype { if (isNoLanguage()) { return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype); } - return SubtypeLocaleUtils.getSubtypeLanguageDisplayName(mSubtype.getLocale()); + return SubtypeLocaleUtils.getSubtypeLanguageDisplayName(mLocale); } @Override @@ -141,14 +127,8 @@ public class RichInputMethodSubtype { return mLocale; } - @NonNull - public Locale getOriginalLocale() { - return mOriginalLocale; - } - public boolean isRtlSubtype() { - // The subtype is considered RTL if the language of the main subtype is RTL. - return LocaleUtils.isRtlLanguage(mLocale); + return mIsRtl; } // TODO: remove this method @@ -215,7 +195,7 @@ public class RichInputMethodSubtype { RichInputMethodSubtype noLanguageSubtype = sNoLanguageSubtype; if (noLanguageSubtype == null) { final InputMethodSubtype rawNoLanguageSubtype = RichInputMethodManager.getInstance() - .findSubtypeByLocaleAndKeyboardLayoutSet(SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY); + .findSubtypeByLocaleAndKeyboardLayoutSet(LocaleUtils.constructLocale(SubtypeLocaleUtils.NO_LANGUAGE), SubtypeLocaleUtils.QWERTY); if (rawNoLanguageSubtype != null) { noLanguageSubtype = new RichInputMethodSubtype(rawNoLanguageSubtype); } diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/UserBinaryDictionary.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/UserBinaryDictionary.java index 571123640..4732961ef 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/UserBinaryDictionary.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/UserBinaryDictionary.java @@ -6,7 +6,6 @@ package org.dslul.openboard.inputmethod.latin; -import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; @@ -55,6 +54,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { private static final String NAME = "userunigram"; private ContentObserver mObserver; + // this really needs to be the locale string, as it interacts with system final private String mLocaleString; final private boolean mAlsoUseMoreRestrictiveLocales; @@ -71,7 +71,6 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { mLocaleString = localeStr; } mAlsoUseMoreRestrictiveLocales = alsoUseMoreRestrictiveLocales; - ContentResolver cres = context.getContentResolver(); mObserver = new ContentObserver(null) { @Override @@ -79,7 +78,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { setNeedsToRecreate(); } }; - cres.registerContentObserver(Words.CONTENT_URI, true, mObserver); + context.getContentResolver().registerContentObserver(Words.CONTENT_URI, true, mObserver); reloadDictionaryIfRequired(); } @@ -104,7 +103,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { public void loadInitialContentsLocked() { // Split the locale. For example "en" => ["en"], "de_DE" => ["de", "DE"], // "en_US_foo_bar_qux" => ["en", "US", "foo_bar_qux"] because of the limit of 3. - // This is correct for locale processing. + // This is correct for locale processing. (well, and it sucks e.g. for sr-Latn, resp. sr__#Latn as string) // For this example, we'll look at the "en_US_POSIX" case. final String[] localeElements = TextUtils.isEmpty(mLocaleString) ? new String[] {} : mLocaleString.split("_", 3); @@ -131,8 +130,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { // and request = "(locale is NULL) or (locale=?) or (locale=?) or (locale=?)" final String[] requestArguments; - // If length == 3, we already have all the arguments we need (common prefix is meaningless - // inside variants + // If length == 3, we already have all the arguments we need (common prefix is meaningless inside variants) if (mAlsoUseMoreRestrictiveLocales && length < 3) { request.append(" or (locale like ?)"); // The following creates an array with one more (null) position @@ -151,18 +149,15 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { } final String requestString = request.toString(); try { - addWordsFromProjectionLocked(PROJECTION_QUERY_WITH_SHORTCUT, requestString, - requestArguments); + addWordsFromProjectionLocked(PROJECTION_QUERY_WITH_SHORTCUT, requestString, requestArguments); } catch (IllegalArgumentException e) { // This may happen on some non-compliant devices where the declared API is JB+ but // the SHORTCUT column is not present for some reason. - addWordsFromProjectionLocked(PROJECTION_QUERY_WITHOUT_SHORTCUT, requestString, - requestArguments); + addWordsFromProjectionLocked(PROJECTION_QUERY_WITHOUT_SHORTCUT, requestString, requestArguments); } } - private void addWordsFromProjectionLocked(final String[] query, String request, - final String[] requestArguments) + private void addWordsFromProjectionLocked(final String[] query, String request, final String[] requestArguments) throws IllegalArgumentException { Cursor cursor = null; try { diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/common/LocaleUtils.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/common/LocaleUtils.java deleted file mode 100644 index 7d7b486fd..000000000 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/common/LocaleUtils.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * modified - * SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only - */ - -package org.dslul.openboard.inputmethod.latin.common; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.dslul.openboard.inputmethod.latin.R; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Locale; - -/** - * A class to help with handling Locales in string form. - *

- * This file has the same meaning and features (and shares all of its code) with the one with the - * same name in Latin IME. They need to be kept synchronized; for any update/bugfix to - * this file, consider also updating/fixing the version in Latin IME. - */ -public final class LocaleUtils { - private LocaleUtils() { - // Intentional empty constructor for utility class. - } - - // Locale match level constants. - // A higher level of match is guaranteed to have a higher numerical value. - // Some room is left within constants to add match cases that may arise necessary - // in the future, for example differentiating between the case where the countries - // are both present and different, and the case where one of the locales does not - // specify the countries. This difference is not needed now. - - // Nothing matches. - public static final int LOCALE_NO_MATCH = 0; - // The languages matches, but the country are different. Or, the reference locale requires a - // country and the tested locale does not have one. - public static final int LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER = 3; - // The languages and country match, but the variants are different. Or, the reference locale - // requires a variant and the tested locale does not have one. - public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER = 6; - // The required locale is null or empty so it will accept anything, and the tested locale - // is non-null and non-empty. - public static final int LOCALE_ANY_MATCH = 10; - // The language matches, and the tested locale specifies a country but the reference locale - // does not require one. - public static final int LOCALE_LANGUAGE_MATCH = 15; - // The language and the country match, and the tested locale specifies a variant but the - // reference locale does not require one. - public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH = 20; - // The compared locales are fully identical. This is the best match level. - public static final int LOCALE_FULL_MATCH = 30; - - // The level at which a match is "normally" considered a locale match with standard algorithms. - // Don't use this directly, use #isMatch to test. - private static final int LOCALE_MATCH = LOCALE_ANY_MATCH; - - /** - * Return how well a tested locale matches a reference locale. - *

- * This will check the tested locale against the reference locale and return a measure of how - * a well it matches the reference. The general idea is that the tested locale has to match - * every specified part of the required locale. A full match occur when they are equal, a - * partial match when the tested locale agrees with the reference locale but is more specific, - * and a difference when the tested locale does not comply with all requirements from the - * reference locale. - * In more detail, if the reference locale specifies at least a language and the testedLocale - * does not specify one, or specifies a different one, LOCALE_NO_MATCH is returned. If the - * reference locale is empty or null, it will match anything - in the form of LOCALE_FULL_MATCH - * if the tested locale is empty or null, and LOCALE_ANY_MATCH otherwise. If the reference and - * tested locale agree on the language, but not on the country, - * LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER is returned if the reference locale specifies a country, - * and LOCALE_LANGUAGE_MATCH otherwise. - * If they agree on both the language and the country, but not on the variant, - * LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER is returned if the reference locale - * specifies a variant, and LOCALE_LANGUAGE_AND_COUNTRY_MATCH otherwise. If everything matches, - * LOCALE_FULL_MATCH is returned. - * Examples: - * en <=> en_US => LOCALE_LANGUAGE_MATCH - * en_US <=> en => LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER - * en_US_POSIX <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER - * en_US <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH - * sp_US <=> en_US => LOCALE_NO_MATCH - * de <=> de => LOCALE_FULL_MATCH - * en_US <=> en_US => LOCALE_FULL_MATCH - * "" <=> en_US => LOCALE_ANY_MATCH - * - * @param referenceLocale the reference locale to test against. - * @param testedLocale the locale to test. - * @return a constant that measures how well the tested locale matches the reference locale. - */ - public static int getMatchLevel(@Nullable final String referenceLocale, - @Nullable final String testedLocale) { - if (StringUtils.isEmpty(referenceLocale)) { - return StringUtils.isEmpty(testedLocale) ? LOCALE_FULL_MATCH : LOCALE_ANY_MATCH; - } - if (null == testedLocale) return LOCALE_NO_MATCH; - final String[] referenceParams = referenceLocale.split("_", 3); - final String[] testedParams = testedLocale.split("_", 3); - // By spec of String#split, [0] cannot be null and length cannot be 0. - if (!referenceParams[0].equals(testedParams[0])) return LOCALE_NO_MATCH; - switch (referenceParams.length) { - case 1: - return 1 == testedParams.length ? LOCALE_FULL_MATCH : LOCALE_LANGUAGE_MATCH; - case 2: - if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; - if (!referenceParams[1].equals(testedParams[1])) - return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; - if (3 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH; - return LOCALE_FULL_MATCH; - case 3: - if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; - if (!referenceParams[1].equals(testedParams[1])) - return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; - if (2 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER; - if (!referenceParams[2].equals(testedParams[2])) - return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER; - return LOCALE_FULL_MATCH; - } - // It should be impossible to come here - return LOCALE_NO_MATCH; - } - - /** - * Find out whether a match level should be considered a match. - *

- * This method takes a match level as returned by the #getMatchLevel method, and returns whether - * it should be considered a match in the usual sense with standard Locale functions. - * - * @param level the match level, as returned by getMatchLevel. - * @return whether this is a match or not. - */ - public static boolean isMatch(final int level) { - return LOCALE_MATCH <= level; - } - - /** similar to isMatch, but returns true if there is anything matching (used for fallback) */ - public static boolean isMatchWeak(final int level) { - return level > LOCALE_NO_MATCH; - } - - private static final HashMap sLocaleCache = new HashMap<>(); - - /** - * Creates a locale from a string specification. - * @param localeString a string specification of a locale, in a format of "ll_cc_variant" where - * "ll" is a language code, "cc" is a country code. - */ - @NonNull - public static Locale constructLocaleFromString(@NonNull final String localeString) { - synchronized (sLocaleCache) { - if (sLocaleCache.containsKey(localeString)) { - return sLocaleCache.get(localeString); - } - final String[] elements = localeString.split("_", 3); - final Locale locale; - if (elements.length == 1) { - locale = new Locale(elements[0]); - } else if (elements.length == 2) { - locale = new Locale(elements[0], elements[1]); - } else { // localeParams.length == 3 - locale = new Locale(elements[0], elements[1], elements[2]); - } - sLocaleCache.put(localeString, locale); - return locale; - } - } - - // TODO: Get this information from the framework instead of maintaining here by ourselves. - private static final HashSet sRtlLanguageCodes = new HashSet<>(); - static { - // List of known Right-To-Left language codes. - sRtlLanguageCodes.add("ar"); // Arabic - sRtlLanguageCodes.add("fa"); // Persian - sRtlLanguageCodes.add("iw"); // Hebrew - sRtlLanguageCodes.add("ku"); // Kurdish - sRtlLanguageCodes.add("ps"); // Pashto - sRtlLanguageCodes.add("sd"); // Sindhi - sRtlLanguageCodes.add("ug"); // Uyghur - sRtlLanguageCodes.add("ur"); // Urdu - sRtlLanguageCodes.add("yi"); // Yiddish - } - - public static boolean isRtlLanguage(@NonNull final Locale locale) { - return sRtlLanguageCodes.contains(locale.getLanguage()); - } - - public static String getLocaleDisplayNameInSystemLocale(final Locale locale, final Context context) { - final String localeString = locale.toString(); - if (localeString.equals("zz")) - return context.getString(R.string.subtype_no_language); - if (localeString.endsWith("_ZZ") || localeString.endsWith("_zz")) { - final int resId = context.getResources().getIdentifier("subtype_"+localeString, "string", context.getPackageName()); - if (resId != 0) - return context.getString(resId); - } - return locale.getDisplayName(context.getResources().getConfiguration().locale); - } -} diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/common/LocaleUtils.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/common/LocaleUtils.kt new file mode 100644 index 000000000..09712f3be --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/common/LocaleUtils.kt @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * modified + * SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only + */ +package org.dslul.openboard.inputmethod.latin.common + +import android.content.Context +import android.os.Build +import org.dslul.openboard.inputmethod.compat.locale +import org.dslul.openboard.inputmethod.latin.R +import org.dslul.openboard.inputmethod.latin.utils.ScriptUtils.script +import org.dslul.openboard.inputmethod.latin.utils.SubtypeLocaleUtils +import java.util.Locale + +/** + * A class to help with handling Locales in string form. + * + * + * This file has the same meaning and features (and shares all of its code) with the one with the + * same name in Latin IME. They need to be kept synchronized; for any update/bugfix to + * this file, consider also updating/fixing the version in Latin IME. + */ +object LocaleUtils { + // Locale match level constants. + // A higher level of match is guaranteed to have a higher numerical value. + // Some room is left within constants to add match cases that may arise necessary + // in the future, for example differentiating between the case where the countries + // are both present and different, and the case where one of the locales does not + // specify the countries. This difference is not needed now. + // Nothing matches. + private const val LOCALE_NO_MATCH = 0 + + // The language (and maybe more) matches, but the script is different + private const val LOCALE_MATCH_SCRIPT_DIFFER = 1 + + // The languages matches, but the country are different. Or, the reference locale requires a + // country and the tested locale does not have one. + private const val LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER = 3 + + // The languages and country match, but the variants are different. Or, the reference locale + // requires a variant and the tested locale does not have one. + private const val LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER = 6 + + // The required locale is null or empty so it will accept anything, and the tested locale + // is non-null and non-empty. + private const val LOCALE_ANY_MATCH = 10 + + // The language matches, and the tested locale specifies a country but the reference locale + // does not require one. + private const val LOCALE_LANGUAGE_MATCH = 15 + + // The language and the country match, and the tested locale specifies a variant but the + // reference locale does not require one. + private const val LOCALE_LANGUAGE_AND_COUNTRY_MATCH = 20 + + // The compared locales are fully identical. This is the best match level. + private const val LOCALE_FULL_MATCH = 30 + + /** + * Return how well a tested locale matches a reference locale. + * + * + * This will check the tested locale against the reference locale and return a measure of how + * a well it matches the reference. The general idea is that the tested locale has to match + * every specified part of the required locale. A full match occur when they are equal, a + * partial match when the tested locale agrees with the reference locale but is more specific, + * and a difference when the tested locale does not comply with all requirements from the + * reference locale. + * In more detail, if the reference locale specifies at least a language and the testedLocale + * does not specify one, or specifies a different one, LOCALE_NO_MATCH is returned. If the + * reference locale is empty or null, it will match anything - in the form of LOCALE_FULL_MATCH + * if the tested locale is empty or null, and LOCALE_ANY_MATCH otherwise. If the reference and + * tested locale agree on the language, but not on the country, + * LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER is returned if the reference locale specifies a country, + * and LOCALE_LANGUAGE_MATCH otherwise. + * If they agree on both the language and the country, but not on the variant, + * LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER is returned if the reference locale + * specifies a variant, and LOCALE_LANGUAGE_AND_COUNTRY_MATCH otherwise. If everything matches, + * LOCALE_FULL_MATCH is returned. + * Examples: + * en <=> en_US => LOCALE_LANGUAGE_MATCH + * en_US <=> en => LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER + * en_US_POSIX <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER + * en_US <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH + * sp_US <=> en_US => LOCALE_NO_MATCH + * de <=> de => LOCALE_FULL_MATCH + * en_US <=> en_US => LOCALE_FULL_MATCH + * "" <=> en_US => LOCALE_ANY_MATCH + * + * @param reference the reference locale to test against. + * @param tested the locale to test. + * @return a constant that measures how well the tested locale matches the reference locale. + */ + private fun getMatchLevel(reference: Locale, tested: Locale): Int { + if (reference == tested) return LOCALE_FULL_MATCH + if (reference.toString().isEmpty()) return LOCALE_ANY_MATCH + if (reference.language != tested.language) return LOCALE_NO_MATCH + // language matches + if (reference.script() != tested.script()) { + return LOCALE_MATCH_SCRIPT_DIFFER + } + // script matches + if (reference.country != tested.country) { + return if (reference.country.isEmpty()) LOCALE_LANGUAGE_MATCH + else LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER + } + // country matches + return if (reference.variant == tested.variant) LOCALE_FULL_MATCH + else if (reference.variant.isEmpty()) LOCALE_LANGUAGE_AND_COUNTRY_MATCH + else LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER + } + + fun getBestMatch(locale: Locale, collection: Collection, toLocale: (T) -> Locale): T? { + var best: T? = null + var bestLevel = 0 + collection.forEach { + val level = getMatchLevel(locale, toLocale(it)) + if (level > bestLevel && level >= LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER) { + bestLevel = level + best = it + } + } + return best + } + + private val sLocaleCache = HashMap() + + /** + * Creates a locale from a string specification or language tag. + * Ideally this works as reverse of Locale.toString and Locale.toLanguageTag + * If a localeString contains "-" it is always interpreted as language tag. + * localeString is a string specification of a locale, in a format of "ll_cc_variant" where + * "ll" is a language code, "cc" is a country code. + * The script may also be part of the locale string, e.g. "ll_cc_#script" + * Converts "ZZ" regions that used to signal latin script into actual latin script. + * "cc" / region should be uppercase and language should be lowercase, this is automatically converted + */ + @JvmStatic + fun String.constructLocale(): Locale { + synchronized(sLocaleCache) { + sLocaleCache[this]?.let { return it } + if (contains("-")) { + // looks like it's actually a language tag, and not a locale string + val locale = Locale.forLanguageTag(this) + sLocaleCache[this] = locale + return locale + } + val elements = split("_", limit = 3) + val language = elements[0].lowercase() + val region = elements.getOrNull(1)?.uppercase() + val locale = if (elements.size == 1) { + Locale(language) // "zz" works both in constructor and forLanguageTag + } else if (elements.size == 2) { + if (region == "ZZ") Locale.forLanguageTag(elements[0] + "-Latn") + else Locale(language, region!!) + } else if (language == "zz") { // localeParams.length == 3 + Locale.Builder().setLanguage(language).setVariant(elements[2]).setScript("Latn").build() + } else if (elements[2].startsWith("#")) { + // best guess: elements[2] is a script, e.g. sr-Latn locale to string is sr__#Latn + Locale.Builder().setLanguage(language).setRegion(region).setScript(elements[2].substringAfter("#")).build() + } else { + Locale(language, region!!, elements[2]) + } + sLocaleCache[this] = locale + return locale + } + } + + @JvmStatic + fun isRtlLanguage(locale: Locale): Boolean { + val displayName = locale.displayName + if (displayName.isEmpty()) return true + return when (Character.getDirectionality(displayName.codePointAt(0))) { + Character.DIRECTIONALITY_RIGHT_TO_LEFT, Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC -> true + else -> false + } + } + + @JvmStatic + fun getLocaleDisplayNameInSystemLocale(locale: Locale, context: Context): String { + val languageTag = locale.toLanguageTag() + if (languageTag == SubtypeLocaleUtils.NO_LANGUAGE) return context.getString(R.string.subtype_no_language) + if (locale.script() != locale.language.constructLocale().script()) { + val resId = context.resources.getIdentifier("subtype_${languageTag.replace("-", "_")}", "string", context.packageName) + if (resId != 0) return context.getString(resId) + } + return locale.getDisplayName(context.resources.configuration.locale()) + } +} diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/inputlogic/InputLogic.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/inputlogic/InputLogic.java index ccd349518..bec3f778a 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/inputlogic/InputLogic.java @@ -261,7 +261,7 @@ public final class InputLogic { // interface public InputTransaction onPickSuggestionManually(final SettingsValues settingsValues, final SuggestedWordInfo suggestionInfo, final int keyboardShiftState, - final int currentKeyboardScriptId, final LatinIME.UIHandler handler) { + final String currentKeyboardScript, final LatinIME.UIHandler handler) { final SuggestedWords suggestedWords = mSuggestedWords; final String suggestion = suggestionInfo.mWord; // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput @@ -271,7 +271,7 @@ public final class InputLogic { // Word separators are suggested before the user inputs something. // Rely on onCodeInput to do the complicated swapping/stripping logic consistently. final Event event = Event.createPunctuationSuggestionPickedEvent(suggestionInfo); - return onCodeInput(settingsValues, event, keyboardShiftState, currentKeyboardScriptId, handler); + return onCodeInput(settingsValues, event, keyboardShiftState, currentKeyboardScript, handler); } final Event event = Event.createSuggestionPickedEvent(suggestionInfo); @@ -426,11 +426,11 @@ public final class InputLogic { */ public InputTransaction onCodeInput(final SettingsValues settingsValues, @NonNull final Event event, final int keyboardShiftMode, - final int currentKeyboardScriptId, final LatinIME.UIHandler handler) { + final String currentKeyboardScript, final LatinIME.UIHandler handler) { mWordBeingCorrectedByCursor = null; mJustRevertedACommit = false; final Event processedEvent; - if (currentKeyboardScriptId == ScriptUtils.SCRIPT_HANGUL + if (currentKeyboardScript.equals(ScriptUtils.SCRIPT_HANGUL) // only use the Hangul chain if codepoint may actually be Hangul // todo: this whole hangul-related logic should probably be somewhere else // need to use hangul combiner for whitespace, because otherwise the current word @@ -472,7 +472,7 @@ public final class InputLogic { if (currentEvent.isConsumed()) { handleConsumedEvent(currentEvent, inputTransaction); } else if (currentEvent.isFunctionalKeyEvent()) { - handleFunctionalEvent(currentEvent, inputTransaction, currentKeyboardScriptId, handler); + handleFunctionalEvent(currentEvent, inputTransaction, currentKeyboardScript, handler); } else { handleNonFunctionalEvent(currentEvent, inputTransaction, handler); } @@ -484,7 +484,7 @@ public final class InputLogic { && (settingsValues.isWordCodePoint(processedEvent.getMCodePoint()) || processedEvent.getMKeyCode() == Constants.CODE_DELETE) ) { - mWordBeingCorrectedByCursor = getWordAtCursor(settingsValues, currentKeyboardScriptId); + mWordBeingCorrectedByCursor = getWordAtCursor(settingsValues, currentKeyboardScript); } if (!inputTransaction.didAutoCorrect() && processedEvent.getMKeyCode() != Constants.CODE_SHIFT && processedEvent.getMKeyCode() != Constants.CODE_CAPSLOCK @@ -645,10 +645,10 @@ public final class InputLogic { * @param inputTransaction The transaction in progress. */ private void handleFunctionalEvent(final Event event, final InputTransaction inputTransaction, - final int currentKeyboardScriptId, final LatinIME.UIHandler handler) { + final String currentKeyboardScript, final LatinIME.UIHandler handler) { switch (event.getMKeyCode()) { case Constants.CODE_DELETE: - handleBackspaceEvent(event, inputTransaction, currentKeyboardScriptId); + handleBackspaceEvent(event, inputTransaction, currentKeyboardScript); // Backspace is a functional key, but it affects the contents of the editor. inputTransaction.setDidAffectContents(); break; @@ -707,7 +707,7 @@ public final class InputLogic { mConnection.selectAll(); break; case Constants.CODE_SELECT_WORD: - mConnection.selectWord(inputTransaction.getMSettingsValues().mSpacingAndPunctuations, currentKeyboardScriptId); + mConnection.selectWord(inputTransaction.getMSettingsValues().mSpacingAndPunctuations, currentKeyboardScript); break; case Constants.CODE_COPY: mConnection.copyText(); @@ -1098,7 +1098,7 @@ public final class InputLogic { * @param inputTransaction The transaction in progress. */ private void handleBackspaceEvent(final Event event, final InputTransaction inputTransaction, - final int currentKeyboardScriptId) { + final String currentKeyboardScript) { mSpaceState = SpaceState.NONE; mDeleteCount++; @@ -1160,7 +1160,7 @@ public final class InputLogic { && inputTransaction.getMSettingsValues().mSpacingAndPunctuations.mCurrentLanguageHasSpaces && !mConnection.isCursorFollowedByWordCharacter( inputTransaction.getMSettingsValues().mSpacingAndPunctuations)) { - restartSuggestionsOnWordTouchedByCursor(inputTransaction.getMSettingsValues(), currentKeyboardScriptId); + restartSuggestionsOnWordTouchedByCursor(inputTransaction.getMSettingsValues(), currentKeyboardScript); } return; } @@ -1235,7 +1235,7 @@ public final class InputLogic { // consider unlearning here because we may have already reached // the previous word, and will lose it after next deletion. hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted( - inputTransaction.getMSettingsValues(), currentKeyboardScriptId); + inputTransaction.getMSettingsValues(), currentKeyboardScript); sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL); totalDeletedLength++; } @@ -1267,7 +1267,7 @@ public final class InputLogic { // consider unlearning here because we may have already reached // the previous word, and will lose it after next deletion. hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted( - inputTransaction.getMSettingsValues(), currentKeyboardScriptId); + inputTransaction.getMSettingsValues(), currentKeyboardScript); final int codePointBeforeCursorToDeleteAgain = mConnection.getCodePointBeforeCursor(); if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) { @@ -1284,7 +1284,7 @@ public final class InputLogic { if (!hasUnlearnedWordBeingDeleted) { // Consider unlearning the word being deleted (if we have not done so already). unlearnWordBeingDeleted( - inputTransaction.getMSettingsValues(), currentKeyboardScriptId); + inputTransaction.getMSettingsValues(), currentKeyboardScript); } if (mConnection.hasSlowInputConnection()) { mSuggestionStripViewAccessor.setNeutralSuggestionStrip(); @@ -1292,18 +1292,18 @@ public final class InputLogic { && inputTransaction.getMSettingsValues().mSpacingAndPunctuations.mCurrentLanguageHasSpaces && !mConnection.isCursorFollowedByWordCharacter( inputTransaction.getMSettingsValues().mSpacingAndPunctuations)) { - restartSuggestionsOnWordTouchedByCursor(inputTransaction.getMSettingsValues(), currentKeyboardScriptId); + restartSuggestionsOnWordTouchedByCursor(inputTransaction.getMSettingsValues(), currentKeyboardScript); } } } - String getWordAtCursor(final SettingsValues settingsValues, final int currentKeyboardScriptId) { + String getWordAtCursor(final SettingsValues settingsValues, final String currentKeyboardScript) { if (!mConnection.hasSelection() && settingsValues.isSuggestionsEnabledPerUserSettings() && settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces) { final TextRange range = mConnection.getWordRangeAtCursor( settingsValues.mSpacingAndPunctuations, - currentKeyboardScriptId, false); + currentKeyboardScript, false); if (range != null) { return range.mWord.toString(); } @@ -1312,7 +1312,7 @@ public final class InputLogic { } boolean unlearnWordBeingDeleted( - final SettingsValues settingsValues, final int currentKeyboardScriptId) { + final SettingsValues settingsValues, final String currentKeyboardScript) { if (mConnection.hasSlowInputConnection()) { // TODO: Refactor unlearning so that it does not incur any extra calls // to the InputConnection. That way it can still be performed on a slow @@ -1324,7 +1324,7 @@ public final class InputLogic { // entered the composing state yet), unlearn the word. // TODO: Consider tracking whether or not this word was typed by the user. if (!mConnection.isCursorFollowedByWordCharacter(settingsValues.mSpacingAndPunctuations)) { - final String wordBeingDeleted = getWordAtCursor(settingsValues, currentKeyboardScriptId); + final String wordBeingDeleted = getWordAtCursor(settingsValues, currentKeyboardScript); if (!TextUtils.isEmpty(wordBeingDeleted)) { unlearnWord(wordBeingDeleted, settingsValues, Constants.EVENT_BACKSPACE); return true; @@ -1625,7 +1625,7 @@ public final class InputLogic { */ public void restartSuggestionsOnWordTouchedByCursor(final SettingsValues settingsValues, // TODO: remove this argument, put it into settingsValues - final int currentKeyboardScriptId) { + final String currentKeyboardScript) { // HACK: We may want to special-case some apps that exhibit bad behavior in case of // recorrection. This is a temporary, stopgap measure that will be removed later. // TODO: remove this. @@ -1653,7 +1653,7 @@ public final class InputLogic { return; } final TextRange range = - mConnection.getWordRangeAtCursor(settingsValues.mSpacingAndPunctuations, currentKeyboardScriptId, true); + mConnection.getWordRangeAtCursor(settingsValues.mSpacingAndPunctuations, currentKeyboardScript, true); if (null == range) return; // Happens if we don't have an input connection at all if (range.length() <= 0) { // Race condition, or touching a word in a non-supported script. diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/makedict/DictionaryHeader.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/makedict/DictionaryHeader.kt index 60017caba..7a368380b 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/makedict/DictionaryHeader.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/makedict/DictionaryHeader.kt @@ -7,6 +7,7 @@ package org.dslul.openboard.inputmethod.latin.makedict import org.dslul.openboard.inputmethod.latin.common.LocaleUtils +import org.dslul.openboard.inputmethod.latin.common.LocaleUtils.constructLocale import org.dslul.openboard.inputmethod.latin.makedict.FormatSpec.DictionaryOptions import org.dslul.openboard.inputmethod.latin.makedict.FormatSpec.FormatOptions import java.text.DateFormat @@ -44,7 +45,7 @@ class DictionaryHeader( fun info(locale: Locale): String { val date = if (mDate == null) "" else DateFormat.getDateInstance(DateFormat.SHORT, locale).format(Date(mDate * 1000L)) + "\n" - return mIdString + "\n" + LocaleUtils.constructLocaleFromString(mLocaleString).getDisplayName(locale) + + return mIdString + "\n" + mLocaleString.constructLocale().getDisplayName(locale) + "\nv" + mVersionString + "\n" + date + description } diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/AdvancedSettingsFragment.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/AdvancedSettingsFragment.kt index 714c1cea6..3a4b31280 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/AdvancedSettingsFragment.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/AdvancedSettingsFragment.kt @@ -19,6 +19,7 @@ import androidx.appcompat.app.AlertDialog import androidx.preference.Preference import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import org.dslul.openboard.inputmethod.compat.locale import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants import org.dslul.openboard.inputmethod.latin.utils.ChecksumCalculator import org.dslul.openboard.inputmethod.keyboard.KeyboardLayoutSet @@ -28,6 +29,7 @@ import org.dslul.openboard.inputmethod.latin.BuildConfig import org.dslul.openboard.inputmethod.latin.R import org.dslul.openboard.inputmethod.latin.SystemBroadcastReceiver import org.dslul.openboard.inputmethod.latin.common.FileUtils +import org.dslul.openboard.inputmethod.latin.common.LocaleUtils.constructLocale import org.dslul.openboard.inputmethod.latin.settings.SeekBarDialogPreference.ValueProxy import org.dslul.openboard.inputmethod.latin.utils.CUSTOM_LAYOUT_PREFIX import org.dslul.openboard.inputmethod.latin.utils.JniUtils @@ -114,7 +116,7 @@ class AdvancedSettingsFragment : SubScreenFragment() { findPreference("custom_background_image")?.setOnPreferenceClickListener { onClickLoadImage() } findPreference("custom_symbols_layout")?.setOnPreferenceClickListener { - val layoutName = Settings.readSymbolsLayoutName(context, context.resources.configuration.locale).takeIf { it.startsWith(CUSTOM_LAYOUT_PREFIX) } + val layoutName = Settings.readSymbolsLayoutName(context, context.resources.configuration.locale()).takeIf { it.startsWith(CUSTOM_LAYOUT_PREFIX) } val oldLayout = if (layoutName != null) null else context.assets.open("layouts${File.separator}symbols.txt").reader().readText() editCustomLayout(layoutName ?: "${CUSTOM_LAYOUT_PREFIX}symbols.txt", context, oldLayout, true) true @@ -169,7 +171,6 @@ class AdvancedSettingsFragment : SubScreenFragment() { } val checksum = ChecksumCalculator.checksum(tmpfile.inputStream()) ?: "" - Log.i("test", "cs $checksum") if (checksum == JniUtils.expectedDefaultChecksum()) { renameToLibfileAndRestart(tmpfile, checksum) } else { @@ -310,7 +311,8 @@ class AdvancedSettingsFragment : SubScreenFragment() { val filesDir = requireContext().filesDir?.path ?: return while (entry != null) { if (backupFilePatterns.any { entry!!.name.matches(it) }) { - val file = File(filesDir, entry.name) + val targetFileName = upgradeFileNames(entry.name) + val file = File(filesDir, targetFileName) FileUtils.copyStreamToNewFile(zip, file) } else if (entry.name == PREFS_FILE_NAME) { val prefLines = String(zip.readBytes()).split("\n") @@ -335,6 +337,38 @@ class AdvancedSettingsFragment : SubScreenFragment() { } } + // todo (later): remove this when new package name has been in use for long enough, this is only for migrating from old openboard name + private fun upgradeFileNames(originalName: String): String { + return when { + originalName.endsWith(USER_DICTIONARY_SUFFIX) -> { + // replace directory after switch to language tag + val dirName = originalName.substringAfter(File.separator).substringBefore(File.separator) + originalName.replace(dirName, dirName.constructLocale().toLanguageTag()) + } + originalName.startsWith("blacklists") -> { + // replace file name after switch to language tag + val fileName = originalName.substringAfter("blacklists${File.separator}").substringBefore(".txt") + originalName.replace(fileName, fileName.constructLocale().toLanguageTag()) + } + originalName.startsWith("layouts") -> { + // replace file name after switch to language tag + // but only if it's not a symbols layout + val localeString = originalName.substringAfter(".").substringBefore(".") + val locale = localeString.constructLocale() + if (locale.toLanguageTag() != "und") + originalName.replace(localeString, locale.toLanguageTag()) + else + originalName // no valid locale -> must be symbols layout, don't change + } + originalName.startsWith("UserHistoryDictionary") -> { + val localeString = originalName.substringAfter(".").substringBefore(".") + val locale = localeString.constructLocale() + originalName.replace(localeString, locale.toLanguageTag()) + } + else -> originalName + } + } + private fun setupKeyLongpressTimeoutSettings() { val prefs = sharedPreferences findPreference(Settings.PREF_KEY_LONGPRESS_TIMEOUT)?.setInterface(object : ValueProxy { diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageFilterList.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageFilterList.kt index 74310ae49..bf6101d3d 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageFilterList.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageFilterList.kt @@ -28,7 +28,6 @@ import org.dslul.openboard.inputmethod.latin.utils.isAdditionalSubtype import org.dslul.openboard.inputmethod.latin.utils.locale import org.dslul.openboard.inputmethod.latin.utils.removeEnabledSubtype import org.dslul.openboard.inputmethod.latin.utils.showMissingDictionaryDialog -import org.dslul.openboard.inputmethod.latin.utils.toLocale class LanguageFilterList(searchField: EditText, recyclerView: RecyclerView) { @@ -124,7 +123,7 @@ private class LanguageAdapter(list: List> = listOf(), c setOnCheckedChangeListener { _, b -> if (b) { if (!infos.first().hasDictionary) - showMissingDictionaryDialog(context, infos.first().subtype.locale().toLocale()) + showMissingDictionaryDialog(context, infos.first().subtype.locale()) addEnabledSubtype(prefs, infos.first().subtype) infos.first().isEnabled = true } else { diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageSettingsDialog.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageSettingsDialog.kt index 0bd8aa0ab..74260e5d6 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageSettingsDialog.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageSettingsDialog.kt @@ -18,16 +18,18 @@ import androidx.core.view.get import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.view.size +import org.dslul.openboard.inputmethod.compat.locale import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants import org.dslul.openboard.inputmethod.keyboard.KeyboardLayoutSet import org.dslul.openboard.inputmethod.keyboard.KeyboardSwitcher -import org.dslul.openboard.inputmethod.latin.BinaryDictionaryGetter import org.dslul.openboard.inputmethod.latin.R import org.dslul.openboard.inputmethod.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET import org.dslul.openboard.inputmethod.latin.common.LocaleUtils +import org.dslul.openboard.inputmethod.latin.common.LocaleUtils.constructLocale import org.dslul.openboard.inputmethod.latin.databinding.LanguageListItemBinding import org.dslul.openboard.inputmethod.latin.databinding.LocaleSettingsDialogBinding import org.dslul.openboard.inputmethod.latin.utils.* +import org.dslul.openboard.inputmethod.latin.utils.ScriptUtils.script import java.io.File import java.util.* @@ -40,8 +42,7 @@ class LanguageSettingsDialog( ) : AlertDialog(context), LanguageSettingsFragment.Listener { private val prefs = DeviceProtectedUtils.getSharedPreferences(context)!! private val binding = LocaleSettingsDialogBinding.inflate(LayoutInflater.from(context)) - private val mainLocaleString = infos.first().subtype.locale() - private val mainLocale = mainLocaleString.toLocale() + private val mainLocale = infos.first().subtype.locale() private var hasInternalDictForLanguage = false private val userDicts = mutableSetOf() @@ -97,7 +98,7 @@ class LanguageSettingsDialog( } private fun addSubtype(name: String) { - val newSubtype = AdditionalSubtypeUtils.createEmojiCapableAdditionalSubtype(mainLocaleString, name, infos.first().subtype.isAsciiCapable) + val newSubtype = AdditionalSubtypeUtils.createEmojiCapableAdditionalSubtype(mainLocale, name, infos.first().subtype.isAsciiCapable) val newSubtypeInfo = newSubtype.toSubtypeInfo(mainLocale, context, true, infos.first().hasDictionary) // enabled by default val displayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(newSubtype) val old = infos.firstOrNull { isAdditionalSubtype(it.subtype) && displayName == SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(it.subtype) } @@ -148,14 +149,15 @@ class LanguageSettingsDialog( .setItems(displayNames.toTypedArray()) { di, i -> di.dismiss() val fileName = context.assets.list("layouts")!!.firstOrNull { it.startsWith(layouts[i]) } ?: return@setItems - loadCustomLayout(context.assets.open("layouts${File.separator}$fileName").reader().readText(), displayNames[i], mainLocaleString, context) { addSubtype(it) } + loadCustomLayout(context.assets.open("layouts${File.separator}$fileName").reader().readText(), + displayNames[i], mainLocale.toLanguageTag(), context) { addSubtype(it) } } .setNegativeButton(android.R.string.cancel, null) .show() } override fun onNewLayoutFile(uri: Uri?) { - loadCustomLayout(uri, mainLocaleString, context) { addSubtype(it) } + loadCustomLayout(uri, mainLocale.toLanguageTag(), context) { addSubtype(it) } } private fun addSubtypeToView(subtype: SubtypeInfo) { @@ -215,10 +217,10 @@ class LanguageSettingsDialog( // can only use multilingual typing if there is more than one dictionary available val availableSecondaryLocales = getAvailableSecondaryLocales( context, - mainLocaleString, + mainLocale, infos.first().subtype.isAsciiCapable ) - val selectedSecondaryLocales = Settings.getSecondaryLocales(prefs, mainLocaleString) + val selectedSecondaryLocales = Settings.getSecondaryLocales(prefs, mainLocale) selectedSecondaryLocales.forEach { addSecondaryLocaleView(it) } @@ -226,14 +228,14 @@ class LanguageSettingsDialog( binding.addSecondaryLanguage.apply { isVisible = true setOnClickListener { - val locales = (availableSecondaryLocales - Settings.getSecondaryLocales(prefs, mainLocaleString)).sortedBy { it.displayName } + val locales = (availableSecondaryLocales - Settings.getSecondaryLocales(prefs, mainLocale)).sortedBy { it.displayName } val localeNames = locales.map { LocaleUtils.getLocaleDisplayNameInSystemLocale(it, context) }.toTypedArray() Builder(context) .setTitle(R.string.button_select_language) .setItems(localeNames) { di, i -> val locale = locales[i] - val localeStrings = Settings.getSecondaryLocales(prefs, mainLocaleString).map { it.toString() } - Settings.setSecondaryLocales(prefs, mainLocaleString, localeStrings + locale.toString()) + val currentSecondaryLocales = Settings.getSecondaryLocales(prefs, mainLocale) + Settings.setSecondaryLocales(prefs, mainLocale, currentSecondaryLocales + locale) addSecondaryLocaleView(locale) di.dismiss() reloadSetting() @@ -256,8 +258,8 @@ class LanguageSettingsDialog( rowBinding.deleteButton.apply { isVisible = true setOnClickListener { - val localeStrings = Settings.getSecondaryLocales(prefs, mainLocaleString).map { it.toString() } - Settings.setSecondaryLocales(prefs, mainLocaleString, localeStrings - locale.toString()) + val currentSecondaryLocales = Settings.getSecondaryLocales(prefs, mainLocale) + Settings.setSecondaryLocales(prefs, mainLocale, currentSecondaryLocales - locale) binding.secondaryLocales.removeView(rowBinding.root) reloadSetting() reloadDictionaries() @@ -280,7 +282,7 @@ class LanguageSettingsDialog( dialog.show() (dialog.findViewById(android.R.id.message) as? TextView)?.movementMethod = LinkMovementMethod.getInstance() } - val userDictsAndHasInternal = getUserAndInternalDictionaries(context, mainLocaleString) + val userDictsAndHasInternal = getUserAndInternalDictionaries(context, mainLocale) hasInternalDictForLanguage = userDictsAndHasInternal.second userDicts.addAll(userDictsAndHasInternal.first) if (hasInternalDictForLanguage) { @@ -330,11 +332,7 @@ class LanguageSettingsDialog( } rowBinding.languageText.setOnClickListener { if (header == null) return@setOnClickListener - val locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - context.resources.configuration.locales[0] - } else { - @Suppress("Deprecation") context.resources.configuration.locale - } + val locale = context.resources.configuration.locale() Builder(context) .setMessage(header.info(locale)) .setPositiveButton(android.R.string.ok, null) @@ -369,12 +367,12 @@ class LanguageSettingsDialog( private fun setupPopupSettings() { binding.popupOrder.setOnClickListener { val moreKeyTypesDefault = prefs.getString(Settings.PREF_MORE_KEYS_ORDER, MORE_KEYS_ORDER_DEFAULT)!! - reorderMoreKeysDialog(context, Settings.PREF_MORE_KEYS_ORDER + "_" + mainLocaleString, moreKeyTypesDefault, R.string.popup_order) + reorderMoreKeysDialog(context, Settings.PREF_MORE_KEYS_ORDER + "_" + mainLocale.toLanguageTag(), moreKeyTypesDefault, R.string.popup_order) KeyboardLayoutSet.onKeyboardThemeChanged() } binding.popupLabelPriority.setOnClickListener { val moreKeyTypesDefault = prefs.getString(Settings.PREF_MORE_KEYS_LABELS_ORDER, MORE_KEYS_LABEL_DEFAULT)!! - reorderMoreKeysDialog(context, Settings.PREF_MORE_KEYS_LABELS_ORDER + "_" + mainLocaleString, moreKeyTypesDefault, R.string.hint_source) + reorderMoreKeysDialog(context, Settings.PREF_MORE_KEYS_LABELS_ORDER + "_" + mainLocale.toLanguageTag(), moreKeyTypesDefault, R.string.hint_source) KeyboardLayoutSet.onKeyboardThemeChanged() } } @@ -383,11 +381,10 @@ class LanguageSettingsDialog( } /** @return list of user dictionary files and whether an internal dictionary exists */ -fun getUserAndInternalDictionaries(context: Context, locale: String): Pair, Boolean> { - val localeString = locale.lowercase() // internal files and folders always use lowercase +fun getUserAndInternalDictionaries(context: Context, locale: Locale): Pair, Boolean> { val userDicts = mutableListOf() var hasInternalDict = false - val userLocaleDir = File(DictionaryInfoUtils.getWordListCacheDirectory(context), localeString) + val userLocaleDir = File(DictionaryInfoUtils.getCacheDirectoryForLocale(locale, context)) if (userLocaleDir.exists() && userLocaleDir.isDirectory) { userLocaleDir.listFiles()?.forEach { if (it.name.endsWith(USER_DICTIONARY_SUFFIX)) @@ -398,37 +395,24 @@ fun getUserAndInternalDictionaries(context: Context, locale: String): Pair - BinaryDictionaryGetter.extractLocaleFromAssetsDictionaryFile(dictFile)?.let { - if (it == localeString || it.languageConsideringZZ() == language) - return userDicts to true - } + val internalDicts = DictionaryInfoUtils.getAssetsDictionaryList(context) ?: return userDicts to false + val best = LocaleUtils.getBestMatch(locale, internalDicts.toList()) { + DictionaryInfoUtils.extractLocaleFromAssetsDictionaryFile(it)?.constructLocale() ?: SubtypeLocaleUtils.NO_LANGUAGE.constructLocale() } - return userDicts to false -} - -private fun String.languageConsideringZZ(): String { - return if (endsWith("zz", false)) - this - else - substringBefore("_") + return userDicts to (best != null) } // get locales with same script as main locale, but different language -private fun getAvailableSecondaryLocales(context: Context, mainLocaleString: String, asciiCapable: Boolean): Set { - val mainLocale = mainLocaleString.toLocale() +private fun getAvailableSecondaryLocales(context: Context, mainLocale: Locale, asciiCapable: Boolean): Set { val locales = getDictionaryLocales(context) val mainScript = if (asciiCapable) ScriptUtils.SCRIPT_LATIN - else ScriptUtils.getScriptFromSpellCheckerLocale(mainLocale) - // ScriptUtils.getScriptFromSpellCheckerLocale may return latin when it should not - // e.g. for persian or chinese + else mainLocale.script() + // script() extension function may return latin in case script cannot be determined // workaround: don't allow secondary locales for these locales if (!asciiCapable && mainScript == ScriptUtils.SCRIPT_LATIN) return emptySet() locales.removeAll { - it.language == mainLocale.language - || ScriptUtils.getScriptFromSpellCheckerLocale(it) != mainScript + it.language == mainLocale.language || it.script() != mainScript } return locales } diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageSettingsFragment.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageSettingsFragment.kt index cb4844296..15405c296 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageSettingsFragment.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageSettingsFragment.kt @@ -19,8 +19,10 @@ import androidx.core.content.edit import androidx.fragment.app.Fragment import org.dslul.openboard.inputmethod.latin.R import org.dslul.openboard.inputmethod.latin.common.LocaleUtils +import org.dslul.openboard.inputmethod.latin.common.LocaleUtils.constructLocale import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils import org.dslul.openboard.inputmethod.latin.utils.DictionaryInfoUtils +import org.dslul.openboard.inputmethod.latin.utils.ScriptUtils.script import org.dslul.openboard.inputmethod.latin.utils.SubtypeLocaleUtils import org.dslul.openboard.inputmethod.latin.utils.getAllAvailableSubtypes import org.dslul.openboard.inputmethod.latin.utils.getDictionaryLocales @@ -32,13 +34,13 @@ import java.util.* // not a SettingsFragment, because with androidx.preferences it's very complicated or // impossible to have the languages RecyclerView scrollable (this way it works nicely out of the box) class LanguageSettingsFragment : Fragment(R.layout.language_settings) { - private val sortedSubtypes = LinkedHashMap>() + private val sortedSubtypesByDisplayName = LinkedHashMap>() private val enabledSubtypes = mutableListOf() private val systemLocales = mutableListOf() private lateinit var languageFilterList: LanguageFilterList private lateinit var sharedPreferences: SharedPreferences private lateinit var systemOnlySwitch: Switch - private val dictionaryLocales by lazy { getDictionaryLocales(requireContext()).mapTo(HashSet()) { it.languageConsideringZZ() } } + private val dictionaryLocales by lazy { getDictionaryLocales(requireContext()) } private val dictionaryFilePicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode != Activity.RESULT_OK) return@registerForActivityResult @@ -62,11 +64,7 @@ class LanguageSettingsFragment : Fragment(R.layout.language_settings) { systemLocales.addAll(getSystemLocales()) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = super.onCreateView(inflater, container, savedInstanceState) ?: return null systemOnlySwitch = view.findViewById(R.id.language_switch) systemOnlySwitch.isChecked = sharedPreferences.getBoolean(Settings.PREF_USE_SYSTEM_LOCALES, true) @@ -97,46 +95,47 @@ class LanguageSettingsFragment : Fragment(R.layout.language_settings) { } private fun loadSubtypes(systemOnly: Boolean) { - sortedSubtypes.clear() + sortedSubtypesByDisplayName.clear() // list of all subtypes, any subtype added to sortedSubtypes will be removed to avoid duplicates val allSubtypes = getAllAvailableSubtypes().toMutableList() - // todo: re-write this, it's hard to understand - // also consider that more _ZZ languages might be added fun List.sortedAddToSubtypesAndRemoveFromAllSubtypes() { val subtypesToAdd = mutableListOf() forEach { locale -> - val localeString = locale.toString() val iterator = allSubtypes.iterator() var added = false while (iterator.hasNext()) { val subtype = iterator.next() - if (subtype.locale() == localeString) { + if (subtype.locale() == locale) { + // add subtypes with matching locale subtypesToAdd.add(subtype.toSubtypeInfo(locale)) iterator.remove() added = true } } - // try again, but with language only + // if locale has a country try again, but match language and script only if (!added && locale.country.isNotEmpty()) { - val languageString = locale.language + val language = locale.language + val script = locale.script() val iter = allSubtypes.iterator() while (iter.hasNext()) { val subtype = iter.next() - if (subtype.locale() == languageString) { - subtypesToAdd.add(subtype.toSubtypeInfo(LocaleUtils.constructLocaleFromString(languageString))) + val subtypeLocale = subtype.locale() + if (subtypeLocale.toLanguageTag() == subtypeLocale.language && subtypeLocale.language == language && script == subtypeLocale.script()) { + // add subtypes using the language only + subtypesToAdd.add(subtype.toSubtypeInfo(language.constructLocale())) iter.remove() added = true } } } - // special treatment for the known languages with _ZZ types - if (!added && (locale.language == "sr" || locale.language == "hi")) { - val languageString = locale.language + // try again if script is not the default script, match language only + if (!added && locale.script() != locale.language.constructLocale().script()) { + val language = locale.language val iter = allSubtypes.iterator() while (iter.hasNext()) { val subtype = iter.next() - if (subtype.locale().substringBefore("_") == languageString) { - subtypesToAdd.add(subtype.toSubtypeInfo(LocaleUtils.constructLocaleFromString(subtype.locale()))) + if (subtype.locale().language == language) { + subtypesToAdd.add(subtype.toSubtypeInfo(subtype.locale())) iter.remove() } } @@ -146,12 +145,12 @@ class LanguageSettingsFragment : Fragment(R.layout.language_settings) { } // add enabled subtypes - enabledSubtypes.map { it.toSubtypeInfo(LocaleUtils.constructLocaleFromString(it.locale()), true) } + enabledSubtypes.map { it.toSubtypeInfo(it.locale(), true) } .sortedBy { it.displayName }.addToSortedSubtypes() allSubtypes.removeAll(enabledSubtypes) if (systemOnly) { // don't add anything else - languageFilterList.setLanguages(sortedSubtypes.values, systemOnly) + languageFilterList.setLanguages(sortedSubtypesByDisplayName.values, systemOnly) return } @@ -160,7 +159,7 @@ class LanguageSettingsFragment : Fragment(R.layout.language_settings) { if (!dir.isDirectory) return@mapNotNull null if (dir.list()?.any { it.endsWith(USER_DICTIONARY_SUFFIX) } == true) - LocaleUtils.constructLocaleFromString(dir.name) + dir.name.constructLocale() else null } localesWithDictionary?.sortedAddToSubtypesAndRemoveFromAllSubtypes() @@ -169,22 +168,22 @@ class LanguageSettingsFragment : Fragment(R.layout.language_settings) { systemLocales.sortedAddToSubtypesAndRemoveFromAllSubtypes() // add the remaining ones - allSubtypes.map { it.toSubtypeInfo(LocaleUtils.constructLocaleFromString(it.locale())) } - .sortedBy { if (it.subtype.locale().equals("zz", true)) - "zz" // "No language (Alphabet)" should be last + allSubtypes.map { it.toSubtypeInfo(it.locale()) } + .sortedBy { if (it.subtype.locale().toLanguageTag().equals(SubtypeLocaleUtils.NO_LANGUAGE, true)) + SubtypeLocaleUtils.NO_LANGUAGE // "No language (Alphabet)" should be last else it.displayName }.addToSortedSubtypes() // set languages - languageFilterList.setLanguages(sortedSubtypes.values, systemOnly) + languageFilterList.setLanguages(sortedSubtypesByDisplayName.values, systemOnly) } private fun InputMethodSubtype.toSubtypeInfo(locale: Locale, isEnabled: Boolean = false) = - toSubtypeInfo(locale, requireContext(), isEnabled, dictionaryLocales.contains(locale.languageConsideringZZ())) + toSubtypeInfo(locale, requireContext(), isEnabled, LocaleUtils.getBestMatch(locale, dictionaryLocales) {it} != null) private fun List.addToSortedSubtypes() { forEach { - sortedSubtypes.getOrPut(it.displayName) { mutableListOf() }.add(it) + sortedSubtypesByDisplayName.getOrPut(it.displayName) { mutableListOf() }.add(it) } } @@ -230,11 +229,4 @@ class SubtypeInfo(val displayName: String, val subtype: InputMethodSubtype, var fun InputMethodSubtype.toSubtypeInfo(locale: Locale, context: Context, isEnabled: Boolean, hasDictionary: Boolean): SubtypeInfo = SubtypeInfo(LocaleUtils.getLocaleDisplayNameInSystemLocale(locale, context), this, isEnabled, hasDictionary) -private fun Locale.languageConsideringZZ(): String { - return if (country.equals("zz", false)) - "${language}_zz" - else - language -} - const val USER_DICTIONARY_SUFFIX = "user.dict" diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java index 208a78101..909b40137 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java @@ -526,7 +526,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang return name; } } - return ScriptUtils.getScriptFromSpellCheckerLocale(locale) == ScriptUtils.SCRIPT_ARABIC ? "symbols_arabic" : "symbols"; + return ScriptUtils.script(locale).equals(ScriptUtils.SCRIPT_ARABIC) ? "symbols_arabic" : "symbols"; } public static String readShiftedSymbolsLayoutName(final Context context) { @@ -571,27 +571,27 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang sCachedBackgroundNight = null; } - public static List getSecondaryLocales(final SharedPreferences prefs, final String mainLocaleString) { - final String localesString = prefs.getString(PREF_SECONDARY_LOCALES_PREFIX + mainLocaleString.toLowerCase(Locale.ROOT), ""); + public static List getSecondaryLocales(final SharedPreferences prefs, final Locale mainLocale) { + final String localesString = prefs.getString(PREF_SECONDARY_LOCALES_PREFIX + mainLocale.toLanguageTag(), ""); final ArrayList locales = new ArrayList<>(); - for (String locale : localesString.split(";")) { - if (locale.isEmpty()) continue; - locales.add(LocaleUtils.constructLocaleFromString(locale)); + for (String languageTag : localesString.split(";")) { + if (languageTag.isEmpty()) continue; + locales.add(LocaleUtils.constructLocale(languageTag)); } return locales; } - public static void setSecondaryLocales(final SharedPreferences prefs, final String mainLocaleString, final List locales) { + public static void setSecondaryLocales(final SharedPreferences prefs, final Locale mainLocale, final List locales) { if (locales.isEmpty()) { - prefs.edit().putString(PREF_SECONDARY_LOCALES_PREFIX + mainLocaleString.toLowerCase(Locale.ROOT), "").apply(); + prefs.edit().putString(PREF_SECONDARY_LOCALES_PREFIX + mainLocale, "").apply(); return; } final StringBuilder sb = new StringBuilder(); - for (String locale : locales) { - sb.append(";").append(locale); + for (Locale locale : locales) { + sb.append(";").append(locale.toLanguageTag()); } - prefs.edit().putString(PREF_SECONDARY_LOCALES_PREFIX + mainLocaleString.toLowerCase(Locale.ROOT), sb.toString()).apply(); + prefs.edit().putString(PREF_SECONDARY_LOCALES_PREFIX + mainLocale, sb.toString()).apply(); } public static Colors getColorsForCurrentTheme(final Context context, final SharedPreferences prefs) { diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SettingsValues.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SettingsValues.java index 96bfa0417..48393f8bf 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SettingsValues.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SettingsValues.java @@ -16,6 +16,7 @@ import android.view.inputmethod.InputMethodSubtype; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.dslul.openboard.inputmethod.compat.ConfigurationCompatKt; import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.LocaleKeyTextsKt; import org.dslul.openboard.inputmethod.latin.InputAttributes; import org.dslul.openboard.inputmethod.latin.R; @@ -27,6 +28,7 @@ import org.dslul.openboard.inputmethod.latin.utils.Log; import org.dslul.openboard.inputmethod.latin.utils.MoreKeysUtilsKt; import org.dslul.openboard.inputmethod.latin.utils.ScriptUtils; import org.dslul.openboard.inputmethod.latin.utils.SubtypeSettingsKt; +import org.dslul.openboard.inputmethod.latin.utils.SubtypeUtilsKt; import java.util.Arrays; import java.util.List; @@ -127,7 +129,7 @@ public class SettingsValues { // creation of Colors and SpacingAndPunctuations are the slowest parts in here, but still ok public SettingsValues(final Context context, final SharedPreferences prefs, final Resources res, @NonNull final InputAttributes inputAttributes) { - mLocale = res.getConfiguration().locale; + mLocale = ConfigurationCompatKt.locale(res.getConfiguration()); // Store the input attributes mInputAttributes = inputAttributes; @@ -207,7 +209,7 @@ public class SettingsValues { } else mOneHandedModeScale = 1f; final InputMethodSubtype selectedSubtype = SubtypeSettingsKt.getSelectedSubtype(prefs); - mSecondaryLocales = Settings.getSecondaryLocales(prefs, selectedSubtype.getLocale()); + mSecondaryLocales = Settings.getSecondaryLocales(prefs, SubtypeUtilsKt.locale(selectedSubtype)); mShowMoreMoreKeys = selectedSubtype.isAsciiCapable() ? Settings.readMoreMoreKeysPref(prefs) : LocaleKeyTextsKt.MORE_KEYS_NORMAL; diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SpacingAndPunctuations.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SpacingAndPunctuations.java index 7b3534c1e..3c35d310b 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SpacingAndPunctuations.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SpacingAndPunctuations.java @@ -8,6 +8,7 @@ package org.dslul.openboard.inputmethod.latin.settings; import android.content.res.Resources; +import org.dslul.openboard.inputmethod.compat.ConfigurationCompatKt; import org.dslul.openboard.inputmethod.keyboard.internal.MoreKeySpec; import org.dslul.openboard.inputmethod.latin.PunctuationSuggestions; import org.dslul.openboard.inputmethod.latin.R; @@ -50,7 +51,7 @@ public final class SpacingAndPunctuations { mCurrentLanguageHasSpaces = res.getBoolean(R.bool.current_language_has_spaces); // make it empty if language doesn't have spaces, to avoid weird glitches mSortedSometimesWordConnectors = (urlDetection && mCurrentLanguageHasSpaces) ? StringUtils.toSortedCodePointArray(res.getString(R.string.symbols_sometimes_word_connectors)) : new int[0]; - final Locale locale = res.getConfiguration().locale; + final Locale locale = ConfigurationCompatKt.locale(res.getConfiguration()); // Heuristic: we use American Typography rules because it's the most common rules for all // English variants. German rules (not "German typography") also have small gotchas. mUsesAmericanTypography = Locale.ENGLISH.getLanguage().equals(locale.getLanguage()); diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/UserDictionaryAddWordContents.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/UserDictionaryAddWordContents.java index c39430be8..865e09035 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/UserDictionaryAddWordContents.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/UserDictionaryAddWordContents.java @@ -22,10 +22,12 @@ import android.widget.EditText; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.dslul.openboard.inputmethod.compat.ConfigurationCompatKt; import org.dslul.openboard.inputmethod.latin.R; import org.dslul.openboard.inputmethod.latin.common.LocaleUtils; import java.util.ArrayList; +import java.util.Locale; import java.util.TreeSet; public class UserDictionaryAddWordContents { @@ -49,7 +51,7 @@ public class UserDictionaryAddWordContents { private final EditText mWordEditText; private final EditText mShortcutEditText; private final EditText mWeightEditText; - private String mLocaleString; + private Locale mLocale; private final String mOldWord; private final String mOldShortcut; private final String mOldWeight; @@ -94,7 +96,8 @@ public class UserDictionaryAddWordContents { mOldWord = args.getString(EXTRA_WORD); mOldWeight = args.getString(EXTRA_WEIGHT); - updateLocale(mContext, args.getString(EXTRA_LOCALE)); + final String extraLocale = args.getString(EXTRA_LOCALE); + updateLocale(mContext, extraLocale == null ? null : LocaleUtils.constructLocale(extraLocale)); } UserDictionaryAddWordContents(final View view, final UserDictionaryAddWordContents oldInstanceToBeEdited) { @@ -105,26 +108,26 @@ public class UserDictionaryAddWordContents { mOldWord = oldInstanceToBeEdited.mSavedWord; mOldShortcut = oldInstanceToBeEdited.mSavedShortcut; mOldWeight = oldInstanceToBeEdited.mSavedWeight; - updateLocale(mContext, mLocaleString); + updateLocale(mContext, mLocale); } // locale may be null, this means system locale // It may also be the empty string, which means "For all languages" - void updateLocale(final Context context, final String locale) { + void updateLocale(final Context context, @Nullable final Locale locale) { mContext = context; - mLocaleString = null == locale - ? mContext.getResources().getConfiguration().locale.toString() + mLocale = null == locale + ? ConfigurationCompatKt.locale(mContext.getResources().getConfiguration()) : locale; // The keyboard uses the language layout of the user dictionary if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - mWordEditText.setImeHintLocales(new LocaleList(LocaleUtils.constructLocaleFromString(mLocaleString))); + mWordEditText.setImeHintLocales(new LocaleList(mLocale)); } } - String getLocale() { - return mLocaleString; + Locale getLocale() { + return mLocale; } void delete(final Context context) { @@ -133,13 +136,14 @@ public class UserDictionaryAddWordContents { return; final ContentResolver resolver = context.getContentResolver(); // Remove the old entry. - UserDictionarySettings.deleteWordInEditMode(mOldWord, mOldShortcut, mOldWeight, mLocaleString, resolver); + UserDictionarySettings.deleteWordInEditMode(mOldWord, mOldShortcut, mOldWeight, mLocale.toString(), resolver); } + // requires use of locale string for interaction with Android system public final int apply(@NonNull final Context context) { final ContentResolver resolver = context.getContentResolver(); final String newWord = mWordEditText.getText().toString(); - final String locale = mLocaleString; + final String localeString = mLocale.toString(); if (TextUtils.isEmpty(newWord)) { // If the word is empty, don't insert it. @@ -159,35 +163,33 @@ public class UserDictionaryAddWordContents { mSavedWord = newWord; // In edit mode, everything is modified without overwriting other existing words - if (MODE_EDIT == mMode && hasWord(newWord, locale, context) && newWord.equals(mOldWord)) { - UserDictionarySettings.deleteWordInEditMode(mOldWord, mOldShortcut, mOldWeight, locale, resolver); + if (MODE_EDIT == mMode && hasWord(newWord, localeString, context) && newWord.equals(mOldWord)) { + UserDictionarySettings.deleteWordInEditMode(mOldWord, mOldShortcut, mOldWeight, localeString, resolver); } else { mMode = MODE_INSERT; } - if (mMode == MODE_INSERT && hasWord(newWord, locale, context)) { + if (mMode == MODE_INSERT && hasWord(newWord, localeString, context)) { return CODE_ALREADY_PRESENT; } if (mMode == MODE_INSERT) { // Delete duplicate when adding or updating new word - UserDictionarySettings.deleteWordInEditMode(mOldWord, mOldShortcut, mOldWeight, locale, resolver); + UserDictionarySettings.deleteWordInEditMode(mOldWord, mOldShortcut, mOldWeight, localeString, resolver); // Update the existing word by adding a new one - UserDictionary.Words.addWord(context, newWord, - Integer.parseInt(mSavedWeight), mSavedShortcut, TextUtils.isEmpty(mLocaleString) ? - null : LocaleUtils.constructLocaleFromString(mLocaleString)); + UserDictionary.Words.addWord(context, newWord, Integer.parseInt(mSavedWeight), + mSavedShortcut, TextUtils.isEmpty(mLocale.toString()) ? null : mLocale); return CODE_UPDATED; } // Delete duplicates - UserDictionarySettings.deleteWord(newWord, locale, resolver); + UserDictionarySettings.deleteWord(newWord, localeString, resolver); // In this class we use the empty string to represent 'all locales' and mLocale cannot // be null. However the addWord method takes null to mean 'all locales'. - UserDictionary.Words.addWord(context, newWord, - Integer.parseInt(mSavedWeight), mSavedShortcut, TextUtils.isEmpty(mLocaleString) ? - null : LocaleUtils.constructLocaleFromString(mLocaleString)); + UserDictionary.Words.addWord(context, newWord, Integer.parseInt(mSavedWeight), + mSavedShortcut, TextUtils.isEmpty(mLocale.toString()) ? null : mLocale); return CODE_WORD_ADDED; } @@ -195,7 +197,7 @@ public class UserDictionaryAddWordContents { public boolean isExistingWord(final Context context) { final String newWord = mWordEditText.getText().toString(); if (mMode != MODE_EDIT) { - return hasWord(newWord, mLocaleString, context); + return hasWord(newWord, mLocale.toString(), context); } else { return false; } @@ -207,17 +209,18 @@ public class UserDictionaryAddWordContents { private static final String HAS_WORD_AND_ALL_LOCALES_SELECTION = UserDictionary.Words.WORD + "=? AND " + UserDictionary.Words.LOCALE + " is null"; - private boolean hasWord(final String word, final String locale, final Context context) { + // requires use of locale string for interaction with Android system + private boolean hasWord(final String word, final String localeString, final Context context) { final Cursor cursor; - if ("".equals(locale)) { + if ("".equals(localeString)) { cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI, HAS_WORD_PROJECTION, HAS_WORD_AND_ALL_LOCALES_SELECTION, new String[] { word }, null); } else { cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI, HAS_WORD_PROJECTION, HAS_WORD_AND_LOCALE_SELECTION, - new String[] { word, locale}, null); + new String[] { word, localeString}, null); } try { if (null == cursor) return false; @@ -230,28 +233,34 @@ public class UserDictionaryAddWordContents { public static class LocaleRenderer { private final String mLocaleString; private final String mDescription; + private final Locale mLocale; - public LocaleRenderer(final Context context, @Nullable final String localeString) { - mLocaleString = localeString; + public LocaleRenderer(final Context context, @NonNull final Locale locale) { + mLocaleString = locale.toString(); + mLocale = locale; - if (null == localeString || "".equals(localeString)) { + if ("".equals(locale.toString())) { mDescription = context.getString(R.string.user_dict_settings_all_languages); } else { - mDescription = UserDictionarySettings.getLocaleDisplayName(context, localeString); + mDescription = UserDictionarySettings.getLocaleDisplayName(context, locale); } } @Override + // used in ArrayAdapter of spinner in UserDictionaryAddWordFragment public String toString() { return mDescription; } public String getLocaleString() { return mLocaleString; } + public Locale getLocale() { + return mLocale; + } } private static void addLocaleDisplayNameToList(final Context context, - final ArrayList list, final String locale) { + final ArrayList list, final Locale locale) { if (null != locale) { list.add(new LocaleRenderer(context, locale)); } @@ -259,26 +268,26 @@ public class UserDictionaryAddWordContents { // Helper method to get the list of locales and subtypes to display for this word public ArrayList getLocaleRendererList(final Context context) { - final TreeSet sortedLanguages = UserDictionaryListFragment.getSortedDictionaryLocaleStrings(context); + final TreeSet sortedLocales = UserDictionaryListFragment.getSortedDictionaryLocales(context); // mLocale is removed from the language list as it will be added to the top of the list - sortedLanguages.remove(mLocaleString); + sortedLocales.remove(mLocale); // "For all languages" is removed from the language list as it will be added at the end of the list - sortedLanguages.remove(""); + sortedLocales.remove(new Locale("")); // final list of locales to show final ArrayList localesList = new ArrayList<>(); // First, add the language of the personal dictionary at the top of the list - addLocaleDisplayNameToList(context, localesList, mLocaleString); + addLocaleDisplayNameToList(context, localesList, mLocale); // Next, add all other languages which will be sorted alphabetically in UserDictionaryAddWordFragment.updateSpinner() - for (String language : sortedLanguages) { - addLocaleDisplayNameToList(context, localesList, language); + for (Locale locale : sortedLocales) { + addLocaleDisplayNameToList(context, localesList, locale); } // Finally, add "All languages" at the end of the list - if (!"".equals(mLocaleString)) { - addLocaleDisplayNameToList(context, localesList, ""); + if (!"".equals(mLocale.toString())) { + addLocaleDisplayNameToList(context, localesList, new Locale("")); } return localesList; diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/UserDictionaryAddWordFragment.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/UserDictionaryAddWordFragment.java index ba3d3b221..42f2bbe41 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/UserDictionaryAddWordFragment.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/UserDictionaryAddWordFragment.java @@ -32,6 +32,7 @@ import androidx.core.graphics.drawable.DrawableKt; import androidx.core.widget.TextViewKt; import org.dslul.openboard.inputmethod.latin.R; +import org.dslul.openboard.inputmethod.latin.common.LocaleUtils; import org.dslul.openboard.inputmethod.latin.settings.UserDictionaryAddWordContents.LocaleRenderer; import java.util.ArrayList; @@ -165,22 +166,22 @@ public class UserDictionaryAddWordFragment extends SubScreenFragment { localeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { - final LocaleRenderer locale = (LocaleRenderer)parent.getItemAtPosition(position); + final LocaleRenderer localeRenderer = (LocaleRenderer)parent.getItemAtPosition(position); - mContents.updateLocale(requireContext(), locale.getLocaleString()); + mContents.updateLocale(requireContext(), localeRenderer.getLocale()); // To have the selected language at the top of the list, this one is removed from the list localesList.remove(position); // The other languages are then sorted alphabetically by name, with the exception of "For all languages" - Collections.sort(localesList, (locale1, locale2) -> { - if (!locale1.getLocaleString().equals("") && !locale2.getLocaleString().equals("")) { - return locale1.toString().compareToIgnoreCase(locale2.toString()); + Collections.sort(localesList, (localeRenderer1, localeRenderer2) -> { + if (!localeRenderer1.getLocaleString().equals("") && !localeRenderer2.getLocaleString().equals("")) { + return localeRenderer1.toString().compareToIgnoreCase(localeRenderer2.toString()); } else { - return locale1.getLocaleString().compareToIgnoreCase(locale2.getLocaleString()); + return localeRenderer1.getLocaleString().compareToIgnoreCase(localeRenderer2.getLocaleString()); } }); // Set "For all languages" to the end of the list - if (!locale.getLocaleString().equals("")) { + if (!localeRenderer.getLocaleString().equals("")) { // After alphabetical sorting, "For all languages" is always in 1st position. // (The position is 0 because the spinner menu item count starts at 0) final LocaleRenderer forAllLanguages = adapter.getItem(0); @@ -191,13 +192,13 @@ public class UserDictionaryAddWordFragment extends SubScreenFragment { } // Finally, we add the selected language to the top of the list. - localesList.add(0, locale); + localesList.add(0, localeRenderer); // When a language is selected, the keyboard layout changes automatically mInput.restartInput(mWordEditText); // The action bar subtitle is updated when a language is selected in the drop-down menu - mActionBar.setSubtitle(locale.toString()); + mActionBar.setSubtitle(localeRenderer.toString()); } @Override @@ -205,7 +206,8 @@ public class UserDictionaryAddWordFragment extends SubScreenFragment { // I'm not sure we can come here, but if we do, that's the right thing to do. final Bundle args = getArguments(); if (args == null) return; - mContents.updateLocale(requireContext(), args.getString(UserDictionaryAddWordContents.EXTRA_LOCALE)); + final String localeString = args.getString(UserDictionaryAddWordContents.EXTRA_LOCALE); + mContents.updateLocale(requireContext(), localeString == null ? null : LocaleUtils.constructLocale(localeString)); } }); } diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/UserDictionaryListFragment.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/UserDictionaryListFragment.java index af94abfe6..03de8fcc6 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/UserDictionaryListFragment.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/UserDictionaryListFragment.java @@ -25,8 +25,11 @@ import androidx.preference.PreferenceGroup; import org.dslul.openboard.inputmethod.latin.R; import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils; +import org.dslul.openboard.inputmethod.latin.utils.SubtypeLocaleUtils; import org.dslul.openboard.inputmethod.latin.utils.SubtypeSettingsKt; +import org.dslul.openboard.inputmethod.latin.utils.SubtypeUtilsKt; +import java.util.Comparator; import java.util.Locale; import java.util.TreeSet; @@ -91,54 +94,58 @@ public class UserDictionaryListFragment extends SubScreenFragment { * @param userDictGroup The group to put the settings in. */ private void createUserDictSettings(final PreferenceGroup userDictGroup) { - final TreeSet sortedLanguages = getSortedDictionaryLocaleStrings(requireContext()); + final TreeSet sortedLocales = getSortedDictionaryLocales(requireContext()); // Add preference "for all locales" - userDictGroup.addPreference(createUserDictionaryPreference("")); + userDictGroup.addPreference(createUserDictionaryPreference(new Locale(""))); // Add preference for each dictionary locale - for (String localeUserDictionary : sortedLanguages) { - userDictGroup.addPreference(createUserDictionaryPreference(localeUserDictionary)); + for (final Locale locale : sortedLocales) { + userDictGroup.addPreference(createUserDictionaryPreference(locale)); } } - static TreeSet getSortedDictionaryLocaleStrings(final Context context) { + static TreeSet getSortedDictionaryLocales(final Context context) { final SharedPreferences prefs = DeviceProtectedUtils.getSharedPreferences(context); final boolean localeSystemOnly = prefs.getBoolean(Settings.PREF_USE_SYSTEM_LOCALES, true); - final TreeSet sortedLanguages = new TreeSet<>(String::compareToIgnoreCase); + final TreeSet sortedLocales = new TreeSet<>(new LocaleComparator()); // Add the main language selected in the "Language and Layouts" setting except "No language" for (InputMethodSubtype mainSubtype : SubtypeSettingsKt.getEnabledSubtypes(prefs, true)) { - if (!mainSubtype.getLocale().equals("zz")) { - sortedLanguages.add(mainSubtype.getLocale()); + final Locale mainLocale = SubtypeUtilsKt.locale(mainSubtype); + if (!mainLocale.toLanguageTag().equals(SubtypeLocaleUtils.NO_LANGUAGE)) { + sortedLocales.add(mainLocale); } // Secondary language is added only if main language is selected and if system language is not enabled if (!localeSystemOnly) { - for (Locale secondaryLocale : Settings.getSecondaryLocales(prefs, mainSubtype.getLocale())) { - sortedLanguages.add(secondaryLocale.toString()); - } + sortedLocales.addAll(Settings.getSecondaryLocales(prefs, mainLocale)); } } - for (Locale systemSubtype : SubtypeSettingsKt.getSystemLocales()) { - sortedLanguages.add(systemSubtype.toString()); + sortedLocales.addAll(SubtypeSettingsKt.getSystemLocales()); + return sortedLocales; + } + + private static class LocaleComparator implements Comparator { + @Override + public int compare(Locale locale1, Locale locale2) { + return locale1.toLanguageTag().compareToIgnoreCase(locale2.toLanguageTag()); } - return sortedLanguages; } /** * Create a single User Dictionary Preference object, with its parameters set. - * @param localeString The locale for which this user dictionary is for. + * @param locale The locale for which this user dictionary is for. * @return The corresponding preference. */ - private Preference createUserDictionaryPreference(@NonNull final String localeString) { + private Preference createUserDictionaryPreference(@NonNull final Locale locale) { final Preference newPref = new Preference(requireContext()); - if (localeString.isEmpty()) { + if (locale.toString().isEmpty()) { newPref.setTitle(getString(R.string.user_dict_settings_all_languages)); } else { - newPref.setTitle(UserDictionarySettings.getLocaleDisplayName(requireContext(), localeString)); + newPref.setTitle(UserDictionarySettings.getLocaleDisplayName(requireContext(), locale)); } - newPref.getExtras().putString("locale", localeString); + newPref.getExtras().putString("locale", locale.toLanguageTag()); newPref.setIconSpaceReserved(false); newPref.setFragment(UserDictionarySettings.class.getName()); diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/UserDictionarySettings.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/UserDictionarySettings.java index 7f092e554..51ef9c796 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/UserDictionarySettings.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/UserDictionarySettings.java @@ -94,7 +94,7 @@ public class UserDictionarySettings extends ListFragment { private Cursor mCursor; - protected String mLocale; + protected Locale mLocale; @Override public void onCreate(Bundle savedInstanceState) { @@ -122,13 +122,13 @@ public class UserDictionarySettings extends ListFragment { final Bundle arguments = getArguments(); final String localeFromArguments = null == arguments ? null : arguments.getString("locale"); - final String locale; + final String localeString; if (null != localeFromArguments) { - locale = localeFromArguments; - } else locale = localeFromIntent; + localeString = localeFromArguments; + } else localeString = localeFromIntent; + mLocale = localeString == null ? null : LocaleUtils.constructLocale(localeString); - mLocale = locale; - createCursor(locale); + createCursor(mLocale == null ? null : mLocale.toString()); TextView emptyView = view.findViewById(android.R.id.empty); emptyView.setText(R.string.user_dict_settings_empty_text); @@ -158,8 +158,9 @@ public class UserDictionarySettings extends ListFragment { } } - private void createCursor(final String locale) { - // Locale can be any of: + // cursor must be created using localeString to be in line with Android system + private void createCursor(@Nullable final String localeString) { + // localeString can be any of: // - The string representation of a locale, as returned by Locale#toString() // - The empty string. This means we want a cursor returning words valid for all locales. // - null. This means we want a cursor for the current locale, whatever this is. @@ -172,13 +173,13 @@ public class UserDictionarySettings extends ListFragment { // human-readable, like "all_locales" and "current_locales" strings, provided they // can be guaranteed not to match locales that may exist. - if ("".equals(locale)) { + if ("".equals(localeString)) { // Case-insensitive sort mCursor = requireContext().getContentResolver().query(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION, QUERY_SELECTION_ALL_LOCALES, null, "UPPER(" + UserDictionary.Words.WORD + ")"); } else { - final String queryLocale = null != locale ? locale : Locale.getDefault().toString(); + final String queryLocale = null != localeString ? localeString : Locale.getDefault().toString(); mCursor = requireContext().getContentResolver().query(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION, QUERY_SELECTION, new String[] { queryLocale }, "UPPER(" + UserDictionary.Words.WORD + ")"); @@ -200,13 +201,12 @@ public class UserDictionarySettings extends ListFragment { } } - public static String getLocaleDisplayName(Context context, String localeStr) { - if (TextUtils.isEmpty(localeStr)) { + public static String getLocaleDisplayName(Context context, Locale locale) { + if (locale.toString().isEmpty()) { // CAVEAT: localeStr should not be null because a null locale stands for the system // locale in UserDictionary.Words.addWord. return context.getResources().getString(R.string.user_dict_settings_all_languages); } - final Locale locale = LocaleUtils.constructLocaleFromString(localeStr); return LocaleUtils.getLocaleDisplayNameInSystemLocale(locale, context); } @@ -223,7 +223,7 @@ public class UserDictionarySettings extends ListFragment { args.putString(UserDictionaryAddWordContents.EXTRA_WORD, editingWord); args.putString(UserDictionaryAddWordContents.EXTRA_SHORTCUT, editingShortcut); args.putString(UserDictionaryAddWordContents.EXTRA_WEIGHT, editingWeight); - args.putString(UserDictionaryAddWordContents.EXTRA_LOCALE, mLocale); + args.putString(UserDictionaryAddWordContents.EXTRA_LOCALE, mLocale.toLanguageTag()); AppCompatActivity activity = (AppCompatActivity) requireActivity(); activity.getSupportFragmentManager().beginTransaction() .replace(android.R.id.content, UserDictionaryAddWordFragment.class, args) @@ -252,27 +252,28 @@ public class UserDictionarySettings extends ListFragment { return mCursor.getString(mCursor.getColumnIndexOrThrow(column)); } + // requires use of locale string for interaction with Android system public static void deleteWordInEditMode(final String word, final String shortcut, final String weight, - final String locale, final ContentResolver resolver) { + final String localeString, final ContentResolver resolver) { if (TextUtils.isEmpty(shortcut)) { - if ("".equals(locale)) { + if ("".equals(localeString)) { resolver.delete( UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITHOUT_SHORTCUT_AND_WITH_ALL_LOCALES, new String[] { word, weight }); } else { resolver.delete( UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITHOUT_SHORTCUT_AND_WITH_LOCALE, - new String[] { word, weight, locale }); + new String[] { word, weight, localeString }); } } else { - if ("".equals(locale)) { + if ("".equals(localeString)) { resolver.delete( UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITH_SHORTCUT_AND_WITH_ALL_LOCALES, new String[] { word, shortcut, weight }); } else { resolver.delete( UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITH_SHORTCUT_AND_WITH_LOCALE, - new String[] { word, shortcut, weight, locale }); + new String[] { word, shortcut, weight, localeString }); } } } diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index a496d349f..845611ea3 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -29,7 +29,7 @@ import org.dslul.openboard.inputmethod.latin.common.ComposedData; import org.dslul.openboard.inputmethod.latin.settings.SettingsValuesForSuggestion; import org.dslul.openboard.inputmethod.latin.utils.AdditionalSubtypeUtils; import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils; -import org.dslul.openboard.inputmethod.latin.utils.ScriptUtils; +import org.dslul.openboard.inputmethod.latin.utils.SubtypeSettingsKt; import org.dslul.openboard.inputmethod.latin.utils.SuggestionResults; import java.util.Locale; @@ -81,36 +81,17 @@ public final class AndroidSpellCheckerService extends SpellCheckerService @Override public void onCreate() { super.onCreate(); - mRecommendedThreshold = Float.parseFloat( - getString(R.string.spellchecker_recommended_threshold_value)); + mRecommendedThreshold = Float.parseFloat(getString(R.string.spellchecker_recommended_threshold_value)); final SharedPreferences prefs = DeviceProtectedUtils.getSharedPreferences(this); prefs.registerOnSharedPreferenceChangeListener(this); onSharedPreferenceChanged(prefs, PREF_USE_CONTACTS_KEY); + SubtypeSettingsKt.init(this); } public float getRecommendedThreshold() { return mRecommendedThreshold; } - private static String getKeyboardLayoutNameForLocale(final Locale locale) { - // See b/19963288. - if (locale.getLanguage().equals("sr") || locale.getLanguage().equals("mk")) { - return locale.getLanguage(); - } - final int script = ScriptUtils.getScriptFromSpellCheckerLocale(locale); - return switch (script) { - case ScriptUtils.SCRIPT_LATIN -> "qwerty"; - case ScriptUtils.SCRIPT_ARMENIAN -> "armenian_phonetic"; - case ScriptUtils.SCRIPT_CYRILLIC -> "ru"; - case ScriptUtils.SCRIPT_GREEK -> "greek"; - case ScriptUtils.SCRIPT_HEBREW -> "hebrew"; - case ScriptUtils.SCRIPT_BULGARIAN -> "bulgarian"; - case ScriptUtils.SCRIPT_GEORGIAN -> "georgian"; - case ScriptUtils.SCRIPT_BENGALI -> "bengali_unijoy"; - default -> throw new RuntimeException("Wrong script supplied: " + script); - }; - } - @Override public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { if (!PREF_USE_CONTACTS_KEY.equals(key)) return; @@ -201,17 +182,14 @@ public final class AndroidSpellCheckerService extends SpellCheckerService Keyboard keyboard = mKeyboardCache.get(locale); if (keyboard == null) { keyboard = createKeyboardForLocale(locale); - if (keyboard != null) { - mKeyboardCache.put(locale, keyboard); - } + mKeyboardCache.put(locale, keyboard); } return keyboard; } private Keyboard createKeyboardForLocale(final Locale locale) { - final String keyboardLayoutName = getKeyboardLayoutNameForLocale(locale); - final InputMethodSubtype subtype = AdditionalSubtypeUtils.createDummyAdditionalSubtype( - locale.toString(), keyboardLayoutName); + final String keyboardLayoutName = SubtypeSettingsKt.getMatchingLayoutSetNameForLocale(locale); + final InputMethodSubtype subtype = AdditionalSubtypeUtils.createDummyAdditionalSubtype(locale, keyboardLayoutName); final KeyboardLayoutSet keyboardLayoutSet = createKeyboardSetForSpellChecker(subtype); return keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET); } diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java index e946c0821..e6544cd42 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java @@ -9,16 +9,16 @@ package org.dslul.openboard.inputmethod.latin.spellcheck; import android.content.res.Resources; import android.os.Binder; import android.text.TextUtils; -import org.dslul.openboard.inputmethod.latin.utils.Log; import android.view.textservice.SentenceSuggestionsInfo; import android.view.textservice.SuggestionsInfo; import android.view.textservice.TextInfo; import org.dslul.openboard.inputmethod.latin.NgramContext; +import org.dslul.openboard.inputmethod.latin.common.LocaleUtils; +import org.dslul.openboard.inputmethod.latin.utils.Log; import org.dslul.openboard.inputmethod.latin.utils.SpannableStringUtils; import java.util.ArrayList; -import java.util.Locale; public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession { private static final String TAG = AndroidSpellCheckerSession.class.getSimpleName(); @@ -142,10 +142,9 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck synchronized(this) { sentenceLevelAdapter = mSentenceLevelAdapter; if (sentenceLevelAdapter == null) { - final String localeStr = getLocale(); - if (!TextUtils.isEmpty(localeStr)) { - sentenceLevelAdapter = new SentenceLevelAdapter(mResources, - new Locale(localeStr)); + final String localeString = getLocale(); + if (!TextUtils.isEmpty(localeString)) { + sentenceLevelAdapter = new SentenceLevelAdapter(mResources, LocaleUtils.constructLocale(localeString)); mSentenceLevelAdapter = sentenceLevelAdapter; } } diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java index 0fb2ecc37..3f473fe2f 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java @@ -50,7 +50,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { // Immutable, but not available in the constructor. private Locale mLocale; // Cache this for performance - private int mScript; // One of SCRIPT_LATIN or SCRIPT_CYRILLIC for now. + private String mScript; private final AndroidSpellCheckerService mService; protected final SuggestionsCache mSuggestionsCache = new SuggestionsCache(); private final ContentObserver mObserver; @@ -58,7 +58,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { private static final String quotesRegexp = "(\\u0022|\\u0027|\\u0060|\\u00B4|\\u2018|\\u2018|\\u201C|\\u201D)"; - private static final Map scriptToPunctuationRegexMap = new TreeMap<>(); + private static final Map scriptToPunctuationRegexMap = new TreeMap<>(); static { // TODO: add other non-English language specific punctuation later. @@ -107,27 +107,26 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { AndroidWordLevelSpellCheckerSession(final AndroidSpellCheckerService service) { mService = service; - final ContentResolver cres = service.getContentResolver(); - mObserver = new ContentObserver(null) { @Override public void onChange(boolean self) { mSuggestionsCache.clearCache(); } }; - cres.registerContentObserver(Words.CONTENT_URI, true, mObserver); + service.getContentResolver().registerContentObserver(Words.CONTENT_URI, true, mObserver); } private void updateLocale() { final String localeString = getLocale(); if (mLocale == null || !mLocale.toString().equals(localeString)) { - final String oldLocal = mLocale == null ? "null" : mLocale.toString(); - Log.d(TAG, "Updating locale from " + oldLocal + " to " + localeString); + final String oldLocale = mLocale == null ? "null" : mLocale.toString(); + Log.d(TAG, "Updating locale from " + oldLocale + " to " + localeString); mLocale = (null == localeString) ? null - : LocaleUtils.constructLocaleFromString(localeString); - mScript = ScriptUtils.getScriptFromSpellCheckerLocale(mLocale); + : LocaleUtils.constructLocale(localeString); + if (mLocale == null) mScript = ScriptUtils.SCRIPT_UNKNOWN; + else mScript = ScriptUtils.script(mLocale); } } @@ -137,7 +136,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { } @Override - public String getLocale() { + public String getLocale() { // unfortunately this can only return a string, with the obvious issues for // This function was taken from https://github.com/LineageOS/android_frameworks_base/blob/1235c24a0f092d0e41fd8e86f332f8dc03896a7b/services/core/java/com/android/server/TextServicesManagerService.java#L544 and slightly adopted. final InputMethodManager imm; @@ -179,7 +178,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { private static final int CHECKABILITY_TOO_SHORT = 5; /** * Finds out whether a particular string should be filtered out of spell checking. - * + *

* This will loosely match URLs, numbers, symbols. To avoid always underlining words that * we know we will never recognize, this accepts a script identifier that should be one * of the SCRIPT_* constants defined above, to rule out quickly characters from very @@ -189,7 +188,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { * @param script the identifier for the script this spell checker recognizes * @return one of the FILTER_OUT_* constants above. */ - private static int getCheckabilityInScript(final String text, final int script) { + private static int getCheckabilityInScript(final String text, final String script) { if (TextUtils.isEmpty(text) || text.length() <= 1) return CHECKABILITY_TOO_SHORT; // TODO: check if an equivalent processing can't be done more quickly with a @@ -228,7 +227,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { /** * Helper method to test valid capitalizations of a word. - * + *

* If the "text" is lower-case, we test only the exact string. * If the "Text" is capitalized, we test the exact string "Text" and the lower-cased * version of it "text". @@ -276,9 +275,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { .replaceAll("^" + quotesRegexp, "") .replaceAll(quotesRegexp + "$", ""); - final String localeRegex = scriptToPunctuationRegexMap.get( - ScriptUtils.getScriptFromSpellCheckerLocale(mLocale) - ); + final String localeRegex = scriptToPunctuationRegexMap.get(ScriptUtils.script(mLocale)); if (localeRegex != null) { text = text.replaceAll(localeRegex, ""); @@ -332,8 +329,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { if (null == keyboard) { Log.w(TAG, "onGetSuggestionsInternal() : No keyboard for locale: " + mLocale); // If there is no keyboard for this locale, don't do any spell-checking. - return AndroidSpellCheckerService.getNotInDictEmptySuggestions( - false /* reportAsTypo */); + return AndroidSpellCheckerService.getNotInDictEmptySuggestions(false); } final WordComposer composer = new WordComposer(); diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/AdditionalSubtypeUtils.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/AdditionalSubtypeUtils.java index 9cde8b1c6..60477acbc 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/AdditionalSubtypeUtils.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/AdditionalSubtypeUtils.java @@ -17,6 +17,7 @@ import org.dslul.openboard.inputmethod.latin.common.StringUtils; import java.util.ArrayList; import java.util.Arrays; +import java.util.Locale; import static org.dslul.openboard.inputmethod.latin.common.Constants.Subtype.ExtraValue.ASCII_CAPABLE; import static org.dslul.openboard.inputmethod.latin.common.Constants.Subtype.ExtraValue.EMOJI_CAPABLE; @@ -40,7 +41,7 @@ public final class AdditionalSubtypeUtils { } private static final String LOCALE_AND_LAYOUT_SEPARATOR = ":"; - private static final int INDEX_OF_LOCALE = 0; + private static final int INDEX_OF_LANGUAGE_TAG = 0; private static final int INDEX_OF_KEYBOARD_LAYOUT = 1; private static final int INDEX_OF_EXTRA_VALUE = 2; private static final int LENGTH_WITHOUT_EXTRA_VALUE = (INDEX_OF_KEYBOARD_LAYOUT + 1); @@ -48,17 +49,17 @@ public final class AdditionalSubtypeUtils { private static final String PREF_SUBTYPE_SEPARATOR = ";"; private static InputMethodSubtype createAdditionalSubtypeInternal( - final String localeString, final String keyboardLayoutSetName, + final Locale locale, final String keyboardLayoutSetName, final boolean isAsciiCapable, final boolean isEmojiCapable) { - final int nameId = SubtypeLocaleUtils.getSubtypeNameId(localeString, keyboardLayoutSetName); + final int nameId = SubtypeLocaleUtils.getSubtypeNameId(locale, keyboardLayoutSetName); final String platformVersionDependentExtraValues = getPlatformVersionDependentExtraValue( - localeString, keyboardLayoutSetName, isAsciiCapable, isEmojiCapable); + locale, keyboardLayoutSetName, isAsciiCapable, isEmojiCapable); final int platformVersionIndependentSubtypeId = - getPlatformVersionIndependentSubtypeId(localeString, keyboardLayoutSetName); + getPlatformVersionIndependentSubtypeId(locale, keyboardLayoutSetName); final InputMethodSubtype.InputMethodSubtypeBuilder builder = new InputMethodSubtype.InputMethodSubtypeBuilder() .setSubtypeNameResId(nameId) .setSubtypeIconResId(R.drawable.ic_ime_switcher) - .setSubtypeLocale(localeString) + .setSubtypeLocale(locale.toString()) .setSubtypeMode(KEYBOARD_MODE) .setSubtypeExtraValue(platformVersionDependentExtraValues) .setIsAuxiliary(false) @@ -66,28 +67,27 @@ public final class AdditionalSubtypeUtils { .setSubtypeId(platformVersionIndependentSubtypeId) .setIsAsciiCapable(isAsciiCapable); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - builder.setLanguageTag(LocaleUtils.constructLocaleFromString(localeString).toLanguageTag()); + builder.setLanguageTag(locale.toLanguageTag()); return builder.build(); } public static InputMethodSubtype createDummyAdditionalSubtype( - final String localeString, final String keyboardLayoutSetName) { - return createAdditionalSubtypeInternal(localeString, keyboardLayoutSetName, false, false); + final Locale locale, final String keyboardLayoutSetName) { + return createAdditionalSubtypeInternal(locale, keyboardLayoutSetName, false, false); } public static InputMethodSubtype createEmojiCapableAdditionalSubtype( - final String localeString, final String keyboardLayoutSetName, final boolean asciiCapable) { - return createAdditionalSubtypeInternal(localeString, keyboardLayoutSetName, asciiCapable, true); + final Locale locale, final String keyboardLayoutSetName, final boolean asciiCapable) { + return createAdditionalSubtypeInternal(locale, keyboardLayoutSetName, asciiCapable, true); } - public static String getPrefSubtype(final InputMethodSubtype subtype) { - final String localeString = subtype.getLocale(); + private static String getPrefSubtype(final InputMethodSubtype subtype) { final String keyboardLayoutSetName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName; final String extraValue = StringUtils.removeFromCommaSplittableTextIfExists( layoutExtraValue, StringUtils.removeFromCommaSplittableTextIfExists( IS_ADDITIONAL_SUBTYPE, subtype.getExtraValue())); - final String basePrefSubtype = localeString + LOCALE_AND_LAYOUT_SEPARATOR + final String basePrefSubtype = SubtypeUtilsKt.locale(subtype).toLanguageTag() + LOCALE_AND_LAYOUT_SEPARATOR + keyboardLayoutSetName; return extraValue.isEmpty() ? basePrefSubtype : basePrefSubtype + LOCALE_AND_LAYOUT_SEPARATOR + extraValue; @@ -115,11 +115,12 @@ public final class AdditionalSubtypeUtils { Log.w(TAG, "Unknown additional subtype specified: " + prefSubtype); return null; } - final String localeString = elems[INDEX_OF_LOCALE]; + final String languageTag = elems[INDEX_OF_LANGUAGE_TAG]; + final Locale locale = LocaleUtils.constructLocale(languageTag); final String keyboardLayoutSetName = elems[INDEX_OF_KEYBOARD_LAYOUT]; - final boolean asciiCapable = ScriptUtils.getScriptFromSpellCheckerLocale(LocaleUtils.constructLocaleFromString(localeString)) == ScriptUtils.SCRIPT_LATIN; + final boolean asciiCapable = ScriptUtils.script(locale).equals(ScriptUtils.SCRIPT_LATIN); // Here we assume that all the additional subtypes are EmojiCapable - final InputMethodSubtype subtype = createEmojiCapableAdditionalSubtype(localeString, keyboardLayoutSetName, asciiCapable); + final InputMethodSubtype subtype = createEmojiCapableAdditionalSubtype(locale, keyboardLayoutSetName, asciiCapable); if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT && !keyboardLayoutSetName.startsWith(CustomLayoutUtilsKt.CUSTOM_LAYOUT_PREFIX)) { // Skip unknown keyboard layout subtype. This may happen when predefined keyboard // layout has been removed. @@ -164,14 +165,13 @@ public final class AdditionalSubtypeUtils { * assume that the extra values stored in a persistent storage are always valid. We need to * regenerate the extra value on the fly instead. *

- * @param localeString the locale string (e.g., "en_US"). * @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak"). * @param isAsciiCapable true when ASCII characters are supported with this layout. * @param isEmojiCapable true when Unicode Emoji characters are supported with this layout. * @return extra value that is optimized for the running OS. - * @see #getPlatformVersionIndependentSubtypeId(String, String) + * @see #getPlatformVersionIndependentSubtypeId(Locale, String) */ - private static String getPlatformVersionDependentExtraValue(final String localeString, + private static String getPlatformVersionDependentExtraValue(final Locale locale, final String keyboardLayoutSetName, final boolean isAsciiCapable, final boolean isEmojiCapable) { final ArrayList extraValueItems = new ArrayList<>(); @@ -179,7 +179,7 @@ public final class AdditionalSubtypeUtils { if (isAsciiCapable) { extraValueItems.add(ASCII_CAPABLE); } - if (SubtypeLocaleUtils.isExceptionalLocale(localeString)) { + if (SubtypeLocaleUtils.isExceptionalLocale(locale)) { extraValueItems.add(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(keyboardLayoutSetName)); } @@ -202,12 +202,11 @@ public final class AdditionalSubtypeUtils { * method even when we need to add some new extra values for the actual instance of * {@link InputMethodSubtype}. *

- * @param localeString the locale string (e.g., "en_US"). * @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak"). * @return a platform-version independent subtype ID. - * @see #getPlatformVersionDependentExtraValue(String, String, boolean, boolean) + * @see #getPlatformVersionDependentExtraValue(Locale, String, boolean, boolean) */ - private static int getPlatformVersionIndependentSubtypeId(final String localeString, + private static int getPlatformVersionIndependentSubtypeId(final Locale locale, final String keyboardLayoutSetName) { // For compatibility reasons, we concatenate the extra values in the following order. // - KeyboardLayoutSet @@ -218,7 +217,7 @@ public final class AdditionalSubtypeUtils { final ArrayList compatibilityExtraValueItems = new ArrayList<>(); compatibilityExtraValueItems.add(KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName); compatibilityExtraValueItems.add(ASCII_CAPABLE); - if (SubtypeLocaleUtils.isExceptionalLocale(localeString)) { + if (SubtypeLocaleUtils.isExceptionalLocale(locale)) { compatibilityExtraValueItems.add(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(keyboardLayoutSetName)); } @@ -226,7 +225,7 @@ public final class AdditionalSubtypeUtils { compatibilityExtraValueItems.add(IS_ADDITIONAL_SUBTYPE); final String compatibilityExtraValues = TextUtils.join(",", compatibilityExtraValueItems); return Arrays.hashCode(new Object[] { - localeString, + locale, KEYBOARD_MODE, compatibilityExtraValues, false /* isAuxiliary */, diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/CustomLayoutUtils.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/CustomLayoutUtils.kt index 00b30a13d..b01c05247 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/CustomLayoutUtils.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/CustomLayoutUtils.kt @@ -22,7 +22,7 @@ import java.io.File import java.io.IOException import java.math.BigInteger -fun loadCustomLayout(uri: Uri?, localeString: String, context: Context, onAdded: (String) -> Unit) { +fun loadCustomLayout(uri: Uri?, languageTag: String, context: Context, onAdded: (String) -> Unit) { if (uri == null) return infoDialog(context, context.getString(R.string.layout_error, "layout file not found")) val layoutContent: String @@ -41,10 +41,10 @@ fun loadCustomLayout(uri: Uri?, localeString: String, context: Context, onAdded: name = it.getString(idx).substringBeforeLast(".") } } - loadCustomLayout(layoutContent, name, localeString, context, onAdded) + loadCustomLayout(layoutContent, name, languageTag, context, onAdded) } -fun loadCustomLayout(layoutContent: String, layoutName: String, localeString: String, context: Context, onAdded: (String) -> Unit) { +fun loadCustomLayout(layoutContent: String, layoutName: String, languageTag: String, context: Context, onAdded: (String) -> Unit) { var name = layoutName val isJson = checkLayout(layoutContent, context) ?: return infoDialog(context, context.getString(R.string.layout_error, "invalid layout file, ${Log.getLog(10).lastOrNull { it.tag == TAG }?.message}")) @@ -60,7 +60,7 @@ fun loadCustomLayout(layoutContent: String, layoutName: String, localeString: St }) .setPositiveButton(android.R.string.ok) { _, _ -> // name must be encoded to avoid issues with validity of subtype extra string or file name - name = "$CUSTOM_LAYOUT_PREFIX${localeString}.${encodeBase36(name)}.${if (isJson) "json" else "txt"}" + name = "$CUSTOM_LAYOUT_PREFIX${languageTag}.${encodeBase36(name)}.${if (isJson) "json" else "txt"}" val file = getFile(name, context) if (file.exists()) file.delete() diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryInfoUtils.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryInfoUtils.java index aa38ac3a0..5a9e43794 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryInfoUtils.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryInfoUtils.java @@ -15,7 +15,7 @@ import androidx.annotation.Nullable; import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; import org.dslul.openboard.inputmethod.annotations.UsedForTesting; -import org.dslul.openboard.inputmethod.latin.BinaryDictionaryGetter; +import org.dslul.openboard.inputmethod.latin.Dictionary; import org.dslul.openboard.inputmethod.latin.define.DecoderSpecificConstants; import org.dslul.openboard.inputmethod.latin.makedict.DictionaryHeader; import org.dslul.openboard.inputmethod.latin.makedict.UnsupportedFormatException; @@ -32,9 +32,11 @@ public class DictionaryInfoUtils { private static final String TAG = DictionaryInfoUtils.class.getSimpleName(); public static final String DEFAULT_MAIN_DICT = "main"; public static final String MAIN_DICT_PREFIX = DEFAULT_MAIN_DICT + "_"; - private static final String DICTIONARY_CATEGORY_SEPARATOR_EXPRESSION = "[" + BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + "_]"; // 6 digits - unicode is limited to 21 bits private static final int MAX_HEX_DIGITS_FOR_CODEPOINT = 6; + public static final String ASSETS_DICTIONARY_FOLDER = "dicts"; + public static final String ID_CATEGORY_SEPARATOR = ":"; + private static final String DICTIONARY_CATEGORY_SEPARATOR_EXPRESSION = "[" + ID_CATEGORY_SEPARATOR + "_]"; private DictionaryInfoUtils() { // Private constructor to forbid instantation of this helper class. @@ -49,7 +51,7 @@ public class DictionaryInfoUtils { if (codePoint >= 0x30 && codePoint <= 0x39) return true; // Digit if (codePoint >= 0x41 && codePoint <= 0x5A) return true; // Uppercase if (codePoint >= 0x61 && codePoint <= 0x7A) return true; // Lowercase - return codePoint == '_'; // Underscore + return codePoint == '_' || codePoint == '-'; } /** @@ -83,13 +85,6 @@ public class DictionaryInfoUtils { return context.getFilesDir() + File.separator + "dicts"; } - /** - * Helper method to get the top level temp directory. - */ - public static String getWordListTempDirectory(final Context context) { - return context.getFilesDir() + File.separator + "tmp"; - } - /** * Reverse escaping done by {@link #replaceFileNameDangerousCharacters(String)}. */ @@ -119,32 +114,11 @@ public class DictionaryInfoUtils { return new File(DictionaryInfoUtils.getWordListCacheDirectory(context)).listFiles(); } - /** - * Returns the category for a given file name. - *

- * This parses the file name, extracts the category, and returns it. See - * {@link #getMainDictId(Locale)} and {@link #isMainWordListId(String)}. - * @return The category as a string or null if it can't be found in the file name. - */ - @Nullable - public static String getCategoryFromFileName(@NonNull final String fileName) { - final String id = getWordListIdFromFileName(fileName); - final String[] idArray = id.split(DICTIONARY_CATEGORY_SEPARATOR_EXPRESSION); - // An id is supposed to be in format category:locale, so splitting on the separator - // should yield a 2-elements array - // Also allow '_' as separator, this is ok for locales like pt_br because - // we're interested in the part before first separator anyway - if (1 == idArray.length) { - return null; - } - return idArray[0]; - } - /** * Find out the cache directory associated with a specific locale. */ - public static String getCacheDirectoryForLocale(final String locale, final Context context) { - final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale).toLowerCase(Locale.ENGLISH); + public static String getCacheDirectoryForLocale(final Locale locale, final Context context) { + final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale.toLanguageTag()); final String absoluteDirectoryName = getWordListCacheDirectory(context) + File.separator + relativeDirectoryName; final File directory = new File(absoluteDirectoryName); if (!directory.exists()) { @@ -155,16 +129,11 @@ public class DictionaryInfoUtils { return absoluteDirectoryName; } - public static boolean isMainWordListId(final String id) { - final String[] idArray = id.split(DICTIONARY_CATEGORY_SEPARATOR_EXPRESSION); - // An id is supposed to be in format category:locale, so splitting on the separator - // should yield a 2-elements array - // Also allow '_' as separator, this is ok for locales like pt_br because - // we're interested in the part before first separator anyway - if (1 == idArray.length) { - return false; - } - return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY.equals(idArray[0]); + public static File[] getCachedDictsForLocale(final Locale locale, final Context context) { + final File cachedDir = new File(getCacheDirectoryForLocale(locale, context)); + if (!cachedDir.isDirectory()) + return new File[]{}; + return cachedDir.listFiles(); } /** @@ -179,17 +148,11 @@ public class DictionaryInfoUtils { // This works because we don't include by default different dictionaries for // different countries. This actually needs to return the id that we would // like to use for word lists included in resources, and the following is okay. - return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY + - BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale.toString().toLowerCase(); + return Dictionary.TYPE_MAIN + ID_CATEGORY_SEPARATOR + locale.toString().toLowerCase(); } - public static String getMainDictFilename(@NonNull final String locale) { - return MAIN_DICT_PREFIX + locale.toLowerCase(Locale.ENGLISH) + ".dict"; - } - - public static File getMainDictFile(@NonNull final String locale, @NonNull final Context context) { - return new File(DictionaryInfoUtils.getCacheDirectoryForLocale(locale, context) + - File.separator + DictionaryInfoUtils.getMainDictFilename(locale)); + public static String getExtractedMainDictFilename() { + return DEFAULT_MAIN_DICT + ".dict"; } @Nullable @@ -202,6 +165,43 @@ public class DictionaryInfoUtils { } } + @Nullable + public static DictionaryHeader getDictionaryFileHeaderOrNull(final File file) { + try { + return BinaryDictionaryUtils.getHeader(file); + } catch (UnsupportedFormatException | IOException e) { + return null; + } + } + + /** + * Returns the locale for a dictionary file name stored in assets. + *

+ * Assumes file name main_[locale].dict + *

+ * Returns the locale, or null if file name does not match the pattern + */ + @Nullable public static String extractLocaleFromAssetsDictionaryFile(final String dictionaryFileName) { + if (dictionaryFileName.startsWith(DictionaryInfoUtils.MAIN_DICT_PREFIX) + && dictionaryFileName.endsWith(".dict")) { + return dictionaryFileName.substring( + DictionaryInfoUtils.MAIN_DICT_PREFIX.length(), + dictionaryFileName.lastIndexOf('.') + ); + } + return null; + } + + @Nullable public static String[] getAssetsDictionaryList(final Context context) { + final String[] dictionaryList; + try { + dictionaryList = context.getAssets().list(ASSETS_DICTIONARY_FOLDER); + } catch (IOException e) { + return null; + } + return dictionaryList; + } + @UsedForTesting public static boolean looksValidForDictionaryInsertion(final CharSequence text, final SpacingAndPunctuations spacingAndPunctuations) { diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryUtils.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryUtils.kt index 60a1c3fe1..320a551d2 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryUtils.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryUtils.kt @@ -8,8 +8,8 @@ import android.view.View import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.core.content.edit -import org.dslul.openboard.inputmethod.latin.BinaryDictionaryGetter import org.dslul.openboard.inputmethod.latin.R +import org.dslul.openboard.inputmethod.latin.common.LocaleUtils.constructLocale import org.dslul.openboard.inputmethod.latin.settings.Settings import java.io.File import java.util.* @@ -24,19 +24,15 @@ fun getDictionaryLocales(context: Context): MutableSet { for (directory in cachedDirectoryList) { if (!directory.isDirectory) continue if (!hasAnythingOtherThanExtractedMainDictionary(directory)) continue - val dirLocale = DictionaryInfoUtils.getWordListIdFromFileName(directory.name) - val locale = dirLocale.toLocale() + val locale = DictionaryInfoUtils.getWordListIdFromFileName(directory.name).constructLocale() locales.add(locale) } } // get assets dictionaries - val assetsDictionaryList = BinaryDictionaryGetter.getAssetsDictionaryList(context) + val assetsDictionaryList = DictionaryInfoUtils.getAssetsDictionaryList(context) if (assetsDictionaryList != null) { for (dictionary in assetsDictionaryList) { - val dictLocale = - BinaryDictionaryGetter.extractLocaleFromAssetsDictionaryFile(dictionary) - ?: continue - val locale = dictLocale.toLocale() + val locale = DictionaryInfoUtils.extractLocaleFromAssetsDictionaryFile(dictionary)?.constructLocale() ?: continue locales.add(locale) } } @@ -73,15 +69,15 @@ fun cleanUnusedMainDicts(context: Context) { val dictionaryDir = File(DictionaryInfoUtils.getWordListCacheDirectory(context)) val dirs = dictionaryDir.listFiles() ?: return val prefs = DeviceProtectedUtils.getSharedPreferences(context) - val usedLocales = hashSetOf() + val usedLocaleLanguageTags = hashSetOf() getEnabledSubtypes(prefs).forEach { val locale = it.locale() - usedLocales.add(locale) - Settings.getSecondaryLocales(prefs, locale).forEach { usedLocales.add(it.toString().lowercase()) } + usedLocaleLanguageTags.add(locale.toLanguageTag()) + Settings.getSecondaryLocales(prefs, locale).forEach { usedLocaleLanguageTags.add(it.toLanguageTag()) } } for (dir in dirs) { if (!dir.isDirectory) continue - if (dir.name in usedLocales) continue + if (dir.name in usedLocaleLanguageTags) continue if (hasAnythingOtherThanExtractedMainDictionary(dir)) continue dir.deleteRecursively() @@ -89,6 +85,6 @@ fun cleanUnusedMainDicts(context: Context) { } private fun hasAnythingOtherThanExtractedMainDictionary(dir: File) = - dir.listFiles()?.any { it.name != DictionaryInfoUtils.getMainDictFilename(dir.name) } != false + dir.listFiles()?.any { it.name != DictionaryInfoUtils.getExtractedMainDictFilename() } != false const val DICTIONARY_URL = "https://codeberg.org/Helium314/aosp-dictionaries" diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/LanguageOnSpacebarUtils.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/LanguageOnSpacebarUtils.java index 2e3661d39..0bde8ff9f 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/LanguageOnSpacebarUtils.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/LanguageOnSpacebarUtils.java @@ -49,7 +49,7 @@ public final class LanguageOnSpacebarUtils { final String keyboardLayout = subtype.getKeyboardLayoutSetName(); int sameLanguageAndLayoutCount = 0; for (final InputMethodSubtype ims : sEnabledSubtypes) { - final String language = SubtypeLocaleUtils.getSubtypeLocale(ims).getLanguage(); + final String language = SubtypeUtilsKt.locale(ims).getLanguage(); if (keyboardLanguage.equals(language) && keyboardLayout.equals( SubtypeLocaleUtils.getKeyboardLayoutSetName(ims))) { sameLanguageAndLayoutCount++; diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/NewDictionaryAdder.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/NewDictionaryAdder.kt index 0db2f1607..8d295ca88 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/NewDictionaryAdder.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/NewDictionaryAdder.kt @@ -6,13 +6,17 @@ import android.content.Context import android.content.Intent import android.net.Uri import androidx.appcompat.app.AlertDialog +import org.dslul.openboard.inputmethod.compat.locale import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants +import org.dslul.openboard.inputmethod.latin.Dictionary import org.dslul.openboard.inputmethod.latin.R import org.dslul.openboard.inputmethod.latin.ReadOnlyBinaryDictionary import org.dslul.openboard.inputmethod.latin.common.FileUtils import org.dslul.openboard.inputmethod.latin.common.LocaleUtils +import org.dslul.openboard.inputmethod.latin.common.LocaleUtils.constructLocale import org.dslul.openboard.inputmethod.latin.makedict.DictionaryHeader import org.dslul.openboard.inputmethod.latin.settings.* +import org.dslul.openboard.inputmethod.latin.utils.ScriptUtils.script import java.io.File import java.io.IOException import java.util.* @@ -34,7 +38,7 @@ class NewDictionaryAdder(private val context: Context, private val onAdded: ((Bo val newHeader = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(cachedDictionaryFile, 0, cachedDictionaryFile.length()) ?: return onDictionaryLoadingError(R.string.dictionary_file_error) - val locale = newHeader.mLocaleString.toLocale() + val locale = newHeader.mLocaleString.constructLocale() val dict = ReadOnlyBinaryDictionary(cachedDictionaryFile.absolutePath, 0, cachedDictionaryFile.length(), false, locale, "test") if (!dict.isValidDictionary) { @@ -53,7 +57,7 @@ class NewDictionaryAdder(private val context: Context, private val onAdded: ((Bo .setMessage(message) .setNeutralButton(android.R.string.cancel) { _, _ -> cachedDictionaryFile.delete() } .setNegativeButton(R.string.button_select_language) { _, _ -> selectLocaleForDictionary(newHeader, locale) } - if (hasMatchingSubtypeForLocaleString(locale.toString())) { + if (hasMatchingSubtypeForLocale(locale)) { val buttonText = context.getString(R.string.button_add_to_language, localeName) b.setPositiveButton(buttonText) { _, _ -> addDictAndAskToReplace(newHeader, locale) @@ -65,7 +69,7 @@ class NewDictionaryAdder(private val context: Context, private val onAdded: ((Bo // ScriptUtils.getScriptFromSpellCheckerLocale may return latin when it should not, // e.g. for Persian or Chinese. But at least fail when dictionary certainly is incompatible - if (ScriptUtils.getScriptFromSpellCheckerLocale(locale) != ScriptUtils.getScriptFromSpellCheckerLocale(mainLocale)) + if (locale.script() != mainLocale.script()) return onDictionaryLoadingError(R.string.dictionary_file_wrong_script) if (locale != mainLocale) { @@ -87,13 +91,7 @@ class NewDictionaryAdder(private val context: Context, private val onAdded: ((Bo } private fun selectLocaleForDictionary(newHeader: DictionaryHeader, dictLocale: Locale) { - val localeStrings = getAvailableSubtypeLocaleStrings() - val locales = linkedSetOf() - localeStrings.forEach { - if (it.substringBefore("_") == dictLocale.language) - locales.add(it.toLocale()) - } - localeStrings.forEach { locales.add(it.toLocale()) } + val locales = getAvailableSubtypeLocales().sortedBy { it.language != dictLocale.language } // matching languages should show first val displayNamesArray = locales.map { LocaleUtils.getLocaleDisplayNameInSystemLocale(it, context) }.toTypedArray() AlertDialog.Builder(context) .setTitle(R.string.button_select_language) @@ -110,20 +108,17 @@ class NewDictionaryAdder(private val context: Context, private val onAdded: ((Bo private fun addDictAndAskToReplace(header: DictionaryHeader, mainLocale: Locale) { val dictionaryType = header.mIdString.substringBefore(":") - val dictFilename = DictionaryInfoUtils.getCacheDirectoryForLocale(mainLocale.toString(), context) + - File.separator + dictionaryType + "_" + USER_DICTIONARY_SUFFIX - val dictFile = File(dictFilename) + val cacheDir = DictionaryInfoUtils.getCacheDirectoryForLocale(mainLocale, context) + val dictFile = File(cacheDir, dictionaryType + "_" + USER_DICTIONARY_SUFFIX) fun moveDict(replaced: Boolean) { if (!cachedDictionaryFile.renameTo(dictFile)) { return onDictionaryLoadingError(R.string.dictionary_load_error) } - if (dictionaryType == DictionaryInfoUtils.DEFAULT_MAIN_DICT) { + if (dictionaryType == Dictionary.TYPE_MAIN) { // replaced main dict, remove the one created from internal data - // todo: currently not, see also BinaryDictionaryGetter.getDictionaryFiles -// val internalMainDictFilename = DictionaryInfoUtils.getCacheDirectoryForLocale(mainLocaleString, context) + -// File.separator + DictionaryInfoUtils.getMainDictFilename(mainLocaleString) -// File(internalMainDictFilename).delete() + val internalMainDictFile = File(cacheDir, DictionaryInfoUtils.getExtractedMainDictFilename()) + internalMainDictFile.delete() } val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION) context.sendBroadcast(newDictBroadcast) @@ -134,7 +129,7 @@ class NewDictionaryAdder(private val context: Context, private val onAdded: ((Bo return moveDict(false) } - val systemLocale = context.resources.configuration.locale + val systemLocale = context.resources.configuration.locale() val newInfo = header.info(systemLocale) val oldInfo = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(dictFile, 0, dictFile.length())?.info(systemLocale) confirmDialog(context, context.getString(R.string.replace_dictionary_message, dictionaryType, newInfo, oldInfo), context.getString( @@ -148,5 +143,3 @@ class NewDictionaryAdder(private val context: Context, private val onAdded: ((Bo infoDialog(context, messageId) } } - -fun String.toLocale() = LocaleUtils.constructLocaleFromString(this) diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/ScriptUtils.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/ScriptUtils.java deleted file mode 100644 index 33eca1add..000000000 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/ScriptUtils.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * modified - * SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only - */ - -package org.dslul.openboard.inputmethod.latin.utils; - - -import androidx.collection.ArraySet; - -import java.util.Locale; -import java.util.TreeMap; - -/** - * A class to help with handling different writing scripts. - */ -public class ScriptUtils { - - // Used for hardware keyboards - public static final int SCRIPT_UNKNOWN = -1; - - // These should be aligned with KeyboardLayoutSet_Feature values in attrs.xml. - public static final int SCRIPT_ARABIC = 0; - public static final int SCRIPT_ARMENIAN = 1; - public static final int SCRIPT_BENGALI = 2; - public static final int SCRIPT_CYRILLIC = 3; - public static final int SCRIPT_DEVANAGARI = 4; - public static final int SCRIPT_GEORGIAN = 5; - public static final int SCRIPT_GREEK = 6; - public static final int SCRIPT_HEBREW = 7; - public static final int SCRIPT_KANNADA = 8; - public static final int SCRIPT_KHMER = 9; - public static final int SCRIPT_LAO = 10; - public static final int SCRIPT_LATIN = 11; - public static final int SCRIPT_MALAYALAM = 12; - public static final int SCRIPT_MYANMAR = 13; - public static final int SCRIPT_SINHALA = 14; - public static final int SCRIPT_TAMIL = 15; - public static final int SCRIPT_TELUGU = 16; - public static final int SCRIPT_THAI = 17; - public static final int SCRIPT_BULGARIAN = 18; // todo: why is bulgarian a separate script? - public static final int SCRIPT_HANGUL = 19; - public static final int SCRIPT_GUJARATI = 20; - - private static final TreeMap mLanguageCodeToScriptCode; - private final static ArraySet UPPERCASE_SCRIPTS = new ArraySet<>(); - - - static { - mLanguageCodeToScriptCode = new TreeMap<>(); - mLanguageCodeToScriptCode.put("", SCRIPT_LATIN); // default - mLanguageCodeToScriptCode.put("ar", SCRIPT_ARABIC); - mLanguageCodeToScriptCode.put("ur", SCRIPT_ARABIC); - mLanguageCodeToScriptCode.put("fa", SCRIPT_ARABIC); - mLanguageCodeToScriptCode.put("hy", SCRIPT_ARMENIAN); - mLanguageCodeToScriptCode.put("bg", SCRIPT_BULGARIAN); - mLanguageCodeToScriptCode.put("bn", SCRIPT_BENGALI); - mLanguageCodeToScriptCode.put("sr", SCRIPT_CYRILLIC); - mLanguageCodeToScriptCode.put("mk", SCRIPT_CYRILLIC); - mLanguageCodeToScriptCode.put("ru", SCRIPT_CYRILLIC); - mLanguageCodeToScriptCode.put("ka", SCRIPT_GEORGIAN); - mLanguageCodeToScriptCode.put("el", SCRIPT_GREEK); - mLanguageCodeToScriptCode.put("iw", SCRIPT_HEBREW); - mLanguageCodeToScriptCode.put("km", SCRIPT_KHMER); - mLanguageCodeToScriptCode.put("lo", SCRIPT_LAO); - mLanguageCodeToScriptCode.put("ml", SCRIPT_MALAYALAM); - mLanguageCodeToScriptCode.put("my", SCRIPT_MYANMAR); - mLanguageCodeToScriptCode.put("si", SCRIPT_SINHALA); - mLanguageCodeToScriptCode.put("ta", SCRIPT_TAMIL); - mLanguageCodeToScriptCode.put("te", SCRIPT_TELUGU); - mLanguageCodeToScriptCode.put("th", SCRIPT_THAI); - mLanguageCodeToScriptCode.put("uk", SCRIPT_CYRILLIC); - mLanguageCodeToScriptCode.put("ko", SCRIPT_HANGUL); - mLanguageCodeToScriptCode.put("hi", SCRIPT_DEVANAGARI); - mLanguageCodeToScriptCode.put("kn", SCRIPT_KANNADA); - mLanguageCodeToScriptCode.put("mr", SCRIPT_DEVANAGARI); - mLanguageCodeToScriptCode.put("mn", SCRIPT_CYRILLIC); - mLanguageCodeToScriptCode.put("be", SCRIPT_CYRILLIC); - mLanguageCodeToScriptCode.put("kk", SCRIPT_CYRILLIC); - mLanguageCodeToScriptCode.put("ky", SCRIPT_CYRILLIC); - mLanguageCodeToScriptCode.put("ne", SCRIPT_DEVANAGARI); - mLanguageCodeToScriptCode.put("gu", SCRIPT_GUJARATI); - - // only Latin, Cyrillic, Greek and Armenian have upper/lower case - // https://unicode.org/faq/casemap_charprop.html#3 - // adding Bulgarian because internally it uses a separate script value - UPPERCASE_SCRIPTS.add(SCRIPT_LATIN); - UPPERCASE_SCRIPTS.add(SCRIPT_CYRILLIC); - UPPERCASE_SCRIPTS.add(SCRIPT_BULGARIAN); - UPPERCASE_SCRIPTS.add(SCRIPT_GREEK); - UPPERCASE_SCRIPTS.add(SCRIPT_ARMENIAN); - } - - - public static boolean scriptSupportsUppercase(Locale locale) { - return UPPERCASE_SCRIPTS.contains(getScriptFromSpellCheckerLocale(locale)); - } - - /* - * Returns whether the code point is a letter that makes sense for the specified - * locale for this spell checker. - * The dictionaries supported by Latin IME are described in res/xml/spellchecker.xml - * and is limited to EFIGS languages and Russian. - * Hence at the moment this explicitly tests for Cyrillic characters or Latin characters - * as appropriate, and explicitly excludes CJK, Arabic and Hebrew characters. - */ - public static boolean isLetterPartOfScript(final int codePoint, final int scriptId) { - switch (scriptId) { - case SCRIPT_ARABIC: - // Arabic letters can be in any of the following blocks: - // Arabic U+0600..U+06FF - // Arabic Supplement, Thaana U+0750..U+077F, U+0780..U+07BF - // Arabic Extended-A U+08A0..U+08FF - // Arabic Presentation Forms-A U+FB50..U+FDFF - // Arabic Presentation Forms-B U+FE70..U+FEFF - return (codePoint >= 0x600 && codePoint <= 0x6FF) - || (codePoint >= 0x750 && codePoint <= 0x7BF) - || (codePoint >= 0x8A0 && codePoint <= 0x8FF) - || (codePoint >= 0xFB50 && codePoint <= 0xFDFF) - || (codePoint >= 0xFE70 && codePoint <= 0xFEFF); - case SCRIPT_ARMENIAN: - // Armenian letters are in the Armenian unicode block, U+0530..U+058F and - // Alphabetic Presentation Forms block, U+FB00..U+FB4F, but only in the Armenian part - // of that block, which is U+FB13..U+FB17. - return (codePoint >= 0x530 && codePoint <= 0x58F - || codePoint >= 0xFB13 && codePoint <= 0xFB17); - case SCRIPT_BENGALI: - // Bengali unicode block is U+0980..U+09FF - return (codePoint >= 0x980 && codePoint <= 0x9FF); - case SCRIPT_BULGARIAN: - case SCRIPT_CYRILLIC: - // All Cyrillic characters are in the 400~52F block. There are some in the upper - // Unicode range, but they are archaic characters that are not used in modern - // Russian and are not used by our dictionary. - return codePoint >= 0x400 && codePoint <= 0x52F && Character.isLetter(codePoint); - case SCRIPT_DEVANAGARI: - // Devanagari unicode block is +0900..U+097F - return (codePoint >= 0x900 && codePoint <= 0x97F); - case SCRIPT_GEORGIAN: - // Georgian letters are in the Georgian unicode block, U+10A0..U+10FF, - // or Georgian supplement block, U+2D00..U+2D2F - return (codePoint >= 0x10A0 && codePoint <= 0x10FF - || codePoint >= 0x2D00 && codePoint <= 0x2D2F); - case SCRIPT_GREEK: - // Greek letters are either in the 370~3FF range (Greek & Coptic), or in the - // 1F00~1FFF range (Greek extended). Our dictionary contains both sort of characters. - // Our dictionary also contains a few words with 0xF2; it would be best to check - // if that's correct, but a web search does return results for these words so - // they are probably okay. - return (codePoint >= 0x370 && codePoint <= 0x3FF) - || (codePoint >= 0x1F00 && codePoint <= 0x1FFF) - || codePoint == 0xF2; - case SCRIPT_HEBREW: - // Hebrew letters are in the Hebrew unicode block, which spans from U+0590 to U+05FF, - // or in the Alphabetic Presentation Forms block, U+FB00..U+FB4F, but only in the - // Hebrew part of that block, which is U+FB1D..U+FB4F. - return (codePoint >= 0x590 && codePoint <= 0x5FF - || codePoint >= 0xFB1D && codePoint <= 0xFB4F); - case SCRIPT_KANNADA: - // Kannada unicode block is U+0C80..U+0CFF - return (codePoint >= 0xC80 && codePoint <= 0xCFF); - case SCRIPT_KHMER: - // Khmer letters are in unicode block U+1780..U+17FF, and the Khmer symbols block - // is U+19E0..U+19FF - return (codePoint >= 0x1780 && codePoint <= 0x17FF - || codePoint >= 0x19E0 && codePoint <= 0x19FF); - case SCRIPT_LAO: - // The Lao block is U+0E80..U+0EFF - return (codePoint >= 0xE80 && codePoint <= 0xEFF); - case SCRIPT_LATIN: - // Our supported latin script dictionaries (EFIGS) at the moment only include - // characters in the C0, C1, Latin Extended A and B, IPA extensions unicode - // blocks. As it happens, those are back-to-back in the code range 0x40 to 0x2AF, - // so the below is a very efficient way to test for it. As for the 0-0x3F, it's - // excluded from isLetter anyway. - return codePoint <= 0x2AF && Character.isLetter(codePoint); - case SCRIPT_MALAYALAM: - // Malayalam unicode block is U+0D00..U+0D7F - return (codePoint >= 0xD00 && codePoint <= 0xD7F); - case SCRIPT_MYANMAR: - // Myanmar has three unicode blocks : - // Myanmar U+1000..U+109F - // Myanmar extended-A U+AA60..U+AA7F - // Myanmar extended-B U+A9E0..U+A9FF - return (codePoint >= 0x1000 && codePoint <= 0x109F - || codePoint >= 0xAA60 && codePoint <= 0xAA7F - || codePoint >= 0xA9E0 && codePoint <= 0xA9FF); - case SCRIPT_SINHALA: - // Sinhala unicode block is U+0D80..U+0DFF - return (codePoint >= 0xD80 && codePoint <= 0xDFF); - case SCRIPT_TAMIL: - // Tamil unicode block is U+0B80..U+0BFF - return (codePoint >= 0xB80 && codePoint <= 0xBFF); - case SCRIPT_TELUGU: - // Telugu unicode block is U+0C00..U+0C7F - return (codePoint >= 0xC00 && codePoint <= 0xC7F); - case SCRIPT_THAI: - // Thai unicode block is U+0E00..U+0E7F - return (codePoint >= 0xE00 && codePoint <= 0xE7F); - case SCRIPT_HANGUL: - return (codePoint >= 0xAC00 && codePoint <= 0xD7A3 - || codePoint >= 0x3131 && codePoint <= 0x318E - || codePoint >= 0x1100 && codePoint <= 0x11FF - || codePoint >= 0xA960 && codePoint <= 0xA97C - || codePoint >= 0xD7B0 && codePoint <= 0xD7C6 - || codePoint >= 0xD7CB && codePoint <= 0xD7FB); - case SCRIPT_GUJARATI: - // Gujarati unicode block is U+0A80..U+0AFF - return (codePoint >= 0xA80 && codePoint <= 0xAFF); - case SCRIPT_UNKNOWN: - return true; - default: - // Should never come here - throw new RuntimeException("Impossible value of script: " + scriptId); - } - } - - /** - * @param locale spell checker locale - * @return internal Latin IME script code that maps to a language code - * {@see http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes} - */ - public static int getScriptFromSpellCheckerLocale(final Locale locale) { - // need special treatment of serbian latin and hinglish, which would get detected as cyrillic / - if (locale.toString().toLowerCase(Locale.ENGLISH).endsWith("_zz")) - return ScriptUtils.SCRIPT_LATIN; - String language = locale.getLanguage(); - Integer script = mLanguageCodeToScriptCode.get(language); - if (script == null) { - // Default to Latin. - script = mLanguageCodeToScriptCode.get(""); - } - return script; - } -} diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/ScriptUtils.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/ScriptUtils.kt new file mode 100644 index 000000000..1d83d2f58 --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/ScriptUtils.kt @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * modified + * SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only + */ +package org.dslul.openboard.inputmethod.latin.utils + +import java.util.Locale + +/** + * A class to help with handling different writing scripts. + */ +object ScriptUtils { + // Unicode scripts (ISO 15924), incomplete + const val SCRIPT_UNKNOWN = "" // Used for hardware keyboards + const val SCRIPT_ARABIC = "Arab" + const val SCRIPT_ARMENIAN = "Armn" + const val SCRIPT_BENGALI = "Beng" + const val SCRIPT_CYRILLIC = "Cyrl" + const val SCRIPT_DEVANAGARI = "Deva" + const val SCRIPT_GEORGIAN = "Geor" + const val SCRIPT_GREEK = "Grek" + const val SCRIPT_HEBREW = "Hebr" + const val SCRIPT_KANNADA = "Knda" + const val SCRIPT_KHMER = "Khmr" + const val SCRIPT_LAO = "Laoo" + const val SCRIPT_LATIN = "Latn" + const val SCRIPT_MALAYALAM = "Mlym" + const val SCRIPT_MYANMAR = "Mymr" + const val SCRIPT_SINHALA = "Sinh" + const val SCRIPT_TAMIL = "Taml" + const val SCRIPT_TELUGU = "Telu" + const val SCRIPT_THAI = "Thai" + const val SCRIPT_HANGUL = "Hang" + const val SCRIPT_GUJARATI = "Gujr" + + @JvmStatic + fun scriptSupportsUppercase(locale: Locale): Boolean { + // only Latin, Cyrillic, Greek and Armenian have upper/lower case + // https://unicode.org/faq/casemap_charprop.html#3 + return when (locale.script()) { + SCRIPT_LATIN, SCRIPT_CYRILLIC, SCRIPT_GREEK, SCRIPT_ARMENIAN -> true + else -> false + } + } + + /* + * Returns whether the code point is a letter that makes sense for the specified + * locale for this spell checker. + * The dictionaries supported by Latin IME are described in res/xml/spellchecker.xml + * and is limited to EFIGS languages and Russian. + * Hence at the moment this explicitly tests for Cyrillic characters or Latin characters + * as appropriate, and explicitly excludes CJK, Arabic and Hebrew characters. + */ + @JvmStatic + fun isLetterPartOfScript(codePoint: Int, script: String): Boolean { + return when (script) { + SCRIPT_ARABIC -> + // Arabic letters can be in any of the following blocks: + // Arabic U+0600..U+06FF + // Arabic Supplement, Thaana U+0750..U+077F, U+0780..U+07BF + // Arabic Extended-A U+08A0..U+08FF + // Arabic Presentation Forms-A U+FB50..U+FDFF + // Arabic Presentation Forms-B U+FE70..U+FEFF + codePoint in 0x600..0x6FF + || codePoint in 0x750..0x7BF + || codePoint in 0x8A0..0x8FF + || codePoint in 0xFB50..0xFDFF + || codePoint in 0xFE70..0xFEFF + SCRIPT_ARMENIAN -> + // Armenian letters are in the Armenian unicode block, U+0530..U+058F and + // Alphabetic Presentation Forms block, U+FB00..U+FB4F, but only in the Armenian part + // of that block, which is U+FB13..U+FB17. + codePoint in 0x530..0x58F || codePoint in 0xFB13..0xFB17 + SCRIPT_BENGALI -> + // Bengali unicode block is U+0980..U+09FF + codePoint in 0x980..0x9FF + SCRIPT_CYRILLIC -> + // All Cyrillic characters are in the 400~52F block. There are some in the upper + // Unicode range, but they are archaic characters that are not used in modern + // Russian and are not used by our dictionary. + codePoint in 0x400..0x52F && Character.isLetter(codePoint) + SCRIPT_DEVANAGARI -> + // Devanagari unicode block is +0900..U+097F + codePoint in 0x900..0x97F + SCRIPT_GEORGIAN -> + // Georgian letters are in the Georgian unicode block, U+10A0..U+10FF, + // or Georgian supplement block, U+2D00..U+2D2F + codePoint in 0x10A0..0x10FF || codePoint in 0x2D00..0x2D2F + SCRIPT_GREEK -> + // Greek letters are either in the 370~3FF range (Greek & Coptic), or in the + // 1F00~1FFF range (Greek extended). Our dictionary contains both sort of characters. + // Our dictionary also contains a few words with 0xF2; it would be best to check + // if that's correct, but a web search does return results for these words so + // they are probably okay. + codePoint in 0x370..0x3FF || codePoint in 0x1F00..0x1FFF || codePoint == 0xF2 + SCRIPT_HEBREW -> + // Hebrew letters are in the Hebrew unicode block, which spans from U+0590 to U+05FF, + // or in the Alphabetic Presentation Forms block, U+FB00..U+FB4F, but only in the + // Hebrew part of that block, which is U+FB1D..U+FB4F. + codePoint in 0x590..0x5FF || codePoint in 0xFB1D..0xFB4F + SCRIPT_KANNADA -> + // Kannada unicode block is U+0C80..U+0CFF + codePoint in 0xC80..0xCFF + SCRIPT_KHMER -> + // Khmer letters are in unicode block U+1780..U+17FF, and the Khmer symbols block + // is U+19E0..U+19FF + codePoint in 0x1780..0x17FF || codePoint in 0x19E0..0x19FF + SCRIPT_LAO -> + // The Lao block is U+0E80..U+0EFF + codePoint in 0xE80..0xEFF + SCRIPT_LATIN -> + // Our supported latin script dictionaries (EFIGS) at the moment only include + // characters in the C0, C1, Latin Extended A and B, IPA extensions unicode + // blocks. As it happens, those are back-to-back in the code range 0x40 to 0x2AF, + // so the below is a very efficient way to test for it. As for the 0-0x3F, it's + // excluded from isLetter anyway. + codePoint <= 0x2AF && Character.isLetter(codePoint) + SCRIPT_MALAYALAM -> + // Malayalam unicode block is U+0D00..U+0D7F + codePoint in 0xD00..0xD7F + SCRIPT_MYANMAR -> + // Myanmar has three unicode blocks : + // Myanmar U+1000..U+109F + // Myanmar extended-A U+AA60..U+AA7F + // Myanmar extended-B U+A9E0..U+A9FF + codePoint in 0x1000..0x109F || codePoint in 0xAA60..0xAA7F || codePoint in 0xA9E0..0xA9FF + SCRIPT_SINHALA -> + // Sinhala unicode block is U+0D80..U+0DFF + codePoint in 0xD80..0xDFF + SCRIPT_TAMIL -> + // Tamil unicode block is U+0B80..U+0BFF + codePoint in 0xB80..0xBFF + SCRIPT_TELUGU -> + // Telugu unicode block is U+0C00..U+0C7F + codePoint in 0xC00..0xC7F + SCRIPT_THAI -> + // Thai unicode block is U+0E00..U+0E7F + codePoint in 0xE00..0xE7F + SCRIPT_HANGUL -> codePoint in 0xAC00..0xD7A3 + || codePoint in 0x3131..0x318E + || codePoint in 0x1100..0x11FF + || codePoint in 0xA960..0xA97C + || codePoint in 0xD7B0..0xD7C6 + || codePoint in 0xD7CB..0xD7FB + SCRIPT_GUJARATI -> + // Gujarati unicode block is U+0A80..U+0AFF + codePoint in 0xA80..0xAFF + SCRIPT_UNKNOWN -> true + else -> throw RuntimeException("Unknown value of script: $script") + } + } + + /** + * returns the locale script with fallback to default scripts + */ + @JvmStatic + fun Locale.script(): String { + if (script.isNotEmpty()) return script + if (country.equals("ZZ", true)) { + Log.w("ScriptUtils", "old _ZZ locale found: $this") + return SCRIPT_LATIN + } + return when (language) { + "ar", "ur", "fa" -> SCRIPT_ARABIC + "hy" -> SCRIPT_ARMENIAN + "bn" -> SCRIPT_BENGALI + "sr", "mk", "ru", "uk", "mn", "be", "kk", "ky", "bg" -> SCRIPT_CYRILLIC + "ka" -> SCRIPT_GEORGIAN + "el" -> SCRIPT_GREEK + "iw" -> SCRIPT_HEBREW + "km" -> SCRIPT_KHMER + "lo" -> SCRIPT_LAO + "ml" -> SCRIPT_MALAYALAM + "my" -> SCRIPT_MYANMAR + "si" -> SCRIPT_SINHALA + "ta" -> SCRIPT_TAMIL + "te" -> SCRIPT_TELUGU + "th" -> SCRIPT_THAI + "ko" -> SCRIPT_HANGUL + "hi", "mr", "ne" -> SCRIPT_DEVANAGARI + "kn" -> SCRIPT_KANNADA + "gu" -> SCRIPT_GUJARATI + else -> SCRIPT_LATIN // use as fallback + } + } +} diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/SubtypeLocaleUtils.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/SubtypeLocaleUtils.java index d9d3fa1f7..287436059 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/SubtypeLocaleUtils.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/SubtypeLocaleUtils.java @@ -10,6 +10,7 @@ import android.content.Context; import android.content.res.Resources; import android.view.inputmethod.InputMethodSubtype; +import org.dslul.openboard.inputmethod.compat.ConfigurationCompatKt; import org.dslul.openboard.inputmethod.latin.R; import org.dslul.openboard.inputmethod.latin.common.LocaleUtils; import org.dslul.openboard.inputmethod.latin.common.StringUtils; @@ -96,36 +97,37 @@ public final class SubtypeLocaleUtils { } final String[] exceptionalLocaleInRootLocale = res.getStringArray(R.array.subtype_locale_displayed_in_root_locale); - for (final String localeString : exceptionalLocaleInRootLocale) { - final String resourceName = SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX + localeString; + for (final String languageTag : exceptionalLocaleInRootLocale) { + final String resourceName = SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX + languageTag.replace('-', '_'); final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME); - sExceptionalLocaleDisplayedInRootLocale.put(localeString, resId); + sExceptionalLocaleDisplayedInRootLocale.put(languageTag, resId); } final String[] exceptionalLocales = res.getStringArray(R.array.subtype_locale_exception_keys); - for (final String localeString : exceptionalLocales) { - final String resourceName = SUBTYPE_NAME_RESOURCE_PREFIX + localeString; + for (final String languageTag : exceptionalLocales) { + final String resourceName = SUBTYPE_NAME_RESOURCE_PREFIX + languageTag.replace('-', '_'); final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME); - sExceptionalLocaleToNameIdsMap.put(localeString, resId); - final String resourceNameWithLayout = SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX + localeString; + sExceptionalLocaleToNameIdsMap.put(languageTag, resId); + final String resourceNameWithLayout = SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX + languageTag.replace('-', '_'); final int resIdWithLayout = res.getIdentifier(resourceNameWithLayout, null, RESOURCE_PACKAGE_NAME); - sExceptionalLocaleToWithLayoutNameIdsMap.put(localeString, resIdWithLayout); + sExceptionalLocaleToWithLayoutNameIdsMap.put(languageTag, resIdWithLayout); } } - public static boolean isExceptionalLocale(final String localeString) { - return sExceptionalLocaleToNameIdsMap.containsKey(localeString); + public static boolean isExceptionalLocale(final Locale locale) { + return sExceptionalLocaleToNameIdsMap.containsKey(locale.toLanguageTag()); } - private static final String getNoLanguageLayoutKey(final String keyboardLayoutName) { + private static String getNoLanguageLayoutKey(final String keyboardLayoutName) { return NO_LANGUAGE + "_" + keyboardLayoutName; } - public static int getSubtypeNameId(final String localeString, final String keyboardLayoutName) { - if (isExceptionalLocale(localeString)) { - return sExceptionalLocaleToWithLayoutNameIdsMap.get(localeString); + public static int getSubtypeNameId(final Locale locale, final String keyboardLayoutName) { + final String languageTag = locale.toLanguageTag(); + if (isExceptionalLocale(locale)) { + return sExceptionalLocaleToWithLayoutNameIdsMap.get(languageTag); } - final String key = NO_LANGUAGE.equals(localeString) + final String key = NO_LANGUAGE.equals(languageTag) ? getNoLanguageLayoutKey(keyboardLayoutName) : keyboardLayoutName; final Integer nameId = sKeyboardLayoutToNameIdsMap.get(key); @@ -133,53 +135,54 @@ public final class SubtypeLocaleUtils { } @NonNull - public static Locale getDisplayLocaleOfSubtypeLocale(@NonNull final String localeString) { - if (NO_LANGUAGE.equals(localeString)) { - return sResources.getConfiguration().locale; + public static Locale getDisplayLocaleOfSubtypeLocale(@NonNull final Locale locale) { + final String languageTag = locale.toLanguageTag(); + if (NO_LANGUAGE.equals(languageTag)) { + return ConfigurationCompatKt.locale(sResources.getConfiguration()); } - if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) { + if (sExceptionalLocaleDisplayedInRootLocale.containsKey(languageTag)) { return Locale.ROOT; } - return LocaleUtils.constructLocaleFromString(localeString); + return locale; } - public static String getSubtypeLocaleDisplayNameInSystemLocale( - @NonNull final String localeString) { - final Locale displayLocale = sResources.getConfiguration().locale; - return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale); + public static String getSubtypeLocaleDisplayNameInSystemLocale(@NonNull final Locale locale) { + final Locale displayLocale = ConfigurationCompatKt.locale(sResources.getConfiguration()); + return getSubtypeLocaleDisplayNameInternal(locale, displayLocale); } @NonNull - public static String getSubtypeLocaleDisplayName(@NonNull final String localeString) { - final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString); - return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale); + public static String getSubtypeLocaleDisplayName(@NonNull final Locale locale) { + final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(locale); + return getSubtypeLocaleDisplayNameInternal(locale, displayLocale); } @NonNull - public static String getSubtypeLanguageDisplayName(@NonNull final String localeString) { - final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString); - final String languageString; - if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) { - languageString = localeString; + public static String getSubtypeLanguageDisplayName(@NonNull final Locale locale) { + final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(locale); + final Locale languageLocale; + if (sExceptionalLocaleDisplayedInRootLocale.containsKey(locale.toLanguageTag())) { + languageLocale = locale; } else { - languageString = LocaleUtils.constructLocaleFromString(localeString).getLanguage(); + languageLocale = LocaleUtils.constructLocale(locale.getLanguage()); } - return getSubtypeLocaleDisplayNameInternal(languageString, displayLocale); + return getSubtypeLocaleDisplayNameInternal(languageLocale, displayLocale); } @NonNull - private static String getSubtypeLocaleDisplayNameInternal(@NonNull final String localeString, + private static String getSubtypeLocaleDisplayNameInternal(@NonNull final Locale locale, @NonNull final Locale displayLocale) { - if (NO_LANGUAGE.equals(localeString)) { + final String languageTag = locale.toLanguageTag(); + if (NO_LANGUAGE.equals(locale.toLanguageTag())) { // No language subtype should be displayed in system locale. return sResources.getString(R.string.subtype_no_language); } final Integer exceptionalNameResId; if (displayLocale.equals(Locale.ROOT) - && sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) { - exceptionalNameResId = sExceptionalLocaleDisplayedInRootLocale.get(localeString); - } else if (sExceptionalLocaleToNameIdsMap.containsKey(localeString)) { - exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString); + && sExceptionalLocaleDisplayedInRootLocale.containsKey(languageTag)) { + exceptionalNameResId = sExceptionalLocaleDisplayedInRootLocale.get(languageTag); + } else if (sExceptionalLocaleToNameIdsMap.containsKey(languageTag)) { + exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(languageTag); } else { exceptionalNameResId = null; } @@ -188,8 +191,7 @@ public final class SubtypeLocaleUtils { if (exceptionalNameResId != null) { displayName = RunInLocaleKt.runInLocale(sResources, displayLocale, res -> res.getString(exceptionalNameResId)); } else { - displayName = LocaleUtils.constructLocaleFromString(localeString) - .getDisplayName(displayLocale); + displayName = locale.getDisplayName(displayLocale); } return StringUtils.capitalizeFirstCodePoint(displayName, displayLocale); } @@ -218,13 +220,13 @@ public final class SubtypeLocaleUtils { if (subtype.containsExtraValueKey(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) { return subtype.getExtraValueOf(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME); } - return getSubtypeLocaleDisplayNameInternal(subtype.getLocale(), displayLocale); + return getSubtypeLocaleDisplayNameInternal(SubtypeUtilsKt.locale(subtype), displayLocale); } @NonNull public static String getSubtypeDisplayNameInSystemLocale( @NonNull final InputMethodSubtype subtype) { - final Locale displayLocale = sResources.getConfiguration().locale; + final Locale displayLocale = ConfigurationCompatKt.locale(sResources.getConfiguration()); return getSubtypeDisplayNameInternal(subtype, displayLocale); } @@ -233,7 +235,7 @@ public final class SubtypeLocaleUtils { if (subtype == null) { return ""; } - return getSubtypeLocale(subtype) + "/" + getKeyboardLayoutSetName(subtype); + return SubtypeUtilsKt.locale(subtype) + "/" + getKeyboardLayoutSetName(subtype); } @NonNull @@ -259,12 +261,6 @@ public final class SubtypeLocaleUtils { }); } - @NonNull - public static Locale getSubtypeLocale(@NonNull final InputMethodSubtype subtype) { - final String localeString = subtype.getLocale(); - return LocaleUtils.constructLocaleFromString(localeString); - } - @Nullable public static String getKeyboardLayoutSetDisplayName(@NonNull final InputMethodSubtype subtype) { final String layoutName = getKeyboardLayoutSetName(subtype); diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/SubtypeSettings.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/SubtypeSettings.kt index 59d63af46..742546eff 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/SubtypeSettings.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/SubtypeSettings.kt @@ -13,16 +13,18 @@ import androidx.core.content.edit import org.dslul.openboard.inputmethod.keyboard.KeyboardSwitcher import org.dslul.openboard.inputmethod.latin.R import org.dslul.openboard.inputmethod.latin.RichInputMethodManager +import org.dslul.openboard.inputmethod.latin.common.Constants +import org.dslul.openboard.inputmethod.latin.common.LocaleUtils +import org.dslul.openboard.inputmethod.latin.common.LocaleUtils.constructLocale import org.dslul.openboard.inputmethod.latin.define.DebugFlags import org.dslul.openboard.inputmethod.latin.settings.Settings +import org.dslul.openboard.inputmethod.latin.utils.ScriptUtils.script import org.xmlpull.v1.XmlPullParser import java.io.File import java.util.* -import kotlin.collections.ArrayList -import kotlin.collections.LinkedHashMap /** @return enabled subtypes. If no subtypes are enabled, but a contextForFallback is provided, - * subtypes for system locales will be returned, or en_US if none found. */ + * subtypes for system locales will be returned, or en-US if none found. */ fun getEnabledSubtypes(prefs: SharedPreferences, fallback: Boolean = false): List { require(initialized) if (prefs.getBoolean(Settings.PREF_USE_SYSTEM_LOCALES, true)) @@ -37,6 +39,22 @@ fun getAllAvailableSubtypes(): List { return resourceSubtypesByLocale.values.flatten() + additionalSubtypes } +fun getMatchingLayoutSetNameForLocale(locale: Locale): String { + val subtypes = resourceSubtypesByLocale.values.flatten() + val name = LocaleUtils.getBestMatch(locale, subtypes) { it.locale() }?.getExtraValueOf(Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET) + if (name != null) return name + return when (locale.script()) { + ScriptUtils.SCRIPT_LATIN -> "qwerty" + ScriptUtils.SCRIPT_ARMENIAN -> "armenian_phonetic" + ScriptUtils.SCRIPT_CYRILLIC -> "ru" + ScriptUtils.SCRIPT_GREEK -> "greek" + ScriptUtils.SCRIPT_HEBREW -> "hebrew" + ScriptUtils.SCRIPT_GEORGIAN -> "georgian" + ScriptUtils.SCRIPT_BENGALI -> "bengali_unijoy" + else -> throw RuntimeException("Wrong script supplied: ${locale.script()}") + }; +} + fun addEnabledSubtype(prefs: SharedPreferences, newSubtype: InputMethodSubtype) { require(initialized) val subtypeString = newSubtype.prefString() @@ -46,7 +64,7 @@ fun addEnabledSubtype(prefs: SharedPreferences, newSubtype: InputMethodSubtype) if (newSubtype !in enabledSubtypes) { enabledSubtypes.add(newSubtype) - enabledSubtypes.sortBy { it.locale() } // for consistent order + enabledSubtypes.sortBy { it.locale().toLanguageTag() } // for consistent order RichInputMethodManager.getInstance().refreshSubtypeCaches() } } @@ -77,15 +95,15 @@ fun removeAdditionalSubtype(prefs: SharedPreferences, resources: Resources, subt fun getSelectedSubtype(prefs: SharedPreferences): InputMethodSubtype { require(initialized) - val subtypeString = prefs.getString(Settings.PREF_SELECTED_INPUT_STYLE, "")!!.split(LOCALE_LAYOUT_SEPARATOR) + val localeAndLayout = prefs.getString(Settings.PREF_SELECTED_INPUT_STYLE, "")!!.toLocaleAndLayout() val subtypes = if (prefs.getBoolean(Settings.PREF_USE_SYSTEM_LOCALES, true)) getDefaultEnabledSubtypes() else enabledSubtypes - val subtype = subtypes.firstOrNull { subtypeString.first() == it.locale() && subtypeString.last() == SubtypeLocaleUtils.getKeyboardLayoutSetName(it) } + val subtype = subtypes.firstOrNull { localeAndLayout.first == it.locale() && localeAndLayout.second == SubtypeLocaleUtils.getKeyboardLayoutSetName(it) } ?: subtypes.firstOrNull() if (subtype == null) { val defaultSubtypes = getDefaultEnabledSubtypes() - return defaultSubtypes.firstOrNull { subtypeString.first() == it.locale() && subtypeString.last() == SubtypeLocaleUtils.getKeyboardLayoutSetName(it) } - ?: defaultSubtypes.firstOrNull { subtypeString.first().substringBefore("_") == it.locale().substringBefore("_") && subtypeString.last() == SubtypeLocaleUtils.getKeyboardLayoutSetName(it) } + return defaultSubtypes.firstOrNull { localeAndLayout.first == it.locale() && localeAndLayout.second == SubtypeLocaleUtils.getKeyboardLayoutSetName(it) } + ?: defaultSubtypes.firstOrNull { localeAndLayout.first.language == it.locale().language && localeAndLayout.second == SubtypeLocaleUtils.getKeyboardLayoutSetName(it) } ?: defaultSubtypes.first() } return subtype @@ -93,7 +111,7 @@ fun getSelectedSubtype(prefs: SharedPreferences): InputMethodSubtype { fun setSelectedSubtype(prefs: SharedPreferences, subtype: InputMethodSubtype) { val subtypeString = subtype.prefString() - if (subtype.locale().isEmpty() || prefs.getString(Settings.PREF_SELECTED_INPUT_STYLE, "") == subtypeString) + if (subtype.locale().toLanguageTag().isEmpty() || prefs.getString(Settings.PREF_SELECTED_INPUT_STYLE, "") == subtypeString) return prefs.edit { putString(Settings.PREF_SELECTED_INPUT_STYLE, subtypeString) } } @@ -123,12 +141,12 @@ fun getSystemLocales(): List { return systemLocales } -fun hasMatchingSubtypeForLocaleString(localeString: String): Boolean { +fun hasMatchingSubtypeForLocale(locale: Locale): Boolean { require(initialized) - return !resourceSubtypesByLocale[localeString].isNullOrEmpty() + return !resourceSubtypesByLocale[locale].isNullOrEmpty() } -fun getAvailableSubtypeLocaleStrings(): Collection { +fun getAvailableSubtypeLocales(): Collection { require(initialized) return resourceSubtypesByLocale.keys } @@ -151,29 +169,29 @@ fun init(context: Context) { private fun getDefaultEnabledSubtypes(): List { if (systemSubtypes.isNotEmpty()) return systemSubtypes val subtypes = systemLocales.mapNotNull { locale -> - val localeString = locale.toString() - val subtypesOfLocale = resourceSubtypesByLocale[localeString] - ?: resourceSubtypesByLocale[localeString.substringBefore("_")] // fall back to language matching the subtype - ?: localeString.substringBefore("_").let { language -> // fall back to languages matching subtype language - resourceSubtypesByLocale.firstNotNullOfOrNull { - if (it.key.substringBefore("_") == language) - it.value - else null - } - } + val subtypesOfLocale = resourceSubtypesByLocale[locale] + // get best match + ?: LocaleUtils.getBestMatch(locale, resourceSubtypesByLocale.keys) {it}?.let { resourceSubtypesByLocale[it] } subtypesOfLocale?.firstOrNull() } if (subtypes.isEmpty()) { - // hardcoded fallback for weird cases - systemSubtypes.add(resourceSubtypesByLocale["en_US"]!!.first()) + // hardcoded fallback to en-US for weird cases + systemSubtypes.add(resourceSubtypesByLocale[Locale.US]!!.first()) } else { systemSubtypes.addAll(subtypes) } return systemSubtypes } +/** string for for identifying a subtype, does not contain all necessary information to actually create it */ private fun InputMethodSubtype.prefString() = - locale() + LOCALE_LAYOUT_SEPARATOR + SubtypeLocaleUtils.getKeyboardLayoutSetName(this) + locale().toLanguageTag() + LOCALE_LAYOUT_SEPARATOR + SubtypeLocaleUtils.getKeyboardLayoutSetName(this) + +private fun String.toLocaleAndLayout(): Pair = + substringBefore(LOCALE_LAYOUT_SEPARATOR).constructLocale() to substringAfter(LOCALE_LAYOUT_SEPARATOR) + +private fun Pair.prefString() = + first.toLanguageTag() + LOCALE_LAYOUT_SEPARATOR + second private fun loadResourceSubtypes(resources: Resources) { val xml = resources.getXml(R.xml.method) @@ -185,8 +203,8 @@ private fun loadResourceSubtypes(resources: Resources) { val icon = xml.getAttributeResourceValue(namespace, "icon", 0) val label = xml.getAttributeResourceValue(namespace, "label", 0) val subtypeId = xml.getAttributeIntValue(namespace, "subtypeId", 0) - val locale = xml.getAttributeValue(namespace, "imeSubtypeLocale").intern() - val languageTag = xml.getAttributeValue(namespace, "languageTag") + val localeString = xml.getAttributeValue(namespace, "imeSubtypeLocale").intern() + val languageTag = xml.getAttributeValue(namespace, "languageTag").intern() val imeSubtypeMode = xml.getAttributeValue(namespace, "imeSubtypeMode") val imeSubtypeExtraValue = xml.getAttributeValue(namespace, "imeSubtypeExtraValue").intern() val isAsciiCapable = xml.getAttributeBooleanValue(namespace, "isAsciiCapable", false) @@ -195,12 +213,14 @@ private fun loadResourceSubtypes(resources: Resources) { b.setSubtypeNameResId(label) if (subtypeId != 0) b.setSubtypeId(subtypeId) - b.setSubtypeLocale(locale) + b.setSubtypeLocale(localeString) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && languageTag != null) b.setLanguageTag(languageTag) b.setSubtypeMode(imeSubtypeMode) b.setSubtypeExtraValue(imeSubtypeExtraValue) b.setIsAsciiCapable(isAsciiCapable) + val locale = if (languageTag.isEmpty()) localeString.constructLocale() + else languageTag.constructLocale() resourceSubtypesByLocale.getOrPut(locale) { ArrayList(2) }.add(b.build()) } eventType = xml.next() @@ -223,7 +243,7 @@ private fun removeInvalidCustomSubtypes(context: Context) { additionalSubtypes.forEach { val name = it.substringAfter(":").substringBefore(":") if (!name.startsWith(CUSTOM_LAYOUT_PREFIX)) return@forEach - if (name !in customSubtypeFiles) + if (customSubtypeFiles?.contains(name) != true) subtypesToRemove.add(it) } if (subtypesToRemove.isEmpty()) return @@ -235,30 +255,29 @@ private fun removeInvalidCustomSubtypes(context: Context) { private fun loadEnabledSubtypes(context: Context) { val prefs = DeviceProtectedUtils.getSharedPreferences(context) val subtypeStrings = prefs.getString(Settings.PREF_ENABLED_INPUT_STYLES, "")!! - .split(SUBTYPE_SEPARATOR).filter { it.isNotEmpty() }.map { it.split(LOCALE_LAYOUT_SEPARATOR) } + .split(SUBTYPE_SEPARATOR).filter { it.isNotEmpty() }.map { it.toLocaleAndLayout() } for (localeAndLayout in subtypeStrings) { - require(localeAndLayout.size == 2) - val subtypesForLocale = resourceSubtypesByLocale[localeAndLayout.first()] + val subtypesForLocale = resourceSubtypesByLocale[localeAndLayout.first] if (subtypesForLocale == null) { val message = "no resource subtype for $localeAndLayout" Log.w(TAG, message) if (DebugFlags.DEBUG_ENABLED) Toast.makeText(context, message, Toast.LENGTH_LONG).show() else // don't remove in debug mode - removeEnabledSubtype(prefs, localeAndLayout.joinToString(LOCALE_LAYOUT_SEPARATOR)) + removeEnabledSubtype(prefs, localeAndLayout.prefString()) continue } - val subtype = subtypesForLocale.firstOrNull { SubtypeLocaleUtils.getKeyboardLayoutSetName(it) == localeAndLayout.last() } - ?: additionalSubtypes.firstOrNull { it.locale() == localeAndLayout.first() && SubtypeLocaleUtils.getKeyboardLayoutSetName(it) == localeAndLayout.last() } + val subtype = subtypesForLocale.firstOrNull { SubtypeLocaleUtils.getKeyboardLayoutSetName(it) == localeAndLayout.second } + ?: additionalSubtypes.firstOrNull { it.locale() == localeAndLayout.first && SubtypeLocaleUtils.getKeyboardLayoutSetName(it) == localeAndLayout.second } if (subtype == null) { val message = "subtype $localeAndLayout could not be loaded" Log.w(TAG, message) if (DebugFlags.DEBUG_ENABLED) Toast.makeText(context, message, Toast.LENGTH_LONG).show() else // don't remove in debug mode - removeEnabledSubtype(prefs, localeAndLayout.joinToString(LOCALE_LAYOUT_SEPARATOR)) + removeEnabledSubtype(prefs, localeAndLayout.prefString()) continue } @@ -287,7 +306,7 @@ private fun removeEnabledSubtype(prefs: SharedPreferences, subtypeString: String var initialized = false private set private val enabledSubtypes = mutableListOf() -private val resourceSubtypesByLocale = LinkedHashMap>(100) +private val resourceSubtypesByLocale = LinkedHashMap>(100) private val additionalSubtypes = mutableListOf() private val systemLocales = mutableListOf() private val systemSubtypes = mutableListOf() @@ -295,11 +314,3 @@ private val systemSubtypes = mutableListOf() private const val SUBTYPE_SEPARATOR = ";" private const val LOCALE_LAYOUT_SEPARATOR = ":" private const val TAG = "SubtypeSettings" - -@Suppress("deprecation") // it's deprecated, but no replacement for API < 24 -// todo: subtypes should now have language tags -> use them for api >= 24 -// but only replace subtype-related usage, otherwise the api mess will be horrible -// maybe rather return a locale instead of a string... -// is this acceptable for performance? any place where there are many call to locale()? -// see also InputMethodSubtypeCompatUtils -fun InputMethodSubtype.locale() = locale diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/SubtypeUtils.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/SubtypeUtils.kt new file mode 100644 index 000000000..2bc2bcb21 --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/SubtypeUtils.kt @@ -0,0 +1,14 @@ +package org.dslul.openboard.inputmethod.latin.utils + +import android.os.Build +import android.view.inputmethod.InputMethodSubtype +import org.dslul.openboard.inputmethod.latin.common.LocaleUtils.constructLocale +import java.util.Locale + +fun InputMethodSubtype.locale(): Locale { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (languageTag.isNotEmpty()) + return languageTag.constructLocale() + } + @Suppress("deprecation") return locale.constructLocale() +} diff --git a/app/src/main/res/values-af/strings.xml b/app/src/main/res/values-af/strings.xml index bbb681f6f..99550b9a6 100644 --- a/app/src/main/res/values-af/strings.xml +++ b/app/src/main/res/values-af/strings.xml @@ -52,13 +52,13 @@ "Engels (VK)" "Engels (VS)" "Spaans (VS)" - "Hinglish" - "Serwies (Latyns)" + "Hinglish" + "Serwies (Latyns)" Engels (VK) (%s) Engels (VS) (%s) Spaans (VS) (%s) - Hinglish (%s) - Serwies (%s) + Hinglish (%s) + Serwies (%s) %s (Tradisioneel) %s (Kompak) "Geen taal nie (alfabet)" diff --git a/app/src/main/res/values-am/strings.xml b/app/src/main/res/values-am/strings.xml index 868a608e4..2ce0d1dab 100644 --- a/app/src/main/res/values-am/strings.xml +++ b/app/src/main/res/values-am/strings.xml @@ -52,13 +52,13 @@ "እንግሊዘኛ (የታላቋ ብሪታንያ)" "እንግሊዘኛ (ዩ.ኤስ)" "ስፓኒሽኛ (ዩኤስ)" - "ሂንግሊሽ" - "ሰርብያኛ (ላቲን)" + "ሂንግሊሽ" + "ሰርብያኛ (ላቲን)" "እንግሊዝኛ (ዩኬ) (%s)" "እንግሊዝኛ (አሜሪካ) (%s)" "ስፓኒሽ (አሜሪካ) (%s)" - "ሂንግሊሽ (%s)" - "ሰርቢያኛ (%s)" + "ሂንግሊሽ (%s)" + "ሰርቢያኛ (%s)" "%s (ተለምዷዊ)" "%s (እስግ)" "ምንም ቋንቋ (ፊደላት)" diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 29d34d5ec..b196613c0 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -52,13 +52,13 @@ "الإنجليزية (المملكة المتحدة)" "الإنجليزية (الولايات المتحدة)" "الإسبانية (الأميركية)" - "هنجليزية" - "الصربية (اللاتينية)" + "هنجليزية" + "الصربية (اللاتينية)" الإنجليزية (المملكة المتحدة) (%s) الإنجليزية (الولايات المتحدة) (%s) الإسبانية (الولايات المتحدة) (%s) - الهنجليزية (%s) - الصربية (%s) + الهنجليزية (%s) + الصربية (%s) %s (التقليدية) %s (مضغوط) "بدون لغة (أبجدية)" diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 84b01d960..9f2be332d 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -52,13 +52,13 @@ "İngilis (BK)" "İngilis (ABŞ)" "İspan (ABŞ)" - "Hingilis" - "Serb (Latın)" + "Hingilis" + "Serb (Latın)" "İngilis (Britaniya) (%s)" "İngilis (Amerika) (%s)" "İspan (Amerika) (%s)" - "Hingilis (%s)" - "Serb (%s)" + "Hingilis (%s)" + "Serb (%s)" "%s (Ənənəvi)" "%s (Kompakt)" "Dil yoxdur (Əlifba)" diff --git a/app/src/main/res/values-b+sr+Latn/strings.xml b/app/src/main/res/values-b+sr+Latn/strings.xml index e4b7c62be..43f9092c0 100644 --- a/app/src/main/res/values-b+sr+Latn/strings.xml +++ b/app/src/main/res/values-b+sr+Latn/strings.xml @@ -52,13 +52,13 @@ "engleski (UK)" "engleski (SAD)" "španski (SAD)" - "hengleski" - "srpski (latinica)" + "hengleski" + "srpski (latinica)" "engleski (UK) (%s)" "engleski (SAD) (%s)" "španski (SAD) (%s)" - "hengleski (%s)" - "srpski (%s)" + "hengleski (%s)" + "srpski (%s)" "%s (tradicionalni)" "%s (kompaktna)" "Nema jezika (abeceda)" diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 1ea98f67d..24b003640 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -53,13 +53,13 @@ "Англійская (Вялікабрытанія)" "Англійская (ЗША)" "Іспанская (ЗША)" - "Хінгліш" - "Сербская (Лацініца)" + "Хінгліш" + "Сербская (Лацініца)" Англійская (Вялікабрытанія) (%s) Англійская (ЗША) (%s) Іспанская (ЗША) (%s) - Хінгліш (%s) - Сербская (%s) + Хінгліш (%s) + Сербская (%s) %s (Традыцыйная) %s (Кампактная) "Стандартная (Лацініца)" diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 8a1f70c6b..0e529d8d9 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -52,13 +52,13 @@ "английски (Великобритания)" "английски (САЩ)" "испански (САЩ)" - "Хинглиш" - "Сръбска (латиница)" + "Хинглиш" + "Сръбска (латиница)" "английски (Великобр.) (%s)" "английски (САЩ) (%s)" "испански (САЩ) (%s)" - "Хинглиш (%s)" - "Сръбска (%s)" + "Хинглиш (%s)" + "Сръбска (%s)" "%s (традиционна клавиатура)" "%s (компактна)" "Без език (латиница)" diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index 8026f1e74..c4535361d 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -102,7 +102,7 @@ ইংরেজি (ইউকে) ইংরেজি (ইউএস) স্প্যানিশ (ইউএস) - হিংলিশ + হিংলিশ প্রচ্ছন্ন বৈশিষ্ট্য অপরিদৃষ্ট হতে পারে এমন বৈশিষ্ট্যের বর্ণনা ডিভাইস সুরক্ষিত স্টোরেজ @@ -126,13 +126,12 @@ ► রুট উপলব্ধতাসহ ম্যানুয়াল ব্যাকআপ করা ব্যবহারকারীদের জন্য: Android 7 থেকে শেয়ারড্ প্রেফারেন্স ফাইল ডিফল্ট জায়গা নয়। কারণ অ্যাপ %s ব্যবহার করছে। <br> ডিভাইস আনলকের আগে সেটিংস খোলার জন্য এটি প্রয়োজনীয়, যেমন: বুট করার সময়। <br> ফাইলটি /data/user_de/0/package_id/shared_prefs/ থাকে। যদিও এটা ডিভাইস এবং অ্যান্ড্রয়েড সংস্করণের উপরে নির্ভর করে। - হাঙ্গেরীয় (QWERTY) - সার্বীয় (ল্যাটিন) + সার্বীয় (ল্যাটিন) ইংরেজি (ইউকে) (%s) ইংরেজি (ইউএস) (%s) স্প্যানিশ (ইউএস) (%s) - হিংলিশ (%s) - সার্বিয়ান (%s) + হিংলিশ (%s) + সার্বিয়ান (%s) %s (প্রথাগত) %s (অক্ষর) %s (সংক্ষিপ্ত) diff --git a/app/src/main/res/values-bs/strings.xml b/app/src/main/res/values-bs/strings.xml index 46a2834c0..29e92e338 100644 --- a/app/src/main/res/values-bs/strings.xml +++ b/app/src/main/res/values-bs/strings.xml @@ -53,13 +53,13 @@ "engleski (UK)" "engleski (SAD)" "španski (SAD)" - "Hindu engleski" - "Srpski (latinica)" + "Hindu engleski" + "Srpski (latinica)" "Engleski (UK) (%s)" "Engleski (SAD) (%s)" "Španski (SAD) (%s)" - "Hindu engleski (%s)" - "Srpski (%s)" + "Hindu engleski (%s)" + "Srpski (%s)" "%s (tradicionalan)" "%s (kompaktan)" "Nema jezika (abeceda)" diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index a49979b1c..f2da2bf5c 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -52,13 +52,13 @@ "anglès (Regne Unit)" "anglès (EUA)" "Espanyol (EUA)" - Hinglès - "Serbi (llatí)" + Hinglès + "Serbi (llatí)" Anglès (Regne Unit) (%s) Angles (EUA) (%s) Espanyol (EUA) (%s) - Hinglès (%s) - Serbi (%s) + Hinglès (%s) + Serbi (%s) %s (Tradicional) %s (Compacte) "cap idioma (alfabet)" diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index eb3df0085..24f08b44d 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -53,13 +53,13 @@ "Angličtina (Velká Británie)" "Angličtina (USA)" "Španělština (USA)" - "Hinglish" - "Srbština (Latinka)" + "Hinglish" + "Srbština (Latinka)" "Angličtina (Velká Británie) (%s)" "Angličtina (USA) (%s)" "Španělština (USA) (%s)" - "Hinglish (%s)" - "Srbština (%s)" + "Hinglish (%s)" + "Srbština (%s)" "%s (Tradiční)" "%s (Kompaktní)" "Standardní (Latinka)" diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 0eafa73e0..25d55e641 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -52,13 +52,13 @@ "engelsk (Storbritannien)" "engelsk (USA)" "Spansk (USA)" - "Hinglish" - "Serbisk (latinsk)" + "Hinglish" + "Serbisk (latinsk)" Engelsk (Storbritannien) (%s) Engelsk (USA) (%s) Spansk (USA) (%s) - Hinglish (%s) - Serbisk (%s) + Hinglish (%s) + Serbisk (%s) %s (traditionelt) %s (kompakt) "Intet sprog (Alfabet)" diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index e2eccf7df..04487e302 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -52,13 +52,13 @@ "Englisch (UK)" "Englisch (USA)" "Spanisch (USA)" - Hinglisch - "Serbisch (Lateinisch)" + Hinglisch + "Serbisch (Lateinisch)" Englisch (GB) (%s) Englisch (US) (%s) Spanisch (USA) (%s) - Hinglisch (%s) - Serbisch (%s) + Hinglisch (%s) + Serbisch (%s) %s (Traditionell) %s (Kompakt) "Keine Sprache (lat. Alphabet)" diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 4a125c117..85bbcb33b 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -52,13 +52,13 @@ "Αγγλικά (ΗΒ)" "Αγγλικά (ΗΠΑ)" "Ισπανικά (ΗΠΑ)" - "Hinglish" - "Σερβικά (Λατινικά)" + "Hinglish" + "Σερβικά (Λατινικά)" "Αγγλικά (Ηνωμένο Βασίλειο) (%s)" "Αγγλικά (ΗΠΑ) (%s)" "Ισπανικά (ΗΠΑ) (%s)" - "Hinglish (%s)" - "Σερβικά (%s)" + "Hinglish (%s)" + "Σερβικά (%s)" "%s (Παραδοσιακά)" "%s (Συμπαγές)" "Καμία γλώσσα (Αλφάβητο)" diff --git a/app/src/main/res/values-en-rAU/strings.xml b/app/src/main/res/values-en-rAU/strings.xml index d6a4671ac..c905b4386 100644 --- a/app/src/main/res/values-en-rAU/strings.xml +++ b/app/src/main/res/values-en-rAU/strings.xml @@ -52,13 +52,13 @@ "English (UK)" "English (US)" "Spanish (US)" - "Hinglish" - "Serbian (Latin)" + "Hinglish" + "Serbian (Latin)" "English (UK) (%s)" "English (US) (%s)" "Spanish (US) (%s)" - "Hinglish (%s)" - "Serbian (%s)" + "Hinglish (%s)" + "Serbian (%s)" "%s (Traditional)" "%s (Compact)" "No language (Alphabet)" diff --git a/app/src/main/res/values-en-rCA/strings.xml b/app/src/main/res/values-en-rCA/strings.xml index d6a4671ac..c905b4386 100644 --- a/app/src/main/res/values-en-rCA/strings.xml +++ b/app/src/main/res/values-en-rCA/strings.xml @@ -52,13 +52,13 @@ "English (UK)" "English (US)" "Spanish (US)" - "Hinglish" - "Serbian (Latin)" + "Hinglish" + "Serbian (Latin)" "English (UK) (%s)" "English (US) (%s)" "Spanish (US) (%s)" - "Hinglish (%s)" - "Serbian (%s)" + "Hinglish (%s)" + "Serbian (%s)" "%s (Traditional)" "%s (Compact)" "No language (Alphabet)" diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml index 9347efe4c..10447a09b 100644 --- a/app/src/main/res/values-en-rGB/strings.xml +++ b/app/src/main/res/values-en-rGB/strings.xml @@ -52,13 +52,13 @@ "English (UK)" "English (US)" "Spanish (US)" - "Hinglish" - "Serbian (Latin)" + "Hinglish" + "Serbian (Latin)" English (UK) (%s) English (US) (%s) Spanish (US) (%s) - Hinglish (%s) - Serbian (%s) + Hinglish (%s) + Serbian (%s) %s (Traditional) %s (Compact) "No language (Alphabet)" diff --git a/app/src/main/res/values-en-rIN/strings.xml b/app/src/main/res/values-en-rIN/strings.xml index 190f52326..6ab1765ea 100644 --- a/app/src/main/res/values-en-rIN/strings.xml +++ b/app/src/main/res/values-en-rIN/strings.xml @@ -52,13 +52,13 @@ "English (UK)" "English (US)" "Spanish (US)" - "Hinglish" - "Serbian (Latin)" + "Hinglish" + "Serbian (Latin)" "English (UK) (%s)" "English (US) (%s)" "Spanish (US) (%s)" - "Hinglish (%s)" - "Serbian (%s)" + "Hinglish (%s)" + "Serbian (%s)" "%s (Traditional)" "%s (Compact)" "No language (Alphabet)" diff --git a/app/src/main/res/values-en-rXC/strings.xml b/app/src/main/res/values-en-rXC/strings.xml index 604c91336..2615b889f 100644 --- a/app/src/main/res/values-en-rXC/strings.xml +++ b/app/src/main/res/values-en-rXC/strings.xml @@ -52,13 +52,13 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‎‏‎‎‏‏‏‏‏‎‏‎‏‏‎‎‏‎‎‎‎‎‎‎‎‎‎‏‏‎‎‏‏‏‎‏‏‎‎‎‎‏‏‎‏‏‏‎‏‏‎English (UK)‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‏‏‏‎‎‏‎‏‎‎‏‎‏‏‎‎‎‎‏‎‏‎‏‏‏‏‎‎‏‏‎‏‎‎‏‏‏‎‎‏‏‎‎‏‎‏‎‏‏‏‏‏‎English (US)‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‏‏‏‎‏‏‎‏‎‏‎‎‎‎‏‏‏‏‏‎‎‎‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‎‎‏‎‎‏‏‎‏‎‏‏‎‏‎‎‎‎Spanish (US)‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‎‏‎‏‎‎‏‎‎‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‏‎‎‏‏‎‎‎‏‎‎‎‏‏‏‏‏‎Hinglish‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‎‏‏‎‏‎‎‎‎‎‎‏‎‏‏‏‏‏‎‎‎‎‏‏‎‎‎‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‎‎‏‏‏‎Serbian (Latin)‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‎‏‎‏‎‎‏‎‎‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‏‎‎‏‏‎‎‎‏‎‎‎‏‏‏‏‏‎Hinglish‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‎‏‏‎‏‎‎‎‎‎‎‏‎‏‏‏‏‏‎‎‎‎‏‏‎‎‎‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‎‎‏‏‏‎Serbian (Latin)‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‎‏‏‎‎‎‏‎‏‏‎‏‏‏‎‎‎‎‏‎‏‎‏‏‎‎‎‎‎‎‏‏‎‏‏‎‎‎‏‎‏‏‎‏‏‏‏‏‏‎‎‎‎‎English (UK) (‎‏‎‎‏‏‎%s‎‏‎‎‏‏‏‎)‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‎‎‎‏‏‏‏‎‏‎‏‏‏‏‏‎‎‎‏‎‎‏‏‏‎‎‏‎‎‎‎‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‎‏‏‏‎‎English (US) (‎‏‎‎‏‏‎%s‎‏‎‎‏‏‏‎)‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‎‏‎‎‎‏‏‏‎‎‏‏‏‎‏‏‎‏‏‎‏‏‏‎‏‎‏‎‎‎‎‎‎‎‏‎‏‏‎‏‎‎Spanish (US) (‎‏‎‎‏‏‎%s‎‏‎‎‏‏‏‎)‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‎‎‎‎‏‏‏‎‏‏‎‏‎‎‏‏‏‏‏‎‎‎‎‎‎‏‏‏‏‎‎‏‎‎‎Hinglish (‎‏‎‎‏‏‎%s‎‏‎‎‏‏‏‎)‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‏‎‏‏‎‏‎‏‎‎‏‎‏‏‏‏‏‎‎‎‏‎‎‎‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‎‎‎‎‎‏‎‏‏‏‏‎‏‏‏‎Serbian (‎‏‎‎‏‏‎%s‎‏‎‎‏‏‏‎)‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‎‎‎‎‏‏‏‎‏‏‎‏‎‎‏‏‏‏‏‎‎‎‎‎‎‏‏‏‏‎‎‏‎‎‎Hinglish (‎‏‎‎‏‏‎%s‎‏‎‎‏‏‏‎)‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‏‎‏‏‎‏‎‏‎‎‏‎‏‏‏‏‏‎‎‎‏‎‎‎‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‎‎‎‎‎‏‎‏‏‏‏‎‏‏‏‎Serbian (‎‏‎‎‏‏‎%s‎‏‎‎‏‏‏‎)‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‏‎‏‎‎‏‏‎‏‏‎‎‎‏‎‏‎‎‎‎‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‏‎‏‎‎‎‎‏‎‎‎‏‎‏‎‎‎‏‎‎‏‏‎%s‎‏‎‎‏‏‏‎ (Traditional)‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‎‎‏‎‏‎‏‎‏‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‎‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‏‎%s‎‏‎‎‏‏‏‎ (Compact)‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‎‎‏‏‎‏‎‎‎‏‏‎‏‎‎‏‏‏‏‎‎‏‏‎‏‎‎‏‏‎‏‎‎‏‎‎‏‏‎‏‎‎‏‎‎‎‏‏‏‎‎‏‏‏‎No language (Alphabet)‎‏‎‎‏‎" diff --git a/app/src/main/res/values-es-rUS/strings.xml b/app/src/main/res/values-es-rUS/strings.xml index 2c5f00d7a..8448a3afb 100644 --- a/app/src/main/res/values-es-rUS/strings.xml +++ b/app/src/main/res/values-es-rUS/strings.xml @@ -52,13 +52,13 @@ "Inglés (Reino Unido)" "Inglés (EE.UU.)" "Español (EE.UU.)" - "Hinglish" - "Serbio (latino)" + "Hinglish" + "Serbio (latino)" "Inglés, Reino Unido (%s)" "Inglés, EE. UU. (%s)" "Español, EE. UU. (%s)" - "Hinglish (%s)" - "Serbio (%s)" + "Hinglish (%s)" + "Serbio (%s)" "%s (tradicional)" "%s (compacto)" "Ningún idioma (alfabeto)" diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 6d2eae8ab..d20c1b1ef 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -52,13 +52,13 @@ Inglés (Reino Unido) Inglés (EE.UU.) "Español (EE.UU.)" - inglés indio - "Serbio (latino)" + inglés indio + "Serbio (latino)" Inglés (Reino Unido) (%s) Inglés (EE.UU.) (%s) Español (EE.UU.)) (%s) - inglés indio (%s) - Serbio (%s) + inglés indio (%s) + Serbio (%s) %s (Tradicional) %s (Compacto) "Ningún idioma (alfabeto)" diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index f02163fb3..1f86c4922 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -52,13 +52,13 @@ "Inglise (UK)" "Inglise (USA)" "hispaania (USA)" - "Hindi-inglise" - "Serbia (ladina)" + "Hindi-inglise" + "Serbia (ladina)" "Inglise (Ühendk.) (%s)" "Inglise (USA) (%s)" "Hispaania (USA) (%s)" - "Hindi-inglise (%s)" - "Serbia (%s)" + "Hindi-inglise (%s)" + "Serbia (%s)" "%s (traditsiooniline)" "%s (kompaktne)" "Keel puudub (tähestik)" diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 8d8ff717d..b2b93e3dd 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -52,13 +52,13 @@ "Ingelesa (Erresuma Batua)" "Ingelesa (AEB)" "Gaztelania (AEB)" - "Hinglisha" - "Serbiarra (latindarra)" + "Hinglisha" + "Serbiarra (latindarra)" Ingelesa (Erresuma Batua) (%s) Ingelesa (Estatu Batuak) (%s) Gaztelania (AEB) (%s) - Hinglisha (%s) - Serbiarra (%s) + Hinglisha (%s) + Serbiarra (%s) %s (Tradizionala) %s (Trinkoa) "Ez dago hizkuntzarik (alfabetoa)" diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 5bfa4060a..1925cc51d 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -52,13 +52,13 @@ "انگلیسی (بریتانیا)" "انگلیسی (امریکا)" "اسپانیایی (امریکا)" - "هندی انگلیسی" - "صربی (لاتین)" + "هندی انگلیسی" + "صربی (لاتین)" انگلیسی (بریتانیا) (%s) انگلیسی (امریکا) (%s) اسپانیایی (امریکا) (%s) - هندی انگلیسی (%s) - صربی (%s) + هندی انگلیسی (%s) + صربی (%s) %s (سنتی) %s (فشرده) "بدون زبان (حروف الفبا)" diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 3e73a07c4..21a6689d2 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -52,13 +52,13 @@ "englanti (Iso-Britannia)" "englanti (Yhdysvallat)" "espanja (Yhdysvallat)" - "Hindienglanti" - "serbialainen (latinal.)" + "Hindienglanti" + "serbialainen (latinal.)" "englanti (UK) (%s)" "englanti (US) (%s)" "espanja (US) (%s)" - "Hindienglanti (%s)" - "serbialainen (%s)" + "Hindienglanti (%s)" + "serbialainen (%s)" "%s (perinteinen)" "%s (tiivis)" "Ei kieltä (aakkoset)" diff --git a/app/src/main/res/values-fr-rCA/strings.xml b/app/src/main/res/values-fr-rCA/strings.xml index 18772f9a9..8c525553a 100644 --- a/app/src/main/res/values-fr-rCA/strings.xml +++ b/app/src/main/res/values-fr-rCA/strings.xml @@ -52,13 +52,13 @@ "Anglais (britannique)" "Anglais (États-Unis)" "Espagnol (États-Unis)" - "Hinglish" - "Serbe (latin)" + "Hinglish" + "Serbe (latin)" "anglais (Royaume-Uni) (%s)" "anglais (États-Unis) (%s)" "espagnol (États-Unis) (%s)" - "Hinglish (%s)" - "Serbe (%s)" + "Hinglish (%s)" + "Serbe (%s)" "%s (traditionnel)" "%s (compact)" "Aucune langue (alphabet)" diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 893b6e33d..154e57ab8 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -58,13 +58,13 @@ "Anglais (Royaume-Uni)" "Anglais (États-Unis)" "Espagnol (États-Unis)" - "Hindi/Anglais" - "Serbe (latin)" + "Hindi/Anglais" + "Serbe (latin)" Anglais (Royaume-Uni) (%s) Anglais (États-Unis) (%s) Espagnol (États-Unis) (%s) - Hindi/Anglais (%s) - Serbe (%s) + Hindi/Anglais (%s) + Serbe (%s) %s (Traditionnel) %s (Compact) "Aucune langue (latin)" diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 994939df1..3daba0d1f 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -52,13 +52,13 @@ "Inglés (Reino Unido)" "Inglés (EUA)" "Español (EUA)" - "Hinglish" - "Serbio (alfabeto latino)" + "Hinglish" + "Serbio (alfabeto latino)" "Inglés (Reino Unido) (%s)" "Inglés (EUA) (%s)" "Español (EUA) (%s)" - "Hinglish (%s)" - "Serbio (%s)" + "Hinglish (%s)" + "Serbio (%s)" "%s (tradicional)" "%s (compacto)" "Ningún idioma (alfabeto)" diff --git a/app/src/main/res/values-gu/strings.xml b/app/src/main/res/values-gu/strings.xml index 9668c4212..1810105d2 100644 --- a/app/src/main/res/values-gu/strings.xml +++ b/app/src/main/res/values-gu/strings.xml @@ -52,13 +52,13 @@ "અંગ્રેજી (યુકે)" "અંગ્રેજી (યુ એસ)" "સ્પેનિશ (US)" - "હિંગ્લિશ" - "સર્બિયન (લેટિન)" + "હિંગ્લિશ" + "સર્બિયન (લેટિન)" "અંગ્રેજી (યુકે) (%s)" "અંગ્રેજી (યુએસ) (%s)" "સ્પેનિશ (યુએસ) (%s)" - "હિંગ્લિશ (%s)" - "સર્બિયન (%s)" + "હિંગ્લિશ (%s)" + "સર્બિયન (%s)" "%s (પરંપરાગત)" "%s (કોમ્પેક્ટ)" "ભાષા નથી (આલ્ફાબેટ)" diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 12ecbc228..51a2b606c 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -52,13 +52,13 @@ "अंग्रेज़ी (यूके)" "अंग्रेज़ी (यूएस)" "स्पेनिश (यूएस)" - "हिंग्लिश" - "सर्बियाई (लैटिन)" + "हिंग्लिश" + "सर्बियाई (लैटिन)" "अंग्रेज़ी (यूके) (%s)" "अंग्रेज़ी (यूएस) (%s)" "स्‍पेनिश (यूएस) (%s)" - "हिंग्लिश (%s)" - "सर्बियाई (%s)" + "हिंग्लिश (%s)" + "सर्बियाई (%s)" "%s (पारंपरिक)" "%s (संक्षिप्त)" "भाषा उपलब्ध नहीं है (लैटिन वर्णाक्षर)" diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 259a66310..dcb00904f 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -52,13 +52,13 @@ "Engleski (UK)" "Engleski (SAD)" Španjolski (SAD) - "Hinglish" - "Srpski (latinica)" + "Hinglish" + "Srpski (latinica)" Engleski (UK) (%s) Engleski (US) (%s) Španjolski (US) (%s) - Hinglish (%s) - Srpski (%s) + Hinglish (%s) + Srpski (%s) %s (traditionalno) %s (kompaktno) Neodređen jezik (abeceda) diff --git a/app/src/main/res/values-hu-rZZ/cm_strings.xml b/app/src/main/res/values-hu-rZZ/cm_strings.xml deleted file mode 100644 index 58f3a63a3..000000000 --- a/app/src/main/res/values-hu-rZZ/cm_strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - Ábécé (Bépo) - diff --git a/app/src/main/res/values-hu-rZZ/strings.xml b/app/src/main/res/values-hu-rZZ/strings.xml index 5b0d10e2a..c84d637d8 100644 --- a/app/src/main/res/values-hu-rZZ/strings.xml +++ b/app/src/main/res/values-hu-rZZ/strings.xml @@ -52,13 +52,13 @@ "angol (brit)" "angol (amerikai)" "spanyol (USA)" - "Hinglish (hindi-angol)" - "Szerb (latin)" + "Hinglish (hindi-angol)" + "Szerb (latin)" Angol (UK) (%s) Angol (USA) (%s) Spanyol (USA) (%s) - "Hinglish (hindi-angol, %s)" - Szerb (%s) + "Hinglish (hindi-angol, %s)" + Szerb (%s) "%s (hagyományos)" "%s (kompakt)" "Nincs nyelv (ábécé)" diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 7c8e593b8..c894386a3 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -52,13 +52,13 @@ "angol (brit)" "angol (amerikai)" "spanyol (USA)" - "Hinglish (hindi-angol)" - "Szerb (latin)" + "Hinglish (hindi-angol)" + "Szerb (latin)" "angol (UK) (%s)" "angol (USA) (%s)" "spanyol (USA) (%s)" - "Hinglish (hindi-angol, %s)" - "Szerb (%s)" + "Hinglish (hindi-angol, %s)" + "Szerb (%s)" "%s (hagyományos)" "%s (kompakt)" "Nincs nyelv (ábécé)" diff --git a/app/src/main/res/values-hy/strings.xml b/app/src/main/res/values-hy/strings.xml index 6319ee0f9..4e06052b6 100644 --- a/app/src/main/res/values-hy/strings.xml +++ b/app/src/main/res/values-hy/strings.xml @@ -52,13 +52,13 @@ "Անգլերեն (ՄԹ)" "Անգլերեն (ԱՄՆ)" "Իսպաներեն (ԱՄՆ)" - "Հինգլիշ" - "Սերբերեն (Լատինական)" + "Հինգլիշ" + "Սերբերեն (Լատինական)" "Անգլերեն (ՄԹ) (%s)" "Անգլերեն (ԱՄՆ) (%s)" "Իսպաներեն (ԱՄՆ) (%s)" - "Հինգլիշ (%s)" - "Սերբերեն (%s)" + "Հինգլիշ (%s)" + "Սերբերեն (%s)" "%s (ավանդական)" "%s (սեղմ)" "Ոչ մի լեզվով (Այբուբեն)" diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index d0142b79e..edb240601 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -52,13 +52,13 @@ "Inggris (Inggris)" "Inggris (AS)" "Spanyol (AS)" - "Hinglish" - "Serbia (Latin)" + "Hinglish" + "Serbia (Latin)" Inggris (UK) (%s) Inggris (US) (%s) Spanyol (US) (%s) - Hinglish (%s) - Serbia (%s) + Hinglish (%s) + Serbia (%s) %s (Tradisional) %s (Ringkas) "Tidak ada bahasa (Abjad)" diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml index 85b26b0b5..0c4b7aedc 100644 --- a/app/src/main/res/values-is/strings.xml +++ b/app/src/main/res/values-is/strings.xml @@ -52,13 +52,13 @@ "Enskt (Bretland)" "Enskt (Bandaríkin)" "Spænskt (US)" - "Hinglish" - "Serbneskt (latneskt)" + "Hinglish" + "Serbneskt (latneskt)" "Enskt (Bretland) (%s)" "Enskt (Bandaríkin) (%s)" "Spænskt (Bandaríkin) (%s)" - "Hinglish (%s)" - "Serbneskt (%s)" + "Hinglish (%s)" + "Serbneskt (%s)" "%s (hefðbundið)" "%s (lítið)" "Ekkert tungumál (stafróf)" diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index da853f8f9..82466d790 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -52,13 +52,13 @@ Inglese (Regno Unito) Inglese (Stati Uniti) Spagnolo (Stati Uniti) - "Hinglish" - Serbo (Latino) + "Hinglish" + Serbo (Latino) Inglese (Regno Unito) (%s) Inglese (Stati Uniti) (%s) Spagnolo (Stati Uniti) (%s) - Hinglish (%s) - Serbo (%s) + Hinglish (%s) + Serbo (%s) %s (tradizionale) %s (compatto) "Nessuna lingua (alfabeto)" diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index b838d544e..3071f603d 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -52,13 +52,13 @@ "אנגלית (בריטניה)" "אנגלית (ארה\"ב)" "ספרדית (ארצות הברית)" - "אנגלית הודית" - "סרבית (באותיות לטיניות)" + "אנגלית הודית" + "סרבית (באותיות לטיניות)" אנגלית (בריטניה)‏ %s) אנגלית (ארה\"ב) ‏%s) ספרדית (ארה\"ב) ‏%s) - אנגלית הודית %s) - סרבית ) %s) + אנגלית הודית %s) + סרבית ) %s) "%s (מסורתית)" "%s (קומפקטית)" "ללא שפה (אלף-בית)" diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index ce62bcdc9..3d5e9b43c 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -52,13 +52,13 @@ "英語 (英国)" "英語 (米国)" "スペイン語 (米国)" - "ヒングリッシュ" - "セルビア語(ラテン文字)" + "ヒングリッシュ" + "セルビア語(ラテン文字)" "英語(英国)(%s)" "英語(米国)(%s)" "スペイン語(米国)(%s)" - "ヒングリッシュ(%s)" - "セルビア語(%s)" + "ヒングリッシュ(%s)" + "セルビア語(%s)" "%s(伝統言語)" "%s(コンパクト)" "言語なし(アルファベット)" diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml index ed96bbdf2..7f543f039 100644 --- a/app/src/main/res/values-ka/strings.xml +++ b/app/src/main/res/values-ka/strings.xml @@ -52,13 +52,13 @@ "ინგლისური (გართ. სამ.)" "ინგლისური (აშშ)" "ესპანური (აშშ)" - "ჰინგლისური" - "სერბული (ლათინური)" + "ჰინგლისური" + "სერბული (ლათინური)" ინგლისური (UK) %s ინგლისური (აშშ) (%s) ესპანური (აშშ) (%s) - ჰინგლისური (%s) - სერბული (%s) + ჰინგლისური (%s) + სერბული (%s) %s (ტრადიციული) %s (კომპაქტური) "ენის გარეშე (ლათინური ანბანი)" diff --git a/app/src/main/res/values-kk/strings.xml b/app/src/main/res/values-kk/strings.xml index ea8e1ec08..16662257e 100644 --- a/app/src/main/res/values-kk/strings.xml +++ b/app/src/main/res/values-kk/strings.xml @@ -52,13 +52,13 @@ "ағылшын (ҰБ)" "ағылшын (АҚШ)" "Испан (АҚШ)" - "Хинглиш" - "Серб (латын жазуы)" + "Хинглиш" + "Серб (латын жазуы)" "Ағылшын (Құрама Корольдік) (%s)" "Ағылшын (АҚШ) (%s)" "Испан (АҚШ) (%s)" - "Хинглиш (%s)" - "Серб (%s)" + "Хинглиш (%s)" + "Серб (%s)" "%s (дәстүрлі)" "%s (шағын)" "Тіл жоқ (әліпби)" diff --git a/app/src/main/res/values-km/strings.xml b/app/src/main/res/values-km/strings.xml index 1ec16b438..d02de6218 100644 --- a/app/src/main/res/values-km/strings.xml +++ b/app/src/main/res/values-km/strings.xml @@ -52,13 +52,13 @@ "អង់គ្លេស (​អង់គ្លេស)" "អង់គ្លេស (សហរដ្ឋ​អាមេរិក)" "អេស្ប៉ាញ (សហរដ្ឋ​អាមេរិក​)" - "Hinglish" - "សើប (ឡាតាំង​)" + "Hinglish" + "សើប (ឡាតាំង​)" "អង់គ្លេស (ចក្រភព​អង់គ្លេស) (%s)" "អង់គ្លេស (អាមេរិក) (%s)" "អេស្ប៉ាញ (អាមេរិក) (%s)" - "Hinglish (%s)" - "សើប (%s)" + "Hinglish (%s)" + "សើប (%s)" "%s (អក្សរ​ពេញ)" "%s (បង្រួម)" "គ្មាន​ភាសា (អក្សរ​ក្រម)" diff --git a/app/src/main/res/values-kn/strings.xml b/app/src/main/res/values-kn/strings.xml index 5470db4d7..3b583e70e 100644 --- a/app/src/main/res/values-kn/strings.xml +++ b/app/src/main/res/values-kn/strings.xml @@ -52,13 +52,13 @@ "ಇಂಗ್ಲಿಷ್ (ಯುಕೆ)" "ಇಂಗ್ಲಿಷ್ (US)" "ಸ್ಪ್ಯಾನಿಷ್ (US)" - "ಹಿಂಗ್ಲಿಷ್" - "ಸರ್ಬಿಯನ್ (ಲ್ಯಾಟಿನ್)" + "ಹಿಂಗ್ಲಿಷ್" + "ಸರ್ಬಿಯನ್ (ಲ್ಯಾಟಿನ್)" "ಇಂಗ್ಲಿಷ್ (ಯುಕೆ) (%s)" "ಇಂಗ್ಲಿಷ್ (US) (%s)" "ಸ್ಪ್ಯಾನಿಷ್ (US) (%s)" - "ಹಿಂಗ್ಲಿಷ್ (%s)" - "ಸರ್ಬಿಯನ್ (%s)" + "ಹಿಂಗ್ಲಿಷ್ (%s)" + "ಸರ್ಬಿಯನ್ (%s)" "%s (ಸಾಂಪ್ರದಾಯಿಕ)" "%s (ಕಾಂಪ್ಯಾಕ್ಟ್‌‌)" "ಯಾವುದೇ ಭಾಷೆಯಿಲ್ಲ (ವರ್ಣಮಾಲೆ)" diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index b7ea8b9d8..5a5a51621 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -52,13 +52,13 @@ "영어(영국)" "영어(미국)" "스페인어(미국)" - "인도 영어" - "세르비아어(라틴 문자)" + "인도 영어" + "세르비아어(라틴 문자)" 영어(영국)(%s) 영어(미국)(%s) 스페인어 (미국) (%s) - 인도 영어(%s) - 세르비아어(%s) + 인도 영어(%s) + 세르비아어(%s) %s (번체) %s (소형) "언어 없음(알파벳)" diff --git a/app/src/main/res/values-ky/strings.xml b/app/src/main/res/values-ky/strings.xml index 367da78ce..5ba890347 100644 --- a/app/src/main/res/values-ky/strings.xml +++ b/app/src/main/res/values-ky/strings.xml @@ -52,13 +52,13 @@ "Англисче (UK)" "Англисче (US)" "Испанча (US)" - "Хинглиш" - "Сербче (Латын)" + "Хинглиш" + "Сербче (Латын)" "Англисче (UK) (%s)" "Англисче (US) (%s)" "Испанча (US) (%s)" - "Хинглиш (%s)" - "Сербче (%s)" + "Хинглиш (%s)" + "Сербче (%s)" "%s (Салттык)" "%s (Чакан)" "Тил жок (Алфавит)" diff --git a/app/src/main/res/values-lo/strings.xml b/app/src/main/res/values-lo/strings.xml index 19b571f7c..7f1928094 100644 --- a/app/src/main/res/values-lo/strings.xml +++ b/app/src/main/res/values-lo/strings.xml @@ -52,13 +52,13 @@ "ອັງກິດ (ສະຫະລາດຊະອານາຈັກ)" "ອັງກິດ (ສະຫະລັດຯ)" "ສະເປນ (ອາເມລິກາ)" - "ຮິງ​ລິສ" - "ເຊີ​ບຽນ (​ລາ​ຕິນ)" + "ຮິງ​ລິສ" + "ເຊີ​ບຽນ (​ລາ​ຕິນ)" "ອັງ​ກິດ (ສະ​ຫະ​ລາດ​ຊະ​ອາ​ນາ​ຈັກ) (%s)" "ອັງ​ກິດ (ສະ​ຫະ​ລັດຯ) (%s)" "ສະ​ແປນ​ນິດ (ສະ​ຫະ​ລັດຯ) (%s)" - "ຮິງ​ລິສ (%s)" - "ເຊີ​ບຽນ (%s)" + "ຮິງ​ລິສ (%s)" + "ເຊີ​ບຽນ (%s)" "%s (ດັ້ງ​ເດີມ)" "%s (ກະ​ທັດ​ຮັດ)" "ບໍ່ມີພາສາ (ໂຕອັກສອນ)" diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 56fa6c266..2d6635469 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -52,13 +52,13 @@ "Anglų k. (JK)" "Anglų k. (JAV)" "Ispanų k. (JAV)" - "Hindi ir anglų k. derinys" - "Serbų k. (lot. rašmenys)" + "Hindi ir anglų k. derinys" + "Serbų k. (lot. rašmenys)" "Anglų (JK) (%s)" "Anglų (JAV) (%s)" "Ispanų (JAV) (%s)" - "Hindi ir anglų derinys (%s)" - "Serbų k. (%s)" + "Hindi ir anglų derinys (%s)" + "Serbų k. (%s)" "%s (tradicinė)" "%s (kompaktiška)" "Kalbos nėra (abėcėlė)" diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index 65d34d4ed..7ae8c5eda 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -52,13 +52,13 @@ "Angļu valoda (Lielbritānija)" "Angļu valoda (ASV)" "Spāņu (ASV)" - "Hindi–angļu valoda" - "Serbu (latīņu)" + "Hindi–angļu valoda" + "Serbu (latīņu)" "Angļu (Lielbritānija) (%s)" "Angļu (ASV) (%s)" "Spāņu (ASV) (%s)" - "Hindi–angļu valoda (%s)" - "Serbu (%s)" + "Hindi–angļu valoda (%s)" + "Serbu (%s)" "%s (tradicionālā)" "%s (kompaktā)" "Nav valodas (alfabēts)" diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index bdf3056ae..fd04ecee8 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -52,13 +52,13 @@ "англиски (ОК)" "англиски (САД)" "шпански (САД)" - "Хинглиш" - "Српски (латиница)" + "Хинглиш" + "Српски (латиница)" "англиски (ОК) (%s)" "англиски (САД) (%s)" "шпански (САД) (%s)" - "Хинглиш (%s)" - "Српски (%s)" + "Хинглиш (%s)" + "Српски (%s)" "%s (традиционален)" "%s (Компактна)" "Нема јазик (азбука)" diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index cd7c24875..ceae9acd5 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -52,13 +52,13 @@ "ഇംഗ്ലീഷ് (യുകെ)" "ഇംഗ്ലീഷ് (യുഎസ്)" "സ്‌പാനിഷ് (യുഎസ്)" - "ഹിംഗ്ലീഷ്" - "സെർബിയൻ (ലാറ്റിൻ)" + "ഹിംഗ്ലീഷ്" + "സെർബിയൻ (ലാറ്റിൻ)" ഇംഗ്ലീഷ് (യുകെ) (%s) ഇംഗ്ലീഷ് (യുഎസ്) (%s) സ്‌പാനിഷ് (യുഎസ്) (%s) - ഹിംഗ്ലീഷ് (%s) - സെർബിയൻ (%s) + ഹിംഗ്ലീഷ് (%s) + സെർബിയൻ (%s) %s (പരമ്പരാഗതം) %s (കോം‌പാക്‌ട്) "ഭാഷയില്ല (അക്ഷരമാല)" diff --git a/app/src/main/res/values-mn/strings.xml b/app/src/main/res/values-mn/strings.xml index f8fc96e79..8a0f86357 100644 --- a/app/src/main/res/values-mn/strings.xml +++ b/app/src/main/res/values-mn/strings.xml @@ -52,13 +52,13 @@ "Англи (ИБ)" "Англи (АНУ)" "Испани (АНУ)" - "Хинглиш" - "Серьби хэл (латин)" + "Хинглиш" + "Серьби хэл (латин)" "Англи (ИБ) ( %s )" "Англи (АНУ) ( %s )" "Испани (АНУ-ын) (%s)" - "Хинглиш (%s)" - "Серьби хэл (%s)" + "Хинглиш (%s)" + "Серьби хэл (%s)" "%s (уламжлалт)" "%s (Компакт)" "Хэл байхгүй (Цагаан толгой)" diff --git a/app/src/main/res/values-mr/strings.xml b/app/src/main/res/values-mr/strings.xml index 46c3c96c1..a8d89a382 100644 --- a/app/src/main/res/values-mr/strings.xml +++ b/app/src/main/res/values-mr/strings.xml @@ -52,13 +52,13 @@ "इंग्रजी (यूके)" "इंग्रजी (यूएस)" "स्पॅनिश (यूएस)" - "हिंग्लिश" - "सर्बियन (लॅटिन)" + "हिंग्लिश" + "सर्बियन (लॅटिन)" "इंग्रजी (यूके) (%s)" "इंग्रजी (यूएस) (%s)" "स्पॅनिश (यूएस) (%s)" - "हिंग्लिश (%s)" - "सर्बियन (%s)" + "हिंग्लिश (%s)" + "सर्बियन (%s)" "%s (पारंपारिक)" "%s (संक्षिप्त)" "भाषा नाही (वर्णमाला)" diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index e0d6dc2f0..ff1556315 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -52,13 +52,13 @@ "Bahasa Inggeris (UK)" "Bahasa Inggeris (Australia)" "Bahasa Sepanyol (AS)" - "Hinglish" - "Bahasa Serbia (Latin)" + "Hinglish" + "Bahasa Serbia (Latin)" "Bahasa Inggeris (UK) (%s)" "Bahasa Inggeris (AS) (%s)" "Bahasa Sepanyol (AS) (%s)" - "Hinglish (%s)" - "Bahasa Serbia (%s)" + "Hinglish (%s)" + "Bahasa Serbia (%s)" "%s (Tradisional)" "%s (Sarat)" "Tiada bahasa (Abjad)" diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml index 61e43ae22..3888a1ddf 100644 --- a/app/src/main/res/values-my/strings.xml +++ b/app/src/main/res/values-my/strings.xml @@ -52,13 +52,13 @@ "အင်္ဂလိပ်(ယူကေ)" "အင်္ဂလိပ် (ယူအက်စ်)" "စပိန် (ယူအက်စ်)" - "ဟင်ဂလိပ်" - "ဆားဘီယား (လက်တင်)" + "ဟင်ဂလိပ်" + "ဆားဘီယား (လက်တင်)" "အင်္ဂလိပ် (ယူကေ) (%s)" "အင်္ဂလိပ် (ယူအက်စ်) (%s)" "စပိန် (ယူအက်စ်) (%s)" - "ဟင်ဂလိပ် (%s)" - "ဆားဘီယား (%s)" + "ဟင်ဂလိပ် (%s)" + "ဆားဘီယား (%s)" "%s (ရိုးရာ)" "%s (ကျစ်လစ်သော)" "ဘာသာစကားမရှိ (ဗျည်းအက္ခရာ)" diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index 597cbf572..2d6a20be4 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -52,13 +52,13 @@ "Engelsk (Storbritannia)" "Engelsk (USA)" "spansk (USA)" - "Hinglish" - "Serbisk (latin)" + "Hinglish" + "Serbisk (latin)" "Engelsk (Storbritannia) (%s)" "Engelsk (USA) (%s)" Spansk (USA) (%s) - "Hinglish (%s)" - "Serbisk (%s)" + "Hinglish (%s)" + "Serbisk (%s)" "%s (tradisjonelt)" "%s (kompakt)" "Ingen språk (alfabet)" diff --git a/app/src/main/res/values-ne/strings.xml b/app/src/main/res/values-ne/strings.xml index ce685c1a7..46e79b2ca 100644 --- a/app/src/main/res/values-ne/strings.xml +++ b/app/src/main/res/values-ne/strings.xml @@ -52,13 +52,13 @@ "अंग्रेजी (युके)" "अंग्रेजी (युएस्)" "स्पेनिस (युएस्)" - "हिङ्लिस" - "सर्बियाई (ल्याटिन)" + "हिङ्लिस" + "सर्बियाई (ल्याटिन)" "अंग्रेजी (बेलायत) ( %s )" "अंग्रेजी (अमेरिका) (%s)" "स्पेनेली (अमेरिका) (%s)" - "हिङ्लिस (%s)" - "सर्बियाई (%s)" + "हिङ्लिस (%s)" + "सर्बियाई (%s)" "%s (परम्परागत)" "%s (संकुचित)" "कुनै भाषा होइन (वर्णमाला)" diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index be2a82f06..57397f3d7 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -52,13 +52,13 @@ "Engels (GB)" "Engels (VS)" "Spaans (VS)" - "Hindi-Engels" - "Servisch (Latijns)" + "Hindi-Engels" + "Servisch (Latijns)" "Engels (VK) (%s)" "Engels (VS) (%s)" "Spaans (VS) (%s)" - "Hindi-Engels (%s)" - "Servisch (%s)" + "Hindi-Engels (%s)" + "Servisch (%s)" "%s (traditioneel)" "%s (compact)" "Geen taal (alfabet)" diff --git a/app/src/main/res/values-pa-rPK/strings.xml b/app/src/main/res/values-pa-rPK/strings.xml index 120c7c3b5..a5d67571a 100644 --- a/app/src/main/res/values-pa-rPK/strings.xml +++ b/app/src/main/res/values-pa-rPK/strings.xml @@ -23,7 +23,7 @@ نجی بݨاۓ سجھا مکھ لغت بہت پرجوش - ہِنگریزی + ہِنگریزی ایموجی کیبورڈ دی چوݨ مٹاؤ diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 9f19e00b5..bab098b04 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -52,13 +52,13 @@ "ਅੰਗ੍ਰੇਜ਼ੀ (ਯੂਕੇ)" "ਅੰਗ੍ਰੇਜੀ (ਅਮਰੀਕਾ)" "ਸਪੇਨੀ (ਅਮਰੀਕਾ)" - "ਹਿੰਗਲਿੰਸ਼" - "ਸਰਬੀਅਨ (ਲਾਤੀਨੀ)" + "ਹਿੰਗਲਿੰਸ਼" + "ਸਰਬੀਅਨ (ਲਾਤੀਨੀ)" "ਅੰਗਰੇਜ਼ੀ (ਯੂ.ਕੇ.) (%s)" "ਅੰਗਰੇਜ਼ੀ (ਅਮਰੀਕਾ) (%s)" "ਸਪੇਨੀ (ਅਮਰੀਕਾ) (%s)" - "ਹਿੰਗਲਿਸ਼(%s)" - "ਸਰਬੀਅਨ (%s)" + "ਹਿੰਗਲਿਸ਼(%s)" + "ਸਰਬੀਅਨ (%s)" "%s (ਪਰੰਪਰਿਕ)" "%s (ਸੰਖਿਪਤ)" "ਕੋਈ ਭਾਸ਼ਾ ਨਹੀਂ (ਵਰਨਮਾਲਾ)" diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 228ab4dc9..5fa652f10 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -53,13 +53,13 @@ "Angielski (Wielka Brytania)" "Angielski (Stany Zjednoczone)" "Hiszpański (USA)" - "Hinglish" - "Serbski (alfabet łaciński)" + "Hinglish" + "Serbski (alfabet łaciński)" Angielski (Wielka Brytania) (%s) Angielski (USA) (%s) Hiszpański (USA) (%s) - Hinglish (%s) - Serbski (%s) + Hinglish (%s) + Serbski (%s) %s (tradycyjny) %s (kompaktowa) "Standardowy (Alfabet)" diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 71dfe634b..24f2a3ab2 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -54,10 +54,10 @@ Inglês (Reino Unido) Inglês (EUA) Espanhol (EUA) - Hindi-Inglês - Sérvio (Latino) + Hindi-Inglês + Sérvio (Latino) Espanhol (EUA) (%s) - Sérvio %s + Sérvio %s Nepali (Nepal) %s (Tradicional) Bengali (Bangladesh) %s (Akkhor) Alfabeto (PC) @@ -122,7 +122,7 @@ Linha de números Inglês (Reino Unido) %s Inglês (EUA) %s - Hindi-inglês %s + Hindi-inglês %s Nenhum idioma (Alfabeto) Alfabeto (QWERTY) %s já está ativado nas configurações de \'Idiomas e Entrada\'. Esta etapa já está concluída. Vamos avançar para a próxima! diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index ac7f92b2a..40ee5adf9 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -53,13 +53,13 @@ Inglês (Reino Unido) "Inglês (EUA)" "Espanhol (EUA)" - Hindi-Inglês - Sérvio (Latino) + Hindi-Inglês + Sérvio (Latino) Inglês (Reino Unido) %s Inglês (EUA) %s Espanhol (EUA) (%s) - Hindi-inglês %s - Sérvio %s + Hindi-inglês %s + Sérvio %s %s (Tradicional) %s (Compacto) Nenhum idioma (alfabeto) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 860de07df..d9a09d6b2 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -52,13 +52,13 @@ Inglês (Reino Unido) Inglês (EUA) Espanhol (EUA) - Hindi-Inglês - "Sérvio (Latino)" + Hindi-Inglês + "Sérvio (Latino)" Inglês (Reino Unido) %s Inglês (EUA) %s Espanhol (EUA) (%s) - Hindi-inglês %s - Sérvio %s + Hindi-inglês %s + Sérvio %s %s (Tradicional) %s (Compacto) "Nenhum idioma (alfabeto)" diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index fb43bb9c6..2d9b08c08 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -52,13 +52,13 @@ "engleză (Regatul Unit)" "engleză (S.U.A.)" "spaniolă (S.U.A.)" - "Hinglish" - "Sârbă (caractere latine)" + "Hinglish" + "Sârbă (caractere latine)" "Engleză (Regatul Unit) (%s)" "Engleză (S.U.A.) (%s)" "Spaniolă (S.U.A.) (%s)" - "Hinglish (%s)" - "Sârbă (%s)" + "Hinglish (%s)" + "Sârbă (%s)" "%s (tradițională)" "%s (Compact)" "Nicio limbă (alfabet)" diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c7658caad..062996a75 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -53,13 +53,13 @@ Английский (Великобритания) Английский (США) Испанский (США) - Хинглиш - Сербский (латиница) + Хинглиш + Сербский (латиница) Английский (Великобритания, %s) Английский (США, %s) Испанский (США, %s) - Хинглиш (%s) - Сербский (%s) + Хинглиш (%s) + Сербский (%s) %s (классическая) %s (Компактная) Стандартная (Латиница) diff --git a/app/src/main/res/values-si/strings.xml b/app/src/main/res/values-si/strings.xml index 3b028f7a5..cb3f262e6 100644 --- a/app/src/main/res/values-si/strings.xml +++ b/app/src/main/res/values-si/strings.xml @@ -52,13 +52,13 @@ "ඉංග්‍රීසි (UK)" "ඉංග්‍රීසි (US)" "ස්පාඤ්ඤ (US)" - "හින්ග්ලිෂ්" - "සර්බියානු (ලතින්)" + "හින්ග්ලිෂ්" + "සර්බියානු (ලතින්)" "ඉංග්‍රීසි (එ.රා) (%s)" "ඉංග්‍රීසි (එ.ජ) (%s)" "ස්පාඤ්ඤ (එ.ජ) (%s)" - "හින්ග්ලිෂ් (%s)" - "සර්බියානු (%s)" + "හින්ග්ලිෂ් (%s)" + "සර්බියානු (%s)" "%s (සාම්ප්‍රදායික)" "%s (සංයුක්ත)" "භාෂාවක් නැත (අකාරාදිය)" diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index d2f1444de..f0fb20624 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -52,13 +52,13 @@ "angličtina (Veľká Británia)" "angličtina (USA)" "španielčina (USA)" - Indická angličtina - "srbčina (latinka)" + Indická angličtina + "srbčina (latinka)" "angličtina (VB) (%s)" "angličtina (USA) (%s)" "španielčina (USA) (%s)" - "Hinglish (%s)" - "srbčina (%s)" + "Hinglish (%s)" + "srbčina (%s)" "%s (tradičná)" "%s (kompaktná)" "Žiadny jazyk (latinka)" diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 3170f1405..0d268f61a 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -52,13 +52,13 @@ "angleščina (Združeno kraljestvo)" "angleščina (ZDA)" "španščina (ZDA)" - "Hindujska angleščina" - "Srbščina (latinica)" + "Hindujska angleščina" + "Srbščina (latinica)" "angleščina (VB) (%s)" "angleščina (ZDA) (%s)" "španščina (ZDA) (%s)" - "Hindujska angleščina (%s)" - "Srbščina (%s)" + "Hindujska angleščina (%s)" + "Srbščina (%s)" "%s (tradicionalna)" "%s (kompaktna)" "Brez jezika (latinice)" diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml index ebf4b831d..0123ff232 100644 --- a/app/src/main/res/values-sq/strings.xml +++ b/app/src/main/res/values-sq/strings.xml @@ -52,13 +52,13 @@ "anglisht (MB)" "anglisht (SHBA)" "spanjisht (SHBA)" - "hinglisht" - "serbisht (latin)" + "hinglisht" + "serbisht (latin)" "anglisht (MB) (%s)" "anglisht (SHBA) (%s)" "spanjisht (SHBA) (%s)" - "hinglisht (%s)" - "serbisht (%s)" + "hinglisht (%s)" + "serbisht (%s)" "%s (tradicionale)" "%s (kompakte)" "nuk ka gjuhë (alfabeti)" diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 6117c895a..92b9a4b96 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -52,13 +52,13 @@ "енглески (УК)" "енглески (САД)" "шпански (САД)" - "хенглески" - "српски (латиница)" + "хенглески" + "српски (латиница)" енглески (UK) (%s) енглески (US) (%s) шпански (US) (%s) - хенглески (%s) - српски (%s) + хенглески (%s) + српски (%s) %s (традиционални) %s (компактна) "Нема језика (абецеда)" diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 090685be2..9df00aeee 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -52,13 +52,13 @@ Engelska (UK) Engelska (USA) Spanska (USA) - Hingelska - "Serbiska (latinsk)" + Hingelska + "Serbiska (latinsk)" Engelska (UK) (%s) Engelska (USA) (%s) Spanska (USA) (%s) - Hingelska (%s) - Serbiska (%s) + Hingelska (%s) + Serbiska (%s) %s (traditionell) %s (kompakt) "Inget språk (alfabet)" diff --git a/app/src/main/res/values-sw/strings.xml b/app/src/main/res/values-sw/strings.xml index 4f3ed4369..53d9deae0 100644 --- a/app/src/main/res/values-sw/strings.xml +++ b/app/src/main/res/values-sw/strings.xml @@ -52,13 +52,13 @@ "Kiingereza cha (Uingereza)" "Kiingereza cha (Marekani)" "Kihispania (Marekani)" - "Hinglish" - "Kiserbia (Kilatino)" + "Hinglish" + "Kiserbia (Kilatino)" "Kiingereza (UK) (%s)" "Kiingereza (US) (%s)" "Kihispania (US) (%s)" - "Hinglish (%s)" - "Kiserbia (%s)" + "Hinglish (%s)" + "Kiserbia (%s)" "%s (cha Jadi)" "%s (Thabiti)" "Hakuna lugha (Alfabeti)" diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 23d97a65f..163568716 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -52,13 +52,13 @@ "ஆங்கிலம் (யூகே)" "ஆங்கிலம் (யூஎஸ்)" "ஸ்பானிஷ் (யூஎஸ்)" - "ஹிங்கிலிஷ்" - "செர்பியன் (லத்தீன்)" + "ஹிங்கிலிஷ்" + "செர்பியன் (லத்தீன்)" "ஆங்கிலம் (யூகே) (%s)" "ஆங்கிலம் (யூஎஸ்) (%s)" "ஸ்பானிஷ் (யூஎஸ்) (%s)" - "ஹிங்கிலிஷ் (%s)" - "செர்பியன் (%s)" + "ஹிங்கிலிஷ் (%s)" + "செர்பியன் (%s)" "%s (பாரம்பரியமானது)" "%s (வசதியான)" "மொழியில்லை (அகரவரிசை)" diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml index 730d982fe..0ac88da9d 100644 --- a/app/src/main/res/values-te/strings.xml +++ b/app/src/main/res/values-te/strings.xml @@ -52,13 +52,13 @@ "ఆంగ్లం (యుకె)" "ఆంగ్లం (యుఎస్)" "స్పానిష్ (యుఎస్)" - "హింగ్లీష్" - "సెర్బియన్ (లాటిన్)" + "హింగ్లీష్" + "సెర్బియన్ (లాటిన్)" "ఆంగ్లం (యుకె) (%s)" "ఆంగ్లం (యుఎస్) (%s)" "స్పానిష్ (యుఎస్) (%s)" - "హింగ్లీష్ (%s)" - "సెర్బియన్ (%s)" + "హింగ్లీష్ (%s)" + "సెర్బియన్ (%s)" "%s (సాంప్రదాయకం)" "%s (కాంపాక్ట్)" "భాష లేదు (ఆల్ఫాబెట్)" diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 85367c4a4..c0da1b52f 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -52,13 +52,13 @@ "อังกฤษ (สหราชอาณาจักร)" "อังกฤษ (อเมริกัน)" "สเปน (สหรัฐอเมริกา)" - "ภาษาอังกฤษผสมกับฮินดู" - "เซอร์เบีย (ละติน)" + "ภาษาอังกฤษผสมกับฮินดู" + "เซอร์เบีย (ละติน)" "อังกฤษ (สหราชอาณาจักร) (%s)" "อังกฤษ (สหรัฐอเมริกา) (%s)" "สเปน (สหรัฐอเมริกา) (%s)" - "ภาษาอังกฤษผสมกับฮินดู (%s)" - "เซอร์เบีย (%s)" + "ภาษาอังกฤษผสมกับฮินดู (%s)" + "เซอร์เบีย (%s)" "%s (ดั้งเดิม)" "%s (แบบกะทัดรัด)" "ไม่มีภาษา (ตัวอักษรละติน)" diff --git a/app/src/main/res/values-tl/strings.xml b/app/src/main/res/values-tl/strings.xml index 1a0157849..34c1f3cc6 100644 --- a/app/src/main/res/values-tl/strings.xml +++ b/app/src/main/res/values-tl/strings.xml @@ -52,13 +52,13 @@ "English (UK)" "English (United States)" "Spanish (US)" - "Hinglish" - "Serbian (Latin)" + "Hinglish" + "Serbian (Latin)" "English (UK) (%s)" "English (US) (%s)" "Spanish (US) (%s)" - "Hinglish (%s)" - "Serbian (%s)" + "Hinglish (%s)" + "Serbian (%s)" "%s (Traditional)" "%s (Compact)" "Walang wika (Alpabeto)" diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index ea79a6555..7a665194a 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -52,13 +52,13 @@ "İngilizce (BK)" "İngilizce (ABD)" "İspanyolca (ABD)" - "Hingilizce" - "Sırpça (Latin alfabesi)" + "Hingilizce" + "Sırpça (Latin alfabesi)" İngilizce (İngiltere) (%s) İngilizce (ABD) (%s) İspanyolca (ABD) (%s) - Hingilizce (%s) - Sırpça (%s) + Hingilizce (%s) + Sırpça (%s) %s (Geleneksel) %s (Kompakt) "Dil yok (Alfabe)" diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 854409ff2..b61bcdc93 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -53,13 +53,13 @@ "Англійська (Велика Британія)" "Англійська (США)" "Іспанська (США)" - "Хінґліш" - "Сербська (латиниця)" + "Хінґліш" + "Сербська (латиниця)" Англійська (Британія) (%s) Англійська (США) (%s) Іспанська (США) (%s) - Гінґліш (%s) - Сербська (%s) + Гінґліш (%s) + Сербська (%s) %s (Традиційна) %s (Компактна) "Стандартна (латиниця)" diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml index 058104cc8..3b842122e 100644 --- a/app/src/main/res/values-ur/strings.xml +++ b/app/src/main/res/values-ur/strings.xml @@ -52,13 +52,13 @@ "انگریزی (برطانیہ)" "انگریزی (امریکہ)" "ہسپانوی (امریکہ)" - "ہنگلش" - "سربیائی (لاطینی)" + "ہنگلش" + "سربیائی (لاطینی)" "انگریزی (برطانیہ) (%s)" "انگریزی (امریکہ) (%s)" "ہسپانوی (امریکہ) (%s)" - "ہنگلش (%s)" - "سربیائی (%s)" + "ہنگلش (%s)" + "سربیائی (%s)" "%s (روایتی)" "%s (کمپیکٹ)" "کوئی زبان نہیں (الفابیٹ)" diff --git a/app/src/main/res/values-uz/strings.xml b/app/src/main/res/values-uz/strings.xml index 3cfd14343..d3fe6e0b8 100644 --- a/app/src/main/res/values-uz/strings.xml +++ b/app/src/main/res/values-uz/strings.xml @@ -52,13 +52,13 @@ "Ingliz (Buyuk Britaniya)" "Ingliz (AQSH)" "Ispan (AQSH)" - "Hinglish" - "Serb (Lotin)" + "Hinglish" + "Serb (Lotin)" Ingliz (Buyuk Britaniya) (%s) Ingliz (AQSH) (%s) Ispan (AQSH) (%s) - Hinglish (%s) - Serb (%s) + Hinglish (%s) + Serb (%s) %s (An’anaviy) %s(Ixcham) "Til aniqlanmadi (lotin)" diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 7143eb0eb..63b710ba0 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -52,13 +52,13 @@ "Tiếng Anh (Anh)" "Tiếng Anh (Mỹ)" "Tiếng Tây Ban Nha (Mỹ)" - "Tiếng Anh-Hindi" - "Tiếng Serbia (La tinh)" + "Tiếng Anh-Hindi" + "Tiếng Serbia (La tinh)" "Tiếng Anh (Anh) (%s)" "Tiếng Anh (Mỹ) (%s)" "Tiếng Tây Ban Nha (Mỹ) (%s)" - "Tiếng Anh-Hindi (%s)" - "Tiếng Serbia (%s)" + "Tiếng Anh-Hindi (%s)" + "Tiếng Serbia (%s)" "%s (Truyền thống)" "%s (Viết tắt)" "Không ngôn ngữ nào (Bảng chữ cái)" diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 7f049f9d7..56a5ab734 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -52,13 +52,13 @@ "英语(英国)" "英语(美国)" "西班牙语(美国)" - "印地英语" - "塞尔维亚语(拉丁语布局)" + "印地英语" + "塞尔维亚语(拉丁语布局)" 英式英语 (%s) 英语(美国)(%s) 西班牙语(美国)(%s) - 英语(印度)(%s) - 塞尔维亚语 (%s) + 英语(印度)(%s) + 塞尔维亚语 (%s) %s(传统) %s (紧凑) "无语言(字母)" diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 57ca83142..34ddcc786 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -52,13 +52,13 @@ "英文 (英國)" "英文 (美國)" "西班牙文 (美國)" - "印度英文" - "塞爾維亞文 (拉丁文)" + "印度英文" + "塞爾維亞文 (拉丁文)" "英文 (英國) (%s)" "英文 (美國) (%s)" "西班牙文 (美國) (%s)" - "印度英文 (%s)" - "塞爾維亞文 (%s)" + "印度英文 (%s)" + "塞爾維亞文 (%s)" "%s (傳統)" "%s (精簡版)" "無語言 (字母)" diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index ba3a0fa86..ed6f2151e 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -52,13 +52,13 @@ "英文 (英國)" "英文 (美國)" "西班牙文 (美國)" - "印度英文" - "塞爾維亞文 (拉丁文)" + "印度英文" + "塞爾維亞文 (拉丁文)" "英文 (英國) (%s)" "英文 (美國) (%s)" "西班牙文 (美國) (%s)" - "印度英文 (%s)" - "塞爾維亞文 (%s)" + "印度英文 (%s)" + "塞爾維亞文 (%s)" "%s (傳統)" "%s (精簡)" "無語言 (字母)" diff --git a/app/src/main/res/values-zu/strings.xml b/app/src/main/res/values-zu/strings.xml index 93abf3e4e..64a204e0d 100644 --- a/app/src/main/res/values-zu/strings.xml +++ b/app/src/main/res/values-zu/strings.xml @@ -52,13 +52,13 @@ "i-English(UK)" "i-English (US)" "I-Spanish (US)" - "I-Hinglish" - "Isi-Serbian (Latin)" + "I-Hinglish" + "Isi-Serbian (Latin)" "I-English (UK) ( %s )" "I-English (US) ( %s )" "Isi-Spanish (US) ( %s )" - "I-Hinglish (%s)" - "Isi-Serbian (%s)" + "I-Hinglish (%s)" + "Isi-Serbian (%s)" "Isi-%s (Tradition)" "%s (Okuqoqene ndawonye)" "Alikho ulimi (Alfabhethi)" diff --git a/app/src/main/res/values/cm_strings.xml b/app/src/main/res/values/cm_strings.xml deleted file mode 100644 index 3b39ed220..000000000 --- a/app/src/main/res/values/cm_strings.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Alphabet (Bépo) - Hungarian (QWERTY) - diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 85dfdca70..269b3e171 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -10,21 +10,20 @@ explicit keyboard layout. The string resource name must be "subtype_" or "subtype_with_layout_. Please refer to strings.xml for these resources. --> - en_US - en_GB - es_US - hi_ZZ - hu_ZZ - sr_ZZ + en-US + en-GB + es-US + hi-Latn + sr-Latn - hi_ZZ - sr_ZZ + hi-Latn + sr-Latn - Hinglish - Srpski + Hinglish + Srpski %s diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9fa22bd22..28a47d164 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -279,10 +279,10 @@ (US) should be an abbreviation of United States to fit in the CHAR LIMIT. --> Spanish (US) - Hinglish + Hinglish - Serbian (Latin) + Serbian (Latin) @@ -297,10 +297,10 @@ Spanish (US) (%s) - Hinglish (%s) + Hinglish (%s) - Serbian (%s) + Serbian (%s) %s (Traditional) @@ -403,6 +403,17 @@ are used to. This keyboard does not provide a dictionary, and it is not tied to language among those that use the Latin alphabet. This keyboard is laid out in the Workman disposition rather than other common dispositions for Latin languages. [CHAR LIMIT=25] --> Alphabet (Workman) + + Alphabet (Bépo) Alphabet (PC) Emoji diff --git a/app/src/main/res/xml/method.xml b/app/src/main/res/xml/method.xml index b70e0a024..ecd3ffc09 100644 --- a/app/src/main/res/xml/method.xml +++ b/app/src/main/res/xml/method.xml @@ -451,10 +451,10 @@ This isn't based on the final specification. Was disabled because there is no LM yet, and this layout does not offer anything different. --> -