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.
This commit is contained in:
Helium314 2024-01-28 10:42:42 +01:00 committed by GitHub
parent 93dfecfe9e
commit ac7fb752df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
150 changed files with 1446 additions and 1909 deletions

View file

@ -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
}

View file

@ -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())
}
}

View file

@ -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

View file

@ -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) {

View file

@ -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<Locale> 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));

View file

@ -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
}

View file

@ -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<String, FileAndMatchLevel> 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<AssetFileAddress> 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<AssetFileAddress> 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;
}
}

View file

@ -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) {

View file

@ -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.
* <p>
* 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<Dictionary> dictList = new LinkedList<>();
ArrayList<AssetFileAddress> 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()
);
}
}
}

View file

@ -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<Dictionary>()
// 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 <type>_<language tag>.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<Dictionary>, 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()
}

View file

@ -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.
*
* <p>
* 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<String, String> 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<String, String> getHeaderAttributeMap() {
HashMap<String, String> 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<WordInputEventForPersonalization> 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<SuggestedWordInfo> 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<String, String> attributeMap) {
mAdditionalAttributeMap = attributeMap;
clear();
}
public void dumpAllWordsForDebug() {
reloadDictionaryIfRequired();
final String tag = TAG;

View file

@ -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;
}

View file

@ -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

View file

@ -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<Locale> 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<Locale> 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(

View file

@ -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<Locale, Locale> sLocaleMap = initializeLocaleMap();
private static HashMap<Locale, Locale> initializeLocaleMap() {
final HashMap<Locale, Locale> 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);
}

View file

@ -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 {

View file

@ -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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<String, Locale> 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<String> 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);
}
}

View file

@ -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 <T> getBestMatch(locale: Locale, collection: Collection<T>, 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<String, Locale>()
/**
* 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())
}
}

View file

@ -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.

View file

@ -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
}

View file

@ -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<Preference>("custom_background_image")?.setOnPreferenceClickListener { onClickLoadImage() }
findPreference<Preference>("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<SeekBarDialogPreference>(Settings.PREF_KEY_LONGPRESS_TIMEOUT)?.setInterface(object : ValueProxy {

View file

@ -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<MutableList<SubtypeInfo>> = 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 {

View file

@ -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<File>()
@ -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<View>(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<List<File>, Boolean> {
val localeString = locale.lowercase() // internal files and folders always use lowercase
fun getUserAndInternalDictionaries(context: Context, locale: Locale): Pair<List<File>, Boolean> {
val userDicts = mutableListOf<File>()
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<List<
}
if (hasInternalDict)
return userDicts to true
val language = localeString.languageConsideringZZ()
BinaryDictionaryGetter.getAssetsDictionaryList(context)?.forEach { dictFile ->
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<Locale> {
val mainLocale = mainLocaleString.toLocale()
private fun getAvailableSecondaryLocales(context: Context, mainLocale: Locale, asciiCapable: Boolean): Set<Locale> {
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
}

View file

@ -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<String, MutableList<SubtypeInfo>>()
private val sortedSubtypesByDisplayName = LinkedHashMap<String, MutableList<SubtypeInfo>>()
private val enabledSubtypes = mutableListOf<InputMethodSubtype>()
private val systemLocales = mutableListOf<Locale>()
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<Locale>.sortedAddToSubtypesAndRemoveFromAllSubtypes() {
val subtypesToAdd = mutableListOf<SubtypeInfo>()
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<SubtypeInfo>.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"

View file

@ -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<Locale> getSecondaryLocales(final SharedPreferences prefs, final String mainLocaleString) {
final String localesString = prefs.getString(PREF_SECONDARY_LOCALES_PREFIX + mainLocaleString.toLowerCase(Locale.ROOT), "");
public static List<Locale> getSecondaryLocales(final SharedPreferences prefs, final Locale mainLocale) {
final String localesString = prefs.getString(PREF_SECONDARY_LOCALES_PREFIX + mainLocale.toLanguageTag(), "");
final ArrayList<Locale> 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<String> locales) {
public static void setSecondaryLocales(final SharedPreferences prefs, final Locale mainLocale, final List<Locale> 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) {

View file

@ -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;

View file

@ -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());

View file

@ -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<LocaleRenderer> list, final String locale) {
final ArrayList<LocaleRenderer> 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<LocaleRenderer> getLocaleRendererList(final Context context) {
final TreeSet<String> sortedLanguages = UserDictionaryListFragment.getSortedDictionaryLocaleStrings(context);
final TreeSet<Locale> 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<LocaleRenderer> 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;

View file

@ -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));
}
});
}

View file

@ -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<String> sortedLanguages = getSortedDictionaryLocaleStrings(requireContext());
final TreeSet<Locale> 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<String> getSortedDictionaryLocaleStrings(final Context context) {
static TreeSet<Locale> getSortedDictionaryLocales(final Context context) {
final SharedPreferences prefs = DeviceProtectedUtils.getSharedPreferences(context);
final boolean localeSystemOnly = prefs.getBoolean(Settings.PREF_USE_SYSTEM_LOCALES, true);
final TreeSet<String> sortedLanguages = new TreeSet<>(String::compareToIgnoreCase);
final TreeSet<Locale> 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<Locale> {
@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());

View file

@ -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 });
}
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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<Integer, String> scriptToPunctuationRegexMap = new TreeMap<>();
private static final Map<String, String> 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.
*
* <p>
* 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.
*
* <p>
* 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();

View file

@ -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.
* </p>
* @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<String> 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}.
* </p>
* @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<String> 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 */,

View file

@ -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()

View file

@ -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.
* <p>
* 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.
* <p>
* Assumes file name main_[locale].dict
* <p>
* 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) {

View file

@ -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<Locale> {
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<String>()
val usedLocaleLanguageTags = hashSetOf<String>()
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"

View file

@ -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++;

View file

@ -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<Locale>()
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)

View file

@ -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<String, Integer> mLanguageCodeToScriptCode;
private final static ArraySet<Integer> 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;
}
}

