Merge "Import translations. DO NOT MERGE"
diff --git a/java/res/layout/input_view.xml b/java/res/layout/input_view.xml
index ed387e5..ff0b403 100644
--- a/java/res/layout/input_view.xml
+++ b/java/res/layout/input_view.xml
@@ -43,8 +43,6 @@
             android:layout_width="match_parent"
             android:layout_height="@dimen/config_suggestions_strip_height"
             android:gravity="center_vertical"
-            android:paddingRight="@dimen/config_suggestions_strip_horizontal_padding"
-            android:paddingLeft="@dimen/config_suggestions_strip_horizontal_padding"
             style="?attr/suggestionStripViewStyle" />
 
         <!-- To ensure that key preview popup is correctly placed when the current system locale is
diff --git a/java/res/layout/suggestions_strip.xml b/java/res/layout/suggestions_strip.xml
index 0b61499..bff9a1e 100644
--- a/java/res/layout/suggestions_strip.xml
+++ b/java/res/layout/suggestions_strip.xml
@@ -24,12 +24,16 @@
         android:id="@+id/suggestions_strip"
         android:orientation="horizontal"
         android:layout_width="match_parent"
-        android:layout_height="match_parent" />
+        android:layout_height="match_parent"
+        android:layout_marginLeft="@dimen/config_suggestions_strip_horizontal_margin"
+        android:layout_marginRight="@dimen/config_suggestions_strip_horizontal_margin" />
     <LinearLayout
         android:id="@+id/add_to_dictionary_strip"
         android:orientation="horizontal"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
+        android:layout_marginLeft="@dimen/config_suggestions_strip_horizontal_margin"
+        android:layout_marginRight="@dimen/config_suggestions_strip_horizontal_margin"
         android:visibility="invisible">
         <TextView
             android:id="@+id/word_to_save"
@@ -49,7 +53,9 @@
         android:id="@+id/important_notice_strip"
         android:orientation="horizontal"
         android:layout_width="match_parent"
-        android:layout_height="match_parent">
+        android:layout_height="match_parent"
+        android:layout_marginLeft="@dimen/config_suggestions_strip_horizontal_margin"
+        android:layout_marginRight="@dimen/config_suggestions_strip_horizontal_margin">
         <TextView
             android:id="@+id/important_notice_title"
             android:layout_width="match_parent"
diff --git a/java/res/values-land/config.xml b/java/res/values-land/config.xml
index 43ae068..f72d64f 100644
--- a/java/res/values-land/config.xml
+++ b/java/res/values-land/config.xml
@@ -58,6 +58,7 @@
     <fraction name="config_key_shifted_letter_hint_ratio_5row">48%</fraction>
 
     <dimen name="config_suggestions_strip_height">36dp</dimen>
+    <dimen name="config_suggestions_strip_horizontal_margin">54dp</dimen>
     <dimen name="config_more_suggestions_row_height">36dp</dimen>
     <integer name="config_max_more_suggestions_row">2</integer>
     <fraction name="config_min_more_suggestions_width">60%</fraction>
diff --git a/java/res/values-sw600dp-land/config.xml b/java/res/values-sw600dp-land/config.xml
index 00edde1..8789e72 100644
--- a/java/res/values-sw600dp-land/config.xml
+++ b/java/res/values-sw600dp-land/config.xml
@@ -48,7 +48,8 @@
     <fraction name="config_key_letter_ratio_5row">62%</fraction>
     <fraction name="config_key_shifted_letter_hint_ratio_5row">36%</fraction>
 
-    <dimen name="config_suggestions_strip_horizontal_padding">252.0dp</dimen>
+    <dimen name="config_suggestions_strip_height">44dp</dimen>
+    <dimen name="config_suggestions_strip_horizontal_margin">180.0dp</dimen>
     <integer name="config_max_more_suggestions_row">5</integer>
     <fraction name="config_min_more_suggestions_width">50%</fraction>
 
