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; boolean mIsSpellChecker;
int mKeyboardWidth; int mKeyboardWidth;
int mKeyboardHeight; int mKeyboardHeight;
int mScriptId = ScriptUtils.SCRIPT_LATIN; String mScript = ScriptUtils.SCRIPT_LATIN;
// Indicates if the user has enabled the split-layout preference // Indicates if the user has enabled the split-layout preference
// and the required ProductionFlags are enabled. // and the required ProductionFlags are enabled.
boolean mIsSplitLayoutEnabled; boolean mIsSplitLayoutEnabled;
@ -202,8 +202,8 @@ public final class KeyboardLayoutSet {
return keyboard; return keyboard;
} }
public int getScriptId() { public String getScript() {
return mParams.mScriptId; return mParams.mScript;
} }
public static final class Builder { public static final class Builder {
@ -298,7 +298,7 @@ public final class KeyboardLayoutSet {
public KeyboardLayoutSet build() { public KeyboardLayoutSet build() {
if (mParams.mSubtype == null) if (mParams.mSubtype == null)
throw new RuntimeException("KeyboardLayoutSet subtype is not specified"); 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 // todo: the whole parsing stuff below should be removed, but currently
// it simply breaks when it's not available // it simply breaks when it's not available
// for emojis, moreKeys and moreSuggestions there are relevant parameters included // 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) { if (null == mKeyboardLayoutSet) {
return ScriptUtils.SCRIPT_UNKNOWN; return ScriptUtils.SCRIPT_UNKNOWN;
} }
return mKeyboardLayoutSet.getScriptId(); return mKeyboardLayoutSet.getScript();
} }
public void switchToSubtype(InputMethodSubtype subtype) { public void switchToSubtype(InputMethodSubtype subtype) {

View file

@ -18,7 +18,6 @@ import android.graphics.Paint;
import android.graphics.Paint.Align; import android.graphics.Paint.Align;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.util.AttributeSet; import android.util.AttributeSet;
import org.dslul.openboard.inputmethod.latin.utils.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; 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.AccessibilityUtils;
import org.dslul.openboard.inputmethod.accessibility.MainKeyboardAccessibilityDelegate; import org.dslul.openboard.inputmethod.accessibility.MainKeyboardAccessibilityDelegate;
import org.dslul.openboard.inputmethod.annotations.ExternallyReferenced; 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.DrawingPreviewPlacerView;
import org.dslul.openboard.inputmethod.keyboard.internal.DrawingProxy; import org.dslul.openboard.inputmethod.keyboard.internal.DrawingProxy;
import org.dslul.openboard.inputmethod.keyboard.internal.GestureFloatingTextDrawingPreview; 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.settings.Settings;
import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils; import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils;
import org.dslul.openboard.inputmethod.latin.utils.LanguageOnSpacebarUtils; 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 org.dslul.openboard.inputmethod.latin.utils.TypefaceUtils;
import java.util.ArrayList; 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()); final List<Locale> secondaryLocalesToUse = withoutDuplicateLanguages(secondaryLocales, subtype.getLocale().getLanguage());
if (secondaryLocalesToUse.size() > 0) { if (secondaryLocalesToUse.size() > 0) {
StringBuilder sb = new StringBuilder(subtype.getMiddleDisplayName()); StringBuilder sb = new StringBuilder(subtype.getMiddleDisplayName());
final Locale displayLocale = getResources().getConfiguration().locale; final Locale displayLocale = ConfigurationCompatKt.locale(getResources().getConfiguration());
for (Locale locale : secondaryLocales) { for (Locale locale : secondaryLocales) {
sb.append(" - "); sb.append(" - ");
sb.append(locale.getDisplayLanguage(displayLocale)); 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.splitOnFirstSpacesOnly
import org.dslul.openboard.inputmethod.latin.common.splitOnWhitespace import org.dslul.openboard.inputmethod.latin.common.splitOnWhitespace
import org.dslul.openboard.inputmethod.latin.settings.Settings import org.dslul.openboard.inputmethod.latin.settings.Settings
import org.dslul.openboard.inputmethod.latin.utils.SubtypeLocaleUtils
import java.io.InputStream import java.io.InputStream
import java.util.Locale import java.util.Locale
import kotlin.math.round import kotlin.math.round
@ -244,11 +245,11 @@ private fun createLocaleKeyTexts(context: Context, params: KeyboardParams, moreK
private fun getStreamForLocale(locale: Locale, context: Context) = private fun getStreamForLocale(locale: Locale, context: Context) =
try { try {
if (locale.toString() == "zz") context.assets.open("$LANGUAGE_TEXTS_FOLDER/more_more_keys.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.toString().lowercase()}.txt") else context.assets.open("$LANGUAGE_TEXTS_FOLDER/${locale.toLanguageTag()}.txt")
} catch (_: Exception) { } catch (_: Exception) {
try { try {
context.assets.open("$LANGUAGE_TEXTS_FOLDER/${locale.language.lowercase()}.txt") context.assets.open("$LANGUAGE_TEXTS_FOLDER/${locale.language}.txt")
} catch (_: Exception) { } catch (_: Exception) {
null 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 // load blacklist
if (noExistingDictsForThisLocale) { 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()) if (!new File(newDictGroup.blacklistFileName).exists())
new File(context.getFilesDir().getAbsolutePath() + File.separator + "blacklists").mkdirs(); new File(context.getFilesDir().getAbsolutePath() + File.separator + "blacklists").mkdirs();
newDictGroup.blacklist.addAll(readBlacklistFile(newDictGroup.blacklistFileName)); newDictGroup.blacklist.addAll(readBlacklistFile(newDictGroup.blacklistFileName));
@ -494,7 +494,7 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
mainDicts[i] = null; mainDicts[i] = null;
continue; continue;
} }
mainDicts[i] = DictionaryFactory.createMainDictionaryFromManager(context, dictionaryGroup.mLocale); mainDicts[i] = DictionaryFactoryKt.createMainDictionary(context, dictionaryGroup.mLocale);
} }
synchronized (mLock) { 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 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.SuggestedWords.SuggestedWordInfo;
import org.dslul.openboard.inputmethod.latin.common.ComposedData; import org.dslul.openboard.inputmethod.latin.common.ComposedData;
import org.dslul.openboard.inputmethod.latin.common.FileUtils; 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.AsyncResultHolder;
import org.dslul.openboard.inputmethod.latin.utils.CombinedFormatUtils; import org.dslul.openboard.inputmethod.latin.utils.CombinedFormatUtils;
import org.dslul.openboard.inputmethod.latin.utils.ExecutorUtils; import org.dslul.openboard.inputmethod.latin.utils.ExecutorUtils;
import com.android.inputmethod.latin.utils.WordInputEventForPersonalization;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock; 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 * 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 * 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. * 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 * A class that extends this abstract class must have a static factory method named
* getDictionary(Context context, Locale locale, File dictFile, String dictNamePrefix) * getDictionary(Context context, Locale locale, File dictFile, String dictNamePrefix)
*/ */
@ -96,8 +93,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
private final ReentrantReadWriteLock mLock; private final ReentrantReadWriteLock mLock;
private Map<String, String> mAdditionalAttributeMap = null;
/* A extension for a binary dictionary file. */ /* A extension for a binary dictionary file. */
protected static final String DICT_FILE_EXTENSION = ".dict"; 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, public static String getDictName(final String name, final Locale locale,
final File dictFile) { 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) { private void asyncExecuteTaskWithWriteLock(final Runnable task) {
@ -191,9 +186,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
protected Map<String, String> getHeaderAttributeMap() { protected Map<String, String> getHeaderAttributeMap() {
HashMap<String, String> attributeMap = new HashMap<>(); HashMap<String, String> attributeMap = new HashMap<>();
if (mAdditionalAttributeMap != null) {
attributeMap.putAll(mAdditionalAttributeMap);
}
attributeMap.put(DictionaryHeader.DICTIONARY_ID_KEY, mDictName); attributeMap.put(DictionaryHeader.DICTIONARY_ID_KEY, mDictName);
attributeMap.put(DictionaryHeader.DICTIONARY_LOCALE_KEY, mLocale.toString()); attributeMap.put(DictionaryHeader.DICTIONARY_LOCALE_KEY, mLocale.toString());
attributeMap.put(DictionaryHeader.DICTIONARY_VERSION_KEY, 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 @Override
public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData, public ArrayList<SuggestedWordInfo> getSuggestions(final ComposedData composedData,
final NgramContext ngramContext, final long proximityInfoHandle, 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); 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() { public void dumpAllWordsForDebug() {
reloadDictionaryIfRequired(); reloadDictionaryIfRequired();
final String tag = TAG; 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.accessibility.AccessibilityUtils;
import org.dslul.openboard.inputmethod.annotations.UsedForTesting; 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.EditorInfoCompatUtils;
import org.dslul.openboard.inputmethod.compat.InsetsOutlineProvider; import org.dslul.openboard.inputmethod.compat.InsetsOutlineProvider;
import org.dslul.openboard.inputmethod.compat.ViewOutlineProviderCompatUtils; import org.dslul.openboard.inputmethod.compat.ViewOutlineProviderCompatUtils;
@ -281,7 +282,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
case MSG_RESUME_SUGGESTIONS: case MSG_RESUME_SUGGESTIONS:
latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor( latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor(
latinIme.mSettings.getCurrent(), latinIme.mSettings.getCurrent(),
latinIme.mKeyboardSwitcher.getCurrentKeyboardScriptId()); latinIme.mKeyboardSwitcher.getCurrentKeyboardScript());
break; break;
case MSG_REOPEN_DICTIONARIES: case MSG_REOPEN_DICTIONARIES:
// We need to re-evaluate the currently composing word in case the script has // 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 // case, we are about to go down but we still don't know it, however the system tells
// us there is no current subtype. // us there is no current subtype.
Log.e(TAG, "System is reporting no current subtype."); Log.e(TAG, "System is reporting no current subtype.");
subtypeLocale = getResources().getConfiguration().locale; subtypeLocale = ConfigurationCompatKt.locale(getResources().getConfiguration());
} else { } else {
subtypeLocale = subtypeSwitcherLocale; subtypeLocale = subtypeSwitcherLocale;
} }
@ -1467,7 +1468,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mInputLogic.finishInput(); mInputLogic.finishInput();
int newPosition = mInputLogic.mConnection.mExpectedSelStart + moveSteps; int newPosition = mInputLogic.mConnection.mExpectedSelStart + moveSteps;
mInputLogic.mConnection.setSelection(newPosition, newPosition); mInputLogic.mConnection.setSelection(newPosition, newPosition);
mInputLogic.restartSuggestionsOnWordTouchedByCursor(mSettings.getCurrent(), mKeyboardSwitcher.getCurrentKeyboardScriptId()); mInputLogic.restartSuggestionsOnWordTouchedByCursor(mSettings.getCurrent(), mKeyboardSwitcher.getCurrentKeyboardScript());
} }
@Override @Override
@ -1606,7 +1607,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final InputTransaction completeInputTransaction = final InputTransaction completeInputTransaction =
mInputLogic.onCodeInput(mSettings.getCurrent(), event, mInputLogic.onCodeInput(mSettings.getCurrent(), event,
mKeyboardSwitcher.getKeyboardShiftMode(), mKeyboardSwitcher.getKeyboardShiftMode(),
mKeyboardSwitcher.getCurrentKeyboardScriptId(), mHandler); mKeyboardSwitcher.getCurrentKeyboardScript(), mHandler);
updateStateAfterInputTransaction(completeInputTransaction); updateStateAfterInputTransaction(completeInputTransaction);
mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState()); mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState());
} }
@ -1763,7 +1764,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final InputTransaction completeInputTransaction = mInputLogic.onPickSuggestionManually( final InputTransaction completeInputTransaction = mInputLogic.onPickSuggestionManually(
mSettings.getCurrent(), suggestionInfo, mSettings.getCurrent(), suggestionInfo,
mKeyboardSwitcher.getKeyboardShiftMode(), mKeyboardSwitcher.getKeyboardShiftMode(),
mKeyboardSwitcher.getCurrentKeyboardScriptId(), mKeyboardSwitcher.getCurrentKeyboardScript(),
mHandler); mHandler);
updateStateAfterInputTransaction(completeInputTransaction); updateStateAfterInputTransaction(completeInputTransaction);
} }
@ -1915,7 +1916,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mInputLogic.onCodeInput(mSettings.getCurrent(), event, mInputLogic.onCodeInput(mSettings.getCurrent(), event,
mKeyboardSwitcher.getKeyboardShiftMode(), mKeyboardSwitcher.getKeyboardShiftMode(),
// TODO: this is not necessarily correct for a hardware keyboard right now // TODO: this is not necessarily correct for a hardware keyboard right now
mKeyboardSwitcher.getCurrentKeyboardScriptId(), mKeyboardSwitcher.getCurrentKeyboardScript(),
mHandler); mHandler);
return true; return true;
} }

View file

@ -645,10 +645,10 @@ public final class RichInputConnection implements PrivateCommandPerformer {
mIC.performContextMenuAction(android.R.id.selectAll); 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 (!isConnected()) return;
if (mExpectedSelStart != mExpectedSelEnd) return; // already something selected 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; if (range == null) return;
mIC.setSelection(mExpectedSelStart - range.getNumberOfCharsInWordBeforeCursor(), mExpectedSelStart + range.getNumberOfCharsInWordAfterCursor()); 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, 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. // We always consider word connectors part of compositions.
return spacingAndPunctuations.isWordConnector(codePoint) return spacingAndPunctuations.isWordConnector(codePoint)
// Otherwise, it's part of composition if it's part of script and not a separator. // Otherwise, it's part of composition if it's part of script and not a separator.
|| (!spacingAndPunctuations.isWordSeparator(codePoint) || (!spacingAndPunctuations.isWordSeparator(codePoint)
&& ScriptUtils.isLetterPartOfScript(codePoint, scriptId)); && ScriptUtils.isLetterPartOfScript(codePoint, script));
} }
/** /**
* Returns the text surrounding the cursor. * Returns the text surrounding the cursor.
* *
* @param spacingAndPunctuations the rules for spacing and punctuation * @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 * @return a range containing the text surrounding the cursor
*/ */
public TextRange getWordRangeAtCursor(final SpacingAndPunctuations spacingAndPunctuations, public TextRange getWordRangeAtCursor(final SpacingAndPunctuations spacingAndPunctuations,
final int scriptId, final boolean justDeleted) { final String script, final boolean justDeleted) {
mIC = mParent.getCurrentInputConnection(); mIC = mParent.getCurrentInputConnection();
if (!isConnected()) { if (!isConnected()) {
return null; 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 // we need text before, and text after is either empty or a separator or similar
if (justDeleted && before.length() > 0 && if (justDeleted && before.length() > 0 &&
(after.length() == 0 (after.length() == 0
|| !isPartOfCompositionForScript(Character.codePointAt(after, 0), spacingAndPunctuations, scriptId) || !isPartOfCompositionForScript(Character.codePointAt(after, 0), spacingAndPunctuations, script)
) )
) { ) {
// issue: // issue:
@ -786,7 +786,7 @@ public final class RichInputConnection implements PrivateCommandPerformer {
int endIndexInAfter = -1; int endIndexInAfter = -1;
while (startIndexInBefore > 0) { while (startIndexInBefore > 0) {
final int codePoint = Character.codePointBefore(before, startIndexInBefore); final int codePoint = Character.codePointBefore(before, startIndexInBefore);
if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, scriptId)) { if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, script)) {
if (Character.isWhitespace(codePoint) || !spacingAndPunctuations.mCurrentLanguageHasSpaces) if (Character.isWhitespace(codePoint) || !spacingAndPunctuations.mCurrentLanguageHasSpaces)
break; break;
// continue to the next whitespace and see whether this contains a sometimesWordConnector // 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) { if (endIndexInAfter == -1) {
while (++endIndexInAfter < after.length()) { while (++endIndexInAfter < after.length()) {
final int codePoint = Character.codePointAt(after, endIndexInAfter); final int codePoint = Character.codePointAt(after, endIndexInAfter);
if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, scriptId)) { if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, script)) {
if (Character.isWhitespace(codePoint) || !spacingAndPunctuations.mCurrentLanguageHasSpaces) if (Character.isWhitespace(codePoint) || !spacingAndPunctuations.mCurrentLanguageHasSpaces)
break; break;
// continue to the next whitespace and see whether this contains a sometimesWordConnector // 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.inputmethodservice.InputMethodService;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.IBinder; import android.os.IBinder;
import org.dslul.openboard.inputmethod.latin.utils.Log;
import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.InputMethodSubtype;
import org.dslul.openboard.inputmethod.annotations.UsedForTesting; 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.settings.Settings;
import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils; import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils;
import org.dslul.openboard.inputmethod.latin.utils.LanguageOnSpacebarUtils; 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.ScriptUtils;
import org.dslul.openboard.inputmethod.latin.utils.SubtypeLocaleUtils; import org.dslul.openboard.inputmethod.latin.utils.SubtypeLocaleUtils;
import org.dslul.openboard.inputmethod.latin.utils.SubtypeSettingsKt; import org.dslul.openboard.inputmethod.latin.utils.SubtypeSettingsKt;
import org.dslul.openboard.inputmethod.latin.utils.SubtypeUtilsKt;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -293,14 +294,14 @@ public class RichInputMethodManager {
return keyboardCount > 1; return keyboardCount > 1;
} }
public InputMethodSubtype findSubtypeByLocaleAndKeyboardLayoutSet(final String localeString, public InputMethodSubtype findSubtypeByLocaleAndKeyboardLayoutSet(final Locale locale,
final String keyboardLayoutSetName) { final String keyboardLayoutSetName) {
final InputMethodInfo myImi = getInputMethodInfoOfThisIme(); final InputMethodInfo myImi = getInputMethodInfoOfThisIme();
final int count = myImi.getSubtypeCount(); final int count = myImi.getSubtypeCount();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
final InputMethodSubtype subtype = myImi.getSubtypeAt(i); final InputMethodSubtype subtype = myImi.getSubtypeAt(i);
final String layoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); final String layoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
if (localeString.equals(subtype.getLocale()) if (locale.equals(SubtypeUtilsKt.locale(subtype))
&& keyboardLayoutSetName.equals(layoutName)) { && keyboardLayoutSetName.equals(layoutName)) {
return subtype; return subtype;
} }
@ -316,7 +317,7 @@ public class RichInputMethodManager {
// search for exact match // search for exact match
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
final InputMethodSubtype subtype = subtypes.get(i); final InputMethodSubtype subtype = subtypes.get(i);
final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype); final Locale subtypeLocale = SubtypeUtilsKt.locale(subtype);
if (subtypeLocale.equals(locale)) { if (subtypeLocale.equals(locale)) {
return subtype; return subtype;
} }
@ -324,7 +325,7 @@ public class RichInputMethodManager {
// search for language + country + variant match // search for language + country + variant match
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
final InputMethodSubtype subtype = subtypes.get(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()) && if (subtypeLocale.getLanguage().equals(locale.getLanguage()) &&
subtypeLocale.getCountry().equals(locale.getCountry()) && subtypeLocale.getCountry().equals(locale.getCountry()) &&
subtypeLocale.getVariant().equals(locale.getVariant())) { subtypeLocale.getVariant().equals(locale.getVariant())) {
@ -334,7 +335,7 @@ public class RichInputMethodManager {
// search for language + country match // search for language + country match
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
final InputMethodSubtype subtype = subtypes.get(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()) && if (subtypeLocale.getLanguage().equals(locale.getLanguage()) &&
subtypeLocale.getCountry().equals(locale.getCountry())) { subtypeLocale.getCountry().equals(locale.getCountry())) {
return subtype; return subtype;
@ -344,7 +345,7 @@ public class RichInputMethodManager {
final SharedPreferences prefs = DeviceProtectedUtils.getSharedPreferences(mContext); final SharedPreferences prefs = DeviceProtectedUtils.getSharedPreferences(mContext);
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
final InputMethodSubtype subtype = subtypes.get(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); final List<Locale> secondaryLocales = Settings.getSecondaryLocales(prefs, subtypeLocale);
for (final Locale secondaryLocale : secondaryLocales) { for (final Locale secondaryLocale : secondaryLocales) {
if (secondaryLocale.equals(locale)) { if (secondaryLocale.equals(locale)) {
@ -355,7 +356,7 @@ public class RichInputMethodManager {
// search for language match // search for language match
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
final InputMethodSubtype subtype = subtypes.get(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())) { if (subtypeLocale.getLanguage().equals(locale.getLanguage())) {
return subtype; return subtype;
} }
@ -363,7 +364,7 @@ public class RichInputMethodManager {
// search for secondary language match // search for secondary language match
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
final InputMethodSubtype subtype = subtypes.get(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); final List<Locale> secondaryLocales = Settings.getSecondaryLocales(prefs, subtypeLocale);
for (final Locale secondaryLocale : secondaryLocales) { for (final Locale secondaryLocale : secondaryLocales) {
if (secondaryLocale.getLanguage().equals(locale.getLanguage())) { 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 // 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 // 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); final String script = ScriptUtils.script(locale);
if (script != ScriptUtils.getScriptFromSpellCheckerLocale(getCurrentSubtypeLocale())) { if (!script.equals(ScriptUtils.script(getCurrentSubtypeLocale()))) {
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
final InputMethodSubtype subtype = subtypes.get(i); final InputMethodSubtype subtype = subtypes.get(i);
final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype); final Locale subtypeLocale = SubtypeUtilsKt.locale(subtype);
if (ScriptUtils.getScriptFromSpellCheckerLocale(subtypeLocale) == script) { if (ScriptUtils.script(subtypeLocale).equals(script)) {
return subtype; return subtype;
} }
} }
@ -418,7 +419,7 @@ public class RichInputMethodManager {
final RichInputMethodSubtype richSubtype = mCurrentRichInputMethodSubtype; final RichInputMethodSubtype richSubtype = mCurrentRichInputMethodSubtype;
final boolean implicitlyEnabledSubtype = checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled( final boolean implicitlyEnabledSubtype = checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(
richSubtype.getRawSubtype()); richSubtype.getRawSubtype());
final Locale systemLocale = mContext.getResources().getConfiguration().locale; final Locale systemLocale = ConfigurationCompatKt.locale(mContext.getResources().getConfiguration());
LanguageOnSpacebarUtils.onSubtypeChanged( LanguageOnSpacebarUtils.onSubtypeChanged(
richSubtype, implicitlyEnabledSubtype, systemLocale); richSubtype, implicitlyEnabledSubtype, systemLocale);
LanguageOnSpacebarUtils.setEnabledSubtypes(getMyEnabledInputMethodSubtypeList( LanguageOnSpacebarUtils.setEnabledSubtypes(getMyEnabledInputMethodSubtypeList(

View file

@ -6,17 +6,15 @@
package org.dslul.openboard.inputmethod.latin; package org.dslul.openboard.inputmethod.latin;
import android.os.Build;
import android.view.inputmethod.InputMethodSubtype; 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.Constants;
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils; import org.dslul.openboard.inputmethod.latin.common.LocaleUtils;
import org.dslul.openboard.inputmethod.latin.utils.CustomLayoutUtilsKt; import org.dslul.openboard.inputmethod.latin.utils.CustomLayoutUtilsKt;
import org.dslul.openboard.inputmethod.latin.utils.Log; import org.dslul.openboard.inputmethod.latin.utils.Log;
import org.dslul.openboard.inputmethod.latin.utils.SubtypeLocaleUtils; 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 java.util.Locale;
import static org.dslul.openboard.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE; import static org.dslul.openboard.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE;
@ -33,30 +31,18 @@ import androidx.annotation.Nullable;
public class RichInputMethodSubtype { public class RichInputMethodSubtype {
private static final String TAG = RichInputMethodSubtype.class.getSimpleName(); 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 @NonNull
private final InputMethodSubtype mSubtype; private final InputMethodSubtype mSubtype;
@NonNull @NonNull
private final Locale mLocale; private final Locale mLocale;
@NonNull // The subtype is considered RTL if the language of the main subtype is RTL.
private final Locale mOriginalLocale; // 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) { public RichInputMethodSubtype(@NonNull final InputMethodSubtype subtype) {
mSubtype = subtype; mSubtype = subtype;
mOriginalLocale = InputMethodSubtypeCompatUtils.getLocaleObject(mSubtype); mLocale = SubtypeUtilsKt.locale(mSubtype);
final Locale mappedLocale = sLocaleMap.get(mOriginalLocale); mIsRtl = LocaleUtils.isRtlLanguage(mLocale);
mLocale = mappedLocale != null ? mappedLocale : mOriginalLocale;
} }
// Extra values are determined by the primary subtype. This is probably right, but // Extra values are determined by the primary subtype. This is probably right, but
@ -71,7 +57,7 @@ public class RichInputMethodSubtype {
} }
public boolean isNoLanguage() { public boolean isNoLanguage() {
return SubtypeLocaleUtils.NO_LANGUAGE.equals(mSubtype.getLocale()); return SubtypeLocaleUtils.NO_LANGUAGE.equals(mLocale.getLanguage());
} }
public boolean isCustom() { public boolean isCustom() {
@ -105,7 +91,7 @@ public class RichInputMethodSubtype {
if (isNoLanguage()) { if (isNoLanguage()) {
return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype); return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype);
} }
return SubtypeLocaleUtils.getSubtypeLocaleDisplayName(mSubtype.getLocale()); return SubtypeLocaleUtils.getSubtypeLocaleDisplayName(mLocale);
} }
// Get the RichInputMethodSubtype's middle display name in its locale. // Get the RichInputMethodSubtype's middle display name in its locale.
@ -114,7 +100,7 @@ public class RichInputMethodSubtype {
if (isNoLanguage()) { if (isNoLanguage()) {
return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype); return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype);
} }
return SubtypeLocaleUtils.getSubtypeLanguageDisplayName(mSubtype.getLocale()); return SubtypeLocaleUtils.getSubtypeLanguageDisplayName(mLocale);
} }
@Override @Override
@ -141,14 +127,8 @@ public class RichInputMethodSubtype {
return mLocale; return mLocale;
} }
@NonNull
public Locale getOriginalLocale() {
return mOriginalLocale;
}
public boolean isRtlSubtype() { public boolean isRtlSubtype() {
// The subtype is considered RTL if the language of the main subtype is RTL. return mIsRtl;
return LocaleUtils.isRtlLanguage(mLocale);
} }
// TODO: remove this method // TODO: remove this method
@ -215,7 +195,7 @@ public class RichInputMethodSubtype {
RichInputMethodSubtype noLanguageSubtype = sNoLanguageSubtype; RichInputMethodSubtype noLanguageSubtype = sNoLanguageSubtype;
if (noLanguageSubtype == null) { if (noLanguageSubtype == null) {
final InputMethodSubtype rawNoLanguageSubtype = RichInputMethodManager.getInstance() final InputMethodSubtype rawNoLanguageSubtype = RichInputMethodManager.getInstance()
.findSubtypeByLocaleAndKeyboardLayoutSet(SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY); .findSubtypeByLocaleAndKeyboardLayoutSet(LocaleUtils.constructLocale(SubtypeLocaleUtils.NO_LANGUAGE), SubtypeLocaleUtils.QWERTY);
if (rawNoLanguageSubtype != null) { if (rawNoLanguageSubtype != null) {
noLanguageSubtype = new RichInputMethodSubtype(rawNoLanguageSubtype); noLanguageSubtype = new RichInputMethodSubtype(rawNoLanguageSubtype);
} }

View file

@ -6,7 +6,6 @@
package org.dslul.openboard.inputmethod.latin; package org.dslul.openboard.inputmethod.latin;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.database.ContentObserver; import android.database.ContentObserver;
import android.database.Cursor; import android.database.Cursor;
@ -55,6 +54,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
private static final String NAME = "userunigram"; private static final String NAME = "userunigram";
private ContentObserver mObserver; private ContentObserver mObserver;
// this really needs to be the locale string, as it interacts with system
final private String mLocaleString; final private String mLocaleString;
final private boolean mAlsoUseMoreRestrictiveLocales; final private boolean mAlsoUseMoreRestrictiveLocales;
@ -71,7 +71,6 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
mLocaleString = localeStr; mLocaleString = localeStr;
} }
mAlsoUseMoreRestrictiveLocales = alsoUseMoreRestrictiveLocales; mAlsoUseMoreRestrictiveLocales = alsoUseMoreRestrictiveLocales;
ContentResolver cres = context.getContentResolver();
mObserver = new ContentObserver(null) { mObserver = new ContentObserver(null) {
@Override @Override
@ -79,7 +78,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
setNeedsToRecreate(); setNeedsToRecreate();
} }
}; };
cres.registerContentObserver(Words.CONTENT_URI, true, mObserver); context.getContentResolver().registerContentObserver(Words.CONTENT_URI, true, mObserver);
reloadDictionaryIfRequired(); reloadDictionaryIfRequired();
} }
@ -104,7 +103,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
public void loadInitialContentsLocked() { public void loadInitialContentsLocked() {
// Split the locale. For example "en" => ["en"], "de_DE" => ["de", "DE"], // 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. // "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. // For this example, we'll look at the "en_US_POSIX" case.
final String[] localeElements = final String[] localeElements =
TextUtils.isEmpty(mLocaleString) ? new String[] {} : mLocaleString.split("_", 3); 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=?)" // and request = "(locale is NULL) or (locale=?) or (locale=?) or (locale=?)"
final String[] requestArguments; final String[] requestArguments;
// If length == 3, we already have all the arguments we need (common prefix is meaningless // If length == 3, we already have all the arguments we need (common prefix is meaningless inside variants)
// inside variants
if (mAlsoUseMoreRestrictiveLocales && length < 3) { if (mAlsoUseMoreRestrictiveLocales && length < 3) {
request.append(" or (locale like ?)"); request.append(" or (locale like ?)");
// The following creates an array with one more (null) position // The following creates an array with one more (null) position
@ -151,18 +149,15 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
} }
final String requestString = request.toString(); final String requestString = request.toString();
try { try {
addWordsFromProjectionLocked(PROJECTION_QUERY_WITH_SHORTCUT, requestString, addWordsFromProjectionLocked(PROJECTION_QUERY_WITH_SHORTCUT, requestString, requestArguments);
requestArguments);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// This may happen on some non-compliant devices where the declared API is JB+ but // This may happen on some non-compliant devices where the declared API is JB+ but
// the SHORTCUT column is not present for some reason. // the SHORTCUT column is not present for some reason.
addWordsFromProjectionLocked(PROJECTION_QUERY_WITHOUT_SHORTCUT, requestString, addWordsFromProjectionLocked(PROJECTION_QUERY_WITHOUT_SHORTCUT, requestString, requestArguments);
requestArguments);
} }
} }
private void addWordsFromProjectionLocked(final String[] query, String request, private void addWordsFromProjectionLocked(final String[] query, String request, final String[] requestArguments)
final String[] requestArguments)
throws IllegalArgumentException { throws IllegalArgumentException {
Cursor cursor = null; Cursor cursor = null;
try { 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 // interface
public InputTransaction onPickSuggestionManually(final SettingsValues settingsValues, public InputTransaction onPickSuggestionManually(final SettingsValues settingsValues,
final SuggestedWordInfo suggestionInfo, final int keyboardShiftState, 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 SuggestedWords suggestedWords = mSuggestedWords;
final String suggestion = suggestionInfo.mWord; final String suggestion = suggestionInfo.mWord;
// If this is a punctuation picked from the suggestion strip, pass it to onCodeInput // 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. // Word separators are suggested before the user inputs something.
// Rely on onCodeInput to do the complicated swapping/stripping logic consistently. // Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
final Event event = Event.createPunctuationSuggestionPickedEvent(suggestionInfo); 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); final Event event = Event.createSuggestionPickedEvent(suggestionInfo);
@ -426,11 +426,11 @@ public final class InputLogic {
*/ */
public InputTransaction onCodeInput(final SettingsValues settingsValues, public InputTransaction onCodeInput(final SettingsValues settingsValues,
@NonNull final Event event, final int keyboardShiftMode, @NonNull final Event event, final int keyboardShiftMode,
final int currentKeyboardScriptId, final LatinIME.UIHandler handler) { final String currentKeyboardScript, final LatinIME.UIHandler handler) {
mWordBeingCorrectedByCursor = null; mWordBeingCorrectedByCursor = null;
mJustRevertedACommit = false; mJustRevertedACommit = false;
final Event processedEvent; 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 // only use the Hangul chain if codepoint may actually be Hangul
// todo: this whole hangul-related logic should probably be somewhere else // todo: this whole hangul-related logic should probably be somewhere else
// need to use hangul combiner for whitespace, because otherwise the current word // need to use hangul combiner for whitespace, because otherwise the current word
@ -472,7 +472,7 @@ public final class InputLogic {
if (currentEvent.isConsumed()) { if (currentEvent.isConsumed()) {
handleConsumedEvent(currentEvent, inputTransaction); handleConsumedEvent(currentEvent, inputTransaction);
} else if (currentEvent.isFunctionalKeyEvent()) { } else if (currentEvent.isFunctionalKeyEvent()) {
handleFunctionalEvent(currentEvent, inputTransaction, currentKeyboardScriptId, handler); handleFunctionalEvent(currentEvent, inputTransaction, currentKeyboardScript, handler);
} else { } else {
handleNonFunctionalEvent(currentEvent, inputTransaction, handler); handleNonFunctionalEvent(currentEvent, inputTransaction, handler);
} }
@ -484,7 +484,7 @@ public final class InputLogic {
&& (settingsValues.isWordCodePoint(processedEvent.getMCodePoint()) && (settingsValues.isWordCodePoint(processedEvent.getMCodePoint())
|| processedEvent.getMKeyCode() == Constants.CODE_DELETE) || processedEvent.getMKeyCode() == Constants.CODE_DELETE)
) { ) {
mWordBeingCorrectedByCursor = getWordAtCursor(settingsValues, currentKeyboardScriptId); mWordBeingCorrectedByCursor = getWordAtCursor(settingsValues, currentKeyboardScript);
} }
if (!inputTransaction.didAutoCorrect() && processedEvent.getMKeyCode() != Constants.CODE_SHIFT if (!inputTransaction.didAutoCorrect() && processedEvent.getMKeyCode() != Constants.CODE_SHIFT
&& processedEvent.getMKeyCode() != Constants.CODE_CAPSLOCK && processedEvent.getMKeyCode() != Constants.CODE_CAPSLOCK
@ -645,10 +645,10 @@ public final class InputLogic {
* @param inputTransaction The transaction in progress. * @param inputTransaction The transaction in progress.
*/ */
private void handleFunctionalEvent(final Event event, final InputTransaction inputTransaction, 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()) { switch (event.getMKeyCode()) {
case Constants.CODE_DELETE: 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. // Backspace is a functional key, but it affects the contents of the editor.
inputTransaction.setDidAffectContents(); inputTransaction.setDidAffectContents();
break; break;
@ -707,7 +707,7 @@ public final class InputLogic {
mConnection.selectAll(); mConnection.selectAll();
break; break;
case Constants.CODE_SELECT_WORD: case Constants.CODE_SELECT_WORD:
mConnection.selectWord(inputTransaction.getMSettingsValues().mSpacingAndPunctuations, currentKeyboardScriptId); mConnection.selectWord(inputTransaction.getMSettingsValues().mSpacingAndPunctuations, currentKeyboardScript);
break; break;
case Constants.CODE_COPY: case Constants.CODE_COPY:
mConnection.copyText(); mConnection.copyText();
@ -1098,7 +1098,7 @@ public final class InputLogic {
* @param inputTransaction The transaction in progress. * @param inputTransaction The transaction in progress.
*/ */
private void handleBackspaceEvent(final Event event, final InputTransaction inputTransaction, private void handleBackspaceEvent(final Event event, final InputTransaction inputTransaction,
final int currentKeyboardScriptId) { final String currentKeyboardScript) {
mSpaceState = SpaceState.NONE; mSpaceState = SpaceState.NONE;
mDeleteCount++; mDeleteCount++;
@ -1160,7 +1160,7 @@ public final class InputLogic {
&& inputTransaction.getMSettingsValues().mSpacingAndPunctuations.mCurrentLanguageHasSpaces && inputTransaction.getMSettingsValues().mSpacingAndPunctuations.mCurrentLanguageHasSpaces
&& !mConnection.isCursorFollowedByWordCharacter( && !mConnection.isCursorFollowedByWordCharacter(
inputTransaction.getMSettingsValues().mSpacingAndPunctuations)) { inputTransaction.getMSettingsValues().mSpacingAndPunctuations)) {
restartSuggestionsOnWordTouchedByCursor(inputTransaction.getMSettingsValues(), currentKeyboardScriptId); restartSuggestionsOnWordTouchedByCursor(inputTransaction.getMSettingsValues(), currentKeyboardScript);
} }
return; return;
} }
@ -1235,7 +1235,7 @@ public final class InputLogic {
// consider unlearning here because we may have already reached // consider unlearning here because we may have already reached
// the previous word, and will lose it after next deletion. // the previous word, and will lose it after next deletion.
hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted( hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted(
inputTransaction.getMSettingsValues(), currentKeyboardScriptId); inputTransaction.getMSettingsValues(), currentKeyboardScript);
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL); sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
totalDeletedLength++; totalDeletedLength++;
} }
@ -1267,7 +1267,7 @@ public final class InputLogic {
// consider unlearning here because we may have already reached // consider unlearning here because we may have already reached
// the previous word, and will lose it after next deletion. // the previous word, and will lose it after next deletion.
hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted( hasUnlearnedWordBeingDeleted |= unlearnWordBeingDeleted(
inputTransaction.getMSettingsValues(), currentKeyboardScriptId); inputTransaction.getMSettingsValues(), currentKeyboardScript);
final int codePointBeforeCursorToDeleteAgain = final int codePointBeforeCursorToDeleteAgain =
mConnection.getCodePointBeforeCursor(); mConnection.getCodePointBeforeCursor();
if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) { if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) {
@ -1284,7 +1284,7 @@ public final class InputLogic {
if (!hasUnlearnedWordBeingDeleted) { if (!hasUnlearnedWordBeingDeleted) {
// Consider unlearning the word being deleted (if we have not done so already). // Consider unlearning the word being deleted (if we have not done so already).
unlearnWordBeingDeleted( unlearnWordBeingDeleted(
inputTransaction.getMSettingsValues(), currentKeyboardScriptId); inputTransaction.getMSettingsValues(), currentKeyboardScript);
} }
if (mConnection.hasSlowInputConnection()) { if (mConnection.hasSlowInputConnection()) {
mSuggestionStripViewAccessor.setNeutralSuggestionStrip(); mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
@ -1292,18 +1292,18 @@ public final class InputLogic {
&& inputTransaction.getMSettingsValues().mSpacingAndPunctuations.mCurrentLanguageHasSpaces && inputTransaction.getMSettingsValues().mSpacingAndPunctuations.mCurrentLanguageHasSpaces
&& !mConnection.isCursorFollowedByWordCharacter( && !mConnection.isCursorFollowedByWordCharacter(
inputTransaction.getMSettingsValues().mSpacingAndPunctuations)) { 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() if (!mConnection.hasSelection()
&& settingsValues.isSuggestionsEnabledPerUserSettings() && settingsValues.isSuggestionsEnabledPerUserSettings()
&& settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces) { && settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces) {
final TextRange range = mConnection.getWordRangeAtCursor( final TextRange range = mConnection.getWordRangeAtCursor(
settingsValues.mSpacingAndPunctuations, settingsValues.mSpacingAndPunctuations,
currentKeyboardScriptId, false); currentKeyboardScript, false);
if (range != null) { if (range != null) {
return range.mWord.toString(); return range.mWord.toString();
} }
@ -1312,7 +1312,7 @@ public final class InputLogic {
} }
boolean unlearnWordBeingDeleted( boolean unlearnWordBeingDeleted(
final SettingsValues settingsValues, final int currentKeyboardScriptId) { final SettingsValues settingsValues, final String currentKeyboardScript) {
if (mConnection.hasSlowInputConnection()) { if (mConnection.hasSlowInputConnection()) {
// TODO: Refactor unlearning so that it does not incur any extra calls // 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 // 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. // entered the composing state yet), unlearn the word.
// TODO: Consider tracking whether or not this word was typed by the user. // TODO: Consider tracking whether or not this word was typed by the user.
if (!mConnection.isCursorFollowedByWordCharacter(settingsValues.mSpacingAndPunctuations)) { if (!mConnection.isCursorFollowedByWordCharacter(settingsValues.mSpacingAndPunctuations)) {
final String wordBeingDeleted = getWordAtCursor(settingsValues, currentKeyboardScriptId); final String wordBeingDeleted = getWordAtCursor(settingsValues, currentKeyboardScript);
if (!TextUtils.isEmpty(wordBeingDeleted)) { if (!TextUtils.isEmpty(wordBeingDeleted)) {
unlearnWord(wordBeingDeleted, settingsValues, Constants.EVENT_BACKSPACE); unlearnWord(wordBeingDeleted, settingsValues, Constants.EVENT_BACKSPACE);
return true; return true;
@ -1625,7 +1625,7 @@ public final class InputLogic {
*/ */
public void restartSuggestionsOnWordTouchedByCursor(final SettingsValues settingsValues, public void restartSuggestionsOnWordTouchedByCursor(final SettingsValues settingsValues,
// TODO: remove this argument, put it into 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 // 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. // recorrection. This is a temporary, stopgap measure that will be removed later.
// TODO: remove this. // TODO: remove this.
@ -1653,7 +1653,7 @@ public final class InputLogic {
return; return;
} }
final TextRange range = 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 (null == range) return; // Happens if we don't have an input connection at all
if (range.length() <= 0) { if (range.length() <= 0) {
// Race condition, or touching a word in a non-supported script. // Race condition, or touching a word in a non-supported script.

View file

@ -7,6 +7,7 @@
package org.dslul.openboard.inputmethod.latin.makedict package org.dslul.openboard.inputmethod.latin.makedict
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils 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.DictionaryOptions
import org.dslul.openboard.inputmethod.latin.makedict.FormatSpec.FormatOptions import org.dslul.openboard.inputmethod.latin.makedict.FormatSpec.FormatOptions
import java.text.DateFormat import java.text.DateFormat
@ -44,7 +45,7 @@ class DictionaryHeader(
fun info(locale: Locale): String { fun info(locale: Locale): String {
val date = if (mDate == null) "" val date = if (mDate == null) ""
else DateFormat.getDateInstance(DateFormat.SHORT, locale).format(Date(mDate * 1000L)) + "\n" 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 "\nv" + mVersionString + "\n" + date + description
} }

View file

@ -19,6 +19,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.preference.Preference import androidx.preference.Preference
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.dslul.openboard.inputmethod.compat.locale
import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants
import org.dslul.openboard.inputmethod.latin.utils.ChecksumCalculator import org.dslul.openboard.inputmethod.latin.utils.ChecksumCalculator
import org.dslul.openboard.inputmethod.keyboard.KeyboardLayoutSet 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.R
import org.dslul.openboard.inputmethod.latin.SystemBroadcastReceiver import org.dslul.openboard.inputmethod.latin.SystemBroadcastReceiver
import org.dslul.openboard.inputmethod.latin.common.FileUtils 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.settings.SeekBarDialogPreference.ValueProxy
import org.dslul.openboard.inputmethod.latin.utils.CUSTOM_LAYOUT_PREFIX import org.dslul.openboard.inputmethod.latin.utils.CUSTOM_LAYOUT_PREFIX
import org.dslul.openboard.inputmethod.latin.utils.JniUtils 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_background_image")?.setOnPreferenceClickListener { onClickLoadImage() }
findPreference<Preference>("custom_symbols_layout")?.setOnPreferenceClickListener { 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() 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) editCustomLayout(layoutName ?: "${CUSTOM_LAYOUT_PREFIX}symbols.txt", context, oldLayout, true)
true true
@ -169,7 +171,6 @@ class AdvancedSettingsFragment : SubScreenFragment() {
} }
val checksum = ChecksumCalculator.checksum(tmpfile.inputStream()) ?: "" val checksum = ChecksumCalculator.checksum(tmpfile.inputStream()) ?: ""
Log.i("test", "cs $checksum")
if (checksum == JniUtils.expectedDefaultChecksum()) { if (checksum == JniUtils.expectedDefaultChecksum()) {
renameToLibfileAndRestart(tmpfile, checksum) renameToLibfileAndRestart(tmpfile, checksum)
} else { } else {
@ -310,7 +311,8 @@ class AdvancedSettingsFragment : SubScreenFragment() {
val filesDir = requireContext().filesDir?.path ?: return val filesDir = requireContext().filesDir?.path ?: return
while (entry != null) { while (entry != null) {
if (backupFilePatterns.any { entry!!.name.matches(it) }) { 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) FileUtils.copyStreamToNewFile(zip, file)
} else if (entry.name == PREFS_FILE_NAME) { } else if (entry.name == PREFS_FILE_NAME) {
val prefLines = String(zip.readBytes()).split("\n") 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() { private fun setupKeyLongpressTimeoutSettings() {
val prefs = sharedPreferences val prefs = sharedPreferences
findPreference<SeekBarDialogPreference>(Settings.PREF_KEY_LONGPRESS_TIMEOUT)?.setInterface(object : ValueProxy { 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.locale
import org.dslul.openboard.inputmethod.latin.utils.removeEnabledSubtype import org.dslul.openboard.inputmethod.latin.utils.removeEnabledSubtype
import org.dslul.openboard.inputmethod.latin.utils.showMissingDictionaryDialog import org.dslul.openboard.inputmethod.latin.utils.showMissingDictionaryDialog
import org.dslul.openboard.inputmethod.latin.utils.toLocale
class LanguageFilterList(searchField: EditText, recyclerView: RecyclerView) { class LanguageFilterList(searchField: EditText, recyclerView: RecyclerView) {
@ -124,7 +123,7 @@ private class LanguageAdapter(list: List<MutableList<SubtypeInfo>> = listOf(), c
setOnCheckedChangeListener { _, b -> setOnCheckedChangeListener { _, b ->
if (b) { if (b) {
if (!infos.first().hasDictionary) if (!infos.first().hasDictionary)
showMissingDictionaryDialog(context, infos.first().subtype.locale().toLocale()) showMissingDictionaryDialog(context, infos.first().subtype.locale())
addEnabledSubtype(prefs, infos.first().subtype) addEnabledSubtype(prefs, infos.first().subtype)
infos.first().isEnabled = true infos.first().isEnabled = true
} else { } else {

View file

@ -18,16 +18,18 @@ import androidx.core.view.get
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.size import androidx.core.view.size
import org.dslul.openboard.inputmethod.compat.locale
import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants
import org.dslul.openboard.inputmethod.keyboard.KeyboardLayoutSet import org.dslul.openboard.inputmethod.keyboard.KeyboardLayoutSet
import org.dslul.openboard.inputmethod.keyboard.KeyboardSwitcher 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.R
import org.dslul.openboard.inputmethod.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET 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
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils.constructLocale
import org.dslul.openboard.inputmethod.latin.databinding.LanguageListItemBinding import org.dslul.openboard.inputmethod.latin.databinding.LanguageListItemBinding
import org.dslul.openboard.inputmethod.latin.databinding.LocaleSettingsDialogBinding import org.dslul.openboard.inputmethod.latin.databinding.LocaleSettingsDialogBinding
import org.dslul.openboard.inputmethod.latin.utils.* import org.dslul.openboard.inputmethod.latin.utils.*
import org.dslul.openboard.inputmethod.latin.utils.ScriptUtils.script
import java.io.File import java.io.File
import java.util.* import java.util.*
@ -40,8 +42,7 @@ class LanguageSettingsDialog(
) : AlertDialog(context), LanguageSettingsFragment.Listener { ) : AlertDialog(context), LanguageSettingsFragment.Listener {
private val prefs = DeviceProtectedUtils.getSharedPreferences(context)!! private val prefs = DeviceProtectedUtils.getSharedPreferences(context)!!
private val binding = LocaleSettingsDialogBinding.inflate(LayoutInflater.from(context)) private val binding = LocaleSettingsDialogBinding.inflate(LayoutInflater.from(context))
private val mainLocaleString = infos.first().subtype.locale() private val mainLocale = infos.first().subtype.locale()
private val mainLocale = mainLocaleString.toLocale()
private var hasInternalDictForLanguage = false private var hasInternalDictForLanguage = false
private val userDicts = mutableSetOf<File>() private val userDicts = mutableSetOf<File>()
@ -97,7 +98,7 @@ class LanguageSettingsDialog(
} }
private fun addSubtype(name: String) { 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 newSubtypeInfo = newSubtype.toSubtypeInfo(mainLocale, context, true, infos.first().hasDictionary) // enabled by default
val displayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(newSubtype) val displayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(newSubtype)
val old = infos.firstOrNull { isAdditionalSubtype(it.subtype) && displayName == SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(it.subtype) } val old = infos.firstOrNull { isAdditionalSubtype(it.subtype) && displayName == SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(it.subtype) }
@ -148,14 +149,15 @@ class LanguageSettingsDialog(
.setItems(displayNames.toTypedArray()) { di, i -> .setItems(displayNames.toTypedArray()) { di, i ->
di.dismiss() di.dismiss()
val fileName = context.assets.list("layouts")!!.firstOrNull { it.startsWith(layouts[i]) } ?: return@setItems 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) .setNegativeButton(android.R.string.cancel, null)
.show() .show()
} }
override fun onNewLayoutFile(uri: Uri?) { override fun onNewLayoutFile(uri: Uri?) {
loadCustomLayout(uri, mainLocaleString, context) { addSubtype(it) } loadCustomLayout(uri, mainLocale.toLanguageTag(), context) { addSubtype(it) }
} }
private fun addSubtypeToView(subtype: SubtypeInfo) { private fun addSubtypeToView(subtype: SubtypeInfo) {
@ -215,10 +217,10 @@ class LanguageSettingsDialog(
// can only use multilingual typing if there is more than one dictionary available // can only use multilingual typing if there is more than one dictionary available
val availableSecondaryLocales = getAvailableSecondaryLocales( val availableSecondaryLocales = getAvailableSecondaryLocales(
context, context,
mainLocaleString, mainLocale,
infos.first().subtype.isAsciiCapable infos.first().subtype.isAsciiCapable
) )
val selectedSecondaryLocales = Settings.getSecondaryLocales(prefs, mainLocaleString) val selectedSecondaryLocales = Settings.getSecondaryLocales(prefs, mainLocale)
selectedSecondaryLocales.forEach { selectedSecondaryLocales.forEach {
addSecondaryLocaleView(it) addSecondaryLocaleView(it)
} }
@ -226,14 +228,14 @@ class LanguageSettingsDialog(
binding.addSecondaryLanguage.apply { binding.addSecondaryLanguage.apply {
isVisible = true isVisible = true
setOnClickListener { 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() val localeNames = locales.map { LocaleUtils.getLocaleDisplayNameInSystemLocale(it, context) }.toTypedArray()
Builder(context) Builder(context)
.setTitle(R.string.button_select_language) .setTitle(R.string.button_select_language)
.setItems(localeNames) { di, i -> .setItems(localeNames) { di, i ->
val locale = locales[i] val locale = locales[i]
val localeStrings = Settings.getSecondaryLocales(prefs, mainLocaleString).map { it.toString() } val currentSecondaryLocales = Settings.getSecondaryLocales(prefs, mainLocale)
Settings.setSecondaryLocales(prefs, mainLocaleString, localeStrings + locale.toString()) Settings.setSecondaryLocales(prefs, mainLocale, currentSecondaryLocales + locale)
addSecondaryLocaleView(locale) addSecondaryLocaleView(locale)
di.dismiss() di.dismiss()
reloadSetting() reloadSetting()
@ -256,8 +258,8 @@ class LanguageSettingsDialog(
rowBinding.deleteButton.apply { rowBinding.deleteButton.apply {
isVisible = true isVisible = true
setOnClickListener { setOnClickListener {
val localeStrings = Settings.getSecondaryLocales(prefs, mainLocaleString).map { it.toString() } val currentSecondaryLocales = Settings.getSecondaryLocales(prefs, mainLocale)
Settings.setSecondaryLocales(prefs, mainLocaleString, localeStrings - locale.toString()) Settings.setSecondaryLocales(prefs, mainLocale, currentSecondaryLocales - locale)
binding.secondaryLocales.removeView(rowBinding.root) binding.secondaryLocales.removeView(rowBinding.root)
reloadSetting() reloadSetting()
reloadDictionaries() reloadDictionaries()
@ -280,7 +282,7 @@ class LanguageSettingsDialog(
dialog.show() dialog.show()
(dialog.findViewById<View>(android.R.id.message) as? TextView)?.movementMethod = LinkMovementMethod.getInstance() (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 hasInternalDictForLanguage = userDictsAndHasInternal.second
userDicts.addAll(userDictsAndHasInternal.first) userDicts.addAll(userDictsAndHasInternal.first)
if (hasInternalDictForLanguage) { if (hasInternalDictForLanguage) {
@ -330,11 +332,7 @@ class LanguageSettingsDialog(
} }
rowBinding.languageText.setOnClickListener { rowBinding.languageText.setOnClickListener {
if (header == null) return@setOnClickListener if (header == null) return@setOnClickListener
val locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { val locale = context.resources.configuration.locale()
context.resources.configuration.locales[0]
} else {
@Suppress("Deprecation") context.resources.configuration.locale
}
Builder(context) Builder(context)
.setMessage(header.info(locale)) .setMessage(header.info(locale))
.setPositiveButton(android.R.string.ok, null) .setPositiveButton(android.R.string.ok, null)
@ -369,12 +367,12 @@ class LanguageSettingsDialog(
private fun setupPopupSettings() { private fun setupPopupSettings() {
binding.popupOrder.setOnClickListener { binding.popupOrder.setOnClickListener {
val moreKeyTypesDefault = prefs.getString(Settings.PREF_MORE_KEYS_ORDER, MORE_KEYS_ORDER_DEFAULT)!! 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() KeyboardLayoutSet.onKeyboardThemeChanged()
} }
binding.popupLabelPriority.setOnClickListener { binding.popupLabelPriority.setOnClickListener {
val moreKeyTypesDefault = prefs.getString(Settings.PREF_MORE_KEYS_LABELS_ORDER, MORE_KEYS_LABEL_DEFAULT)!! 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() KeyboardLayoutSet.onKeyboardThemeChanged()
} }
} }
@ -383,11 +381,10 @@ class LanguageSettingsDialog(
} }
/** @return list of user dictionary files and whether an internal dictionary exists */ /** @return list of user dictionary files and whether an internal dictionary exists */
fun getUserAndInternalDictionaries(context: Context, locale: String): Pair<List<File>, Boolean> { fun getUserAndInternalDictionaries(context: Context, locale: Locale): Pair<List<File>, Boolean> {
val localeString = locale.lowercase() // internal files and folders always use lowercase
val userDicts = mutableListOf<File>() val userDicts = mutableListOf<File>()
var hasInternalDict = false var hasInternalDict = false
val userLocaleDir = File(DictionaryInfoUtils.getWordListCacheDirectory(context), localeString) val userLocaleDir = File(DictionaryInfoUtils.getCacheDirectoryForLocale(locale, context))
if (userLocaleDir.exists() && userLocaleDir.isDirectory) { if (userLocaleDir.exists() && userLocaleDir.isDirectory) {
userLocaleDir.listFiles()?.forEach { userLocaleDir.listFiles()?.forEach {
if (it.name.endsWith(USER_DICTIONARY_SUFFIX)) if (it.name.endsWith(USER_DICTIONARY_SUFFIX))
@ -398,37 +395,24 @@ fun getUserAndInternalDictionaries(context: Context, locale: String): Pair<List<
} }
if (hasInternalDict) if (hasInternalDict)
return userDicts to true return userDicts to true
val language = localeString.languageConsideringZZ() val internalDicts = DictionaryInfoUtils.getAssetsDictionaryList(context) ?: return userDicts to false
BinaryDictionaryGetter.getAssetsDictionaryList(context)?.forEach { dictFile -> val best = LocaleUtils.getBestMatch(locale, internalDicts.toList()) {
BinaryDictionaryGetter.extractLocaleFromAssetsDictionaryFile(dictFile)?.let { DictionaryInfoUtils.extractLocaleFromAssetsDictionaryFile(it)?.constructLocale() ?: SubtypeLocaleUtils.NO_LANGUAGE.constructLocale()
if (it == localeString || it.languageConsideringZZ() == language)
return userDicts to true
}
} }
return userDicts to false return userDicts to (best != null)
}
private fun String.languageConsideringZZ(): String {
return if (endsWith("zz", false))
this
else
substringBefore("_")
} }
// get locales with same script as main locale, but different language // get locales with same script as main locale, but different language
private fun getAvailableSecondaryLocales(context: Context, mainLocaleString: String, asciiCapable: Boolean): Set<Locale> { private fun getAvailableSecondaryLocales(context: Context, mainLocale: Locale, asciiCapable: Boolean): Set<Locale> {
val mainLocale = mainLocaleString.toLocale()
val locales = getDictionaryLocales(context) val locales = getDictionaryLocales(context)
val mainScript = if (asciiCapable) ScriptUtils.SCRIPT_LATIN val mainScript = if (asciiCapable) ScriptUtils.SCRIPT_LATIN
else ScriptUtils.getScriptFromSpellCheckerLocale(mainLocale) else mainLocale.script()
// ScriptUtils.getScriptFromSpellCheckerLocale may return latin when it should not // script() extension function may return latin in case script cannot be determined
// e.g. for persian or chinese
// workaround: don't allow secondary locales for these locales // workaround: don't allow secondary locales for these locales
if (!asciiCapable && mainScript == ScriptUtils.SCRIPT_LATIN) return emptySet() if (!asciiCapable && mainScript == ScriptUtils.SCRIPT_LATIN) return emptySet()
locales.removeAll { locales.removeAll {
it.language == mainLocale.language it.language == mainLocale.language || it.script() != mainScript
|| ScriptUtils.getScriptFromSpellCheckerLocale(it) != mainScript
} }
return locales return locales
} }

View file

@ -19,8 +19,10 @@ import androidx.core.content.edit
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import org.dslul.openboard.inputmethod.latin.R import org.dslul.openboard.inputmethod.latin.R
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils 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.DeviceProtectedUtils
import org.dslul.openboard.inputmethod.latin.utils.DictionaryInfoUtils 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.SubtypeLocaleUtils
import org.dslul.openboard.inputmethod.latin.utils.getAllAvailableSubtypes import org.dslul.openboard.inputmethod.latin.utils.getAllAvailableSubtypes
import org.dslul.openboard.inputmethod.latin.utils.getDictionaryLocales 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 // 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) // impossible to have the languages RecyclerView scrollable (this way it works nicely out of the box)
class LanguageSettingsFragment : Fragment(R.layout.language_settings) { 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 enabledSubtypes = mutableListOf<InputMethodSubtype>()
private val systemLocales = mutableListOf<Locale>() private val systemLocales = mutableListOf<Locale>()
private lateinit var languageFilterList: LanguageFilterList private lateinit var languageFilterList: LanguageFilterList
private lateinit var sharedPreferences: SharedPreferences private lateinit var sharedPreferences: SharedPreferences
private lateinit var systemOnlySwitch: Switch 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()) { private val dictionaryFilePicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode != Activity.RESULT_OK) return@registerForActivityResult if (it.resultCode != Activity.RESULT_OK) return@registerForActivityResult
@ -62,11 +64,7 @@ class LanguageSettingsFragment : Fragment(R.layout.language_settings) {
systemLocales.addAll(getSystemLocales()) systemLocales.addAll(getSystemLocales())
} }
override fun onCreateView( override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = super.onCreateView(inflater, container, savedInstanceState) ?: return null val view = super.onCreateView(inflater, container, savedInstanceState) ?: return null
systemOnlySwitch = view.findViewById(R.id.language_switch) systemOnlySwitch = view.findViewById(R.id.language_switch)
systemOnlySwitch.isChecked = sharedPreferences.getBoolean(Settings.PREF_USE_SYSTEM_LOCALES, true) 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) { private fun loadSubtypes(systemOnly: Boolean) {
sortedSubtypes.clear() sortedSubtypesByDisplayName.clear()
// list of all subtypes, any subtype added to sortedSubtypes will be removed to avoid duplicates // list of all subtypes, any subtype added to sortedSubtypes will be removed to avoid duplicates
val allSubtypes = getAllAvailableSubtypes().toMutableList() 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() { fun List<Locale>.sortedAddToSubtypesAndRemoveFromAllSubtypes() {
val subtypesToAdd = mutableListOf<SubtypeInfo>() val subtypesToAdd = mutableListOf<SubtypeInfo>()
forEach { locale -> forEach { locale ->
val localeString = locale.toString()
val iterator = allSubtypes.iterator() val iterator = allSubtypes.iterator()
var added = false var added = false
while (iterator.hasNext()) { while (iterator.hasNext()) {
val subtype = iterator.next() val subtype = iterator.next()
if (subtype.locale() == localeString) { if (subtype.locale() == locale) {
// add subtypes with matching locale
subtypesToAdd.add(subtype.toSubtypeInfo(locale)) subtypesToAdd.add(subtype.toSubtypeInfo(locale))
iterator.remove() iterator.remove()
added = true 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()) { if (!added && locale.country.isNotEmpty()) {
val languageString = locale.language val language = locale.language
val script = locale.script()
val iter = allSubtypes.iterator() val iter = allSubtypes.iterator()
while (iter.hasNext()) { while (iter.hasNext()) {
val subtype = iter.next() val subtype = iter.next()
if (subtype.locale() == languageString) { val subtypeLocale = subtype.locale()
subtypesToAdd.add(subtype.toSubtypeInfo(LocaleUtils.constructLocaleFromString(languageString))) 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() iter.remove()
added = true added = true
} }
} }
} }
// special treatment for the known languages with _ZZ types // try again if script is not the default script, match language only
if (!added && (locale.language == "sr" || locale.language == "hi")) { if (!added && locale.script() != locale.language.constructLocale().script()) {
val languageString = locale.language val language = locale.language
val iter = allSubtypes.iterator() val iter = allSubtypes.iterator()
while (iter.hasNext()) { while (iter.hasNext()) {
val subtype = iter.next() val subtype = iter.next()
if (subtype.locale().substringBefore("_") == languageString) { if (subtype.locale().language == language) {
subtypesToAdd.add(subtype.toSubtypeInfo(LocaleUtils.constructLocaleFromString(subtype.locale()))) subtypesToAdd.add(subtype.toSubtypeInfo(subtype.locale()))
iter.remove() iter.remove()
} }
} }
@ -146,12 +145,12 @@ class LanguageSettingsFragment : Fragment(R.layout.language_settings) {
} }
// add enabled subtypes // add enabled subtypes
enabledSubtypes.map { it.toSubtypeInfo(LocaleUtils.constructLocaleFromString(it.locale()), true) } enabledSubtypes.map { it.toSubtypeInfo(it.locale(), true) }
.sortedBy { it.displayName }.addToSortedSubtypes() .sortedBy { it.displayName }.addToSortedSubtypes()
allSubtypes.removeAll(enabledSubtypes) allSubtypes.removeAll(enabledSubtypes)
if (systemOnly) { // don't add anything else if (systemOnly) { // don't add anything else
languageFilterList.setLanguages(sortedSubtypes.values, systemOnly) languageFilterList.setLanguages(sortedSubtypesByDisplayName.values, systemOnly)
return return
} }
@ -160,7 +159,7 @@ class LanguageSettingsFragment : Fragment(R.layout.language_settings) {
if (!dir.isDirectory) if (!dir.isDirectory)
return@mapNotNull null return@mapNotNull null
if (dir.list()?.any { it.endsWith(USER_DICTIONARY_SUFFIX) } == true) if (dir.list()?.any { it.endsWith(USER_DICTIONARY_SUFFIX) } == true)
LocaleUtils.constructLocaleFromString(dir.name) dir.name.constructLocale()
else null else null
} }
localesWithDictionary?.sortedAddToSubtypesAndRemoveFromAllSubtypes() localesWithDictionary?.sortedAddToSubtypesAndRemoveFromAllSubtypes()
@ -169,22 +168,22 @@ class LanguageSettingsFragment : Fragment(R.layout.language_settings) {
systemLocales.sortedAddToSubtypesAndRemoveFromAllSubtypes() systemLocales.sortedAddToSubtypesAndRemoveFromAllSubtypes()
// add the remaining ones // add the remaining ones
allSubtypes.map { it.toSubtypeInfo(LocaleUtils.constructLocaleFromString(it.locale())) } allSubtypes.map { it.toSubtypeInfo(it.locale()) }
.sortedBy { if (it.subtype.locale().equals("zz", true)) .sortedBy { if (it.subtype.locale().toLanguageTag().equals(SubtypeLocaleUtils.NO_LANGUAGE, true))
"zz" // "No language (Alphabet)" should be last SubtypeLocaleUtils.NO_LANGUAGE // "No language (Alphabet)" should be last
else it.displayName else it.displayName
}.addToSortedSubtypes() }.addToSortedSubtypes()
// set languages // set languages
languageFilterList.setLanguages(sortedSubtypes.values, systemOnly) languageFilterList.setLanguages(sortedSubtypesByDisplayName.values, systemOnly)
} }
private fun InputMethodSubtype.toSubtypeInfo(locale: Locale, isEnabled: Boolean = false) = 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() { private fun List<SubtypeInfo>.addToSortedSubtypes() {
forEach { 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 = fun InputMethodSubtype.toSubtypeInfo(locale: Locale, context: Context, isEnabled: Boolean, hasDictionary: Boolean): SubtypeInfo =
SubtypeInfo(LocaleUtils.getLocaleDisplayNameInSystemLocale(locale, context), this, isEnabled, hasDictionary) 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" const val USER_DICTIONARY_SUFFIX = "user.dict"

View file

@ -526,7 +526,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return name; 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) { public static String readShiftedSymbolsLayoutName(final Context context) {
@ -571,27 +571,27 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
sCachedBackgroundNight = null; sCachedBackgroundNight = null;
} }
public static List<Locale> getSecondaryLocales(final SharedPreferences prefs, final String mainLocaleString) { public static List<Locale> getSecondaryLocales(final SharedPreferences prefs, final Locale mainLocale) {
final String localesString = prefs.getString(PREF_SECONDARY_LOCALES_PREFIX + mainLocaleString.toLowerCase(Locale.ROOT), ""); final String localesString = prefs.getString(PREF_SECONDARY_LOCALES_PREFIX + mainLocale.toLanguageTag(), "");
final ArrayList<Locale> locales = new ArrayList<>(); final ArrayList<Locale> locales = new ArrayList<>();
for (String locale : localesString.split(";")) { for (String languageTag : localesString.split(";")) {
if (locale.isEmpty()) continue; if (languageTag.isEmpty()) continue;
locales.add(LocaleUtils.constructLocaleFromString(locale)); locales.add(LocaleUtils.constructLocale(languageTag));
} }
return locales; 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()) { 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; return;
} }
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
for (String locale : locales) { for (Locale locale : locales) {
sb.append(";").append(locale); 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) { 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.NonNull;
import androidx.annotation.Nullable; 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.keyboard.internal.keyboard_parser.LocaleKeyTextsKt;
import org.dslul.openboard.inputmethod.latin.InputAttributes; import org.dslul.openboard.inputmethod.latin.InputAttributes;
import org.dslul.openboard.inputmethod.latin.R; 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.MoreKeysUtilsKt;
import org.dslul.openboard.inputmethod.latin.utils.ScriptUtils; import org.dslul.openboard.inputmethod.latin.utils.ScriptUtils;
import org.dslul.openboard.inputmethod.latin.utils.SubtypeSettingsKt; import org.dslul.openboard.inputmethod.latin.utils.SubtypeSettingsKt;
import org.dslul.openboard.inputmethod.latin.utils.SubtypeUtilsKt;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; 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 // 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, public SettingsValues(final Context context, final SharedPreferences prefs, final Resources res,
@NonNull final InputAttributes inputAttributes) { @NonNull final InputAttributes inputAttributes) {
mLocale = res.getConfiguration().locale; mLocale = ConfigurationCompatKt.locale(res.getConfiguration());
// Store the input attributes // Store the input attributes
mInputAttributes = inputAttributes; mInputAttributes = inputAttributes;
@ -207,7 +209,7 @@ public class SettingsValues {
} else } else
mOneHandedModeScale = 1f; mOneHandedModeScale = 1f;
final InputMethodSubtype selectedSubtype = SubtypeSettingsKt.getSelectedSubtype(prefs); final InputMethodSubtype selectedSubtype = SubtypeSettingsKt.getSelectedSubtype(prefs);
mSecondaryLocales = Settings.getSecondaryLocales(prefs, selectedSubtype.getLocale()); mSecondaryLocales = Settings.getSecondaryLocales(prefs, SubtypeUtilsKt.locale(selectedSubtype));
mShowMoreMoreKeys = selectedSubtype.isAsciiCapable() mShowMoreMoreKeys = selectedSubtype.isAsciiCapable()
? Settings.readMoreMoreKeysPref(prefs) ? Settings.readMoreMoreKeysPref(prefs)
: LocaleKeyTextsKt.MORE_KEYS_NORMAL; : LocaleKeyTextsKt.MORE_KEYS_NORMAL;

View file

@ -8,6 +8,7 @@ package org.dslul.openboard.inputmethod.latin.settings;
import android.content.res.Resources; 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.keyboard.internal.MoreKeySpec;
import org.dslul.openboard.inputmethod.latin.PunctuationSuggestions; import org.dslul.openboard.inputmethod.latin.PunctuationSuggestions;
import org.dslul.openboard.inputmethod.latin.R; import org.dslul.openboard.inputmethod.latin.R;
@ -50,7 +51,7 @@ public final class SpacingAndPunctuations {
mCurrentLanguageHasSpaces = res.getBoolean(R.bool.current_language_has_spaces); mCurrentLanguageHasSpaces = res.getBoolean(R.bool.current_language_has_spaces);
// make it empty if language doesn't have spaces, to avoid weird glitches // 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]; 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 // 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. // English variants. German rules (not "German typography") also have small gotchas.
mUsesAmericanTypography = Locale.ENGLISH.getLanguage().equals(locale.getLanguage()); mUsesAmericanTypography = Locale.ENGLISH.getLanguage().equals(locale.getLanguage());

View file

@ -22,10 +22,12 @@ import android.widget.EditText;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.dslul.openboard.inputmethod.compat.ConfigurationCompatKt;
import org.dslul.openboard.inputmethod.latin.R; import org.dslul.openboard.inputmethod.latin.R;
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils; import org.dslul.openboard.inputmethod.latin.common.LocaleUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Locale;
import java.util.TreeSet; import java.util.TreeSet;
public class UserDictionaryAddWordContents { public class UserDictionaryAddWordContents {
@ -49,7 +51,7 @@ public class UserDictionaryAddWordContents {
private final EditText mWordEditText; private final EditText mWordEditText;
private final EditText mShortcutEditText; private final EditText mShortcutEditText;
private final EditText mWeightEditText; private final EditText mWeightEditText;
private String mLocaleString; private Locale mLocale;
private final String mOldWord; private final String mOldWord;
private final String mOldShortcut; private final String mOldShortcut;
private final String mOldWeight; private final String mOldWeight;
@ -94,7 +96,8 @@ public class UserDictionaryAddWordContents {
mOldWord = args.getString(EXTRA_WORD); mOldWord = args.getString(EXTRA_WORD);
mOldWeight = args.getString(EXTRA_WEIGHT); 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) { UserDictionaryAddWordContents(final View view, final UserDictionaryAddWordContents oldInstanceToBeEdited) {
@ -105,26 +108,26 @@ public class UserDictionaryAddWordContents {
mOldWord = oldInstanceToBeEdited.mSavedWord; mOldWord = oldInstanceToBeEdited.mSavedWord;
mOldShortcut = oldInstanceToBeEdited.mSavedShortcut; mOldShortcut = oldInstanceToBeEdited.mSavedShortcut;
mOldWeight = oldInstanceToBeEdited.mSavedWeight; mOldWeight = oldInstanceToBeEdited.mSavedWeight;
updateLocale(mContext, mLocaleString); updateLocale(mContext, mLocale);
} }
// locale may be null, this means system locale // locale may be null, this means system locale
// It may also be the empty string, which means "For all languages" // 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; mContext = context;
mLocaleString = null == locale mLocale = null == locale
? mContext.getResources().getConfiguration().locale.toString() ? ConfigurationCompatKt.locale(mContext.getResources().getConfiguration())
: locale; : locale;
// The keyboard uses the language layout of the user dictionary // The keyboard uses the language layout of the user dictionary
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
mWordEditText.setImeHintLocales(new LocaleList(LocaleUtils.constructLocaleFromString(mLocaleString))); mWordEditText.setImeHintLocales(new LocaleList(mLocale));
} }
} }
String getLocale() { Locale getLocale() {
return mLocaleString; return mLocale;
} }
void delete(final Context context) { void delete(final Context context) {
@ -133,13 +136,14 @@ public class UserDictionaryAddWordContents {
return; return;
final ContentResolver resolver = context.getContentResolver(); final ContentResolver resolver = context.getContentResolver();
// Remove the old entry. // 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) { public final int apply(@NonNull final Context context) {
final ContentResolver resolver = context.getContentResolver(); final ContentResolver resolver = context.getContentResolver();
final String newWord = mWordEditText.getText().toString(); final String newWord = mWordEditText.getText().toString();
final String locale = mLocaleString; final String localeString = mLocale.toString();
if (TextUtils.isEmpty(newWord)) { if (TextUtils.isEmpty(newWord)) {
// If the word is empty, don't insert it. // If the word is empty, don't insert it.
@ -159,35 +163,33 @@ public class UserDictionaryAddWordContents {
mSavedWord = newWord; mSavedWord = newWord;
// In edit mode, everything is modified without overwriting other existing words // In edit mode, everything is modified without overwriting other existing words
if (MODE_EDIT == mMode && hasWord(newWord, locale, context) && newWord.equals(mOldWord)) { if (MODE_EDIT == mMode && hasWord(newWord, localeString, context) && newWord.equals(mOldWord)) {
UserDictionarySettings.deleteWordInEditMode(mOldWord, mOldShortcut, mOldWeight, locale, resolver); UserDictionarySettings.deleteWordInEditMode(mOldWord, mOldShortcut, mOldWeight, localeString, resolver);
} else { } else {
mMode = MODE_INSERT; mMode = MODE_INSERT;
} }
if (mMode == MODE_INSERT && hasWord(newWord, locale, context)) { if (mMode == MODE_INSERT && hasWord(newWord, localeString, context)) {
return CODE_ALREADY_PRESENT; return CODE_ALREADY_PRESENT;
} }
if (mMode == MODE_INSERT) { if (mMode == MODE_INSERT) {
// Delete duplicate when adding or updating new word // 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 // Update the existing word by adding a new one
UserDictionary.Words.addWord(context, newWord, UserDictionary.Words.addWord(context, newWord, Integer.parseInt(mSavedWeight),
Integer.parseInt(mSavedWeight), mSavedShortcut, TextUtils.isEmpty(mLocaleString) ? mSavedShortcut, TextUtils.isEmpty(mLocale.toString()) ? null : mLocale);
null : LocaleUtils.constructLocaleFromString(mLocaleString));
return CODE_UPDATED; return CODE_UPDATED;
} }
// Delete duplicates // 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 // 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'. // be null. However the addWord method takes null to mean 'all locales'.
UserDictionary.Words.addWord(context, newWord, UserDictionary.Words.addWord(context, newWord, Integer.parseInt(mSavedWeight),
Integer.parseInt(mSavedWeight), mSavedShortcut, TextUtils.isEmpty(mLocaleString) ? mSavedShortcut, TextUtils.isEmpty(mLocale.toString()) ? null : mLocale);
null : LocaleUtils.constructLocaleFromString(mLocaleString));
return CODE_WORD_ADDED; return CODE_WORD_ADDED;
} }
@ -195,7 +197,7 @@ public class UserDictionaryAddWordContents {
public boolean isExistingWord(final Context context) { public boolean isExistingWord(final Context context) {
final String newWord = mWordEditText.getText().toString(); final String newWord = mWordEditText.getText().toString();
if (mMode != MODE_EDIT) { if (mMode != MODE_EDIT) {
return hasWord(newWord, mLocaleString, context); return hasWord(newWord, mLocale.toString(), context);
} else { } else {
return false; return false;
} }
@ -207,17 +209,18 @@ public class UserDictionaryAddWordContents {
private static final String HAS_WORD_AND_ALL_LOCALES_SELECTION = UserDictionary.Words.WORD + "=? AND " private static final String HAS_WORD_AND_ALL_LOCALES_SELECTION = UserDictionary.Words.WORD + "=? AND "
+ UserDictionary.Words.LOCALE + " is null"; + 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; final Cursor cursor;
if ("".equals(locale)) { if ("".equals(localeString)) {
cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI, cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI,
HAS_WORD_PROJECTION, HAS_WORD_AND_ALL_LOCALES_SELECTION, HAS_WORD_PROJECTION, HAS_WORD_AND_ALL_LOCALES_SELECTION,
new String[] { word }, null); new String[] { word }, null);
} else { } else {
cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI, cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI,
HAS_WORD_PROJECTION, HAS_WORD_AND_LOCALE_SELECTION, HAS_WORD_PROJECTION, HAS_WORD_AND_LOCALE_SELECTION,
new String[] { word, locale}, null); new String[] { word, localeString}, null);
} }
try { try {
if (null == cursor) return false; if (null == cursor) return false;
@ -230,28 +233,34 @@ public class UserDictionaryAddWordContents {
public static class LocaleRenderer { public static class LocaleRenderer {
private final String mLocaleString; private final String mLocaleString;
private final String mDescription; private final String mDescription;
private final Locale mLocale;
public LocaleRenderer(final Context context, @Nullable final String localeString) { public LocaleRenderer(final Context context, @NonNull final Locale locale) {
mLocaleString = localeString; mLocaleString = locale.toString();
mLocale = locale;
if (null == localeString || "".equals(localeString)) { if ("".equals(locale.toString())) {
mDescription = context.getString(R.string.user_dict_settings_all_languages); mDescription = context.getString(R.string.user_dict_settings_all_languages);
} else { } else {
mDescription = UserDictionarySettings.getLocaleDisplayName(context, localeString); mDescription = UserDictionarySettings.getLocaleDisplayName(context, locale);
} }
} }
@Override @Override
// used in ArrayAdapter of spinner in UserDictionaryAddWordFragment
public String toString() { public String toString() {
return mDescription; return mDescription;
} }
public String getLocaleString() { public String getLocaleString() {
return mLocaleString; return mLocaleString;
} }
public Locale getLocale() {
return mLocale;
}
} }
private static void addLocaleDisplayNameToList(final Context context, private static void addLocaleDisplayNameToList(final Context context,
final ArrayList<LocaleRenderer> list, final String locale) { final ArrayList<LocaleRenderer> list, final Locale locale) {
if (null != locale) { if (null != locale) {
list.add(new LocaleRenderer(context, 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 // Helper method to get the list of locales and subtypes to display for this word
public ArrayList<LocaleRenderer> getLocaleRendererList(final Context context) { 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 // 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 // "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 list of locales to show
final ArrayList<LocaleRenderer> localesList = new ArrayList<>(); final ArrayList<LocaleRenderer> localesList = new ArrayList<>();
// First, add the language of the personal dictionary at the top of the list // 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() // Next, add all other languages which will be sorted alphabetically in UserDictionaryAddWordFragment.updateSpinner()
for (String language : sortedLanguages) { for (Locale locale : sortedLocales) {
addLocaleDisplayNameToList(context, localesList, language); addLocaleDisplayNameToList(context, localesList, locale);
} }
// Finally, add "All languages" at the end of the list // Finally, add "All languages" at the end of the list
if (!"".equals(mLocaleString)) { if (!"".equals(mLocale.toString())) {
addLocaleDisplayNameToList(context, localesList, ""); addLocaleDisplayNameToList(context, localesList, new Locale(""));
} }
return localesList; return localesList;

View file

@ -32,6 +32,7 @@ import androidx.core.graphics.drawable.DrawableKt;
import androidx.core.widget.TextViewKt; import androidx.core.widget.TextViewKt;
import org.dslul.openboard.inputmethod.latin.R; 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 org.dslul.openboard.inputmethod.latin.settings.UserDictionaryAddWordContents.LocaleRenderer;
import java.util.ArrayList; import java.util.ArrayList;
@ -165,22 +166,22 @@ public class UserDictionaryAddWordFragment extends SubScreenFragment {
localeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { localeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override @Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 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 // To have the selected language at the top of the list, this one is removed from the list
localesList.remove(position); localesList.remove(position);
// The other languages are then sorted alphabetically by name, with the exception of "For all languages" // The other languages are then sorted alphabetically by name, with the exception of "For all languages"
Collections.sort(localesList, (locale1, locale2) -> { Collections.sort(localesList, (localeRenderer1, localeRenderer2) -> {
if (!locale1.getLocaleString().equals("") && !locale2.getLocaleString().equals("")) { if (!localeRenderer1.getLocaleString().equals("") && !localeRenderer2.getLocaleString().equals("")) {
return locale1.toString().compareToIgnoreCase(locale2.toString()); return localeRenderer1.toString().compareToIgnoreCase(localeRenderer2.toString());
} else { } else {
return locale1.getLocaleString().compareToIgnoreCase(locale2.getLocaleString()); return localeRenderer1.getLocaleString().compareToIgnoreCase(localeRenderer2.getLocaleString());
} }
}); });
// Set "For all languages" to the end of the list // 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. // After alphabetical sorting, "For all languages" is always in 1st position.
// (The position is 0 because the spinner menu item count starts at 0) // (The position is 0 because the spinner menu item count starts at 0)
final LocaleRenderer forAllLanguages = adapter.getItem(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. // 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 // When a language is selected, the keyboard layout changes automatically
mInput.restartInput(mWordEditText); mInput.restartInput(mWordEditText);
// The action bar subtitle is updated when a language is selected in the drop-down menu // 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 @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. // I'm not sure we can come here, but if we do, that's the right thing to do.
final Bundle args = getArguments(); final Bundle args = getArguments();
if (args == null) return; 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.R;
import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils; 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.SubtypeSettingsKt;
import org.dslul.openboard.inputmethod.latin.utils.SubtypeUtilsKt;
import java.util.Comparator;
import java.util.Locale; import java.util.Locale;
import java.util.TreeSet; import java.util.TreeSet;
@ -91,54 +94,58 @@ public class UserDictionaryListFragment extends SubScreenFragment {
* @param userDictGroup The group to put the settings in. * @param userDictGroup The group to put the settings in.
*/ */
private void createUserDictSettings(final PreferenceGroup userDictGroup) { private void createUserDictSettings(final PreferenceGroup userDictGroup) {
final TreeSet<String> sortedLanguages = getSortedDictionaryLocaleStrings(requireContext()); final TreeSet<Locale> sortedLocales = getSortedDictionaryLocales(requireContext());
// Add preference "for all locales" // Add preference "for all locales"
userDictGroup.addPreference(createUserDictionaryPreference("")); userDictGroup.addPreference(createUserDictionaryPreference(new Locale("")));
// Add preference for each dictionary locale // Add preference for each dictionary locale
for (String localeUserDictionary : sortedLanguages) { for (final Locale locale : sortedLocales) {
userDictGroup.addPreference(createUserDictionaryPreference(localeUserDictionary)); 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 SharedPreferences prefs = DeviceProtectedUtils.getSharedPreferences(context);
final boolean localeSystemOnly = prefs.getBoolean(Settings.PREF_USE_SYSTEM_LOCALES, true); 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" // Add the main language selected in the "Language and Layouts" setting except "No language"
for (InputMethodSubtype mainSubtype : SubtypeSettingsKt.getEnabledSubtypes(prefs, true)) { for (InputMethodSubtype mainSubtype : SubtypeSettingsKt.getEnabledSubtypes(prefs, true)) {
if (!mainSubtype.getLocale().equals("zz")) { final Locale mainLocale = SubtypeUtilsKt.locale(mainSubtype);
sortedLanguages.add(mainSubtype.getLocale()); 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 // Secondary language is added only if main language is selected and if system language is not enabled
if (!localeSystemOnly) { if (!localeSystemOnly) {
for (Locale secondaryLocale : Settings.getSecondaryLocales(prefs, mainSubtype.getLocale())) { sortedLocales.addAll(Settings.getSecondaryLocales(prefs, mainLocale));
sortedLanguages.add(secondaryLocale.toString());
}
} }
} }
for (Locale systemSubtype : SubtypeSettingsKt.getSystemLocales()) { sortedLocales.addAll(SubtypeSettingsKt.getSystemLocales());
sortedLanguages.add(systemSubtype.toString()); 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. * 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. * @return The corresponding preference.
*/ */
private Preference createUserDictionaryPreference(@NonNull final String localeString) { private Preference createUserDictionaryPreference(@NonNull final Locale locale) {
final Preference newPref = new Preference(requireContext()); final Preference newPref = new Preference(requireContext());
if (localeString.isEmpty()) { if (locale.toString().isEmpty()) {
newPref.setTitle(getString(R.string.user_dict_settings_all_languages)); newPref.setTitle(getString(R.string.user_dict_settings_all_languages));
} else { } 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.setIconSpaceReserved(false);
newPref.setFragment(UserDictionarySettings.class.getName()); newPref.setFragment(UserDictionarySettings.class.getName());

View file

@ -94,7 +94,7 @@ public class UserDictionarySettings extends ListFragment {
private Cursor mCursor; private Cursor mCursor;
protected String mLocale; protected Locale mLocale;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -122,13 +122,13 @@ public class UserDictionarySettings extends ListFragment {
final Bundle arguments = getArguments(); final Bundle arguments = getArguments();
final String localeFromArguments = null == arguments ? null : arguments.getString("locale"); final String localeFromArguments = null == arguments ? null : arguments.getString("locale");
final String locale; final String localeString;
if (null != localeFromArguments) { if (null != localeFromArguments) {
locale = localeFromArguments; localeString = localeFromArguments;
} else locale = localeFromIntent; } else localeString = localeFromIntent;
mLocale = localeString == null ? null : LocaleUtils.constructLocale(localeString);
mLocale = locale; createCursor(mLocale == null ? null : mLocale.toString());
createCursor(locale);
TextView emptyView = view.findViewById(android.R.id.empty); TextView emptyView = view.findViewById(android.R.id.empty);
emptyView.setText(R.string.user_dict_settings_empty_text); emptyView.setText(R.string.user_dict_settings_empty_text);
@ -158,8 +158,9 @@ public class UserDictionarySettings extends ListFragment {
} }
} }
private void createCursor(final String locale) { // cursor must be created using localeString to be in line with Android system
// Locale can be any of: private void createCursor(@Nullable final String localeString) {
// localeString can be any of:
// - The string representation of a locale, as returned by Locale#toString() // - 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. // - 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. // - 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 // human-readable, like "all_locales" and "current_locales" strings, provided they
// can be guaranteed not to match locales that may exist. // can be guaranteed not to match locales that may exist.
if ("".equals(locale)) { if ("".equals(localeString)) {
// Case-insensitive sort // Case-insensitive sort
mCursor = requireContext().getContentResolver().query(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION, mCursor = requireContext().getContentResolver().query(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
QUERY_SELECTION_ALL_LOCALES, null, QUERY_SELECTION_ALL_LOCALES, null,
"UPPER(" + UserDictionary.Words.WORD + ")"); "UPPER(" + UserDictionary.Words.WORD + ")");
} else { } 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, mCursor = requireContext().getContentResolver().query(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
QUERY_SELECTION, new String[] { queryLocale }, QUERY_SELECTION, new String[] { queryLocale },
"UPPER(" + UserDictionary.Words.WORD + ")"); "UPPER(" + UserDictionary.Words.WORD + ")");
@ -200,13 +201,12 @@ public class UserDictionarySettings extends ListFragment {
} }
} }
public static String getLocaleDisplayName(Context context, String localeStr) { public static String getLocaleDisplayName(Context context, Locale locale) {
if (TextUtils.isEmpty(localeStr)) { if (locale.toString().isEmpty()) {
// CAVEAT: localeStr should not be null because a null locale stands for the system // CAVEAT: localeStr should not be null because a null locale stands for the system
// locale in UserDictionary.Words.addWord. // locale in UserDictionary.Words.addWord.
return context.getResources().getString(R.string.user_dict_settings_all_languages); return context.getResources().getString(R.string.user_dict_settings_all_languages);
} }
final Locale locale = LocaleUtils.constructLocaleFromString(localeStr);
return LocaleUtils.getLocaleDisplayNameInSystemLocale(locale, context); return LocaleUtils.getLocaleDisplayNameInSystemLocale(locale, context);
} }
@ -223,7 +223,7 @@ public class UserDictionarySettings extends ListFragment {
args.putString(UserDictionaryAddWordContents.EXTRA_WORD, editingWord); args.putString(UserDictionaryAddWordContents.EXTRA_WORD, editingWord);
args.putString(UserDictionaryAddWordContents.EXTRA_SHORTCUT, editingShortcut); args.putString(UserDictionaryAddWordContents.EXTRA_SHORTCUT, editingShortcut);
args.putString(UserDictionaryAddWordContents.EXTRA_WEIGHT, editingWeight); args.putString(UserDictionaryAddWordContents.EXTRA_WEIGHT, editingWeight);
args.putString(UserDictionaryAddWordContents.EXTRA_LOCALE, mLocale); args.putString(UserDictionaryAddWordContents.EXTRA_LOCALE, mLocale.toLanguageTag());
AppCompatActivity activity = (AppCompatActivity) requireActivity(); AppCompatActivity activity = (AppCompatActivity) requireActivity();
activity.getSupportFragmentManager().beginTransaction() activity.getSupportFragmentManager().beginTransaction()
.replace(android.R.id.content, UserDictionaryAddWordFragment.class, args) .replace(android.R.id.content, UserDictionaryAddWordFragment.class, args)
@ -252,27 +252,28 @@ public class UserDictionarySettings extends ListFragment {
return mCursor.getString(mCursor.getColumnIndexOrThrow(column)); 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, 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 (TextUtils.isEmpty(shortcut)) {
if ("".equals(locale)) { if ("".equals(localeString)) {
resolver.delete( resolver.delete(
UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITHOUT_SHORTCUT_AND_WITH_ALL_LOCALES, UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITHOUT_SHORTCUT_AND_WITH_ALL_LOCALES,
new String[] { word, weight }); new String[] { word, weight });
} else { } else {
resolver.delete( resolver.delete(
UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITHOUT_SHORTCUT_AND_WITH_LOCALE, UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITHOUT_SHORTCUT_AND_WITH_LOCALE,
new String[] { word, weight, locale }); new String[] { word, weight, localeString });
} }
} else { } else {
if ("".equals(locale)) { if ("".equals(localeString)) {
resolver.delete( resolver.delete(
UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITH_SHORTCUT_AND_WITH_ALL_LOCALES, UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITH_SHORTCUT_AND_WITH_ALL_LOCALES,
new String[] { word, shortcut, weight }); new String[] { word, shortcut, weight });
} else { } else {
resolver.delete( resolver.delete(
UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITH_SHORTCUT_AND_WITH_LOCALE, 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.settings.SettingsValuesForSuggestion;
import org.dslul.openboard.inputmethod.latin.utils.AdditionalSubtypeUtils; import org.dslul.openboard.inputmethod.latin.utils.AdditionalSubtypeUtils;
import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils; 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 org.dslul.openboard.inputmethod.latin.utils.SuggestionResults;
import java.util.Locale; import java.util.Locale;
@ -81,36 +81,17 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
mRecommendedThreshold = Float.parseFloat( mRecommendedThreshold = Float.parseFloat(getString(R.string.spellchecker_recommended_threshold_value));
getString(R.string.spellchecker_recommended_threshold_value));
final SharedPreferences prefs = DeviceProtectedUtils.getSharedPreferences(this); final SharedPreferences prefs = DeviceProtectedUtils.getSharedPreferences(this);
prefs.registerOnSharedPreferenceChangeListener(this); prefs.registerOnSharedPreferenceChangeListener(this);
onSharedPreferenceChanged(prefs, PREF_USE_CONTACTS_KEY); onSharedPreferenceChanged(prefs, PREF_USE_CONTACTS_KEY);
SubtypeSettingsKt.init(this);
} }
public float getRecommendedThreshold() { public float getRecommendedThreshold() {
return mRecommendedThreshold; 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 @Override
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
if (!PREF_USE_CONTACTS_KEY.equals(key)) return; if (!PREF_USE_CONTACTS_KEY.equals(key)) return;
@ -201,17 +182,14 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
Keyboard keyboard = mKeyboardCache.get(locale); Keyboard keyboard = mKeyboardCache.get(locale);
if (keyboard == null) { if (keyboard == null) {
keyboard = createKeyboardForLocale(locale); keyboard = createKeyboardForLocale(locale);
if (keyboard != null) { mKeyboardCache.put(locale, keyboard);
mKeyboardCache.put(locale, keyboard);
}
} }
return keyboard; return keyboard;
} }
private Keyboard createKeyboardForLocale(final Locale locale) { private Keyboard createKeyboardForLocale(final Locale locale) {
final String keyboardLayoutName = getKeyboardLayoutNameForLocale(locale); final String keyboardLayoutName = SubtypeSettingsKt.getMatchingLayoutSetNameForLocale(locale);
final InputMethodSubtype subtype = AdditionalSubtypeUtils.createDummyAdditionalSubtype( final InputMethodSubtype subtype = AdditionalSubtypeUtils.createDummyAdditionalSubtype(locale, keyboardLayoutName);
locale.toString(), keyboardLayoutName);
final KeyboardLayoutSet keyboardLayoutSet = createKeyboardSetForSpellChecker(subtype); final KeyboardLayoutSet keyboardLayoutSet = createKeyboardSetForSpellChecker(subtype);
return keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET); 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.content.res.Resources;
import android.os.Binder; import android.os.Binder;
import android.text.TextUtils; import android.text.TextUtils;
import org.dslul.openboard.inputmethod.latin.utils.Log;
import android.view.textservice.SentenceSuggestionsInfo; import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SuggestionsInfo; import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo; import android.view.textservice.TextInfo;
import org.dslul.openboard.inputmethod.latin.NgramContext; 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 org.dslul.openboard.inputmethod.latin.utils.SpannableStringUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Locale;
public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession { public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession {
private static final String TAG = AndroidSpellCheckerSession.class.getSimpleName(); private static final String TAG = AndroidSpellCheckerSession.class.getSimpleName();
@ -142,10 +142,9 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
synchronized(this) { synchronized(this) {
sentenceLevelAdapter = mSentenceLevelAdapter; sentenceLevelAdapter = mSentenceLevelAdapter;
if (sentenceLevelAdapter == null) { if (sentenceLevelAdapter == null) {
final String localeStr = getLocale(); final String localeString = getLocale();
if (!TextUtils.isEmpty(localeStr)) { if (!TextUtils.isEmpty(localeString)) {
sentenceLevelAdapter = new SentenceLevelAdapter(mResources, sentenceLevelAdapter = new SentenceLevelAdapter(mResources, LocaleUtils.constructLocale(localeString));
new Locale(localeStr));
mSentenceLevelAdapter = sentenceLevelAdapter; mSentenceLevelAdapter = sentenceLevelAdapter;
} }
} }

View file

@ -50,7 +50,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
// Immutable, but not available in the constructor. // Immutable, but not available in the constructor.
private Locale mLocale; private Locale mLocale;
// Cache this for performance // Cache this for performance
private int mScript; // One of SCRIPT_LATIN or SCRIPT_CYRILLIC for now. private String mScript;
private final AndroidSpellCheckerService mService; private final AndroidSpellCheckerService mService;
protected final SuggestionsCache mSuggestionsCache = new SuggestionsCache(); protected final SuggestionsCache mSuggestionsCache = new SuggestionsCache();
private final ContentObserver mObserver; private final ContentObserver mObserver;
@ -58,7 +58,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
private static final String quotesRegexp = private static final String quotesRegexp =
"(\\u0022|\\u0027|\\u0060|\\u00B4|\\u2018|\\u2018|\\u201C|\\u201D)"; "(\\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 { static {
// TODO: add other non-English language specific punctuation later. // TODO: add other non-English language specific punctuation later.
@ -107,27 +107,26 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
AndroidWordLevelSpellCheckerSession(final AndroidSpellCheckerService service) { AndroidWordLevelSpellCheckerSession(final AndroidSpellCheckerService service) {
mService = service; mService = service;
final ContentResolver cres = service.getContentResolver();
mObserver = new ContentObserver(null) { mObserver = new ContentObserver(null) {
@Override @Override
public void onChange(boolean self) { public void onChange(boolean self) {
mSuggestionsCache.clearCache(); mSuggestionsCache.clearCache();
} }
}; };
cres.registerContentObserver(Words.CONTENT_URI, true, mObserver); service.getContentResolver().registerContentObserver(Words.CONTENT_URI, true, mObserver);
} }
private void updateLocale() { private void updateLocale() {
final String localeString = getLocale(); final String localeString = getLocale();
if (mLocale == null || !mLocale.toString().equals(localeString)) { if (mLocale == null || !mLocale.toString().equals(localeString)) {
final String oldLocal = mLocale == null ? "null" : mLocale.toString(); final String oldLocale = mLocale == null ? "null" : mLocale.toString();
Log.d(TAG, "Updating locale from " + oldLocal + " to " + localeString); Log.d(TAG, "Updating locale from " + oldLocale + " to " + localeString);
mLocale = (null == localeString) ? null mLocale = (null == localeString) ? null
: LocaleUtils.constructLocaleFromString(localeString); : LocaleUtils.constructLocale(localeString);
mScript = ScriptUtils.getScriptFromSpellCheckerLocale(mLocale); if (mLocale == null) mScript = ScriptUtils.SCRIPT_UNKNOWN;
else mScript = ScriptUtils.script(mLocale);
} }
} }
@ -137,7 +136,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
} }
@Override @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. // 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; final InputMethodManager imm;
@ -179,7 +178,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
private static final int CHECKABILITY_TOO_SHORT = 5; private static final int CHECKABILITY_TOO_SHORT = 5;
/** /**
* Finds out whether a particular string should be filtered out of spell checking. * 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 * 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 * 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 * 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 * @param script the identifier for the script this spell checker recognizes
* @return one of the FILTER_OUT_* constants above. * @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; 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 // 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. * 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 lower-case, we test only the exact string.
* If the "Text" is capitalized, we test the exact string "Text" and the lower-cased * If the "Text" is capitalized, we test the exact string "Text" and the lower-cased
* version of it "text". * version of it "text".
@ -276,9 +275,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
.replaceAll("^" + quotesRegexp, "") .replaceAll("^" + quotesRegexp, "")
.replaceAll(quotesRegexp + "$", ""); .replaceAll(quotesRegexp + "$", "");
final String localeRegex = scriptToPunctuationRegexMap.get( final String localeRegex = scriptToPunctuationRegexMap.get(ScriptUtils.script(mLocale));
ScriptUtils.getScriptFromSpellCheckerLocale(mLocale)
);
if (localeRegex != null) { if (localeRegex != null) {
text = text.replaceAll(localeRegex, ""); text = text.replaceAll(localeRegex, "");
@ -332,8 +329,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
if (null == keyboard) { if (null == keyboard) {
Log.w(TAG, "onGetSuggestionsInternal() : No keyboard for locale: " + mLocale); Log.w(TAG, "onGetSuggestionsInternal() : No keyboard for locale: " + mLocale);
// If there is no keyboard for this locale, don't do any spell-checking. // If there is no keyboard for this locale, don't do any spell-checking.
return AndroidSpellCheckerService.getNotInDictEmptySuggestions( return AndroidSpellCheckerService.getNotInDictEmptySuggestions(false);
false /* reportAsTypo */);
} }
final WordComposer composer = new WordComposer(); 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.ArrayList;
import java.util.Arrays; 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.ASCII_CAPABLE;
import static org.dslul.openboard.inputmethod.latin.common.Constants.Subtype.ExtraValue.EMOJI_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 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_KEYBOARD_LAYOUT = 1;
private static final int INDEX_OF_EXTRA_VALUE = 2; private static final int INDEX_OF_EXTRA_VALUE = 2;
private static final int LENGTH_WITHOUT_EXTRA_VALUE = (INDEX_OF_KEYBOARD_LAYOUT + 1); 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 final String PREF_SUBTYPE_SEPARATOR = ";";
private static InputMethodSubtype createAdditionalSubtypeInternal( private static InputMethodSubtype createAdditionalSubtypeInternal(
final String localeString, final String keyboardLayoutSetName, final Locale locale, final String keyboardLayoutSetName,
final boolean isAsciiCapable, final boolean isEmojiCapable) { final boolean isAsciiCapable, final boolean isEmojiCapable) {
final int nameId = SubtypeLocaleUtils.getSubtypeNameId(localeString, keyboardLayoutSetName); final int nameId = SubtypeLocaleUtils.getSubtypeNameId(locale, keyboardLayoutSetName);
final String platformVersionDependentExtraValues = getPlatformVersionDependentExtraValue( final String platformVersionDependentExtraValues = getPlatformVersionDependentExtraValue(
localeString, keyboardLayoutSetName, isAsciiCapable, isEmojiCapable); locale, keyboardLayoutSetName, isAsciiCapable, isEmojiCapable);
final int platformVersionIndependentSubtypeId = final int platformVersionIndependentSubtypeId =
getPlatformVersionIndependentSubtypeId(localeString, keyboardLayoutSetName); getPlatformVersionIndependentSubtypeId(locale, keyboardLayoutSetName);
final InputMethodSubtype.InputMethodSubtypeBuilder builder = new InputMethodSubtype.InputMethodSubtypeBuilder() final InputMethodSubtype.InputMethodSubtypeBuilder builder = new InputMethodSubtype.InputMethodSubtypeBuilder()
.setSubtypeNameResId(nameId) .setSubtypeNameResId(nameId)
.setSubtypeIconResId(R.drawable.ic_ime_switcher) .setSubtypeIconResId(R.drawable.ic_ime_switcher)
.setSubtypeLocale(localeString) .setSubtypeLocale(locale.toString())
.setSubtypeMode(KEYBOARD_MODE) .setSubtypeMode(KEYBOARD_MODE)
.setSubtypeExtraValue(platformVersionDependentExtraValues) .setSubtypeExtraValue(platformVersionDependentExtraValues)
.setIsAuxiliary(false) .setIsAuxiliary(false)
@ -66,28 +67,27 @@ public final class AdditionalSubtypeUtils {
.setSubtypeId(platformVersionIndependentSubtypeId) .setSubtypeId(platformVersionIndependentSubtypeId)
.setIsAsciiCapable(isAsciiCapable); .setIsAsciiCapable(isAsciiCapable);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
builder.setLanguageTag(LocaleUtils.constructLocaleFromString(localeString).toLanguageTag()); builder.setLanguageTag(locale.toLanguageTag());
return builder.build(); return builder.build();
} }
public static InputMethodSubtype createDummyAdditionalSubtype( public static InputMethodSubtype createDummyAdditionalSubtype(
final String localeString, final String keyboardLayoutSetName) { final Locale locale, final String keyboardLayoutSetName) {
return createAdditionalSubtypeInternal(localeString, keyboardLayoutSetName, false, false); return createAdditionalSubtypeInternal(locale, keyboardLayoutSetName, false, false);
} }
public static InputMethodSubtype createEmojiCapableAdditionalSubtype( public static InputMethodSubtype createEmojiCapableAdditionalSubtype(
final String localeString, final String keyboardLayoutSetName, final boolean asciiCapable) { final Locale locale, final String keyboardLayoutSetName, final boolean asciiCapable) {
return createAdditionalSubtypeInternal(localeString, keyboardLayoutSetName, asciiCapable, true); return createAdditionalSubtypeInternal(locale, keyboardLayoutSetName, asciiCapable, true);
} }
public static String getPrefSubtype(final InputMethodSubtype subtype) { private static String getPrefSubtype(final InputMethodSubtype subtype) {
final String localeString = subtype.getLocale();
final String keyboardLayoutSetName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); final String keyboardLayoutSetName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName; final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName;
final String extraValue = StringUtils.removeFromCommaSplittableTextIfExists( final String extraValue = StringUtils.removeFromCommaSplittableTextIfExists(
layoutExtraValue, StringUtils.removeFromCommaSplittableTextIfExists( layoutExtraValue, StringUtils.removeFromCommaSplittableTextIfExists(
IS_ADDITIONAL_SUBTYPE, subtype.getExtraValue())); 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; + keyboardLayoutSetName;
return extraValue.isEmpty() ? basePrefSubtype return extraValue.isEmpty() ? basePrefSubtype
: basePrefSubtype + LOCALE_AND_LAYOUT_SEPARATOR + extraValue; : basePrefSubtype + LOCALE_AND_LAYOUT_SEPARATOR + extraValue;
@ -115,11 +115,12 @@ public final class AdditionalSubtypeUtils {
Log.w(TAG, "Unknown additional subtype specified: " + prefSubtype); Log.w(TAG, "Unknown additional subtype specified: " + prefSubtype);
return null; 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 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 // 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)) { if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT && !keyboardLayoutSetName.startsWith(CustomLayoutUtilsKt.CUSTOM_LAYOUT_PREFIX)) {
// Skip unknown keyboard layout subtype. This may happen when predefined keyboard // Skip unknown keyboard layout subtype. This may happen when predefined keyboard
// layout has been removed. // 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 * assume that the extra values stored in a persistent storage are always valid. We need to
* regenerate the extra value on the fly instead. * regenerate the extra value on the fly instead.
* </p> * </p>
* @param localeString the locale string (e.g., "en_US").
* @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak"). * @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak").
* @param isAsciiCapable true when ASCII characters are supported with this layout. * @param isAsciiCapable true when ASCII characters are supported with this layout.
* @param isEmojiCapable true when Unicode Emoji 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. * @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 String keyboardLayoutSetName, final boolean isAsciiCapable,
final boolean isEmojiCapable) { final boolean isEmojiCapable) {
final ArrayList<String> extraValueItems = new ArrayList<>(); final ArrayList<String> extraValueItems = new ArrayList<>();
@ -179,7 +179,7 @@ public final class AdditionalSubtypeUtils {
if (isAsciiCapable) { if (isAsciiCapable) {
extraValueItems.add(ASCII_CAPABLE); extraValueItems.add(ASCII_CAPABLE);
} }
if (SubtypeLocaleUtils.isExceptionalLocale(localeString)) { if (SubtypeLocaleUtils.isExceptionalLocale(locale)) {
extraValueItems.add(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + extraValueItems.add(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" +
SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(keyboardLayoutSetName)); 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 * method even when we need to add some new extra values for the actual instance of
* {@link InputMethodSubtype}. * {@link InputMethodSubtype}.
* </p> * </p>
* @param localeString the locale string (e.g., "en_US").
* @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak"). * @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak").
* @return a platform-version independent subtype ID. * @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) { final String keyboardLayoutSetName) {
// For compatibility reasons, we concatenate the extra values in the following order. // For compatibility reasons, we concatenate the extra values in the following order.
// - KeyboardLayoutSet // - KeyboardLayoutSet
@ -218,7 +217,7 @@ public final class AdditionalSubtypeUtils {
final ArrayList<String> compatibilityExtraValueItems = new ArrayList<>(); final ArrayList<String> compatibilityExtraValueItems = new ArrayList<>();
compatibilityExtraValueItems.add(KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName); compatibilityExtraValueItems.add(KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName);
compatibilityExtraValueItems.add(ASCII_CAPABLE); compatibilityExtraValueItems.add(ASCII_CAPABLE);
if (SubtypeLocaleUtils.isExceptionalLocale(localeString)) { if (SubtypeLocaleUtils.isExceptionalLocale(locale)) {
compatibilityExtraValueItems.add(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + compatibilityExtraValueItems.add(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" +
SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(keyboardLayoutSetName)); SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(keyboardLayoutSetName));
} }
@ -226,7 +225,7 @@ public final class AdditionalSubtypeUtils {
compatibilityExtraValueItems.add(IS_ADDITIONAL_SUBTYPE); compatibilityExtraValueItems.add(IS_ADDITIONAL_SUBTYPE);
final String compatibilityExtraValues = TextUtils.join(",", compatibilityExtraValueItems); final String compatibilityExtraValues = TextUtils.join(",", compatibilityExtraValueItems);
return Arrays.hashCode(new Object[] { return Arrays.hashCode(new Object[] {
localeString, locale,
KEYBOARD_MODE, KEYBOARD_MODE,
compatibilityExtraValues, compatibilityExtraValues,
false /* isAuxiliary */, false /* isAuxiliary */,

View file

@ -22,7 +22,7 @@ import java.io.File
import java.io.IOException import java.io.IOException
import java.math.BigInteger 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) if (uri == null)
return infoDialog(context, context.getString(R.string.layout_error, "layout file not found")) return infoDialog(context, context.getString(R.string.layout_error, "layout file not found"))
val layoutContent: String val layoutContent: String
@ -41,10 +41,10 @@ fun loadCustomLayout(uri: Uri?, localeString: String, context: Context, onAdded:
name = it.getString(idx).substringBeforeLast(".") 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 var name = layoutName
val isJson = checkLayout(layoutContent, context) 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}")) ?: 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) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
// name must be encoded to avoid issues with validity of subtype extra string or file name // 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) val file = getFile(name, context)
if (file.exists()) if (file.exists())
file.delete() file.delete()

View file

@ -15,7 +15,7 @@ import androidx.annotation.Nullable;
import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
import org.dslul.openboard.inputmethod.annotations.UsedForTesting; 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.define.DecoderSpecificConstants;
import org.dslul.openboard.inputmethod.latin.makedict.DictionaryHeader; import org.dslul.openboard.inputmethod.latin.makedict.DictionaryHeader;
import org.dslul.openboard.inputmethod.latin.makedict.UnsupportedFormatException; import org.dslul.openboard.inputmethod.latin.makedict.UnsupportedFormatException;
@ -32,9 +32,11 @@ public class DictionaryInfoUtils {
private static final String TAG = DictionaryInfoUtils.class.getSimpleName(); private static final String TAG = DictionaryInfoUtils.class.getSimpleName();
public static final String DEFAULT_MAIN_DICT = "main"; public static final String DEFAULT_MAIN_DICT = "main";
public static final String MAIN_DICT_PREFIX = DEFAULT_MAIN_DICT + "_"; 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 // 6 digits - unicode is limited to 21 bits
private static final int MAX_HEX_DIGITS_FOR_CODEPOINT = 6; 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 DictionaryInfoUtils() {
// Private constructor to forbid instantation of this helper class. // 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 >= 0x30 && codePoint <= 0x39) return true; // Digit
if (codePoint >= 0x41 && codePoint <= 0x5A) return true; // Uppercase if (codePoint >= 0x41 && codePoint <= 0x5A) return true; // Uppercase
if (codePoint >= 0x61 && codePoint <= 0x7A) return true; // Lowercase 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"; 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)}. * Reverse escaping done by {@link #replaceFileNameDangerousCharacters(String)}.
*/ */
@ -119,32 +114,11 @@ public class DictionaryInfoUtils {
return new File(DictionaryInfoUtils.getWordListCacheDirectory(context)).listFiles(); 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. * Find out the cache directory associated with a specific locale.
*/ */
public static String getCacheDirectoryForLocale(final String locale, final Context context) { public static String getCacheDirectoryForLocale(final Locale locale, final Context context) {
final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale).toLowerCase(Locale.ENGLISH); final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale.toLanguageTag());
final String absoluteDirectoryName = getWordListCacheDirectory(context) + File.separator + relativeDirectoryName; final String absoluteDirectoryName = getWordListCacheDirectory(context) + File.separator + relativeDirectoryName;
final File directory = new File(absoluteDirectoryName); final File directory = new File(absoluteDirectoryName);
if (!directory.exists()) { if (!directory.exists()) {
@ -155,16 +129,11 @@ public class DictionaryInfoUtils {
return absoluteDirectoryName; return absoluteDirectoryName;
} }
public static boolean isMainWordListId(final String id) { public static File[] getCachedDictsForLocale(final Locale locale, final Context context) {
final String[] idArray = id.split(DICTIONARY_CATEGORY_SEPARATOR_EXPRESSION); final File cachedDir = new File(getCacheDirectoryForLocale(locale, context));
// An id is supposed to be in format category:locale, so splitting on the separator if (!cachedDir.isDirectory())
// should yield a 2-elements array return new File[]{};
// Also allow '_' as separator, this is ok for locales like pt_br because return cachedDir.listFiles();
// we're interested in the part before first separator anyway
if (1 == idArray.length) {
return false;
}
return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
} }
/** /**
@ -179,17 +148,11 @@ public class DictionaryInfoUtils {
// This works because we don't include by default different dictionaries for // This works because we don't include by default different dictionaries for
// different countries. This actually needs to return the id that we would // 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. // like to use for word lists included in resources, and the following is okay.
return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY + return Dictionary.TYPE_MAIN + ID_CATEGORY_SEPARATOR + locale.toString().toLowerCase();
BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale.toString().toLowerCase();
} }
public static String getMainDictFilename(@NonNull final String locale) { public static String getExtractedMainDictFilename() {
return MAIN_DICT_PREFIX + locale.toLowerCase(Locale.ENGLISH) + ".dict"; return DEFAULT_MAIN_DICT + ".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));
} }
@Nullable @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 @UsedForTesting
public static boolean looksValidForDictionaryInsertion(final CharSequence text, public static boolean looksValidForDictionaryInsertion(final CharSequence text,
final SpacingAndPunctuations spacingAndPunctuations) { final SpacingAndPunctuations spacingAndPunctuations) {

View file

@ -8,8 +8,8 @@ import android.view.View
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.edit 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.R
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils.constructLocale
import org.dslul.openboard.inputmethod.latin.settings.Settings import org.dslul.openboard.inputmethod.latin.settings.Settings
import java.io.File import java.io.File
import java.util.* import java.util.*
@ -24,19 +24,15 @@ fun getDictionaryLocales(context: Context): MutableSet<Locale> {
for (directory in cachedDirectoryList) { for (directory in cachedDirectoryList) {
if (!directory.isDirectory) continue if (!directory.isDirectory) continue
if (!hasAnythingOtherThanExtractedMainDictionary(directory)) continue if (!hasAnythingOtherThanExtractedMainDictionary(directory)) continue
val dirLocale = DictionaryInfoUtils.getWordListIdFromFileName(directory.name) val locale = DictionaryInfoUtils.getWordListIdFromFileName(directory.name).constructLocale()
val locale = dirLocale.toLocale()
locales.add(locale) locales.add(locale)
} }
} }
// get assets dictionaries // get assets dictionaries
val assetsDictionaryList = BinaryDictionaryGetter.getAssetsDictionaryList(context) val assetsDictionaryList = DictionaryInfoUtils.getAssetsDictionaryList(context)
if (assetsDictionaryList != null) { if (assetsDictionaryList != null) {
for (dictionary in assetsDictionaryList) { for (dictionary in assetsDictionaryList) {
val dictLocale = val locale = DictionaryInfoUtils.extractLocaleFromAssetsDictionaryFile(dictionary)?.constructLocale() ?: continue
BinaryDictionaryGetter.extractLocaleFromAssetsDictionaryFile(dictionary)
?: continue
val locale = dictLocale.toLocale()
locales.add(locale) locales.add(locale)
} }
} }
@ -73,15 +69,15 @@ fun cleanUnusedMainDicts(context: Context) {
val dictionaryDir = File(DictionaryInfoUtils.getWordListCacheDirectory(context)) val dictionaryDir = File(DictionaryInfoUtils.getWordListCacheDirectory(context))
val dirs = dictionaryDir.listFiles() ?: return val dirs = dictionaryDir.listFiles() ?: return
val prefs = DeviceProtectedUtils.getSharedPreferences(context) val prefs = DeviceProtectedUtils.getSharedPreferences(context)
val usedLocales = hashSetOf<String>() val usedLocaleLanguageTags = hashSetOf<String>()
getEnabledSubtypes(prefs).forEach { getEnabledSubtypes(prefs).forEach {
val locale = it.locale() val locale = it.locale()
usedLocales.add(locale) usedLocaleLanguageTags.add(locale.toLanguageTag())
Settings.getSecondaryLocales(prefs, locale).forEach { usedLocales.add(it.toString().lowercase()) } Settings.getSecondaryLocales(prefs, locale).forEach { usedLocaleLanguageTags.add(it.toLanguageTag()) }
} }
for (dir in dirs) { for (dir in dirs) {
if (!dir.isDirectory) continue if (!dir.isDirectory) continue
if (dir.name in usedLocales) continue if (dir.name in usedLocaleLanguageTags) continue
if (hasAnythingOtherThanExtractedMainDictionary(dir)) if (hasAnythingOtherThanExtractedMainDictionary(dir))
continue continue
dir.deleteRecursively() dir.deleteRecursively()
@ -89,6 +85,6 @@ fun cleanUnusedMainDicts(context: Context) {
} }
private fun hasAnythingOtherThanExtractedMainDictionary(dir: File) = 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" 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(); final String keyboardLayout = subtype.getKeyboardLayoutSetName();
int sameLanguageAndLayoutCount = 0; int sameLanguageAndLayoutCount = 0;
for (final InputMethodSubtype ims : sEnabledSubtypes) { 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( if (keyboardLanguage.equals(language) && keyboardLayout.equals(
SubtypeLocaleUtils.getKeyboardLayoutSetName(ims))) { SubtypeLocaleUtils.getKeyboardLayoutSetName(ims))) {
sameLanguageAndLayoutCount++; sameLanguageAndLayoutCount++;

View file

@ -6,13 +6,17 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import org.dslul.openboard.inputmethod.compat.locale
import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants 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.R
import org.dslul.openboard.inputmethod.latin.ReadOnlyBinaryDictionary import org.dslul.openboard.inputmethod.latin.ReadOnlyBinaryDictionary
import org.dslul.openboard.inputmethod.latin.common.FileUtils import org.dslul.openboard.inputmethod.latin.common.FileUtils
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils 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.makedict.DictionaryHeader
import org.dslul.openboard.inputmethod.latin.settings.* import org.dslul.openboard.inputmethod.latin.settings.*
import org.dslul.openboard.inputmethod.latin.utils.ScriptUtils.script
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.* 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()) val newHeader = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(cachedDictionaryFile, 0, cachedDictionaryFile.length())
?: return onDictionaryLoadingError(R.string.dictionary_file_error) ?: 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") val dict = ReadOnlyBinaryDictionary(cachedDictionaryFile.absolutePath, 0, cachedDictionaryFile.length(), false, locale, "test")
if (!dict.isValidDictionary) { if (!dict.isValidDictionary) {
@ -53,7 +57,7 @@ class NewDictionaryAdder(private val context: Context, private val onAdded: ((Bo
.setMessage(message) .setMessage(message)
.setNeutralButton(android.R.string.cancel) { _, _ -> cachedDictionaryFile.delete() } .setNeutralButton(android.R.string.cancel) { _, _ -> cachedDictionaryFile.delete() }
.setNegativeButton(R.string.button_select_language) { _, _ -> selectLocaleForDictionary(newHeader, locale) } .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) val buttonText = context.getString(R.string.button_add_to_language, localeName)
b.setPositiveButton(buttonText) { _, _ -> b.setPositiveButton(buttonText) { _, _ ->
addDictAndAskToReplace(newHeader, locale) 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, // ScriptUtils.getScriptFromSpellCheckerLocale may return latin when it should not,
// e.g. for Persian or Chinese. But at least fail when dictionary certainly is incompatible // 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) return onDictionaryLoadingError(R.string.dictionary_file_wrong_script)
if (locale != mainLocale) { if (locale != mainLocale) {
@ -87,13 +91,7 @@ class NewDictionaryAdder(private val context: Context, private val onAdded: ((Bo
} }
private fun selectLocaleForDictionary(newHeader: DictionaryHeader, dictLocale: Locale) { private fun selectLocaleForDictionary(newHeader: DictionaryHeader, dictLocale: Locale) {
val localeStrings = getAvailableSubtypeLocaleStrings() val locales = getAvailableSubtypeLocales().sortedBy { it.language != dictLocale.language } // matching languages should show first
val locales = linkedSetOf<Locale>()
localeStrings.forEach {
if (it.substringBefore("_") == dictLocale.language)
locales.add(it.toLocale())
}
localeStrings.forEach { locales.add(it.toLocale()) }
val displayNamesArray = locales.map { LocaleUtils.getLocaleDisplayNameInSystemLocale(it, context) }.toTypedArray() val displayNamesArray = locales.map { LocaleUtils.getLocaleDisplayNameInSystemLocale(it, context) }.toTypedArray()
AlertDialog.Builder(context) AlertDialog.Builder(context)
.setTitle(R.string.button_select_language) .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) { private fun addDictAndAskToReplace(header: DictionaryHeader, mainLocale: Locale) {
val dictionaryType = header.mIdString.substringBefore(":") val dictionaryType = header.mIdString.substringBefore(":")
val dictFilename = DictionaryInfoUtils.getCacheDirectoryForLocale(mainLocale.toString(), context) + val cacheDir = DictionaryInfoUtils.getCacheDirectoryForLocale(mainLocale, context)
File.separator + dictionaryType + "_" + USER_DICTIONARY_SUFFIX val dictFile = File(cacheDir, dictionaryType + "_" + USER_DICTIONARY_SUFFIX)
val dictFile = File(dictFilename)
fun moveDict(replaced: Boolean) { fun moveDict(replaced: Boolean) {
if (!cachedDictionaryFile.renameTo(dictFile)) { if (!cachedDictionaryFile.renameTo(dictFile)) {
return onDictionaryLoadingError(R.string.dictionary_load_error) 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 // replaced main dict, remove the one created from internal data
// todo: currently not, see also BinaryDictionaryGetter.getDictionaryFiles val internalMainDictFile = File(cacheDir, DictionaryInfoUtils.getExtractedMainDictFilename())
// val internalMainDictFilename = DictionaryInfoUtils.getCacheDirectoryForLocale(mainLocaleString, context) + internalMainDictFile.delete()
// File.separator + DictionaryInfoUtils.getMainDictFilename(mainLocaleString)
// File(internalMainDictFilename).delete()
} }
val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION) val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)
context.sendBroadcast(newDictBroadcast) context.sendBroadcast(newDictBroadcast)
@ -134,7 +129,7 @@ class NewDictionaryAdder(private val context: Context, private val onAdded: ((Bo
return moveDict(false) return moveDict(false)
} }
val systemLocale = context.resources.configuration.locale val systemLocale = context.resources.configuration.locale()
val newInfo = header.info(systemLocale) val newInfo = header.info(systemLocale)
val oldInfo = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(dictFile, 0, dictFile.length())?.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( 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) 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.content.res.Resources;
import android.view.inputmethod.InputMethodSubtype; 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.R;
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils; import org.dslul.openboard.inputmethod.latin.common.LocaleUtils;
import org.dslul.openboard.inputmethod.latin.common.StringUtils; 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); final String[] exceptionalLocaleInRootLocale = res.getStringArray(R.array.subtype_locale_displayed_in_root_locale);
for (final String localeString : exceptionalLocaleInRootLocale) { for (final String languageTag : exceptionalLocaleInRootLocale) {
final String resourceName = SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX + localeString; final String resourceName = SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX + languageTag.replace('-', '_');
final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME); 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); final String[] exceptionalLocales = res.getStringArray(R.array.subtype_locale_exception_keys);
for (final String localeString : exceptionalLocales) { for (final String languageTag : exceptionalLocales) {
final String resourceName = SUBTYPE_NAME_RESOURCE_PREFIX + localeString; final String resourceName = SUBTYPE_NAME_RESOURCE_PREFIX + languageTag.replace('-', '_');
final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME); final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME);
sExceptionalLocaleToNameIdsMap.put(localeString, resId); sExceptionalLocaleToNameIdsMap.put(languageTag, resId);
final String resourceNameWithLayout = SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX + localeString; final String resourceNameWithLayout = SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX + languageTag.replace('-', '_');
final int resIdWithLayout = res.getIdentifier(resourceNameWithLayout, null, RESOURCE_PACKAGE_NAME); 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) { public static boolean isExceptionalLocale(final Locale locale) {
return sExceptionalLocaleToNameIdsMap.containsKey(localeString); return sExceptionalLocaleToNameIdsMap.containsKey(locale.toLanguageTag());
} }
private static final String getNoLanguageLayoutKey(final String keyboardLayoutName) { private static String getNoLanguageLayoutKey(final String keyboardLayoutName) {
return NO_LANGUAGE + "_" + keyboardLayoutName; return NO_LANGUAGE + "_" + keyboardLayoutName;
} }
public static int getSubtypeNameId(final String localeString, final String keyboardLayoutName) { public static int getSubtypeNameId(final Locale locale, final String keyboardLayoutName) {
if (isExceptionalLocale(localeString)) { final String languageTag = locale.toLanguageTag();
return sExceptionalLocaleToWithLayoutNameIdsMap.get(localeString); if (isExceptionalLocale(locale)) {
return sExceptionalLocaleToWithLayoutNameIdsMap.get(languageTag);
} }
final String key = NO_LANGUAGE.equals(localeString) final String key = NO_LANGUAGE.equals(languageTag)
? getNoLanguageLayoutKey(keyboardLayoutName) ? getNoLanguageLayoutKey(keyboardLayoutName)
: keyboardLayoutName; : keyboardLayoutName;
final Integer nameId = sKeyboardLayoutToNameIdsMap.get(key); final Integer nameId = sKeyboardLayoutToNameIdsMap.get(key);
@ -133,53 +135,54 @@ public final class SubtypeLocaleUtils {
} }
@NonNull @NonNull
public static Locale getDisplayLocaleOfSubtypeLocale(@NonNull final String localeString) { public static Locale getDisplayLocaleOfSubtypeLocale(@NonNull final Locale locale) {
if (NO_LANGUAGE.equals(localeString)) { final String languageTag = locale.toLanguageTag();
return sResources.getConfiguration().locale; if (NO_LANGUAGE.equals(languageTag)) {
return ConfigurationCompatKt.locale(sResources.getConfiguration());
} }
if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) { if (sExceptionalLocaleDisplayedInRootLocale.containsKey(languageTag)) {
return Locale.ROOT; return Locale.ROOT;
} }
return LocaleUtils.constructLocaleFromString(localeString); return locale;
} }
public static String getSubtypeLocaleDisplayNameInSystemLocale( public static String getSubtypeLocaleDisplayNameInSystemLocale(@NonNull final Locale locale) {
@NonNull final String localeString) { final Locale displayLocale = ConfigurationCompatKt.locale(sResources.getConfiguration());
final Locale displayLocale = sResources.getConfiguration().locale; return getSubtypeLocaleDisplayNameInternal(locale, displayLocale);
return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale);
} }
@NonNull @NonNull
public static String getSubtypeLocaleDisplayName(@NonNull final String localeString) { public static String getSubtypeLocaleDisplayName(@NonNull final Locale locale) {
final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString); final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(locale);
return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale); return getSubtypeLocaleDisplayNameInternal(locale, displayLocale);
} }
@NonNull @NonNull
public static String getSubtypeLanguageDisplayName(@NonNull final String localeString) { public static String getSubtypeLanguageDisplayName(@NonNull final Locale locale) {
final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString); final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(locale);
final String languageString; final Locale languageLocale;
if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) { if (sExceptionalLocaleDisplayedInRootLocale.containsKey(locale.toLanguageTag())) {
languageString = localeString; languageLocale = locale;
} else { } else {
languageString = LocaleUtils.constructLocaleFromString(localeString).getLanguage(); languageLocale = LocaleUtils.constructLocale(locale.getLanguage());
} }
return getSubtypeLocaleDisplayNameInternal(languageString, displayLocale); return getSubtypeLocaleDisplayNameInternal(languageLocale, displayLocale);
} }
@NonNull @NonNull
private static String getSubtypeLocaleDisplayNameInternal(@NonNull final String localeString, private static String getSubtypeLocaleDisplayNameInternal(@NonNull final Locale locale,
@NonNull final Locale displayLocale) { @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. // No language subtype should be displayed in system locale.
return sResources.getString(R.string.subtype_no_language); return sResources.getString(R.string.subtype_no_language);
} }
final Integer exceptionalNameResId; final Integer exceptionalNameResId;
if (displayLocale.equals(Locale.ROOT) if (displayLocale.equals(Locale.ROOT)
&& sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) { && sExceptionalLocaleDisplayedInRootLocale.containsKey(languageTag)) {
exceptionalNameResId = sExceptionalLocaleDisplayedInRootLocale.get(localeString); exceptionalNameResId = sExceptionalLocaleDisplayedInRootLocale.get(languageTag);
} else if (sExceptionalLocaleToNameIdsMap.containsKey(localeString)) { } else if (sExceptionalLocaleToNameIdsMap.containsKey(languageTag)) {
exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString); exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(languageTag);
} else { } else {
exceptionalNameResId = null; exceptionalNameResId = null;
} }
@ -188,8 +191,7 @@ public final class SubtypeLocaleUtils {
if (exceptionalNameResId != null) { if (exceptionalNameResId != null) {
displayName = RunInLocaleKt.runInLocale(sResources, displayLocale, res -> res.getString(exceptionalNameResId)); displayName = RunInLocaleKt.runInLocale(sResources, displayLocale, res -> res.getString(exceptionalNameResId));
} else { } else {
displayName = LocaleUtils.constructLocaleFromString(localeString) displayName = locale.getDisplayName(displayLocale);
.getDisplayName(displayLocale);
} }
return StringUtils.capitalizeFirstCodePoint(displayName, displayLocale); return StringUtils.capitalizeFirstCodePoint(displayName, displayLocale);
} }
@ -218,13 +220,13 @@ public final class SubtypeLocaleUtils {
if (subtype.containsExtraValueKey(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) { if (subtype.containsExtraValueKey(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) {
return subtype.getExtraValueOf(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 @NonNull
public static String getSubtypeDisplayNameInSystemLocale( public static String getSubtypeDisplayNameInSystemLocale(
@NonNull final InputMethodSubtype subtype) { @NonNull final InputMethodSubtype subtype) {
final Locale displayLocale = sResources.getConfiguration().locale; final Locale displayLocale = ConfigurationCompatKt.locale(sResources.getConfiguration());
return getSubtypeDisplayNameInternal(subtype, displayLocale); return getSubtypeDisplayNameInternal(subtype, displayLocale);
} }
@ -233,7 +235,7 @@ public final class SubtypeLocaleUtils {
if (subtype == null) { if (subtype == null) {
return "<null subtype>"; return "<null subtype>";
} }
return getSubtypeLocale(subtype) + "/" + getKeyboardLayoutSetName(subtype); return SubtypeUtilsKt.locale(subtype) + "/" + getKeyboardLayoutSetName(subtype);
} }
@NonNull @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 @Nullable
public static String getKeyboardLayoutSetDisplayName(@NonNull final InputMethodSubtype subtype) { public static String getKeyboardLayoutSetDisplayName(@NonNull final InputMethodSubtype subtype) {
final String layoutName = getKeyboardLayoutSetName(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.keyboard.KeyboardSwitcher
import org.dslul.openboard.inputmethod.latin.R import org.dslul.openboard.inputmethod.latin.R
import org.dslul.openboard.inputmethod.latin.RichInputMethodManager 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.define.DebugFlags
import org.dslul.openboard.inputmethod.latin.settings.Settings import org.dslul.openboard.inputmethod.latin.settings.Settings
import org.dslul.openboard.inputmethod.latin.utils.ScriptUtils.script
import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser
import java.io.File import java.io.File
import java.util.* import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.LinkedHashMap
/** @return enabled subtypes. If no subtypes are enabled, but a contextForFallback is provided, /** @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> { fun getEnabledSubtypes(prefs: SharedPreferences, fallback: Boolean = false): List<InputMethodSubtype> {
require(initialized) require(initialized)
if (prefs.getBoolean(Settings.PREF_USE_SYSTEM_LOCALES, true)) if (prefs.getBoolean(Settings.PREF_USE_SYSTEM_LOCALES, true))
@ -37,6 +39,22 @@ fun getAllAvailableSubtypes(): List<InputMethodSubtype> {
return resourceSubtypesByLocale.values.flatten() + additionalSubtypes 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) { fun addEnabledSubtype(prefs: SharedPreferences, newSubtype: InputMethodSubtype) {
require(initialized) require(initialized)
val subtypeString = newSubtype.prefString() val subtypeString = newSubtype.prefString()
@ -46,7 +64,7 @@ fun addEnabledSubtype(prefs: SharedPreferences, newSubtype: InputMethodSubtype)
if (newSubtype !in enabledSubtypes) { if (newSubtype !in enabledSubtypes) {
enabledSubtypes.add(newSubtype) enabledSubtypes.add(newSubtype)
enabledSubtypes.sortBy { it.locale() } // for consistent order enabledSubtypes.sortBy { it.locale().toLanguageTag() } // for consistent order
RichInputMethodManager.getInstance().refreshSubtypeCaches() RichInputMethodManager.getInstance().refreshSubtypeCaches()
} }
} }
@ -77,15 +95,15 @@ fun removeAdditionalSubtype(prefs: SharedPreferences, resources: Resources, subt
fun getSelectedSubtype(prefs: SharedPreferences): InputMethodSubtype { fun getSelectedSubtype(prefs: SharedPreferences): InputMethodSubtype {
require(initialized) 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() val subtypes = if (prefs.getBoolean(Settings.PREF_USE_SYSTEM_LOCALES, true)) getDefaultEnabledSubtypes()
else enabledSubtypes 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() ?: subtypes.firstOrNull()
if (subtype == null) { if (subtype == null) {
val defaultSubtypes = getDefaultEnabledSubtypes() val defaultSubtypes = getDefaultEnabledSubtypes()
return defaultSubtypes.firstOrNull { subtypeString.first() == it.locale() && subtypeString.last() == SubtypeLocaleUtils.getKeyboardLayoutSetName(it) } return defaultSubtypes.firstOrNull { localeAndLayout.first == it.locale() && localeAndLayout.second == SubtypeLocaleUtils.getKeyboardLayoutSetName(it) }
?: defaultSubtypes.firstOrNull { subtypeString.first().substringBefore("_") == it.locale().substringBefore("_") && subtypeString.last() == SubtypeLocaleUtils.getKeyboardLayoutSetName(it) } ?: defaultSubtypes.firstOrNull { localeAndLayout.first.language == it.locale().language && localeAndLayout.second == SubtypeLocaleUtils.getKeyboardLayoutSetName(it) }
?: defaultSubtypes.first() ?: defaultSubtypes.first()
} }
return subtype return subtype
@ -93,7 +111,7 @@ fun getSelectedSubtype(prefs: SharedPreferences): InputMethodSubtype {
fun setSelectedSubtype(prefs: SharedPreferences, subtype: InputMethodSubtype) { fun setSelectedSubtype(prefs: SharedPreferences, subtype: InputMethodSubtype) {
val subtypeString = subtype.prefString() 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 return
prefs.edit { putString(Settings.PREF_SELECTED_INPUT_STYLE, subtypeString) } prefs.edit { putString(Settings.PREF_SELECTED_INPUT_STYLE, subtypeString) }
} }
@ -123,12 +141,12 @@ fun getSystemLocales(): List<Locale> {
return systemLocales return systemLocales
} }
fun hasMatchingSubtypeForLocaleString(localeString: String): Boolean { fun hasMatchingSubtypeForLocale(locale: Locale): Boolean {
require(initialized) require(initialized)
return !resourceSubtypesByLocale[localeString].isNullOrEmpty() return !resourceSubtypesByLocale[locale].isNullOrEmpty()
} }
fun getAvailableSubtypeLocaleStrings(): Collection<String> { fun getAvailableSubtypeLocales(): Collection<Locale> {
require(initialized) require(initialized)
return resourceSubtypesByLocale.keys return resourceSubtypesByLocale.keys
} }
@ -151,29 +169,29 @@ fun init(context: Context) {
private fun getDefaultEnabledSubtypes(): List<InputMethodSubtype> { private fun getDefaultEnabledSubtypes(): List<InputMethodSubtype> {
if (systemSubtypes.isNotEmpty()) return systemSubtypes if (systemSubtypes.isNotEmpty()) return systemSubtypes
val subtypes = systemLocales.mapNotNull { locale -> val subtypes = systemLocales.mapNotNull { locale ->
val localeString = locale.toString() val subtypesOfLocale = resourceSubtypesByLocale[locale]
val subtypesOfLocale = resourceSubtypesByLocale[localeString] // get best match
?: resourceSubtypesByLocale[localeString.substringBefore("_")] // fall back to language matching the subtype ?: LocaleUtils.getBestMatch(locale, resourceSubtypesByLocale.keys) {it}?.let { resourceSubtypesByLocale[it] }
?: localeString.substringBefore("_").let { language -> // fall back to languages matching subtype language
resourceSubtypesByLocale.firstNotNullOfOrNull {
if (it.key.substringBefore("_") == language)
it.value
else null
}
}
subtypesOfLocale?.firstOrNull() subtypesOfLocale?.firstOrNull()
} }
if (subtypes.isEmpty()) { if (subtypes.isEmpty()) {
// hardcoded fallback for weird cases // hardcoded fallback to en-US for weird cases
systemSubtypes.add(resourceSubtypesByLocale["en_US"]!!.first()) systemSubtypes.add(resourceSubtypesByLocale[Locale.US]!!.first())
} else { } else {
systemSubtypes.addAll(subtypes) systemSubtypes.addAll(subtypes)
} }
return systemSubtypes return systemSubtypes
} }
/** string for for identifying a subtype, does not contain all necessary information to actually create it */
private fun InputMethodSubtype.prefString() = 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) { private fun loadResourceSubtypes(resources: Resources) {
val xml = resources.getXml(R.xml.method) val xml = resources.getXml(R.xml.method)
@ -185,8 +203,8 @@ private fun loadResourceSubtypes(resources: Resources) {
val icon = xml.getAttributeResourceValue(namespace, "icon", 0) val icon = xml.getAttributeResourceValue(namespace, "icon", 0)
val label = xml.getAttributeResourceValue(namespace, "label", 0) val label = xml.getAttributeResourceValue(namespace, "label", 0)
val subtypeId = xml.getAttributeIntValue(namespace, "subtypeId", 0) val subtypeId = xml.getAttributeIntValue(namespace, "subtypeId", 0)
val locale = xml.getAttributeValue(namespace, "imeSubtypeLocale").intern() val localeString = xml.getAttributeValue(namespace, "imeSubtypeLocale").intern()
val languageTag = xml.getAttributeValue(namespace, "languageTag") val languageTag = xml.getAttributeValue(namespace, "languageTag").intern()
val imeSubtypeMode = xml.getAttributeValue(namespace, "imeSubtypeMode") val imeSubtypeMode = xml.getAttributeValue(namespace, "imeSubtypeMode")
val imeSubtypeExtraValue = xml.getAttributeValue(namespace, "imeSubtypeExtraValue").intern() val imeSubtypeExtraValue = xml.getAttributeValue(namespace, "imeSubtypeExtraValue").intern()
val isAsciiCapable = xml.getAttributeBooleanValue(namespace, "isAsciiCapable", false) val isAsciiCapable = xml.getAttributeBooleanValue(namespace, "isAsciiCapable", false)
@ -195,12 +213,14 @@ private fun loadResourceSubtypes(resources: Resources) {
b.setSubtypeNameResId(label) b.setSubtypeNameResId(label)
if (subtypeId != 0) if (subtypeId != 0)
b.setSubtypeId(subtypeId) b.setSubtypeId(subtypeId)
b.setSubtypeLocale(locale) b.setSubtypeLocale(localeString)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && languageTag != null) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && languageTag != null)
b.setLanguageTag(languageTag) b.setLanguageTag(languageTag)
b.setSubtypeMode(imeSubtypeMode) b.setSubtypeMode(imeSubtypeMode)
b.setSubtypeExtraValue(imeSubtypeExtraValue) b.setSubtypeExtraValue(imeSubtypeExtraValue)
b.setIsAsciiCapable(isAsciiCapable) b.setIsAsciiCapable(isAsciiCapable)
val locale = if (languageTag.isEmpty()) localeString.constructLocale()
else languageTag.constructLocale()
resourceSubtypesByLocale.getOrPut(locale) { ArrayList(2) }.add(b.build()) resourceSubtypesByLocale.getOrPut(locale) { ArrayList(2) }.add(b.build())
} }
eventType = xml.next() eventType = xml.next()
@ -223,7 +243,7 @@ private fun removeInvalidCustomSubtypes(context: Context) {
additionalSubtypes.forEach { additionalSubtypes.forEach {
val name = it.substringAfter(":").substringBefore(":") val name = it.substringAfter(":").substringBefore(":")
if (!name.startsWith(CUSTOM_LAYOUT_PREFIX)) return@forEach if (!name.startsWith(CUSTOM_LAYOUT_PREFIX)) return@forEach
if (name !in customSubtypeFiles) if (customSubtypeFiles?.contains(name) != true)
subtypesToRemove.add(it) subtypesToRemove.add(it)
} }
if (subtypesToRemove.isEmpty()) return if (subtypesToRemove.isEmpty()) return
@ -235,30 +255,29 @@ private fun removeInvalidCustomSubtypes(context: Context) {
private fun loadEnabledSubtypes(context: Context) { private fun loadEnabledSubtypes(context: Context) {
val prefs = DeviceProtectedUtils.getSharedPreferences(context) val prefs = DeviceProtectedUtils.getSharedPreferences(context)
val subtypeStrings = prefs.getString(Settings.PREF_ENABLED_INPUT_STYLES, "")!! 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) { for (localeAndLayout in subtypeStrings) {
require(localeAndLayout.size == 2) val subtypesForLocale = resourceSubtypesByLocale[localeAndLayout.first]
val subtypesForLocale = resourceSubtypesByLocale[localeAndLayout.first()]
if (subtypesForLocale == null) { if (subtypesForLocale == null) {
val message = "no resource subtype for $localeAndLayout" val message = "no resource subtype for $localeAndLayout"
Log.w(TAG, message) Log.w(TAG, message)
if (DebugFlags.DEBUG_ENABLED) if (DebugFlags.DEBUG_ENABLED)
Toast.makeText(context, message, Toast.LENGTH_LONG).show() Toast.makeText(context, message, Toast.LENGTH_LONG).show()
else // don't remove in debug mode else // don't remove in debug mode
removeEnabledSubtype(prefs, localeAndLayout.joinToString(LOCALE_LAYOUT_SEPARATOR)) removeEnabledSubtype(prefs, localeAndLayout.prefString())
continue continue
} }
val subtype = subtypesForLocale.firstOrNull { SubtypeLocaleUtils.getKeyboardLayoutSetName(it) == localeAndLayout.last() } val subtype = subtypesForLocale.firstOrNull { SubtypeLocaleUtils.getKeyboardLayoutSetName(it) == localeAndLayout.second }
?: additionalSubtypes.firstOrNull { it.locale() == localeAndLayout.first() && SubtypeLocaleUtils.getKeyboardLayoutSetName(it) == localeAndLayout.last() } ?: additionalSubtypes.firstOrNull { it.locale() == localeAndLayout.first && SubtypeLocaleUtils.getKeyboardLayoutSetName(it) == localeAndLayout.second }
if (subtype == null) { if (subtype == null) {
val message = "subtype $localeAndLayout could not be loaded" val message = "subtype $localeAndLayout could not be loaded"
Log.w(TAG, message) Log.w(TAG, message)
if (DebugFlags.DEBUG_ENABLED) if (DebugFlags.DEBUG_ENABLED)
Toast.makeText(context, message, Toast.LENGTH_LONG).show() Toast.makeText(context, message, Toast.LENGTH_LONG).show()
else // don't remove in debug mode else // don't remove in debug mode
removeEnabledSubtype(prefs, localeAndLayout.joinToString(LOCALE_LAYOUT_SEPARATOR)) removeEnabledSubtype(prefs, localeAndLayout.prefString())
continue continue
} }
@ -287,7 +306,7 @@ private fun removeEnabledSubtype(prefs: SharedPreferences, subtypeString: String
var initialized = false var initialized = false
private set private set
private val enabledSubtypes = mutableListOf<InputMethodSubtype>() 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 additionalSubtypes = mutableListOf<InputMethodSubtype>()
private val systemLocales = mutableListOf<Locale>() private val systemLocales = mutableListOf<Locale>()
private val systemSubtypes = mutableListOf<InputMethodSubtype>() private val systemSubtypes = mutableListOf<InputMethodSubtype>()
@ -295,11 +314,3 @@ private val systemSubtypes = mutableListOf<InputMethodSubtype>()
private const val SUBTYPE_SEPARATOR = ";" private const val SUBTYPE_SEPARATOR = ";"
private const val LOCALE_LAYOUT_SEPARATOR = ":" private const val LOCALE_LAYOUT_SEPARATOR = ":"
private const val TAG = "SubtypeSettings" 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_GB" msgid="88170601942311355">"Engels (VK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Engels (VS)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"Engels (VS)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Spaans (VS)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"Spaans (VS)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serwies (Latyns)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"እንግሊዘኛ (የታላቋ ብሪታንያ)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"እንግሊዘኛ (ዩ.ኤስ)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"እንግሊዘኛ (ዩ.ኤስ)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"ስፓኒሽኛ (ዩኤስ)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"ስፓኒሽኛ (ዩኤስ)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"ሂንግሊሽ"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"ሂንግሊሽ"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"ሰርብያኛ (ላቲን)"</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_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_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_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_hi_Latn" 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_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_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_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (እስግ)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"ምንም ቋንቋ (ፊደላት)"</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_GB" msgid="88170601942311355">"الإنجليزية (المملكة المتحدة)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"الإنجليزية (الولايات المتحدة)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"الإنجليزية (الولايات المتحدة)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"الإسبانية (الأميركية)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"الإسبانية (الأميركية)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"هنجليزية"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"هنجليزية"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"الصربية (اللاتينية)"</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_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_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_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_hi_Latn" 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_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_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_generic_compact" msgid="3353673321203202922"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (مضغوط)</string>
<string name="subtype_no_language" msgid="7137390094240139495">"بدون لغة (أبجدية)"</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_GB" msgid="88170601942311355">"İngilis (BK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"İngilis (ABŞ)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"İngilis (ABŞ)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"İspan (ABŞ)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"İspan (ABŞ)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hingilis"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hingilis"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serb (Latın)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"engleski (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"engleski (SAD)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"engleski (SAD)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"španski (SAD)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"španski (SAD)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"hengleski"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"hengleski"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"srpski (latinica)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"Англійская (Вялікабрытанія)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Англійская (ЗША)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"Англійская (ЗША)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Іспанская (ЗША)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"Іспанская (ЗША)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Хінгліш"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Хінгліш"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Сербская (Лацініца)"</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_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_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_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_hi_Latn" 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_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_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_generic_compact" msgid="3353673321203202922"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (Кампактная)</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Стандартная (Лацініца)"</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_GB" msgid="88170601942311355">"английски (Великобритания)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"английски (САЩ)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"английски (САЩ)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"испански (САЩ)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"испански (САЩ)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Хинглиш"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Хинглиш"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Сръбска (латиница)"</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_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_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_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_hi_Latn" 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_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_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_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (компактна)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Без език (латиница)"</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_GB">ইংরেজি (ইউকে)</string>
<string name="subtype_en_US">ইংরেজি (ইউএস)</string> <string name="subtype_en_US">ইংরেজি (ইউএস)</string>
<string name="subtype_es_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_title">প্রচ্ছন্ন বৈশিষ্ট্য</string>
<string name="hidden_features_summary">অপরিদৃষ্ট হতে পারে এমন বৈশিষ্ট্যের বর্ণনা</string> <string name="hidden_features_summary">অপরিদৃষ্ট হতে পারে এমন বৈশিষ্ট্যের বর্ণনা</string>
<string name="hidden_features_text">ডিভাইস সুরক্ষিত স্টোরেজ</string> <string name="hidden_features_text">ডিভাইস সুরক্ষিত স্টোরেজ</string>
@ -126,13 +126,12 @@
&#9658; রুট উপলব্ধতাসহ ম্যানুয়াল ব্যাকআপ করা ব্যবহারকারীদের জন্য: Android 7 থেকে শেয়ারড্ প্রেফারেন্স ফাইল ডিফল্ট জায়গা নয়। কারণ অ্যাপ %s ব্যবহার করছে। &lt;br&gt; &#9658; রুট উপলব্ধতাসহ ম্যানুয়াল ব্যাকআপ করা ব্যবহারকারীদের জন্য: Android 7 থেকে শেয়ারড্ প্রেফারেন্স ফাইল ডিফল্ট জায়গা নয়। কারণ অ্যাপ %s ব্যবহার করছে। &lt;br&gt;
ডিভাইস আনলকের আগে সেটিংস খোলার জন্য এটি প্রয়োজনীয়, যেমন: বুট করার সময়। &lt;br&gt; ডিভাইস আনলকের আগে সেটিংস খোলার জন্য এটি প্রয়োজনীয়, যেমন: বুট করার সময়। &lt;br&gt;
ফাইলটি /data/user_de/0/package_id/shared_prefs/ থাকে। যদিও এটা ডিভাইস এবং অ্যান্ড্রয়েড সংস্করণের উপরে নির্ভর করে।</string> ফাইলটি /data/user_de/0/package_id/shared_prefs/ থাকে। যদিও এটা ডিভাইস এবং অ্যান্ড্রয়েড সংস্করণের উপরে নির্ভর করে।</string>
<string name="subtype_hu_ZZ">হাঙ্গেরীয় (QWERTY)</string> <string name="subtype_sr_Latn">সার্বীয় (ল্যাটিন)</string>
<string name="subtype_sr_ZZ">সার্বীয় (ল্যাটিন)</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_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_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_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_hi_Latn">হিংলিশ (<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_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_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_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> <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_GB" msgid="88170601942311355">"engleski (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"engleski (SAD)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"engleski (SAD)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"španski (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_hi_Latn" msgid="8860448146262798623">"Hindu engleski"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Srpski (latinica)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"anglès (Regne Unit)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"anglès (EUA)"</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_es_US" msgid="5583145191430180200">"Espanyol (EUA)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">Hinglès</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">Hinglès</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbi (llatí)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"Angličtina (Velká Británie)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Angličtina (USA)"</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_es_US" msgid="5583145191430180200">"Španělština (USA)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Srbština (Latinka)"</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_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_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_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_hi_Latn" 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_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_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_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Kompaktní)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Standardní (Latinka)"</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_GB" msgid="88170601942311355">"engelsk (Storbritannien)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"engelsk (USA)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"engelsk (USA)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Spansk (USA)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"Spansk (USA)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbisk (latinsk)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"Englisch (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Englisch (USA)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"Englisch (USA)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Spanisch (USA)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"Spanisch (USA)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">Hinglisch</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">Hinglisch</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbisch (Lateinisch)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"Αγγλικά (ΗΒ)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Αγγλικά (ΗΠΑ)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"Αγγλικά (ΗΠΑ)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Ισπανικά (ΗΠΑ)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"Ισπανικά (ΗΠΑ)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Σερβικά (Λατινικά)"</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_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_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_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_hi_Latn" 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_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_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_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (Συμπαγές)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Καμία γλώσσα (Αλφάβητο)"</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_GB" msgid="88170601942311355">"English (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"English (US)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"English (US)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Spanish (US)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"Spanish (US)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbian (Latin)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"English (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"English (US)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"English (US)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Spanish (US)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"Spanish (US)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbian (Latin)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"English (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"English (US)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"English (US)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Spanish (US)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"Spanish (US)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbian (Latin)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"English (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"English (US)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"English (US)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Spanish (US)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"Spanish (US)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbian (Latin)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"English (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"English (US)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"English (US)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Spanish (US)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"Spanish (US)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbian (Latin)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"Inglés (Reino Unido)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Inglés (EE.UU.)"</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_es_US" msgid="5583145191430180200">"Español (EE.UU.)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbio (latino)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">Inglés (Reino Unido)</string>
<string name="subtype_en_US" msgid="6160452336634534239">Inglés (EE.UU.)</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_es_US" msgid="5583145191430180200">"Español (EE.UU.)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">inglés indio</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">inglés indio</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbio (latino)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"Inglise (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Inglise (USA)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"Inglise (USA)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"hispaania (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_hi_Latn" msgid="8860448146262798623">"Hindi-inglise"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbia (ladina)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"Ingelesa (Erresuma Batua)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Ingelesa (AEB)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"Ingelesa (AEB)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Gaztelania (AEB)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"Gaztelania (AEB)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglisha"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglisha"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbiarra (latindarra)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"انگلیسی (بریتانیا)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"انگلیسی (امریکا)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"انگلیسی (امریکا)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"اسپانیایی (امریکا)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"اسپانیایی (امریکا)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"هندی انگلیسی"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"هندی انگلیسی"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"صربی (لاتین)"</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_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_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_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_hi_Latn" 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_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_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_generic_compact" msgid="3353673321203202922"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (فشرده)</string>
<string name="subtype_no_language" msgid="7137390094240139495">"بدون زبان (حروف الفبا)"</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_GB" msgid="88170601942311355">"englanti (Iso-Britannia)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"englanti (Yhdysvallat)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"englanti (Yhdysvallat)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"espanja (Yhdysvallat)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"espanja (Yhdysvallat)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hindienglanti"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hindienglanti"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"serbialainen (latinal.)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"Anglais (britannique)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Anglais (États-Unis)"</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_es_US" msgid="5583145191430180200">"Espagnol (États-Unis)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbe (latin)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"Anglais (Royaume-Uni)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Anglais (États-Unis)"</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_es_US" msgid="5583145191430180200">"Espagnol (États-Unis)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hindi/Anglais"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hindi/Anglais"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbe (latin)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"Inglés (Reino Unido)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Inglés (EUA)"</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_es_US" msgid="5583145191430180200">"Español (EUA)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbio (alfabeto latino)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"અંગ્રેજી (યુકે)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"અંગ્રેજી (યુ એસ)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"અંગ્રેજી (યુ એસ)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"સ્પેનિશ (US)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"સ્પેનિશ (US)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"હિંગ્લિશ"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"હિંગ્લિશ"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"સર્બિયન (લેટિન)"</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_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_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_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_hi_Latn" 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_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_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_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (કોમ્પેક્ટ)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"ભાષા નથી (આલ્ફાબેટ)"</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_GB" msgid="88170601942311355">"अंग्रेज़ी (यूके)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"अंग्रेज़ी (यूएस)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"अंग्रेज़ी (यूएस)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"स्पेनिश (यूएस)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"स्पेनिश (यूएस)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"हिंग्लिश"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"हिंग्लिश"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"सर्बियाई (लैटिन)"</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_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_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_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_hi_Latn" 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_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_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_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (संक्षिप्त)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"भाषा उपलब्ध नहीं है (लैटिन वर्णाक्षर)"</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_GB" msgid="88170601942311355">"Engleski (UK)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Engleski (SAD)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"Engleski (SAD)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">Španjolski (SAD)</string> <string name="subtype_es_US" msgid="5583145191430180200">Španjolski (SAD)</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Srpski (latinica)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"angol (brit)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"angol (amerikai)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"angol (amerikai)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"spanyol (USA)"</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_hi_Latn" msgid="8860448146262798623">"Hinglish (hindi-angol)"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Szerb (latin)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"angol (brit)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"angol (amerikai)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"angol (amerikai)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"spanyol (USA)"</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_hi_Latn" msgid="8860448146262798623">"Hinglish (hindi-angol)"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Szerb (latin)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"Անգլերեն (ՄԹ)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Անգլերեն (ԱՄՆ)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"Անգլերեն (ԱՄՆ)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Իսպաներեն (ԱՄՆ)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"Իսպաներեն (ԱՄՆ)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Հինգլիշ"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Հինգլիշ"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Սերբերեն (Լատինական)"</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_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_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_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_hi_Latn" 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_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_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_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (սեղմ)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Ոչ մի լեզվով (Այբուբեն)"</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_GB" msgid="88170601942311355">"Inggris (Inggris)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Inggris (AS)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"Inggris (AS)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Spanyol (AS)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"Spanyol (AS)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbia (Latin)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"Enskt (Bretland)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"Enskt (Bandaríkin)"</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_es_US" msgid="5583145191430180200">"Spænskt (US)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Serbneskt (latneskt)"</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">Inglese (Regno Unito)</string>
<string name="subtype_en_US" msgid="6160452336634534239">Inglese (Stati Uniti)</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_es_US" msgid="5583145191430180200">Spagnolo (Stati Uniti)</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">Serbo (Latino)</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_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_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_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_hi_Latn" 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_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_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_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> <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_GB" msgid="88170601942311355">"אנגלית (בריטניה)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"אנגלית (ארה\"ב)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"אנגלית (ארה\"ב)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"ספרדית (ארצות הברית)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"ספרדית (ארצות הברית)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"אנגלית הודית"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"אנגלית הודית"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"סרבית (באותיות לטיניות)"</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_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_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_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_hi_Latn" 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_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_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_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (קומפקטית)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"ללא שפה (אלף-בית)"</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_GB" msgid="88170601942311355">"英語 (英国)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"英語 (米国)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"英語 (米国)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"スペイン語 (米国)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"スペイン語 (米国)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"ヒングリッシュ"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"ヒングリッシュ"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"セルビア語(ラテン文字)"</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_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_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_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_hi_Latn" 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_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_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_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g>(コンパクト)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"言語なし(アルファベット)"</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_GB" msgid="88170601942311355">"ინგლისური (გართ. სამ.)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"ინგლისური (აშშ)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"ინგლისური (აშშ)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"ესპანური (აშშ)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"ესპანური (აშშ)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"ჰინგლისური"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"ჰინგლისური"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"სერბული (ლათინური)"</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_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_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_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_hi_Latn" 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_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_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_generic_compact" msgid="3353673321203202922"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (კომპაქტური)</string>
<string name="subtype_no_language" msgid="7137390094240139495">"ენის გარეშე (ლათინური ანბანი)"</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_GB" msgid="88170601942311355">"ағылшын (ҰБ)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"ағылшын (АҚШ)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"ағылшын (АҚШ)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"Испан (АҚШ)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"Испан (АҚШ)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Хинглиш"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Хинглиш"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"Серб (латын жазуы)"</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_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_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_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_hi_Latn" 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_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_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_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (шағын)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"Тіл жоқ (әліпби)"</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_GB" msgid="88170601942311355">"អង់គ្លេស (​អង់គ្លេស)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"អង់គ្លេស (សហរដ្ឋ​អាមេរិក)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"អង់គ្លេស (សហរដ្ឋ​អាមេរិក)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"អេស្ប៉ាញ (សហរដ្ឋ​អាមេរិក​)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"អេស្ប៉ាញ (សហរដ្ឋ​អាមេរិក​)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"Hinglish"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"Hinglish"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"សើប (ឡាតាំង​)"</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_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_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_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_hi_Latn" 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_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_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_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (បង្រួម)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"គ្មាន​ភាសា (អក្សរ​ក្រម)"</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_GB" msgid="88170601942311355">"ಇಂಗ್ಲಿಷ್ (ಯುಕೆ)"</string>
<string name="subtype_en_US" msgid="6160452336634534239">"ಇಂಗ್ಲಿಷ್ (US)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"ಇಂಗ್ಲಿಷ್ (US)"</string>
<string name="subtype_es_US" msgid="5583145191430180200">"ಸ್ಪ್ಯಾನಿಷ್ (US)"</string> <string name="subtype_es_US" msgid="5583145191430180200">"ಸ್ಪ್ಯಾನಿಷ್ (US)"</string>
<string name="subtype_hi_ZZ" msgid="8860448146262798623">"ಹಿಂಗ್ಲಿಷ್"</string> <string name="subtype_hi_Latn" msgid="8860448146262798623">"ಹಿಂಗ್ಲಿಷ್"</string>
<string name="subtype_sr_ZZ" msgid="9059219552986034343">"ಸರ್ಬಿಯನ್ (ಲ್ಯಾಟಿನ್)"</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_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_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_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_hi_Latn" 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_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_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_generic_compact" msgid="3353673321203202922">"<xliff:g id="LANGUAGE_NAME">%s</xliff:g> (ಕಾಂಪ್ಯಾಕ್ಟ್‌‌)"</string>
<string name="subtype_no_language" msgid="7137390094240139495">"ಯಾವುದೇ ಭಾಷೆಯಿಲ್ಲ (ವರ್ಣಮಾಲೆ)"</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