View file

@ -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
}
}
}

View file

@ -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 "<null subtype>";
}
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);

View file

@ -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<InputMethodSubtype> {
require(initialized)
if (prefs.getBoolean(Settings.PREF_USE_SYSTEM_LOCALES, true))
@ -37,6 +39,22 @@ fun getAllAvailableSubtypes(): List<InputMethodSubtype> {
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<Locale> {
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<String> {
fun getAvailableSubtypeLocales(): Collection<Locale> {
require(initialized)
return resourceSubtypesByLocale.keys
}
@ -151,29 +169,29 @@ fun init(context: Context) {
private fun getDefaultEnabledSubtypes(): List<InputMethodSubtype> {
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<Locale, String> =
substringBefore(LOCALE_LAYOUT_SEPARATOR).constructLocale() to substringAfter(LOCALE_LAYOUT_SEPARATOR)
private fun Pair<Locale, String>.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<InputMethodSubtype>()
private val resourceSubtypesByLocale = LinkedHashMap<String, MutableList<InputMethodSubtype>>(100)
private val resourceSubtypesByLocale = LinkedHashMap<Locale, MutableList<InputMethodSubtype>>(100)
private val additionalSubtypes = mutableListOf<InputMethodSubtype>()
private val systemLocales = mutableListOf<Locale>()
private val systemSubtypes = mutableListOf<InputMethodSubtype>()
@ -295,11 +314,3 @@ private val systemSubtypes = mutableListOf<InputMethodSubtype>()
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

View file

@ -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()
}

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"Engels (VK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Engels (VS)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Spaans (VS)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serwies (Latyns)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Serwies (Latyns)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">Engels (VK) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">Engels (VS) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">Spaans (VS) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">Hinglish (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">Serwies (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">Hinglish (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">Serwies (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586"><xliff:g id="LANGUAGE_NAME" example="Nepali">%s</xliff:g> (Tradisioneel)</string>
<string name="subtype_generic_compact" msgid="3353673321203202922"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (Kompak)</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Geen taal nie (alfabet)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"እንግሊዘኛ (የታላቋ ብሪታንያ)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"እንግሊዘኛ (ዩ.ኤስ)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"ስፓኒሽኛ (ዩኤስ)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"ሂንግሊሽ"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"ሰርብያኛ (ላቲን)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"ሂንግሊሽ"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"ሰርብያኛ (ላቲን)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"እንግሊዝኛ (ዩኬ) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"እንግሊዝኛ (አሜሪካ) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"ስፓኒሽ (አሜሪካ) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"ሂንግሊሽ (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"ሰርቢያኛ (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"ሂንግሊሽ (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"ሰርቢያኛ (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (ተለምዷዊ)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (እስግ)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"ምንም ቋንቋ (ፊደላት)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"الإنجليزية (المملكة المتحدة)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"الإنجليزية (الولايات المتحدة)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"الإسبانية (الأميركية)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"هنجليزية"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"الصربية (اللاتينية)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"هنجليزية"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"الصربية (اللاتينية)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">الإنجليزية (المملكة المتحدة) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">الإنجليزية (الولايات المتحدة) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">الإسبانية (الولايات المتحدة) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">الهنجليزية (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">الصربية (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">الهنجليزية (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">الصربية (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586"><xliff:g id="LANGUAGE_NAME" example="Nepali">%s</xliff:g> (التقليدية)</string>
<string name="subtype_generic_compact" msgid="3353673321203202922"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (مضغوط)</string>
<string name="subtype_no_language" msgid="7137390094240139495">"بدون لغة (أبجدية)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"İngilis (BK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"İngilis (ABŞ)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"İspan (ABŞ)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hingilis"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serb (Latın)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hingilis"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Serb (Latın)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"İngilis (Britaniya) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"İngilis (Amerika) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"İspan (Amerika) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hingilis (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serb (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Hingilis (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"Serb (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Ənənəvi)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Kompakt)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Dil yoxdur (Əlifba)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"engleski (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"engleski (SAD)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"španski (SAD)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"hengleski"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"srpski (latinica)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"hengleski"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"srpski (latinica)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"engleski (UK) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"engleski (SAD) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"španski (SAD) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"hengleski (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"srpski (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"hengleski (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"srpski (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (tradicionalni)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (kompaktna)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Nema jezika (abeceda)"</string>

View file

@ -53,13 +53,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"Англійская (Вялікабрытанія)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Англійская (ЗША)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Іспанская (ЗША)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Хінгліш"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Сербская (Лацініца)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Хінгліш"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Сербская (Лацініца)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">Англійская (Вялікабрытанія) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">Англійская (ЗША) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">Іспанская (ЗША) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">Хінгліш (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">Сербская (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">Хінгліш (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">Сербская (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586"><xliff:g id="LANGUAGE_NAME" example="Nepali">%s</xliff:g> (Традыцыйная)</string>
<string name="subtype_generic_compact" msgid="3353673321203202922"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (Кампактная)</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Стандартная (Лацініца)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"английски (Великобритания)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"английски (САЩ)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"испански (САЩ)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Хинглиш"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Сръбска (латиница)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Хинглиш"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Сръбска (латиница)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"английски (Великобр.) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"английски (САЩ) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"испански (САЩ) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Хинглиш (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Сръбска (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Хинглиш (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"Сръбска (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (традиционна клавиатура)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (компактна)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Без език (латиница)"</string>

View file

@ -102,7 +102,7 @@
<string name="subtype_en_GB">ইংরেজি (ইউকে)</string>
<string name="subtype_en_US">ইংরেজি (ইউএস)</string>
<string name="subtype_es_US">স্প্যানিশ (ইউএস)</string>
<string name="subtype_hi_ZZ">হিংলিশ</string>
<string name="subtype_hi_Latn">হিংলিশ</string>
<string name="hidden_features_title">প্রচ্ছন্ন বৈশিষ্ট্য</string>
<string name="hidden_features_summary">অপরিদৃষ্ট হতে পারে এমন বৈশিষ্ট্যের বর্ণনা</string>
<string name="hidden_features_text">ডিভাইস সুরক্ষিত স্টোরেজ</string>
@ -126,13 +126,12 @@
&#9658; রুট উপলব্ধতাসহ ম্যানুয়াল ব্যাকআপ করা ব্যবহারকারীদের জন্য: Android 7 থেকে শেয়ারড্ প্রেফারেন্স ফাইল ডিফল্ট জায়গা নয়। কারণ অ্যাপ %s ব্যবহার করছে। &lt;br&gt;
ডিভাইস আনলকের আগে সেটিংস খোলার জন্য এটি প্রয়োজনীয়, যেমন: বুট করার সময়। &lt;br&gt;
ফাইলটি /data/user_de/0/package_id/shared_prefs/ থাকে। যদিও এটা ডিভাইস এবং অ্যান্ড্রয়েড সংস্করণের উপরে নির্ভর করে।</string>
<string name="subtype_hu_ZZ">হাঙ্গেরীয় (QWERTY)</string>
<string name="subtype_sr_ZZ">সার্বীয় (ল্যাটিন)</string>
<string name="subtype_sr_Latn">সার্বীয় (ল্যাটিন)</string>
<string name="subtype_with_layout_en_GB">ইংরেজি (ইউকে) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_en_US">ইংরেজি (ইউএস) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_es_US">স্প্যানিশ (ইউএস) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_ZZ">হিংলিশ (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_ZZ">সার্বিয়ান (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_Latn">হিংলিশ (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_Latn">সার্বিয়ান (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_generic_traditional"><xliff:g id="LANGUAGE_NAME" example="Nepali">%s</xliff:g> (প্রথাগত)</string>
<string name="subtype_with_layout_bn_BD"><xliff:g id="LANGUAGE_NAME" example="Bangla">%s</xliff:g> (অক্ষর)</string>
<string name="subtype_generic_compact"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (সংক্ষিপ্ত)</string>

View file

@ -53,13 +53,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"engleski (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"engleski (SAD)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"španski (SAD)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hindu engleski"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Srpski (latinica)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hindu engleski"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Srpski (latinica)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"Engleski (UK) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"Engleski (SAD) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"Španski (SAD) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hindu engleski (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Srpski (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Hindu engleski (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"Srpski (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (tradicionalan)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (kompaktan)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Nema jezika (abeceda)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"anglès (Regne Unit)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"anglès (EUA)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Espanyol (EUA)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">Hinglès</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbi (llatí)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">Hinglès</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Serbi (llatí)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">Anglès (Regne Unit) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">Angles (EUA) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">Espanyol (EUA) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">Hinglès (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">Serbi (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">Hinglès (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">Serbi (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586"><xliff:g id="LANGUAGE_NAME" example="Nepali">%s</xliff:g> (Tradicional)</string>
<string name="subtype_generic_compact" msgid="3353673321203202922"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (Compacte)</string>
<string name="subtype_no_language" msgid="7137390094240139495">"cap idioma (alfabet)"</string>

View file

@ -53,13 +53,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"Angličtina (Velká Británie)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Angličtina (USA)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Španělština (USA)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Srbština (Latinka)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Srbština (Latinka)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"Angličtina (Velká Británie) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"Angličtina (USA) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"Španělština (USA) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Srbština (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"Srbština (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Tradiční)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Kompaktní)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Standardní (Latinka)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"engelsk (Storbritannien)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"engelsk (USA)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Spansk (USA)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbisk (latinsk)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Serbisk (latinsk)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">Engelsk (Storbritannien) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">Engelsk (USA) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">Spansk (USA) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">Hinglish (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">Serbisk (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">Hinglish (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">Serbisk (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586"><xliff:g id="LANGUAGE_NAME" example="Nepali">%s</xliff:g> (traditionelt)</string>
<string name="subtype_generic_compact" msgid="3353673321203202922"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (kompakt)</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Intet sprog (Alfabet)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"Englisch (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Englisch (USA)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Spanisch (USA)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">Hinglisch</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbisch (Lateinisch)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">Hinglisch</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Serbisch (Lateinisch)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">Englisch (GB) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">Englisch (US) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">Spanisch (USA) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">Hinglisch (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">Serbisch (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">Hinglisch (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">Serbisch (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586"><xliff:g id="LANGUAGE_NAME" example="Nepali">%s</xliff:g> (Traditionell)</string>
<string name="subtype_generic_compact" msgid="3353673321203202922"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (Kompakt)</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Keine Sprache (lat. Alphabet)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"Αγγλικά (ΗΒ)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Αγγλικά (ΗΠΑ)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Ισπανικά (ΗΠΑ)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Σερβικά (Λατινικά)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Σερβικά (Λατινικά)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"Αγγλικά (Ηνωμένο Βασίλειο) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"Αγγλικά (ΗΠΑ) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"Ισπανικά (ΗΠΑ) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Σερβικά (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"Σερβικά (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Παραδοσιακά)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Συμπαγές)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Καμία γλώσσα (Αλφάβητο)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"English (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"English (US)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Spanish (US)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbian (Latin)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Serbian (Latin)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"English (UK) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"English (US) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"Spanish (US) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbian (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"Serbian (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Traditional)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Compact)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"No language (Alphabet)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"English (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"English (US)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Spanish (US)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbian (Latin)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Serbian (Latin)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"English (UK) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"English (US) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"Spanish (US) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbian (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"Serbian (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Traditional)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Compact)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"No language (Alphabet)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"English (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"English (US)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Spanish (US)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbian (Latin)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Serbian (Latin)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">English (UK) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">English (US) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">Spanish (US) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">Hinglish (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">Serbian (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">Hinglish (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">Serbian (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586"><xliff:g id="LANGUAGE_NAME" example="Nepali">%s</xliff:g> (Traditional)</string>
<string name="subtype_generic_compact" msgid="3353673321203202922"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (Compact)</string>
<string name="subtype_no_language" msgid="7137390094240139495">"No language (Alphabet)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"English (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"English (US)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Spanish (US)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbian (Latin)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Serbian (Latin)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"English (UK) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"English (US) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"Spanish (US) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbian (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"Serbian (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Traditional)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Compact)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"No language (Alphabet)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"English (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"English (US)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Spanish (US)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbian (Latin)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Serbian (Latin)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"English (UK) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"English (US) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"Spanish (US) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbian (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"Serbian (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Traditional)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Compact)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"No language (Alphabet)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"Inglés (Reino Unido)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Inglés (EE.UU.)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Español (EE.UU.)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbio (latino)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Serbio (latino)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"Inglés, Reino Unido (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"Inglés, EE. UU. (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"Español, EE. UU. (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbio (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"Serbio (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (tradicional)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (compacto)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Ningún idioma (alfabeto)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">Inglés (Reino Unido)</string>
<string name="subtype_en_US" msgid="6160452336634534239">Inglés (EE.UU.)</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Español (EE.UU.)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">inglés indio</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbio (latino)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">inglés indio</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Serbio (latino)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">Inglés (Reino Unido) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">Inglés (EE.UU.) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">Español (EE.UU.)) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">inglés indio (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">Serbio (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">inglés indio (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">Serbio (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586"><xliff:g id="LANGUAGE_NAME" example="Nepali">%s</xliff:g> (Tradicional)</string>
<string name="subtype_generic_compact" msgid="3353673321203202922"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (Compacto)</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Ningún idioma (alfabeto)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"Inglise (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Inglise (USA)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"hispaania (USA)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hindi-inglise"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbia (ladina)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hindi-inglise"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Serbia (ladina)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"Inglise (Ühendk.) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"Inglise (USA) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"Hispaania (USA) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hindi-inglise (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbia (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Hindi-inglise (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"Serbia (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (traditsiooniline)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (kompaktne)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Keel puudub (tähestik)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"Ingelesa (Erresuma Batua)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Ingelesa (AEB)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Gaztelania (AEB)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglisha"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbiarra (latindarra)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglisha"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Serbiarra (latindarra)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">Ingelesa (Erresuma Batua) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">Ingelesa (Estatu Batuak) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">Gaztelania (AEB) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">Hinglisha (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">Serbiarra (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">Hinglisha (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">Serbiarra (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586"><xliff:g id="LANGUAGE_NAME" example="Nepali">%s</xliff:g> (Tradizionala)</string>
<string name="subtype_generic_compact" msgid="3353673321203202922"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (Trinkoa)</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Ez dago hizkuntzarik (alfabetoa)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"انگلیسی (بریتانیا)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"انگلیسی (امریکا)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"اسپانیایی (امریکا)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"هندی انگلیسی"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"صربی (لاتین)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"هندی انگلیسی"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"صربی (لاتین)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">انگلیسی (بریتانیا) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">انگلیسی (امریکا) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">اسپانیایی (امریکا) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">هندی انگلیسی (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">صربی (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">هندی انگلیسی (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">صربی (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586"><xliff:g id="LANGUAGE_NAME" example="Nepali">%s</xliff:g> (سنتی)</string>
<string name="subtype_generic_compact" msgid="3353673321203202922"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (فشرده)</string>
<string name="subtype_no_language" msgid="7137390094240139495">"بدون زبان (حروف الفبا)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"englanti (Iso-Britannia)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"englanti (Yhdysvallat)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"espanja (Yhdysvallat)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hindienglanti"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"serbialainen (latinal.)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hindienglanti"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"serbialainen (latinal.)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"englanti (UK) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"englanti (US) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"espanja (US) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hindienglanti (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"serbialainen (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Hindienglanti (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"serbialainen (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (perinteinen)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (tiivis)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Ei kieltä (aakkoset)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"Anglais (britannique)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Anglais (États-Unis)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Espagnol (États-Unis)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbe (latin)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Serbe (latin)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"anglais (Royaume-Uni) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"anglais (États-Unis) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"espagnol (États-Unis) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbe (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"Serbe (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (traditionnel)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (compact)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Aucune langue (alphabet)"</string>

View file

@ -58,13 +58,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"Anglais (Royaume-Uni)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Anglais (États-Unis)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Espagnol (États-Unis)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hindi/Anglais"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbe (latin)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hindi/Anglais"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Serbe (latin)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">Anglais (Royaume-Uni) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">Anglais (États-Unis) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">Espagnol (États-Unis) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">Hindi/Anglais (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">Serbe (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">Hindi/Anglais (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">Serbe (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586"><xliff:g id="LANGUAGE_NAME" example="Nepali">%s</xliff:g> (Traditionnel)</string>
<string name="subtype_generic_compact" msgid="3353673321203202922"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (Compact)</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Aucune langue (latin)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"Inglés (Reino Unido)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Inglés (EUA)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Español (EUA)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbio (alfabeto latino)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Serbio (alfabeto latino)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"Inglés (Reino Unido) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"Inglés (EUA) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"Español (EUA) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbio (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"Serbio (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (tradicional)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (compacto)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Ningún idioma (alfabeto)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"અંગ્રેજી (યુકે)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"અંગ્રેજી (યુ એસ)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"સ્પેનિશ (US)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"હિંગ્લિશ"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"સર્બિયન (લેટિન)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"હિંગ્લિશ"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"સર્બિયન (લેટિન)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"અંગ્રેજી (યુકે) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"અંગ્રેજી (યુએસ) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"સ્પેનિશ (યુએસ) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"હિંગ્લિશ (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"સર્બિયન (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"હિંગ્લિશ (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"સર્બિયન (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (પરંપરાગત)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (કોમ્પેક્ટ)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"ભાષા નથી (આલ્ફાબેટ)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"अंग्रेज़ी (यूके)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"अंग्रेज़ी (यूएस)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"स्पेनिश (यूएस)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"हिंग्लिश"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"सर्बियाई (लैटिन)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"हिंग्लिश"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"सर्बियाई (लैटिन)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"अंग्रेज़ी (यूके) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"अंग्रेज़ी (यूएस) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"स्‍पेनिश (यूएस) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"हिंग्लिश (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"सर्बियाई (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"हिंग्लिश (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"सर्बियाई (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (पारंपरिक)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (संक्षिप्त)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"भाषा उपलब्ध नहीं है (लैटिन वर्णाक्षर)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"Engleski (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Engleski (SAD)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">Španjolski (SAD)</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Srpski (latinica)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Srpski (latinica)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">Engleski (UK) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">Engleski (US) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">Španjolski (US) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">Hinglish (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">Srpski (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">Hinglish (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">Srpski (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586"><xliff:g id="LANGUAGE_NAME" example="Nepali">%s</xliff:g> (traditionalno)</string>
<string name="subtype_generic_compact" msgid="3353673321203202922"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (kompaktno)</string>
<string name="subtype_no_language" msgid="7137390094240139495">Neodređen jezik (abeceda)</string>

View file

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--Generated by crowdin.com-->
<!--
Copyright (C) 2015 The Android Open Source Project
SPDX-License-Identifier: Apache-2.0
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- This string is displayed in the description for a keyboard type. It refers specifically to
the Latin alphabet, as opposed to Cyrillic, Arabic, Hebrew or other scripts.
When the device is configured to use a language using a script other than the Latin alphabet, the
user still needs a keyboard that can input Latin characters for passwords or login names for
example, and a way to switch to this Latin alphabet keyboard. This string is the description for
this keyboard, so users of other scripts should understand when they read this that it represents a
keyboard that is meant for them to be able to enter Latin characters as opposed to the script they
are used to. This keyboard does not provide a dictionary, and it is not tied to any specific
language among those that use the Latin alphabet. This keyboard is laid out in the Bépo
disposition rather than other common dispositions for Latin languages. [CHAR LIMIT=25] -->
<string name="subtype_no_language_bepo">Ábécé (Bépo)</string>
</resources>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"angol (brit)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"angol (amerikai)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"spanyol (USA)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish (hindi-angol)"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Szerb (latin)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish (hindi-angol)"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Szerb (latin)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">Angol (UK) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">Angol (USA) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">Spanyol (USA) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (hindi-angol, <xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">Szerb (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Hinglish (hindi-angol, <xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">Szerb (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (hagyományos)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (kompakt)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Nincs nyelv (ábécé)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"angol (brit)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"angol (amerikai)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"spanyol (USA)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish (hindi-angol)"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Szerb (latin)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish (hindi-angol)"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Szerb (latin)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"angol (UK) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"angol (USA) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"spanyol (USA) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (hindi-angol, <xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Szerb (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Hinglish (hindi-angol, <xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"Szerb (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (hagyományos)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (kompakt)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Nincs nyelv (ábécé)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"Անգլերեն (ՄԹ)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Անգլերեն (ԱՄՆ)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Իսպաներեն (ԱՄՆ)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Հինգլիշ"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Սերբերեն (Լատինական)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Հինգլիշ"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Սերբերեն (Լատինական)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"Անգլերեն (ՄԹ) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"Անգլերեն (ԱՄՆ) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"Իսպաներեն (ԱՄՆ) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Հինգլիշ (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Սերբերեն (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Հինգլիշ (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"Սերբերեն (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (ավանդական)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (սեղմ)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Ոչ մի լեզվով (Այբուբեն)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"Inggris (Inggris)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Inggris (AS)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Spanyol (AS)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbia (Latin)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Serbia (Latin)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">Inggris (UK) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">Inggris (US) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">Spanyol (US) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">Hinglish (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">Serbia (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">Hinglish (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">Serbia (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586"><xliff:g id="LANGUAGE_NAME" example="Nepali">%s</xliff:g> (Tradisional)</string>
<string name="subtype_generic_compact" msgid="3353673321203202922"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (Ringkas)</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Tidak ada bahasa (Abjad)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"Enskt (Bretland)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Enskt (Bandaríkin)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Spænskt (US)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbneskt (latneskt)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Serbneskt (latneskt)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"Enskt (Bretland) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"Enskt (Bandaríkin) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"Spænskt (Bandaríkin) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Serbneskt (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"Serbneskt (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (hefðbundið)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (lítið)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Ekkert tungumál (stafróf)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">Inglese (Regno Unito)</string>
<string name="subtype_en_US" msgid="6160452336634534239">Inglese (Stati Uniti)</string>
<string name="subtype_es_US" msgid="5583145191430180200">Spagnolo (Stati Uniti)</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">Serbo (Latino)</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">Serbo (Latino)</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">Inglese (Regno Unito) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">Inglese (Stati Uniti) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">Spagnolo (Stati Uniti) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">Hinglish (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">Serbo (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">Hinglish (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">Serbo (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586"><xliff:g id="LANGUAGE_NAME" example="Nepali">%s</xliff:g> (tradizionale)</string>
<string name="subtype_generic_compact" msgid="3353673321203202922"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (compatto)</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Nessuna lingua (alfabeto)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"אנגלית (בריטניה)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"אנגלית (ארה\"ב)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"ספרדית (ארצות הברית)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"אנגלית הודית"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"סרבית (באותיות לטיניות)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"אנגלית הודית"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"סרבית (באותיות לטיניות)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">אנגלית (בריטניה) <xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">אנגלית (ארה\"ב) <xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">ספרדית (ארה\"ב) <xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">אנגלית הודית <xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">סרבית ) <xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">אנגלית הודית <xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">סרבית ) <xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (מסורתית)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (קומפקטית)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"ללא שפה (אלף-בית)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"英語 (英国)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"英語 (米国)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"スペイン語 (米国)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"ヒングリッシュ"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"セルビア語(ラテン文字)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"ヒングリッシュ"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"セルビア語(ラテン文字)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"英語(英国)(<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"英語(米国)(<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"スペイン語(米国)(<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"ヒングリッシュ(<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"セルビア語(<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"ヒングリッシュ(<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"セルビア語(<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g>(伝統言語)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g>(コンパクト)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"言語なし(アルファベット)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"ინგლისური (გართ. სამ.)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"ინგლისური (აშშ)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"ესპანური (აშშ)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"ჰინგლისური"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"სერბული (ლათინური)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"ჰინგლისური"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"სერბული (ლათინური)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">ინგლისური (UK) <xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g></string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">ინგლისური (აშშ) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">ესპანური (აშშ) (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">ჰინგლისური (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">სერბული (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">ჰინგლისური (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">სერბული (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586"><xliff:g id="LANGUAGE_NAME" example="Nepali">%s</xliff:g> (ტრადიციული)</string>
<string name="subtype_generic_compact" msgid="3353673321203202922"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (კომპაქტური)</string>
<string name="subtype_no_language" msgid="7137390094240139495">"ენის გარეშე (ლათინური ანბანი)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"ағылшын (ҰБ)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"ағылшын (АҚШ)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Испан (АҚШ)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Хинглиш"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Серб (латын жазуы)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Хинглиш"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"Серб (латын жазуы)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"Ағылшын (Құрама Корольдік) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"Ағылшын (АҚШ) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"Испан (АҚШ) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Хинглиш (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"Серб (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Хинглиш (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"Серб (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (дәстүрлі)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (шағын)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Тіл жоқ (әліпби)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"អង់គ្លេស (​អង់គ្លេស)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"អង់គ្លេស (សហរដ្ឋ​អាមេរិក)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"អេស្ប៉ាញ (សហរដ្ឋ​អាមេរិក​)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"សើប (ឡាតាំង​)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"សើប (ឡាតាំង​)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"អង់គ្លេស (ចក្រភព​អង់គ្លេស) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"អង់គ្លេស (អាមេរិក) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"អេស្ប៉ាញ (អាមេរិក) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"សើប (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"Hinglish (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"សើប (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (អក្សរ​ពេញ)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (បង្រួម)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"គ្មាន​ភាសា (អក្សរ​ក្រម)"</string>

View file

@ -52,13 +52,13 @@
<string name="subtype_en_GB" msgid="88170601942311355">"ಇಂಗ್ಲಿಷ್ (ಯುಕೆ)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"ಇಂಗ್ಲಿಷ್ (US)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"ಸ್ಪ್ಯಾನಿಷ್ (US)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"ಹಿಂಗ್ಲಿಷ್"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"ಸರ್ಬಿಯನ್ (ಲ್ಯಾಟಿನ್)"</string>
<string name="subtype_hi_Latn" msgid="8860448146262798623">"ಹಿಂಗ್ಲಿಷ್"</string>
<string name="subtype_sr_Latn" msgid="9059219552986034343">"ಸರ್ಬಿಯನ್ (ಲ್ಯಾಟಿನ್)"</string>
<string name="subtype_with_layout_en_GB" msgid="1931018968641592304">"ಇಂಗ್ಲಿಷ್ (ಯುಕೆ) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_en_US" msgid="8809311287529805422">"ಇಂಗ್ಲಿಷ್ (US) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_es_US" msgid="510930471167541338">"ಸ್ಪ್ಯಾನಿಷ್ (US) (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_ZZ" msgid="6827402953860547044">"ಹಿಂಗ್ಲಿಷ್ (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_ZZ" msgid="2859024772719772407">"ಸರ್ಬಿಯನ್ (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_hi_Latn" msgid="6827402953860547044">"ಹಿಂಗ್ಲಿಷ್ (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_with_layout_sr_Latn" msgid="2859024772719772407">"ಸರ್ಬಿಯನ್ (<xliff:g id="KEYBOARD_LAYOUT">%s</xliff:g>)"</string>
<string name="subtype_generic_traditional" msgid="8584594350973800586">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (ಸಾಂಪ್ರದಾಯಿಕ)"</string>
<string name="subtype_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (ಕಾಂಪ್ಯಾಕ್ಟ್‌‌)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"ಯಾವುದೇ ಭಾಷೆಯಿಲ್ಲ (ವರ್ಣಮಾಲೆ)"</string>

Some files were not shown because too many files have changed in this diff Show more