diff --git a/java/res/values-sw600dp/config.xml b/java/res/values-sw600dp/config.xml
index 3bd8439..12e9832 100644
--- a/java/res/values-sw600dp/config.xml
+++ b/java/res/values-sw600dp/config.xml
@@ -63,12 +63,12 @@
     <fraction name="config_key_shifted_letter_hint_ratio_5row">27%</fraction>
 
     <dimen name="config_suggestions_strip_height">44dp</dimen>
+    <dimen name="config_suggestions_strip_horizontal_margin">0dp</dimen>
     <dimen name="config_more_suggestions_row_height">44dp</dimen>
     <integer name="config_max_more_suggestions_row">6</integer>
     <fraction name="config_min_more_suggestions_width">90%</fraction>
-    <dimen name="config_suggestions_strip_horizontal_padding">94.5dp</dimen>
     <dimen name="config_suggestion_min_width">48.0dp</dimen>
-    <dimen name="config_suggestion_text_horizontal_padding">12dp</dimen>
+    <dimen name="config_suggestion_text_horizontal_padding">10dp</dimen>
     <dimen name="config_suggestion_text_size">22dp</dimen>
     <dimen name="config_more_suggestions_hint_text_size">33dp</dimen>
 
diff --git a/java/res/values-sw768dp-land/config.xml b/java/res/values-sw768dp-land/config.xml
index 3878a9e..17733f0 100644
--- a/java/res/values-sw768dp-land/config.xml
+++ b/java/res/values-sw768dp-land/config.xml
@@ -49,7 +49,8 @@
     <fraction name="config_key_letter_ratio_5row">53%</fraction>
     <fraction name="config_key_shifted_letter_hint_ratio_5row">30%</fraction>
 
-    <dimen name="config_suggestions_strip_horizontal_padding">252.0dp</dimen>
+    <dimen name="config_suggestions_strip_height">44dp</dimen>
+    <dimen name="config_suggestions_strip_horizontal_margin">340dp</dimen>
     <fraction name="config_min_more_suggestions_width">50%</fraction>
 
     <!-- Gesture floating preview text parameters -->
diff --git a/java/res/values-sw768dp/config.xml b/java/res/values-sw768dp/config.xml
index 34eec38..647cca9 100644
--- a/java/res/values-sw768dp/config.xml
+++ b/java/res/values-sw768dp/config.xml
@@ -61,12 +61,12 @@
     <fraction name="config_key_shifted_letter_hint_ratio_5row">33%</fraction>
 
     <dimen name="config_suggestions_strip_height">44dp</dimen>
+    <dimen name="config_suggestions_strip_horizontal_margin">100dp</dimen>
     <dimen name="config_more_suggestions_row_height">44dp</dimen>
     <integer name="config_max_more_suggestions_row">6</integer>
     <fraction name="config_min_more_suggestions_width">90%</fraction>
-    <dimen name="config_suggestions_strip_horizontal_padding">94.5dp</dimen>
     <dimen name="config_suggestion_min_width">46dp</dimen>
-    <dimen name="config_suggestion_text_horizontal_padding">8dp</dimen>
+    <dimen name="config_suggestion_text_horizontal_padding">10dp</dimen>
     <dimen name="config_suggestion_text_size">22dp</dimen>
     <dimen name="config_more_suggestions_hint_text_size">33dp</dimen>
 
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 45ea483..1ab4927 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -64,10 +64,10 @@
     <fraction name="config_key_shifted_letter_hint_ratio_5row">41%</fraction>
 
     <dimen name="config_suggestions_strip_height">40dp</dimen>
+    <dimen name="config_suggestions_strip_horizontal_margin">0dp</dimen>
     <dimen name="config_more_suggestions_row_height">40dp</dimen>
     <integer name="config_max_more_suggestions_row">6</integer>
     <fraction name="config_min_more_suggestions_width">90%</fraction>
-    <dimen name="config_suggestions_strip_horizontal_padding">0dp</dimen>
     <dimen name="config_suggestion_min_width">44dp</dimen>
     <dimen name="config_suggestion_text_horizontal_padding">6dp</dimen>
     <dimen name="config_suggestion_text_size">18dp</dimen>
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
index 0f9645e..1a27be9 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
@@ -1067,33 +1067,33 @@
         // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
         // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON
         /* morekeys_a */ "\u00E0,\u00E1,\u00E2,\u00E4,\u00E6,\u00E3,\u00E5,\u0101",
