diff --git a/java/res/values/config-auto-correction-thresholds.xml b/java/res/values/config-auto-correction-thresholds.xml
index 7d94a42a401cd2ec6e0061a57d0f8f6b6fc8c329..fc701c7ff6b3b8e13ec4478ef9934ab0eb1bb7d3 100644
--- a/java/res/values/config-auto-correction-thresholds.xml
+++ b/java/res/values/config-auto-correction-thresholds.xml
@@ -34,6 +34,12 @@
         <item>floatNegativeInfinity</item>
     </string-array>
 
+    <!-- Chosen to be slightly less than the "aggressive" threshold. This is the threshold for
+         a mildly plausible suggestion given the input; if no "plausible" suggestion is present
+         for a language, it's a strong indicator the user is not typing in this language, so we
+         may be more forgiving of whitelist entries in another language. -->
+    <string name="plausibility_threshold" translatable="false">0.065</string>
+
     <!-- The index of the auto correction threshold values array. -->
     <string name="auto_correction_threshold_mode_index_off" translatable="false">0</string>
     <string name="auto_correction_threshold_mode_index_modest" translatable="false">1</string>
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 7b7b6d35e0f1d6608597acbe0409e43e9cc5059b..66746cb6ada29123469968221043259bfbc4b5c4 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -708,6 +708,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
             mInputLogic.mSuggest.setAutoCorrectionThreshold(
                     settingsValues.mAutoCorrectionThreshold);
         }
+        mInputLogic.mSuggest.setPlausibilityThreshold(settingsValues.mPlausibilityThreshold);
     }
 
     /**
@@ -1007,6 +1008,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
                 suggest.setAutoCorrectionThreshold(
                         currentSettingsValues.mAutoCorrectionThreshold);
             }
+            suggest.setPlausibilityThreshold(currentSettingsValues.mPlausibilityThreshold);
 
             switcher.loadKeyboard(editorInfo, currentSettingsValues, getCurrentAutoCapsState(),
                     getCurrentRecapitalizeState());
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 93a21043c6b74bab24755e7199f95eb6e63f61c4..0bf0f687aa3a273a5ab8deede2c23a8e864ce7b8 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -65,15 +65,30 @@ public final class Suggest {
     }
 
     private float mAutoCorrectionThreshold;
+    private float mPlausibilityThreshold;
 
     public Suggest(final DictionaryFacilitator dictionaryFacilitator) {
         mDictionaryFacilitator = dictionaryFacilitator;
     }
 
+    /**
+     * Set the normalized-score threshold for a suggestion to be considered strong enough that we
+     * will auto-correct to this.
+     * @param threshold the threshold
+     */
     public void setAutoCorrectionThreshold(final float threshold) {
         mAutoCorrectionThreshold = threshold;
     }
 
+    /**
+     * Set the normalized-score threshold for what we consider a "plausible" suggestion, in
+     * the same dimension as the auto-correction threshold.
+     * @param threshold the threshold
+     */
+    public void setPlausibilityThreshold(final float threshold) {
+        mPlausibilityThreshold = threshold;
+    }
+
     public interface OnGetSuggestedWordsCallback {
         public void onGetSuggestedWords(final SuggestedWords suggestedWords);
     }
@@ -130,6 +145,18 @@ public final class Suggest {
         return firstSuggestedWordInfo;
     }
 
