diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 6423c5c9f3..e9d5f7e785 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -77,6 +77,13 @@
Use the previous word in making suggestions
+
+ Auto-switch language
+
+ Automatically switch to a matching language if the text field requests it (e.g. Translate)
+
+ Advanced options
+
Languages
diff --git a/java/src/org/futo/inputmethod/latin/InputAttributes.java b/java/src/org/futo/inputmethod/latin/InputAttributes.java
index 198ca7b13f..901e16b490 100644
--- a/java/src/org/futo/inputmethod/latin/InputAttributes.java
+++ b/java/src/org/futo/inputmethod/latin/InputAttributes.java
@@ -20,6 +20,7 @@
import static org.futo.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE;
import static org.futo.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE_COMPAT;
+import android.os.LocaleList;
import android.text.InputType;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
@@ -56,6 +57,8 @@ public final class InputAttributes {
final public Locale mLocaleOverride;
@Nullable
final public String mLayoutOverride;
+ @Nullable
+ final public LocaleList mHintLocales;
/**
* Whether the floating gesture preview should be disabled. If true, this should override the
@@ -111,6 +114,7 @@ public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMo
mIsWebField = false;
mLocaleOverride = null;
mLayoutOverride = null;
+ mHintLocales = null;
return;
}
// inputClass == InputType.TYPE_CLASS_TEXT
@@ -191,6 +195,7 @@ public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMo
}
mLayoutOverride = privateImeOptions.get("org.futo.inputmethod.latin.ForceLayout");
+ mHintLocales = editorInfo.hintLocales;
}
public boolean isTypeNull() {
diff --git a/java/src/org/futo/inputmethod/latin/LatinIME.kt b/java/src/org/futo/inputmethod/latin/LatinIME.kt
index 876a415de5..4402fb208a 100644
--- a/java/src/org/futo/inputmethod/latin/LatinIME.kt
+++ b/java/src/org/futo/inputmethod/latin/LatinIME.kt
@@ -306,6 +306,50 @@ class LatinIME : InputMethodServiceCompose(), LatinIMELegacy.SuggestionStripCont
}
}
+ private fun updateKeyboardLanguage() {
+ val settings = latinIMELegacy.mSettings.current
+ if (!settings.mAutoSwitchLanguage) return
+
+ val hintLocales = settings.mInputAttributes.mHintLocales ?: return
+ if (hintLocales.isEmpty) return
+
+ // We need to read these right from the settings. LatinIME just seems to return a generic "keyboard" subtype
+ // with no language info when you get subtypes from InputMethodManager
+ val enabledSubtypes = getSettingBlocking(SubtypesSetting).map(Subtypes::convertToSubtype)
+ if (enabledSubtypes.isEmpty()) return
+
+ val currentSubtype = RichInputMethodManager.getInstance().currentSubtype.rawSubtype
+ val currentSubtypeString = Subtypes.subtypeToString(currentSubtype)
+ val currentLanguage = Subtypes.getLocale(currentSubtype).language
+ val hintedLocales = (0 until hintLocales.size()).map(hintLocales::get)
+
+ if (hintedLocales.any { it.language == currentLanguage }) {
+ // Some apps update hintLocales faster than keyboard view state settles.
+ // If the current language is already valid for hints, keep it but force one UI resync.
+ // Example is changing between JP/EN in translate, going from 12 key back to EN would not work correctly.
+ if (lastAutoSwitchResyncSubtype != currentSubtypeString) {
+ latinIMELegacy.onCurrentInputMethodSubtypeChanged(currentSubtype)
+ lastAutoSwitchResyncSubtype = currentSubtypeString
+ }
+ return
+ }
+
+ // checking subtypes by tag and fallback to language if none found
+ val targetSubtype = hintedLocales.firstNotNullOfOrNull { hintLocale ->
+ enabledSubtypes.find {
+ Subtypes.getLocale(it).toLanguageTag().equals(hintLocale.toLanguageTag(), true)
+ } ?: enabledSubtypes.find {
+ val subtypeLocale = Subtypes.getLocale(it)
+ subtypeLocale.language.isNotEmpty() && subtypeLocale.language == hintLocale.language
+ }
+ } ?: return
+
+ if (Subtypes.subtypeToString(targetSubtype) == currentSubtypeString) return
+
+ lastAutoSwitchResyncSubtype = ""
+ latinIMELegacy.onCurrentInputMethodSubtypeChanged(targetSubtype)
+ }
+
fun onSizeUpdated() {
val newSize = calculateSize() ?: return
val shouldInvalidateKeyboard = size.value?.let { oldSize ->
@@ -350,6 +394,7 @@ class LatinIME : InputMethodServiceCompose(), LatinIMELegacy.SuggestionStripCont
}
private var currentSubtype = ""
+ private var lastAutoSwitchResyncSubtype = ""
val jobs = mutableListOf()
private fun launchJob(task: suspend CoroutineScope.() -> Unit) {
@@ -592,6 +637,7 @@ class LatinIME : InputMethodServiceCompose(), LatinIMELegacy.SuggestionStripCont
latinIMELegacy.onStartInputView(info, restarting)
lifecycleScope.launch { uixManager.showUpdateNoticeIfNeeded() }
updateColorsIfDynamicChanged()
+ updateKeyboardLanguage()
}
override fun onFinishInputView(finishingInput: Boolean) {
diff --git a/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java b/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java
index 49bd237883..8c96bc60d1 100644
--- a/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java
+++ b/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java
@@ -343,7 +343,9 @@ public void onCurrentInputMethodSubtypeChanged(final InputMethodSubtype subtype)
}
void onStartInputInternal(final EditorInfo editorInfo, final boolean restarting) {
-
+ // Refresh InputAttributes even when restarting in the same field. Some apps
+ // (e.g Translate) update hintLocales but not inputType, leading to out of date hintLocales
+ loadSettings();
}
public void updateMainKeyboardViewSettings() {
diff --git a/java/src/org/futo/inputmethod/latin/settings/Settings.java b/java/src/org/futo/inputmethod/latin/settings/Settings.java
index 61ac200881..b5fc8587e7 100644
--- a/java/src/org/futo/inputmethod/latin/settings/Settings.java
+++ b/java/src/org/futo/inputmethod/latin/settings/Settings.java
@@ -145,6 +145,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final int DEFAULT_ALT_SPACES_MODE = SPACES_MODE_ALL;
+ public static final String PREF_AUTO_SWITCH_LANGUAGE = "pref_auto_switch_language";
+
// Emoji
public static final String PREF_EMOJI_RECENT_KEYS = "emoji_recent_keys";
public static final String PREF_EMOJI_CATEGORY_LAST_TYPED_ID = "emoji_category_last_typed_id";
diff --git a/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java b/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java
index 8c25837adb..54bd81e816 100644
--- a/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java
@@ -106,7 +106,8 @@ public class SettingsValues {
public final int mBackspaceMode;
public final int mNumberRowMode;
public final int mAltSpacesMode;
-
+ public final boolean mAutoSwitchLanguage;
+
// From the input box
@Nonnull
public final InputAttributes mInputAttributes;
@@ -201,9 +202,10 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina
prefs.getInt(Settings.PREF_NUMBER_ROW_MODE, Settings.NUMBER_ROW_MODE_DEFAULT)
: Settings.NUMBER_ROW_MODE_DEFAULT;
mAltSpacesMode = prefs.getInt(Settings.PREF_ALT_SPACES_MODE, Settings.DEFAULT_ALT_SPACES_MODE);
+ mAutoSwitchLanguage = prefs.getBoolean(Settings.PREF_AUTO_SWITCH_LANGUAGE, false);
mShouldShowLxxSuggestionUi = Settings.SHOULD_SHOW_LXX_SUGGESTION_UI
- && prefs.getBoolean(DebugSettings.PREF_SHOULD_SHOW_LXX_SUGGESTION_UI, true);
+ && prefs.getBoolean(DebugSettings.PREF_SHOULD_SHOW_LXX_SUGGESTION_UI, true);
// Compute other readable settings
mKeyLongpressTimeout = Settings.readKeyLongpressTimeout(prefs, res);
mKeypressVibrationDuration = Settings.readKeypressVibrationDuration(prefs, res);
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/pages/Languages.kt b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Languages.kt
index e8f1b87836..41563a235e 100644
--- a/java/src/org/futo/inputmethod/latin/uix/settings/pages/Languages.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Languages.kt
@@ -52,6 +52,7 @@ import org.futo.inputmethod.latin.MultilingualBucketSetting
import org.futo.inputmethod.latin.R
import org.futo.inputmethod.latin.Subtypes
import org.futo.inputmethod.latin.SubtypesSetting
+import org.futo.inputmethod.latin.settings.Settings
import org.futo.inputmethod.latin.uix.FileKind
import org.futo.inputmethod.latin.uix.ResourceHelper
import org.futo.inputmethod.latin.uix.getSetting
@@ -66,6 +67,7 @@ import org.futo.inputmethod.latin.uix.settings.pages.modelmanager.openModelImpor
import org.futo.inputmethod.latin.uix.settings.useDataStore
import org.futo.inputmethod.latin.uix.settings.useDataStoreValue
import org.futo.inputmethod.latin.uix.settings.userSettingNavigationItem
+import org.futo.inputmethod.latin.uix.settings.userSettingToggleSharedPrefs
import org.futo.inputmethod.latin.uix.theme.Typography
import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper
import org.futo.inputmethod.latin.uix.theme.presets.DynamicDarkTheme
@@ -507,6 +509,16 @@ val LanguageSettingsTop = listOf(
navigateTo = "addLanguage",
)
)
+
+val LanguageSettingsToggles = listOf(
+ userSettingToggleSharedPrefs(
+ title = R.string.auto_switch_language_title,
+ subtitle = R.string.auto_switch_language_subtitle,
+ key = Settings.PREF_AUTO_SWITCH_LANGUAGE,
+ default = { false }
+ )
+)
+
val LanguageSettingsBottom = listOf(
userSettingNavigationItem(
title = R.string.language_settings_import_resource_from_file,
@@ -750,5 +762,13 @@ fun LanguagesScreen(navController: NavHostController = rememberNavController())
items(LanguageSettingsBottom) {
it.component()
}
+
+ item {
+ ScreenTitle(stringResource(R.string.language_settings_advanced_options))
+ }
+
+ items(LanguageSettingsToggles) {
+ it.component()
+ }
}
}
\ No newline at end of file
diff --git a/tests/src/org/futo/inputmethod/latin/InputAttributesTests.java b/tests/src/org/futo/inputmethod/latin/InputAttributesTests.java
new file mode 100644
index 0000000000..1d5d6499c7
--- /dev/null
+++ b/tests/src/org/futo/inputmethod/latin/InputAttributesTests.java
@@ -0,0 +1,29 @@
+package org.futo.inputmethod.latin;
+
+import android.os.LocaleList;
+import android.view.inputmethod.EditorInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.Locale;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class InputAttributesTests {
+
+ @Test
+ public void testHintLocales() {
+ final EditorInfo editorInfo = mock(EditorInfo.class);
+ final LocaleList localeList = new LocaleList(new Locale("en", "US"), new Locale("ja", "JP"));
+ when(editorInfo.getHintLocales()).thenReturn(localeList);
+
+ final InputAttributes inputAttributes = new InputAttributes(editorInfo, false, "");
+
+ assertEquals(localeList, inputAttributes.mHintLocales);
+ }
+}
\ No newline at end of file