From 3dad0577d20ad2106ef7e42c2fdb2b711276097b Mon Sep 17 00:00:00 2001 From: kikkia <10608318+kikkia@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:42:58 +0900 Subject: [PATCH] feat: add language hint support. When enabled, the keyboard checks the languages the input box provides as expected and if it finds one, swaps to that input language. Ideally helpful for apps like google translate or similar. --- java/res/values/strings.xml | 7 +++ .../inputmethod/latin/InputAttributes.java | 5 ++ .../org/futo/inputmethod/latin/LatinIME.kt | 46 +++++++++++++++++++ .../inputmethod/latin/LatinIMELegacy.java | 4 +- .../inputmethod/latin/settings/Settings.java | 2 + .../latin/settings/SettingsValues.java | 6 ++- .../latin/uix/settings/pages/Languages.kt | 20 ++++++++ .../latin/InputAttributesTests.java | 29 ++++++++++++ 8 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 tests/src/org/futo/inputmethod/latin/InputAttributesTests.java 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