+    // Quality constants for dictionary match
+    // In increasing order of quality
+    // This source dictionary does not match the typed word.
+    private static final int QUALITY_NO_MATCH = 0;
+    // This source dictionary has a null locale, and the preferred locale is also null.
+    private static final int QUALITY_MATCH_NULL = 1;
+    // This source dictionary has a non-null locale different from the preferred locale. The
+    // preferred locale may be null : this is still better than MATCH_NULL.
+    private static final int QUALITY_MATCH_OTHER_LOCALE = 2;
+    // This source dictionary matches the preferred locale.
+    private static final int QUALITY_MATCH_PREFERRED_LOCALE = 3;
+
     // Retrieves suggestions for non-batch input (typing, recorrection, predictions...)
     // and calls the callback function with the suggestions.
     private void getSuggestedWordsForNonBatchInput(final WordComposer wordComposer,
@@ -154,20 +181,52 @@ public final class Suggest {
                         // For transforming suggestions that don't come for any dictionary, we
                         // use the currently most probable locale as it's our best bet.
                         mostProbableLocale);
-        @Nullable final Dictionary sourceDictionaryOfRemovedWord =
-                SuggestedWordInfo.removeDupsAndReturnSourceOfTypedWord(wordComposer.getTypedWord(),
-                        mostProbableLocale /* preferredLocale */, suggestionsContainer);
+
+        boolean typedWordExistsInAnotherLanguage = false;
+        int qualityOfFoundSourceDictionary = QUALITY_NO_MATCH;
+        @Nullable Dictionary sourceDictionaryOfRemovedWord = null;
+        for (final SuggestedWordInfo info : suggestionsContainer) {
+            // Search for the best dictionary, defined as the first one with the highest match
+            // quality we can find.
+            if (typedWordString.equals(info.mWord)) {
+                if (mostProbableLocale.equals(info.mSourceDict.mLocale)) {
+                    if (qualityOfFoundSourceDictionary < QUALITY_MATCH_PREFERRED_LOCALE) {
+                        // Use this source if the old match had lower quality than this match
+                        sourceDictionaryOfRemovedWord = info.mSourceDict;
+                        qualityOfFoundSourceDictionary = QUALITY_MATCH_PREFERRED_LOCALE;
+                    }
+                } else {
+                    final int matchQuality = (null == info.mSourceDict.mLocale)
+                            ? QUALITY_MATCH_NULL : QUALITY_MATCH_OTHER_LOCALE;
+                    if (qualityOfFoundSourceDictionary < matchQuality) {
+                        // Use this source if the old match had lower quality than this match
+                        sourceDictionaryOfRemovedWord = info.mSourceDict;
+                        qualityOfFoundSourceDictionary = matchQuality;
+                    }
+                    typedWordExistsInAnotherLanguage = true;
+                }
+            }
+        }
+
+        SuggestedWordInfo.removeDups(typedWordString, suggestionsContainer);
 
         final SuggestedWordInfo whitelistedWordInfo =
                 getWhitelistedWordInfoOrNull(suggestionsContainer);
         final String whitelistedWord;
         if (null != whitelistedWordInfo &&
-                mDictionaryFacilitator.isConfidentAboutCurrentLanguageBeing(
-                        whitelistedWordInfo.mSourceDict.mLocale)) {
+                (mDictionaryFacilitator.isConfidentAboutCurrentLanguageBeing(
+                        whitelistedWordInfo.mSourceDict.mLocale)
+                || (!typedWordExistsInAnotherLanguage
+                        && !hasPlausibleCandidateInAnyOtherLanguage(suggestionsContainer,
+                                consideredWord, whitelistedWordInfo)))) {
+            // We'll use the whitelist candidate if we are confident the user is typing in the
+            // language of the dictionary it's coming from, or if there is no plausible candidate
+            // coming from another language.
             whitelistedWord = whitelistedWordInfo.mWord;
         } else {
-            // Even if we have a whitelist candidate, we don't use it unless we are confident
-            // the user is typing in the language this whitelist candidate comes from.
+            // If on the contrary we are not confident in the current language and we have
+            // at least a plausible candidate in any other language, then we don't use this
+            // whitelist candidate.
             whitelistedWord = null;
         }
         final boolean resultsArePredictions = !wordComposer.isComposingWord();
@@ -211,7 +270,7 @@ public final class Suggest {
             hasAutoCorrection = false;
         } else {
             final SuggestedWordInfo firstSuggestion = suggestionResults.first();
-            if (!AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold(
+            if (!AutoCorrectionUtils.suggestionExceedsThreshold(
                     firstSuggestion, consideredWord, mAutoCorrectionThreshold)) {
                 // Score is too low for autocorrect
                 hasAutoCorrection = false;
@@ -260,6 +319,20 @@ public final class Suggest {
                 false /* isObsoleteSuggestions */, inputStyle, sequenceNumber));
     }
 
+    private boolean hasPlausibleCandidateInAnyOtherLanguage(
+            final ArrayList<SuggestedWordInfo> suggestionsContainer, final String consideredWord,
+            final SuggestedWordInfo whitelistedWordInfo) {
+        for (final SuggestedWordInfo info : suggestionsContainer) {
+            if (whitelistedWordInfo.mSourceDict.mLocale.equals(info.mSourceDict.mLocale)) {
+                continue;
+            }
+            return AutoCorrectionUtils.suggestionExceedsThreshold(info, consideredWord,
+                    mPlausibilityThreshold);
+        }
+        // No candidate in another language
+        return false;
+    }
+
     // Retrieves suggestions for the batch input
     // and calls the callback function with the suggestions.
     private void getSuggestedWordsForBatchInput(final WordComposer wordComposer,
@@ -293,8 +366,7 @@ public final class Suggest {
             final SuggestedWordInfo rejected = suggestionsContainer.remove(0);
             suggestionsContainer.add(1, rejected);
         }
-        SuggestedWordInfo.removeDupsAndReturnSourceOfTypedWord(null /* typedWord */,
-                null /* preferredLocale */, suggestionsContainer);
+        SuggestedWordInfo.removeDups(null /* typedWord */, suggestionsContainer);
 
         // For some reason some suggestions with MIN_VALUE are making their way here.
         // TODO: Find a more robust way to detect distracters.
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index cbf48f0c05deb861c0d68f8c904687da455b4c5a..390b311e219b4fb5bb7d0a8ee049263da6840b76 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -356,53 +356,30 @@ public class SuggestedWords {
         }
 
         // This will always remove the higher index if a duplicate is found.
-        // Returns null if the typed word is not found. Always return the dictionary for the
-        // highest suggestion matching the locale if found, otherwise return the dictionary for
-        // the highest suggestion.
-        @Nullable
-        public static Dictionary removeDupsAndReturnSourceOfTypedWord(
-                @Nullable final String typedWord,
-                @Nullable final Locale preferredLocale,
+        public static void removeDups(@Nullable final String typedWord,
                 @Nonnull ArrayList<SuggestedWordInfo> candidates) {
             if (candidates.isEmpty()) {
-                return null;
+                return;
             }
-            final Dictionary sourceDictionaryOfTypedWord;
             if (!TextUtils.isEmpty(typedWord)) {
-                sourceDictionaryOfTypedWord =
-                        removeSuggestedWordInfoFromListAndReturnSourceDictionary(typedWord,
-                                preferredLocale, candidates, -1 /* startIndexExclusive */);
-            } else {
-                sourceDictionaryOfTypedWord = null;
+                removeSuggestedWordInfoFromList(typedWord, candidates, -1 /* startIndexExclusive */);
             }
             for (int i = 0; i < candidates.size(); ++i) {
-                removeSuggestedWordInfoFromListAndReturnSourceDictionary(candidates.get(i).mWord,
-                        null /* preferredLocale */, candidates, i /* startIndexExclusive */);
+                removeSuggestedWordInfoFromList(candidates.get(i).mWord, candidates,
+                        i /* startIndexExclusive */);
             }
-            return sourceDictionaryOfTypedWord;
         }
 
-        @Nullable
-        private static Dictionary removeSuggestedWordInfoFromListAndReturnSourceDictionary(
-                @Nonnull final String word, @Nullable final Locale preferredLocale,
-                @Nonnull final ArrayList<SuggestedWordInfo> candidates,
+        private static void removeSuggestedWordInfoFromList(
+                @Nonnull final String word, @Nonnull final ArrayList<SuggestedWordInfo> candidates,
                 final int startIndexExclusive) {
-            Dictionary sourceDictionaryOfTypedWord = null;
             for (int i = startIndexExclusive + 1; i < candidates.size(); ++i) {
                 final SuggestedWordInfo previous = candidates.get(i);
                 if (word.equals(previous.mWord)) {
-                    if (null == sourceDictionaryOfTypedWord
-                            || (null != preferredLocale
-                                    && preferredLocale.equals(previous.mSourceDict.mLocale))) {
-                        if (Dictionary.TYPE_USER_HISTORY != previous.mSourceDict.mDictType) {
-                            sourceDictionaryOfTypedWord = previous.mSourceDict;
-                        }
-                    }
                     candidates.remove(i);
                     --i;
                 }
             }
-            return sourceDictionaryOfTypedWord;
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index 16c0534742f6779515466ebb90aa4f72689c974e..490fa827c507f306d8d7d5c3a027f9beb3f3ea14 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -238,6 +238,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
         return !currentAutoCorrectionSetting.equals(autoCorrectionOff);
     }
 
+    public static float readPlausibilityThreshold(final Resources res) {
+        return Float.parseFloat(res.getString(R.string.plausibility_threshold));
+    }
+
     public static boolean readBlockPotentiallyOffensive(final SharedPreferences prefs,
             final Resources res) {
         return prefs.getBoolean(PREF_BLOCK_POTENTIALLY_OFFENSIVE,
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 26415e7d4b23abb7a25356348e8805608e273d90..c3755792c45f80692cea17b88fc74f63d49cd6a3 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -96,6 +96,7 @@ public class SettingsValues {
     public final int mKeyPreviewPopupDismissDelay;
     private final boolean mAutoCorrectEnabled;
     public final float mAutoCorrectionThreshold;
+    public final float mPlausibilityThreshold;
     public final boolean mAutoCorrectionEnabledPerUserSettings;
     private final boolean mSuggestionsEnabledPerUserSettings;
     private final AsyncResultHolder<AppWorkaroundsUtils> mAppWorkarounds;
@@ -172,6 +173,7 @@ public class SettingsValues {
                 Settings.PREF_ENABLE_EMOJI_ALT_PHYSICAL_KEY, true);
         mAutoCorrectionThreshold = readAutoCorrectionThreshold(res,
                 autoCorrectionThresholdRawValue);
+        mPlausibilityThreshold = Settings.readPlausibilityThreshold(res);
         mGestureInputEnabled = Settings.readGestureInputEnabled(prefs, res);
         mGestureTrailEnabled = prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true);
         mGestureFloatingPreviewTextEnabled = !mInputAttributes.mDisableGestureFloatingPreviewText
diff --git a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
index 120cffbde25b87851626ed840a2c6f5e7512dc30..2fd257922253749cfc59339e60a9126595476b38 100644
--- a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
@@ -29,9 +29,8 @@ public final class AutoCorrectionUtils {
         // Purely static class: can't instantiate.
     }
 
-    public static boolean suggestionExceedsAutoCorrectionThreshold(
-            final SuggestedWordInfo suggestion, final String consideredWord,
-            final float autoCorrectionThreshold) {
+    public static boolean suggestionExceedsThreshold(final SuggestedWordInfo suggestion,
+            final String consideredWord, final float threshold) {
         if (null != suggestion) {
             // Shortlist a whitelisted word
             if (suggestion.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) {
@@ -45,11 +44,11 @@ public final class AutoCorrectionUtils {
             if (DBG) {
                 Log.d(TAG, "Normalized " + consideredWord + "," + suggestion + ","
                         + autoCorrectionSuggestionScore + ", " + normalizedScore
-                        + "(" + autoCorrectionThreshold + ")");
+                        + "(" + threshold + ")");
             }
-            if (normalizedScore >= autoCorrectionThreshold) {
+            if (normalizedScore >= threshold) {
                 if (DBG) {
-                    Log.d(TAG, "Auto corrected by S-threshold.");
+                    Log.d(TAG, "Exceeds threshold.");
                 }
                 return true;
             }