+        // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
         // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
         // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
         // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
-        // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
         // U+0153: "œ" LATIN SMALL LIGATURE OE
         // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
         // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
         // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
-        /* morekeys_o */ "\u00F4,\u00F6,\u00F2,\u00F3,\u0153,\u00F8,\u014D,\u00F5",
+        /* morekeys_o */ "\u00F3,\u00F4,\u00F6,\u00F2,\u0153,\u00F8,\u014D,\u00F5",
+        // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
         // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
         // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
         // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
-        // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
         // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
-        /* morekeys_u */ "\u00FB,\u00FC,\u00F9,\u00FA,\u016B",
-        // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+        /* morekeys_u */ "\u00FA,\u00FB,\u00FC,\u00F9,\u016B",
         // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+        // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
         // U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
         // U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
         // U+0113: "ē" LATIN SMALL LETTER E WITH MACRON
-        /* morekeys_e */ "\u00E8,\u00E9,\u00EA,\u00EB,\u0113",
+        /* morekeys_e */ "\u00E9,\u00E8,\u00EA,\u00EB,\u0113",
+        // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
         // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
         // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
-        // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
         // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
         // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
-        /* morekeys_i */ "\u00EE,\u00EF,\u00ED,\u012B,\u00EC",
+        /* morekeys_i */ "\u00ED,\u00EE,\u00EF,\u012B,\u00EC",
         // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
         /* morekeys_c */ "\u00E7",
         /* double_quotes */ null,
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index b88509f..83ee982 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -218,6 +218,8 @@
             int bigramProbability);
     private static native String getPropertyNative(long dict, String query);
     private static native boolean isCorruptedNative(long dict);
+    private static native boolean migrateNative(long dict, String dictFilePath,
+            long newFormatVersion);
 
     // TODO: Move native dict into session
     private final void loadDictionary(final String path, final long startOffset,
@@ -533,7 +535,9 @@
             return false;
         }
         final String tmpDictFilePath = mDictFilePath + DICT_FILE_NAME_SUFFIX_FOR_MIGRATION;
-        // TODO: Implement migrateNative(tmpDictFilePath, newFormatVersion).
+        if (!migrateNative(mNativeDict, tmpDictFilePath, newFormatVersion)) {
+            return false;
+        }
         close();
         final File dictFile = new File(mDictFilePath);
         final File tmpDictFile = new File(tmpDictFilePath);
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index c825ca4..e3bed31 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -470,7 +470,10 @@
         }
         if (mBinaryDictionary.isValidDictionary()
                 && needsToMigrateDictionary(mBinaryDictionary.getFormatVersion())) {
-            mBinaryDictionary.migrateTo(DICTIONARY_FORMAT_VERSION);
+            if (!mBinaryDictionary.migrateTo(DICTIONARY_FORMAT_VERSION)) {
+                Log.e(TAG, "Dictionary migration failed: " + mDictName);
+                removeBinaryDictionaryLocked();
+            }
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
index 1d84bb5..8bfa63c 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -309,9 +309,8 @@
 
         setupWordViewsTextAndColor(suggestedWords, mSuggestionsCountInStrip);
         final TextView centerWordView = mWordViews.get(mCenterPositionInStrip);
-        final int availableStripWidth = placerView.getWidth()
-                - placerView.getPaddingRight() - placerView.getPaddingLeft();
-        final int centerWidth = getSuggestionWidth(mCenterPositionInStrip, availableStripWidth);
+        final int stripWidth = stripView.getWidth();
+        final int centerWidth = getSuggestionWidth(mCenterPositionInStrip, stripWidth);
         final int countInStrip;
         if (suggestedWords.size() == 1 || getTextScaleX(centerWordView.getText(), centerWidth,
                 centerWordView.getPaint()) < MIN_TEXT_XSCALE) {
@@ -319,11 +318,11 @@
             // by consolidating all slots in the strip.
             countInStrip = 1;
             mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
-            layoutWord(mCenterPositionInStrip, availableStripWidth - mPadding);
+            layoutWord(mCenterPositionInStrip, stripWidth - mPadding);
             stripView.addView(centerWordView);
             setLayoutWeight(centerWordView, 1.0f, ViewGroup.LayoutParams.MATCH_PARENT);
             if (SuggestionStripView.DBG) {
-                layoutDebugInfo(mCenterPositionInStrip, placerView, availableStripWidth);
+                layoutDebugInfo(mCenterPositionInStrip, placerView, stripWidth);
             }
         } else {
             countInStrip = mSuggestionsCountInStrip;
@@ -337,7 +336,7 @@
                     x += divider.getMeasuredWidth();
                 }
 
-                final int width = getSuggestionWidth(positionInStrip, availableStripWidth);
+                final int width = getSuggestionWidth(positionInStrip, stripWidth);
                 final TextView wordView = layoutWord(positionInStrip, width);
                 stripView.addView(wordView);
                 setLayoutWeight(wordView, getSuggestionWeight(positionInStrip),
@@ -474,8 +473,8 @@
         return countInStrip;
     }
 
-    public void layoutAddToDictionaryHint(final String word, final ViewGroup addToDictionaryStrip,
-            final int stripWidth) {
+    public void layoutAddToDictionaryHint(final String word, final ViewGroup addToDictionaryStrip) {
+        final int stripWidth = addToDictionaryStrip.getWidth();
         final int width = stripWidth - mDividerWidth - mPadding * 2;
 
         final TextView wordView = (TextView)addToDictionaryStrip.findViewById(R.id.word_to_save);
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index a0793b1..a578fa4 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -209,7 +209,7 @@
     }
 
     public void showAddToDictionaryHint(final String word) {
-        mLayoutHelper.layoutAddToDictionaryHint(word, mAddToDictionaryStrip, getWidth());
+        mLayoutHelper.layoutAddToDictionaryHint(word, mAddToDictionaryStrip);
         // {@link TextView#setTag()} is used to hold the word to be added to dictionary. The word
         // will be extracted at {@link #onClick(View)}.
         mAddToDictionaryStrip.setTag(word);
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
index f2a1e52..48e43d6 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
@@ -40,7 +40,7 @@
         mKeyboard = keyboard;
     }
 
-    public boolean isDistractorToWordsInDictionaries(final String prevWord,
+    public boolean isDistracterToWordsInDictionaries(final String prevWord,
             final String targetWord) {
         // TODO: to be implemented
         return false;
diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
index 5ce977d..55061f4 100644
--- a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
+++ b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
@@ -80,7 +80,8 @@
     public static ArrayList<LanguageModelParam> createLanguageModelParamsFrom(
             final ArrayList<String> tokens, final int timestamp,
             final DictionaryFacilitatorForSuggest dictionaryFacilitator,
-            final SpacingAndPunctuations spacingAndPunctuations) {
+            final SpacingAndPunctuations spacingAndPunctuations,
+            final DistracterFilter distracterFilter) {
         final ArrayList<LanguageModelParam> languageModelParams =
                 CollectionUtils.newArrayList();
         final int N = tokens.size();
@@ -109,7 +110,8 @@
             }
             final LanguageModelParam languageModelParam =
                     detectWhetherVaildWordOrNotAndGetLanguageModelParam(
-                            prevWord, tempWord, timestamp, dictionaryFacilitator);
+                            prevWord, tempWord, timestamp, dictionaryFacilitator,
+                            distracterFilter);
             if (languageModelParam == null) {
                 continue;
             }
@@ -121,27 +123,33 @@
 
     private static LanguageModelParam detectWhetherVaildWordOrNotAndGetLanguageModelParam(
             final String prevWord, final String targetWord, final int timestamp,
-            final DictionaryFacilitatorForSuggest dictionaryFacilitator) {
+            final DictionaryFacilitatorForSuggest dictionaryFacilitator,
+            final DistracterFilter distracterFilter) {
         final Locale locale = dictionaryFacilitator.getLocale();
         if (locale == null) {
             return null;
         }
-        if (!dictionaryFacilitator.isValidWord(targetWord, true /* ignoreCase */)) {
-            // OOV word.
-            return createAndGetLanguageModelParamOfWord(prevWord, targetWord, timestamp,
-                    false /* isValidWord */, locale);
-        }
         if (dictionaryFacilitator.isValidWord(targetWord, false /* ignoreCase */)) {
             return createAndGetLanguageModelParamOfWord(prevWord, targetWord, timestamp,
                     true /* isValidWord */, locale);
         }
+
         final String lowerCaseTargetWord = targetWord.toLowerCase(locale);
         if (dictionaryFacilitator.isValidWord(lowerCaseTargetWord, false /* ignoreCase */)) {
             // Add the lower-cased word.
             return createAndGetLanguageModelParamOfWord(prevWord, lowerCaseTargetWord,
                     timestamp, true /* isValidWord */, locale);
         }
-        // Treat the word as an OOV word.
+
+        // Treat the word as an OOV word. The following statement checks whether this OOV
+        // is a distracter to words in dictionaries. Being a distracter means the OOV word is
+        // too close to a common word in dictionaries (e.g., the OOV "mot" is very close to "not").
+        // Adding such a word to dictonaries would interfere with entering in-dictionary words. For
+        // example, adding "mot" to dictionaries might interfere with entering "not".
+        // This kind of OOV should be filtered out.
+        if (distracterFilter.isDistracterToWordsInDictionaries(prevWord, targetWord)) {
+            return null;
+        }
         return createAndGetLanguageModelParamOfWord(prevWord, targetWord, timestamp,
                 false /* isValidWord */, locale);
     }
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 9016cae..3ac424f 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -32,6 +32,7 @@
 #include "suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.h"
 #include "utils/char_utils.h"
 #include "utils/jni_data_utils.h"
+#include "utils/log_utils.h"
 #include "utils/time_keeper.h"
 
 namespace latinime {
@@ -489,6 +490,87 @@
     return dictionary->getDictionaryStructurePolicy()->isCorrupted();
 }
 
+static DictionaryStructureWithBufferPolicy::StructurePolicyPtr runGCAndGetNewStructurePolicy(
+        DictionaryStructureWithBufferPolicy::StructurePolicyPtr structurePolicy,
+        const char *const dictFilePath) {
+    structurePolicy->flushWithGC(dictFilePath);
+    structurePolicy.release();
+    return DictionaryStructureWithBufferPolicyFactory::newPolicyForExistingDictFile(
+            dictFilePath, 0 /* offset */, 0 /* size */, true /* isUpdatable */);
+}
+
+static bool latinime_BinaryDictionary_migrateNative(JNIEnv *env, jclass clazz, jlong dict,
+        jstring dictFilePath, jlong newFormatVersion) {
+    Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
+    if (!dictionary) {
+        return false;
+    }
+    const jsize filePathUtf8Length = env->GetStringUTFLength(dictFilePath);
+    char dictFilePathChars[filePathUtf8Length + 1];
+    env->GetStringUTFRegion(dictFilePath, 0, env->GetStringLength(dictFilePath), dictFilePathChars);
+    dictFilePathChars[filePathUtf8Length] = '\0';
+
+    const DictionaryHeaderStructurePolicy *const headerPolicy =
+            dictionary->getDictionaryStructurePolicy()->getHeaderStructurePolicy();
+    DictionaryStructureWithBufferPolicy::StructurePolicyPtr dictionaryStructureWithBufferPolicy =
+            DictionaryStructureWithBufferPolicyFactory::newPolicyForOnMemoryDict(
+                    newFormatVersion, *headerPolicy->getLocale(), headerPolicy->getAttributeMap());
+    if (!dictionaryStructureWithBufferPolicy) {
+        LogUtils::logToJava(env, "Cannot migrate header.");
+        return false;
+    }
+
+    // TODO: Migrate historical information.
+    int wordCodePoints[MAX_WORD_LENGTH];
+    int token = 0;
+    // Add unigrams.
+    do {
+        token = dictionary->getNextWordAndNextToken(token, wordCodePoints);
+        const int wordLength = CharUtils::getCodePointCount(MAX_WORD_LENGTH, wordCodePoints);
+        const WordProperty wordProperty = dictionary->getWordProperty(wordCodePoints, wordLength);
+        if (dictionaryStructureWithBufferPolicy->needsToRunGC(true /* mindsBlockByGC */)) {
+            dictionaryStructureWithBufferPolicy = runGCAndGetNewStructurePolicy(
+                    std::move(dictionaryStructureWithBufferPolicy), dictFilePathChars);
+            if (!dictionaryStructureWithBufferPolicy) {
+                LogUtils::logToJava(env, "Cannot open dict after GC.");
+                return false;
+            }
+        }
+        if (!dictionaryStructureWithBufferPolicy->addUnigramWord(wordCodePoints, wordLength,
+                wordProperty.getUnigramProperty())) {
+            LogUtils::logToJava(env, "Cannot add unigram to the new dict.");
+            return false;
+        }
+    } while (token != 0);
+
+    // Add bigrams.
+    do {
+        token = dictionary->getNextWordAndNextToken(token, wordCodePoints);
+        const int wordLength = CharUtils::getCodePointCount(MAX_WORD_LENGTH, wordCodePoints);
+        const WordProperty wordProperty = dictionary->getWordProperty(wordCodePoints, wordLength);
+        if (dictionaryStructureWithBufferPolicy->needsToRunGC(true /* mindsBlockByGC */)) {
+            dictionaryStructureWithBufferPolicy = runGCAndGetNewStructurePolicy(
+                    std::move(dictionaryStructureWithBufferPolicy), dictFilePathChars);
+            if (!dictionaryStructureWithBufferPolicy) {
+                LogUtils::logToJava(env, "Cannot open dict after GC.");
+                return false;
+            }
+        }
+        for (const BigramProperty &bigarmProperty : *wordProperty.getBigramProperties()) {
+            const std::vector<int> *targetCodePoints = bigarmProperty.getTargetCodePoints();
+            if (!dictionaryStructureWithBufferPolicy->addBigramWords(wordCodePoints, wordLength,
+                    targetCodePoints->data(), targetCodePoints->size(),
+                    bigarmProperty.getProbability(), bigarmProperty.getTimestamp())) {
+                LogUtils::logToJava(env, "Cannot add bigram to the new dict.");
+                return false;
+            }
+        }
+    } while (token != 0);
+    // Save to File.
+    dictionaryStructureWithBufferPolicy->flushWithGC(dictFilePathChars);
+    return true;
+}
+
 static const JNINativeMethod sMethods[] = {
     {
         const_cast<char *>("openNative"),
@@ -591,6 +673,11 @@
         const_cast<char *>("isCorruptedNative"),
         const_cast<char *>("(J)Z"),
         reinterpret_cast<void *>(latinime_BinaryDictionary_isCorruptedNative)
+    },
+    {
+        const_cast<char *>("migrateNative"),
+        const_cast<char *>("(JLjava/lang/String;J)Z"),
+        reinterpret_cast<void *>(latinime_BinaryDictionary_migrateNative)
     }
 };
 
diff --git a/native/jni/src/suggest/core/dictionary/property/word_property.h b/native/jni/src/suggest/core/dictionary/property/word_property.h
index 5519a91..aa3e0b6 100644
--- a/native/jni/src/suggest/core/dictionary/property/word_property.h
+++ b/native/jni/src/suggest/core/dictionary/property/word_property.h
@@ -42,6 +42,14 @@
             jintArray outProbabilityInfo, jobject outBigramTargets, jobject outBigramProbabilities,
             jobject outShortcutTargets, jobject outShortcutProbabilities) const;
 
+    const UnigramProperty *getUnigramProperty() const {
+        return &mUnigramProperty;
+    }
+
+    const std::vector<BigramProperty> *getBigramProperties() const {
+        return &mBigrams;
+    }
+
  private:
     // Default copy constructor is used for using as a return value.
     DISALLOW_ASSIGNMENT_OPERATOR(WordProperty);
diff --git a/native/jni/src/suggest/core/policy/dictionary_header_structure_policy.h b/native/jni/src/suggest/core/policy/dictionary_header_structure_policy.h
index a8dab9f..845e629 100644
--- a/native/jni/src/suggest/core/policy/dictionary_header_structure_policy.h
+++ b/native/jni/src/suggest/core/policy/dictionary_header_structure_policy.h
@@ -49,6 +49,8 @@
 
     virtual bool shouldBoostExactMatches() const = 0;
 
+    virtual const std::vector<int> *getLocale() const = 0;
+
  protected:
     DictionaryHeaderStructurePolicy() {}
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
index 251a719..e4a6dc5 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
@@ -238,6 +238,10 @@
             const int unigramCount, const int bigramCount, const int extendedRegionSize,
             DictionaryHeaderStructurePolicy::AttributeMap *outAttributeMap) const;
 
+    AK_FORCE_INLINE const std::vector<int> *getLocale() const {
+        return &mLocale;
+    }
+
  private:
     DISALLOW_COPY_AND_ASSIGN(HeaderPolicy);
 
diff --git a/tools/make-keyboard-text/res/values-en/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-en/donottranslate-more-keys.xml
index 7998bf1..c22edba 100644
--- a/tools/make-keyboard-text/res/values-en/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-en/donottranslate-more-keys.xml
@@ -27,33 +27,33 @@
          U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE
          U+0101: "ā" LATIN SMALL LETTER A WITH MACRON -->
     <string name="morekeys_a">&#x00E0;,&#x00E1;,&#x00E2;,&#x00E4;,&#x00E6;,&#x00E3;,&#x00E5;,&#x0101;</string>
-    <!-- U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
-         U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+    <!-- U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+         U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
          U+00EA: "ê" LATIN SMALL LETTER E WITH CIRCUMFLEX
          U+00EB: "ë" LATIN SMALL LETTER E WITH DIAERESIS
          U+0113: "ē" LATIN SMALL LETTER E WITH MACRON -->
-    <string name="morekeys_e">&#x00E8;,&#x00E9;,&#x00EA;,&#x00EB;,&#x0113;</string>
-    <!-- U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+    <string name="morekeys_e">&#x00E9;,&#x00E8;,&#x00EA;,&#x00EB;,&#x0113;</string>
+    <!-- U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
          U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
-         U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
          U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
          U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE -->
-    <string name="morekeys_i">&#x00EE;,&#x00EF;,&#x00ED;,&#x012B;,&#x00EC;</string>
-    <!-- U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+    <string name="morekeys_i">&#x00ED;,&#x00EE;,&#x00EF;,&#x012B;,&#x00EC;</string>
+    <!-- U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
          U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
          U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
-         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
          U+0153: "œ" LATIN SMALL LIGATURE OE
          U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
          U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
          U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE -->
-    <string name="morekeys_o">&#x00F4;,&#x00F6;,&#x00F2;,&#x00F3;,&#x0153;,&#x00F8;,&#x014D;,&#x00F5;</string>
-    <!-- U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+    <string name="morekeys_o">&#x00F3;,&#x00F4;,&#x00F6;,&#x00F2;,&#x0153;,&#x00F8;,&#x014D;,&#x00F5;</string>
+    <!-- U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
          U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
          U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
-         U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
          U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
-    <string name="morekeys_u">&#x00FB;,&#x00FC;,&#x00F9;,&#x00FA;,&#x016B;</string>
+    <string name="morekeys_u">&#x00FA;,&#x00FB;,&#x00FC;,&#x00F9;,&#x016B;</string>
     <!-- U+00DF: "ß" LATIN SMALL LETTER SHARP S -->
     <string name="morekeys_s">&#x00DF;</string>
     <!-- U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE -->