am 67caab34: (-s ours) am d5600b9b: (-s ours) am d075a7af: (-s ours) Import translations. DO NOT MERGE

* commit '67caab34441fd906af95026d8731bb02db2e3dbd':
  Import translations. DO NOT MERGE
diff --git a/java/res/drawable-hdpi/keyboard_popup_panel_background_holo.9.png b/java/res/drawable-hdpi/keyboard_popup_panel_background_holo.9.png
index dc2fc7d..f9dd3b8 100644
--- a/java/res/drawable-hdpi/keyboard_popup_panel_background_holo.9.png
+++ b/java/res/drawable-hdpi/keyboard_popup_panel_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/keyboard_popup_panel_background_holo.9.png b/java/res/drawable-mdpi/keyboard_popup_panel_background_holo.9.png
index 441edc3..8965055 100644
--- a/java/res/drawable-mdpi/keyboard_popup_panel_background_holo.9.png
+++ b/java/res/drawable-mdpi/keyboard_popup_panel_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/keyboard_popup_panel_background_holo.9.png b/java/res/drawable-xhdpi/keyboard_popup_panel_background_holo.9.png
index dde1856..36df715 100644
--- a/java/res/drawable-xhdpi/keyboard_popup_panel_background_holo.9.png
+++ b/java/res/drawable-xhdpi/keyboard_popup_panel_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_popup_panel_background_holo.9.png b/java/res/drawable-xxhdpi/keyboard_popup_panel_background_holo.9.png
index ca576de..91d5d7f 100644
--- a/java/res/drawable-xxhdpi/keyboard_popup_panel_background_holo.9.png
+++ b/java/res/drawable-xxhdpi/keyboard_popup_panel_background_holo.9.png
Binary files differ
diff --git a/java/res/layout/emoji_keyboard_page.xml b/java/res/layout/emoji_keyboard_page.xml
index e0b752b..9afad36 100644
--- a/java/res/layout/emoji_keyboard_page.xml
+++ b/java/res/layout/emoji_keyboard_page.xml
@@ -18,16 +18,9 @@
 */
 -->
 
-<com.android.inputmethod.keyboard.internal.ScrollViewWithNotifier
+<com.android.inputmethod.keyboard.internal.EmojiPageKeyboardView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/emoji_keyboard_scroller"
-    android:clipToPadding="false"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
->
-    <com.android.inputmethod.keyboard.internal.ScrollKeyboardView
-        android:id="@+id/emoji_keyboard_page"
-        android:layoutDirection="ltr"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content" />
-</com.android.inputmethod.keyboard.internal.ScrollViewWithNotifier>
+    android:id="@+id/emoji_keyboard_page"
+    android:layoutDirection="ltr"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content" />
diff --git a/java/res/raw/setup_welcome_image.png b/java/res/raw/setup_welcome_image.png
index 2445915..98e7313 100644
--- a/java/res/raw/setup_welcome_image.png
+++ b/java/res/raw/setup_welcome_image.png
Binary files differ
diff --git a/java/res/raw/setup_welcome_video.mp4 b/java/res/raw/setup_welcome_video.mp4
index 8208525..224bf25 100644
--- a/java/res/raw/setup_welcome_video.mp4
+++ b/java/res/raw/setup_welcome_video.mp4
Binary files differ
diff --git a/java/res/values-af/strings.xml b/java/res/values-af/strings.xml
index a3c95b2..adf9e5b 100644
--- a/java/res/values-af/strings.xml
+++ b/java/res/values-af/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Koppel \'n kopstuk om te hoor hoe wagwoordsleutels hardop gesê word."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Huidige teks is %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Geen teks ingevoer nie"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> korrigeer <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> na <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> voer outokorreksie uit"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Sleutelkode %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift aan (tik om te deaktiveer)"</string>
diff --git a/java/res/values-am/strings.xml b/java/res/values-am/strings.xml
index 89e37b2..42f60a4 100644
--- a/java/res/values-am/strings.xml
+++ b/java/res/values-am/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"የይለፍቃል ቁልፎች ጮክ በለው ሲነገሩ ለመስማት የጆሮ ማዳመጫ ሰካ::"</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"የአሁኑ ፅሁፍ %s ነው"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"ምንም ፅሁፍ አልገባም"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g>ን ወደ <xliff:g id="CORRECTED">%3$s</xliff:g> ያርመዋል"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> ራስ-ሰር እርማትን ያከናውናል"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"የቁልፍ ኮድ%d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"ቀይር"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"ቅያር በርቷል (ለማሰናክል ንካ)"</string>
@@ -163,7 +161,7 @@
     <string name="save" msgid="7646738597196767214">"አስቀምጥ"</string>
     <string name="subtype_locale" msgid="8576443440738143764">"ቋንቋ"</string>
     <string name="keyboard_layout_set" msgid="4309233698194565609">"አቀማመጥ"</string>
-    <string name="custom_input_style_note_message" msgid="8826731320846363423">"የተበጀው የግብዓት ቅጥህን ከመጠቀምህ በፊት መንቃት አለበት። አሁን ማንቃት ትፈልጋለህ?"</string>
+    <string name="custom_input_style_note_message" msgid="8826731320846363423">"የተበጀው የእርስዎን ግብዓት ቅጥ ከመጠቀምዎ በፊት መንቃት አለበት። አሁን ማንቃት ይፈልጋሉ?"</string>
     <string name="enable" msgid="5031294444630523247">"አንቃ"</string>
     <string name="not_now" msgid="6172462888202790482">"አሁን አልፈልግም"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"ተመሳሳዩ የግብዓት ቅጥ አስቀድሞ አለ፦ <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
diff --git a/java/res/values-ar/strings-appname.xml b/java/res/values-ar/strings-appname.xml
index d5176d0..35387cb 100644
--- a/java/res/values-ar/strings-appname.xml
+++ b/java/res/values-ar/strings-appname.xml
@@ -20,8 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="5940510615957428904">"لوحة مفاتيح Android ‏(AOSP)"</string>
-    <string name="spell_checker_service_name" msgid="1254221805440242662">"المدقق الإملائي في Android‏ (AOSP)"</string>
-    <string name="english_ime_settings" msgid="5760361067176802794">"إعدادات لوحة مفاتيح Android‏ (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="6123949487832861885">"إعدادات المدقق الإملائي في Android‏ (AOSP)"</string>
+    <string name="english_ime_name" msgid="5940510615957428904">"‏لوحة مفاتيح Android ‏(AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"‏المدقق الإملائي في Android‏ (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"‏إعدادات لوحة مفاتيح Android‏ (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"‏إعدادات المدقق الإملائي في Android‏ (AOSP)"</string>
 </resources>
diff --git a/java/res/values-ar/strings.xml b/java/res/values-ar/strings.xml
index 9c3ff51..201976a 100644
--- a/java/res/values-ar/strings.xml
+++ b/java/res/values-ar/strings.xml
@@ -38,7 +38,7 @@
     <string name="show_language_switch_key" msgid="5915478828318774384">"مفتاح تبديل اللغة"</string>
     <string name="show_language_switch_key_summary" msgid="7343403647474265713">"يظهر عندما يتم تمكين لغات الإدخال متعددة"</string>
     <string name="sliding_key_input_preview" msgid="6604262359510068370">"إظهار مؤشر التمرير"</string>
-    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"عرض تلميح مرئي أثناء التمرير من مفتاح Shift أو Symbol"</string>
+    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"‏عرض تلميح مرئي أثناء التمرير من مفتاح Shift أو Symbol"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"تأخير إزالة النافذة المنبثقة الأساسية"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"بلا تأخير"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"افتراضي"</string>
@@ -82,16 +82,14 @@
     <string name="label_pause_key" msgid="181098308428035340">"توقف مؤقت"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"انتظار"</string>
     <string name="spoken_use_headphones" msgid="896961781287283493">"يمكنك توصيل سماعة رأس لسماع مفاتيح كلمة المرور منطوقة بصوت عالٍ."</string>
-    <string name="spoken_current_text_is" msgid="2485723011272583845">"النص الحالي هو %s"</string>
+    <string name="spoken_current_text_is" msgid="2485723011272583845">"‏النص الحالي هو %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"لم يتم إدخال نص"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
-    <string name="spoken_description_unknown" msgid="3197434010402179157">"رمز المفتاح %d"</string>
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> لتصحيح <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> إلى <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> للتصحيح التلقائي"</string>
+    <string name="spoken_description_unknown" msgid="3197434010402179157">"‏رمز المفتاح %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"العالي"</string>
-    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift يعمل (انقر للتعطيل)"</string>
-    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock يعمل (انقر للتعطيل)"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"‏Shift يعمل (انقر للتعطيل)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"‏Caps lock يعمل (انقر للتعطيل)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"حذف"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"الرموز"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"أحرف"</string>
@@ -107,9 +105,9 @@
     <string name="spoken_description_language_switch" msgid="5507091328222331316">"تبديل اللغة"</string>
     <string name="spoken_description_action_next" msgid="8636078276664150324">"التالي"</string>
     <string name="spoken_description_action_previous" msgid="800872415009336208">"السابق"</string>
-    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"تم تمكين Shift"</string>
-    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"تم تمكين Caps lock"</string>
-    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"تم تعطيل Shift"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"‏تم تمكين Shift"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"‏تم تمكين Caps lock"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"‏تم تعطيل Shift"</string>
     <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"وضع الرموز"</string>
     <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"وضع الأحرف"</string>
     <string name="spoken_description_mode_phone" msgid="6520207943132026264">"وضع الهاتف"</string>
@@ -124,7 +122,7 @@
     <string name="keyboard_mode_phone" msgid="6851627527401433229">"الهاتف"</string>
     <string name="keyboard_mode_text" msgid="6479436687899701619">"النص"</string>
     <string name="keyboard_mode_time" msgid="4381856885582143277">"الوقت"</string>
-    <string name="keyboard_mode_url" msgid="1519819835514911218">"عنوان URL"</string>
+    <string name="keyboard_mode_url" msgid="1519819835514911218">"‏عنوان URL"</string>
     <string name="voice_input" msgid="3583258583521397548">"مفتاح الإدخال الصوتي"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"لوحة مفاتيح رئيسية"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"لوحة مفاتيح الرموز"</string>
@@ -149,12 +147,12 @@
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"الإسبانية (الأمريكية) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (التقليدية)"</string>
     <string name="subtype_no_language" msgid="7137390094240139495">"بدون لغة (أبجدية)"</string>
-    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"الأبجدية (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"الأبجدية (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"الأبجدية (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"الأبجدية (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"الأبجدية (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"الأبجدية (PC)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"‏الأبجدية (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"‏الأبجدية (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"‏الأبجدية (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"‏الأبجدية (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"‏الأبجدية (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"‏الأبجدية (PC)"</string>
     <string name="subtype_emoji" msgid="7483586578074549196">"الرموز التعبيرية"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"أنماط الإدخال المخصصة"</string>
     <string name="add_style" msgid="6163126614514489951">"إضافة نمط"</string>
@@ -219,9 +217,9 @@
     <string name="install_dict" msgid="180852772562189365">"تثبيت"</string>
     <string name="cancel_download_dict" msgid="7843340278507019303">"إلغاء"</string>
     <string name="delete_dict" msgid="756853268088330054">"حذف"</string>
-    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"اللغة المحددة على جهازك الجوال تشتمل على قاموس متوفر.&lt;br/&gt; نوصي &lt;b&gt;بتنزيل&lt;/b&gt; قاموس <xliff:g id="LANGUAGE">%1$s</xliff:g> لتحسين تجربة الكتابة.&lt;br/&gt; &lt;br/&gt; قد يستغرق التنزيل دقيقة أو دقيقتين أكثر من المدة التي يستغرقها التنزيل عبر شبكة الجيل الثالث. قد تنطبق الرسوم إذا لم تكن مشتركًا في &lt;b&gt;خطة البيانات غير المحدودة&lt;/b&gt;.&lt;br/&gt; إذا لم تكن متأكدًا من خطة البيانات المتوفرة لديك، فنحن نوصي بالبحث عن اتصال Wi-Fi لبدء عملية التنزيل تلقائيًا.&lt;br/&gt; &lt;br/&gt; نصيحة: يمكنك تنزيل القواميس وإزالتها عن طريق الانتقال إلى &lt;b&gt;اللغة والإدخال&lt;/b&gt; في قائمة &lt;b&gt;إعدادات&lt;/b&gt; في جهازك الجوَّال."</string>
+    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"‏اللغة المحددة على جهازك الجوال تشتمل على قاموس متوفر.&lt;br/&gt; نوصي &lt;b&gt;بتنزيل&lt;/b&gt; قاموس <xliff:g id="LANGUAGE">%1$s</xliff:g> لتحسين تجربة الكتابة.&lt;br/&gt; &lt;br/&gt; قد يستغرق التنزيل دقيقة أو دقيقتين أكثر من المدة التي يستغرقها التنزيل عبر شبكة الجيل الثالث. قد تنطبق الرسوم إذا لم تكن مشتركًا في &lt;b&gt;خطة البيانات غير المحدودة&lt;/b&gt;.&lt;br/&gt; إذا لم تكن متأكدًا من خطة البيانات المتوفرة لديك، فنحن نوصي بالبحث عن اتصال Wi-Fi لبدء عملية التنزيل تلقائيًا.&lt;br/&gt; &lt;br/&gt; نصيحة: يمكنك تنزيل القواميس وإزالتها عن طريق الانتقال إلى &lt;b&gt;اللغة والإدخال&lt;/b&gt; في قائمة &lt;b&gt;إعدادات&lt;/b&gt; في جهازك الجوَّال."</string>
     <string name="download_over_metered" msgid="1643065851159409546">"التنزيل الآن (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g> ميغابايت)"</string>
-    <string name="do_not_download_over_metered" msgid="2176209579313941583">"التنزيل عبر شبكة Wi-Fi"</string>
+    <string name="do_not_download_over_metered" msgid="2176209579313941583">"‏التنزيل عبر شبكة Wi-Fi"</string>
     <string name="dict_available_notification_title" msgid="6514288591959117288">"هناك قاموس متوفر للغة <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"اضغط للمراجعة والتنزيل"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"جارٍ التنزيل: ستتوفر اقتراحات للغة <xliff:g id="LANGUAGE">%1$s</xliff:g> بعد قليل."</string>
diff --git a/java/res/values-be/strings.xml b/java/res/values-be/strings.xml
index 9e591de..d9b9708 100644
--- a/java/res/values-be/strings.xml
+++ b/java/res/values-be/strings.xml
@@ -87,9 +87,9 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Каб праслухаць паролi, падключыце гарнiтуру."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Бягучы тэкст %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Тэкст не ўведзены"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+    <!-- no translation found for spoken_auto_correct (8005997889020109763) -->
     <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+    <!-- no translation found for spoken_auto_correct_obscured (6276420476908833791) -->
     <skip />
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Клавішны код %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Зрух"</string>
diff --git a/java/res/values-bg/strings.xml b/java/res/values-bg/strings.xml
index c21b534..eecc0f0 100644
--- a/java/res/values-bg/strings.xml
+++ b/java/res/values-bg/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Включете слушалки, за да чуете клавишите за паролата на висок глас."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Текущият текст е %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Няма въведен текст"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"„<xliff:g id="KEY">%1$s</xliff:g>“ коригира „<xliff:g id="ORIGINAL_WORD">%2$s</xliff:g>“ на „<xliff:g id="CORRECTED">%3$s</xliff:g>“"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"„<xliff:g id="KEY">%1$s</xliff:g>“ изпълнява автоматично коригиране"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Код на клавишa %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"„Shift“ е включен (докоснете за деактивиране)"</string>
diff --git a/java/res/values-ca/strings.xml b/java/res/values-ca/strings.xml
index d791452..ad3c92e 100644
--- a/java/res/values-ca/strings.xml
+++ b/java/res/values-ca/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Connecta un auricular per escoltar les claus de la contrasenya en veu alta."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"El text actual és %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"No s\'ha introduït cap text"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> corregeix <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> per <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> aplica correccions automàtiques"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Clau de codi %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Maj"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Maj activat (pica per desactivar)"</string>
diff --git a/java/res/values-cs/strings.xml b/java/res/values-cs/strings.xml
index 2cd159d..930d305 100644
--- a/java/res/values-cs/strings.xml
+++ b/java/res/values-cs/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Chcete-li slyšet, které klávesy jste při zadávání hesla stiskli, připojte sluchátka."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Aktuální text je %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Není zadán žádný text"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"Klávesou <xliff:g id="KEY">%1$s</xliff:g> opravíte <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> na <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"Klávesa <xliff:g id="KEY">%1$s</xliff:g> provádí automatickou opravu"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Kód klávesy %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Klávesa Shift je zapnutá (vypnete ji klepnutím)."</string>
diff --git a/java/res/values-da/strings.xml b/java/res/values-da/strings.xml
index 6c9d9d7..93d5c4e 100644
--- a/java/res/values-da/strings.xml
+++ b/java/res/values-da/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Tilslut et headset for at høre indtastningen blive læst højt ved angivelse af adgangskode."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Nuværende tekst er %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Der er ingen indtastet tekst"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> retter <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> til <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> udfører automatisk rettelse"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Tastekode %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift-tast"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Skift er slået til (tryk for at deaktivere)"</string>
@@ -194,7 +192,7 @@
     <string name="setup_step3_action" msgid="600879797256942259">"Konfigurer flere sprog"</string>
     <string name="setup_finish_action" msgid="276559243409465389">"Afslut"</string>
     <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Vis appikon"</string>
-    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Vis appikon på applikationsliste"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Vis appikon på applisten"</string>
     <string name="app_name" msgid="6320102637491234792">"Dictionary Provider"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Dictionary Provider"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Ordbogstjeneste"</string>
diff --git a/java/res/values-de/strings.xml b/java/res/values-de/strings.xml
index 1a4e385..ac51926 100644
--- a/java/res/values-de/strings.xml
+++ b/java/res/values-de/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Schließen Sie ein Headset an, um das Passwort gesprochen zu hören."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Aktueller Text lautet %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Kein Text eingegeben"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"Mit <xliff:g id="KEY">%1$s</xliff:g> wird <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> zu <xliff:g id="CORRECTED">%3$s</xliff:g> korrigiert."</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"Mit <xliff:g id="KEY">%1$s</xliff:g> erfolgt eine Autokorrektur."</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Tastencode %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Umschalttaste"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Umschalttaste aktiviert (zum Deaktivieren berühren)"</string>
diff --git a/java/res/values-el/strings.xml b/java/res/values-el/strings.xml
index c2ee2d3..54b3c3e 100644
--- a/java/res/values-el/strings.xml
+++ b/java/res/values-el/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Συνδέστε ένα σετ ακουστικών για να ακούσετε τα πλήκτρα του κωδικού πρόσβασης να εκφωνούνται δυνατά."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Το τρέχον κείμενο είναι %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Δεν υπάρχει κείμενο"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> διορθώνει το <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> σε <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> εκτελεί αυτόματη διόρθωση"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Κωδικός πλήκτρου %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Το Shift είναι ενεργοποιημένο (πατήστε για απενεργοποίηση)"</string>
diff --git a/java/res/values-en-rGB/strings.xml b/java/res/values-en-rGB/strings.xml
index 0586bcc..bdffb94 100644
--- a/java/res/values-en-rGB/strings.xml
+++ b/java/res/values-en-rGB/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Plug in a headset to hear password keys spoken aloud."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Current text is %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"No text entered"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> corrects <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> to <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> performs auto-correction"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Key code %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift on (tap to disable)"</string>
diff --git a/java/res/values-en-rIN/strings-appname.xml b/java/res/values-en-rIN/strings-appname.xml
new file mode 100644
index 0000000..5ad5eae
--- /dev/null
+++ b/java/res/values-en-rIN/strings-appname.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="5940510615957428904">"Android Keyboard (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android Spell Checker (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Android Keyboard Settings (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Android Spell Checker Settings (AOSP)"</string>
+</resources>
diff --git a/java/res/values-en-rIN/strings.xml b/java/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..bdffb94
--- /dev/null
+++ b/java/res/values-en-rIN/strings.xml
@@ -0,0 +1,246 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_input_options" msgid="3909945612939668554">"Input options"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Research Log Commands"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Look up contact names"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Spell checker uses entries from your contact list"</string>
+    <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrate on keypress"</string>
+    <string name="sound_on_keypress" msgid="6093592297198243644">"Sound on keypress"</string>
+    <string name="popup_on_keypress" msgid="123894815723512944">"Pop-up on key press"</string>
+    <string name="general_category" msgid="1859088467017573195">"General"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Text correction"</string>
+    <string name="gesture_typing_category" msgid="497263612130532630">"Gesture typing"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Other options"</string>
+    <string name="advanced_settings" msgid="362895144495591463">"Advanced settings"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Options for experts"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Switch to other input methods"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Language switch key also covers other input methods"</string>
+    <string name="show_language_switch_key" msgid="5915478828318774384">"Language switch key"</string>
+    <string name="show_language_switch_key_summary" msgid="7343403647474265713">"Show when multiple input languages are enabled"</string>
+    <string name="sliding_key_input_preview" msgid="6604262359510068370">"Show slide indicator"</string>
+    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"Display visual cue while sliding from Shift or Symbol keys"</string>
+    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Key pop-up dismiss delay"</string>
+    <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"No delay"</string>
+    <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Default"</string>
+    <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"System default"</string>
+    <string name="use_contacts_dict" msgid="4435317977804180815">"Suggest Contact names"</string>
+    <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Use names from Contacts for suggestions and corrections"</string>
+    <string name="use_double_space_period" msgid="8781529969425082860">"Double-space full stop"</string>
+    <string name="use_double_space_period_summary" msgid="6532892187247952799">"Double tap on spacebar inserts a full stop followed by a space"</string>
+    <string name="auto_cap" msgid="1719746674854628252">"Auto-capitalisation"</string>
+    <string name="auto_cap_summary" msgid="7934452761022946874">"Capitalise the first word of each sentence"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Personal dictionary"</string>
+    <string name="configure_dictionaries_title" msgid="4238652338556902049">"Add-on dictionaries"</string>
+    <string name="main_dictionary" msgid="4798763781818361168">"Main dictionary"</string>
+    <string name="prefs_show_suggestions" msgid="8026799663445531637">"Show correction suggestions"</string>
+    <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"Display suggested words while typing"</string>
+    <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Always show"</string>
+    <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Show in portrait mode"</string>
+    <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Always hide"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Block offensive words"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Do not suggest potentially offensive words"</string>
+    <string name="auto_correction" msgid="7630720885194996950">"Auto-correction"</string>
+    <string name="auto_correction_summary" msgid="5625751551134658006">"Correct mistyped words automatically with spacebar and punctuation"</string>
+    <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Off"</string>
+    <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Modest"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Aggressive"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Very aggressive"</string>
+    <string name="bigram_prediction" msgid="1084449187723948550">"Next-word suggestions"</string>
+    <string name="bigram_prediction_summary" msgid="3896362682751109677">"Use the previous word when making suggestions"</string>
+    <string name="gesture_input" msgid="826951152254563827">"Enable gesture typing"</string>
+    <string name="gesture_input_summary" msgid="9180350639305731231">"Input a word by sliding through the letters"</string>
+    <string name="gesture_preview_trail" msgid="3802333369335722221">"Show gesture trail"</string>
+    <string name="gesture_floating_preview_text" msgid="4443240334739381053">"Dynamic floating preview"</string>
+    <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"See the suggested word while gesturing"</string>
+    <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Saved"</string>
+    <string name="label_go_key" msgid="1635148082137219148">"Go"</string>
+    <string name="label_next_key" msgid="362972844525672568">"Next"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Prev"</string>
+    <string name="label_done_key" msgid="2441578748772529288">"Done"</string>
+    <string name="label_send_key" msgid="2815056534433717444">"Send"</string>
+    <string name="label_pause_key" msgid="181098308428035340">"Pause"</string>
+    <string name="label_wait_key" msgid="6402152600878093134">"Wait"</string>
+    <string name="spoken_use_headphones" msgid="896961781287283493">"Plug in a headset to hear password keys spoken aloud."</string>
+    <string name="spoken_current_text_is" msgid="2485723011272583845">"Current text is %s"</string>
+    <string name="spoken_no_text_entered" msgid="7479685225597344496">"No text entered"</string>
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> corrects <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> to <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> performs auto-correction"</string>
+    <string name="spoken_description_unknown" msgid="3197434010402179157">"Key code %d"</string>
+    <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift on (tap to disable)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock on (tap to disable)"</string>
+    <string name="spoken_description_delete" msgid="8740376944276199801">"Delete"</string>
+    <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Symbols"</string>
+    <string name="spoken_description_to_alpha" msgid="23129338819771807">"Letters"</string>
+    <string name="spoken_description_to_numeric" msgid="591752092685161732">"Numbers"</string>
+    <string name="spoken_description_settings" msgid="4627462689603838099">"Settings"</string>
+    <string name="spoken_description_tab" msgid="2667716002663482248">"Tab"</string>
+    <string name="spoken_description_space" msgid="2582521050049860859">"Space"</string>
+    <string name="spoken_description_mic" msgid="615536748882611950">"Voice input"</string>
+    <string name="spoken_description_smiley" msgid="2256309826200113918">"Smiley face"</string>
+    <string name="spoken_description_return" msgid="8178083177238315647">"Return"</string>
+    <string name="spoken_description_search" msgid="1247236163755920808">"Search"</string>
+    <string name="spoken_description_dot" msgid="40711082435231673">"Dot"</string>
+    <string name="spoken_description_language_switch" msgid="5507091328222331316">"Switch language"</string>
+    <string name="spoken_description_action_next" msgid="8636078276664150324">"Next"</string>
+    <string name="spoken_description_action_previous" msgid="800872415009336208">"Previous"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift enabled"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock enabled"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift disabled"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Symbols mode"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Letters mode"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Phone mode"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Phone symbols mode"</string>
+    <string name="announce_keyboard_hidden" msgid="8718927835531429807">"Keyboard hidden"</string>
+    <string name="announce_keyboard_mode" msgid="4729081055438508321">"Showing <xliff:g id="MODE">%s</xliff:g> keyboard"</string>
+    <string name="keyboard_mode_date" msgid="3137520166817128102">"date"</string>
+    <string name="keyboard_mode_date_time" msgid="339593358488851072">"date and time"</string>
+    <string name="keyboard_mode_email" msgid="6216248078128294262">"email"</string>
+    <string name="keyboard_mode_im" msgid="1137405089766557048">"messaging"</string>
+    <string name="keyboard_mode_number" msgid="7991623440699957069">"number"</string>
+    <string name="keyboard_mode_phone" msgid="6851627527401433229">"phone"</string>
+    <string name="keyboard_mode_text" msgid="6479436687899701619">"text"</string>
+    <string name="keyboard_mode_time" msgid="4381856885582143277">"time"</string>
+    <string name="keyboard_mode_url" msgid="1519819835514911218">"URL"</string>
+    <string name="voice_input" msgid="3583258583521397548">"Voice input key"</string>
+    <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"On main keyboard"</string>
+    <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"On symbols keyboard"</string>
+    <string name="voice_input_modes_off" msgid="3745699748218082014">"Off"</string>
+    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Mic on main keyboard"</string>
+    <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mic on symbols keyboard"</string>
+    <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Voice input is disabled"</string>
+    <string name="configure_input_method" msgid="373356270290742459">"Configure input methods"</string>
+    <string name="language_selection_title" msgid="1651299598555326750">"Input languages"</string>
+    <string name="send_feedback" msgid="1780431884109392046">"Send feedback"</string>
+    <string name="select_language" msgid="3693815588777926848">"Input languages"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Touch again to save"</string>
+    <string name="has_dictionary" msgid="6071847973466625007">"Dictionary available"</string>
+    <string name="prefs_enable_log" msgid="6620424505072963557">"Enable user feedback"</string>
+    <string name="prefs_description_log" msgid="7525225584555429211">"Help improve this input method editor by automatically sending usage statistics and crash reports"</string>
+    <string name="keyboard_layout" msgid="8451164783510487501">"Keyboard theme"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"English (UK)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"English (US)"</string>
+    <string name="subtype_es_US" msgid="5583145191430180200">"Spanish (US)"</string>
+    <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"English (UK) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"English (US) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Spanish (US) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (Traditional)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"No language (Alphabet)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alphabet (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alphabet (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alphabet (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alphabet (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alphabet (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alphabet (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
+    <string name="custom_input_styles_title" msgid="8429952441821251512">"Customised input styles"</string>
+    <string name="add_style" msgid="6163126614514489951">"Add style"</string>
+    <string name="add" msgid="8299699805688017798">"Add"</string>
+    <string name="remove" msgid="4486081658752944606">"Remove"</string>
+    <string name="save" msgid="7646738597196767214">"Save"</string>
+    <string name="subtype_locale" msgid="8576443440738143764">"Language"</string>
+    <string name="keyboard_layout_set" msgid="4309233698194565609">"Layout"</string>
+    <string name="custom_input_style_note_message" msgid="8826731320846363423">"Your customised input style needs to be enabled before you start using it. Do you want to enable it now?"</string>
+    <string name="enable" msgid="5031294444630523247">"Enable"</string>
+    <string name="not_now" msgid="6172462888202790482">"Not now"</string>
+    <string name="custom_input_style_already_exists" msgid="8008728952215449707">"The same input style already exists: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
+    <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Usability study mode"</string>
+    <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"Key long press delay"</string>
+    <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"Keypress vibration duration"</string>
+    <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"Keypress sound volume"</string>
+    <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Read external dictionary file"</string>
+    <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"No dictionary files in the Downloads folder"</string>
+    <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Select a dictionary file to install"</string>
+    <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Really install this file for <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
+    <string name="error" msgid="8940763624668513648">"There was an error"</string>
+    <string name="button_default" msgid="3988017840431881491">"Default"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Welcome to <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"with Gesture Typing"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Get started"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Next step"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Setting up <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Enable <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Please tick \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" in your Language &amp; input settings. This will authorise it to run on your device."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> is already enabled in your Language &amp; input settings, so this step is done. On to the next one!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Enable in Settings"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Switch to <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Next, select \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" as your active text-input method."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Switch input methods"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Congratulations, you\'re all set!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Now you can type in all your favourite apps with <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Configure additional languages"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Finished"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Show app icon"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Display application icon in the launcher"</string>
+    <string name="app_name" msgid="6320102637491234792">"Dictionary Provider"</string>
+    <string name="dictionary_provider_name" msgid="3027315045397363079">"Dictionary Provider"</string>
+    <string name="dictionary_service_name" msgid="6237472350693511448">"Dictionary Service"</string>
+    <string name="download_description" msgid="6014835283119198591">"Dictionary update information"</string>
+    <string name="dictionary_settings_title" msgid="8091417676045693313">"Add-on dictionaries"</string>
+    <string name="dictionary_install_over_metered_network_prompt" msgid="3587517870006332980">"Dictionary available"</string>
+    <string name="dictionary_settings_summary" msgid="5305694987799824349">"Settings for dictionaries"</string>
+    <string name="user_dictionaries" msgid="3582332055892252845">"User dictionaries"</string>
+    <string name="default_user_dict_pref_name" msgid="1625055720489280530">"User dictionary"</string>
+    <string name="dictionary_available" msgid="4728975345815214218">"Dictionary available"</string>
+    <string name="dictionary_downloading" msgid="2982650524622620983">"Currently downloading"</string>
+    <string name="dictionary_installed" msgid="8081558343559342962">"Installed"</string>
+    <string name="dictionary_disabled" msgid="8950383219564621762">"Installed, disabled"</string>
+    <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"Problem connecting to dictionary service"</string>
+    <string name="no_dictionaries_available" msgid="8039920716566132611">"No dictionaries available"</string>
+    <string name="check_for_updates_now" msgid="8087688440916388581">"Refresh"</string>
+    <string name="last_update" msgid="730467549913588780">"Last updated"</string>
+    <string name="message_updating" msgid="4457761393932375219">"Checking for updates"</string>
+    <string name="message_loading" msgid="8689096636874758814">"Loading..."</string>
+    <string name="main_dict_description" msgid="3072821352793492143">"Main dictionary"</string>
+    <string name="cancel" msgid="6830980399865683324">"Cancel"</string>
+    <string name="install_dict" msgid="180852772562189365">"Install"</string>
+    <string name="cancel_download_dict" msgid="7843340278507019303">"Cancel"</string>
+    <string name="delete_dict" msgid="756853268088330054">"Delete"</string>
+    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"The selected language on your mobile device has an available dictionary.&lt;br/&gt; We recommend &lt;b&gt;downloading&lt;/b&gt; the <xliff:g id="LANGUAGE">%1$s</xliff:g> dictionary to improve your typing experience.&lt;br/&gt; &lt;br/&gt; The download could take a minute or two over 3G. Charges may apply if you don\'t have an &lt;b&gt;unlimited data plan&lt;/b&gt;.&lt;br/&gt; If you are not sure which data plan you have, we recommend finding a Wi-Fi connection to start the download automatically.&lt;br/&gt; &lt;br/&gt; Tip: You can download and remove dictionaries by going to &lt;b&gt;Language &amp; input&lt;/b&gt; in the &lt;b&gt;Settings&lt;/b&gt; menu of your mobile device."</string>
+    <string name="download_over_metered" msgid="1643065851159409546">"Download now (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g>MB)"</string>
+    <string name="do_not_download_over_metered" msgid="2176209579313941583">"Download over Wi-Fi"</string>
+    <string name="dict_available_notification_title" msgid="6514288591959117288">"A dictionary is available for <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
+    <string name="dict_available_notification_description" msgid="1075194169443163487">"Press to review and download"</string>
+    <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Downloading: suggestions for <xliff:g id="LANGUAGE">%1$s</xliff:g> will be ready soon."</string>
+    <string name="version_text" msgid="2715354215568469385">"Version <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Add"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Add to dictionary"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Phrase"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"More options"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Fewer options"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Word:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Shortcut:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Language:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Type a word"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Optional shortcut"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Edit word"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Edit"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Delete"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"You don\'t have any words in the user dictionary. Add a word by touching the Add (+) button."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"For all languages"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"More languages…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Delete"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+</resources>
diff --git a/java/res/values-es-rUS/strings.xml b/java/res/values-es-rUS/strings.xml
index 42c3932..2d7872a 100644
--- a/java/res/values-es-rUS/strings.xml
+++ b/java/res/values-es-rUS/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Enchufa tus auriculares para escuchar en voz alta qué teclas presionas al ingresar una contraseña."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"El texto actual es %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"No se ingresó texto."</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"La tecla <xliff:g id="KEY">%1$s</xliff:g> corrige <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> por <xliff:g id="CORRECTED">%3$s</xliff:g>."</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"La tecla <xliff:g id="KEY">%1$s</xliff:g> corrige automáticamente."</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Clave de código %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Mayús"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Se activó el modo Mayúscula (toca para desactivarlo)."</string>
diff --git a/java/res/values-es/strings.xml b/java/res/values-es/strings.xml
index 4d72799..4dfc57f 100644
--- a/java/res/values-es/strings.xml
+++ b/java/res/values-es/strings.xml
@@ -62,7 +62,7 @@
     <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"No sugerir palabras potencialmente ofensivas"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Autocorrección"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Pulsar la tecla de espacio o punto para corregir errores"</string>
-    <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desactivada"</string>
+    <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"No"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Parcial"</string>
     <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Total"</string>
     <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Casi total"</string>
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Conecta un auricular para escuchar las contraseñas en voz alta."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"El texto actual es %s."</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"No se ha introducido texto."</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"La tecla <xliff:g id="KEY">%1$s</xliff:g> corrige <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> a <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"La tecla <xliff:g id="KEY">%1$s</xliff:g> corrige automáticamente"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Código del teclado: %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Mayús"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Mayúsculas activadas (tocar para inhabilitar)"</string>
@@ -128,7 +126,7 @@
     <string name="voice_input" msgid="3583258583521397548">"Tecla de entrada de voz"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"En teclado principal"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"En teclado de símbolos"</string>
-    <string name="voice_input_modes_off" msgid="3745699748218082014">"Desactivada"</string>
+    <string name="voice_input_modes_off" msgid="3745699748218082014">"No"</string>
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Micrófono en teclado principal"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Micrófono en teclado de símbolos"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Entrada de voz inhabilitada"</string>
diff --git a/java/res/values-et-rEE/strings.xml b/java/res/values-et-rEE/strings.xml
index 5706fa1..3a27a88 100644
--- a/java/res/values-et-rEE/strings.xml
+++ b/java/res/values-et-rEE/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Ühendage peakomplekt, et kuulata paroole."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Praegune tekst on %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Teksti ei ole sisestatud"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"Klahvi <xliff:g id="KEY">%1$s</xliff:g> vajutamisel parandatakse sõna <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> sõnaks <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"Klahvi <xliff:g id="KEY">%1$s</xliff:g> vajutamisel tehakse automaatne parandus"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Klahvi kood: %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Tõstuklahv"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Tõstuklahv sees (puudutage keelamiseks)"</string>
diff --git a/java/res/values-fa/strings-appname.xml b/java/res/values-fa/strings-appname.xml
index 38234c2..f6c9f54 100644
--- a/java/res/values-fa/strings-appname.xml
+++ b/java/res/values-fa/strings-appname.xml
@@ -20,8 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="5940510615957428904">"صفحه کلید Android ‏(AOSP)"</string>
-    <string name="spell_checker_service_name" msgid="1254221805440242662">"غلط‌گیر Android ‏(AOSP)"</string>
-    <string name="english_ime_settings" msgid="5760361067176802794">"تنظیمات صفحه کلید Android ‏(AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="6123949487832861885">"تنظیمات غلط‌گیر Android ‏(AOSP)"</string>
+    <string name="english_ime_name" msgid="5940510615957428904">"‏صفحه کلید Android ‏(AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"‏غلط‌گیر Android ‏(AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"‏تنظیمات صفحه کلید Android ‏(AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"‏تنظیمات غلط‌گیر Android ‏(AOSP)"</string>
 </resources>
diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml
index c03e440..0c194f7 100644
--- a/java/res/values-fa/strings.xml
+++ b/java/res/values-fa/strings.xml
@@ -38,7 +38,7 @@
     <string name="show_language_switch_key" msgid="5915478828318774384">"کلید تغییر زبان"</string>
     <string name="show_language_switch_key_summary" msgid="7343403647474265713">"وقتی چند زبان ورودی فعال است نشان داده شود"</string>
     <string name="sliding_key_input_preview" msgid="6604262359510068370">"نمایش نشانگر لغزاندن"</string>
-    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"نمایش نشان بصری، هنگام لغزاندن کلیدهای Shift یا نماد"</string>
+    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"‏نمایش نشان بصری، هنگام لغزاندن کلیدهای Shift یا نماد"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"تأخیر در رد کردن کلید نمایشی"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"بدون تأخیر"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"پیش‌فرض"</string>
@@ -86,16 +86,14 @@
     <!-- no translation found for spoken_current_text_is (2485723011272583845) -->
     <skip />
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"متنی وارد نشده است"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g>، ‏<xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> را به <xliff:g id="CORRECTED">%3$s</xliff:g> تصحیح می‌کند"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> تصحیح خودکار را انجام می‌دهد"</string>
     <!-- String.format failed for translation -->
     <!-- no translation found for spoken_description_unknown (3197434010402179157) -->
     <skip />
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift فعال است (برای غیرفعال کردن ضربه بزنید)"</string>
-    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps Lock روشن است (برای غیرفعال کردن ضربه بزنید)"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"‏Shift فعال است (برای غیرفعال کردن ضربه بزنید)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"‏Caps Lock روشن است (برای غیرفعال کردن ضربه بزنید)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"Delete"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"نمادها"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"حروف"</string>
@@ -111,9 +109,9 @@
     <string name="spoken_description_language_switch" msgid="5507091328222331316">"تغییر زبان"</string>
     <string name="spoken_description_action_next" msgid="8636078276664150324">"بعدی"</string>
     <string name="spoken_description_action_previous" msgid="800872415009336208">"قبلی"</string>
-    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift فعال است"</string>
-    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock فعال شد"</string>
-    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift غیرفعال است"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"‏Shift فعال است"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"‏Caps lock فعال شد"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"‏Shift غیرفعال است"</string>
     <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"حالت نمادها"</string>
     <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"حالت حروف"</string>
     <string name="spoken_description_mode_phone" msgid="6520207943132026264">"حالت تلفن"</string>
@@ -153,12 +151,12 @@
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"اسپانیایی (آمریکا) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (سنتی)"</string>
     <string name="subtype_no_language" msgid="7137390094240139495">"بدون زبان (حروف الفبا)"</string>
-    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"حروف الفبا (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"حروف الفبا (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"حروف الفبا (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"حروف الفبا (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"حروف الفبا (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"حروف الفبا (PC)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"‏حروف الفبا (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"‏حروف الفبا (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"‏حروف الفبا (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"‏حروف الفبا (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"‏حروف الفبا (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"‏حروف الفبا (PC)"</string>
     <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"سبک‌های ورودی سفارشی"</string>
     <string name="add_style" msgid="6163126614514489951">"افزودن سبک"</string>
@@ -223,9 +221,9 @@
     <string name="install_dict" msgid="180852772562189365">"نصب"</string>
     <string name="cancel_download_dict" msgid="7843340278507019303">"لغو"</string>
     <string name="delete_dict" msgid="756853268088330054">"حذف"</string>
-    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"برای زبان انتخاب شده در دستگاه همراه شما فرهنگ لغتی موجود است.&lt;br/&gt; توصیه می‌کنیم فرهنگ لغت <xliff:g id="LANGUAGE">%1$s</xliff:g> را &lt;b&gt;دانلود کنید&lt;/b&gt; تا بهتر تایپ کنید.&lt;br/&gt; &lt;br/&gt; دانلود از طریق 3G ممکن است چند لحظه طول بکشد. اگر &lt;b&gt;طرح داده نامحدود&lt;/b&gt; نداشته باشید ممکن است برایتان هزینه داشته باشد.&lt;br/&gt; اگر مطمئن نیستید طرح داده شما چیست٬ توصیه می‌کنیم یک اتصال Wi-Fi پیدا کنید تا دانلود بطور خودکار شروع شود.&lt;br/&gt; &lt;br/&gt; نکته: می‌توانید فرهنگ لغت را با رفتن به منوی &lt;b&gt;زبان و ورودی&lt;/b&gt; در &lt;b&gt;تنظیمات&lt;/b&gt; در دستگاه همراه خود دانلود و حذف کنید."</string>
+    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"‏برای زبان انتخاب شده در دستگاه همراه شما فرهنگ لغتی موجود است.&lt;br/&gt; توصیه می‌کنیم فرهنگ لغت <xliff:g id="LANGUAGE">%1$s</xliff:g> را &lt;b&gt;دانلود کنید&lt;/b&gt; تا بهتر تایپ کنید.&lt;br/&gt; &lt;br/&gt; دانلود از طریق 3G ممکن است چند لحظه طول بکشد. اگر &lt;b&gt;طرح داده نامحدود&lt;/b&gt; نداشته باشید ممکن است برایتان هزینه داشته باشد.&lt;br/&gt; اگر مطمئن نیستید طرح داده شما چیست٬ توصیه می‌کنیم یک اتصال Wi-Fi پیدا کنید تا دانلود بطور خودکار شروع شود.&lt;br/&gt; &lt;br/&gt; نکته: می‌توانید فرهنگ لغت را با رفتن به منوی &lt;b&gt;زبان و ورودی&lt;/b&gt; در &lt;b&gt;تنظیمات&lt;/b&gt; در دستگاه همراه خود دانلود و حذف کنید."</string>
     <string name="download_over_metered" msgid="1643065851159409546">"هم‌اکنون بارگیری شود (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g> مگابایت)"</string>
-    <string name="do_not_download_over_metered" msgid="2176209579313941583">"دانلود ازطریق Wi-Fi"</string>
+    <string name="do_not_download_over_metered" msgid="2176209579313941583">"‏دانلود ازطریق Wi-Fi"</string>
     <string name="dict_available_notification_title" msgid="6514288591959117288">"یک فرهنگ لغت برای <xliff:g id="LANGUAGE">%1$s</xliff:g> موجود است"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"برای مرور و دانلود فشار دهید"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"دانلود لغات پیشنهادی برای <xliff:g id="LANGUAGE">%1$s</xliff:g> به زودی شروع می‌شود."</string>
diff --git a/java/res/values-fi/strings.xml b/java/res/values-fi/strings.xml
index 744e604..cee1b7d 100644
--- a/java/res/values-fi/strings.xml
+++ b/java/res/values-fi/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Liitä kuulokkeet, niin kuulet mitä näppäimiä painat kirjoittaessasi salasanaa."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Nykyinen teksti on %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Ei kirjoitettua tekstiä"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> korjaa sanan <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> sanaksi <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> suorittaa automaattisen korjauksen"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Näppäimen koodi %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Vaihto"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Vaihto päällä (poista käytöstä napauttamalla)"</string>
diff --git a/java/res/values-fr-rCA/strings-appname.xml b/java/res/values-fr-rCA/strings-appname.xml
new file mode 100644
index 0000000..d45e239
--- /dev/null
+++ b/java/res/values-fr-rCA/strings-appname.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="5940510615957428904">"Clavier Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Correcteur orthographique Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Paramètres du clavier Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Paramètres du correcteur orthographique Android (AOSP)"</string>
+</resources>
diff --git a/java/res/values-fr-rCA/strings.xml b/java/res/values-fr-rCA/strings.xml
index b56463e..6f33539 100644
--- a/java/res/values-fr-rCA/strings.xml
+++ b/java/res/values-fr-rCA/strings.xml
@@ -1,19 +1,246 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!-- 
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
 
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="7252517407088836577">"Clavier Android"</string>
+    <string name="english_ime_input_options" msgid="3909945612939668554">"Options de saisie"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Commandes journaux rech."</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Rechercher noms contacts"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Correcteur orthographique utilise entrées de liste de contacts."</string>
+    <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrer à chaque touche"</string>
+    <string name="sound_on_keypress" msgid="6093592297198243644">"Son à chaque touche"</string>
+    <string name="popup_on_keypress" msgid="123894815723512944">"Agrandir les caractères"</string>
+    <string name="general_category" msgid="1859088467017573195">"Général"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Correction du texte"</string>
+    <string name="gesture_typing_category" msgid="497263612130532630">"Saisie gestuelle"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Autres options"</string>
+    <string name="advanced_settings" msgid="362895144495591463">"Paramètres avancés"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Options destinées aux experts"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Autres modes de saisie"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"La touche de sélection de langue couvre d\'autres modes de saisie"</string>
+    <string name="show_language_switch_key" msgid="5915478828318774384">"Touche de sélection de langue"</string>
+    <string name="show_language_switch_key_summary" msgid="7343403647474265713">"Afficher lorsque plusieurs langues de saisie sont activées"</string>
+    <string name="sliding_key_input_preview" msgid="6604262359510068370">"Aff. indicateur saisie gestuelle"</string>
+    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"Aff. un repère visuel si l\'utilisateur appuie sur Maj ou Symboles"</string>
+    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Masquer touche agrandie"</string>
+    <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Aucun délai"</string>
+    <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Par défaut"</string>
+    <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Paramètres par défaut"</string>
+    <string name="use_contacts_dict" msgid="4435317977804180815">"Proposer noms de contacts"</string>
+    <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Utiliser des noms de contacts pour les suggestions et corrections"</string>
+    <string name="use_double_space_period" msgid="8781529969425082860">"Point et espace"</string>
+    <string name="use_double_space_period_summary" msgid="6532892187247952799">"Appuyez deux fois sur la barre d\'espace pour insérer un point et une espace"</string>
+    <string name="auto_cap" msgid="1719746674854628252">"Majuscules automatiques"</string>
+    <string name="auto_cap_summary" msgid="7934452761022946874">"Majuscule au premier mot de chaque phrase"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Dictionnaire personnel"</string>
+    <string name="configure_dictionaries_title" msgid="4238652338556902049">"Dictionnaires complémentaires"</string>
+    <string name="main_dictionary" msgid="4798763781818361168">"Dictionnaire principal"</string>
+    <string name="prefs_show_suggestions" msgid="8026799663445531637">"Suggestions de correction"</string>
+    <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"Afficher les suggestions de terme lors de la saisie"</string>
+    <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Toujours afficher"</string>
+    <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Afficher en mode Portrait"</string>
+    <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Toujours masquer"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Bloquer les termes choquants"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Pas de termes potentiellement choquants"</string>
+    <string name="auto_correction" msgid="7630720885194996950">"Correction auto"</string>
+    <string name="auto_correction_summary" msgid="5625751551134658006">"Corriger autom. orthographe (pression sur barre espace/signes ponctuation)"</string>
+    <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Désactiver"</string>
+    <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Simple"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Proactive"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Très exigeante"</string>
+    <string name="bigram_prediction" msgid="1084449187723948550">"Suggestions pour le mot suivant"</string>
+    <string name="bigram_prediction_summary" msgid="3896362682751109677">"Utiliser le mot précédent pour les suggestions"</string>
+    <string name="gesture_input" msgid="826951152254563827">"Activer la saisie gestuelle"</string>
+    <string name="gesture_input_summary" msgid="9180350639305731231">"Saisir un mot en faisant glisser le doigt sur les lettres"</string>
+    <string name="gesture_preview_trail" msgid="3802333369335722221">"Afficher le tracé du geste"</string>
+    <string name="gesture_floating_preview_text" msgid="4443240334739381053">"Aperçu flottant dynamique"</string>
+    <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"Afficher le mot suggéré lors des gestes"</string>
+    <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : enregistré"</string>
+    <string name="label_go_key" msgid="1635148082137219148">"Aller"</string>
+    <string name="label_next_key" msgid="362972844525672568">"Suivant"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Préc."</string>
+    <string name="label_done_key" msgid="2441578748772529288">"Terminé"</string>
+    <string name="label_send_key" msgid="2815056534433717444">"Envoyer"</string>
+    <string name="label_pause_key" msgid="181098308428035340">"Suspendre"</string>
+    <string name="label_wait_key" msgid="6402152600878093134">"Attendre"</string>
+    <string name="spoken_use_headphones" msgid="896961781287283493">"Branchez des écouteurs pour entendre l\'énoncé à haute voix des touches lors de la saisie du mot de passe."</string>
+    <string name="spoken_current_text_is" msgid="2485723011272583845">"Le texte actuel est %s"</string>
+    <string name="spoken_no_text_entered" msgid="7479685225597344496">"Aucun texte saisi"</string>
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"La touche <xliff:g id="KEY">%1$s</xliff:g> permet de corriger « <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> » par « <xliff:g id="CORRECTED">%3$s</xliff:g> »"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"La touche <xliff:g id="KEY">%1$s</xliff:g> permet d\'activer la correction automatique"</string>
+    <string name="spoken_description_unknown" msgid="3197434010402179157">"Code touche %d"</string>
+    <string name="spoken_description_shift" msgid="244197883292549308">"Maj"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Touche Maj activée (appuyer pour désactiver)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Verrouillage des majuscules activé (appuyer pour désactiver)"</string>
+    <string name="spoken_description_delete" msgid="8740376944276199801">"Supprimer"</string>
+    <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Symboles"</string>
+    <string name="spoken_description_to_alpha" msgid="23129338819771807">"Lettres"</string>
+    <string name="spoken_description_to_numeric" msgid="591752092685161732">"Nombres"</string>
+    <string name="spoken_description_settings" msgid="4627462689603838099">"Paramètres"</string>
+    <string name="spoken_description_tab" msgid="2667716002663482248">"Onglet"</string>
+    <string name="spoken_description_space" msgid="2582521050049860859">"Espace"</string>
+    <string name="spoken_description_mic" msgid="615536748882611950">"Saisie vocale"</string>
+    <string name="spoken_description_smiley" msgid="2256309826200113918">"Émoticône"</string>
+    <string name="spoken_description_return" msgid="8178083177238315647">"Renvoyer"</string>
+    <string name="spoken_description_search" msgid="1247236163755920808">"Rechercher"</string>
+    <string name="spoken_description_dot" msgid="40711082435231673">"Point"</string>
+    <string name="spoken_description_language_switch" msgid="5507091328222331316">"Changer de langue"</string>
+    <string name="spoken_description_action_next" msgid="8636078276664150324">"Suivant"</string>
+    <string name="spoken_description_action_previous" msgid="800872415009336208">"Précédent"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Touche Maj activée"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Verrouillage des majuscules activé"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Touche Maj désactivée"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Mode Symboles"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Mode Lettres"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Mode Téléphone"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Mode Symboles du téléphone"</string>
+    <string name="announce_keyboard_hidden" msgid="8718927835531429807">"Clavier masqué"</string>
+    <string name="announce_keyboard_mode" msgid="4729081055438508321">"Affichage du clavier <xliff:g id="MODE">%s</xliff:g>"</string>
+    <string name="keyboard_mode_date" msgid="3137520166817128102">"Date"</string>
+    <string name="keyboard_mode_date_time" msgid="339593358488851072">"Date et heure"</string>
+    <string name="keyboard_mode_email" msgid="6216248078128294262">"Courriel"</string>
+    <string name="keyboard_mode_im" msgid="1137405089766557048">"SMS/MMS"</string>
+    <string name="keyboard_mode_number" msgid="7991623440699957069">"Nombre"</string>
+    <string name="keyboard_mode_phone" msgid="6851627527401433229">"Numéro de téléphone"</string>
+    <string name="keyboard_mode_text" msgid="6479436687899701619">"Texte"</string>
+    <string name="keyboard_mode_time" msgid="4381856885582143277">"Heure"</string>
+    <string name="keyboard_mode_url" msgid="1519819835514911218">"URL"</string>
+    <string name="voice_input" msgid="3583258583521397548">"Touche de saisie vocale"</string>
+    <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Sur le clavier principal"</string>
+    <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Sur clavier symboles"</string>
+    <string name="voice_input_modes_off" msgid="3745699748218082014">"Désactiver"</string>
+    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Micro sur le clavier principal"</string>
+    <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Micro sur le clavier des symboles"</string>
+    <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Saisie vocale désactivée"</string>
+    <string name="configure_input_method" msgid="373356270290742459">"Configurer les modes de saisie"</string>
+    <string name="language_selection_title" msgid="1651299598555326750">"Langues de saisie"</string>
+    <string name="send_feedback" msgid="1780431884109392046">"Envoyer des commentaires"</string>
+    <string name="select_language" msgid="3693815588777926848">"Langues de saisie"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Appuyer de nouveau pour enregistrer"</string>
+    <string name="has_dictionary" msgid="6071847973466625007">"Dictionnaire disponible"</string>
+    <string name="prefs_enable_log" msgid="6620424505072963557">"Autoriser les commentaires des utilisateurs"</string>
+    <string name="prefs_description_log" msgid="7525225584555429211">"Contribuer à l\'amélioration de cet éditeur du mode de saisie grâce à l\'envoi automatique de statistiques d\'utilisation et de rapports d\'erreur"</string>
+    <string name="keyboard_layout" msgid="8451164783510487501">"Thème du clavier"</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_es_US" msgid="5583145191430180200">"Espagnol (États-Unis)"</string>
+    <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Anglais (Royaume-Uni) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Anglais (États-Unis) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Espagnol, États-Unis (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (traditionnel)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Aucune langue (alphabet)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Alphabet latin (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Alphabet latin (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Alphabet latin (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Alphabet latin (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Alphabet latin (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Alphabet latin (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
+    <string name="custom_input_styles_title" msgid="8429952441821251512">"Styles saisie personnalisés"</string>
+    <string name="add_style" msgid="6163126614514489951">"Ajouter style"</string>
+    <string name="add" msgid="8299699805688017798">"Ajouter"</string>
+    <string name="remove" msgid="4486081658752944606">"Supprimer"</string>
+    <string name="save" msgid="7646738597196767214">"Enregistrer"</string>
+    <string name="subtype_locale" msgid="8576443440738143764">"Langue"</string>
+    <string name="keyboard_layout_set" msgid="4309233698194565609">"Disposition"</string>
+    <string name="custom_input_style_note_message" msgid="8826731320846363423">"Vous devez activer votre style de saisie personnalisé avant de l\'utiliser. Voulez-vous le faire maintenant ?"</string>
+    <string name="enable" msgid="5031294444630523247">"Activer"</string>
+    <string name="not_now" msgid="6172462888202790482">"Pas maintenant"</string>
+    <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Le style de saisie suivant existe déjà : <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>."</string>
+    <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Mode d\'étude de l\'utilisabilité"</string>
+    <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"Délai appui prolongé sur touche"</string>
+    <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"Durée vibration press. touche"</string>
+    <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"Volume pression de touche"</string>
+    <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Lire un fichier de dictionnaire externe"</string>
+    <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Aucun fichier de dictionnaire dans le dossier \"Téléchargements\""</string>
+    <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Sélectionner un fichier de dictionnaire à installer"</string>
+    <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Installer ce fichier pour la langue \"<xliff:g id="LOCALE_NAME">%s</xliff:g>\" ?"</string>
+    <string name="error" msgid="8940763624668513648">"Une erreur s\'est produite"</string>
+    <string name="button_default" msgid="3988017840431881491">"Par défaut"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Bienvenue dans <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"avec la saisie gestuelle"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Commencer"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Étape suivante"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Configurer <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Activer <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Sous \"Langue et saisie\", cochez \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" pour autoriser son exécution sur l\'appareil."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"L\'application \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" est déjà activée dans vos paramètres \"Langue et saisie\". Passez à l\'étape suivante."</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Activer le clavier dans les paramètres"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Basculer vers <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Sélectionnez ensuite \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" comme mode de saisie actif."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Changer de mode de saisie"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Félicitations, l\'opération est terminée"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Avec <xliff:g id="APPLICATION_NAME">%s</xliff:g>, vous pouvez saisir du texte dans toutes vos applications préférées."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Configurer des langues supplémentaires"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"OK"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Afficher icône application"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Afficher l\'icône de l\'application dans le lanceur"</string>
+    <string name="app_name" msgid="6320102637491234792">"Fournisseur de dictionnaires"</string>
+    <string name="dictionary_provider_name" msgid="3027315045397363079">"Fournisseur de dictionnaires"</string>
+    <string name="dictionary_service_name" msgid="6237472350693511448">"Service de dictionnaires"</string>
+    <string name="download_description" msgid="6014835283119198591">"Informations relatives à la mise à jour des dictionnaires"</string>
+    <string name="dictionary_settings_title" msgid="8091417676045693313">"Dictionnaires complémentaires"</string>
+    <string name="dictionary_install_over_metered_network_prompt" msgid="3587517870006332980">"Dictionnaire disponible"</string>
+    <string name="dictionary_settings_summary" msgid="5305694987799824349">"Paramètres des dictionnaires"</string>
+    <string name="user_dictionaries" msgid="3582332055892252845">"Dictionnaires personnels"</string>
+    <string name="default_user_dict_pref_name" msgid="1625055720489280530">"Dictionnaire personnel"</string>
+    <string name="dictionary_available" msgid="4728975345815214218">"Dictionnaire disponible"</string>
+    <string name="dictionary_downloading" msgid="2982650524622620983">"Téléchargement en cours…"</string>
+    <string name="dictionary_installed" msgid="8081558343559342962">"Installé"</string>
+    <string name="dictionary_disabled" msgid="8950383219564621762">"Installé, désactivé"</string>
+    <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"Pas de service dico."</string>
+    <string name="no_dictionaries_available" msgid="8039920716566132611">"Aucun dictionnaire"</string>
+    <string name="check_for_updates_now" msgid="8087688440916388581">"Actualiser"</string>
+    <string name="last_update" msgid="730467549913588780">"Dernière mise à jour"</string>
+    <string name="message_updating" msgid="4457761393932375219">"Recherche de mises à jour en cours…"</string>
+    <string name="message_loading" msgid="8689096636874758814">"Chargement en cours..."</string>
+    <string name="main_dict_description" msgid="3072821352793492143">"Dictionnaire principal"</string>
+    <string name="cancel" msgid="6830980399865683324">"Annuler"</string>
+    <string name="install_dict" msgid="180852772562189365">"Installer"</string>
+    <string name="cancel_download_dict" msgid="7843340278507019303">"Annuler"</string>
+    <string name="delete_dict" msgid="756853268088330054">"Supprimer"</string>
+    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"Un dictionnaire est disponible pour la langue sélectionnée sur votre appareil mobile.&lt;br/&gt; Nous vous invitons à &lt;b&gt;télécharger&lt;/b&gt; le dictionnaire <xliff:g id="LANGUAGE">%1$s</xliff:g> pour faciliter votre saisie.&lt;br/&gt; &lt;br/&gt; Le téléchargement peut prendre une à deux minutes via une connexion 3G. Des frais peuvent s\'appliquer si vous ne disposez pas d\'un &lt;b&gt;forfait Internet illimité&lt;/b&gt;.&lt;br/&gt; Si vous n\'êtes pas sûr de votre forfait, nous vous conseillons d\'utiliser une connexion Wi-Fi pour lancer automatiquement le téléchargement.&lt;br/&gt; &lt;br/&gt; Astuce : Vous pouvez télécharger et supprimer des dictionnaires dans la section &lt;b&gt;Langue et saisie&lt;/b&gt; du menu &lt;b&gt;Paramètres&lt;/b&gt; de votre appareil mobile."</string>
+    <string name="download_over_metered" msgid="1643065851159409546">"Télécharger (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g> Mo)"</string>
+    <string name="do_not_download_over_metered" msgid="2176209579313941583">"Télécharger via Wi-Fi"</string>
+    <string name="dict_available_notification_title" msgid="6514288591959117288">"Un dictionnaire est disponible en <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
+    <string name="dict_available_notification_description" msgid="1075194169443163487">"Appuyez ici pour consulter et télécharger le dictionnaire."</string>
+    <string name="toast_downloading_suggestions" msgid="1313027353588566660">"En cours de téléchargement. Des suggestions pour la langue suivante seront bientôt disponibles : <xliff:g id="LANGUAGE">%1$s</xliff:g>."</string>
+    <string name="version_text" msgid="2715354215568469385">"Version <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Ajouter"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Ajouter au dictionnaire"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Expression"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Plus d\'options"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Moins d\'options"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Mot :"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Raccourci :"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Langue :"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Saisissez un mot"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Raccourci facultatif"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Modifier le mot"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Modifier"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Supprimer"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Votre dictionnaire personnel ne contient aucun mot. Ajoutez un mot en appuyant sur le bouton d\'ajout (\"+\")."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Pour toutes les langues"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Plus de langues…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Supprimer"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-fr/strings.xml b/java/res/values-fr/strings.xml
index 5fedd4e..b850df9 100644
--- a/java/res/values-fr/strings.xml
+++ b/java/res/values-fr/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Branchez des écouteurs pour entendre l\'énoncé à haute voix des touches lors de la saisie du mot de passe."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Le texte actuel est %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Aucun texte saisi"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"La touche <xliff:g id="KEY">%1$s</xliff:g> permet de remplacer \"<xliff:g id="ORIGINAL_WORD">%2$s</xliff:g>\" par \"<xliff:g id="CORRECTED">%3$s</xliff:g>\"."</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"La touche <xliff:g id="KEY">%1$s</xliff:g> permet d\'activer la correction automatique."</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Code touche %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Maj"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Touche Maj activée (appuyer pour désactiver)"</string>
diff --git a/java/res/values-hi/strings.xml b/java/res/values-hi/strings.xml
index 3b3228a..6d2d5d2 100644
--- a/java/res/values-hi/strings.xml
+++ b/java/res/values-hi/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"ज़ोर से बोली गई पासवर्ड कुंजियां सुनने के लिए हेडसेट प्‍लग इन करें."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"वर्तमान पाठ %s है"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"कोई पाठ दर्ज नहीं किया गया"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> को सुधार कर <xliff:g id="CORRECTED">%3$s</xliff:g> करता है"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> स्वत: सुधार करता है"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"कुंजी कोड %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"शिफ़्ट"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift चालू (अक्षम करने के लिए टैप करें)"</string>
diff --git a/java/res/values-hr/strings.xml b/java/res/values-hr/strings.xml
index caef954..ee2e68a 100644
--- a/java/res/values-hr/strings.xml
+++ b/java/res/values-hr/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Priključite slušalice da biste čuli tipke zaporke izgovorene naglas."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Trenutačni tekst je %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Nije unesen tekst"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> ispravlja <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> u <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> vrši samoispravljanje"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Kôd tipke %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Uključena tipka Shift (dotaknite da onemogućite)"</string>
diff --git a/java/res/values-hu/strings.xml b/java/res/values-hu/strings.xml
index 3b0ee47..71fce94 100644
--- a/java/res/values-hu/strings.xml
+++ b/java/res/values-hu/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Csatlakoztasson egy headsetet, ha hallani szeretné a jelszót felolvasva."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"A jelenlegi szöveg: %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Szöveg nincs megadva"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> billentyű: <xliff:g id="CORRECTED">%3$s</xliff:g> szóra javítja a következőt: <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> billentyű automatikus javítást végez"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Billentyűkód: %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift be van kapcsolva (érintse meg a kikapcsoláshoz)"</string>
diff --git a/java/res/values-hy-rAM/strings-appname.xml b/java/res/values-hy-rAM/strings-appname.xml
new file mode 100644
index 0000000..dc3c0c6
--- /dev/null
+++ b/java/res/values-hy-rAM/strings-appname.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="5940510615957428904">"Android Ստեղնաշար (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android տառասխալների ուղղիչ (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Android ստեղնաշարի կարգավորումներ (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Android տառասխալների ուղղիչի կարգավորումներ (AOSP)"</string>
+</resources>
diff --git a/java/res/values-hy-rAM/strings.xml b/java/res/values-hy-rAM/strings.xml
new file mode 100644
index 0000000..b7ca149
--- /dev/null
+++ b/java/res/values-hy-rAM/strings.xml
@@ -0,0 +1,246 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_input_options" msgid="3909945612939668554">"Ներածման ընտրանքներ"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Հետազոտական գրառումների հրամաններ"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Փնտրել կոնտակտային անուններ"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Տառասխալների ուղղիչն օգտագործում է ձեր կոնտակտների ցանկի տվյալները"</string>
+    <string name="vibrate_on_keypress" msgid="5258079494276955460">"Թրթռալ սեղմման ժամանակ"</string>
+    <string name="sound_on_keypress" msgid="6093592297198243644">"Ձայնը սեղմման ժամանակ"</string>
+    <string name="popup_on_keypress" msgid="123894815723512944">"Ելնող պատուհան՝ ստեղնի հպման դեպքում"</string>
+    <string name="general_category" msgid="1859088467017573195">"Ընդհանուր"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Տեքստի ուղղում"</string>
+    <string name="gesture_typing_category" msgid="497263612130532630">"Ժեստերով մուտքագրում"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Այլ ընտրանքներ"</string>
+    <string name="advanced_settings" msgid="362895144495591463">"Ընդլայնված կարգավորումներ"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Ընտրանքներ փորձագետների համար"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Անցնել մուտքագրման այլ եղանակների"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Լեզվի փոխարկման բանալին ընդգրկում է այլ մուտքագրման եղանակներ ևս"</string>
+    <string name="show_language_switch_key" msgid="5915478828318774384">"Լեզվի փոխարկման ստեղն"</string>
+    <string name="show_language_switch_key_summary" msgid="7343403647474265713">"Ցույց տալ, երբ մուտքագրման մի քանի լեզուներ են միացված"</string>
+    <string name="sliding_key_input_preview" msgid="6604262359510068370">"Ցուցադրել սահքի ցուցիչը"</string>
+    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"Ցուցադրել տեսողական հուշումը Shift-ի կամ նշանների ստեղներից սահեցման ընթացքում"</string>
+    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Ելնող պատուհանի հեռացման հետաձգման ստեղն"</string>
+    <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Առանց հետաձգման"</string>
+    <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Նախնականը"</string>
+    <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>մվ"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Համակարգի լռելյայնները"</string>
+    <string name="use_contacts_dict" msgid="4435317977804180815">"Առաջարկել կոնտակտների անունները"</string>
+    <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Օգտագործել կոնտակտների անունները՝ առաջարկների և ուղղումների համար"</string>
+    <string name="use_double_space_period" msgid="8781529969425082860">"Կրկնաբացակի վերջակետ"</string>
+    <string name="use_double_space_period_summary" msgid="6532892187247952799">"Բացակի ստեղնի կրկնակի հպումը բացակից հետո վերջակետ է դնում"</string>
+    <string name="auto_cap" msgid="1719746674854628252">"Ավտոմատ գլխատառացում"</string>
+    <string name="auto_cap_summary" msgid="7934452761022946874">"Գլխատառել յուրաքանչյուր նախադասության առաջին բառը"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Անհատական բառարան"</string>
+    <string name="configure_dictionaries_title" msgid="4238652338556902049">"Ավելացնել բառարաններ"</string>
+    <string name="main_dictionary" msgid="4798763781818361168">"Հիմնական բառարան"</string>
+    <string name="prefs_show_suggestions" msgid="8026799663445531637">"Ցուցադրել ուղղումների առաջարկներ"</string>
+    <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"Ցուցադրել առաջարկվող բառերը մուտքագրման ընթացքում"</string>
+    <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Միշտ ցուցադրել"</string>
+    <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Ցուցադրել դիմանկարային ռեժիմում"</string>
+    <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Միշտ թաքցնել"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Արգելափակել վիրավորական բառերը"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Չառաջարկել հավանական վիրավորական բառերը"</string>
+    <string name="auto_correction" msgid="7630720885194996950">"Ինքնուղղում"</string>
+    <string name="auto_correction_summary" msgid="5625751551134658006">"Տպագրական սխալով բառերում ավտոմատ տեղադրել բացակներն ու կետադրական նշանները"</string>
+    <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Անջատված"</string>
+    <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Համեստ"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Ագրեսիվ"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Շատ ագրեսիվ"</string>
+    <string name="bigram_prediction" msgid="1084449187723948550">"Հաջորդ բառի առաջարկներ"</string>
+    <string name="bigram_prediction_summary" msgid="3896362682751109677">"Առաջարկներ կազմելու համար օգտագործել նախորդ բառը"</string>
+    <string name="gesture_input" msgid="826951152254563827">"Միացնել ժեստերով մուտքագրումը"</string>
+    <string name="gesture_input_summary" msgid="9180350639305731231">"Մուտքագրեք բառ` սահեցնելով տառերը"</string>
+    <string name="gesture_preview_trail" msgid="3802333369335722221">"Ցույց տալ ժեստի հետագիծը"</string>
+    <string name="gesture_floating_preview_text" msgid="4443240334739381053">"Դինամիկ սահող նախատեսք"</string>
+    <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"Տեսեք առաջարկված բառը՝ ժեստի միջոցով"</string>
+    <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>` պահված է"</string>
+    <string name="label_go_key" msgid="1635148082137219148">"Առաջ"</string>
+    <string name="label_next_key" msgid="362972844525672568">"Հաջորդը"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Նխրդ"</string>
+    <string name="label_done_key" msgid="2441578748772529288">"Կատարված է"</string>
+    <string name="label_send_key" msgid="2815056534433717444">"Ուղարկել"</string>
+    <string name="label_pause_key" msgid="181098308428035340">"Դադար"</string>
+    <string name="label_wait_key" msgid="6402152600878093134">"Սպասել"</string>
+    <string name="spoken_use_headphones" msgid="896961781287283493">"Միացրեք ականջակալը՝ բարձրաձայն արտասանվող գաղտնաբառը լսելու համար:"</string>
+    <string name="spoken_current_text_is" msgid="2485723011272583845">"Տվյալ տեքստը %s է"</string>
+    <string name="spoken_no_text_entered" msgid="7479685225597344496">"Տեքստ չի մուտքագրվել"</string>
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g>-ը շտկում է <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g>-ը և դարձնում <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> ստեղնը ինքնաշտկում է կատարում"</string>
+    <string name="spoken_description_unknown" msgid="3197434010402179157">"Բանալու կոդը՝ %d"</string>
+    <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift-ը միացված է (հպել անջատելու համար)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock-ը միացված է (հպել՝ անջատելու համար)"</string>
+    <string name="spoken_description_delete" msgid="8740376944276199801">"Ջնջել"</string>
+    <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Նշաններ"</string>
+    <string name="spoken_description_to_alpha" msgid="23129338819771807">"Տառեր"</string>
+    <string name="spoken_description_to_numeric" msgid="591752092685161732">"Թվեր"</string>
+    <string name="spoken_description_settings" msgid="4627462689603838099">"Կարգավորումներ"</string>
+    <string name="spoken_description_tab" msgid="2667716002663482248">"Tab"</string>
+    <string name="spoken_description_space" msgid="2582521050049860859">"Բացակ"</string>
+    <string name="spoken_description_mic" msgid="615536748882611950">"Ձայնային մուտքագրում"</string>
+    <string name="spoken_description_smiley" msgid="2256309826200113918">"Ժպիտ"</string>
+    <string name="spoken_description_return" msgid="8178083177238315647">"Վերադարձ"</string>
+    <string name="spoken_description_search" msgid="1247236163755920808">"Որոնել"</string>
+    <string name="spoken_description_dot" msgid="40711082435231673">"Կետ"</string>
+    <string name="spoken_description_language_switch" msgid="5507091328222331316">"Փոխել լեզուն"</string>
+    <string name="spoken_description_action_next" msgid="8636078276664150324">"Հաջորդը"</string>
+    <string name="spoken_description_action_previous" msgid="800872415009336208">"Նախորդը"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift-ը միացված է"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps Lock-ը միացված է"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift-ն անջատված է"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Նշանների ռեժիմ"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Տառերի ռեժիմ"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Հեռախոսային ռեժիմ"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Հեռախոսի նշանների ռեժիմ"</string>
+    <string name="announce_keyboard_hidden" msgid="8718927835531429807">"Ստեղնաշարը թաքցված է"</string>
+    <string name="announce_keyboard_mode" msgid="4729081055438508321">"Ցուցադրված է <xliff:g id="MODE">%s</xliff:g> ստեղնաշարը"</string>
+    <string name="keyboard_mode_date" msgid="3137520166817128102">"ամսաթիվ"</string>
+    <string name="keyboard_mode_date_time" msgid="339593358488851072">"ամսաթիվ և ժամ"</string>
+    <string name="keyboard_mode_email" msgid="6216248078128294262">"էլփոստ"</string>
+    <string name="keyboard_mode_im" msgid="1137405089766557048">"նամակագրություն"</string>
+    <string name="keyboard_mode_number" msgid="7991623440699957069">"թվեր"</string>
+    <string name="keyboard_mode_phone" msgid="6851627527401433229">"հեռախոսահամար"</string>
+    <string name="keyboard_mode_text" msgid="6479436687899701619">"տեքստ"</string>
+    <string name="keyboard_mode_time" msgid="4381856885582143277">"ժամանակ"</string>
+    <string name="keyboard_mode_url" msgid="1519819835514911218">"URL"</string>
+    <string name="voice_input" msgid="3583258583521397548">"Ձայնային մուտքագրման ստեղն"</string>
+    <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Հիմնական ստեղնաշարի վրա"</string>
+    <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Նշանների ստեղնաշարի վրա"</string>
+    <string name="voice_input_modes_off" msgid="3745699748218082014">"Անջատված"</string>
+    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Բարձրախոս հիմնական ստեղնաշարի վրա"</string>
+    <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Բարձրախոս նշանների ստեղնաշարի վրա"</string>
+    <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Ձայնային մուտքագրումն անջատված է"</string>
+    <string name="configure_input_method" msgid="373356270290742459">"Կարգավորել մուտքագրման մեթոդները"</string>
+    <string name="language_selection_title" msgid="1651299598555326750">"Մուտքագրման լեզուներ"</string>
+    <string name="send_feedback" msgid="1780431884109392046">"Արձագանքել"</string>
+    <string name="select_language" msgid="3693815588777926848">"Մուտքագրման լեզուներ"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Պահպանելու համար կրկին հպեք"</string>
+    <string name="has_dictionary" msgid="6071847973466625007">"Բառարանն առկա է"</string>
+    <string name="prefs_enable_log" msgid="6620424505072963557">"Միացնել օգտվողի արձագանքը"</string>
+    <string name="prefs_description_log" msgid="7525225584555429211">"Օգնել բարելավել այս մուտքագրման եղանակի խմբագրիչը՝ ինքնուրույն ուղարկելով Google-ին օգտագործման վիճակագրությունն ու վթարների հաշվետվությունները:"</string>
+    <string name="keyboard_layout" msgid="8451164783510487501">"Ստեղնաշարի թեման"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"Անգլերեն (ՄԹ)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"Անգլերեն (ԱՄՆ)"</string>
+    <string name="subtype_es_US" msgid="5583145191430180200">"Իսպաներեն (ԱՄՆ)"</string>
+    <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Անգլերեն (ՄԹ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Անգլերեն (ԱՄՆ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Իսպաներեն (ԱՄՆ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (ավանդական)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"Ոչ մի լեզվով (Այբուբեն)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Այբուբեն (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Այբուբեն (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Այբուբեն (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Այբուբեն (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Այբուբեն (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Այբուբեն (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Զմայլիկներ"</string>
+    <string name="custom_input_styles_title" msgid="8429952441821251512">"Մուտքագրման հատուկ ոճեր"</string>
+    <string name="add_style" msgid="6163126614514489951">"Ավելացնել ոճ"</string>
+    <string name="add" msgid="8299699805688017798">"Ավելացնել"</string>
+    <string name="remove" msgid="4486081658752944606">"Հեռացնել"</string>
+    <string name="save" msgid="7646738597196767214">"Պահել"</string>
+    <string name="subtype_locale" msgid="8576443440738143764">"Lեզու"</string>
+    <string name="keyboard_layout_set" msgid="4309233698194565609">"Դասավորություն"</string>
+    <string name="custom_input_style_note_message" msgid="8826731320846363423">"Մուտքագրման ձեր հատուկ ոճը պետք է միացված լինի նախքան դուք կսկսեք օգտագործել այն: Ցանկանո՞ւմ եք միացնել այն հիմա:"</string>
+    <string name="enable" msgid="5031294444630523247">"Միացնել"</string>
+    <string name="not_now" msgid="6172462888202790482">"Ոչ հիմա"</string>
+    <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Մուտքագրման այսպիսի ոճ արդեն գոյություն ունի՝ <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
+    <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Հարմարավետության ուսումնասիրության ռեժիմ"</string>
+    <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"Ստեղնի երկար սեղմման ուշացում"</string>
+    <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"Սեղմման թրթռոցի տևողություն"</string>
+    <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"Սեղմման ձայնի բարձրությունը"</string>
+    <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Կարդալ արտաքին բառարանի ֆայլը"</string>
+    <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Ներբեռնումների թղթապանակում բառարանային ֆայլեր չկան"</string>
+    <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Ընտրեք բառարանային ֆայլը տեղադրման համար"</string>
+    <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Իրո՞ք ուզում եք տեղադրել այս ֆայլը <xliff:g id="LOCALE_NAME">%s</xliff:g>-ում:"</string>
+    <string name="error" msgid="8940763624668513648">"Տեղի է ունեցել սխալ"</string>
+    <string name="button_default" msgid="3988017840431881491">"Լռելյայնը"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Բարի գալուստ <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"Ժեստային մուտքագրմամբ"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Սկսել"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Հաջորդ քայլը"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Տեղադրվում է <xliff:g id="APPLICATION_NAME">%s</xliff:g>-ը"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Միացնել <xliff:g id="APPLICATION_NAME">%s</xliff:g>-ը"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Խնդրում ենք ստուգել «<xliff:g id="APPLICATION_NAME">%s</xliff:g>»-ը ձեր Լեզվի &amp; մուտքագրման կարգավորումներում: Դա կլիազորի նրան գործարկվել ձեր սարքում:"</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>-ն արդեն միացված է ձեր Լեզվի &amp; մուտքագրման կարգավորումներում, ուստի այս քայլն արված է: Անցնել հաջորդին:"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Միացնել կարգավորումներից"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Փոխարկել <xliff:g id="APPLICATION_NAME">%s</xliff:g>-ին"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Հաջորդիվ, ընտրեք «<xliff:g id="APPLICATION_NAME">%s</xliff:g>»-ը որպես ձեր ակտիվ տեքստային մուտքագրման եղանակ:"</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Փոխարկել մուտքագրման եղանակները"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Շնորհավորում ենք, դուք տեղադրեցիք բոլորը:"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Այժմ դուք կարող եք մուտքագրել ձեր բոլոր սիրելի հավելվածներում <xliff:g id="APPLICATION_NAME">%s</xliff:g>-ով:"</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Կարգավորել լրացուցիչ լեզուները"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Ավարտված"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Ցույց տալ հավելվածի պատկերակը"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Ցուցադրել հավելվածի պատկերակը թողարկչում"</string>
+    <string name="app_name" msgid="6320102637491234792">"Բառարանի մատակարար"</string>
+    <string name="dictionary_provider_name" msgid="3027315045397363079">"Բառարանի մատակարար"</string>
+    <string name="dictionary_service_name" msgid="6237472350693511448">"Բառարանի ծառայություն"</string>
+    <string name="download_description" msgid="6014835283119198591">"Տեղեկություններ բառարանների թարմացման մասին"</string>
+    <string name="dictionary_settings_title" msgid="8091417676045693313">"Ավելացնել բառարաններ"</string>
+    <string name="dictionary_install_over_metered_network_prompt" msgid="3587517870006332980">"Բառարանն առկա է"</string>
+    <string name="dictionary_settings_summary" msgid="5305694987799824349">"Բառարանների կարգավորումներ"</string>
+    <string name="user_dictionaries" msgid="3582332055892252845">"Օգտվողի բառարաններ"</string>
+    <string name="default_user_dict_pref_name" msgid="1625055720489280530">"Օգտվողի բառարան"</string>
+    <string name="dictionary_available" msgid="4728975345815214218">"Բառարանն առկա է"</string>
+    <string name="dictionary_downloading" msgid="2982650524622620983">"Այս պահին ներբեռնվում է"</string>
+    <string name="dictionary_installed" msgid="8081558343559342962">"Տեղադրված է"</string>
+    <string name="dictionary_disabled" msgid="8950383219564621762">"Տեղադրված է, անջատված է"</string>
+    <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"Բառարանային ծառայությանը միացման խնդիր կա"</string>
+    <string name="no_dictionaries_available" msgid="8039920716566132611">"Բառարաններ չկան"</string>
+    <string name="check_for_updates_now" msgid="8087688440916388581">"Թարմացնել"</string>
+    <string name="last_update" msgid="730467549913588780">"Վերջին անգամ թարմացվել է"</string>
+    <string name="message_updating" msgid="4457761393932375219">"Ստուգվում է թարմացումների առկայությունը"</string>
+    <string name="message_loading" msgid="8689096636874758814">"Բեռնվում է..."</string>
+    <string name="main_dict_description" msgid="3072821352793492143">"Հիմնական բառարան"</string>
+    <string name="cancel" msgid="6830980399865683324">"Չեղարկել"</string>
+    <string name="install_dict" msgid="180852772562189365">"Տեղադրել"</string>
+    <string name="cancel_download_dict" msgid="7843340278507019303">"Չեղարկել"</string>
+    <string name="delete_dict" msgid="756853268088330054">"Ջնջել"</string>
+    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"Ձեր բջջային սարքում ընտրված լեզվով առկա է բառարան:<br/> Խորհուրդ ենք տալիս &lt;b&gt;ներբեռնել&lt;/b&gt; <xliff:g id="LANGUAGE">%1$s</xliff:g> բառարանը ձեր մուտքագրման հմտությունների բարելավման համար:&lt;br/&gt; &lt;br/&gt; Ներբեռնումը կարող է խլել մեկ կամ երկու րոպե 3G-ի դեպքում: Հնարավոր է գանձում կատարվի, եթե դուք չունեք &lt;b&gt;տվյալների անսահմանափակ փաթեթ&lt;/b&gt;.&lt;br/&gt; Եթե դուք վստահ չեք, թե տվյալների որ փաթեթն ունեք, խորհուրդ ենք տալիս գտնել Wi-Fi կապ՝ ներբեռնումն ավտոմատ սկսելու համար:&lt;br/&gt; &lt;br/&gt; Հուշում. դուք կարող եք ներբեռնել և հեռացնել բառարաններ՝ գնալով ձեր բջջային սարքի &lt;b&gt;Կարգավորումներ ցանկի Լեզու &amp; մուտքագրման&lt;/b&gt; բաժինը:"</string>
+    <string name="download_over_metered" msgid="1643065851159409546">"Ներբեռնել հիմա (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g>Մբ)"</string>
+    <string name="do_not_download_over_metered" msgid="2176209579313941583">"Ներբեռնել Wi-Fi-ով"</string>
+    <string name="dict_available_notification_title" msgid="6514288591959117288">"<xliff:g id="LANGUAGE">%1$s</xliff:g>-ով առկա է մի բառարան"</string>
+    <string name="dict_available_notification_description" msgid="1075194169443163487">"Սեղմեք՝ վերանայելու և ներբեռնելու համար"</string>
+    <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Ներբեռնվում է. <xliff:g id="LANGUAGE">%1$s</xliff:g>-ի համար առաջարկները շուտով պատրաստ կլինեն:"</string>
+    <string name="version_text" msgid="2715354215568469385">"Տարբերակ <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Ավելացնել"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Ավելացնել բառարանում"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Արտահայտություն"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Այլ ընտրանքներ"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Սակավ ընտրանքներ"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"Լավ"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Բառը՝"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Դյուրանցումը՝"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Lեզուն՝"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Մուտքագրեք բառը"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Ընտրովի դյուրանցում"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Խմբագրել բառը"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Խմբագրել"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Ջնջել"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Դուք չունեք ոչ մի բառ օգտվողի բառարանում: Ավելացնել բառեր՝ հպելով Ավելացնել (+) կոճակը:"</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Բոլոր լեզուներով"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Ավելի շատ լեզուներով..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Ջնջել"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ԱԲԳԴԵԶԷԸԹԺԻԼԽԾԿՀՁՂՃՄՅՆՇՈՉՊՋՌՍՎՏՐՑՈՒՓՔԵւՕՖ"</string>
+</resources>
diff --git a/java/res/values-in/strings.xml b/java/res/values-in/strings.xml
index bfbd2a7..a731478 100644
--- a/java/res/values-in/strings.xml
+++ b/java/res/values-in/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Pasang headset untuk mendengar tombol sandi yang diucapkan dengan keras."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Teks saat ini adalah %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Tidak ada teks yang dimasukkan"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> mengoreksi <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> menjadi <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> melakukan koreksi otomatis"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Kode tombol %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift hidup (ketuk untuk mematikan)"</string>
diff --git a/java/res/values-is/strings.xml b/java/res/values-is/strings.xml
new file mode 100644
index 0000000..5cc643d
--- /dev/null
+++ b/java/res/values-is/strings.xml
@@ -0,0 +1,464 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_input_options (3909945612939668554) -->
+    <skip />
+    <!-- no translation found for english_ime_research_log (8492602295696577851) -->
+    <skip />
+    <!-- no translation found for use_contacts_for_spellchecking_option_title (5374120998125353898) -->
+    <skip />
+    <!-- no translation found for use_contacts_for_spellchecking_option_summary (8754413382543307713) -->
+    <skip />
+    <!-- no translation found for vibrate_on_keypress (5258079494276955460) -->
+    <skip />
+    <!-- no translation found for sound_on_keypress (6093592297198243644) -->
+    <skip />
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
+    <!-- no translation found for general_category (1859088467017573195) -->
+    <skip />
+    <!-- no translation found for correction_category (2236750915056607613) -->
+    <skip />
+    <!-- no translation found for gesture_typing_category (497263612130532630) -->
+    <skip />
+    <!-- no translation found for misc_category (6894192814868233453) -->
+    <skip />
+    <!-- no translation found for advanced_settings (362895144495591463) -->
+    <skip />
+    <!-- no translation found for advanced_settings_summary (4487980456152830271) -->
+    <skip />
+    <!-- no translation found for include_other_imes_in_language_switch_list (4533689960308565519) -->
+    <skip />
+    <!-- no translation found for include_other_imes_in_language_switch_list_summary (840637129103317635) -->
+    <skip />
+    <!-- no translation found for show_language_switch_key (5915478828318774384) -->
+    <skip />
+    <!-- no translation found for show_language_switch_key_summary (7343403647474265713) -->
+    <skip />
+    <!-- no translation found for sliding_key_input_preview (6604262359510068370) -->
+    <skip />
+    <!-- no translation found for sliding_key_input_preview_summary (6340524345729093886) -->
+    <skip />
+    <!-- no translation found for key_preview_popup_dismiss_delay (6213164897443068248) -->
+    <skip />
+    <!-- no translation found for key_preview_popup_dismiss_no_delay (2096123151571458064) -->
+    <skip />
+    <!-- no translation found for key_preview_popup_dismiss_default_delay (2166964333903906734) -->
+    <skip />
+    <!-- no translation found for abbreviation_unit_milliseconds (8700286094028323363) -->
+    <skip />
+    <!-- no translation found for settings_system_default (6268225104743331821) -->
+    <skip />
+    <!-- no translation found for use_contacts_dict (4435317977804180815) -->
+    <skip />
+    <!-- no translation found for use_contacts_dict_summary (6599983334507879959) -->
+    <skip />
+    <!-- no translation found for use_double_space_period (8781529969425082860) -->
+    <skip />
+    <!-- no translation found for use_double_space_period_summary (6532892187247952799) -->
+    <skip />
+    <!-- no translation found for auto_cap (1719746674854628252) -->
+    <skip />
+    <!-- no translation found for auto_cap_summary (7934452761022946874) -->
+    <skip />
+    <!-- no translation found for edit_personal_dictionary (3996910038952940420) -->
+    <skip />
+    <!-- no translation found for configure_dictionaries_title (4238652338556902049) -->
+    <skip />
+    <!-- no translation found for main_dictionary (4798763781818361168) -->
+    <skip />
+    <!-- no translation found for prefs_show_suggestions (8026799663445531637) -->
+    <skip />
+    <!-- no translation found for prefs_show_suggestions_summary (1583132279498502825) -->
+    <skip />
+    <!-- no translation found for prefs_suggestion_visibility_show_name (3219916594067551303) -->
+    <skip />
+    <!-- no translation found for prefs_suggestion_visibility_show_only_portrait_name (3859783767435239118) -->
+    <skip />
+    <!-- no translation found for prefs_suggestion_visibility_hide_name (6309143926422234673) -->
+    <skip />
+    <!-- no translation found for prefs_block_potentially_offensive_title (5078480071057408934) -->
+    <skip />
+    <!-- no translation found for prefs_block_potentially_offensive_summary (2371835479734991364) -->
+    <skip />
+    <!-- no translation found for auto_correction (7630720885194996950) -->
+    <skip />
+    <!-- no translation found for auto_correction_summary (5625751551134658006) -->
+    <skip />
+    <!-- no translation found for auto_correction_threshold_mode_off (8470882665417944026) -->
+    <skip />
+    <!-- no translation found for auto_correction_threshold_mode_modest (8788366690620799097) -->
+    <skip />
+    <!-- no translation found for auto_correction_threshold_mode_aggressive (7319007299148899623) -->
+    <skip />
+    <!-- no translation found for auto_correction_threshold_mode_very_aggressive (1853309024129480416) -->
+    <skip />
+    <!-- no translation found for bigram_prediction (1084449187723948550) -->
+    <skip />
+    <!-- no translation found for bigram_prediction_summary (3896362682751109677) -->
+    <skip />
+    <!-- no translation found for gesture_input (826951152254563827) -->
+    <skip />
+    <!-- no translation found for gesture_input_summary (9180350639305731231) -->
+    <skip />
+    <!-- no translation found for gesture_preview_trail (3802333369335722221) -->
+    <skip />
+    <!-- no translation found for gesture_floating_preview_text (4443240334739381053) -->
+    <skip />
+    <!-- no translation found for gesture_floating_preview_text_summary (4472696213996203533) -->
+    <skip />
+    <!-- no translation found for added_word (8993883354622484372) -->
+    <skip />
+    <string name="label_go_key" msgid="1635148082137219148">"Áfram"</string>
+    <string name="label_next_key" msgid="362972844525672568">"Næsta"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Fyrra"</string>
+    <string name="label_done_key" msgid="2441578748772529288">"Lokið"</string>
+    <string name="label_send_key" msgid="2815056534433717444">"Senda"</string>
+    <!-- no translation found for label_pause_key (181098308428035340) -->
+    <skip />
+    <!-- no translation found for label_wait_key (6402152600878093134) -->
+    <skip />
+    <!-- no translation found for spoken_use_headphones (896961781287283493) -->
+    <skip />
+    <!-- no translation found for spoken_current_text_is (2485723011272583845) -->
+    <skip />
+    <!-- no translation found for spoken_no_text_entered (7479685225597344496) -->
+    <skip />
+    <!-- no translation found for spoken_auto_correct (8005997889020109763) -->
+    <skip />
+    <!-- no translation found for spoken_auto_correct_obscured (6276420476908833791) -->
+    <skip />
+    <!-- no translation found for spoken_description_unknown (3197434010402179157) -->
+    <skip />
+    <!-- no translation found for spoken_description_shift (244197883292549308) -->
+    <skip />
+    <!-- no translation found for spoken_description_shift_shifted (1681877323344195035) -->
+    <skip />
+    <!-- no translation found for spoken_description_caps_lock (3276478269526304432) -->
+    <skip />
+    <!-- no translation found for spoken_description_delete (8740376944276199801) -->
+    <skip />
+    <!-- no translation found for spoken_description_to_symbol (5486340107500448969) -->
+    <skip />
+    <!-- no translation found for spoken_description_to_alpha (23129338819771807) -->
+    <skip />
+    <!-- no translation found for spoken_description_to_numeric (591752092685161732) -->
+    <skip />
+    <!-- no translation found for spoken_description_settings (4627462689603838099) -->
+    <skip />
+    <!-- no translation found for spoken_description_tab (2667716002663482248) -->
+    <skip />
+    <!-- no translation found for spoken_description_space (2582521050049860859) -->
+    <skip />
+    <!-- no translation found for spoken_description_mic (615536748882611950) -->
+    <skip />
+    <!-- no translation found for spoken_description_smiley (2256309826200113918) -->
+    <skip />
+    <!-- no translation found for spoken_description_return (8178083177238315647) -->
+    <skip />
+    <!-- no translation found for spoken_description_search (1247236163755920808) -->
+    <skip />
+    <!-- no translation found for spoken_description_dot (40711082435231673) -->
+    <skip />
+    <!-- no translation found for spoken_description_language_switch (5507091328222331316) -->
+    <skip />
+    <!-- no translation found for spoken_description_action_next (8636078276664150324) -->
+    <skip />
+    <!-- no translation found for spoken_description_action_previous (800872415009336208) -->
+    <skip />
+    <!-- no translation found for spoken_description_shiftmode_on (5700440798609574589) -->
+    <skip />
+    <!-- no translation found for spoken_description_shiftmode_locked (593175803181701830) -->
+    <skip />
+    <!-- no translation found for spoken_description_shiftmode_off (657219998449174808) -->
+    <skip />
+    <!-- no translation found for spoken_description_mode_symbol (7183343879909747642) -->
+    <skip />
+    <!-- no translation found for spoken_description_mode_alpha (3528307674390156956) -->
+    <skip />
+    <!-- no translation found for spoken_description_mode_phone (6520207943132026264) -->
+    <skip />
+    <!-- no translation found for spoken_description_mode_phone_shift (5499629753962641227) -->
+    <skip />
+    <!-- no translation found for announce_keyboard_hidden (8718927835531429807) -->
+    <skip />
+    <!-- no translation found for announce_keyboard_mode (4729081055438508321) -->
+    <skip />
+    <!-- no translation found for keyboard_mode_date (3137520166817128102) -->
+    <skip />
+    <!-- no translation found for keyboard_mode_date_time (339593358488851072) -->
+    <skip />
+    <!-- no translation found for keyboard_mode_email (6216248078128294262) -->
+    <skip />
+    <!-- no translation found for keyboard_mode_im (1137405089766557048) -->
+    <skip />
+    <!-- no translation found for keyboard_mode_number (7991623440699957069) -->
+    <skip />
+    <!-- no translation found for keyboard_mode_phone (6851627527401433229) -->
+    <skip />
+    <!-- no translation found for keyboard_mode_text (6479436687899701619) -->
+    <skip />
+    <!-- no translation found for keyboard_mode_time (4381856885582143277) -->
+    <skip />
+    <!-- no translation found for keyboard_mode_url (1519819835514911218) -->
+    <skip />
+    <!-- no translation found for voice_input (3583258583521397548) -->
+    <skip />
+    <!-- no translation found for voice_input_modes_main_keyboard (3360660341121083174) -->
+    <skip />
+    <!-- no translation found for voice_input_modes_symbols_keyboard (7203213240786084067) -->
+    <skip />
+    <!-- no translation found for voice_input_modes_off (3745699748218082014) -->
+    <skip />
+    <!-- no translation found for voice_input_modes_summary_main_keyboard (6586544292900314339) -->
+    <skip />
+    <!-- no translation found for voice_input_modes_summary_symbols_keyboard (5233725927281932391) -->
+    <skip />
+    <!-- no translation found for voice_input_modes_summary_off (63875609591897607) -->
+    <skip />
+    <!-- no translation found for configure_input_method (373356270290742459) -->
+    <skip />
+    <!-- no translation found for language_selection_title (1651299598555326750) -->
+    <skip />
+    <!-- no translation found for send_feedback (1780431884109392046) -->
+    <skip />
+    <!-- no translation found for select_language (3693815588777926848) -->
+    <skip />
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
+    <!-- no translation found for has_dictionary (6071847973466625007) -->
+    <skip />
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (8451164783510487501) -->
+    <skip />
+    <!-- no translation found for subtype_en_GB (88170601942311355) -->
+    <skip />
+    <!-- no translation found for subtype_en_US (6160452336634534239) -->
+    <skip />
+    <!-- no translation found for subtype_es_US (5583145191430180200) -->
+    <skip />
+    <!-- no translation found for subtype_with_layout_en_GB (2179097748724725906) -->
+    <skip />
+    <!-- no translation found for subtype_with_layout_en_US (1362581347576714579) -->
+    <skip />
+    <!-- no translation found for subtype_with_layout_es_US (6261791057007890189) -->
+    <skip />
+    <!-- no translation found for subtype_nepali_traditional (9032247506728040447) -->
+    <skip />
+    <!-- no translation found for subtype_no_language (7137390094240139495) -->
+    <skip />
+    <!-- no translation found for subtype_no_language_qwerty (244337630616742604) -->
+    <skip />
+    <!-- no translation found for subtype_no_language_qwertz (443066912507547976) -->
+    <skip />
+    <!-- no translation found for subtype_no_language_azerty (8144348527575640087) -->
+    <skip />
+    <!-- no translation found for subtype_no_language_dvorak (1564494667584718094) -->
+    <skip />
+    <!-- no translation found for subtype_no_language_colemak (5837418400010302623) -->
+    <skip />
+    <!-- no translation found for subtype_no_language_pcqwerty (5354918232046200018) -->
+    <skip />
+    <!-- no translation found for subtype_emoji (7483586578074549196) -->
+    <skip />
+    <!-- no translation found for custom_input_styles_title (8429952441821251512) -->
+    <skip />
+    <!-- no translation found for add_style (6163126614514489951) -->
+    <skip />
+    <!-- no translation found for add (8299699805688017798) -->
+    <skip />
+    <!-- no translation found for remove (4486081658752944606) -->
+    <skip />
+    <!-- no translation found for save (7646738597196767214) -->
+    <skip />
+    <!-- no translation found for subtype_locale (8576443440738143764) -->
+    <skip />
+    <!-- no translation found for keyboard_layout_set (4309233698194565609) -->
+    <skip />
+    <!-- no translation found for custom_input_style_note_message (8826731320846363423) -->
+    <skip />
+    <!-- no translation found for enable (5031294444630523247) -->
+    <skip />
+    <!-- no translation found for not_now (6172462888202790482) -->
+    <skip />
+    <!-- no translation found for custom_input_style_already_exists (8008728952215449707) -->
+    <skip />
+    <!-- no translation found for prefs_usability_study_mode (1261130555134595254) -->
+    <skip />
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
+    <!-- no translation found for prefs_read_external_dictionary (2588931418575013067) -->
+    <skip />
+    <!-- no translation found for read_external_dictionary_no_files_message (4947420942224623792) -->
+    <skip />
+    <!-- no translation found for read_external_dictionary_multiple_files_title (7637749044265808628) -->
+    <skip />
+    <!-- no translation found for read_external_dictionary_confirm_install_message (6898610163768980870) -->
+    <skip />
+    <!-- no translation found for error (8940763624668513648) -->
+    <skip />
+    <!-- no translation found for button_default (3988017840431881491) -->
+    <skip />
+    <!-- no translation found for setup_welcome_title (6112821709832031715) -->
+    <skip />
+    <!-- no translation found for setup_welcome_additional_description (8150252008545768953) -->
+    <skip />
+    <!-- no translation found for setup_start_action (8936036460897347708) -->
+    <skip />
+    <!-- no translation found for setup_next_action (371821437915144603) -->
+    <skip />
+    <!-- no translation found for setup_steps_title (6400373034871816182) -->
+    <skip />
+    <!-- no translation found for setup_step1_title (3147967630253462315) -->
+    <skip />
+    <!-- no translation found for setup_step1_instruction (2578631936624637241) -->
+    <skip />
+    <!-- no translation found for setup_step1_finished_instruction (10761482004957994) -->
+    <skip />
+    <!-- no translation found for setup_step1_action (4366513534999901728) -->
+    <skip />
+    <!-- no translation found for setup_step2_title (6860725447906690594) -->
+    <skip />
+    <!-- no translation found for setup_step2_instruction (9141481964870023336) -->
+    <skip />
+    <!-- no translation found for setup_step2_action (1660330307159824337) -->
+    <skip />
+    <!-- no translation found for setup_step3_title (3154757183631490281) -->
+    <skip />
+    <!-- no translation found for setup_step3_instruction (8025981829605426000) -->
+    <skip />
+    <!-- no translation found for setup_step3_action (600879797256942259) -->
+    <skip />
+    <!-- no translation found for setup_finish_action (276559243409465389) -->
+    <skip />
+    <!-- no translation found for show_setup_wizard_icon (5008028590593710830) -->
+    <skip />
+    <!-- no translation found for show_setup_wizard_icon_summary (4119998322536880213) -->
+    <skip />
+    <!-- no translation found for app_name (6320102637491234792) -->
+    <skip />
+    <!-- no translation found for dictionary_provider_name (3027315045397363079) -->
+    <skip />
+    <!-- no translation found for dictionary_service_name (6237472350693511448) -->
+    <skip />
+    <!-- no translation found for download_description (6014835283119198591) -->
+    <skip />
+    <!-- no translation found for dictionary_settings_title (8091417676045693313) -->
+    <skip />
+    <!-- no translation found for dictionary_install_over_metered_network_prompt (3587517870006332980) -->
+    <skip />
+    <!-- no translation found for dictionary_settings_summary (5305694987799824349) -->
+    <skip />
+    <!-- no translation found for user_dictionaries (3582332055892252845) -->
+    <skip />
+    <!-- no translation found for default_user_dict_pref_name (1625055720489280530) -->
+    <skip />
+    <!-- no translation found for dictionary_available (4728975345815214218) -->
+    <skip />
+    <!-- no translation found for dictionary_downloading (2982650524622620983) -->
+    <skip />
+    <!-- no translation found for dictionary_installed (8081558343559342962) -->
+    <skip />
+    <!-- no translation found for dictionary_disabled (8950383219564621762) -->
+    <skip />
+    <!-- no translation found for cannot_connect_to_dict_service (9216933695765732398) -->
+    <skip />
+    <!-- no translation found for no_dictionaries_available (8039920716566132611) -->
+    <skip />
+    <!-- no translation found for check_for_updates_now (8087688440916388581) -->
+    <skip />
+    <!-- no translation found for last_update (730467549913588780) -->
+    <skip />
+    <!-- no translation found for message_updating (4457761393932375219) -->
+    <skip />
+    <!-- no translation found for message_loading (8689096636874758814) -->
+    <skip />
+    <!-- no translation found for main_dict_description (3072821352793492143) -->
+    <skip />
+    <!-- no translation found for cancel (6830980399865683324) -->
+    <skip />
+    <!-- no translation found for install_dict (180852772562189365) -->
+    <skip />
+    <!-- no translation found for cancel_download_dict (7843340278507019303) -->
+    <skip />
+    <!-- no translation found for delete_dict (756853268088330054) -->
+    <skip />
+    <!-- no translation found for should_download_over_metered_prompt (2878629598667658845) -->
+    <skip />
+    <!-- no translation found for download_over_metered (1643065851159409546) -->
+    <skip />
+    <!-- no translation found for do_not_download_over_metered (2176209579313941583) -->
+    <skip />
+    <!-- no translation found for dict_available_notification_title (6514288591959117288) -->
+    <skip />
+    <!-- no translation found for dict_available_notification_description (1075194169443163487) -->
+    <skip />
+    <!-- no translation found for toast_downloading_suggestions (1313027353588566660) -->
+    <skip />
+    <!-- no translation found for version_text (2715354215568469385) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_menu_title (1254195365689387076) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_dialog_title (4096700390211748168) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_screen_title (5818914331629278758) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_dialog_more_options (5671682004887093112) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_dialog_less_options (2716586567241724126) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_dialog_confirm (4703129507388332950) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_word_option_name (6665558053408962865) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_shortcut_option_name (3094731590655523777) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_locale_option_name (4738643440987277705) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_word_hint (4902434148985906707) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_shortcut_hint (2265453012555060178) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_edit_dialog_title (3765774633869590352) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_context_menu_edit_title (6812255903472456302) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_context_menu_delete_title (8142932447689461181) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_empty_text (558499587532668203) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_all_languages (8276126583216298886) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_more_languages (7131268499685180461) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_delete (110413335187193859) -->
+    <skip />
+    <!-- no translation found for user_dict_fast_scroll_alphabet (5431919401558285473) -->
+    <skip />
+</resources>
diff --git a/java/res/values-it/strings.xml b/java/res/values-it/strings.xml
index 6989643..ec27fd5 100644
--- a/java/res/values-it/strings.xml
+++ b/java/res/values-it/strings.xml
@@ -62,7 +62,7 @@
     <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Non suggerire parole potenzialmente offensive"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Correzione automatica"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Barra spaziatrice/punteggiatura correggono parole con errori"</string>
-    <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Off"</string>
+    <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"OFF"</string>
     <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Media"</string>
     <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Molto elevata"</string>
     <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Massima"</string>
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Collega gli auricolari per ascoltare la pronuncia dei tasti premuti per la password."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Il testo attuale è %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Nessun testo inserito"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> corregge <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> con <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> esegue correzione automatica"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Codice tasto %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Maiuscolo"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Maiuscolo attivo (tocca per disattivare)"</string>
@@ -128,7 +126,7 @@
     <string name="voice_input" msgid="3583258583521397548">"Tasto input vocale"</string>
     <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Su tastiera principale"</string>
     <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Su tastiera simboli"</string>
-    <string name="voice_input_modes_off" msgid="3745699748218082014">"Non attivo"</string>
+    <string name="voice_input_modes_off" msgid="3745699748218082014">"OFF"</string>
     <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Microfono su tastiera principale"</string>
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Microfono su tastiera simboli"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Input vocale disatt."</string>
diff --git a/java/res/values-iw/strings-appname.xml b/java/res/values-iw/strings-appname.xml
index 1a07c54..fc040f9 100644
--- a/java/res/values-iw/strings-appname.xml
+++ b/java/res/values-iw/strings-appname.xml
@@ -20,8 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="english_ime_name" msgid="5940510615957428904">"מקלדת Android‏ (AOSP)"</string>
-    <string name="spell_checker_service_name" msgid="1254221805440242662">"בודק האיות של Android‏ (AOSP)"</string>
-    <string name="english_ime_settings" msgid="5760361067176802794">"הגדרות מקלדת Android‏ (AOSP)"</string>
-    <string name="android_spell_checker_settings" msgid="6123949487832861885">"הגדרות בודק האיות של Android‏ (AOSP)"</string>
+    <string name="english_ime_name" msgid="5940510615957428904">"‏מקלדת Android‏ (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"‏בודק האיות של Android‏ (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"‏הגדרות מקלדת Android‏ (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"‏הגדרות בודק האיות של Android‏ (AOSP)"</string>
 </resources>
diff --git a/java/res/values-iw/strings.xml b/java/res/values-iw/strings.xml
index b3ee014..664bfcf 100644
--- a/java/res/values-iw/strings.xml
+++ b/java/res/values-iw/strings.xml
@@ -38,7 +38,7 @@
     <string name="show_language_switch_key" msgid="5915478828318774384">"מתג החלפת שפה"</string>
     <string name="show_language_switch_key_summary" msgid="7343403647474265713">"הצג כאשר ניתן להשתמש בשפות קלט מרובות"</string>
     <string name="sliding_key_input_preview" msgid="6604262359510068370">"הצג את סמן ההסטה"</string>
-    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"הצג סימון ויזואלי בעת הסטה מ-Shift או ממקשי סמלים"</string>
+    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"‏הצג סימון ויזואלי בעת הסטה מ-Shift או ממקשי סמלים"</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"עיכוב בסגירת חלון קופץ של מקש"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"ללא עיכוב"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"ברירת מחדל"</string>
@@ -82,16 +82,14 @@
     <string name="label_pause_key" msgid="181098308428035340">"השהה"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"המתן"</string>
     <string name="spoken_use_headphones" msgid="896961781287283493">"חבר אוזניות כדי לשמוע הקראה של מפתחות סיסמה."</string>
-    <string name="spoken_current_text_is" msgid="2485723011272583845">"הטקסט הנוכחי הוא %s"</string>
+    <string name="spoken_current_text_is" msgid="2485723011272583845">"‏הטקסט הנוכחי הוא %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"לא הוזן טקסט"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
-    <string name="spoken_description_unknown" msgid="3197434010402179157">"קוד מקש %d"</string>
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> מתקן את <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> ל-<xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> מבצע תיקון אוטומטי"</string>
+    <string name="spoken_description_unknown" msgid="3197434010402179157">"‏קוד מקש %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift פועל (הקש כדי להשבית)"</string>
-    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps Lock פועל (הקש כדי להשבית)"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"‏Shift פועל (הקש כדי להשבית)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"‏Caps Lock פועל (הקש כדי להשבית)"</string>
     <string name="spoken_description_delete" msgid="8740376944276199801">"מחק"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"סמלים"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"אותיות"</string>
@@ -107,9 +105,9 @@
     <string name="spoken_description_language_switch" msgid="5507091328222331316">"החלף שפה"</string>
     <string name="spoken_description_action_next" msgid="8636078276664150324">"הבא"</string>
     <string name="spoken_description_action_previous" msgid="800872415009336208">"הקודם"</string>
-    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift פועל"</string>
-    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps Lock פועל"</string>
-    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift מושבת"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"‏Shift פועל"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"‏Caps Lock פועל"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"‏Shift מושבת"</string>
     <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"מצב סמלים"</string>
     <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"מצב אותיות"</string>
     <string name="spoken_description_mode_phone" msgid="6520207943132026264">"מצב טלפון"</string>
@@ -149,12 +147,12 @@
     <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"ספרדית (ארצות הברית) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (מסורתית)"</string>
     <string name="subtype_no_language" msgid="7137390094240139495">"ללא שפה (אלף-בית)"</string>
-    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"אלף-בית (QWERTY)"</string>
-    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"אלף-בית (QWERTZ)"</string>
-    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"אלף-בית (AZERTY)"</string>
-    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"אלף-בית (Dvorak)"</string>
-    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"אלף-בית (Colemak)"</string>
-    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"אלף-בית (PC)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"‏אלף-בית (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"‏אלף-בית (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"‏אלף-בית (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"‏אלף-בית (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"‏אלף-בית (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"‏אלף-בית (PC)"</string>
     <string name="subtype_emoji" msgid="7483586578074549196">"אמוג\'י"</string>
     <string name="custom_input_styles_title" msgid="8429952441821251512">"סגנונות קלט מותאמים אישית"</string>
     <string name="add_style" msgid="6163126614514489951">"הוסף סגנון"</string>
@@ -219,9 +217,9 @@
     <string name="install_dict" msgid="180852772562189365">"התקן"</string>
     <string name="cancel_download_dict" msgid="7843340278507019303">"בטל"</string>
     <string name="delete_dict" msgid="756853268088330054">"מחק"</string>
-    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"לשפה הנבחרת במכשיר הנייד שלך יש מילון זמין.&lt;br/&gt; אנו ממליצים &lt;b&gt;להוריד&lt;/b&gt; את המילון ב<xliff:g id="LANGUAGE">%1$s</xliff:g> כדי לשפר את חוויית ההקלדה.&lt;br/&gt; &lt;br/&gt; ההורדה עשויה לארוך דקה או שתיים ב-3G. ייתכן שתחויב אם אין לך &lt;b&gt;תוכנית נתונים בלתי מוגבלת&lt;/b&gt;.&lt;br/&gt; אם אינך בטוח איזו תוכנית נתונים יש לך, אנו ממליצים לחפש חיבור Wi-Fi כדי להתחיל בהורדה באופן אוטומטי.&lt;br/&gt; &lt;br/&gt; טיפ: ניתן להוריד ולהסיר מילונים ב&lt;b&gt;שפה וקלט&lt;/b&gt; בתפריט &lt;b&gt;הגדרות&lt;/b&gt; של המכשיר הנייד שלך."</string>
-    <string name="download_over_metered" msgid="1643065851159409546">"הורד עכשיו (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g>MB)"</string>
-    <string name="do_not_download_over_metered" msgid="2176209579313941583">"הורד באמצעות Wi-Fi"</string>
+    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"‏לשפה הנבחרת במכשיר הנייד שלך יש מילון זמין.&lt;br/&gt; אנו ממליצים &lt;b&gt;להוריד&lt;/b&gt; את המילון ב<xliff:g id="LANGUAGE">%1$s</xliff:g> כדי לשפר את חוויית ההקלדה.&lt;br/&gt; &lt;br/&gt; ההורדה עשויה לארוך דקה או שתיים ב-3G. ייתכן שתחויב אם אין לך &lt;b&gt;תוכנית נתונים בלתי מוגבלת&lt;/b&gt;.&lt;br/&gt; אם אינך בטוח איזו תוכנית נתונים יש לך, אנו ממליצים לחפש חיבור Wi-Fi כדי להתחיל בהורדה באופן אוטומטי.&lt;br/&gt; &lt;br/&gt; טיפ: ניתן להוריד ולהסיר מילונים ב&lt;b&gt;שפה וקלט&lt;/b&gt; בתפריט &lt;b&gt;הגדרות&lt;/b&gt; של המכשיר הנייד שלך."</string>
+    <string name="download_over_metered" msgid="1643065851159409546">"‏הורד עכשיו (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g>MB)"</string>
+    <string name="do_not_download_over_metered" msgid="2176209579313941583">"‏הורד באמצעות Wi-Fi"</string>
     <string name="dict_available_notification_title" msgid="6514288591959117288">"יש מילון זמין עבור <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"לחץ כדי לעיין ולהוריד"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"מוריד: הצעות ב<xliff:g id="LANGUAGE">%1$s</xliff:g> יהיו מוכנות בקרוב."</string>
diff --git a/java/res/values-ja/strings.xml b/java/res/values-ja/strings.xml
index 37f0637..fd546d9 100644
--- a/java/res/values-ja/strings.xml
+++ b/java/res/values-ja/strings.xml
@@ -50,7 +50,7 @@
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"スペースバーをダブルタップするとピリオドとスペースを挿入できます"</string>
     <string name="auto_cap" msgid="1719746674854628252">"自動大文字変換"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"英字入力で各文の最初の単語を大文字にします"</string>
-    <string name="edit_personal_dictionary" msgid="3996910038952940420">"ユーザー辞書"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"単語リスト"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"アドオン辞書"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"メイン辞書"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"修正候補を表示する"</string>
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"パスワードのキーが音声出力されるのでヘッドセットを接続してください。"</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"現在のテキスト:%s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"テキストが入力されていません"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g>は<xliff:g id="ORIGINAL_WORD">%2$s</xliff:g>を<xliff:g id="CORRECTED">%3$s</xliff:g>に修正します"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g>で自動修正が実行されます"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"キーコード:%d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift有効(タップして解除)"</string>
diff --git a/java/res/values-ka-rGE/strings.xml b/java/res/values-ka-rGE/strings.xml
index 193f511..6224961 100644
--- a/java/res/values-ka-rGE/strings.xml
+++ b/java/res/values-ka-rGE/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"შეაერთეთ ყურსაცვამი, რათა მოისმინოთ აკრეფილი პაროლის კლავიშების სახელები."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"მიმდინარე ტექსტი არის %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"ტექსტი არ შეყვანილა"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> შეასწორებს <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g>-ს <xliff:g id="CORRECTED">%3$s</xliff:g>-ად"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> ასრულებს ავტოკორექციას"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"კლავიატურის კოდი %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift ჩართულია (შეეხეთ გამოსართავად)"</string>
diff --git a/java/res/values-kk/strings.xml b/java/res/values-kk/strings.xml
new file mode 100644
index 0000000..4c78be5
--- /dev/null
+++ b/java/res/values-kk/strings.xml
@@ -0,0 +1,250 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_input_options" msgid="3909945612939668554">"Енгізу опциялары"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Журнал пәрмендерін зерттеу"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Контакт аттарын іздеу"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Емлені тексеру құралы контактілер тізімінің жазбаларын пайдаланады"</string>
+    <string name="vibrate_on_keypress" msgid="5258079494276955460">"Пернені басқан кездегі діріл"</string>
+    <string name="sound_on_keypress" msgid="6093592297198243644">"Пернені басу кезіндегі дыбыс"</string>
+    <string name="popup_on_keypress" msgid="123894815723512944">"Пернені басқан кездегі ашылмалы мәзір"</string>
+    <string name="general_category" msgid="1859088467017573195">"Жалпы"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Мәтін түзетпесі"</string>
+    <string name="gesture_typing_category" msgid="497263612130532630">"Қимылмен теру"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Басқа опциялар"</string>
+    <string name="advanced_settings" msgid="362895144495591463">"Қосымша параметрлер"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Сарапшылар опциялары"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Басқа енгізу әдістеріне ауыстырыңыз"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Тілді ауыстыру пернесі басқа енгізу әдістерін де қамтиды"</string>
+    <string name="show_language_switch_key" msgid="5915478828318774384">"Тілді ауыстыру пернесі"</string>
+    <string name="show_language_switch_key_summary" msgid="7343403647474265713">"Бірнеше енгізу тілдері қосылған кезде көрсету"</string>
+    <string name="sliding_key_input_preview" msgid="6604262359510068370">"Жылжыту индикаторын көрсету"</string>
+    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"Shift немесе Таңба пернелерінен жылжыту кезіндегі көрнекі сөзкөмекті көрсету"</string>
+    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Перненің ашылмалы мәзірі кідірісті жояды"</string>
+    <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Кідіріс жоқ"</string>
+    <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Әдепкі"</string>
+    <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>мс"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Жүйе әдепкісі"</string>
+    <string name="use_contacts_dict" msgid="4435317977804180815">"Контакт аттарын ұсыну"</string>
+    <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Ұсыныстар мен түзетулер үшін контакт аттарын пайдалану"</string>
+    <string name="use_double_space_period" msgid="8781529969425082860">"Қос бос орын кезеңі"</string>
+    <string name="use_double_space_period_summary" msgid="6532892187247952799">"Бос орынға екі рет түрту бос орыннан кейінгі кезеңді енгізеді"</string>
+    <string name="auto_cap" msgid="1719746674854628252">"Авто бас әріптерге түрлендіру"</string>
+    <string name="auto_cap_summary" msgid="7934452761022946874">"Әрбір сөйлемнің бірінші әріпін бас әріпке түрлендіру"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Жеке сөздік"</string>
+    <string name="configure_dictionaries_title" msgid="4238652338556902049">"Қосымша сөздіктер"</string>
+    <string name="main_dictionary" msgid="4798763781818361168">"Негізгі сөздік"</string>
+    <string name="prefs_show_suggestions" msgid="8026799663445531637">"Түзету ұсыныстарын көрсету"</string>
+    <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"Теру кезінде ұсынылған сөздерді көрсету"</string>
+    <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Үнемі көрсету"</string>
+    <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Портрет режимінде көрсету"</string>
+    <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Үнемі жасыру"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Қорлаушы сөздерді құлыптамау"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Қорлаушы сөздерді ұсынбау"</string>
+    <string name="auto_correction" msgid="7630720885194996950">"Авто түзету"</string>
+    <string name="auto_correction_summary" msgid="5625751551134658006">"Бос орын және тыныс белгі автоматты түрде қателерді түзетеді"</string>
+    <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Өшірулі"</string>
+    <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Баяу"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"Белсенді"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"Өте белсенді"</string>
+    <string name="bigram_prediction" msgid="1084449187723948550">"Келесі сөз ұсыныстары"</string>
+    <string name="bigram_prediction_summary" msgid="3896362682751109677">"Ұсыныстар жасауда бастапқы сөзді пайдалану"</string>
+    <string name="gesture_input" msgid="826951152254563827">"Қимылмен теруді қосу"</string>
+    <string name="gesture_input_summary" msgid="9180350639305731231">"Әріптерді жанап өту арқылы сөзді енгізу"</string>
+    <string name="gesture_preview_trail" msgid="3802333369335722221">"Қимыл қадамын көрсету"</string>
+    <string name="gesture_floating_preview_text" msgid="4443240334739381053">"Динамикалық қалқымалы қарап шығу"</string>
+    <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"Қимылдау кезінде ұсынылған сөзді көру"</string>
+    <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Сақталды"</string>
+    <string name="label_go_key" msgid="1635148082137219148">"Өту"</string>
+    <string name="label_next_key" msgid="362972844525672568">"Келесі"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Алдағы"</string>
+    <string name="label_done_key" msgid="2441578748772529288">"Дайын"</string>
+    <string name="label_send_key" msgid="2815056534433717444">"Жіберу"</string>
+    <string name="label_pause_key" msgid="181098308428035340">"Тоқтата тұру"</string>
+    <string name="label_wait_key" msgid="6402152600878093134">"Күту"</string>
+    <string name="spoken_use_headphones" msgid="896961781287283493">"Дауыспен айтылатын құпия сөз кілттерін есту үшін құлақаспап қосыңыз."</string>
+    <string name="spoken_current_text_is" msgid="2485723011272583845">"Ағымдағы мәтін - %s"</string>
+    <string name="spoken_no_text_entered" msgid="7479685225597344496">"Мәтін енгізілмеген"</string>
+    <!-- no translation found for spoken_auto_correct (8005997889020109763) -->
+    <skip />
+    <!-- no translation found for spoken_auto_correct_obscured (6276420476908833791) -->
+    <skip />
+    <string name="spoken_description_unknown" msgid="3197434010402179157">"Перне коды %d"</string>
+    <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift қосулы (өшіру үшін түрту)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock қосулы (өшіру үшін түрту)"</string>
+    <string name="spoken_description_delete" msgid="8740376944276199801">"Жою"</string>
+    <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Таңбалар"</string>
+    <string name="spoken_description_to_alpha" msgid="23129338819771807">"Әріптер"</string>
+    <string name="spoken_description_to_numeric" msgid="591752092685161732">"Сандар"</string>
+    <string name="spoken_description_settings" msgid="4627462689603838099">"Баптаулар"</string>
+    <string name="spoken_description_tab" msgid="2667716002663482248">"Tab"</string>
+    <string name="spoken_description_space" msgid="2582521050049860859">"Бос орын"</string>
+    <string name="spoken_description_mic" msgid="615536748882611950">"Дауыстық енгізу"</string>
+    <string name="spoken_description_smiley" msgid="2256309826200113918">"Жымиған жүз"</string>
+    <string name="spoken_description_return" msgid="8178083177238315647">"Қалпына келтіру"</string>
+    <string name="spoken_description_search" msgid="1247236163755920808">"Іздеу"</string>
+    <string name="spoken_description_dot" msgid="40711082435231673">"Нүкте"</string>
+    <string name="spoken_description_language_switch" msgid="5507091328222331316">"Тілді ауыстыру"</string>
+    <string name="spoken_description_action_next" msgid="8636078276664150324">"Келесі"</string>
+    <string name="spoken_description_action_previous" msgid="800872415009336208">"Алдағы"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift қосылған"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock қосылған"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift өшірілген"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Таңбалар режимі"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Әріптер режимі"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Телефон режимі"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Телефон таңбалары режимі"</string>
+    <string name="announce_keyboard_hidden" msgid="8718927835531429807">"Пернетақта жасырылған"</string>
+    <string name="announce_keyboard_mode" msgid="4729081055438508321">"Көрсетілетін <xliff:g id="MODE">%s</xliff:g> пернетақтасы"</string>
+    <string name="keyboard_mode_date" msgid="3137520166817128102">"күн"</string>
+    <string name="keyboard_mode_date_time" msgid="339593358488851072">"күн мен уақыт"</string>
+    <string name="keyboard_mode_email" msgid="6216248078128294262">"электрондық пошта"</string>
+    <string name="keyboard_mode_im" msgid="1137405089766557048">"хабар алмасу"</string>
+    <string name="keyboard_mode_number" msgid="7991623440699957069">"нөмір"</string>
+    <string name="keyboard_mode_phone" msgid="6851627527401433229">"телефон"</string>
+    <string name="keyboard_mode_text" msgid="6479436687899701619">"мәтін"</string>
+    <string name="keyboard_mode_time" msgid="4381856885582143277">"уақыт"</string>
+    <string name="keyboard_mode_url" msgid="1519819835514911218">"URL"</string>
+    <string name="voice_input" msgid="3583258583521397548">"Дауыстық енгізу пернесі"</string>
+    <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Негізгі пернетақтада"</string>
+    <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Таңбалар пернетақтасында"</string>
+    <string name="voice_input_modes_off" msgid="3745699748218082014">"Өшірулі"</string>
+    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Негізгі пернетақтадағы Mic"</string>
+    <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Таңбалар пернетақтасындағы Mic"</string>
+    <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Дауыстық енгізу өшірілген"</string>
+    <string name="configure_input_method" msgid="373356270290742459">"Енгізу әдістерін теңшеу"</string>
+    <string name="language_selection_title" msgid="1651299598555326750">"Енгізу тілдері"</string>
+    <string name="send_feedback" msgid="1780431884109392046">"Кері байланыс жіберу"</string>
+    <string name="select_language" msgid="3693815588777926848">"Енгізу тілдері"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Сақтау үшін қайта түртіңіз"</string>
+    <string name="has_dictionary" msgid="6071847973466625007">"Сөздік қолжетімді"</string>
+    <string name="prefs_enable_log" msgid="6620424505072963557">"Пайдаланушының кері байланысын қосу"</string>
+    <string name="prefs_description_log" msgid="7525225584555429211">"Қолданыс статистикасын және бұзылыс есептерін автоматты түрде жіберу арқылы осы енгізу әдісінің редакторын арттыруға көмектесу"</string>
+    <string name="keyboard_layout" msgid="8451164783510487501">"Пернетақта тақырыбы"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"ағылшын (ҰБ)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"ағылшын (АҚШ)"</string>
+    <string name="subtype_es_US" msgid="5583145191430180200">"Испан (АҚШ)"</string>
+    <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"ағылшын (ҰБ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"ағылшын (АҚШ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Испан (АҚШ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <!-- no translation found for subtype_nepali_traditional (9032247506728040447) -->
+    <skip />
+    <string name="subtype_no_language" msgid="7137390094240139495">"Тіл жоқ (әліпби)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"Әліпби (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"Әліпби (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"Әліпби (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"Әліпби (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"Әліпби (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"Әліпби (ДК)"</string>
+    <!-- no translation found for subtype_emoji (7483586578074549196) -->
+    <skip />
+    <string name="custom_input_styles_title" msgid="8429952441821251512">"Реттелетін енгізу стильдері"</string>
+    <string name="add_style" msgid="6163126614514489951">"Стиль қосу"</string>
+    <string name="add" msgid="8299699805688017798">"Қосу"</string>
+    <string name="remove" msgid="4486081658752944606">"Аластау"</string>
+    <string name="save" msgid="7646738597196767214">"Сақтау"</string>
+    <string name="subtype_locale" msgid="8576443440738143764">"Тіл"</string>
+    <string name="keyboard_layout_set" msgid="4309233698194565609">"Пішім"</string>
+    <string name="custom_input_style_note_message" msgid="8826731320846363423">"Реттелетін енгізу стилі пайдалануды бастамас бұрын қосылуы қажет. Оны қазір қосу қажет пе?"</string>
+    <string name="enable" msgid="5031294444630523247">"Қосу"</string>
+    <string name="not_now" msgid="6172462888202790482">"Қазір емес"</string>
+    <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Бірдей енгізу стилі бұрыннан бар: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
+    <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Қолайлылықты зерттеу режимі"</string>
+    <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"Пернені ұзақ басу кідірісі"</string>
+    <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"Пернені басқан кездегі діріл ұзақтығы"</string>
+    <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"Пернені басқан кездегі дыбыс деңгейі"</string>
+    <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Сыртқы сөздік файлын оқу"</string>
+    <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Жүктеп алулар қалтасында сөздік файлдары жоқ"</string>
+    <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Орнату үшін сөздік файлын таңдау"</string>
+    <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"<xliff:g id="LOCALE_NAME">%s</xliff:g> үшін осы файлды орнату қажет пе?"</string>
+    <string name="error" msgid="8940763624668513648">"Қате болды"</string>
+    <string name="button_default" msgid="3988017840431881491">"Әдепкі"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> қолданбасына қош келдіңіз"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"Қимылмен теру арқылы"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Іске қосылды"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Келесі қадам"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> орнату"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> қосу"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Тіл &amp; енгізу параметрлерінде <xliff:g id="APPLICATION_NAME">%s</xliff:g> тексеріңіз. Бұл оған құрылғыңызды басқаруға рұқсат береді."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> қолданбасы әлдеқашан Тіл &amp; енгізу параметрлерінде қосылған, сол себепті бұл қадам орындалған.Келесі біреуін қосыңыз!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Параметрлер ішінде қосу"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> қолданбасына ауыстыру"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Одан кейін \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" қолданбасын белсенді мәтінді енгізу әдісі ретінде таңдаңыз."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Енгізу әдістерін ауыстыру"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Құттықтаймыз, барлығы дайын!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Қазір барлық таңдаулы қолданбаларда <xliff:g id="APPLICATION_NAME">%s</xliff:g> арқылы теруге болады."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Қосымша тілдерді теңшеу"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Аяқталған"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Қолданба белгішесін көрсету"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Іске қосу құралындағы қолданба белгішесін көрсету"</string>
+    <string name="app_name" msgid="6320102637491234792">"Сөздік провайдері"</string>
+    <string name="dictionary_provider_name" msgid="3027315045397363079">"Сөздік провайдері"</string>
+    <string name="dictionary_service_name" msgid="6237472350693511448">"Сөздік қызметі"</string>
+    <string name="download_description" msgid="6014835283119198591">"Сөздікті жаңарту ақпараты"</string>
+    <string name="dictionary_settings_title" msgid="8091417676045693313">"Қосымша сөздіктер"</string>
+    <string name="dictionary_install_over_metered_network_prompt" msgid="3587517870006332980">"Сөздік қолжетімді"</string>
+    <string name="dictionary_settings_summary" msgid="5305694987799824349">"Сөздіктер параметрлері"</string>
+    <string name="user_dictionaries" msgid="3582332055892252845">"Пайдаланушы сөздіктері"</string>
+    <string name="default_user_dict_pref_name" msgid="1625055720489280530">"Пайдаланушы сөздігі"</string>
+    <string name="dictionary_available" msgid="4728975345815214218">"Сөздік қолжетімді"</string>
+    <string name="dictionary_downloading" msgid="2982650524622620983">"Ағымда жүктеп алынуда"</string>
+    <string name="dictionary_installed" msgid="8081558343559342962">"Орнатылған"</string>
+    <string name="dictionary_disabled" msgid="8950383219564621762">"Орнатылған, өшірілген"</string>
+    <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"Сөздік қызметіне қосылу мәселесі"</string>
+    <string name="no_dictionaries_available" msgid="8039920716566132611">"Сөздіктер қолжетімсіз"</string>
+    <string name="check_for_updates_now" msgid="8087688440916388581">"Жаңарту"</string>
+    <string name="last_update" msgid="730467549913588780">"Соңғы жаңартылған"</string>
+    <string name="message_updating" msgid="4457761393932375219">"Жаңартуларды тексеру"</string>
+    <string name="message_loading" msgid="8689096636874758814">"Жүктелуде..."</string>
+    <string name="main_dict_description" msgid="3072821352793492143">"Негізгі сөздік"</string>
+    <string name="cancel" msgid="6830980399865683324">"Болдырмау"</string>
+    <string name="install_dict" msgid="180852772562189365">"Орнату"</string>
+    <string name="cancel_download_dict" msgid="7843340278507019303">"Болдырмау"</string>
+    <string name="delete_dict" msgid="756853268088330054">"Жою"</string>
+    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"Ұялы құрылғыдағы таңдалған тілде қолжетімді сөздік бар.&lt;br/&gt; Теру тәжірибесін арттыру үшін <xliff:g id="LANGUAGE">%1$s</xliff:g> сөздігін &lt;b&gt;жүктеп алуды&lt;/b&gt; ұсынамыз.&lt;br/&gt; &lt;br/&gt; Жүктеп алу 3G арқылы бір немесе екі минут алуы мүмкін. Егер &lt;b&gt;шектеусіз деректер жоспары&lt;/b&gt; болмаса, қосымша төлем алынуы мүмкін.&lt;br/&gt; Егер қай дерек жоспарына ие екеніңізді білмесеңіз, жүктеп алуды автоматты түрде бастау үшін Wi-Fi байланысын табуды ұсынамыз.&lt;br/&gt; &lt;br/&gt; Кеңес: Ұялы құрылғының &lt;b&gt;Параметрлер&lt;/b&gt; мәзіріндегі &lt;b&gt;Тіл &amp; енгізу&lt;/b&gt; параметріне өту арқылы сөздіктерді жүктеп алуға және жоюға болады."</string>
+    <string name="download_over_metered" msgid="1643065851159409546">"Қазір жүктеп алу (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g>МБ)"</string>
+    <string name="do_not_download_over_metered" msgid="2176209579313941583">"Wi-Fi арқылы жүктеп алу"</string>
+    <string name="dict_available_notification_title" msgid="6514288591959117288">"<xliff:g id="LANGUAGE">%1$s</xliff:g> үшін сөздік қолжетімді"</string>
+    <string name="dict_available_notification_description" msgid="1075194169443163487">"Қарап шығу және жүктеп алу үшін басу"</string>
+    <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Жүктеп алу: <xliff:g id="LANGUAGE">%1$s</xliff:g> ұсыныстары жақында дайын болады."</string>
+    <string name="version_text" msgid="2715354215568469385">"<xliff:g id="VERSION_NUMBER">%1$s</xliff:g> нұсқасы"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Қосу"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Сөздікке қосу"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Фраза"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Басқа талғаулар"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Аз опциялар"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"Жарайды"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Сөз:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Пернелер тіркесімі:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Тіл:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Сөзді теру"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Қосымша пернелер тіркесімі"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Сөзді өңдеу"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Өңдеу"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Жою"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Пайдаланушы сөздігінде сөздер жоқ. Қосу (+) түймесін басу арқылы сөзді қосыңыз."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Барлық тілдер үшін"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Қосымша тілдер…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Жою"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" АӘБВГҒДЕЁЖЗИЙКҚЛМНҢОӨПРСТУҰҮФХҺЦЧШЩЪЫІЬЭЮЯ"</string>
+</resources>
diff --git a/java/res/values-km-rKH/strings-appname.xml b/java/res/values-km-rKH/strings-appname.xml
new file mode 100644
index 0000000..e7b2707
--- /dev/null
+++ b/java/res/values-km-rKH/strings-appname.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="5940510615957428904">"ក្ដារចុច Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"កម្មវិធី​ពិនិត្យ​អក្ខរាវិរុទ្ធ Android  (AOSP​)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"ការ​កំណត់​ក្ដារ​ចុច Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"កំណត់​​កម្មវិធី​ពិនិត្យ​​អក្ខរាវិរុទ្ធ​សម្រាប់ ​​Android (AOSP)"</string>
+</resources>
diff --git a/java/res/values-km-rKH/strings.xml b/java/res/values-km-rKH/strings.xml
new file mode 100644
index 0000000..79a0b2c
--- /dev/null
+++ b/java/res/values-km-rKH/strings.xml
@@ -0,0 +1,246 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_input_options" msgid="3909945612939668554">"ជម្រើស​​បញ្ចូល"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"ពាក្យ​បញ្ជា​កំណត់​ហេតុ​​ការ​ស្រាវជ្រាវ"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"រក​មើល​ឈ្មោះ​ទំនាក់ទំនង"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"កម្មវិធី​ពិនិត្យ​អក្ខរាវិរុទ្ធ​ប្រើ​ធាតុ​ពី​​ក្នុង​បញ្ជី​ទំនាក់ទំនង​របស់​អ្នក"</string>
+    <string name="vibrate_on_keypress" msgid="5258079494276955460">"ញ័រ​នៅ​ពេល​ចុច​គ្រាប់ចុច"</string>
+    <string name="sound_on_keypress" msgid="6093592297198243644">"សំឡេង​នៅ​ពេល​ចុច​គ្រាប់ចុច"</string>
+    <string name="popup_on_keypress" msgid="123894815723512944">"លេច​ឡើង​នៅ​​ពេល​ចុច​គ្រាប់​ចុច"</string>
+    <string name="general_category" msgid="1859088467017573195">"ទូទៅ"</string>
+    <string name="correction_category" msgid="2236750915056607613">"ការ​កែ​អត្ថបទ"</string>
+    <string name="gesture_typing_category" msgid="497263612130532630">"បញ្ចូល​ដោយ​ប្រើ​កាយវិការ"</string>
+    <string name="misc_category" msgid="6894192814868233453">"ជម្រើស​ផ្សេងទៀត"</string>
+    <string name="advanced_settings" msgid="362895144495591463">"ការ​កំណត់​កម្រិត​ខ្ពស់"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"ជម្រើស​សម្រាប់​អ្នក​ជំនាញ"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"ប្ដូរ​ទៅ​​​វិធីសាស្ត្រ​បញ្ចូល​​​ផ្សេង​ទៀត"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"គ្រាប់ចុច​ប្ដូរ​ភាសា​តាម​វិធីសាស្ត្រ​បញ្ចូល​ផ្សេងទៀត"</string>
+    <string name="show_language_switch_key" msgid="5915478828318774384">"គ្រាប់​ចុច​ប្ដូរ​​ភាសា"</string>
+    <string name="show_language_switch_key_summary" msgid="7343403647474265713">"បង្ហាញ​នៅ​ពេល​ដែល​បើក​ភាសា​បញ្ចូល​ច្រើន"</string>
+    <string name="sliding_key_input_preview" msgid="6604262359510068370">"បង្ហាញ​ទ្រនិច​បង្ហាញ​ស្លាយ"</string>
+    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"បង្ហាញ​​សញ្ញា​មើល​​ឃើញ​ខណៈ​ពេល​ដែល​រុញ​ពី​ឆ្វេង ឬ​​គ្រាប់​ចុច​​និមិត្ត​សញ្ញា"</string>
+    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"​សោ​លេចឡើង​បោះបង់​ការ​​ពន្យារពេល"</string>
+    <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"គ្មាន​ការ​ពន្យារពេល"</string>
+    <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"លំនាំដើម"</string>
+    <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> មិល្លី​វិនាទី"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"លំនាំ​ដើម​​​ប្រព័ន្ធ"</string>
+    <string name="use_contacts_dict" msgid="4435317977804180815">"ស្នើ​ឈ្មោះ​ទំនាក់ទំនង"</string>
+    <string name="use_contacts_dict_summary" msgid="6599983334507879959">"ប្រើ​ឈ្មោះ​ពី​ទំនាក់ទំនង​សម្រាប់​ការ​​ស្នើ និង​​​កែ"</string>
+    <string name="use_double_space_period" msgid="8781529969425082860">"រយៈ​ពេល​ចុច​ដកឃ្លា​ពីរដង"</string>
+    <string name="use_double_space_period_summary" msgid="6532892187247952799">"ប៉ះ​ដកឃ្លា​ពីរ​​ដង​បញ្ចូល​​​រយៈ​ពេល​ដែល​អនុវត្ត​តាម​ដកឃ្លា"</string>
+    <string name="auto_cap" msgid="1719746674854628252">"ការ​សរសេរ​ជា​អក្សរ​ធំ​​ស្វ័យប្រវត្តិ"</string>
+    <string name="auto_cap_summary" msgid="7934452761022946874">"សរសេរ​ពាក្យ​ដំបូង​​​ជា​អក្សរ​ធំ​​នៃ​ប្រយោគ​នីមួយ​ៗ"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"វចនានុក្រម​ផ្ទាល់ខ្លួន"</string>
+    <string name="configure_dictionaries_title" msgid="4238652338556902049">"ផ្នែក​បន្ថែម​វចនានុក្រម"</string>
+    <string name="main_dictionary" msgid="4798763781818361168">"វចនានុក្រម​ចម្បង"</string>
+    <string name="prefs_show_suggestions" msgid="8026799663445531637">"បង្ហាញ​ការ​ស្នើ​​កែ"</string>
+    <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"បង្ហាញ​ពាក្យ​​បាន​​ផ្ដល់​​ស្នើ​​ខណៈ​ពេល​​​វាយ​បញ្ចូល"</string>
+    <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"បង្ហាញ​ជា​និច្ច"</string>
+    <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"បង្ហាញ​នៅ​ក្នុង​របៀប​បញ្ឈរ"</string>
+    <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"លាក់​ជានិច្ច"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"ទប់ស្កាត់​​ពាក្យ​​បំពាន"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"កុំ​ស្នើ​ឲ្យ​ពាក្យ​បំពាន​មាន​សក្ដានុពល"</string>
+    <string name="auto_correction" msgid="7630720885194996950">"ការ​កែ​​​ស្វ័យប្រវត្តិ"</string>
+    <string name="auto_correction_summary" msgid="5625751551134658006">"ចន្លោះ​មិន​ឃើញ ​និង​សញ្ញា​​វណ្ណយុត្ត​កែ​ពាក្យ​ដែល​បាន​វាយ​ខុស​ស្វ័យប្រវត្តិ"</string>
+    <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"បិទ"</string>
+    <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"ល្មម"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"បំពាន"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"បំពាន​ខ្លាំង"</string>
+    <string name="bigram_prediction" msgid="1084449187723948550">"ការ​ស្នើ​ពាក្យ​បន្ទាប់"</string>
+    <string name="bigram_prediction_summary" msgid="3896362682751109677">"ប្រើ​ពាក្យ​មុន​​នៅ​ពេល​ធ្វើ​ការ​​​ស្នើ"</string>
+    <string name="gesture_input" msgid="826951152254563827">"បើក​ការ​​បញ្ចូល​​កាយវិការ"</string>
+    <string name="gesture_input_summary" msgid="9180350639305731231">"បញ្ចូល​ពាក្យ​ដោយ​រំកិល​​​តាម​​អក្សរ"</string>
+    <string name="gesture_preview_trail" msgid="3802333369335722221">"បង្ហាញ​ដាន​កាយវិការ"</string>
+    <string name="gesture_floating_preview_text" msgid="4443240334739381053">"មើល​ការ​​អណ្ដែត​ដែល​មាន​ចលនា​ជា​មុន"</string>
+    <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"​មើល​ពាក្យ​​​ដែល​បាន​ស្នើ​​​ខណៈ​ពេល​កំពុង​ធ្វើ​កាយ​វិការ"</string>
+    <string name="added_word" msgid="8993883354622484372">"បាន​រក្សាទុក <xliff:g id="WORD">%s</xliff:g> ៖"</string>
+    <string name="label_go_key" msgid="1635148082137219148">"ទៅ"</string>
+    <string name="label_next_key" msgid="362972844525672568">"បន្ទាប់"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"មុន"</string>
+    <string name="label_done_key" msgid="2441578748772529288">"រួចរាល់"</string>
+    <string name="label_send_key" msgid="2815056534433717444">"ផ្ញើ"</string>
+    <string name="label_pause_key" msgid="181098308428035340">"ផ្អាក"</string>
+    <string name="label_wait_key" msgid="6402152600878093134">"រង់ចាំ"</string>
+    <string name="spoken_use_headphones" msgid="896961781287283493">"ដោត​កាស ដើម្បី​ស្ដាប់​ពាក្យ​សម្ងាត់។"</string>
+    <string name="spoken_current_text_is" msgid="2485723011272583845">"អត្ថបទ​បច្ចុប្បន្ន​គឺ %s"</string>
+    <string name="spoken_no_text_entered" msgid="7479685225597344496">"គ្មាន​អត្ថបទ​​​បាន​បញ្ចូល"</string>
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> កែ <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> ទៅ <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> អនុវត្ត​ការ​កែ​ដោយស្វ័យប្រវត្តិ"</string>
+    <string name="spoken_description_unknown" msgid="3197434010402179157">"កូដ​គ្រាប់​ចុច %d"</string>
+    <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"បើក Shift (​ប៉ះ​ដើម្បី​បិទ)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"បើក Caps lock (ប៉ះ​​ដើម្បី​បិទ)"</string>
+    <string name="spoken_description_delete" msgid="8740376944276199801">"Delete"</string>
+    <string name="spoken_description_to_symbol" msgid="5486340107500448969">"និមិត្ត​សញ្ញា"</string>
+    <string name="spoken_description_to_alpha" msgid="23129338819771807">"អក្សរ"</string>
+    <string name="spoken_description_to_numeric" msgid="591752092685161732">"លេខ"</string>
+    <string name="spoken_description_settings" msgid="4627462689603838099">"ការ​កំណត់"</string>
+    <string name="spoken_description_tab" msgid="2667716002663482248">"Tab"</string>
+    <string name="spoken_description_space" msgid="2582521050049860859">"ដកឃ្លា"</string>
+    <string name="spoken_description_mic" msgid="615536748882611950">"បញ្ចូលសំឡេង"</string>
+    <string name="spoken_description_smiley" msgid="2256309826200113918">"មុខ​ញញឹម"</string>
+    <string name="spoken_description_return" msgid="8178083177238315647">"Return"</string>
+    <string name="spoken_description_search" msgid="1247236163755920808">"ស្វែងរក"</string>
+    <string name="spoken_description_dot" msgid="40711082435231673">"Dot"</string>
+    <string name="spoken_description_language_switch" msgid="5507091328222331316">"ប្ដូរ​​ភាសា"</string>
+    <string name="spoken_description_action_next" msgid="8636078276664150324">"បន្ទាប់"</string>
+    <string name="spoken_description_action_previous" msgid="800872415009336208">"មុន"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"បាន​បើក Shift"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"បាន​បើក Caps lock"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"បាន​បិទ Shift"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"របៀប​និមិត្តសញ្ញា"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"របៀប​អក្សរ"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"របៀប​ទូរស័ព្ទ"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"​របៀប​និមិត្ត​សញ្ញា​ទូរស័ព្ទ"</string>
+    <string name="announce_keyboard_hidden" msgid="8718927835531429807">"បាន​លាក់​ក្ដារចុច"</string>
+    <string name="announce_keyboard_mode" msgid="4729081055438508321">"បង្ហាញ​ក្ដារ​ចុច <xliff:g id="MODE">%s</xliff:g>"</string>
+    <string name="keyboard_mode_date" msgid="3137520166817128102">"កាលបរិច្ឆេទ"</string>
+    <string name="keyboard_mode_date_time" msgid="339593358488851072">"កាល​បរិច្ឆេទ​ និង​ពេល​វេលា"</string>
+    <string name="keyboard_mode_email" msgid="6216248078128294262">"អ៊ីមែល"</string>
+    <string name="keyboard_mode_im" msgid="1137405089766557048">"​ផ្ញើ​សារ"</string>
+    <string name="keyboard_mode_number" msgid="7991623440699957069">"លេខ"</string>
+    <string name="keyboard_mode_phone" msgid="6851627527401433229">"ទូរស័ព្ទ"</string>
+    <string name="keyboard_mode_text" msgid="6479436687899701619">"អត្ថបទ"</string>
+    <string name="keyboard_mode_time" msgid="4381856885582143277">"ពេលវេលា"</string>
+    <string name="keyboard_mode_url" msgid="1519819835514911218">"URL"</string>
+    <string name="voice_input" msgid="3583258583521397548">"គ្រាប់​ចុច​បញ្ចូល​​សំឡេង"</string>
+    <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"នៅ​លើ​ក្ដារចុច​ចម្បង"</string>
+    <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"នៅ​លើ​ក្ដារចុច​​និមិត្ត​សញ្ញា"</string>
+    <string name="voice_input_modes_off" msgid="3745699748218082014">"បិទ"</string>
+    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"មីក្រូហ្វូន​នៅ​លើ​​ក្ដារចុច​ចម្បង"</string>
+    <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"មីក្រូហ្វូន​នៅ​លើ​​ក្ដារចុច​និមិត្ត​សញ្ញា"</string>
+    <string name="voice_input_modes_summary_off" msgid="63875609591897607">"បាន​បិទ​ការ​បញ្ចូល​សំឡេង"</string>
+    <string name="configure_input_method" msgid="373356270290742459">"កំណត់​រចនាសម្ព័ន្ធ​វិធីសាស្ត្រ​បញ្ចូល"</string>
+    <string name="language_selection_title" msgid="1651299598555326750">"បញ្ចូល​ភាសា"</string>
+    <string name="send_feedback" msgid="1780431884109392046">"ផ្ញើ​មតិ​អ្នក​ប្រើ"</string>
+    <string name="select_language" msgid="3693815588777926848">"​​បញ្ចូល​ភាសា"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"ប៉ះ​ម្ដង​ទៀត​ ដើម្បី​រក្សា​ទុក"</string>
+    <string name="has_dictionary" msgid="6071847973466625007">"មាន​វចនានុក្រម"</string>
+    <string name="prefs_enable_log" msgid="6620424505072963557">"បើក​មតិត្រឡប់"</string>
+    <string name="prefs_description_log" msgid="7525225584555429211">"ជំនួយ​​​ធ្វើ​ឲ្យ​​ប្រសើរ​ឡើង​​នៃ​កម្មវិធី​កែ​​វិធី​សាស្ត្រ​​បញ្ចូល​ដោយ​ស្វ័យ​ប្រវត្តិ​ ដោយ​ផ្ញើ​ស្ថិតិ​​ប្រើ​ប្រាស់​ ​និង​របាយការណ៍​គាំង"</string>
+    <string name="keyboard_layout" msgid="8451164783510487501">"រូបរាង​ក្ដារចុច"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"អង់គ្លេស (​អង់គ្លេស)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"អង់គ្លេស (សហរដ្ឋ​អាមេរិក)"</string>
+    <string name="subtype_es_US" msgid="5583145191430180200">"អេស្ប៉ាញ (សហរដ្ឋ​អាមេរិក​)"</string>
+    <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"អង់គ្លេស (ចក្រភព​អង់គ្លេស) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"អង់គ្លេស (អាមេរិក) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"អេស្ប៉ាញ (អាមេរិក​) ( <xliff:g id="LAYOUT">%s</xliff:g> )"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (អក្សរ​ពេញ​)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"គ្មាន​ភាសា (អក្សរ​ក្រម)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"តាម​លំដាប់​អក្សរក្រម (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"តាម​លំដាប់​អក្សរក្រម (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"តាម​លំដាប់​អក្សរក្រម (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"តាម​លំដាប់​អក្សរក្រម (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"តាម​លំដាប់​អក្សរក្រម (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"តាម​លំដាប់​អក្សរក្រម (កុំព្យូទ័រ)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"សញ្ញា​អារម្មណ៍"</string>
+    <string name="custom_input_styles_title" msgid="8429952441821251512">"រចនាប័ទ្ម​បញ្ចូល​ផ្ទាល់ខ្លួន"</string>
+    <string name="add_style" msgid="6163126614514489951">"បន្ថែម​រចនាប័ទ្ម"</string>
+    <string name="add" msgid="8299699805688017798">"បន្ថែម"</string>
+    <string name="remove" msgid="4486081658752944606">"លុប​ចេញ"</string>
+    <string name="save" msgid="7646738597196767214">"រក្សាទុក"</string>
+    <string name="subtype_locale" msgid="8576443440738143764">"ភាសា"</string>
+    <string name="keyboard_layout_set" msgid="4309233698194565609">"ប្លង់"</string>
+    <string name="custom_input_style_note_message" msgid="8826731320846363423">"ចាំបាច់​ត្រូវ​បើក​រចនាប័ទ្ម​បញ្ចូល​ផ្ទាល់​ខ្លួន​របស់​អ្នក មុន​ពេល​អ្នក​ចាប់ផ្ដើម​ប្រើ​វា។ តើ​អ្នក​ចង់​បើក​វា​ឥឡូវ​នេះ​ឬ?"</string>
+    <string name="enable" msgid="5031294444630523247">"បើក"</string>
+    <string name="not_now" msgid="6172462888202790482">"មិនមែន​ឥឡូវ"</string>
+    <string name="custom_input_style_already_exists" msgid="8008728952215449707">"មាន​រចនាប័ទ្ម​បញ្ចូល​ដូច​គ្នា​ដូច​ហើយ៖ <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
+    <string name="prefs_usability_study_mode" msgid="1261130555134595254">"របៀប​ការ​សិក្សា​ដែល​អាច​ប្រើ​បាន"</string>
+    <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"ពន្យារពេល​​​ចុច​គ្រាប់​ចុច​ឲ្យ​​យូរ"</string>
+    <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"ថិរវេលា​​ញ័រ​​ពេល​ចុច​គ្រាប់ចុច"</string>
+    <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"កម្រិត​សំឡេង​ពេល​ចុច​គ្រាប់​ចុច"</string>
+    <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"អាន​ឯកសារ​វចនានុក្រម​ខាង​ក្រៅ"</string>
+    <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"គ្មាន​ឯកសារ​វចនានុក្រម​នៅ​ក្នុង​ថត​ទាញ​យក"</string>
+    <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"ជ្រើស​ឯកសារ​វចនានុក្រម​ ដើម្បី​ដំឡើង"</string>
+    <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"ពិត​ជា​ដំឡើង​ឯកសារ​នេះ​សម្រាប់ <xliff:g id="LOCALE_NAME">%s</xliff:g> ឬ?"</string>
+    <string name="error" msgid="8940763624668513648">"មាន​កំហុស"</string>
+    <string name="button_default" msgid="3988017840431881491">"លំនាំដើម"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"សូម​ស្វាគមន៍​មក​កាន់ <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"ជាមួយ​​​ការ​វាយ​បញ្ចូល​ដោយ​ប្រើ​​​កាយវិការ"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"បាន​ចាប់ផ្ដើម"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"ជំហាន​បន្ទាប់"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"រៀបចំ <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"បើក <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"សូម​ពិនិត្យ​ \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" នៅ​ក្នុង​ការ​កំណត់​ភាសា &amp; និង​ការ​បញ្ចូល​របស់​អ្នក។ វា​នឹង​ដំណើរការ​នៅ​លើ​ឧបករណ៍​របស់​អ្នក។"</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>បាន​បើក​នៅ​ក្នុង​​ការ​កំណត់​​ភាសា​ &amp; ការ​បញ្ចូល​របស់ ដូច្នេះ​ជំហាន​នេះ​រួចរាល់​ហើយ។ បន្ត​ទៅ​ជំហាន​បន្ទាប់!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"បើក​នៅ​ក្នុង​ការ​កំណត់"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"ប្ដូរ​ទៅ <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"បន្ទាប់ ជ្រើស \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" ជា​វិធី​សាស្ត្រ​បញ្ចូល​អត្ថបទ​សកម្ម​របស់​អ្នក។"</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"ប្ដូរ​វិធីសាស្ត្រ​បញ្ចូល"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"សូម​អបអរ​សាទរ,​ អ្នក​​បាន​កំណត់​​រួចរាល់​ហើយ!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"ឥឡូវ​​អ្នក​អាច​​វាយ​បញ្ចូល​នៅ​ក្នុង​​កម្មវិធី​​ពេញ​ចិត្ត​របស់​អ្នក​ទាំងអស់​ជាមួយ <xliff:g id="APPLICATION_NAME">%s</xliff:g> ។"</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"កំណត់​រចនា​សម្ព័ន្ធ​ភាសា​បន្ថែម"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"បាន​បញ្ចប់"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"បង្ហាញ​រូប​តំណាង​កម្មវិធី"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"បង្ហាញ​រូប​តំណាង​កម្មវិធី​នៅ​ក្នុង​កម្ម​​វិធី​ចាប់ផ្ដើម"</string>
+    <string name="app_name" msgid="6320102637491234792">"កម្មវិធី​ផ្ដល់​វចនានុក្រម"</string>
+    <string name="dictionary_provider_name" msgid="3027315045397363079">"កម្មវិធី​ផ្ដល់​វចនានុក្រម"</string>
+    <string name="dictionary_service_name" msgid="6237472350693511448">"សេវាកម្ម​​វចនានុក្រម"</string>
+    <string name="download_description" msgid="6014835283119198591">"ព័ត៌មាន​បច្ចុប្បន្នភាព​វចនានុក្រម"</string>
+    <string name="dictionary_settings_title" msgid="8091417676045693313">"ផ្នែក​បន្ថែម​វចនានុក្រម"</string>
+    <string name="dictionary_install_over_metered_network_prompt" msgid="3587517870006332980">"វចនានុក្រម​​​​​អាច​ប្រើ​បាន"</string>
+    <string name="dictionary_settings_summary" msgid="5305694987799824349">"ការ​កំណត់​សម្រាប់​វចនានុក្រម"</string>
+    <string name="user_dictionaries" msgid="3582332055892252845">"វចនានុក្រម​​​អ្នក​ប្រើ"</string>
+    <string name="default_user_dict_pref_name" msgid="1625055720489280530">"វចនានុក្រម​អ្នកប្រើ"</string>
+    <string name="dictionary_available" msgid="4728975345815214218">"វចនានុក្រម​​​អាច​ប្រើ​បាន"</string>
+    <string name="dictionary_downloading" msgid="2982650524622620983">"បច្ចុប្បន្ន​កំពុង​ទាញ​យក"</string>
+    <string name="dictionary_installed" msgid="8081558343559342962">"បាន​ដំឡើង"</string>
+    <string name="dictionary_disabled" msgid="8950383219564621762">"បាន​ដំឡើង បាន​បិទ"</string>
+    <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"មាន​បញ្ហា​ក្នុង​ការ​​ភ្ជាប់​ទៅ​​សេវា​កម្ម​វចនានុក្រម"</string>
+    <string name="no_dictionaries_available" msgid="8039920716566132611">"គ្មាន​វចនានុក្រម"</string>
+    <string name="check_for_updates_now" msgid="8087688440916388581">"ផ្ទុក​ឡើងវិញ"</string>
+    <string name="last_update" msgid="730467549913588780">"បាន​ធ្វើ​បច្ចុប្បន្នភាព​ចុងក្រោយ"</string>
+    <string name="message_updating" msgid="4457761393932375219">"ពិនិត្យមើល​បច្ចុប្បន្នភាព"</string>
+    <string name="message_loading" msgid="8689096636874758814">"កំពុង​ផ្ទុក..."</string>
+    <string name="main_dict_description" msgid="3072821352793492143">"វចនានុក្រម​ចម្បង"</string>
+    <string name="cancel" msgid="6830980399865683324">"បោះ​បង់"</string>
+    <string name="install_dict" msgid="180852772562189365">"ដំឡើង"</string>
+    <string name="cancel_download_dict" msgid="7843340278507019303">"បោះ​បង់"</string>
+    <string name="delete_dict" msgid="756853268088330054">"លុប"</string>
+    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"ភាសា​ដែល​បាន​ជ្រើស​នៅ​លើ​ឧបករណ៍​របស់​អ្នក​មាន​វចនានុក្រម។ &lt;br/&gt; យើង​បាន​ផ្ដល់​អនុសាសន៍ &lt;b&gt;ទាញ​យក​&lt;/b&gt;  <xliff:g id="LANGUAGE">%1$s</xliff:g> វចនានុក្រម ដើម្បី​ធ្វើ​ឲ្យ​ការ​វាយ​បញ្ចូល​របស់​អ្នក​ប្រសើរ​ឡើង។ &lt;br/&gt; &lt;br/&gt; ការ​ទាញ​យក​អាច​ចំណាយ​ពេល​​មួយ ឬ​ពីរ​នាទី​​​តាម 3G ។ ការ​​កាត់​លុយ​អាច​អនុវត្ត​ ប្រសិន​​​បើ​អ្នក​​បាន​​ &lt;b&gt;កំណត់​ទិន្នន័យ​គ្មាន​ដែន​កំណត់ &lt;/b&gt;.&lt;br/&gt; ប្រសិនបើ​​អ្នក​មិន​ប្រាកដ​​ថា​ទិន្នន័យ​អ្នក​​មិន​បាន​​កំណត់ យើង​បាន​ផ្ដល់​អនុសាសន៍​ដោយ​ស្វែងរក​ការ​ភ្ជាប់​​វ៉ាយហ្វាយ ដើម្បី​ចាប់ផ្ដើម​ទាញ​យក​ដោយ​ស្វ័យប្រវត្តិ។&lt;br/&gt; &lt;br/&gt; ព័ត៌មាន​ជំនួយ៖ អ្នក​អាច​ទាញ​យក និង​លុប​​វចនានុក្រម​​ដោយ​ចូល​ទៅ​ &lt;b&gt;ភាសា&amp; បញ្ចូល&lt;/b&gt;​នៅ​ក្នុង​ម៉ឺនុយ &lt;b&gt;ការ​កំណត់ &lt;/b&gt; របស់​ឧបករណ៍​ចល័ត។"</string>
+    <string name="download_over_metered" msgid="1643065851159409546">"ទាញ​យក​ឥឡូវ​នេះ (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g> មេកាបៃ)"</string>
+    <string name="do_not_download_over_metered" msgid="2176209579313941583">"ទាញ​យក​តាម​វ៉ាយហ្វាយ"</string>
+    <string name="dict_available_notification_title" msgid="6514288591959117288">"វចនានុក្រម​​អាច​ប្រើ​បាន​​សម្រាប់ <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
+    <string name="dict_available_notification_description" msgid="1075194169443163487">"ចុច​ ដើម្បី​ពិនិត្យ​មើល​ឡើង​​វិញ​ និង​ទាញ​យក"</string>
+    <string name="toast_downloading_suggestions" msgid="1313027353588566660">"ទាញ​យក៖ ការ​​ស្នើ​សម្រាប់ <xliff:g id="LANGUAGE">%1$s</xliff:g> នឹង​បញ្ចប់​ឆាប់ៗ។"</string>
+    <string name="version_text" msgid="2715354215568469385">"កំណែ <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"បន្ថែម"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"បន្ថែម​ទៅ​វចនានុក្រម"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"ឃ្លា"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"ជម្រើស​ច្រើន"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"ជម្រើស​តិច"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"យល់ព្រម"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"ពាក្យ៖"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"ផ្លូវកាត់​៖"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"ភាសា៖"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"វាយ​បញ្ចូល​ពាក្យ"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"ផ្លូវកាត់​ជា​ជម្រើស"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"កែ​ពាក្យ"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"កែ"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"លុប"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"អ្នក​មិន​មាន​ពាក្យ​ណាមួយ​នៅ​ក្នុង​វចនានុក្រម​អ្នក​ប្រើ​ទេ។ បន្ថែម​ពាក្យ​ដោយ​​​​ប៉ះ​ប៊ូតុង​ បន្ថែម ​ (+)។"</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"សម្រាប់​ភាសា​ទាំងអស់"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"ភាសា​ច្រើន​ទៀត…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"លុប"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+</resources>
diff --git a/java/res/values-ko/strings.xml b/java/res/values-ko/strings.xml
index 1497812..255e234 100644
--- a/java/res/values-ko/strings.xml
+++ b/java/res/values-ko/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"비밀번호 키를 음성으로 들으려면 헤드셋을 연결하세요."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"입력한 텍스트: %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"입력한 텍스트 없음"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g>을(를) 누르면 <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g>을(를) <xliff:g id="CORRECTED">%3$s</xliff:g>(으)로 수정합니다."</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g>을(를) 누르면 자동 수정됩니다."</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"키 코드 %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"시프트 키"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift 사용(사용하지 않으려면 탭하세요.)"</string>
diff --git a/java/res/values-lo-rLA/strings-appname.xml b/java/res/values-lo-rLA/strings-appname.xml
new file mode 100644
index 0000000..17a0094
--- /dev/null
+++ b/java/res/values-lo-rLA/strings-appname.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="5940510615957428904">"ແປ້ນພິມ Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"ໂຕກວດການສະກົດຄຳໃນ Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"ຕັ້ງຄ່າແປ້ນພິມ Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Android Spell Checker Settings (AOSP)"</string>
+</resources>
diff --git a/java/res/values-lo-rLA/strings.xml b/java/res/values-lo-rLA/strings.xml
new file mode 100644
index 0000000..1597f92
--- /dev/null
+++ b/java/res/values-lo-rLA/strings.xml
@@ -0,0 +1,246 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_input_options" msgid="3909945612939668554">"ຕົວເລືອກການປ້ອນຂໍ້ມູນ"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"Research Log Commands"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"ເບິ່ງທີ່ຊື່ຂອງລາຍຊື່ຜູ່ຕິດຕໍ່"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"ໂຕຊ່ວຍສະກົດໃຊ້ຂໍ້ມູນຈາກລາຍການຂອງລາຍຊື່ຜູ່ຕິດຕໍ່ຂອງທ່ານ"</string>
+    <string name="vibrate_on_keypress" msgid="5258079494276955460">"ສັ່ນເຕືອນເມື່ອພິມ"</string>
+    <string name="sound_on_keypress" msgid="6093592297198243644">"ສຽງໃນການກົດປຸ່ມ"</string>
+    <string name="popup_on_keypress" msgid="123894815723512944">"ໂຕອັກສອນເວລາພິມ"</string>
+    <string name="general_category" msgid="1859088467017573195">"ທົ່ວໄປ"</string>
+    <string name="correction_category" msgid="2236750915056607613">"ໂຕຊ່ວຍແປງຂໍ້ຄວາມ"</string>
+    <string name="gesture_typing_category" msgid="497263612130532630">"ການພິມແບບ Gesture"</string>
+    <string name="misc_category" msgid="6894192814868233453">"ໂຕເລືອກ​ອື່ນໆ"</string>
+    <string name="advanced_settings" msgid="362895144495591463">"ການຕັ້ງຄ່າຂັ້ນສູງ"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"ຕົວເລືອກສຳລັບຜູ່ທີ່ຊຳນານ"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"ປ່ຽນໄປໃຊ້ການປ້ອນຂໍ້ມູນແບບອື່ນ"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"ໂຕປ່ຽນພາສາເປັນທັງໂຕປ່ຽນຮູບແບບການປ້ອນຂໍ້ມູນເຊັ່ນກັນ"</string>
+    <string name="show_language_switch_key" msgid="5915478828318774384">"ປຸ່ມປ່ຽນພາສາ"</string>
+    <string name="show_language_switch_key_summary" msgid="7343403647474265713">"ສະແດງໃນເວລາທີ່ຕົວເລືອກການປ້ອນຂໍ້ມູນຫຼາຍໂຕຖືກເປີດຢູ່"</string>
+    <string name="sliding_key_input_preview" msgid="6604262359510068370">"ສະແດງໂຕບົ່ງບອກການສະໄລ້"</string>
+    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"ສະແດງແນວທາງໃນຂະນະທີ່ສະໄລ້ຈາກ Shift ຫຼື ປຸ່ມເຄື່ອງໝາຍ"</string>
+    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"ໄລຍະເວລາການສະແດງໂຕອັກສອນ"</string>
+    <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"ບໍ່ຕ້ອໜ່ວງເວລາ"</string>
+    <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"ຄ່າເລີ່ມຕົ້ນ"</string>
+    <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"ຄ່າເລີ່ມຕົ້ນຂອງລະບົບ"</string>
+    <string name="use_contacts_dict" msgid="4435317977804180815">"ແນະນຳລາຍຊື່ຜູ່ຕິດຕໍ່"</string>
+    <string name="use_contacts_dict_summary" msgid="6599983334507879959">"ໃຊ້ຊື່ຈາກລາຍຊື່ຜູ່ຕິດຕໍ່ສຳລັບການແນະນຳ ແລະ ການຊ່ວຍແກ້ຄຳ"</string>
+    <string name="use_double_space_period" msgid="8781529969425082860">"ຍະຫວ່າງສອງເທື່ອເພື່ອໃສ່ຈ້ຳເມັດ"</string>
+    <string name="use_double_space_period_summary" msgid="6532892187247952799">"ກົດທີ່ປຸ່ມຍະຫວ່າງສອງເທື່ອເພື່ອໃສ່ຈ້ຳເມັດແລ້ວຕາມດ້ວຍການຍະຫວ່າງ"</string>
+    <string name="auto_cap" msgid="1719746674854628252">"ເຮັດໂຕພິມໃຫຍ່ອັດຕະໂນມັດ"</string>
+    <string name="auto_cap_summary" msgid="7934452761022946874">"ເຮັດໂຕພິມໃຫຍ່ໃຫ້ໂຕອັກສອນທຳອິດຂອງແຕ່ລໃນປະໂຫຍກ"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"ວັດຈະນານຸກົມສ່ວນໂຕ"</string>
+    <string name="configure_dictionaries_title" msgid="4238652338556902049">"ໂຕເສີມວັດຈະນານຸກົມ"</string>
+    <string name="main_dictionary" msgid="4798763781818361168">"ວັດຈະນານຸກົມຫຼັກ"</string>
+    <string name="prefs_show_suggestions" msgid="8026799663445531637">"ສະແດງການແນະນຳຄຳທີ່ຖືກຕ້ອງ"</string>
+    <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"ສະແດງຄຳສັບທີ່ແນະນຳໃນເວລາທີ່ກຳລັງພິມ"</string>
+    <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"ສະແດງຕະຫລອດ"</string>
+    <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"ກົດທີ່ຄຳສັບທີ່ພິມລົງໄປເພື່ອແປງໃຫ້ມັນຖືກຕ້ອງ"</string>
+    <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"ເຊື່ອງໄວ້ຕະຫລອດ"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"ປິດກັ້ນຄຳທີ່ບໍ່ສຸພາບ"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"ຫ້າມແນະນຳຄຳທີ່ບໍ່ສຸພາບ"</string>
+    <string name="auto_correction" msgid="7630720885194996950">"ໂຕຊ່ວຍສະກົດຄຳ"</string>
+    <string name="auto_correction_summary" msgid="5625751551134658006">"ການຍະຫວ່າງ ແລະ ການໃສ່ເຄື່ອງໝາຍຈະຖືກປ່ຽນແປງໃຫ້ຖືກຕ້ອງ ໃນຄຳທີ່ພິມຜິດໂດຍອັດຕະໂນມັດ"</string>
+    <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"ປິດ"</string>
+    <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"ປານກາງ"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"ສູງ"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"ສູງສຸດ"</string>
+    <string name="bigram_prediction" msgid="1084449187723948550">"ການແນະນຳຄຳຕໍ່ໄປ"</string>
+    <string name="bigram_prediction_summary" msgid="3896362682751109677">"ໃຊ້ຄຳທີ່ຜ່ານມາໃນການແນະນຳຄຳ"</string>
+    <string name="gesture_input" msgid="826951152254563827">"ເປີດນຳໃຊ້ການພິມແບບ Gesture"</string>
+    <string name="gesture_input_summary" msgid="9180350639305731231">"ໃສ່ຄຳສັບລົງໄປໂດຍການສະໄລ້ຜ່ານໂຕອັກສອນ"</string>
+    <string name="gesture_preview_trail" msgid="3802333369335722221">"ສະແດງຫາງຂອງ Gesture"</string>
+    <string name="gesture_floating_preview_text" msgid="4443240334739381053">"ມີຄຳຕົວຢ່າງລອຍຂຶ້ນມາ"</string>
+    <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"ເບິ່ງຄຳທີ່ຖືກແນະນຳໃນເວລາທີ່ກຳລັງຊີ້"</string>
+    <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : ບັນທຶກແລ້ວ"</string>
+    <string name="label_go_key" msgid="1635148082137219148">"ໄປ"</string>
+    <string name="label_next_key" msgid="362972844525672568">"ຕໍ່ໄປ"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"ກ່ອນໜ້າ"</string>
+    <string name="label_done_key" msgid="2441578748772529288">"ແລ້ວໆ"</string>
+    <string name="label_send_key" msgid="2815056534433717444">"ສົ່ງ"</string>
+    <string name="label_pause_key" msgid="181098308428035340">"ຄ້າງໄວ້"</string>
+    <string name="label_wait_key" msgid="6402152600878093134">"ລໍຖ້າ"</string>
+    <string name="spoken_use_headphones" msgid="896961781287283493">"ສຽບສາຍຫູຟັງເພື່ອຟັງລະຫັດຜ່ານ."</string>
+    <string name="spoken_current_text_is" msgid="2485723011272583845">"ຂໍ້ຄວາມປະຈຸບັນແມ່ນ %s"</string>
+    <string name="spoken_no_text_entered" msgid="7479685225597344496">"ບໍ່ມີການໃສ່ຂໍ້ຄວາມ"</string>
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> ແກ້ໄຂ <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> ເປັນ <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> ປະຕິບັດການແປງຄຳຜິດອັດຕະໂນມັດ"</string>
+    <string name="spoken_description_unknown" msgid="3197434010402179157">"ລະຫັດກະແຈ %d"</string>
+    <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift ເປີດນຳໃຊ້ຢູ່ (ກົດເພື່ອປິດນຳໃຊ້)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock ເປີດຢູ່ (ກົດເພື່ອປິດນຳໃຊ້)"</string>
+    <string name="spoken_description_delete" msgid="8740376944276199801">"ລຶບ"</string>
+    <string name="spoken_description_to_symbol" msgid="5486340107500448969">"ສັນຍາລັກ"</string>
+    <string name="spoken_description_to_alpha" msgid="23129338819771807">"ໂຕອັກ​ສອນ"</string>
+    <string name="spoken_description_to_numeric" msgid="591752092685161732">"ໂຕເລກ"</string>
+    <string name="spoken_description_settings" msgid="4627462689603838099">"ການຕັ້ງຄ່າ"</string>
+    <string name="spoken_description_tab" msgid="2667716002663482248">"ແທັບ"</string>
+    <string name="spoken_description_space" msgid="2582521050049860859">"ຍະຫວ່າງ"</string>
+    <string name="spoken_description_mic" msgid="615536748882611950">"ການປ້ອນຂໍ້ມູນດ້ວຍສຽງ"</string>
+    <string name="spoken_description_smiley" msgid="2256309826200113918">"ຮອຍຍິ້ມ"</string>
+    <string name="spoken_description_return" msgid="8178083177238315647">"ກັບຄືນ"</string>
+    <string name="spoken_description_search" msgid="1247236163755920808">"ຊອກຫາ"</string>
+    <string name="spoken_description_dot" msgid="40711082435231673">"ຈ້ຳ"</string>
+    <string name="spoken_description_language_switch" msgid="5507091328222331316">"ສະລັບພາສາ"</string>
+    <string name="spoken_description_action_next" msgid="8636078276664150324">"ຕໍ່ໄປ"</string>
+    <string name="spoken_description_action_previous" msgid="800872415009336208">"ກ່ອນໜ້າ"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift ເປີດນຳໃຊ້ຢູ່"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock ເປີດນຳໃຊ້ຢູ່"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift ປິດນຳໃຊ້ຢູ່"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"ໂຫມດສັນຍາລັກ"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"ໂຫມດ​ໂຕອັກ​ສອນ"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"ໂຫມດໂທລະສັບ"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"ໂຫມດສັນຍາລັກໂທລະສັບ"</string>
+    <string name="announce_keyboard_hidden" msgid="8718927835531429807">"ແປ້ນ​ພິມ​ເຊື່ອງ​ໄວ້"</string>
+    <string name="announce_keyboard_mode" msgid="4729081055438508321">"ກຳລັງສະແດງແປ້ນພິມ <xliff:g id="MODE">%s</xliff:g>"</string>
+    <string name="keyboard_mode_date" msgid="3137520166817128102">"ວັນທີ"</string>
+    <string name="keyboard_mode_date_time" msgid="339593358488851072">"ວັນ​ທີ​ແລະ​ເວ​ລາ"</string>
+    <string name="keyboard_mode_email" msgid="6216248078128294262">"email"</string>
+    <string name="keyboard_mode_im" msgid="1137405089766557048">"ຂໍ້ຄວາມ"</string>
+    <string name="keyboard_mode_number" msgid="7991623440699957069">"ໂຕເລກ"</string>
+    <string name="keyboard_mode_phone" msgid="6851627527401433229">"ໂທລະສັບ"</string>
+    <string name="keyboard_mode_text" msgid="6479436687899701619">"ຂໍ້ຄວາມ"</string>
+    <string name="keyboard_mode_time" msgid="4381856885582143277">"ເວລາ"</string>
+    <string name="keyboard_mode_url" msgid="1519819835514911218">"URL"</string>
+    <string name="voice_input" msgid="3583258583521397548">"ປຸ່ມປ້ອນຂໍ້ມູນດ້ວຍສຽງ"</string>
+    <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"ແປ້ນພິມຫຼັກ"</string>
+    <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"ໃນແປ້ນພິມສັນຍາລັກ"</string>
+    <string name="voice_input_modes_off" msgid="3745699748218082014">"ປິດ"</string>
+    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"ໄມໃນແປ້ນພິມຫຼັກ"</string>
+    <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"ໄມໃນແປ້ນພິມສັນຍາລັກ"</string>
+    <string name="voice_input_modes_summary_off" msgid="63875609591897607">"ການປ້ອນຂໍ້ມູນດ້ວຍສຽງປິດນຳໃຊ້ຢູ່"</string>
+    <string name="configure_input_method" msgid="373356270290742459">"ຕັ້ງຄ່າຮູບແບບການປ້ອນຂໍ້ມູນ"</string>
+    <string name="language_selection_title" msgid="1651299598555326750">"ພາສາການປ້ອນຂໍ້ມູນ"</string>
+    <string name="send_feedback" msgid="1780431884109392046">"ສົ່ງຄຳຕິຊົມ"</string>
+    <string name="select_language" msgid="3693815588777926848">"ພາສາການປ້ອນຂໍ້ມູນ"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"ກົດອີກຄັ້ງເພື່ອບັນທຶກ"</string>
+    <string name="has_dictionary" msgid="6071847973466625007">"ມີວັດຈະນານຸກົມ"</string>
+    <string name="prefs_enable_log" msgid="6620424505072963557">"ເປີດນຳໃຊ້ຄຳຕິຊົມຈາກຜູ່ໃຊ້"</string>
+    <string name="prefs_description_log" msgid="7525225584555429211">"ຊ່ວຍເພີ່ມປະສິດທິພາບໂຕແກ້ໄຂການປ້ອນຂໍ້ມູນ ໂດຍການສົ່ງສະຖິຕິການນຳໃຊ້ ແລະການລາຍການຂໍ້ຜິດພາດໂດຍອັດຕະໂນມັດ"</string>
+    <string name="keyboard_layout" msgid="8451164783510487501">"ສີສັນແປ້ນພິມ"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"ອັງກິດ (ສະຫະລາດຊະອານາຈັກ)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"ອັງກິດ (ສະຫະລັດຯ)"</string>
+    <string name="subtype_es_US" msgid="5583145191430180200">"ສະເປນ (ອາເມລິກາ)"</string>
+    <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"ພາສາອັງກິດ (ອັງກິດ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"ອັງກິດ (ອາເມລິກາ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"ແອສປາໂຍນ (ສະ​ຫະ​ລັດ​) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (ດັ້ງເດີມ)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"ບໍ່ມີພາສາ (ໂຕອັກສອນ)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"ໂຕອັກສອນ (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"ໂຕອັກສອນ (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"ໂຕອັກສອນ (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"ໂຕອັກສອນ (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"ໂຕອັກສອນ (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"ໂຕອັກສອນ (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"ອີໂມຈິ"</string>
+    <string name="custom_input_styles_title" msgid="8429952441821251512">"ຮູບແບບການປ້ອນຂໍ້ມູນສ່ວນຕົວ"</string>
+    <string name="add_style" msgid="6163126614514489951">"ເພີ່ມຮູບແບບ"</string>
+    <string name="add" msgid="8299699805688017798">"ເພີ່ມ"</string>
+    <string name="remove" msgid="4486081658752944606">"ລຶບອອກ"</string>
+    <string name="save" msgid="7646738597196767214">"ບັນທຶກ"</string>
+    <string name="subtype_locale" msgid="8576443440738143764">"ພາສາ"</string>
+    <string name="keyboard_layout_set" msgid="4309233698194565609">"ຮູບແບບ"</string>
+    <string name="custom_input_style_note_message" msgid="8826731320846363423">"ຮູບແບບການປ້ອນຂໍ້ມູນແບບສ່ວນຕົວຂອງທ່ານ ຕ້ອງຖືກເປີດນຳໃຊ້ຢູ່ກ່ອນທີ່ທ່ານຈະສາມາດໃຊ້ມັນໄດ້. ທ່ານຕ້ອງການທີ່ຈະເປີດໃຊ້ມັນດຽວນີ້ບໍ່?"</string>
+    <string name="enable" msgid="5031294444630523247">"ເປີດນຳໃຊ້"</string>
+    <string name="not_now" msgid="6172462888202790482">"ບໍ່ແມ່ນຕອນນີ້"</string>
+    <string name="custom_input_style_already_exists" msgid="8008728952215449707">"ຮູບແບບການປ້ອນຂໍ້ມູນທີ່ຄືກັນມີຢູ່ແລ້ວ: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
+    <string name="prefs_usability_study_mode" msgid="1261130555134595254">"ໂໝດການສຶກສາ Usability"</string>
+    <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"ໄລຍະເວລາຂອງການກົດປຸ່ມ"</string>
+    <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"ໄລຍະເວລາຂອງການສັ່ນໃນການກົດປຸ່ມ"</string>
+    <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"ລະດັບສຽງຂອງການກົດປຸ່ມ"</string>
+    <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"ອ່ານໄຟລ໌ວັດຈະນານຸກົມພາຍນອກ"</string>
+    <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"ບໍ່ມີໄຟລ໌ວັດຈະນານຸກົມໃນໂຟນເດີຂອງການດາວໂຫລດ"</string>
+    <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"ເລືອກໄຟລ໌ວັດຈະນານຸກົມເພື່ອຕິດຕັ້ງ"</string>
+    <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"ຕິດຕັ້ງໄຟລ໌ນີ້ສຳລັບ <xliff:g id="LOCALE_NAME">%s</xliff:g> ແທ້ບໍ່?"</string>
+    <string name="error" msgid="8940763624668513648">"ມີຂໍ້ຜິດພາດເກີດຂຶ້ນ"</string>
+    <string name="button_default" msgid="3988017840431881491">"ຄ່າເລີ່ມຕົ້ນ"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"ຍິນ​ດີ​ຕ້ອນ​ຮັບສູ່ <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"ດ້ວຍການພິມແບບ Gesture"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"ເລີ່ມຕົ້ນ"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"ຂັ້ນຕອນຕໍ່ໄປ"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"ຕັ້ງຄ່າ <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"ເປີດນຳໃຊ້ <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"ກະລຸນາກວດເບິ່ງ \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" ໃນການຕັ້ງຄ່າພາສາ &amp; ການປ້ອນຂໍ້ມູນຂອງທ່ານ. ນີ້ຈະເປັນການອະນຸຍາດໃຫ້ມັນເຮັດວຽກໃນອຸປະກອນຂອງທ່ານ"</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> ຖືກເປີດນຳໃຊ້ໃນການຕັ້ງຄ່າພາສາ &amp; ການປ້ອນຂໍ້ມູນຂອງທ່ານແລ້ວ, ສະນັ້ນຂັ້ນຕອນນີ້ແມ່ນສຳເລັດໄປແລ້ວ. ໄປທີ່ຂັ້ນຕອນຕໍ່ໄປ!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"ເປີດນຳໃຊ້ໃນການຕັ້ງຄ່າ"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"ປ່ຽນເປັນ <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"ຕໍ່ໄປ, ເລືອກເອົາ \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" ເປັນຮູບແບບການປ້ອນຂໍ້ມູນຂອງທ່ານ."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"ປ່ຽນຮູບແບບການປ້ອນຂໍ້ມູນ"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"ຍິນດີດ້ວຍ, ທ່ານເຮັດແລ້ວໆ!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"ຕອນນີ້ທ່ານສາມາດພິມໃນແອັບຯທີ່ທ່ານມັກໄດ້ທຸກແອັບຯດ້ວຍ <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"ປັບຄ່າພາສາເພີ່ມເຕີມ"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"ສຳເລັດແລ້ວ"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"ສະແດງໄອຄອນຂອງແອັບຯ"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"ສະແດງໄອຄອນຂອງແອັບຯໃນ Launcher"</string>
+    <string name="app_name" msgid="6320102637491234792">"ຜູ່​ສະ​ຫນອງ​ວັດຈະ​ນາ​ນຸ​ກົມ"</string>
+    <string name="dictionary_provider_name" msgid="3027315045397363079">"ຜູ່​ສະ​ຫນອງ​ວັດຈະ​ນາ​ນຸ​ກົມ"</string>
+    <string name="dictionary_service_name" msgid="6237472350693511448">"ບໍລິການວັດຈະນານຸກົມ"</string>
+    <string name="download_description" msgid="6014835283119198591">"ຂໍ້ມູນການອັບເດດວັດຈະນານຸກົມ"</string>
+    <string name="dictionary_settings_title" msgid="8091417676045693313">"ໂຕເສີມວັດຈະນານຸກົມ"</string>
+    <string name="dictionary_install_over_metered_network_prompt" msgid="3587517870006332980">"ມີວັດຈະນານຸກົມ"</string>
+    <string name="dictionary_settings_summary" msgid="5305694987799824349">"ການຕັ້ງຄ່າສຳລັບວັດຈະນານຸກົມ"</string>
+    <string name="user_dictionaries" msgid="3582332055892252845">"ວັດຈະນານຸກົມຜູ່ໃຊ້"</string>
+    <string name="default_user_dict_pref_name" msgid="1625055720489280530">"ວັດຈະນານຸກົມຜູ່ໃຊ້"</string>
+    <string name="dictionary_available" msgid="4728975345815214218">"ມີວັດຈະນານຸກົມ"</string>
+    <string name="dictionary_downloading" msgid="2982650524622620983">"ກຳລັງດາວໂຫລດ"</string>
+    <string name="dictionary_installed" msgid="8081558343559342962">"ຕິດຕັ້ງແລ້ວ"</string>
+    <string name="dictionary_disabled" msgid="8950383219564621762">"ຕິດຕັ້ງແລ້ວ, ປິດການນຳໃຊ້ແລ້ວ"</string>
+    <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"ມີປັນຫາໃນການເຊື່ອມຕໍ່ກັບບໍລິການວັດຈະນານຸກົມ"</string>
+    <string name="no_dictionaries_available" msgid="8039920716566132611">"ບໍ່ມີວັດຈະນານຸກົມ"</string>
+    <string name="check_for_updates_now" msgid="8087688440916388581">"ດຶງຂໍ້ມູນໃຫມ່"</string>
+    <string name="last_update" msgid="730467549913588780">"ອັບເດດຫຼ້າສຸດ"</string>
+    <string name="message_updating" msgid="4457761393932375219">"ກຳລັງກວດການອັບເດດ"</string>
+    <string name="message_loading" msgid="8689096636874758814">"ກຳລັງໂຫລດ..."</string>
+    <string name="main_dict_description" msgid="3072821352793492143">"ວັດຈະນານຸກົມຫຼັກ"</string>
+    <string name="cancel" msgid="6830980399865683324">"ຍົກເລີກ"</string>
+    <string name="install_dict" msgid="180852772562189365">"ຕິດຕັ້ງ"</string>
+    <string name="cancel_download_dict" msgid="7843340278507019303">"ຍົກເລີກ"</string>
+    <string name="delete_dict" msgid="756853268088330054">"ລຶບ"</string>
+    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"ພາສາທີ່ທ່ານເລືອກໃຊ້ໃນອຸປະກອນຂອງທ່ານນັ້ນ ມີວັດຈະນານຸກົມໃຫ້ໃຊ້ພ້ອມ.&lt;br/&gt; ພວກເຮົາແນະນຳໃຫ້ &lt;b&gt;ດາວໂຫລດ&lt;/b&gt; <xliff:g id="LANGUAGE">%1$s</xliff:g> ວັດຈະນານຸກົມດັ່ງກ່າວ ເພື່ອເພີ່ມປະສົບການໃນການພິມຂອງທ່ານ.&lt;br/&gt; &lt;br/&gt; ການດາວໂຫລດອາດຈະໃຊ້ເວລາພຽງໜຶ່ງເຖິງສອງນາທີ ໂດຍການໃຊ້ 3G. ທ່ານອາດຈະເສຍຄ່າບໍລິການສຳລັບອິນເຕີເນັດ ຫາກທ່ານບໍ່ມີ &lt;b&gt;ການນຳໃຊ້ອິນເຕີເນັດແບບບໍ່ຈຳກັດ&lt;/b&gt;.&lt;br/&gt; ຫາກທ່ານບໍ່ແນ່ໃຈວ່າຮູບແບບການໃຊ້ໃດທີ່ທ່ານມີຢູ່ ພວກເຮົາແນະນຳໃຫ້ຊອກຫາການເຊື່ອມຕໍ່ Wi-Fi ເພື່ອດາວໂຫລດມັນໂດຍອັດຕະໂນມັດ.&lt;br/&gt; &lt;br/&gt; ເຄັດລັບ: ທ່ານສາມາດດາວໂຫລດ ແລະ ລຶບວັດຈະນານຸກົມໄດ້ທີ່ &lt;b&gt;ພາສາ &amp; ການປ້ອນຂໍ້ມູນ&lt;/b&gt; ຢູ່ໃນເມນູ &lt;b&gt;ການຕັ້ງຄ່າ&lt;/b&gt; ຂອງອຸປະກອນພົກພາຂອງທ່ານ."</string>
+    <string name="download_over_metered" msgid="1643065851159409546">"ດາວໂຫລດດຽວນີ້ (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g>MB)"</string>
+    <string name="do_not_download_over_metered" msgid="2176209579313941583">"ດາວ​ໂຫລດຜ່ານ Wi-Fi"</string>
+    <string name="dict_available_notification_title" msgid="6514288591959117288">"ວັດຈະນານຸກົມສາມາດໃຊ້ໄດ້ກັບ <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
+    <string name="dict_available_notification_description" msgid="1075194169443163487">"ກົດທີ່ກວດຄືນ ແລະ ດາວໂຫລດ"</string>
+    <string name="toast_downloading_suggestions" msgid="1313027353588566660">"ກຳລັງດາວໂຫລດ: ການແນະນຳສຳລັບ <xliff:g id="LANGUAGE">%1$s</xliff:g> ແລະມັນຈະພ້ອມນຳໃຊ້ໄວໆນີ້"</string>
+    <string name="version_text" msgid="2715354215568469385">"ເວີຊັນ <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"ເພີ່ມ"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"ເພີ່ມໄປທີ່ວັດຈະນານຸກົມ"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"ປະໂຫຍກ"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"ຕົວເລືອກເພີ່ມເຕີມ"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"ຕົວເລືອກໜ້ອຍລົງ"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"ຕົກລົງ"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"ຄຳສັບ:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"ທາງລັດ:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"ພາສາ:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"ພິມ​ຄໍາ​ສັບ​ໃດ​ນຶ່ງ"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"ໂຕເລືອກທາງລັດ"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"ແກ້ໄຂຄຳສັບ"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"ແກ້ໄຂ"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"ລຶບ"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"ທ່ານບໍ່ມີຄຳສັບໃດໆໃນວັດຈະນານຸກົມຜູ່ໃຊ້ເທື່ອ. ເພີ່ມຄຳສັບໄດ້ໂດຍການສຳພັດທີ່ປຸ່ມ ເພີ່ມ (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"ສໍາ​ລັບ​ທຸກໆ​ພາ​ສາ"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"ພາສາອື່ນໆ..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"ລຶບ"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+</resources>
diff --git a/java/res/values-lt/strings.xml b/java/res/values-lt/strings.xml
index 88328db..1cb84d0 100644
--- a/java/res/values-lt/strings.xml
+++ b/java/res/values-lt/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Prijunkite ausines, kad išgirstumėte sakomus slaptažodžio klavišus."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Dabartinis tekstas yra %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Nėra įvesto teksto"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> pataiso „<xliff:g id="ORIGINAL_WORD">%2$s</xliff:g>“ į „<xliff:g id="CORRECTED">%3$s</xliff:g>“"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> atlieka automatinį taisymą"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Klavišo kodas %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Antrojo lygio klavišas"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Įjungtas antrasis lygis (palieskite, kad išjungtumėte)"</string>
diff --git a/java/res/values-lv/strings.xml b/java/res/values-lv/strings.xml
index 069c487..221cc2e 100644
--- a/java/res/values-lv/strings.xml
+++ b/java/res/values-lv/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Pievienojiet austiņas, lai dzirdētu paroles rakstzīmes."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Pašreizējais teksts ir %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Nav ievadīts teksts"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"Nospiežot taustiņu <xliff:g id="KEY">%1$s</xliff:g>, “<xliff:g id="ORIGINAL_WORD">%2$s</xliff:g>” tiek labots uz “<xliff:g id="CORRECTED">%3$s</xliff:g>”."</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"Taustiņam <xliff:g id="KEY">%1$s</xliff:g> ir automātiskas labošanas funkcija."</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Taustiņu kods %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Pārslēgšanas taustiņš"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Pārslēgšanas taustiņš iespējots (pieskarieties, lai atspējotu)"</string>
diff --git a/java/res/values-mk/strings.xml b/java/res/values-mk/strings.xml
new file mode 100644
index 0000000..6ee84fa
--- /dev/null
+++ b/java/res/values-mk/strings.xml
@@ -0,0 +1,464 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- no translation found for english_ime_input_options (3909945612939668554) -->
+    <skip />
+    <!-- no translation found for english_ime_research_log (8492602295696577851) -->
+    <skip />
+    <!-- no translation found for use_contacts_for_spellchecking_option_title (5374120998125353898) -->
+    <skip />
+    <!-- no translation found for use_contacts_for_spellchecking_option_summary (8754413382543307713) -->
+    <skip />
+    <!-- no translation found for vibrate_on_keypress (5258079494276955460) -->
+    <skip />
+    <!-- no translation found for sound_on_keypress (6093592297198243644) -->
+    <skip />
+    <!-- no translation found for popup_on_keypress (123894815723512944) -->
+    <skip />
+    <!-- no translation found for general_category (1859088467017573195) -->
+    <skip />
+    <!-- no translation found for correction_category (2236750915056607613) -->
+    <skip />
+    <!-- no translation found for gesture_typing_category (497263612130532630) -->
+    <skip />
+    <!-- no translation found for misc_category (6894192814868233453) -->
+    <skip />
+    <!-- no translation found for advanced_settings (362895144495591463) -->
+    <skip />
+    <!-- no translation found for advanced_settings_summary (4487980456152830271) -->
+    <skip />
+    <!-- no translation found for include_other_imes_in_language_switch_list (4533689960308565519) -->
+    <skip />
+    <!-- no translation found for include_other_imes_in_language_switch_list_summary (840637129103317635) -->
+    <skip />
+    <!-- no translation found for show_language_switch_key (5915478828318774384) -->
+    <skip />
+    <!-- no translation found for show_language_switch_key_summary (7343403647474265713) -->
+    <skip />
+    <!-- no translation found for sliding_key_input_preview (6604262359510068370) -->
+    <skip />
+    <!-- no translation found for sliding_key_input_preview_summary (6340524345729093886) -->
+    <skip />
+    <!-- no translation found for key_preview_popup_dismiss_delay (6213164897443068248) -->
+    <skip />
+    <!-- no translation found for key_preview_popup_dismiss_no_delay (2096123151571458064) -->
+    <skip />
+    <!-- no translation found for key_preview_popup_dismiss_default_delay (2166964333903906734) -->
+    <skip />
+    <!-- no translation found for abbreviation_unit_milliseconds (8700286094028323363) -->
+    <skip />
+    <!-- no translation found for settings_system_default (6268225104743331821) -->
+    <skip />
+    <!-- no translation found for use_contacts_dict (4435317977804180815) -->
+    <skip />
+    <!-- no translation found for use_contacts_dict_summary (6599983334507879959) -->
+    <skip />
+    <!-- no translation found for use_double_space_period (8781529969425082860) -->
+    <skip />
+    <!-- no translation found for use_double_space_period_summary (6532892187247952799) -->
+    <skip />
+    <!-- no translation found for auto_cap (1719746674854628252) -->
+    <skip />
+    <!-- no translation found for auto_cap_summary (7934452761022946874) -->
+    <skip />
+    <!-- no translation found for edit_personal_dictionary (3996910038952940420) -->
+    <skip />
+    <!-- no translation found for configure_dictionaries_title (4238652338556902049) -->
+    <skip />
+    <!-- no translation found for main_dictionary (4798763781818361168) -->
+    <skip />
+    <!-- no translation found for prefs_show_suggestions (8026799663445531637) -->
+    <skip />
+    <!-- no translation found for prefs_show_suggestions_summary (1583132279498502825) -->
+    <skip />
+    <!-- no translation found for prefs_suggestion_visibility_show_name (3219916594067551303) -->
+    <skip />
+    <!-- no translation found for prefs_suggestion_visibility_show_only_portrait_name (3859783767435239118) -->
+    <skip />
+    <!-- no translation found for prefs_suggestion_visibility_hide_name (6309143926422234673) -->
+    <skip />
+    <!-- no translation found for prefs_block_potentially_offensive_title (5078480071057408934) -->
+    <skip />
+    <!-- no translation found for prefs_block_potentially_offensive_summary (2371835479734991364) -->
+    <skip />
+    <!-- no translation found for auto_correction (7630720885194996950) -->
+    <skip />
+    <!-- no translation found for auto_correction_summary (5625751551134658006) -->
+    <skip />
+    <!-- no translation found for auto_correction_threshold_mode_off (8470882665417944026) -->
+    <skip />
+    <!-- no translation found for auto_correction_threshold_mode_modest (8788366690620799097) -->
+    <skip />
+    <!-- no translation found for auto_correction_threshold_mode_aggressive (7319007299148899623) -->
+    <skip />
+    <!-- no translation found for auto_correction_threshold_mode_very_aggressive (1853309024129480416) -->
+    <skip />
+    <!-- no translation found for bigram_prediction (1084449187723948550) -->
+    <skip />
+    <!-- no translation found for bigram_prediction_summary (3896362682751109677) -->
+    <skip />
+    <!-- no translation found for gesture_input (826951152254563827) -->
+    <skip />
+    <!-- no translation found for gesture_input_summary (9180350639305731231) -->
+    <skip />
+    <!-- no translation found for gesture_preview_trail (3802333369335722221) -->
+    <skip />
+    <!-- no translation found for gesture_floating_preview_text (4443240334739381053) -->
+    <skip />
+    <!-- no translation found for gesture_floating_preview_text_summary (4472696213996203533) -->
+    <skip />
+    <!-- no translation found for added_word (8993883354622484372) -->
+    <skip />
+    <string name="label_go_key" msgid="1635148082137219148">"Оди"</string>
+    <string name="label_next_key" msgid="362972844525672568">"Следно"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"Претходно"</string>
+    <string name="label_done_key" msgid="2441578748772529288">"Готово"</string>
+    <string name="label_send_key" msgid="2815056534433717444">"Испрати"</string>
+    <!-- no translation found for label_pause_key (181098308428035340) -->
+    <skip />
+    <!-- no translation found for label_wait_key (6402152600878093134) -->
+    <skip />
+    <!-- no translation found for spoken_use_headphones (896961781287283493) -->
+    <skip />
+    <!-- no translation found for spoken_current_text_is (2485723011272583845) -->
+    <skip />
+    <!-- no translation found for spoken_no_text_entered (7479685225597344496) -->
+    <skip />
+    <!-- no translation found for spoken_auto_correct (8005997889020109763) -->
+    <skip />
+    <!-- no translation found for spoken_auto_correct_obscured (6276420476908833791) -->
+    <skip />
+    <!-- no translation found for spoken_description_unknown (3197434010402179157) -->
+    <skip />
+    <!-- no translation found for spoken_description_shift (244197883292549308) -->
+    <skip />
+    <!-- no translation found for spoken_description_shift_shifted (1681877323344195035) -->
+    <skip />
+    <!-- no translation found for spoken_description_caps_lock (3276478269526304432) -->
+    <skip />
+    <!-- no translation found for spoken_description_delete (8740376944276199801) -->
+    <skip />
+    <!-- no translation found for spoken_description_to_symbol (5486340107500448969) -->
+    <skip />
+    <!-- no translation found for spoken_description_to_alpha (23129338819771807) -->
+    <skip />
+    <!-- no translation found for spoken_description_to_numeric (591752092685161732) -->
+    <skip />
+    <!-- no translation found for spoken_description_settings (4627462689603838099) -->
+    <skip />
+    <!-- no translation found for spoken_description_tab (2667716002663482248) -->
+    <skip />
+    <!-- no translation found for spoken_description_space (2582521050049860859) -->
+    <skip />
+    <!-- no translation found for spoken_description_mic (615536748882611950) -->
+    <skip />
+    <!-- no translation found for spoken_description_smiley (2256309826200113918) -->
+    <skip />
+    <!-- no translation found for spoken_description_return (8178083177238315647) -->
+    <skip />
+    <!-- no translation found for spoken_description_search (1247236163755920808) -->
+    <skip />
+    <!-- no translation found for spoken_description_dot (40711082435231673) -->
+    <skip />
+    <!-- no translation found for spoken_description_language_switch (5507091328222331316) -->
+    <skip />
+    <!-- no translation found for spoken_description_action_next (8636078276664150324) -->
+    <skip />
+    <!-- no translation found for spoken_description_action_previous (800872415009336208) -->
+    <skip />
+    <!-- no translation found for spoken_description_shiftmode_on (5700440798609574589) -->
+    <skip />
+    <!-- no translation found for spoken_description_shiftmode_locked (593175803181701830) -->
+    <skip />
+    <!-- no translation found for spoken_description_shiftmode_off (657219998449174808) -->
+    <skip />
+    <!-- no translation found for spoken_description_mode_symbol (7183343879909747642) -->
+    <skip />
+    <!-- no translation found for spoken_description_mode_alpha (3528307674390156956) -->
+    <skip />
+    <!-- no translation found for spoken_description_mode_phone (6520207943132026264) -->
+    <skip />
+    <!-- no translation found for spoken_description_mode_phone_shift (5499629753962641227) -->
+    <skip />
+    <!-- no translation found for announce_keyboard_hidden (8718927835531429807) -->
+    <skip />
+    <!-- no translation found for announce_keyboard_mode (4729081055438508321) -->
+    <skip />
+    <!-- no translation found for keyboard_mode_date (3137520166817128102) -->
+    <skip />
+    <!-- no translation found for keyboard_mode_date_time (339593358488851072) -->
+    <skip />
+    <!-- no translation found for keyboard_mode_email (6216248078128294262) -->
+    <skip />
+    <!-- no translation found for keyboard_mode_im (1137405089766557048) -->
+    <skip />
+    <!-- no translation found for keyboard_mode_number (7991623440699957069) -->
+    <skip />
+    <!-- no translation found for keyboard_mode_phone (6851627527401433229) -->
+    <skip />
+    <!-- no translation found for keyboard_mode_text (6479436687899701619) -->
+    <skip />
+    <!-- no translation found for keyboard_mode_time (4381856885582143277) -->
+    <skip />
+    <!-- no translation found for keyboard_mode_url (1519819835514911218) -->
+    <skip />
+    <!-- no translation found for voice_input (3583258583521397548) -->
+    <skip />
+    <!-- no translation found for voice_input_modes_main_keyboard (3360660341121083174) -->
+    <skip />
+    <!-- no translation found for voice_input_modes_symbols_keyboard (7203213240786084067) -->
+    <skip />
+    <!-- no translation found for voice_input_modes_off (3745699748218082014) -->
+    <skip />
+    <!-- no translation found for voice_input_modes_summary_main_keyboard (6586544292900314339) -->
+    <skip />
+    <!-- no translation found for voice_input_modes_summary_symbols_keyboard (5233725927281932391) -->
+    <skip />
+    <!-- no translation found for voice_input_modes_summary_off (63875609591897607) -->
+    <skip />
+    <!-- no translation found for configure_input_method (373356270290742459) -->
+    <skip />
+    <!-- no translation found for language_selection_title (1651299598555326750) -->
+    <skip />
+    <!-- no translation found for send_feedback (1780431884109392046) -->
+    <skip />
+    <!-- no translation found for select_language (3693815588777926848) -->
+    <skip />
+    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
+    <skip />
+    <!-- no translation found for has_dictionary (6071847973466625007) -->
+    <skip />
+    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
+    <skip />
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
+    <!-- no translation found for keyboard_layout (8451164783510487501) -->
+    <skip />
+    <!-- no translation found for subtype_en_GB (88170601942311355) -->
+    <skip />
+    <!-- no translation found for subtype_en_US (6160452336634534239) -->
+    <skip />
+    <!-- no translation found for subtype_es_US (5583145191430180200) -->
+    <skip />
+    <!-- no translation found for subtype_with_layout_en_GB (2179097748724725906) -->
+    <skip />
+    <!-- no translation found for subtype_with_layout_en_US (1362581347576714579) -->
+    <skip />
+    <!-- no translation found for subtype_with_layout_es_US (6261791057007890189) -->
+    <skip />
+    <!-- no translation found for subtype_nepali_traditional (9032247506728040447) -->
+    <skip />
+    <!-- no translation found for subtype_no_language (7137390094240139495) -->
+    <skip />
+    <!-- no translation found for subtype_no_language_qwerty (244337630616742604) -->
+    <skip />
+    <!-- no translation found for subtype_no_language_qwertz (443066912507547976) -->
+    <skip />
+    <!-- no translation found for subtype_no_language_azerty (8144348527575640087) -->
+    <skip />
+    <!-- no translation found for subtype_no_language_dvorak (1564494667584718094) -->
+    <skip />
+    <!-- no translation found for subtype_no_language_colemak (5837418400010302623) -->
+    <skip />
+    <!-- no translation found for subtype_no_language_pcqwerty (5354918232046200018) -->
+    <skip />
+    <!-- no translation found for subtype_emoji (7483586578074549196) -->
+    <skip />
+    <!-- no translation found for custom_input_styles_title (8429952441821251512) -->
+    <skip />
+    <!-- no translation found for add_style (6163126614514489951) -->
+    <skip />
+    <!-- no translation found for add (8299699805688017798) -->
+    <skip />
+    <!-- no translation found for remove (4486081658752944606) -->
+    <skip />
+    <!-- no translation found for save (7646738597196767214) -->
+    <skip />
+    <!-- no translation found for subtype_locale (8576443440738143764) -->
+    <skip />
+    <!-- no translation found for keyboard_layout_set (4309233698194565609) -->
+    <skip />
+    <!-- no translation found for custom_input_style_note_message (8826731320846363423) -->
+    <skip />
+    <!-- no translation found for enable (5031294444630523247) -->
+    <skip />
+    <!-- no translation found for not_now (6172462888202790482) -->
+    <skip />
+    <!-- no translation found for custom_input_style_already_exists (8008728952215449707) -->
+    <skip />
+    <!-- no translation found for prefs_usability_study_mode (1261130555134595254) -->
+    <skip />
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
+    <!-- no translation found for prefs_read_external_dictionary (2588931418575013067) -->
+    <skip />
+    <!-- no translation found for read_external_dictionary_no_files_message (4947420942224623792) -->
+    <skip />
+    <!-- no translation found for read_external_dictionary_multiple_files_title (7637749044265808628) -->
+    <skip />
+    <!-- no translation found for read_external_dictionary_confirm_install_message (6898610163768980870) -->
+    <skip />
+    <!-- no translation found for error (8940763624668513648) -->
+    <skip />
+    <!-- no translation found for button_default (3988017840431881491) -->
+    <skip />
+    <!-- no translation found for setup_welcome_title (6112821709832031715) -->
+    <skip />
+    <!-- no translation found for setup_welcome_additional_description (8150252008545768953) -->
+    <skip />
+    <!-- no translation found for setup_start_action (8936036460897347708) -->
+    <skip />
+    <!-- no translation found for setup_next_action (371821437915144603) -->
+    <skip />
+    <!-- no translation found for setup_steps_title (6400373034871816182) -->
+    <skip />
+    <!-- no translation found for setup_step1_title (3147967630253462315) -->
+    <skip />
+    <!-- no translation found for setup_step1_instruction (2578631936624637241) -->
+    <skip />
+    <!-- no translation found for setup_step1_finished_instruction (10761482004957994) -->
+    <skip />
+    <!-- no translation found for setup_step1_action (4366513534999901728) -->
+    <skip />
+    <!-- no translation found for setup_step2_title (6860725447906690594) -->
+    <skip />
+    <!-- no translation found for setup_step2_instruction (9141481964870023336) -->
+    <skip />
+    <!-- no translation found for setup_step2_action (1660330307159824337) -->
+    <skip />
+    <!-- no translation found for setup_step3_title (3154757183631490281) -->
+    <skip />
+    <!-- no translation found for setup_step3_instruction (8025981829605426000) -->
+    <skip />
+    <!-- no translation found for setup_step3_action (600879797256942259) -->
+    <skip />
+    <!-- no translation found for setup_finish_action (276559243409465389) -->
+    <skip />
+    <!-- no translation found for show_setup_wizard_icon (5008028590593710830) -->
+    <skip />
+    <!-- no translation found for show_setup_wizard_icon_summary (4119998322536880213) -->
+    <skip />
+    <!-- no translation found for app_name (6320102637491234792) -->
+    <skip />
+    <!-- no translation found for dictionary_provider_name (3027315045397363079) -->
+    <skip />
+    <!-- no translation found for dictionary_service_name (6237472350693511448) -->
+    <skip />
+    <!-- no translation found for download_description (6014835283119198591) -->
+    <skip />
+    <!-- no translation found for dictionary_settings_title (8091417676045693313) -->
+    <skip />
+    <!-- no translation found for dictionary_install_over_metered_network_prompt (3587517870006332980) -->
+    <skip />
+    <!-- no translation found for dictionary_settings_summary (5305694987799824349) -->
+    <skip />
+    <!-- no translation found for user_dictionaries (3582332055892252845) -->
+    <skip />
+    <!-- no translation found for default_user_dict_pref_name (1625055720489280530) -->
+    <skip />
+    <!-- no translation found for dictionary_available (4728975345815214218) -->
+    <skip />
+    <!-- no translation found for dictionary_downloading (2982650524622620983) -->
+    <skip />
+    <!-- no translation found for dictionary_installed (8081558343559342962) -->
+    <skip />
+    <!-- no translation found for dictionary_disabled (8950383219564621762) -->
+    <skip />
+    <!-- no translation found for cannot_connect_to_dict_service (9216933695765732398) -->
+    <skip />
+    <!-- no translation found for no_dictionaries_available (8039920716566132611) -->
+    <skip />
+    <!-- no translation found for check_for_updates_now (8087688440916388581) -->
+    <skip />
+    <!-- no translation found for last_update (730467549913588780) -->
+    <skip />
+    <!-- no translation found for message_updating (4457761393932375219) -->
+    <skip />
+    <!-- no translation found for message_loading (8689096636874758814) -->
+    <skip />
+    <!-- no translation found for main_dict_description (3072821352793492143) -->
+    <skip />
+    <!-- no translation found for cancel (6830980399865683324) -->
+    <skip />
+    <!-- no translation found for install_dict (180852772562189365) -->
+    <skip />
+    <!-- no translation found for cancel_download_dict (7843340278507019303) -->
+    <skip />
+    <!-- no translation found for delete_dict (756853268088330054) -->
+    <skip />
+    <!-- no translation found for should_download_over_metered_prompt (2878629598667658845) -->
+    <skip />
+    <!-- no translation found for download_over_metered (1643065851159409546) -->
+    <skip />
+    <!-- no translation found for do_not_download_over_metered (2176209579313941583) -->
+    <skip />
+    <!-- no translation found for dict_available_notification_title (6514288591959117288) -->
+    <skip />
+    <!-- no translation found for dict_available_notification_description (1075194169443163487) -->
+    <skip />
+    <!-- no translation found for toast_downloading_suggestions (1313027353588566660) -->
+    <skip />
+    <!-- no translation found for version_text (2715354215568469385) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_menu_title (1254195365689387076) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_dialog_title (4096700390211748168) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_screen_title (5818914331629278758) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_dialog_more_options (5671682004887093112) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_dialog_less_options (2716586567241724126) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_dialog_confirm (4703129507388332950) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_word_option_name (6665558053408962865) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_shortcut_option_name (3094731590655523777) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_locale_option_name (4738643440987277705) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_word_hint (4902434148985906707) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_shortcut_hint (2265453012555060178) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_edit_dialog_title (3765774633869590352) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_context_menu_edit_title (6812255903472456302) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_context_menu_delete_title (8142932447689461181) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_empty_text (558499587532668203) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_all_languages (8276126583216298886) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_more_languages (7131268499685180461) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_delete (110413335187193859) -->
+    <skip />
+    <!-- no translation found for user_dict_fast_scroll_alphabet (5431919401558285473) -->
+    <skip />
+</resources>
diff --git a/java/res/values-mn-rMN/strings.xml b/java/res/values-mn-rMN/strings.xml
index 3b039f1..5633cf8 100644
--- a/java/res/values-mn-rMN/strings.xml
+++ b/java/res/values-mn-rMN/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Нууц үгний товчнуудыг чангаар уншихыг сонсохын тулд чихэвчээ залгана уу."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Одоогийн текст %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Текст оруулаагүй"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g>-г <xliff:g id="CORRECTED">%3$s</xliff:g> руу залруулна"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> автоматаар залруулна"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Товчийн код %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Сэлгэх"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Сэлгэхийг идэвхжүүлсэн (товшиж идэвхгүйжүүлнэ үү)"</string>
diff --git a/java/res/values-ms-rMY/strings.xml b/java/res/values-ms-rMY/strings.xml
index ee241d6..41bb9f8 100644
--- a/java/res/values-ms-rMY/strings.xml
+++ b/java/res/values-ms-rMY/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Pasangkan set kepala untuk mendengar kekunci kata laluan disebut dengan kuat."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Teks semasa adalah %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Tiada teks dimasukkan"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> membetulkan <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> menjadi <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> melakukan auto pembetulan"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Kod kunci %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Kunci anjak dihidupkan (ketik untuk melumpuhkan)"</string>
diff --git a/java/res/values-nb/strings.xml b/java/res/values-nb/strings.xml
index ae43d10..a473cce 100644
--- a/java/res/values-nb/strings.xml
+++ b/java/res/values-nb/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Koble til hodetelefoner for å høre opplesing av bokstavene i passordet."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Gjeldende tekst er %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Ingen tekst er skrevet inn"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> retter <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> til <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> utfører automatisk retting"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Tastaturkode %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift er på (trykk for å deaktivere)"</string>
@@ -224,7 +222,7 @@
     <string name="do_not_download_over_metered" msgid="2176209579313941583">"Last ned via Wi-Fi"</string>
     <string name="dict_available_notification_title" msgid="6514288591959117288">"En ordliste er tilgjengelig for <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Trykk for å se gjennom og laste ned"</string>
-    <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Laster ned: forslag blir snart tilgjengelige for <xliff:g id="LANGUAGE">%1$s</xliff:g>."</string>
+    <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Laster ned: Forslag blir snart tilgjengelige for <xliff:g id="LANGUAGE">%1$s</xliff:g>."</string>
     <string name="version_text" msgid="2715354215568469385">"Versjon <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
     <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Legg til"</string>
     <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Legg til i ordlisten"</string>
diff --git a/java/res/values-nl/strings.xml b/java/res/values-nl/strings.xml
index de20da4..60db9f0 100644
--- a/java/res/values-nl/strings.xml
+++ b/java/res/values-nl/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Sluit een headset aan om wachtwoordtoetsen hardop te laten voorlezen."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Huidige tekst is %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Geen tekst ingevoerd"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"Met <xliff:g id="KEY">%1$s</xliff:g> wordt <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> gecorrigeerd naar <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"Met <xliff:g id="KEY">%1$s</xliff:g> voert u automatische correctie uit"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Toetscode %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift aan (tik om uit te schakelen)"</string>
diff --git a/java/res/values-pl/strings.xml b/java/res/values-pl/strings.xml
index 3908375..c3ed2d8 100644
--- a/java/res/values-pl/strings.xml
+++ b/java/res/values-pl/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Podłącz zestaw słuchawkowy, aby usłyszeć znaki hasła wypowiadane na głos."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Aktualny tekst: %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Nie wprowadzono tekstu"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> poprawia <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> na <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> wykonuje autokorektę"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Kod klawisza: %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift włączony (kliknij, by wyłączyć)"</string>
diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml
index 4c81479..2a4c2b8 100644
--- a/java/res/values-pt-rPT/strings.xml
+++ b/java/res/values-pt-rPT/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Ligar auscultadores com microfone integrado para ouvir as teclas da palavra-passe."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"O texto atual é %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Nenhum texto digitado"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> corrige <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> para <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> executa correção automática"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Código da tecla %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift ativado (tocar para desativar)"</string>
diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml
index ef533ae..c0e1aa6 100644
--- a/java/res/values-pt/strings.xml
+++ b/java/res/values-pt/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Conecte um fone de ouvido para ouvir as chaves de senha em voz alta."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"O texto atual é %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Nenhum texto digitado"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> corrige <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> para <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> realiza correção automática"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Código de tecla %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift ativado (toque para desativar)"</string>
diff --git a/java/res/values-rm/strings.xml b/java/res/values-rm/strings.xml
index c68d30c..b956619 100644
--- a/java/res/values-rm/strings.xml
+++ b/java/res/values-rm/strings.xml
@@ -139,9 +139,9 @@
     <skip />
     <!-- no translation found for spoken_no_text_entered (7479685225597344496) -->
     <skip />
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
+    <!-- no translation found for spoken_auto_correct (8005997889020109763) -->
     <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
+    <!-- no translation found for spoken_auto_correct_obscured (6276420476908833791) -->
     <skip />
     <!-- no translation found for spoken_description_unknown (3197434010402179157) -->
     <skip />
diff --git a/java/res/values-ro/strings.xml b/java/res/values-ro/strings.xml
index 930b68b..c24ec01 100644
--- a/java/res/values-ro/strings.xml
+++ b/java/res/values-ro/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Conectaţi un set căşti-microfon pentru a auzi tastele apăsate când introduceţi parola."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Textul curent este %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Nu a fost introdus text"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> corectează <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> cu <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> efectuează corectare automată"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Tasta cu codul %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Tasta Shift este activată (apăsaţi pentru a o dezactiva)"</string>
diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml
index 8bfb011..176e75e 100644
--- a/java/res/values-ru/strings.xml
+++ b/java/res/values-ru/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Подключите гарнитуру, чтобы услышать пароль."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Введенный текст: %s."</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Текст не введен"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"При нажатии клавиши \"<xliff:g id="KEY">%1$s</xliff:g>\" слово \"<xliff:g id="ORIGINAL_WORD">%2$s</xliff:g>\" будет исправлено на \"<xliff:g id="CORRECTED">%3$s</xliff:g>\""</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"Для клавиши \"<xliff:g id="KEY">%1$s</xliff:g>\" назначена функция автоисправления"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Код клавиши:%d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Клавиша верхнего регистра"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Верхний регистр включен (нажмите, чтобы отключить)"</string>
diff --git a/java/res/values-sk/strings.xml b/java/res/values-sk/strings.xml
index 5c25f28..bc5d6d2 100644
--- a/java/res/values-sk/strings.xml
+++ b/java/res/values-sk/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Ak si chcete pri zadávaní hesla vypočuť nahlas vyslovené klávesy, pripojte náhlavnú súpravu."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Aktuálny text je %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Nie je zadaný žiadny text"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"Klávesom <xliff:g id="KEY">%1$s</xliff:g> opravíte slovo <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> na <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"Klávesom <xliff:g id="KEY">%1$s</xliff:g> spustíte automatické opravy"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Kód klávesu %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Kláves Shift je zapnutý (zakážete ho klepnutím)"</string>
diff --git a/java/res/values-sl/strings.xml b/java/res/values-sl/strings.xml
index dde01dd..c7d698b 100644
--- a/java/res/values-sl/strings.xml
+++ b/java/res/values-sl/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Priključite slušalke, če želite slišati izgovorjene tipke gesla."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Trenutno besedilo je %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Ni vnesenega besedila"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"Tipka <xliff:g id="KEY">%1$s</xliff:g> popravi <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> v <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"Tipka <xliff:g id="KEY">%1$s</xliff:g> izvede samodejno popravljanje"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Koda tipke %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift je vklopljen (dotaknite se, da onemogočite)"</string>
diff --git a/java/res/values-sr/strings.xml b/java/res/values-sr/strings.xml
index e7f6904..e93661a 100644
--- a/java/res/values-sr/strings.xml
+++ b/java/res/values-sr/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Укључите слушалице да бисте чули наглас изговорене тастере за лозинку."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Тренутни текст је %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Текст није унет"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> исправља <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> у <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> обавља функцију аутоматског исправљања"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Кôд тастера %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift је укључен (додирните да бисте га онемогућили)"</string>
diff --git a/java/res/values-sv/strings.xml b/java/res/values-sv/strings.xml
index 0fcca48..1b23e33 100644
--- a/java/res/values-sv/strings.xml
+++ b/java/res/values-sv/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Anslut hörlurar om du vill att lösenordet ska läsas upp."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Nuvarande text är %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Ingen text har angetts"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"Om du trycker på <xliff:g id="KEY">%1$s</xliff:g> rättas <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> till <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"Om du trycker på <xliff:g id="KEY">%1$s</xliff:g> utförs autokorrigering"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Nyckelkod %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Skift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Skift på (knacka lätt för att inaktivera)"</string>
diff --git a/java/res/values-sw/strings.xml b/java/res/values-sw/strings.xml
index fbf868f..9a22d20 100644
--- a/java/res/values-sw/strings.xml
+++ b/java/res/values-sw/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Chomeka plagi ya kifaa cha kichwa cha kusikiza ili kusikiliza msimbo wa nenosiri inayozungumwa kwa sauti ya juu."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Maandishi ya sasa ni %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Hakuna maandishi yaliyoingizwa"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> hurekebisha <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> kuwa <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> hurekebisha kiotomatiki"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Msimbo wa kitufe %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Badilisha"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift imewashwa (gonga ili kulemaza)"</string>
diff --git a/java/res/values-th/strings.xml b/java/res/values-th/strings.xml
index f84b32c..bb8b556 100644
--- a/java/res/values-th/strings.xml
+++ b/java/res/values-th/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"เสียบชุดหูฟังเพื่อฟังเสียงเมื่อพิมพ์รหัสผ่าน"</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"ข้อความปัจจุบันคือ %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"ไม่มีข้อความ"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> แก้ไข <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> เป็น <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> ทำการแก้ไขอัตโนมัติ"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"รหัสคีย์ %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift เปิดอยู่ (แตะเพื่อปิดใช้งาน)"</string>
diff --git a/java/res/values-tl/strings.xml b/java/res/values-tl/strings.xml
index af1c68e..60ba081 100644
--- a/java/res/values-tl/strings.xml
+++ b/java/res/values-tl/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Mag-plug in ng headset upang marinig ang mga password key na binabanggit nang malakas."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Ang kasalukuyang teksto ay %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Walang tekstong inilagay"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"Itatama at gagawing <xliff:g id="CORRECTED">%3$s</xliff:g> ng <xliff:g id="KEY">%1$s</xliff:g> ang <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"Magsasagawa ng auto-correction ang <xliff:g id="KEY">%1$s</xliff:g>"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Code ng key %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Naka-on ang shift (i-tap upang huwag paganahin)"</string>
diff --git a/java/res/values-tr/strings.xml b/java/res/values-tr/strings.xml
index 4c8e1a8..676cc3c 100644
--- a/java/res/values-tr/strings.xml
+++ b/java/res/values-tr/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Şifre tuşlarının sesli okunmasını dinlemek için mikrofonlu kulaklık takın."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Mevcut metin: %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Hiç metin girilmedi"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> tuşu <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> kelimesini <xliff:g id="CORRECTED">%3$s</xliff:g> olarak düzeltir"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> tuşu otomatik düzeltme gerçekleştirir"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Tuş kodu: %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Üst Karakter"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Üst karakter açık (devre dışı bırakmak için hafifçe vurun)"</string>
diff --git a/java/res/values-uk/strings.xml b/java/res/values-uk/strings.xml
index 287bcb3..2a8b937 100644
--- a/java/res/values-uk/strings.xml
+++ b/java/res/values-uk/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Підключіть гарнітуру, щоб прослухати відтворені вголос символи пароля."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Поточний текст – %s."</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Текст не введено"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> виправляє <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> на <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> здійснює автоматичне виправлення"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Код клавіші – %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Клавіша Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift увімкнено (швидко торкніться, щоб вимкнути)"</string>
diff --git a/java/res/values-vi/strings.xml b/java/res/values-vi/strings.xml
index 8764df5..74ef95a 100644
--- a/java/res/values-vi/strings.xml
+++ b/java/res/values-vi/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Cắm tai nghe để nghe mật khẩu."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Ký tự hiện tại là %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Không có ký tự nào được nhập"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"<xliff:g id="KEY">%1$s</xliff:g> sửa <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> thành <xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"<xliff:g id="KEY">%1$s</xliff:g> thực hiện tự động sửa"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Mã phím %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift đang bật (bấm để tắt)"</string>
diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml
index f7ceff4..c552693 100644
--- a/java/res/values-zh-rCN/strings.xml
+++ b/java/res/values-zh-rCN/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"需要插入耳机才能听到密码的按键声。"</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"当前文本为%s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"未输入文字"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"按<xliff:g id="KEY">%1$s</xliff:g>可将<xliff:g id="ORIGINAL_WORD">%2$s</xliff:g>更正为<xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"按<xliff:g id="KEY">%1$s</xliff:g>可执行自动更正"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"键码为 %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift 模式已启用(点按即可停用)"</string>
diff --git a/java/res/values-zh-rHK/strings-appname.xml b/java/res/values-zh-rHK/strings-appname.xml
new file mode 100644
index 0000000..8761a98
--- /dev/null
+++ b/java/res/values-zh-rHK/strings-appname.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="5940510615957428904">"Android 鍵盤 (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android 拼字檢查工具 (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Android 鍵盤設定 (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Android 拼字檢查工具設定 (AOSP)"</string>
+</resources>
diff --git a/java/res/values-zh-rHK/strings.xml b/java/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..1d4f36a
--- /dev/null
+++ b/java/res/values-zh-rHK/strings.xml
@@ -0,0 +1,246 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_input_options" msgid="3909945612939668554">"輸入選項"</string>
+    <string name="english_ime_research_log" msgid="8492602295696577851">"研究記錄指令"</string>
+    <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"查找聯絡人姓名"</string>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"拼字檢查程式使用您的聯絡人名單中的各項記錄"</string>
+    <string name="vibrate_on_keypress" msgid="5258079494276955460">"按鍵時震動"</string>
+    <string name="sound_on_keypress" msgid="6093592297198243644">"按鍵時播放音效"</string>
+    <string name="popup_on_keypress" msgid="123894815723512944">"按鍵時顯示彈出式視窗"</string>
+    <string name="general_category" msgid="1859088467017573195">"一般設定"</string>
+    <string name="correction_category" msgid="2236750915056607613">"文字更正"</string>
+    <string name="gesture_typing_category" msgid="497263612130532630">"手勢輸入"</string>
+    <string name="misc_category" msgid="6894192814868233453">"其他選項"</string>
+    <string name="advanced_settings" msgid="362895144495591463">"進階設定"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"專家選項"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"切換至其他輸入法"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"語言切換鍵包括其他輸入法"</string>
+    <string name="show_language_switch_key" msgid="5915478828318774384">"語言切換鍵"</string>
+    <string name="show_language_switch_key_summary" msgid="7343403647474265713">"在啟用多種輸入語言時顯示"</string>
+    <string name="sliding_key_input_preview" msgid="6604262359510068370">"顯示滑動指示器"</string>
+    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"從 Shift 鍵或符號鍵開始滑動時顯示視覺提示"</string>
+    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"關閉彈出式鍵盤的延遲時間"</string>
+    <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"不延遲"</string>
+    <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"預設"</string>
+    <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> 毫秒"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"系統預設"</string>
+    <string name="use_contacts_dict" msgid="4435317977804180815">"建議聯絡人名稱"</string>
+    <string name="use_contacts_dict_summary" msgid="6599983334507879959">"使用「聯絡人」的名稱提供建議與修正"</string>
+    <string name="use_double_space_period" msgid="8781529969425082860">"按兩下空格鍵插入句號"</string>
+    <string name="use_double_space_period_summary" msgid="6532892187247952799">"只要輕按兩下空格鍵,即可插入句號並在後面加上一個空格"</string>
+    <string name="auto_cap" msgid="1719746674854628252">"自動大寫"</string>
+    <string name="auto_cap_summary" msgid="7934452761022946874">"每句首個字詞大寫"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"個人字典"</string>
+    <string name="configure_dictionaries_title" msgid="4238652338556902049">"附加字典"</string>
+    <string name="main_dictionary" msgid="4798763781818361168">"主要字典"</string>
+    <string name="prefs_show_suggestions" msgid="8026799663445531637">"顯示更正建議"</string>
+    <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"輸入時顯示建議字詞"</string>
+    <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"永遠顯示"</string>
+    <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"以垂直模式顯示"</string>
+    <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"永遠隱藏"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"封鎖令人反感的字詞"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"不建議使用可能令人反感的字詞"</string>
+    <string name="auto_correction" msgid="7630720885194996950">"自動更正"</string>
+    <string name="auto_correction_summary" msgid="5625751551134658006">"自動插入空白鍵和標點符號鍵盤,以修正拼字錯誤"</string>
+    <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"關閉"</string>
+    <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"普通模式"</string>
+    <string name="auto_correction_threshold_mode_aggressive" msgid="7319007299148899623">"加強模式"</string>
+    <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"極度加強模式"</string>
+    <string name="bigram_prediction" msgid="1084449187723948550">"建議下一個字詞"</string>
+    <string name="bigram_prediction_summary" msgid="3896362682751109677">"根據前一個字詞提出建議"</string>
+    <string name="gesture_input" msgid="826951152254563827">"啟用手勢輸入"</string>
+    <string name="gesture_input_summary" msgid="9180350639305731231">"透過滑動手指寫出字母來輸入字詞"</string>
+    <string name="gesture_preview_trail" msgid="3802333369335722221">"顯示手勢軌跡"</string>
+    <string name="gesture_floating_preview_text" msgid="4443240334739381053">"動態浮動預覽"</string>
+    <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"在啟用手勢輸入時顯示建議的字詞"</string>
+    <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>:已儲存"</string>
+    <string name="label_go_key" msgid="1635148082137219148">"開始"</string>
+    <string name="label_next_key" msgid="362972844525672568">"下一步"</string>
+    <string name="label_previous_key" msgid="1211868118071386787">"上一步"</string>
+    <string name="label_done_key" msgid="2441578748772529288">"完成"</string>
+    <string name="label_send_key" msgid="2815056534433717444">"發送"</string>
+    <string name="label_pause_key" msgid="181098308428035340">"暫停"</string>
+    <string name="label_wait_key" msgid="6402152600878093134">"等候"</string>
+    <string name="spoken_use_headphones" msgid="896961781287283493">"插上耳機即可聽到系統朗讀密碼鍵。"</string>
+    <string name="spoken_current_text_is" msgid="2485723011272583845">"目前文字為 %s"</string>
+    <string name="spoken_no_text_entered" msgid="7479685225597344496">"未輸入文字"</string>
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"按「<xliff:g id="KEY">%1$s</xliff:g>」可將「<xliff:g id="ORIGINAL_WORD">%2$s</xliff:g>」修正為「<xliff:g id="CORRECTED">%3$s</xliff:g>」"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"按「<xliff:g id="KEY">%1$s</xliff:g>」可自動修正"</string>
+    <string name="spoken_description_unknown" msgid="3197434010402179157">"按鍵代碼 %d"</string>
+    <string name="spoken_description_shift" msgid="244197883292549308">"Shift 鍵"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift 鍵已開啟 (輕按即可停用)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"大寫鎖定已開啟 (輕按即可停用)"</string>
+    <string name="spoken_description_delete" msgid="8740376944276199801">"刪除"</string>
+    <string name="spoken_description_to_symbol" msgid="5486340107500448969">"符號"</string>
+    <string name="spoken_description_to_alpha" msgid="23129338819771807">"字母"</string>
+    <string name="spoken_description_to_numeric" msgid="591752092685161732">"數字"</string>
+    <string name="spoken_description_settings" msgid="4627462689603838099">"設定"</string>
+    <string name="spoken_description_tab" msgid="2667716002663482248">"Tab 鍵"</string>
+    <string name="spoken_description_space" msgid="2582521050049860859">"空白鍵"</string>
+    <string name="spoken_description_mic" msgid="615536748882611950">"語音輸入"</string>
+    <string name="spoken_description_smiley" msgid="2256309826200113918">"笑臉"</string>
+    <string name="spoken_description_return" msgid="8178083177238315647">"Return 鍵"</string>
+    <string name="spoken_description_search" msgid="1247236163755920808">"搜尋"</string>
+    <string name="spoken_description_dot" msgid="40711082435231673">"點"</string>
+    <string name="spoken_description_language_switch" msgid="5507091328222331316">"切換語言"</string>
+    <string name="spoken_description_action_next" msgid="8636078276664150324">"下一步"</string>
+    <string name="spoken_description_action_previous" msgid="800872415009336208">"上一步"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift 鍵已啟用"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"大寫鎖定已啟用"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift 鍵已停用"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"符號模式"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"字母模式"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"撥號模式"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"符號撥號模式"</string>
+    <string name="announce_keyboard_hidden" msgid="8718927835531429807">"鍵盤已隱藏"</string>
+    <string name="announce_keyboard_mode" msgid="4729081055438508321">"目前顯示的是<xliff:g id="MODE">%s</xliff:g>鍵盤"</string>
+    <string name="keyboard_mode_date" msgid="3137520166817128102">"日期"</string>
+    <string name="keyboard_mode_date_time" msgid="339593358488851072">"日期和時間"</string>
+    <string name="keyboard_mode_email" msgid="6216248078128294262">"電郵"</string>
+    <string name="keyboard_mode_im" msgid="1137405089766557048">"短訊"</string>
+    <string name="keyboard_mode_number" msgid="7991623440699957069">"數字"</string>
+    <string name="keyboard_mode_phone" msgid="6851627527401433229">"電話"</string>
+    <string name="keyboard_mode_text" msgid="6479436687899701619">"文字"</string>
+    <string name="keyboard_mode_time" msgid="4381856885582143277">"時間"</string>
+    <string name="keyboard_mode_url" msgid="1519819835514911218">"網址"</string>
+    <string name="voice_input" msgid="3583258583521397548">"語音輸入鍵"</string>
+    <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"於主鍵盤"</string>
+    <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"符號鍵盤上"</string>
+    <string name="voice_input_modes_off" msgid="3745699748218082014">"關閉"</string>
+    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"主鍵盤上的麥克風"</string>
+    <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"符號鍵盤上的麥克風"</string>
+    <string name="voice_input_modes_summary_off" msgid="63875609591897607">"語音輸入已停用"</string>
+    <string name="configure_input_method" msgid="373356270290742459">"設定輸入法"</string>
+    <string name="language_selection_title" msgid="1651299598555326750">"輸入語言"</string>
+    <string name="send_feedback" msgid="1780431884109392046">"傳送意見"</string>
+    <string name="select_language" msgid="3693815588777926848">"輸入語言"</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"再次輕觸即可儲存"</string>
+    <string name="has_dictionary" msgid="6071847973466625007">"可使用字典"</string>
+    <string name="prefs_enable_log" msgid="6620424505072963557">"啟用用戶意見反映"</string>
+    <string name="prefs_description_log" msgid="7525225584555429211">"自動傳送使用統計資料和當機報告,協助改良這個輸入法編輯器"</string>
+    <string name="keyboard_layout" msgid="8451164783510487501">"鍵盤主題"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"英文 (英國)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"英文 (美國)"</string>
+    <string name="subtype_es_US" msgid="5583145191430180200">"西班牙文 (美國)"</string>
+    <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"英文 (英國) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"英文 (美國) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"西班牙文 (美國) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_nepali_traditional" msgid="9032247506728040447">"<xliff:g id="LANGUAGE">%s</xliff:g> (傳統)"</string>
+    <string name="subtype_no_language" msgid="7137390094240139495">"無語言 (字母)"</string>
+    <string name="subtype_no_language_qwerty" msgid="244337630616742604">"字母 (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="443066912507547976">"字母 (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8144348527575640087">"字母 (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="1564494667584718094">"字母 (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="5837418400010302623">"字母 (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="5354918232046200018">"字母 (PC)"</string>
+    <string name="subtype_emoji" msgid="7483586578074549196">"Emoji"</string>
+    <string name="custom_input_styles_title" msgid="8429952441821251512">"自訂輸入樣式"</string>
+    <string name="add_style" msgid="6163126614514489951">"新增樣式"</string>
+    <string name="add" msgid="8299699805688017798">"新增"</string>
+    <string name="remove" msgid="4486081658752944606">"移除"</string>
+    <string name="save" msgid="7646738597196767214">"儲存"</string>
+    <string name="subtype_locale" msgid="8576443440738143764">"語言"</string>
+    <string name="keyboard_layout_set" msgid="4309233698194565609">"配置"</string>
+    <string name="custom_input_style_note_message" msgid="8826731320846363423">"您必須先啟用自訂輸入樣式,才能開始使用。您要立即啟用嗎?"</string>
+    <string name="enable" msgid="5031294444630523247">"啟用"</string>
+    <string name="not_now" msgid="6172462888202790482">"暫時不要"</string>
+    <string name="custom_input_style_already_exists" msgid="8008728952215449707">"已存在相同的輸入樣式:<xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
+    <string name="prefs_usability_study_mode" msgid="1261130555134595254">"可用性研究模式"</string>
+    <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"長按鍵延遲"</string>
+    <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"按鍵震動時間"</string>
+    <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"按鍵音量"</string>
+    <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"讀取外部字典檔案"</string>
+    <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"「下載」資料夾中沒有任何字典檔案"</string>
+    <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"選取要安裝的字典檔案"</string>
+    <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"準備好要為<xliff:g id="LOCALE_NAME">%s</xliff:g>版本安裝這個檔案嗎?"</string>
+    <string name="error" msgid="8940763624668513648">"發生錯誤"</string>
+    <string name="button_default" msgid="3988017840431881491">"預設"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"歡迎使用「<xliff:g id="APPLICATION_NAME">%s</xliff:g>」"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"配備觸控輸入功能"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"開始"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"下一步"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"設定「<xliff:g id="APPLICATION_NAME">%s</xliff:g>」"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"啟用「<xliff:g id="APPLICATION_NAME">%s</xliff:g>」"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"請在語言與輸入設定中勾選「<xliff:g id="APPLICATION_NAME">%s</xliff:g>」,授權這個應用程式在您的裝置上執行。"</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"您已在 [語言與輸入設定] 中啟用「<xliff:g id="APPLICATION_NAME">%s</xliff:g>」。這個步驟已完成,可繼續下一個步驟了!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"在設定中啟用"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"切換至「<xliff:g id="APPLICATION_NAME">%s</xliff:g>」"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"接著,請選取「<xliff:g id="APPLICATION_NAME">%s</xliff:g>」作為目前使用的文字輸入方法。"</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"切換輸入方法"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"恭喜,一切就緒!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"現在,您可以在所有最愛的應用程式中使用「<xliff:g id="APPLICATION_NAME">%s</xliff:g>」輸入文字。"</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"設定其他語言"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"完成"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"顯示應用程式圖示"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"在啟動器中顯示應用程式圖示"</string>
+    <string name="app_name" msgid="6320102637491234792">"字典供應商"</string>
+    <string name="dictionary_provider_name" msgid="3027315045397363079">"字典供應商"</string>
+    <string name="dictionary_service_name" msgid="6237472350693511448">"字典服務"</string>
+    <string name="download_description" msgid="6014835283119198591">"字典更新資訊"</string>
+    <string name="dictionary_settings_title" msgid="8091417676045693313">"附加字典"</string>
+    <string name="dictionary_install_over_metered_network_prompt" msgid="3587517870006332980">"可使用字典"</string>
+    <string name="dictionary_settings_summary" msgid="5305694987799824349">"字典設定"</string>
+    <string name="user_dictionaries" msgid="3582332055892252845">"用戶字典"</string>
+    <string name="default_user_dict_pref_name" msgid="1625055720489280530">"用戶字典"</string>
+    <string name="dictionary_available" msgid="4728975345815214218">"可使用字典"</string>
+    <string name="dictionary_downloading" msgid="2982650524622620983">"目前下載中"</string>
+    <string name="dictionary_installed" msgid="8081558343559342962">"已安裝"</string>
+    <string name="dictionary_disabled" msgid="8950383219564621762">"已安裝,但已停用"</string>
+    <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"連線至字典服務時發生問題"</string>
+    <string name="no_dictionaries_available" msgid="8039920716566132611">"沒有可用的字典"</string>
+    <string name="check_for_updates_now" msgid="8087688440916388581">"重新整理"</string>
+    <string name="last_update" msgid="730467549913588780">"上次更新日期"</string>
+    <string name="message_updating" msgid="4457761393932375219">"正在查看更新"</string>
+    <string name="message_loading" msgid="8689096636874758814">"正在載入..."</string>
+    <string name="main_dict_description" msgid="3072821352793492143">"主要字典"</string>
+    <string name="cancel" msgid="6830980399865683324">"取消"</string>
+    <string name="install_dict" msgid="180852772562189365">"安裝"</string>
+    <string name="cancel_download_dict" msgid="7843340278507019303">"取消"</string>
+    <string name="delete_dict" msgid="756853268088330054">"刪除"</string>
+    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"您的流動裝置所選取的語言現有字典可供使用。&lt;br/&gt;建議您&lt;b&gt;下載&lt;/b&gt;<xliff:g id="LANGUAGE">%1$s</xliff:g>字典,讓您輸入時更方便。&lt;br/&gt;&lt;br/&gt;經由 3G 網絡下載需時一兩分鐘。如果您未使用&lt;b&gt;無限上網計劃&lt;/b&gt;,可能須另外付費。&lt;br/&gt;如果您不確定自己使用哪種上網計劃,建議您在連接 Wi-Fi 網絡後才開始自動下載。&lt;br/&gt;&lt;br/&gt;提示:您可以前往流動裝置的 [設定] &lt;b&gt;&lt;/b&gt;選單,透過其中的 [語言和輸入] &lt;b&gt;&lt;/b&gt;下載和移除字典。"</string>
+    <string name="download_over_metered" msgid="1643065851159409546">"立即下載 (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g> MB)"</string>
+    <string name="do_not_download_over_metered" msgid="2176209579313941583">"經由 Wi-Fi 下載"</string>
+    <string name="dict_available_notification_title" msgid="6514288591959117288">"可使用<xliff:g id="LANGUAGE">%1$s</xliff:g>字典"</string>
+    <string name="dict_available_notification_description" msgid="1075194169443163487">"按下即可查看並下載"</string>
+    <string name="toast_downloading_suggestions" msgid="1313027353588566660">"下載中:很快就能提供<xliff:g id="LANGUAGE">%1$s</xliff:g>字詞建議。"</string>
+    <string name="version_text" msgid="2715354215568469385">"版本 <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"新增"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"加入字典"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"詞組"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"更多選項"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"較少選項"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"確定"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"字詞:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"快速鍵:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"語言:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"輸入字詞"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"自選快速鍵"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"編輯字詞"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"編輯"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"刪除"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"您的用戶字典中沒有任何字詞。輕觸 [新增] (+) 按鈕即可新增字詞。"</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"所有語言"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"更多語言..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"刪除"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
+</resources>
diff --git a/java/res/values-zh-rTW/strings.xml b/java/res/values-zh-rTW/strings.xml
index b773ad2..4d39050 100644
--- a/java/res/values-zh-rTW/strings.xml
+++ b/java/res/values-zh-rTW/strings.xml
@@ -29,7 +29,7 @@
     <string name="popup_on_keypress" msgid="123894815723512944">"按鍵時彈出"</string>
     <string name="general_category" msgid="1859088467017573195">"一般"</string>
     <string name="correction_category" msgid="2236750915056607613">"文字修正"</string>
-    <string name="gesture_typing_category" msgid="497263612130532630">"手勢輸入"</string>
+    <string name="gesture_typing_category" msgid="497263612130532630">"滑行輸入"</string>
     <string name="misc_category" msgid="6894192814868233453">"其他選項"</string>
     <string name="advanced_settings" msgid="362895144495591463">"進階設定"</string>
     <string name="advanced_settings_summary" msgid="4487980456152830271">"進階選項"</string>
@@ -44,7 +44,7 @@
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"預設"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> 毫秒"</string>
     <string name="settings_system_default" msgid="6268225104743331821">"系統預設"</string>
-    <string name="use_contacts_dict" msgid="4435317977804180815">"建議聯絡人姓名"</string>
+    <string name="use_contacts_dict" msgid="4435317977804180815">"聯絡人姓名建議"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"根據「聯絡人」名稱提供建議與修正"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"輕按兩下空格鍵即插入句號"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"輕按兩下空格鍵可插入句號另加一個空格"</string>
@@ -68,11 +68,11 @@
     <string name="auto_correction_threshold_mode_very_aggressive" msgid="1853309024129480416">"極大幅度更正"</string>
     <string name="bigram_prediction" msgid="1084449187723948550">"建議下一個字詞"</string>
     <string name="bigram_prediction_summary" msgid="3896362682751109677">"根據前一個字詞提供建議"</string>
-    <string name="gesture_input" msgid="826951152254563827">"啟用手勢輸入"</string>
+    <string name="gesture_input" msgid="826951152254563827">"啟用滑行輸入"</string>
     <string name="gesture_input_summary" msgid="9180350639305731231">"以滑動方式寫出字詞中字母來輸入字詞"</string>
     <string name="gesture_preview_trail" msgid="3802333369335722221">"顯示手勢軌跡"</string>
     <string name="gesture_floating_preview_text" msgid="4443240334739381053">"動態浮動預覽"</string>
-    <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"使用手勢輸入時顯示建議字詞"</string>
+    <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"使用滑行輸入時顯示建議字詞"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>:已儲存"</string>
     <string name="label_go_key" msgid="1635148082137219148">"前往"</string>
     <string name="label_next_key" msgid="362972844525672568">"下一頁"</string>
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"連接耳機即可聽取系統朗讀密碼按鍵。"</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"目前文字為 %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"未輸入文字"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"按下「<xliff:g id="KEY">%1$s</xliff:g>」可將「<xliff:g id="ORIGINAL_WORD">%2$s</xliff:g>」修正為「<xliff:g id="CORRECTED">%3$s</xliff:g>」"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"按下「<xliff:g id="KEY">%1$s</xliff:g>」可執行自動修正"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"按鍵代碼 %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift 鍵"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift 鍵已開啟 (輕按即可停用)"</string>
@@ -178,7 +176,7 @@
     <string name="error" msgid="8940763624668513648">"發生錯誤"</string>
     <string name="button_default" msgid="3988017840431881491">"預設"</string>
     <string name="setup_welcome_title" msgid="6112821709832031715">"歡迎使用 <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
-    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"含手勢輸入功能"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"含滑行輸入功能"</string>
     <string name="setup_start_action" msgid="8936036460897347708">"開始設定"</string>
     <string name="setup_next_action" msgid="371821437915144603">"下一步"</string>
     <string name="setup_steps_title" msgid="6400373034871816182">"正在設定「<xliff:g id="APPLICATION_NAME">%s</xliff:g>」"</string>
diff --git a/java/res/values-zu/strings.xml b/java/res/values-zu/strings.xml
index 8f722cc..d245c32 100644
--- a/java/res/values-zu/strings.xml
+++ b/java/res/values-zu/strings.xml
@@ -84,10 +84,8 @@
     <string name="spoken_use_headphones" msgid="896961781287283493">"Plaka ku-headset ukuze uzwe okhiye bephasiwedi ezindlebeni zakho bezwakala kakhulu."</string>
     <string name="spoken_current_text_is" msgid="2485723011272583845">"Umbhalo wamanje ngu %s"</string>
     <string name="spoken_no_text_entered" msgid="7479685225597344496">"Awukho umbhalo ofakiwe"</string>
-    <!-- no translation found for spoken_auto_correct (5381764628886369268) -->
-    <skip />
-    <!-- no translation found for spoken_auto_correct_obscured (1186884531440481089) -->
-    <skip />
+    <string name="spoken_auto_correct" msgid="8005997889020109763">"I-<xliff:g id="KEY">%1$s</xliff:g> ilungisa i-<xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> ibe yi-<xliff:g id="CORRECTED">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="6276420476908833791">"I-<xliff:g id="KEY">%1$s</xliff:g> yenza ukulungiswa kokuzenzakalela"</string>
     <string name="spoken_description_unknown" msgid="3197434010402179157">"Ikhodi yokhiye %d"</string>
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"U-Shift uvuliwe (thepha ukuwuvimbela)"</string>
diff --git a/java/res/xml-sw600dp/rows_swiss.xml b/java/res/xml-sw600dp/rows_swiss.xml
new file mode 100644
index 0000000..4f4ca85
--- /dev/null
+++ b/java/res/xml-sw600dp/rows_swiss.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="8.182%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_swiss1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <Row
+        latin:keyWidth="8.182%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_swiss2" />
+        <Key
+            latin:keyStyle="enterKeyStyle"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <Row
+        latin:keyWidth="8.182%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="10.0%p" />
+        <Spacer
+            latin:keyWidth="3.181%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwertz3" />
+        <include
+            latin:keyboardLayout="@xml/keys_exclamation_question" />
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyXPos="-10.0%p"
+            latin:keyWidth="fillRight" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/res/xml/kbd_swiss.xml b/java/res/xml/kbd_swiss.xml
new file mode 100644
index 0000000..c64ad11
--- /dev/null
+++ b/java/res/xml/kbd_swiss.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<Keyboard
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/rows_swiss" />
+</Keyboard>
diff --git a/java/res/xml/keyboard_layout_set_swiss.xml b/java/res/xml/keyboard_layout_set_swiss.xml
new file mode 100644
index 0000000..e17a5ab
--- /dev/null
+++ b/java/res/xml/keyboard_layout_set_swiss.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<KeyboardLayoutSet
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin">
+    <Element
+        latin:elementName="alphabet"
+        latin:elementKeyboard="@xml/kbd_swiss"
+        latin:enableProximityCharsCorrection="true" />
+    <Element
+        latin:elementName="symbols"
+        latin:elementKeyboard="@xml/kbd_symbols" />
+    <Element
+        latin:elementName="symbolsShifted"
+        latin:elementKeyboard="@xml/kbd_symbols_shift" />
+    <Element
+        latin:elementName="phone"
+        latin:elementKeyboard="@xml/kbd_phone" />
+    <Element
+        latin:elementName="phoneSymbols"
+        latin:elementKeyboard="@xml/kbd_phone_symbols" />
+    <Element
+        latin:elementName="number"
+        latin:elementKeyboard="@xml/kbd_number" />
+</KeyboardLayoutSet>
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index 0a27da9..8a84a4c 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -32,6 +32,7 @@
     cs: Czech/qwertz
     da: Danish/nordic
     de: German/qwertz
+    de_CH: German Switzerland/swiss
     el: Greek/greek
     en_US: English United States/qwerty
     en_GB: English Great Britain/qwerty
@@ -44,6 +45,7 @@
     fi: Finnish/nordic
     fr: French/azerty
     fr_CA: French Canada/qwerty
+    fr_CH: French Switzerland/swiss
     hi: Hindi/hindi
     hr: Croatian/qwertz
     hu: Hungarian/qwertz
@@ -183,6 +185,13 @@
     />
     <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
+            android:subtypeId="0x7acfd0aa"
+            android:imeSubtypeLocale="de_CH"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=swiss,AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
+    />
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
+            android:label="@string/subtype_generic"
             android:subtypeId="0x0e7802d3"
             android:imeSubtypeLocale="el"
             android:imeSubtypeMode="keyboard"
@@ -255,6 +264,13 @@
     />
     <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
+            android:subtypeId="0xeadc55f5"
+            android:imeSubtypeLocale="fr_CH"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=swiss,AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
+    />
+    <subtype android:icon="@drawable/ic_ime_switcher_dark"
+            android:label="@string/subtype_generic"
             android:subtypeId="0x39753b7f"
             android:imeSubtypeLocale="hi"
             android:imeSubtypeMode="keyboard"
diff --git a/java/res/xml/rowkeys_swiss1.xml b/java/res/xml/rowkeys_swiss1.xml
new file mode 100644
index 0000000..e3b8426
--- /dev/null
+++ b/java/res/xml/rowkeys_swiss1.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/rowkeys_qwertz1" />
+    <Key
+        latin:keyLabel="!text/keylabel_for_swiss_row1_11"
+        latin:moreKeys="!text/more_keys_for_swiss_row1_11" />
+</merge>
diff --git a/java/res/xml/rowkeys_swiss2.xml b/java/res/xml/rowkeys_swiss2.xml
new file mode 100644
index 0000000..5364a44
--- /dev/null
+++ b/java/res/xml/rowkeys_swiss2.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/rowkeys_qwerty2" />
+    <Key
+        latin:keyLabel="!text/keylabel_for_swiss_row2_10"
+        latin:moreKeys="!text/more_keys_for_swiss_row2_10" />
+    <Key
+        latin:keyLabel="!text/keylabel_for_swiss_row2_11"
+        latin:moreKeys="!text/more_keys_for_swiss_row2_11" />
+</merge>
diff --git a/java/res/xml/rows_swiss.xml b/java/res/xml/rows_swiss.xml
new file mode 100644
index 0000000..03e4129
--- /dev/null
+++ b/java/res/xml/rows_swiss.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <include
+        latin:keyboardLayout="@xml/key_styles_common" />
+    <Row
+        latin:keyWidth="9.091%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_swiss1" />
+    </Row>
+    <Row
+        latin:keyWidth="9.091%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_swiss2" />
+    </Row>
+    <Row
+        latin:keyWidth="9.2%p"
+    >
+        <Key
+            latin:keyStyle="shiftKeyStyle"
+            latin:keyWidth="15%p"
+            latin:visualInsetsRight="1%p" />
+        <Spacer
+            latin:keyWidth="2.8%p" />
+        <include
+            latin:keyboardLayout="@xml/rowkeys_qwertz3" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyXPos="-15%p"
+            latin:keyWidth="fillRight"
+            latin:visualInsetsLeft="1%p" />
+    </Row>
+    <include
+        latin:keyboardLayout="@xml/row_qwerty4" />
+</merge>
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java
index f123735..c375223 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java
@@ -44,8 +44,7 @@
 import android.widget.TextView;
 
 import com.android.inputmethod.keyboard.internal.DynamicGridKeyboard;
-import com.android.inputmethod.keyboard.internal.ScrollKeyboardView;
-import com.android.inputmethod.keyboard.internal.ScrollViewWithNotifier;
+import com.android.inputmethod.keyboard.internal.EmojiPageKeyboardView;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SubtypeSwitcher;
@@ -72,7 +71,7 @@
  */
 public final class EmojiPalettesView extends LinearLayout implements OnTabChangeListener,
         ViewPager.OnPageChangeListener, View.OnClickListener,
-        ScrollKeyboardView.OnKeyClickListener {
+        EmojiPageKeyboardView.OnKeyClickListener {
     private static final String TAG = EmojiPalettesView.class.getSimpleName();
     private static final boolean DEBUG_PAGER = false;
     private final int mKeyBackgroundId;
@@ -628,16 +627,16 @@
     }
 
     private static class EmojiPalettesAdapter extends PagerAdapter {
-        private final ScrollKeyboardView.OnKeyClickListener mListener;
+        private final EmojiPageKeyboardView.OnKeyClickListener mListener;
         private final DynamicGridKeyboard mRecentsKeyboard;
-        private final SparseArray<ScrollKeyboardView> mActiveKeyboardViews =
+        private final SparseArray<EmojiPageKeyboardView> mActiveKeyboardViews =
                 CollectionUtils.newSparseArray();
         private final EmojiCategory mEmojiCategory;
         private int mActivePosition = 0;
 
         public EmojiPalettesAdapter(final EmojiCategory emojiCategory,
                 final KeyboardLayoutSet layoutSet,
-                final ScrollKeyboardView.OnKeyClickListener listener) {
+                final EmojiPageKeyboardView.OnKeyClickListener listener) {
             mEmojiCategory = emojiCategory;
             mListener = listener;
             mRecentsKeyboard = mEmojiCategory.getKeyboard(CATEGORY_ID_RECENTS, 0);
@@ -675,7 +674,7 @@
             if (mActivePosition == position) {
                 return;
             }
-            final ScrollKeyboardView oldKeyboardView = mActiveKeyboardViews.get(mActivePosition);
+            final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(mActivePosition);
             if (oldKeyboardView != null) {
                 oldKeyboardView.releaseCurrentKey();
                 oldKeyboardView.deallocateMemory();
@@ -688,7 +687,7 @@
             if (DEBUG_PAGER) {
                 Log.d(TAG, "instantiate item: " + position);
             }
-            final ScrollKeyboardView oldKeyboardView = mActiveKeyboardViews.get(position);
+            final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(position);
             if (oldKeyboardView != null) {
                 oldKeyboardView.deallocateMemory();
                 // This may be redundant but wanted to be safer..
@@ -697,18 +696,13 @@
             final Keyboard keyboard =
                     mEmojiCategory.getKeyboardFromPagePosition(position);
             final LayoutInflater inflater = LayoutInflater.from(container.getContext());
-            final View view = inflater.inflate(
+            final EmojiPageKeyboardView keyboardView = (EmojiPageKeyboardView)inflater.inflate(
                     R.layout.emoji_keyboard_page, container, false /* attachToRoot */);
-            final ScrollKeyboardView keyboardView = (ScrollKeyboardView)view.findViewById(
-                    R.id.emoji_keyboard_page);
             keyboardView.setKeyboard(keyboard);
             keyboardView.setOnKeyClickListener(mListener);
-            final ScrollViewWithNotifier scrollView = (ScrollViewWithNotifier)view.findViewById(
-                    R.id.emoji_keyboard_scroller);
-            keyboardView.setScrollView(scrollView);
-            container.addView(view);
+            container.addView(keyboardView);
             mActiveKeyboardViews.put(position, keyboardView);
-            return view;
+            return keyboardView;
         }
 
         @Override
@@ -722,7 +716,7 @@
             if (DEBUG_PAGER) {
                 Log.d(TAG, "destroy item: " + position + ", " + object.getClass().getSimpleName());
             }
-            final ScrollKeyboardView keyboardView = mActiveKeyboardViews.get(position);
+            final EmojiPageKeyboardView keyboardView = mActiveKeyboardViews.get(position);
             if (keyboardView != null) {
                 keyboardView.deallocateMemory();
                 mActiveKeyboardViews.remove(position);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/ScrollKeyboardView.java b/java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java
similarity index 64%
rename from java/src/com/android/inputmethod/keyboard/internal/ScrollKeyboardView.java
rename to java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java
index 9cf68d4..5a996ff 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/ScrollKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java
@@ -20,25 +20,21 @@
 import android.util.AttributeSet;
 import android.view.GestureDetector;
 import android.view.MotionEvent;
-import android.widget.ScrollView;
-import android.widget.Scroller;
 
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.KeyDetector;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.keyboard.PointerTracker;
 import com.android.inputmethod.latin.R;
 
 /**
- * This is an extended {@link KeyboardView} class that hosts a vertical scroll keyboard.
+ * This is an extended {@link KeyboardView} class that hosts an emoji page keyboard.
  * Multi-touch unsupported. No {@link PointerTracker}s. No gesture support.
- * TODO: Vertical scroll capability should be removed from this class because it's no longer used.
  */
 // TODO: Implement key popup preview.
-public final class ScrollKeyboardView extends KeyboardView implements
-        ScrollViewWithNotifier.ScrollListener, GestureDetector.OnGestureListener {
-    private static final boolean PAGINATION = false;
-
+public final class EmojiPageKeyboardView extends KeyboardView implements
+        GestureDetector.OnGestureListener {
     public interface OnKeyClickListener {
         public void onKeyClick(Key key);
     }
@@ -52,63 +48,15 @@
     private final KeyDetector mKeyDetector = new KeyDetector(0.0f /*keyHysteresisDistance */);
     private final GestureDetector mGestureDetector;
 
-    private final Scroller mScroller;
-    private ScrollViewWithNotifier mScrollView;
-
-    public ScrollKeyboardView(final Context context, final AttributeSet attrs) {
+    public EmojiPageKeyboardView(final Context context, final AttributeSet attrs) {
         this(context, attrs, R.attr.keyboardViewStyle);
     }
 
-    public ScrollKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
+    public EmojiPageKeyboardView(final Context context, final AttributeSet attrs,
+            final int defStyle) {
         super(context, attrs, defStyle);
         mGestureDetector = new GestureDetector(context, this);
         mGestureDetector.setIsLongpressEnabled(false /* isLongpressEnabled */);
-        mScroller = new Scroller(context);
-    }
-
-    public void setScrollView(final ScrollViewWithNotifier scrollView) {
-        mScrollView = scrollView;
-        scrollView.setScrollListener(this);
-    }
-
-    private final Runnable mScrollTask = new Runnable() {
-        @Override
-        public void run() {
-            final Scroller scroller = mScroller;
-            final ScrollView scrollView = mScrollView;
-            scroller.computeScrollOffset();
-            scrollView.scrollTo(0, scroller.getCurrY());
-            if (!scroller.isFinished()) {
-                scrollView.post(this);
-            }
-        }
-    };
-
-    // {@link ScrollViewWithNotified#ScrollListener} methods.
-    @Override
-    public void notifyScrollChanged(final int scrollX, final int scrollY, final int oldX,
-            final int oldY) {
-        if (PAGINATION) {
-            mScroller.forceFinished(true /* finished */);
-            mScrollView.removeCallbacks(mScrollTask);
-            final int currentTop = mScrollView.getScrollY();
-            final int pageHeight = getKeyboard().mBaseHeight;
-            final int lastPageNo = currentTop / pageHeight;
-            final int lastPageTop = lastPageNo * pageHeight;
-            final int nextPageNo = lastPageNo + 1;
-            final int nextPageTop = Math.min(nextPageNo * pageHeight, getHeight() - pageHeight);
-            final int scrollTo = (currentTop - lastPageTop) < (nextPageTop - currentTop)
-                    ? lastPageTop : nextPageTop;
-            final int deltaY = scrollTo - currentTop;
-            mScroller.startScroll(0, currentTop, 0, deltaY, 300);
-            mScrollView.post(mScrollTask);
-        }
-    }
-
-    @Override
-    public void notifyOverScrolled(final int scrollX, final int scrollY, final boolean clampedX,
-            final boolean clampedY) {
-        releaseCurrentKey();
     }
 
     public void setOnKeyClickListener(final OnKeyClickListener listener) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index e769e3c..98515c8 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -147,111 +147,117 @@
         /* 42 */ "keylabel_for_south_slavic_row3_8",
         /* 43 */ "more_keys_for_cyrillic_ie",
         /* 44 */ "more_keys_for_cyrillic_i",
-        /* 45 */ "label_to_alpha_key",
-        /* 46 */ "single_quotes",
-        /* 47 */ "double_quotes",
-        /* 48 */ "single_angle_quotes",
-        /* 49 */ "double_angle_quotes",
-        /* 50 */ "more_keys_for_currency_dollar",
-        /* 51 */ "keylabel_for_currency",
-        /* 52 */ "more_keys_for_currency",
-        /* 53 */ "more_keys_for_punctuation",
-        /* 54 */ "more_keys_for_star",
-        /* 55 */ "more_keys_for_bullet",
-        /* 56 */ "more_keys_for_plus",
-        /* 57 */ "more_keys_for_left_parenthesis",
-        /* 58 */ "more_keys_for_right_parenthesis",
-        /* 59 */ "more_keys_for_less_than",
-        /* 60 */ "more_keys_for_greater_than",
-        /* 61 */ "more_keys_for_arabic_diacritics",
-        /* 62 */ "keyhintlabel_for_arabic_diacritics",
-        /* 63 */ "keylabel_for_symbols_1",
-        /* 64 */ "keylabel_for_symbols_2",
-        /* 65 */ "keylabel_for_symbols_3",
-        /* 66 */ "keylabel_for_symbols_4",
-        /* 67 */ "keylabel_for_symbols_5",
-        /* 68 */ "keylabel_for_symbols_6",
-        /* 69 */ "keylabel_for_symbols_7",
-        /* 70 */ "keylabel_for_symbols_8",
-        /* 71 */ "keylabel_for_symbols_9",
-        /* 72 */ "keylabel_for_symbols_0",
-        /* 73 */ "label_to_symbol_key",
-        /* 74 */ "label_to_symbol_with_microphone_key",
-        /* 75 */ "additional_more_keys_for_symbols_1",
-        /* 76 */ "additional_more_keys_for_symbols_2",
-        /* 77 */ "additional_more_keys_for_symbols_3",
-        /* 78 */ "additional_more_keys_for_symbols_4",
-        /* 79 */ "additional_more_keys_for_symbols_5",
-        /* 80 */ "additional_more_keys_for_symbols_6",
-        /* 81 */ "additional_more_keys_for_symbols_7",
-        /* 82 */ "additional_more_keys_for_symbols_8",
-        /* 83 */ "additional_more_keys_for_symbols_9",
-        /* 84 */ "additional_more_keys_for_symbols_0",
-        /* 85 */ "more_keys_for_symbols_1",
-        /* 86 */ "more_keys_for_symbols_2",
-        /* 87 */ "more_keys_for_symbols_3",
-        /* 88 */ "more_keys_for_symbols_4",
-        /* 89 */ "more_keys_for_symbols_5",
-        /* 90 */ "more_keys_for_symbols_6",
-        /* 91 */ "more_keys_for_symbols_7",
-        /* 92 */ "more_keys_for_symbols_8",
-        /* 93 */ "more_keys_for_symbols_9",
-        /* 94 */ "more_keys_for_symbols_0",
-        /* 95 */ "keylabel_for_comma",
-        /* 96 */ "more_keys_for_comma",
-        /* 97 */ "keylabel_for_symbols_question",
-        /* 98 */ "keylabel_for_symbols_semicolon",
-        /* 99 */ "keylabel_for_symbols_percent",
-        /* 100 */ "more_keys_for_symbols_exclamation",
-        /* 101 */ "more_keys_for_symbols_question",
-        /* 102 */ "more_keys_for_symbols_semicolon",
-        /* 103 */ "more_keys_for_symbols_percent",
-        /* 104 */ "keylabel_for_tablet_comma",
-        /* 105 */ "keyhintlabel_for_tablet_comma",
-        /* 106 */ "more_keys_for_tablet_comma",
-        /* 107 */ "keyhintlabel_for_period",
-        /* 108 */ "more_keys_for_period",
-        /* 109 */ "keylabel_for_apostrophe",
-        /* 110 */ "keyhintlabel_for_apostrophe",
-        /* 111 */ "more_keys_for_apostrophe",
-        /* 112 */ "more_keys_for_q",
-        /* 113 */ "more_keys_for_x",
-        /* 114 */ "keylabel_for_q",
-        /* 115 */ "keylabel_for_w",
-        /* 116 */ "keylabel_for_y",
-        /* 117 */ "keylabel_for_x",
-        /* 118 */ "keylabel_for_spanish_row2_10",
-        /* 119 */ "more_keys_for_am_pm",
-        /* 120 */ "settings_as_more_key",
-        /* 121 */ "shortcut_as_more_key",
-        /* 122 */ "action_next_as_more_key",
-        /* 123 */ "action_previous_as_more_key",
-        /* 124 */ "label_to_more_symbol_key",
-        /* 125 */ "label_to_more_symbol_for_tablet_key",
-        /* 126 */ "label_tab_key",
-        /* 127 */ "label_to_phone_numeric_key",
-        /* 128 */ "label_to_phone_symbols_key",
-        /* 129 */ "label_time_am",
-        /* 130 */ "label_time_pm",
-        /* 131 */ "keylabel_for_popular_domain",
-        /* 132 */ "more_keys_for_popular_domain",
-        /* 133 */ "more_keys_for_smiley",
-        /* 134 */ "single_laqm_raqm",
-        /* 135 */ "single_laqm_raqm_rtl",
-        /* 136 */ "single_raqm_laqm",
-        /* 137 */ "double_laqm_raqm",
-        /* 138 */ "double_laqm_raqm_rtl",
-        /* 139 */ "double_raqm_laqm",
-        /* 140 */ "single_lqm_rqm",
-        /* 141 */ "single_9qm_lqm",
-        /* 142 */ "single_9qm_rqm",
-        /* 143 */ "double_lqm_rqm",
-        /* 144 */ "double_9qm_lqm",
-        /* 145 */ "double_9qm_rqm",
-        /* 146 */ "more_keys_for_single_quote",
-        /* 147 */ "more_keys_for_double_quote",
-        /* 148 */ "more_keys_for_tablet_double_quote",
-        /* 149 */ "emoji_key_as_more_key",
+        /* 45 */ "keylabel_for_swiss_row1_11",
+        /* 46 */ "keylabel_for_swiss_row2_10",
+        /* 47 */ "keylabel_for_swiss_row2_11",
+        /* 48 */ "more_keys_for_swiss_row1_11",
+        /* 49 */ "more_keys_for_swiss_row2_10",
+        /* 50 */ "more_keys_for_swiss_row2_11",
+        /* 51 */ "label_to_alpha_key",
+        /* 52 */ "single_quotes",
+        /* 53 */ "double_quotes",
+        /* 54 */ "single_angle_quotes",
+        /* 55 */ "double_angle_quotes",
+        /* 56 */ "more_keys_for_currency_dollar",
+        /* 57 */ "keylabel_for_currency",
+        /* 58 */ "more_keys_for_currency",
+        /* 59 */ "more_keys_for_punctuation",
+        /* 60 */ "more_keys_for_star",
+        /* 61 */ "more_keys_for_bullet",
+        /* 62 */ "more_keys_for_plus",
+        /* 63 */ "more_keys_for_left_parenthesis",
+        /* 64 */ "more_keys_for_right_parenthesis",
+        /* 65 */ "more_keys_for_less_than",
+        /* 66 */ "more_keys_for_greater_than",
+        /* 67 */ "more_keys_for_arabic_diacritics",
+        /* 68 */ "keyhintlabel_for_arabic_diacritics",
+        /* 69 */ "keylabel_for_symbols_1",
+        /* 70 */ "keylabel_for_symbols_2",
+        /* 71 */ "keylabel_for_symbols_3",
+        /* 72 */ "keylabel_for_symbols_4",
+        /* 73 */ "keylabel_for_symbols_5",
+        /* 74 */ "keylabel_for_symbols_6",
+        /* 75 */ "keylabel_for_symbols_7",
+        /* 76 */ "keylabel_for_symbols_8",
+        /* 77 */ "keylabel_for_symbols_9",
+        /* 78 */ "keylabel_for_symbols_0",
+        /* 79 */ "label_to_symbol_key",
+        /* 80 */ "label_to_symbol_with_microphone_key",
+        /* 81 */ "additional_more_keys_for_symbols_1",
+        /* 82 */ "additional_more_keys_for_symbols_2",
+        /* 83 */ "additional_more_keys_for_symbols_3",
+        /* 84 */ "additional_more_keys_for_symbols_4",
+        /* 85 */ "additional_more_keys_for_symbols_5",
+        /* 86 */ "additional_more_keys_for_symbols_6",
+        /* 87 */ "additional_more_keys_for_symbols_7",
+        /* 88 */ "additional_more_keys_for_symbols_8",
+        /* 89 */ "additional_more_keys_for_symbols_9",
+        /* 90 */ "additional_more_keys_for_symbols_0",
+        /* 91 */ "more_keys_for_symbols_1",
+        /* 92 */ "more_keys_for_symbols_2",
+        /* 93 */ "more_keys_for_symbols_3",
+        /* 94 */ "more_keys_for_symbols_4",
+        /* 95 */ "more_keys_for_symbols_5",
+        /* 96 */ "more_keys_for_symbols_6",
+        /* 97 */ "more_keys_for_symbols_7",
+        /* 98 */ "more_keys_for_symbols_8",
+        /* 99 */ "more_keys_for_symbols_9",
+        /* 100 */ "more_keys_for_symbols_0",
+        /* 101 */ "keylabel_for_comma",
+        /* 102 */ "more_keys_for_comma",
+        /* 103 */ "keylabel_for_symbols_question",
+        /* 104 */ "keylabel_for_symbols_semicolon",
+        /* 105 */ "keylabel_for_symbols_percent",
+        /* 106 */ "more_keys_for_symbols_exclamation",
+        /* 107 */ "more_keys_for_symbols_question",
+        /* 108 */ "more_keys_for_symbols_semicolon",
+        /* 109 */ "more_keys_for_symbols_percent",
+        /* 110 */ "keylabel_for_tablet_comma",
+        /* 111 */ "keyhintlabel_for_tablet_comma",
+        /* 112 */ "more_keys_for_tablet_comma",
+        /* 113 */ "keyhintlabel_for_period",
+        /* 114 */ "more_keys_for_period",
+        /* 115 */ "keylabel_for_apostrophe",
+        /* 116 */ "keyhintlabel_for_apostrophe",
+        /* 117 */ "more_keys_for_apostrophe",
+        /* 118 */ "more_keys_for_q",
+        /* 119 */ "more_keys_for_x",
+        /* 120 */ "keylabel_for_q",
+        /* 121 */ "keylabel_for_w",
+        /* 122 */ "keylabel_for_y",
+        /* 123 */ "keylabel_for_x",
+        /* 124 */ "keylabel_for_spanish_row2_10",
+        /* 125 */ "more_keys_for_am_pm",
+        /* 126 */ "settings_as_more_key",
+        /* 127 */ "shortcut_as_more_key",
+        /* 128 */ "action_next_as_more_key",
+        /* 129 */ "action_previous_as_more_key",
+        /* 130 */ "label_to_more_symbol_key",
+        /* 131 */ "label_to_more_symbol_for_tablet_key",
+        /* 132 */ "label_tab_key",
+        /* 133 */ "label_to_phone_numeric_key",
+        /* 134 */ "label_to_phone_symbols_key",
+        /* 135 */ "label_time_am",
+        /* 136 */ "label_time_pm",
+        /* 137 */ "keylabel_for_popular_domain",
+        /* 138 */ "more_keys_for_popular_domain",
+        /* 139 */ "more_keys_for_smiley",
+        /* 140 */ "single_laqm_raqm",
+        /* 141 */ "single_laqm_raqm_rtl",
+        /* 142 */ "single_raqm_laqm",
+        /* 143 */ "double_laqm_raqm",
+        /* 144 */ "double_laqm_raqm_rtl",
+        /* 145 */ "double_raqm_laqm",
+        /* 146 */ "single_lqm_rqm",
+        /* 147 */ "single_9qm_lqm",
+        /* 148 */ "single_9qm_rqm",
+        /* 149 */ "double_lqm_rqm",
+        /* 150 */ "double_9qm_lqm",
+        /* 151 */ "double_9qm_rqm",
+        /* 152 */ "more_keys_for_single_quote",
+        /* 153 */ "more_keys_for_double_quote",
+        /* 154 */ "more_keys_for_tablet_double_quote",
+        /* 155 */ "emoji_key_as_more_key",
     };
 
     private static final String EMPTY = "";
@@ -262,145 +268,145 @@
         EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
         EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
         EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
-        EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
-        /* ~44 */
+        EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
+        /* ~50 */
         // Label for "switch to alphabetic" key.
-        /* 45 */ "ABC",
-        /* 46 */ "!text/single_lqm_rqm",
-        /* 47 */ "!text/double_lqm_rqm",
-        /* 48 */ "!text/single_laqm_raqm",
-        /* 49 */ "!text/double_laqm_raqm",
+        /* 51 */ "ABC",
+        /* 52 */ "!text/single_lqm_rqm",
+        /* 53 */ "!text/double_lqm_rqm",
+        /* 54 */ "!text/single_laqm_raqm",
+        /* 55 */ "!text/double_laqm_raqm",
         // U+00A2: "¢" CENT SIGN
         // U+00A3: "£" POUND SIGN
         // U+20AC: "€" EURO SIGN
         // U+00A5: "¥" YEN SIGN
         // U+20B1: "₱" PESO SIGN
-        /* 50 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
-        /* 51 */ "$",
-        /* 52 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1",
-        /* 53 */ "!fixedColumnOrder!4,#,!,\\,,?,-,:,',@",
+        /* 56 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
+        /* 57 */ "$",
+        /* 58 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1",
+        /* 59 */ "!fixedColumnOrder!4,#,!,\\,,?,-,:,',@",
         // U+2020: "†" DAGGER
         // U+2021: "‡" DOUBLE DAGGER
         // U+2605: "★" BLACK STAR
-        /* 54 */ "\u2020,\u2021,\u2605",
+        /* 60 */ "\u2020,\u2021,\u2605",
         // U+266A: "♪" EIGHTH NOTE
         // U+2665: "♥" BLACK HEART SUIT
         // U+2660: "♠" BLACK SPADE SUIT
         // U+2666: "♦" BLACK DIAMOND SUIT
         // U+2663: "♣" BLACK CLUB SUIT
-        /* 55 */ "\u266A,\u2665,\u2660,\u2666,\u2663",
+        /* 61 */ "\u266A,\u2665,\u2660,\u2666,\u2663",
         // U+00B1: "±" PLUS-MINUS SIGN
-        /* 56 */ "\u00B1",
+        /* 62 */ "\u00B1",
         // The all letters need to be mirrored are found at
         // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
-        /* 57 */ "!fixedColumnOrder!3,<,{,[",
-        /* 58 */ "!fixedColumnOrder!3,>,},]",
+        /* 63 */ "!fixedColumnOrder!3,<,{,[",
+        /* 64 */ "!fixedColumnOrder!3,>,},]",
         // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
         // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
         // U+2264: "≤" LESS-THAN OR EQUAL TO
         // U+2265: "≥" GREATER-THAN EQUAL TO
         // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
-        /* 59 */ "!fixedColumnOrder!3,\u2039,\u2264,\u00AB",
-        /* 60 */ "!fixedColumnOrder!3,\u203A,\u2265,\u00BB",
-        /* 61 */ EMPTY,
-        /* 62 */ EMPTY,
-        /* 63 */ "1",
-        /* 64 */ "2",
-        /* 65 */ "3",
-        /* 66 */ "4",
-        /* 67 */ "5",
-        /* 68 */ "6",
-        /* 69 */ "7",
-        /* 70 */ "8",
-        /* 71 */ "9",
-        /* 72 */ "0",
+        /* 65 */ "!fixedColumnOrder!3,\u2039,\u2264,\u00AB",
+        /* 66 */ "!fixedColumnOrder!3,\u203A,\u2265,\u00BB",
+        /* 67 */ EMPTY,
+        /* 68 */ EMPTY,
+        /* 69 */ "1",
+        /* 70 */ "2",
+        /* 71 */ "3",
+        /* 72 */ "4",
+        /* 73 */ "5",
+        /* 74 */ "6",
+        /* 75 */ "7",
+        /* 76 */ "8",
+        /* 77 */ "9",
+        /* 78 */ "0",
         // Label for "switch to symbols" key.
-        /* 73 */ "?123",
+        /* 79 */ "?123",
         // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
         // part because it'll be appended by the code.
-        /* 74 */ "123",
-        /* 75~ */
+        /* 80 */ "123",
+        /* 81~ */
         EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
-        /* ~84 */
+        /* ~90 */
         // U+00B9: "¹" SUPERSCRIPT ONE
         // U+00BD: "½" VULGAR FRACTION ONE HALF
         // U+2153: "⅓" VULGAR FRACTION ONE THIRD
         // U+00BC: "¼" VULGAR FRACTION ONE QUARTER
         // U+215B: "⅛" VULGAR FRACTION ONE EIGHTH
-        /* 85 */ "\u00B9,\u00BD,\u2153,\u00BC,\u215B",
+        /* 91 */ "\u00B9,\u00BD,\u2153,\u00BC,\u215B",
         // U+00B2: "²" SUPERSCRIPT TWO
         // U+2154: "⅔" VULGAR FRACTION TWO THIRDS
-        /* 86 */ "\u00B2,\u2154",
+        /* 92 */ "\u00B2,\u2154",
         // U+00B3: "³" SUPERSCRIPT THREE
         // U+00BE: "¾" VULGAR FRACTION THREE QUARTERS
         // U+215C: "⅜" VULGAR FRACTION THREE EIGHTHS
-        /* 87 */ "\u00B3,\u00BE,\u215C",
+        /* 93 */ "\u00B3,\u00BE,\u215C",
         // U+2074: "⁴" SUPERSCRIPT FOUR
-        /* 88 */ "\u2074",
+        /* 94 */ "\u2074",
         // U+215D: "⅝" VULGAR FRACTION FIVE EIGHTHS
-        /* 89 */ "\u215D",
-        /* 90 */ EMPTY,
+        /* 95 */ "\u215D",
+        /* 96 */ EMPTY,
         // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS
-        /* 91 */ "\u215E",
-        /* 92 */ EMPTY,
-        /* 93 */ EMPTY,
+        /* 97 */ "\u215E",
+        /* 98 */ EMPTY,
+        /* 99 */ EMPTY,
         // U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N
         // U+2205: "∅" EMPTY SET
-        /* 94 */ "\u207F,\u2205",
-        /* 95 */ ",",
-        /* 96 */ EMPTY,
-        /* 97 */ "?",
-        /* 98 */ ";",
-        /* 99 */ "%",
-        // U+00A1: "¡" INVERTED EXCLAMATION MARK
-        /* 100 */ "\u00A1",
-        // U+00BF: "¿" INVERTED QUESTION MARK
-        /* 101 */ "\u00BF",
+        /* 100 */ "\u207F,\u2205",
+        /* 101 */ ",",
         /* 102 */ EMPTY,
+        /* 103 */ "?",
+        /* 104 */ ";",
+        /* 105 */ "%",
+        // U+00A1: "¡" INVERTED EXCLAMATION MARK
+        /* 106 */ "\u00A1",
+        // U+00BF: "¿" INVERTED QUESTION MARK
+        /* 107 */ "\u00BF",
+        /* 108 */ EMPTY,
         // U+2030: "‰" PER MILLE SIGN
-        /* 103 */ "\u2030",
-        /* 104 */ ",",
-        /* 105~ */
+        /* 109 */ "\u2030",
+        /* 110 */ ",",
+        /* 111~ */
         EMPTY, EMPTY, EMPTY,
-        /* ~107 */
+        /* ~113 */
         // U+2026: "…" HORIZONTAL ELLIPSIS
-        /* 108 */ "\u2026",
-        /* 109 */ "\'",
-        /* 110 */ "\"",
-        /* 111 */ "\"",
-        /* 112 */ EMPTY,
-        /* 113 */ EMPTY,
-        /* 114 */ "q",
-        /* 115 */ "w",
-        /* 116 */ "y",
-        /* 117 */ "x",
+        /* 114 */ "\u2026",
+        /* 115 */ "\'",
+        /* 116 */ "\"",
+        /* 117 */ "\"",
         /* 118 */ EMPTY,
-        /* 119 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm",
-        /* 120 */ "!icon/settings_key|!code/key_settings",
-        /* 121 */ "!icon/shortcut_key|!code/key_shortcut",
-        /* 122 */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
-        /* 123 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
+        /* 119 */ EMPTY,
+        /* 120 */ "q",
+        /* 121 */ "w",
+        /* 122 */ "y",
+        /* 123 */ "x",
+        /* 124 */ EMPTY,
+        /* 125 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm",
+        /* 126 */ "!icon/settings_key|!code/key_settings",
+        /* 127 */ "!icon/shortcut_key|!code/key_shortcut",
+        /* 128 */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
+        /* 129 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
         // Label for "switch to more symbol" modifier key.  Must be short to fit on key!
-        /* 124 */ "= \\ <",
+        /* 130 */ "= \\ <",
         // Label for "switch to more symbol" modifier key on tablets.  Must be short to fit on key!
-        /* 125 */ "~ [ <",
+        /* 131 */ "~ [ <",
         // Label for "Tab" key.  Must be short to fit on key!
-        /* 126 */ "Tab",
+        /* 132 */ "Tab",
         // Label for "switch to phone numeric" key.  Must be short to fit on key!
-        /* 127 */ "123",
+        /* 133 */ "123",
         // Label for "switch to phone symbols" key.  Must be short to fit on key!
         // U+FF0A: "*" FULLWIDTH ASTERISK
         // U+FF03: "#" FULLWIDTH NUMBER SIGN
-        /* 128 */ "\uFF0A\uFF03",
+        /* 134 */ "\uFF0A\uFF03",
         // Key label for "ante meridiem"
-        /* 129 */ "AM",
+        /* 135 */ "AM",
         // Key label for "post meridiem"
-        /* 130 */ "PM",
-        /* 131 */ ".com",
+        /* 136 */ "PM",
+        /* 137 */ ".com",
         // popular web domains for the locale - most popular, displayed on the keyboard
-        /* 132 */ "!hasLabels!,.net,.org,.gov,.edu",
-        /* 133 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
+        /* 138 */ "!hasLabels!,.net,.org,.gov,.edu",
+        /* 139 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
         // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
         // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
         // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
@@ -422,25 +428,25 @@
         // The following each quotation mark pair consist of
         // <opening quotation mark>, <closing quotation mark>
         // and is named after (single|double)_<opening quotation mark>_<closing quotation mark>.
-        /* 134 */ "\u2039,\u203A",
-        /* 135 */ "\u2039|\u203A,\u203A|\u2039",
-        /* 136 */ "\u203A,\u2039",
-        /* 137 */ "\u00AB,\u00BB",
-        /* 138 */ "\u00AB|\u00BB,\u00BB|\u00AB",
-        /* 139 */ "\u00BB,\u00AB",
+        /* 140 */ "\u2039,\u203A",
+        /* 141 */ "\u2039|\u203A,\u203A|\u2039",
+        /* 142 */ "\u203A,\u2039",
+        /* 143 */ "\u00AB,\u00BB",
+        /* 144 */ "\u00AB|\u00BB,\u00BB|\u00AB",
+        /* 145 */ "\u00BB,\u00AB",
         // The following each quotation mark triplet consists of
         // <another quotation mark>, <opening quotation mark>, <closing quotation mark>
         // and is named after (single|double)_<opening quotation mark>_<closing quotation mark>.
-        /* 140 */ "\u201A,\u2018,\u2019",
-        /* 141 */ "\u2019,\u201A,\u2018",
-        /* 142 */ "\u2018,\u201A,\u2019",
-        /* 143 */ "\u201E,\u201C,\u201D",
-        /* 144 */ "\u201D,\u201E,\u201C",
-        /* 145 */ "\u201C,\u201E,\u201D",
-        /* 146 */ "!fixedColumnOrder!5,!text/single_quotes,!text/single_angle_quotes",
-        /* 147 */ "!fixedColumnOrder!5,!text/double_quotes,!text/double_angle_quotes",
-        /* 148 */ "!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes",
-        /* 149 */ "!icon/emoji_key|!code/key_emoji",
+        /* 146 */ "\u201A,\u2018,\u2019",
+        /* 147 */ "\u2019,\u201A,\u2018",
+        /* 148 */ "\u2018,\u201A,\u2019",
+        /* 149 */ "\u201E,\u201C,\u201D",
+        /* 150 */ "\u201D,\u201E,\u201C",
+        /* 151 */ "\u201C,\u201E,\u201D",
+        /* 152 */ "!fixedColumnOrder!5,!text/single_quotes,!text/single_angle_quotes",
+        /* 153 */ "!fixedColumnOrder!5,!text/double_quotes,!text/double_angle_quotes",
+        /* 154 */ "!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes",
+        /* 155 */ "!icon/emoji_key|!code/key_emoji",
     };
 
     /* Language af: Afrikaans */
@@ -502,44 +508,45 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~44 */
+        null, null, null, null, null, null,
+        /* ~50 */
         // Label for "switch to alphabetic" key.
         // U+0623: "ا" ARABIC LETTER ALEF
         // U+200C: ZERO WIDTH NON-JOINER
         // U+0628: "ب" ARABIC LETTER BEH
         // U+062C: "پ" ARABIC LETTER PEH
-        /* 45 */ "\u0623\u200C\u0628\u200C\u062C",
-        /* 46 */ null,
-        /* 47 */ null,
-        /* 48 */ "!text/single_laqm_raqm_rtl",
-        /* 49 */ "!text/double_laqm_raqm_rtl",
-        /* 50~ */
+        /* 51 */ "\u0623\u200C\u0628\u200C\u062C",
+        /* 52 */ null,
+        /* 53 */ null,
+        /* 54 */ "!text/single_laqm_raqm_rtl",
+        /* 55 */ "!text/double_laqm_raqm_rtl",
+        /* 56~ */
         null, null, null,
-        /* ~52 */
+        /* ~58 */
         // U+061F: "؟" ARABIC QUESTION MARK
         // U+060C: "،" ARABIC COMMA
         // U+061B: "؛" ARABIC SEMICOLON
-        /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(|),)|(",
+        /* 59 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(|),)|(",
         // U+2605: "★" BLACK STAR
         // U+066D: "٭" ARABIC FIVE POINTED STAR
-        /* 54 */ "\u2605,\u066D",
+        /* 60 */ "\u2605,\u066D",
         // U+266A: "♪" EIGHTH NOTE
-        /* 55 */ "\u266A",
-        /* 56 */ null,
+        /* 61 */ "\u266A",
+        /* 62 */ null,
         // The all letters need to be mirrored are found at
         // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
         // U+FD3E: "﴾" ORNATE LEFT PARENTHESIS
         // U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS
-        /* 57 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
-        /* 58 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
+        /* 63 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
+        /* 64 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
         // U+2264: "≤" LESS-THAN OR EQUAL TO
         // U+2265: "≥" GREATER-THAN EQUAL TO
         // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
         // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
-        /* 59 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
-        /* 60 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
+        /* 65 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
+        /* 66 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
         // U+0655: "ٕ" ARABIC HAMZA BELOW
         // U+0654: "ٔ" ARABIC HAMZA ABOVE
         // U+0652: "ْ" ARABIC SUKUN
@@ -556,70 +563,70 @@
         // U+0640: "ـ" ARABIC TATWEEL
         // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label.
         // Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly.
-        /* 61 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0654|\u0654, \u0652|\u0652, \u064D|\u064D, \u064C|\u064C, \u064B|\u064B, \u0651|\u0651, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u0650|\u0650, \u064F|\u064F, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
-        /* 62 */ "\u0651",
+        /* 67 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0654|\u0654, \u0652|\u0652, \u064D|\u064D, \u064C|\u064C, \u064B|\u064B, \u0651|\u0651, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u0650|\u0650, \u064F|\u064F, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
+        /* 68 */ "\u0651",
         // U+0661: "١" ARABIC-INDIC DIGIT ONE
-        /* 63 */ "\u0661",
+        /* 69 */ "\u0661",
         // U+0662: "٢" ARABIC-INDIC DIGIT TWO
-        /* 64 */ "\u0662",
+        /* 70 */ "\u0662",
         // U+0663: "٣" ARABIC-INDIC DIGIT THREE
-        /* 65 */ "\u0663",
+        /* 71 */ "\u0663",
         // U+0664: "٤" ARABIC-INDIC DIGIT FOUR
-        /* 66 */ "\u0664",
+        /* 72 */ "\u0664",
         // U+0665: "٥" ARABIC-INDIC DIGIT FIVE
-        /* 67 */ "\u0665",
+        /* 73 */ "\u0665",
         // U+0666: "٦" ARABIC-INDIC DIGIT SIX
-        /* 68 */ "\u0666",
+        /* 74 */ "\u0666",
         // U+0667: "٧" ARABIC-INDIC DIGIT SEVEN
-        /* 69 */ "\u0667",
+        /* 75 */ "\u0667",
         // U+0668: "٨" ARABIC-INDIC DIGIT EIGHT
-        /* 70 */ "\u0668",
+        /* 76 */ "\u0668",
         // U+0669: "٩" ARABIC-INDIC DIGIT NINE
-        /* 71 */ "\u0669",
+        /* 77 */ "\u0669",
         // U+0660: "٠" ARABIC-INDIC DIGIT ZERO
-        /* 72 */ "\u0660",
+        /* 78 */ "\u0660",
         // Label for "switch to symbols" key.
         // U+061F: "؟" ARABIC QUESTION MARK
-        /* 73 */ "\u0663\u0662\u0661\u061F",
+        /* 79 */ "\u0663\u0662\u0661\u061F",
         // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
         // part because it'll be appended by the code.
-        /* 74 */ "\u0663\u0662\u0661",
-        /* 75 */ "1",
-        /* 76 */ "2",
-        /* 77 */ "3",
-        /* 78 */ "4",
-        /* 79 */ "5",
-        /* 80 */ "6",
-        /* 81 */ "7",
-        /* 82 */ "8",
-        /* 83 */ "9",
+        /* 80 */ "\u0663\u0662\u0661",
+        /* 81 */ "1",
+        /* 82 */ "2",
+        /* 83 */ "3",
+        /* 84 */ "4",
+        /* 85 */ "5",
+        /* 86 */ "6",
+        /* 87 */ "7",
+        /* 88 */ "8",
+        /* 89 */ "9",
         // U+066B: "٫" ARABIC DECIMAL SEPARATOR
         // U+066C: "٬" ARABIC THOUSANDS SEPARATOR
-        /* 84 */ "0,\u066B,\u066C",
-        /* 85~ */
+        /* 90 */ "0,\u066B,\u066C",
+        /* 91~ */
         null, null, null, null, null, null, null, null, null, null,
-        /* ~94 */
+        /* ~100 */
         // U+060C: "،" ARABIC COMMA
-        /* 95 */ "\u060C",
-        /* 96 */ "\\,",
-        /* 97 */ "\u061F",
-        /* 98 */ "\u061B",
+        /* 101 */ "\u060C",
+        /* 102 */ "\\,",
+        /* 103 */ "\u061F",
+        /* 104 */ "\u061B",
         // U+066A: "٪" ARABIC PERCENT SIGN
-        /* 99 */ "\u066A",
-        /* 100 */ null,
-        /* 101 */ "?",
-        /* 102 */ ";",
+        /* 105 */ "\u066A",
+        /* 106 */ null,
+        /* 107 */ "?",
+        /* 108 */ ";",
         // U+2030: "‰" PER MILLE SIGN
-        /* 103 */ "\\%,\u2030",
-        /* 104~ */
+        /* 109 */ "\\%,\u2030",
+        /* 110~ */
         null, null, null, null, null,
-        /* ~108 */
+        /* ~114 */
         // U+060C: "،" ARABIC COMMA
         // U+061B: "؛" ARABIC SEMICOLON
         // U+061F: "؟" ARABIC QUESTION MARK
-        /* 109 */ "\u060C",
-        /* 110 */ "\u061F",
-        /* 111 */ "\u061F,\u061B,!,:,-,/,\',\"",
+        /* 115 */ "\u060C",
+        /* 116 */ "\u061F",
+        /* 117 */ "\u061F,\u061B,!,:,-,/,\',\"",
     };
 
     /* Language az: Azerbaijani */
@@ -694,14 +701,16 @@
         /* ~42 */
         // U+0451: "ё" CYRILLIC SMALL LETTER IO
         /* 43 */ "\u0451",
-        /* 44 */ null,
+        /* 44~ */
+        null, null, null, null, null, null, null,
+        /* ~50 */
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 45 */ "\u0410\u0411\u0412",
-        /* 46 */ "!text/single_9qm_lqm",
-        /* 47 */ "!text/double_9qm_lqm",
+        /* 51 */ "\u0410\u0411\u0412",
+        /* 52 */ "!text/single_9qm_lqm",
+        /* 53 */ "!text/double_9qm_lqm",
     };
 
     /* Language bg: Bulgarian */
@@ -710,15 +719,16 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~44 */
+        null, null, null, null, null, null,
+        /* ~50 */
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 45 */ "\u0410\u0411\u0412",
-        /* 46 */ null,
+        /* 51 */ "\u0410\u0411\u0412",
+        /* 52 */ null,
         // single_quotes of Bulgarian is default single_quotes_right_left.
-        /* 47 */ "!text/double_9qm_lqm",
+        /* 53 */ "!text/double_9qm_lqm",
     };
 
     /* Language ca: Catalan */
@@ -782,22 +792,22 @@
         /* 15~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null,
-        /* ~52 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~58 */
         // U+00B7: "·" MIDDLE DOT
-        /* 53 */ "!fixedColumnOrder!4,\u00B7,!,\\,,?,:,;,@",
-        /* 54~ */
+        /* 59 */ "!fixedColumnOrder!4,\u00B7,!,\\,,?,:,;,@",
+        /* 60~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null,
-        /* ~107 */
-        /* 108 */ "?,\u00B7",
-        /* 109~ */
+        /* ~113 */
+        /* 114 */ "?,\u00B7",
+        /* 115~ */
         null, null, null, null, null, null, null, null, null,
-        /* ~117 */
+        /* ~123 */
         // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
-        /* 118 */ "\u00E7",
+        /* 124 */ "\u00E7",
     };
 
     /* Language cs: Czech */
@@ -871,12 +881,12 @@
         /* 13~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null,
-        /* ~45 */
-        /* 46 */ "!text/single_9qm_lqm",
-        /* 47 */ "!text/double_9qm_lqm",
-        /* 48 */ "!text/single_raqm_laqm",
-        /* 49 */ "!text/double_raqm_laqm",
+        null, null, null, null, null, null, null, null, null,
+        /* ~51 */
+        /* 52 */ "!text/single_9qm_lqm",
+        /* 53 */ "!text/double_9qm_lqm",
+        /* 54 */ "!text/single_raqm_laqm",
+        /* 55 */ "!text/double_raqm_laqm",
     };
 
     /* Language da: Danish */
@@ -940,12 +950,12 @@
         /* 24 */ "\u00F6",
         /* 25~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null,
-        /* ~45 */
-        /* 46 */ "!text/single_9qm_lqm",
-        /* 47 */ "!text/double_9qm_lqm",
-        /* 48 */ "!text/single_raqm_laqm",
-        /* 49 */ "!text/double_raqm_laqm",
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~51 */
+        /* 52 */ "!text/single_9qm_lqm",
+        /* 53 */ "!text/double_9qm_lqm",
+        /* 54 */ "!text/single_raqm_laqm",
+        /* 55 */ "!text/double_raqm_laqm",
     };
 
     /* Language de: German */
@@ -991,12 +1001,25 @@
         /* 7~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null,
-        /* ~45 */
-        /* 46 */ "!text/single_9qm_lqm",
-        /* 47 */ "!text/double_9qm_lqm",
-        /* 48 */ "!text/single_raqm_laqm",
-        /* 49 */ "!text/double_raqm_laqm",
+        null, null, null, null, null, null, null, null,
+        /* ~44 */
+        // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+        /* 45 */ "\u00FC",
+        // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+        /* 46 */ "\u00F6",
+        // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+        /* 47 */ "\u00E4",
+        // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+        /* 48 */ "\u00E8",
+        // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+        /* 49 */ "\u00E9",
+        // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+        /* 50 */ "\u00E0",
+        /* 51 */ null,
+        /* 52 */ "!text/single_9qm_lqm",
+        /* 53 */ "!text/double_9qm_lqm",
+        /* 54 */ "!text/single_raqm_laqm",
+        /* 55 */ "!text/double_raqm_laqm",
     };
 
     /* Language el: Greek */
@@ -1005,12 +1028,13 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~44 */
+        null, null, null, null, null, null,
+        /* ~50 */
         // Label for "switch to alphabetic" key.
         // U+0391: "Α" GREEK CAPITAL LETTER ALPHA
         // U+0392: "Β" GREEK CAPITAL LETTER BETA
         // U+0393: "Γ" GREEK CAPITAL LETTER GAMMA
-        /* 45 */ "\u0391\u0392\u0393",
+        /* 51 */ "\u0391\u0392\u0393",
     };
 
     /* Language en: English */
@@ -1182,20 +1206,20 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null,
-        /* ~111 */
-        /* 112 */ "q",
-        /* 113 */ "x",
+        null, null, null, null, null, null, null, null,
+        /* ~117 */
+        /* 118 */ "q",
+        /* 119 */ "x",
         // U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX
-        /* 114 */ "\u015D",
+        /* 120 */ "\u015D",
         // U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX
-        /* 115 */ "\u011D",
+        /* 121 */ "\u011D",
         // U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE
-        /* 116 */ "\u016D",
+        /* 122 */ "\u016D",
         // U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX
-        /* 117 */ "\u0109",
+        /* 123 */ "\u0109",
         // U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX
-        /* 118 */ "\u0135",
+        /* 124 */ "\u0135",
     };
 
     /* Language es: Spanish */
@@ -1254,29 +1278,30 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~52 */
+        null, null, null, null, null, null,
+        /* ~58 */
         // U+00A1: "¡" INVERTED EXCLAMATION MARK
         // U+00BF: "¿" INVERTED QUESTION MARK
-        /* 53 */ "!fixedColumnOrder!4,;,!,\\,,?,:,\u00A1,@,\u00BF",
-        /* 54~ */
+        /* 59 */ "!fixedColumnOrder!4,;,!,\\,,?,:,\u00A1,@,\u00BF",
+        /* 60~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null,
-        /* ~105 */
+        /* ~111 */
         // U+00A1: "¡" INVERTED EXCLAMATION MARK
-        /* 106 */ "!,\u00A1",
-        /* 107 */ null,
+        /* 112 */ "!,\u00A1",
+        /* 113 */ null,
         // U+00BF: "¿" INVERTED QUESTION MARK
-        /* 108 */ "?,\u00BF",
-        /* 109 */ "\"",
-        /* 110 */ "\'",
-        /* 111 */ "\'",
-        /* 112~ */
+        /* 114 */ "?,\u00BF",
+        /* 115 */ "\"",
+        /* 116 */ "\'",
+        /* 117 */ "\'",
+        /* 118~ */
         null, null, null, null, null, null,
-        /* ~117 */
+        /* ~123 */
         // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
-        /* 118 */ "\u00F1",
+        /* 124 */ "\u00F1",
     };
 
     /* Language et: Estonian */
@@ -1379,10 +1404,10 @@
         /* 23 */ "\u00F5",
         /* 24~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null,
-        /* ~45 */
-        /* 46 */ "!text/single_9qm_lqm",
-        /* 47 */ "!text/double_9qm_lqm",
+        null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~51 */
+        /* 52 */ "!text/single_9qm_lqm",
+        /* 53 */ "!text/double_9qm_lqm",
     };
 
     /* Language fa: Persian */
@@ -1391,45 +1416,46 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~44 */
+        null, null, null, null, null, null,
+        /* ~50 */
         // Label for "switch to alphabetic" key.
         // U+0627: "ا" ARABIC LETTER ALEF
         // U+200C: ZERO WIDTH NON-JOINER
         // U+0628: "ب" ARABIC LETTER BEH
         // U+067E: "پ" ARABIC LETTER PEH
-        /* 45 */ "\u0627\u200C\u0628\u200C\u067E",
-        /* 46 */ null,
-        /* 47 */ null,
-        /* 48 */ "!text/single_laqm_raqm_rtl",
-        /* 49 */ "!text/double_laqm_raqm_rtl",
-        /* 50 */ null,
-        // U+FDFC: "﷼" RIAL SIGN
-        /* 51 */ "\uFDFC",
+        /* 51 */ "\u0627\u200C\u0628\u200C\u067E",
         /* 52 */ null,
+        /* 53 */ null,
+        /* 54 */ "!text/single_laqm_raqm_rtl",
+        /* 55 */ "!text/double_laqm_raqm_rtl",
+        /* 56 */ null,
+        // U+FDFC: "﷼" RIAL SIGN
+        /* 57 */ "\uFDFC",
+        /* 58 */ null,
         // U+061F: "؟" ARABIC QUESTION MARK
         // U+060C: "،" ARABIC COMMA
         // U+061B: "؛" ARABIC SEMICOLON
-        /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(|),)|(",
+        /* 59 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(|),)|(",
         // U+2605: "★" BLACK STAR
         // U+066D: "٭" ARABIC FIVE POINTED STAR
-        /* 54 */ "\u2605,\u066D",
+        /* 60 */ "\u2605,\u066D",
         // U+266A: "♪" EIGHTH NOTE
-        /* 55 */ "\u266A",
-        /* 56 */ null,
+        /* 61 */ "\u266A",
+        /* 62 */ null,
         // The all letters need to be mirrored are found at
         // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
         // U+FD3E: "﴾" ORNATE LEFT PARENTHESIS
         // U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS
-        /* 57 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
-        /* 58 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
+        /* 63 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
+        /* 64 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
         // U+2264: "≤" LESS-THAN OR EQUAL TO
         // U+2265: "≥" GREATER-THAN EQUAL TO
         // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
         // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
-        /* 59 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,<|>",
-        /* 60 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,>|<",
+        /* 65 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,<|>",
+        /* 66 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,>|<",
         // U+0655: "ٕ" ARABIC HAMZA BELOW
         // U+0652: "ْ" ARABIC SUKUN
         // U+0651: "ّ" ARABIC SHADDA
@@ -1446,74 +1472,74 @@
         // U+0640: "ـ" ARABIC TATWEEL
         // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label.
         // Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly.
-        /* 61 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0652|\u0652, \u0651|\u0651, \u064C|\u064C, \u064D|\u064D, \u064B|\u064B, \u0654|\u0654, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u064F|\u064F, \u0650|\u0650, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
-        /* 62 */ "\u064B",
+        /* 67 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0652|\u0652, \u0651|\u0651, \u064C|\u064C, \u064D|\u064D, \u064B|\u064B, \u0654|\u0654, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u064F|\u064F, \u0650|\u0650, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
+        /* 68 */ "\u064B",
         // U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE
-        /* 63 */ "\u06F1",
+        /* 69 */ "\u06F1",
         // U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO
-        /* 64 */ "\u06F2",
+        /* 70 */ "\u06F2",
         // U+06F3: "۳" EXTENDED ARABIC-INDIC DIGIT THREE
-        /* 65 */ "\u06F3",
+        /* 71 */ "\u06F3",
         // U+06F4: "۴" EXTENDED ARABIC-INDIC DIGIT FOUR
-        /* 66 */ "\u06F4",
+        /* 72 */ "\u06F4",
         // U+06F5: "۵" EXTENDED ARABIC-INDIC DIGIT FIVE
-        /* 67 */ "\u06F5",
+        /* 73 */ "\u06F5",
         // U+06F6: "۶" EXTENDED ARABIC-INDIC DIGIT SIX
-        /* 68 */ "\u06F6",
+        /* 74 */ "\u06F6",
         // U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN
-        /* 69 */ "\u06F7",
+        /* 75 */ "\u06F7",
         // U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT
-        /* 70 */ "\u06F8",
+        /* 76 */ "\u06F8",
         // U+06F9: "۹" EXTENDED ARABIC-INDIC DIGIT NINE
-        /* 71 */ "\u06F9",
+        /* 77 */ "\u06F9",
         // U+06F0: "۰" EXTENDED ARABIC-INDIC DIGIT ZERO
-        /* 72 */ "\u06F0",
+        /* 78 */ "\u06F0",
         // Label for "switch to symbols" key.
         // U+061F: "؟" ARABIC QUESTION MARK
-        /* 73 */ "\u06F3\u06F2\u06F1\u061F",
+        /* 79 */ "\u06F3\u06F2\u06F1\u061F",
         // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
         // part because it'll be appended by the code.
-        /* 74 */ "\u06F3\u06F2\u06F1",
-        /* 75 */ "1",
-        /* 76 */ "2",
-        /* 77 */ "3",
-        /* 78 */ "4",
-        /* 79 */ "5",
-        /* 80 */ "6",
-        /* 81 */ "7",
-        /* 82 */ "8",
-        /* 83 */ "9",
+        /* 80 */ "\u06F3\u06F2\u06F1",
+        /* 81 */ "1",
+        /* 82 */ "2",
+        /* 83 */ "3",
+        /* 84 */ "4",
+        /* 85 */ "5",
+        /* 86 */ "6",
+        /* 87 */ "7",
+        /* 88 */ "8",
+        /* 89 */ "9",
         // U+066B: "٫" ARABIC DECIMAL SEPARATOR
         // U+066C: "٬" ARABIC THOUSANDS SEPARATOR
-        /* 84 */ "0,\u066B,\u066C",
-        /* 85~ */
+        /* 90 */ "0,\u066B,\u066C",
+        /* 91~ */
         null, null, null, null, null, null, null, null, null, null,
-        /* ~94 */
+        /* ~100 */
         // U+060C: "،" ARABIC COMMA
-        /* 95 */ "\u060C",
-        /* 96 */ "\\,",
-        /* 97 */ "\u061F",
-        /* 98 */ "\u061B",
+        /* 101 */ "\u060C",
+        /* 102 */ "\\,",
+        /* 103 */ "\u061F",
+        /* 104 */ "\u061B",
         // U+066A: "٪" ARABIC PERCENT SIGN
-        /* 99 */ "\u066A",
-        /* 100 */ null,
-        /* 101 */ "?",
-        /* 102 */ ";",
+        /* 105 */ "\u066A",
+        /* 106 */ null,
+        /* 107 */ "?",
+        /* 108 */ ";",
         // U+2030: "‰" PER MILLE SIGN
-        /* 103 */ "\\%,\u2030",
+        /* 109 */ "\\%,\u2030",
         // U+060C: "،" ARABIC COMMA
         // U+061B: "؛" ARABIC SEMICOLON
         // U+061F: "؟" ARABIC QUESTION MARK
         // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
-        /* 104 */ "\u060C",
-        /* 105 */ "!",
-        /* 106 */ "!,\\,",
-        /* 107 */ "\u061F",
-        /* 108 */ "\u061F,?",
-        /* 109 */ "\u060C",
-        /* 110 */ "\u061F",
-        /* 111 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB",
+        /* 110 */ "\u060C",
+        /* 111 */ "!",
+        /* 112 */ "!,\\,",
+        /* 113 */ "\u061F",
+        /* 114 */ "\u061F,?",
+        /* 115 */ "\u060C",
+        /* 116 */ "\u061F",
+        /* 117 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB",
     };
 
     /* Language fi: Finnish */
@@ -1614,6 +1640,23 @@
         /* 7 */ "\u00E7,\u0107,\u010D",
         // U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS
         /* 8 */ "%,\u00FF",
+        /* 9~ */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null,
+        /* ~44 */
+        // U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE
+        /* 45 */ "\u00E8",
+        // U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE
+        /* 46 */ "\u00E9",
+        // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE
+        /* 47 */ "\u00E0",
+        // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+        /* 48 */ "\u00FC",
+        // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+        /* 49 */ "\u00F6",
+        // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
+        /* 50 */ "\u00E4",
     };
 
     /* Language hi: Hindi */
@@ -1622,55 +1665,56 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~44 */
+        null, null, null, null, null, null,
+        /* ~50 */
         // Label for "switch to alphabetic" key.
         // U+0915: "क" DEVANAGARI LETTER KA
         // U+0916: "ख" DEVANAGARI LETTER KHA
         // U+0917: "ग" DEVANAGARI LETTER GA
-        /* 45 */ "\u0915\u0916\u0917",
-        /* 46~ */
-        null, null, null, null, null,
-        /* ~50 */
-        // U+20B9: "₹" INDIAN RUPEE SIGN
-        /* 51 */ "\u20B9",
+        /* 51 */ "\u0915\u0916\u0917",
         /* 52~ */
+        null, null, null, null, null,
+        /* ~56 */
+        // U+20B9: "₹" INDIAN RUPEE SIGN
+        /* 57 */ "\u20B9",
+        /* 58~ */
         null, null, null, null, null, null, null, null, null, null, null,
-        /* ~62 */
+        /* ~68 */
         // U+0967: "१" DEVANAGARI DIGIT ONE
-        /* 63 */ "\u0967",
+        /* 69 */ "\u0967",
         // U+0968: "२" DEVANAGARI DIGIT TWO
-        /* 64 */ "\u0968",
+        /* 70 */ "\u0968",
         // U+0969: "३" DEVANAGARI DIGIT THREE
-        /* 65 */ "\u0969",
+        /* 71 */ "\u0969",
         // U+096A: "४" DEVANAGARI DIGIT FOUR
-        /* 66 */ "\u096A",
+        /* 72 */ "\u096A",
         // U+096B: "५" DEVANAGARI DIGIT FIVE
-        /* 67 */ "\u096B",
+        /* 73 */ "\u096B",
         // U+096C: "६" DEVANAGARI DIGIT SIX
-        /* 68 */ "\u096C",
+        /* 74 */ "\u096C",
         // U+096D: "७" DEVANAGARI DIGIT SEVEN
-        /* 69 */ "\u096D",
+        /* 75 */ "\u096D",
         // U+096E: "८" DEVANAGARI DIGIT EIGHT
-        /* 70 */ "\u096E",
+        /* 76 */ "\u096E",
         // U+096F: "९" DEVANAGARI DIGIT NINE
-        /* 71 */ "\u096F",
+        /* 77 */ "\u096F",
         // U+0966: "०" DEVANAGARI DIGIT ZERO
-        /* 72 */ "\u0966",
+        /* 78 */ "\u0966",
         // Label for "switch to symbols" key.
-        /* 73 */ "?\u0967\u0968\u0969",
+        /* 79 */ "?\u0967\u0968\u0969",
         // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
         // part because it'll be appended by the code.
-        /* 74 */ "\u0967\u0968\u0969",
-        /* 75 */ "1",
-        /* 76 */ "2",
-        /* 77 */ "3",
-        /* 78 */ "4",
-        /* 79 */ "5",
-        /* 80 */ "6",
-        /* 81 */ "7",
-        /* 82 */ "8",
-        /* 83 */ "9",
-        /* 84 */ "0",
+        /* 80 */ "\u0967\u0968\u0969",
+        /* 81 */ "1",
+        /* 82 */ "2",
+        /* 83 */ "3",
+        /* 84 */ "4",
+        /* 85 */ "5",
+        /* 86 */ "6",
+        /* 87 */ "7",
+        /* 88 */ "8",
+        /* 89 */ "9",
+        /* 90 */ "0",
     };
 
     /* Language hr: Croatian */
@@ -1701,12 +1745,12 @@
         /* 13~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null,
-        /* ~45 */
-        /* 46 */ "!text/single_9qm_rqm",
-        /* 47 */ "!text/double_9qm_rqm",
-        /* 48 */ "!text/single_raqm_laqm",
-        /* 49 */ "!text/double_raqm_laqm",
+        null, null, null, null, null, null, null, null, null,
+        /* ~51 */
+        /* 52 */ "!text/single_9qm_rqm",
+        /* 53 */ "!text/double_9qm_rqm",
+        /* 54 */ "!text/single_raqm_laqm",
+        /* 55 */ "!text/double_raqm_laqm",
     };
 
     /* Language hu: Hungarian */
@@ -1755,12 +1799,13 @@
         /* 5~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null,
-        /* ~45 */
-        /* 46 */ "!text/single_9qm_rqm",
-        /* 47 */ "!text/double_9qm_rqm",
-        /* 48 */ "!text/single_raqm_laqm",
-        /* 49 */ "!text/double_raqm_laqm",
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null,
+        /* ~51 */
+        /* 52 */ "!text/single_9qm_rqm",
+        /* 53 */ "!text/double_9qm_rqm",
+        /* 54 */ "!text/single_raqm_laqm",
+        /* 55 */ "!text/double_raqm_laqm",
     };
 
     /* Language hy: Armenian */
@@ -1769,8 +1814,8 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null,
-        /* ~52 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~58 */
         // U+058A: "֊" ARMENIAN HYPHEN
         // U+055C: "՜" ARMENIAN EXCLAMATION MARK
         // U+055D: "՝" ARMENIAN COMMA
@@ -1779,19 +1824,19 @@
         // U+055A: "՚" ARMENIAN APOSTROPHE
         // U+055B: "՛" ARMENIAN EMPHASIS MARK
         // U+055F: "՟" ARMENIAN ABBREVIATION MARK
-        /* 53 */ "!fixedColumnOrder!8,!,?,\\,,.,\u058A,\u055C,\u055D,\u055E,:,;,@,\u0559,\u055A,\u055B,\u055F",
-        /* 54~ */
+        /* 59 */ "!fixedColumnOrder!8,!,?,\\,,.,\u058A,\u055C,\u055D,\u055E,:,;,@,\u0559,\u055A,\u055B,\u055F",
+        /* 60~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null,
-        /* ~99 */
+        /* ~105 */
         // U+055C: "՜" ARMENIAN EXCLAMATION MARK
         // U+00A1: "¡" INVERTED EXCLAMATION MARK
-        /* 100 */ "\u055C,\u00A1",
+        /* 106 */ "\u055C,\u00A1",
         // U+055E: "՞" ARMENIAN QUESTION MARK
         // U+00BF: "¿" INVERTED QUESTION MARK
-        /* 101 */ "\u055E,\u00BF",
+        /* 107 */ "\u055E,\u00BF",
     };
 
     /* Language is: Icelandic */
@@ -1857,10 +1902,10 @@
         /* 22 */ "\u00FE",
         /* 23~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null,
-        /* ~45 */
-        /* 46 */ "!text/single_9qm_lqm",
-        /* 47 */ "!text/double_9qm_lqm",
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~51 */
+        /* 52 */ "!text/single_9qm_lqm",
+        /* 53 */ "!text/double_9qm_lqm",
     };
 
     /* Language it: Italian */
@@ -1914,12 +1959,13 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~44 */
+        null, null, null, null, null, null,
+        /* ~50 */
         // Label for "switch to alphabetic" key.
         // U+05D0: "א" HEBREW LETTER ALEF
         // U+05D1: "ב" HEBREW LETTER BET
         // U+05D2: "ג" HEBREW LETTER GIMEL
-        /* 45 */ "\u05D0\u05D1\u05D2",
+        /* 51 */ "\u05D0\u05D1\u05D2",
         // The following characters don't need BIDI mirroring.
         // U+2018: "‘" LEFT SINGLE QUOTATION MARK
         // U+2019: "’" RIGHT SINGLE QUOTATION MARK
@@ -1927,42 +1973,42 @@
         // U+201C: "“" LEFT DOUBLE QUOTATION MARK
         // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
         // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-        /* 46 */ "\u2018,\u2019,\u201A",
-        /* 47 */ "\u201C,\u201D,\u201E",
-        /* 48 */ "!text/single_laqm_raqm_rtl",
-        /* 49 */ "!text/double_laqm_raqm_rtl",
-        /* 50 */ null,
+        /* 52 */ "\u2018,\u2019,\u201A",
+        /* 53 */ "\u201C,\u201D,\u201E",
+        /* 54 */ "!text/single_laqm_raqm_rtl",
+        /* 55 */ "!text/double_laqm_raqm_rtl",
+        /* 56 */ null,
         // U+20AA: "₪" NEW SHEQEL SIGN
-        /* 51 */ "\u20AA",
-        /* 52 */ null,
-        /* 53 */ null,
+        /* 57 */ "\u20AA",
+        /* 58 */ null,
+        /* 59 */ null,
         // U+2605: "★" BLACK STAR
-        /* 54 */ "\u2605",
-        /* 55 */ null,
+        /* 60 */ "\u2605",
+        /* 61 */ null,
         // U+00B1: "±" PLUS-MINUS SIGN
         // U+FB29: "﬩" HEBREW LETTER ALTERNATIVE PLUS SIGN
-        /* 56 */ "\u00B1,\uFB29",
+        /* 62 */ "\u00B1,\uFB29",
         // The all letters need to be mirrored are found at
         // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
-        /* 57 */ "!fixedColumnOrder!3,<|>,{|},[|]",
-        /* 58 */ "!fixedColumnOrder!3,>|<,}|{,]|[",
+        /* 63 */ "!fixedColumnOrder!3,<|>,{|},[|]",
+        /* 64 */ "!fixedColumnOrder!3,>|<,}|{,]|[",
         // U+2264: "≤" LESS-THAN OR EQUAL TO
         // U+2265: "≥" GREATER-THAN EQUAL TO
         // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
         // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
-        /* 59 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
-        /* 60 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
-        /* 61~ */
+        /* 65 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
+        /* 66 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
+        /* 67~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~104 */
-        /* 105 */ "!",
-        /* 106 */ "!",
-        /* 107 */ "?",
-        /* 108 */ "?",
+        /* ~110 */
+        /* 111 */ "!",
+        /* 112 */ "!",
+        /* 113 */ "?",
+        /* 114 */ "?",
     };
 
     /* Language ka: Georgian */
@@ -1971,14 +2017,15 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~44 */
+        null, null, null, null, null, null,
+        /* ~50 */
         // Label for "switch to alphabetic" key.
         // U+10D0: "ა" GEORGIAN LETTER AN
         // U+10D1: "ბ" GEORGIAN LETTER BAN
         // U+10D2: "გ" GEORGIAN LETTER GAN
-        /* 45 */ "\u10D0\u10D1\u10D2",
-        /* 46 */ "!text/single_9qm_lqm",
-        /* 47 */ "!text/double_9qm_lqm",
+        /* 51 */ "\u10D0\u10D1\u10D2",
+        /* 52 */ "!text/single_9qm_lqm",
+        /* 53 */ "!text/double_9qm_lqm",
     };
 
     /* Language kk: Kazakh */
@@ -2021,12 +2068,14 @@
         /* ~42 */
         // U+0451: "ё" CYRILLIC SMALL LETTER IO
         /* 43 */ "\u0451",
-        /* 44 */ null,
+        /* 44~ */
+        null, null, null, null, null, null, null,
+        /* ~50 */
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 45 */ "\u0410\u0411\u0412",
+        /* 51 */ "\u0410\u0411\u0412",
     };
 
     /* Language km: Khmer */
@@ -2035,17 +2084,18 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~44 */
+        null, null, null, null, null, null,
+        /* ~50 */
         // Label for "switch to alphabetic" key.
         // U+1780: "ក" KHMER LETTER KA
         // U+1781: "ខ" KHMER LETTER KHA
         // U+1782: "គ" KHMER LETTER KO
-        /* 45 */ "\u1780\u1781\u1782",
-        /* 46~ */
+        /* 51 */ "\u1780\u1781\u1782",
+        /* 52~ */
         null, null, null, null,
-        /* ~49 */
+        /* ~55 */
         // U+17DB: "៛" KHMER CURRENCY SYMBOL RIEL
-        /* 50 */ "\u17DB,\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
+        /* 56 */ "\u17DB,\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
     };
 
     /* Language ky: Kirghiz */
@@ -2081,12 +2131,14 @@
         /* ~42 */
         // U+0451: "ё" CYRILLIC SMALL LETTER IO
         /* 43 */ "\u0451",
-        /* 44 */ null,
+        /* 44~ */
+        null, null, null, null, null, null, null,
+        /* ~50 */
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 45 */ "\u0410\u0411\u0412",
+        /* 51 */ "\u0410\u0411\u0412",
     };
 
     /* Language lo: Lao */
@@ -2095,17 +2147,18 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~44 */
+        null, null, null, null, null, null,
+        /* ~50 */
         // Label for "switch to alphabetic" key.
         // U+0E81: "ກ" LAO LETTER KO
         // U+0E82: "ຂ" LAO LETTER KHO SUNG
         // U+0E84: "ຄ" LAO LETTER KHO TAM
-        /* 45 */ "\u0E81\u0E82\u0E84",
-        /* 46~ */
+        /* 51 */ "\u0E81\u0E82\u0E84",
+        /* 52~ */
         null, null, null, null, null,
-        /* ~50 */
+        /* ~56 */
         // U+20AD: "₭" KIP SIGN
-        /* 51 */ "\u20AD",
+        /* 57 */ "\u20AD",
     };
 
     /* Language lt: Lithuanian */
@@ -2199,9 +2252,10 @@
         /* 16~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~45 */
-        /* 46 */ "!text/single_9qm_lqm",
-        /* 47 */ "!text/double_9qm_lqm",
+        null, null, null, null, null, null,
+        /* ~51 */
+        /* 52 */ "!text/single_9qm_lqm",
+        /* 53 */ "!text/double_9qm_lqm",
     };
 
     /* Language lv: Latvian */
@@ -2294,9 +2348,10 @@
         /* 16~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~45 */
-        /* 46 */ "!text/single_9qm_lqm",
-        /* 47 */ "!text/double_9qm_lqm",
+        null, null, null, null, null, null,
+        /* ~51 */
+        /* 52 */ "!text/single_9qm_lqm",
+        /* 53 */ "!text/double_9qm_lqm",
     };
 
     /* Language mk: Macedonian */
@@ -2318,13 +2373,16 @@
         /* 43 */ "\u0450",
         // U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE
         /* 44 */ "\u045D",
+        /* 45~ */
+        null, null, null, null, null, null,
+        /* ~50 */
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 45 */ "\u0410\u0411\u0412",
-        /* 46 */ "!text/single_9qm_lqm",
-        /* 47 */ "!text/double_9qm_lqm",
+        /* 51 */ "\u0410\u0411\u0412",
+        /* 52 */ "!text/single_9qm_lqm",
+        /* 53 */ "!text/double_9qm_lqm",
     };
 
     /* Language mn: Mongolian */
@@ -2333,17 +2391,18 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~44 */
+        null, null, null, null, null, null,
+        /* ~50 */
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 45 */ "\u0410\u0411\u0412",
-        /* 46~ */
+        /* 51 */ "\u0410\u0411\u0412",
+        /* 52~ */
         null, null, null, null, null,
-        /* ~50 */
+        /* ~56 */
         // U+20AE: "₮" TUGRIK SIGN
-        /* 51 */ "\u20AE",
+        /* 57 */ "\u20AE",
     };
 
     /* Language nb: Norwegian Bokmål */
@@ -2393,10 +2452,10 @@
         /* 24 */ "\u00E4",
         /* 25~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null,
-        /* ~45 */
-        /* 46 */ "!text/single_9qm_rqm",
-        /* 47 */ "!text/double_9qm_rqm",
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~51 */
+        /* 52 */ "!text/single_9qm_rqm",
+        /* 53 */ "!text/double_9qm_rqm",
     };
 
     /* Language ne: Nepali */
@@ -2405,55 +2464,56 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~44 */
+        null, null, null, null, null, null,
+        /* ~50 */
         // Label for "switch to alphabetic" key.
         // U+0915: "क" DEVANAGARI LETTER KA
         // U+0916: "ख" DEVANAGARI LETTER KHA
         // U+0917: "ग" DEVANAGARI LETTER GA
-        /* 45 */ "\u0915\u0916\u0917",
-        /* 46~ */
-        null, null, null, null, null,
-        /* ~50 */
-        // U+0930/U+0941/U+002E "रु." NEPALESE RUPEE SIGN
-        /* 51 */ "\u0930\u0941.",
+        /* 51 */ "\u0915\u0916\u0917",
         /* 52~ */
+        null, null, null, null, null,
+        /* ~56 */
+        // U+0930/U+0941/U+002E "रु." NEPALESE RUPEE SIGN
+        /* 57 */ "\u0930\u0941.",
+        /* 58~ */
         null, null, null, null, null, null, null, null, null, null, null,
-        /* ~62 */
+        /* ~68 */
         // U+0967: "१" DEVANAGARI DIGIT ONE
-        /* 63 */ "\u0967",
+        /* 69 */ "\u0967",
         // U+0968: "२" DEVANAGARI DIGIT TWO
-        /* 64 */ "\u0968",
+        /* 70 */ "\u0968",
         // U+0969: "३" DEVANAGARI DIGIT THREE
-        /* 65 */ "\u0969",
+        /* 71 */ "\u0969",
         // U+096A: "४" DEVANAGARI DIGIT FOUR
-        /* 66 */ "\u096A",
+        /* 72 */ "\u096A",
         // U+096B: "५" DEVANAGARI DIGIT FIVE
-        /* 67 */ "\u096B",
+        /* 73 */ "\u096B",
         // U+096C: "६" DEVANAGARI DIGIT SIX
-        /* 68 */ "\u096C",
+        /* 74 */ "\u096C",
         // U+096D: "७" DEVANAGARI DIGIT SEVEN
-        /* 69 */ "\u096D",
+        /* 75 */ "\u096D",
         // U+096E: "८" DEVANAGARI DIGIT EIGHT
-        /* 70 */ "\u096E",
+        /* 76 */ "\u096E",
         // U+096F: "९" DEVANAGARI DIGIT NINE
-        /* 71 */ "\u096F",
+        /* 77 */ "\u096F",
         // U+0966: "०" DEVANAGARI DIGIT ZERO
-        /* 72 */ "\u0966",
+        /* 78 */ "\u0966",
         // Label for "switch to symbols" key.
-        /* 73 */ "?\u0967\u0968\u0969",
+        /* 79 */ "?\u0967\u0968\u0969",
         // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
         // part because it'll be appended by the code.
-        /* 74 */ "\u0967\u0968\u0969",
-        /* 75 */ "1",
-        /* 76 */ "2",
-        /* 77 */ "3",
-        /* 78 */ "4",
-        /* 79 */ "5",
-        /* 80 */ "6",
-        /* 81 */ "7",
-        /* 82 */ "8",
-        /* 83 */ "9",
-        /* 84 */ "0",
+        /* 80 */ "\u0967\u0968\u0969",
+        /* 81 */ "1",
+        /* 82 */ "2",
+        /* 83 */ "3",
+        /* 84 */ "4",
+        /* 85 */ "5",
+        /* 86 */ "6",
+        /* 87 */ "7",
+        /* 88 */ "8",
+        /* 89 */ "9",
+        /* 90 */ "0",
     };
 
     /* Language nl: Dutch */
@@ -2508,10 +2568,10 @@
         /* 9~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null,
-        /* ~45 */
-        /* 46 */ "!text/single_9qm_rqm",
-        /* 47 */ "!text/double_9qm_rqm",
+        null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~51 */
+        /* 52 */ "!text/single_9qm_rqm",
+        /* 53 */ "!text/double_9qm_rqm",
     };
 
     /* Language pl: Polish */
@@ -2569,10 +2629,10 @@
         /* 15~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null,
-        /* ~45 */
-        /* 46 */ "!text/single_9qm_rqm",
-        /* 47 */ "!text/double_9qm_rqm",
+        null, null, null, null, null, null, null,
+        /* ~51 */
+        /* 52 */ "!text/single_9qm_rqm",
+        /* 53 */ "!text/double_9qm_rqm",
     };
 
     /* Language pt: Portuguese */
@@ -2675,10 +2735,10 @@
         /* 12~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null,
-        /* ~45 */
-        /* 46 */ "!text/single_9qm_rqm",
-        /* 47 */ "!text/double_9qm_rqm",
+        null, null, null, null, null, null, null, null, null, null,
+        /* ~51 */
+        /* 52 */ "!text/single_9qm_rqm",
+        /* 53 */ "!text/double_9qm_rqm",
     };
 
     /* Language ru: Russian */
@@ -2707,14 +2767,16 @@
         /* ~42 */
         // U+0451: "ё" CYRILLIC SMALL LETTER IO
         /* 43 */ "\u0451",
-        /* 44 */ null,
+        /* 44~ */
+        null, null, null, null, null, null, null,
+        /* ~50 */
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 45 */ "\u0410\u0411\u0412",
-        /* 46 */ "!text/single_9qm_lqm",
-        /* 47 */ "!text/double_9qm_lqm",
+        /* 51 */ "\u0410\u0411\u0412",
+        /* 52 */ "!text/single_9qm_lqm",
+        /* 53 */ "!text/double_9qm_lqm",
     };
 
     /* Language sk: Slovak */
@@ -2808,11 +2870,12 @@
         /* 16~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~45 */
-        /* 46 */ "!text/single_9qm_lqm",
-        /* 47 */ "!text/double_9qm_lqm",
-        /* 48 */ "!text/single_raqm_laqm",
-        /* 49 */ "!text/double_raqm_laqm",
+        null, null, null, null, null, null,
+        /* ~51 */
+        /* 52 */ "!text/single_9qm_lqm",
+        /* 53 */ "!text/double_9qm_lqm",
+        /* 54 */ "!text/single_raqm_laqm",
+        /* 55 */ "!text/double_raqm_laqm",
     };
 
     /* Language sl: Slovenian */
@@ -2836,12 +2899,12 @@
         /* 13~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null,
-        /* ~45 */
-        /* 46 */ "!text/single_9qm_lqm",
-        /* 47 */ "!text/double_9qm_lqm",
-        /* 48 */ "!text/single_raqm_laqm",
-        /* 49 */ "!text/double_raqm_laqm",
+        null, null, null, null, null, null, null, null, null,
+        /* ~51 */
+        /* 52 */ "!text/single_9qm_lqm",
+        /* 53 */ "!text/double_9qm_lqm",
+        /* 54 */ "!text/single_raqm_laqm",
+        /* 55 */ "!text/double_raqm_laqm",
     };
 
     /* Language sr: Serbian */
@@ -2881,16 +2944,19 @@
         /* 43 */ "\u0450",
         // U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE
         /* 44 */ "\u045D",
+        /* 45~ */
+        null, null, null, null, null, null,
+        /* ~50 */
         // END: More keys definitions for Serbian (Cyrillic)
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 45 */ "\u0410\u0411\u0412",
-        /* 46 */ "!text/single_9qm_lqm",
-        /* 47 */ "!text/double_9qm_lqm",
-        /* 48 */ "!text/single_raqm_laqm",
-        /* 49 */ "!text/double_raqm_laqm",
+        /* 51 */ "\u0410\u0411\u0412",
+        /* 52 */ "!text/single_9qm_lqm",
+        /* 53 */ "!text/double_9qm_lqm",
+        /* 54 */ "!text/single_raqm_laqm",
+        /* 55 */ "!text/double_raqm_laqm",
     };
 
     /* Language sv: Swedish */
@@ -2972,10 +3038,10 @@
         /* 24 */ "\u00E6",
         /* 25~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null,
-        /* ~47 */
-        /* 48 */ "!text/single_raqm_laqm",
-        /* 49 */ "!text/double_raqm_laqm",
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~53 */
+        /* 54 */ "!text/single_raqm_laqm",
+        /* 55 */ "!text/double_raqm_laqm",
     };
 
     /* Language sw: Swahili */
@@ -3035,17 +3101,18 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~44 */
+        null, null, null, null, null, null,
+        /* ~50 */
         // Label for "switch to alphabetic" key.
         // U+0E01: "ก" THAI CHARACTER KO KAI
         // U+0E02: "ข" THAI CHARACTER KHO KHAI
         // U+0E04: "ค" THAI CHARACTER KHO KHWAI
-        /* 45 */ "\u0E01\u0E02\u0E04",
-        /* 46~ */
+        /* 51 */ "\u0E01\u0E02\u0E04",
+        /* 52~ */
         null, null, null, null, null,
-        /* ~50 */
+        /* ~56 */
         // U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT
-        /* 51 */ "\u0E3F",
+        /* 57 */ "\u0E3F",
     };
 
     /* Language tl: Tagalog */
@@ -3175,20 +3242,20 @@
         // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
         /* 37 */ "\u044A",
         /* 38~ */
-        null, null, null, null, null, null, null,
-        /* ~44 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~50 */
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 45 */ "\u0410\u0411\u0412",
-        /* 46 */ "!text/single_9qm_lqm",
-        /* 47 */ "!text/double_9qm_lqm",
-        /* 48~ */
+        /* 51 */ "\u0410\u0411\u0412",
+        /* 52 */ "!text/single_9qm_lqm",
+        /* 53 */ "!text/double_9qm_lqm",
+        /* 54~ */
         null, null, null,
-        /* ~50 */
+        /* ~56 */
         // U+20B4: "₴" HRYVNIA SIGN
-        /* 51 */ "\u20B4",
+        /* 57 */ "\u20B4",
     };
 
     /* Language vi: Vietnamese */
@@ -3273,10 +3340,11 @@
         /* 10~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null,
-        /* ~50 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null,
+        /* ~56 */
         // U+20AB: "₫" DONG SIGN
-        /* 51 */ "\u20AB",
+        /* 57 */ "\u20AB",
     };
 
     /* Language zu: Zulu */
diff --git a/java/src/com/android/inputmethod/keyboard/internal/ScrollViewWithNotifier.java b/java/src/com/android/inputmethod/keyboard/internal/ScrollViewWithNotifier.java
deleted file mode 100644
index d1ccdc7..0000000
--- a/java/src/com/android/inputmethod/keyboard/internal/ScrollViewWithNotifier.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.keyboard.internal;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.ScrollView;
-
-/**
- * This is an extended {@link ScrollView} that can notify
- * {@link ScrollView#onScrollChanged(int,int,int,int} and
- * {@link ScrollView#onOverScrolled(int,int,int,int)} to a content view.
- */
-public class ScrollViewWithNotifier extends ScrollView {
-    private ScrollListener mScrollListener = EMPTY_LISTER;
-
-    public interface ScrollListener {
-        public void notifyScrollChanged(int scrollX, int scrollY, int oldX, int oldY);
-        public void notifyOverScrolled(int scrollX, int scrollY, boolean clampedX,
-                boolean clampedY);
-    }
-
-    private static final ScrollListener EMPTY_LISTER = new ScrollListener() {
-        @Override
-        public void notifyScrollChanged(int scrollX, int scrollY, int oldX, int oldY) {}
-        @Override
-        public void notifyOverScrolled(int scrollX, int scrollY, boolean clampedX,
-                boolean clampedY) {}
-    };
-
-    public ScrollViewWithNotifier(final Context context, final AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected void onScrollChanged(final int scrollX, final int scrollY, final int oldX,
-            final int oldY) {
-        super.onScrollChanged(scrollX, scrollY, oldX, oldY);
-        mScrollListener.notifyScrollChanged(scrollX, scrollY, oldX, oldY);
-    }
-
-    @Override
-    protected void onOverScrolled(final int scrollX, final int scrollY, final boolean clampedX,
-            final boolean clampedY) {
-        super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
-        mScrollListener.notifyOverScrolled(scrollX, scrollY, clampedX, clampedY);
-    }
-
-    public void setScrollListener(final ScrollListener listener) {
-        mScrollListener = listener;
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
index 463d093..d034515 100644
--- a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
@@ -55,8 +55,7 @@
 
     // TODO: Remove lastModifiedTime after making binary dictionary support forgetting curve.
     abstract public void addBigramWords(final String word0, final String word1,
-            final int frequency, final boolean isValid,
-            final long lastModifiedTime);
+            final int frequency, final boolean isValid, final long lastModifiedTime);
 
     abstract public void removeBigramWords(final String word0, final String word1);
 
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 722a829..ad94a04 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -432,8 +432,9 @@
 
         // Actually copy the file
         final byte[] buffer = new byte[FILE_READ_BUFFER_SIZE];
-        for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer))
+        for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer)) {
             output.write(buffer, 0, readBytes);
+        }
         input.close();
     }
 
@@ -478,8 +479,7 @@
      * @param context the context for resources and providers.
      * @param clientId the client ID to use.
      */
-    public static void initializeClientRecordHelper(final Context context,
-            final String clientId) {
+    public static void initializeClientRecordHelper(final Context context, final String clientId) {
         try {
             final ContentProviderClient client = context.getContentResolver().
                     acquireContentProviderClient(getProviderUriBuilder("").build());
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index c4f9601..9a96530 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -174,6 +174,7 @@
     public static final int CODE_SLASH = '/';
     public static final int CODE_COMMERCIAL_AT = '@';
     public static final int CODE_PLUS = '+';
+    public static final int CODE_PERCENT = '%';
     public static final int CODE_CLOSING_PARENTHESIS = ')';
     public static final int CODE_CLOSING_SQUARE_BRACKET = ']';
     public static final int CODE_CLOSING_CURLY_BRACKET = '}';
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index eb8650e..710c3ea 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -55,7 +55,7 @@
 
     // TODO: Remove.
     /** Whether to call binary dictionary dynamically updating methods. */
-    public static boolean ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE = true;
+    public static final boolean ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE = true;
 
     private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100;
 
@@ -733,4 +733,19 @@
     public boolean isTerminatedForTests() {
         return getExecutor(mFilename).isTerminated();
     }
+
+    @UsedForTesting
+    protected void runAfterGcForDebug(final Runnable r) {
+        getExecutor(mFilename).executePrioritized(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    mBinaryDictionary.flushWithGC();
+                    r.run();
+                } finally {
+                    mFilenameDictionaryUpdateController.mIsRegenerating.set(false);
+                }
+            }
+        });
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 95c9bca..8fdff8f 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -33,6 +33,7 @@
  * be searched for suggestions and valid words.
  */
 // TODO: Remove after binary dictionary supports dynamic update.
+@UsedForTesting
 public class ExpandableDictionary extends Dictionary {
     private static final String TAG = ExpandableDictionary.class.getSimpleName();
     /**
@@ -146,6 +147,7 @@
 
     private int[][] mCodes;
 
+    @UsedForTesting
     public ExpandableDictionary(final String dictType) {
         super(dictType);
         clearDictionary();
@@ -164,6 +166,7 @@
      * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored
      *   if shortcutTarget is null.
      */
+    @UsedForTesting
     public void addWord(final String word, final String shortcutTarget, final int frequency,
             final int shortcutFreq) {
         if (word.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH) {
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index 8caf6f1..fcf0430 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -52,8 +52,7 @@
             } else if (inputClass == 0) {
                 // TODO: is this check still necessary?
                 Log.w(TAG, String.format("Unexpected input class: inputType=0x%08x"
-                        + " imeOptions=0x%08x",
-                        inputType, editorInfo.imeOptions));
+                        + " imeOptions=0x%08x", inputType, editorInfo.imeOptions));
             }
             mIsSettingsSuggestionStripOn = false;
             mInputTypeNoAutoCorrect = false;
@@ -204,8 +203,7 @@
     public static boolean inPrivateImeOptions(String packageName, String key,
             EditorInfo editorInfo) {
         if (editorInfo == null) return false;
-        final String findingKey = (packageName != null) ? packageName + "." + key
-                : key;
+        final String findingKey = (packageName != null) ? packageName + "." + key : key;
         return StringUtils.containsInCommaSplittableText(findingKey, editorInfo.privateImeOptions);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 608bb3c..9936b3e 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -196,9 +196,6 @@
     private int mLastSelectionStart = NOT_A_CURSOR_POSITION;
     private int mLastSelectionEnd = NOT_A_CURSOR_POSITION;
 
-    // Whether we are expecting an onUpdateSelection event to fire. If it does when we don't
-    // "expect" it, it means the user actually moved the cursor.
-    private boolean mExpectingUpdateSelection;
     private int mDeleteCount;
     private long mLastKeyTime;
     private final TreeSet<Long> mCurrentlyPressedHardwareKeys = CollectionUtils.newTreeSet();
@@ -761,8 +758,9 @@
                 .findViewById(android.R.id.extractArea);
         mKeyPreviewBackingView = view.findViewById(R.id.key_preview_backing);
         mSuggestionStripView = (SuggestionStripView)view.findViewById(R.id.suggestion_strip_view);
-        if (mSuggestionStripView != null)
+        if (mSuggestionStripView != null) {
             mSuggestionStripView.setListener(this, view);
+        }
         if (LatinImeLogger.sVISUALDEBUG) {
             mKeyPreviewBackingView.setBackgroundColor(0x10FF0000);
         }
@@ -1081,16 +1079,9 @@
                     + ", ce=" + composingSpanEnd);
         }
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            final boolean expectingUpdateSelectionFromLogger =
-                    ResearchLogger.getAndClearLatinIMEExpectingUpdateSelection();
             ResearchLogger.latinIME_onUpdateSelection(mLastSelectionStart, mLastSelectionEnd,
                     oldSelStart, oldSelEnd, newSelStart, newSelEnd, composingSpanStart,
-                    composingSpanEnd, mExpectingUpdateSelection,
-                    expectingUpdateSelectionFromLogger, mConnection);
-            if (expectingUpdateSelectionFromLogger) {
-                // TODO: Investigate. Quitting now sounds wrong - we won't do the resetting work
-                return;
-            }
+                    composingSpanEnd, mConnection);
         }
 
         final boolean selectionChanged = mLastSelectionStart != newSelStart
@@ -1109,14 +1100,7 @@
         // TODO: revisit this when LatinIME supports hardware keyboards.
         // NOTE: the test harness subclasses LatinIME and overrides isInputViewShown().
         // TODO: find a better way to simulate actual execution.
-        if (isInputViewShown() && !mExpectingUpdateSelection
-                && !mConnection.isBelatedExpectedUpdate(oldSelStart, newSelStart)) {
-            // TAKE CARE: there is a race condition when we enter this test even when the user
-            // did not explicitly move the cursor. This happens when typing fast, where two keys
-            // turn this flag on in succession and both onUpdateSelection() calls arrive after
-            // the second one - the first call successfully avoids this test, but the second one
-            // enters. For the moment we rely on noComposingSpan to further reduce the impact.
-
+        if (isInputViewShown() && !mConnection.isBelatedExpectedUpdate(oldSelStart, newSelStart)) {
             // TODO: the following is probably better done in resetEntireInputState().
             // it should only happen when the cursor moved, and the very purpose of the
             // test below is to narrow down whether this happened or not. Likewise with
@@ -1161,7 +1145,6 @@
             mRecapitalizeStatus.deactivate();
             mKeyboardSwitcher.updateShiftState();
         }
-        mExpectingUpdateSelection = false;
 
         // Make a note of the cursor position
         mLastSelectionStart = newSelStart;
@@ -1347,8 +1330,7 @@
     @Override
     public boolean onEvaluateFullscreenMode() {
         // Reread resource value here, because this method is called by framework anytime as needed.
-        final boolean isFullscreenModeAllowed =
-                Settings.readUseFullscreenMode(getResources());
+        final boolean isFullscreenModeAllowed = Settings.readUseFullscreenMode(getResources());
         if (super.onEvaluateFullscreenMode() && isFullscreenModeAllowed) {
             // TODO: Remove this hack. Actually we should not really assume NO_EXTRACT_UI
             // implies NO_FULLSCREEN. However, the framework mistakenly does.  i.e. NO_EXTRACT_UI
@@ -1388,8 +1370,9 @@
 
     private void resetComposingState(final boolean alsoResetLastComposedWord) {
         mWordComposer.reset();
-        if (alsoResetLastComposedWord)
+        if (alsoResetLastComposedWord) {
             mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
+        }
     }
 
     private void commitTyped(final String separatorString) {
@@ -1436,15 +1419,16 @@
         if (0 != (auto & TextUtils.CAP_MODE_CHARACTERS)) {
             return WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED;
         }
-        if (0 != auto) return WordComposer.CAPS_MODE_AUTO_SHIFTED;
+        if (0 != auto) {
+            return WordComposer.CAPS_MODE_AUTO_SHIFTED;
+        }
         return WordComposer.CAPS_MODE_OFF;
     }
 
     private void swapSwapperAndSpace() {
         final CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0);
         // It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
-        if (lastTwo != null && lastTwo.length() == 2
-                && lastTwo.charAt(0) == Constants.CODE_SPACE) {
+        if (lastTwo != null && lastTwo.length() == 2 && lastTwo.charAt(0) == Constants.CODE_SPACE) {
             mConnection.deleteSurroundingText(2, 0);
             final String text = lastTwo.charAt(1) + " ";
             mConnection.commitText(text, 1);
@@ -1502,6 +1486,7 @@
                 || codePoint == Constants.CODE_CLOSING_CURLY_BRACKET
                 || codePoint == Constants.CODE_CLOSING_ANGLE_BRACKET
                 || codePoint == Constants.CODE_PLUS
+                || codePoint == Constants.CODE_PERCENT
                 || Character.getType(codePoint) == Character.OTHER_SYMBOL;
     }
 
@@ -1739,7 +1724,6 @@
             }
             handleCharacter(primaryCode, keyX, keyY, spaceState);
         }
-        mExpectingUpdateSelection = true;
         return didAutoCorrect;
     }
 
@@ -1805,7 +1789,6 @@
             } else {
                 commitTyped(LastComposedWord.NOT_A_SEPARATOR);
             }
-            mExpectingUpdateSelection = true;
         }
         final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
         if (Character.isLetterOrDigit(codePointBeforeCursor)
@@ -1996,8 +1979,7 @@
 
     // This method must run in UI Thread.
     public void onEndBatchInputAsyncInternal(final SuggestedWords suggestedWords) {
-        final String batchInputText = suggestedWords.isEmpty()
-                ? null : suggestedWords.getWord(0);
+        final String batchInputText = suggestedWords.isEmpty() ? null : suggestedWords.getWord(0);
         if (TextUtils.isEmpty(batchInputText)) {
             return;
         }
@@ -2019,7 +2001,6 @@
             mWordComposer.setBatchInputWord(batchInputText);
             mConnection.setComposingText(batchInputText, 1);
         }
-        mExpectingUpdateSelection = true;
         mConnection.endBatchEdit();
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.latinIME_onEndBatchInput(batchInputText, 0, suggestedWords);
@@ -2073,9 +2054,7 @@
     }
 
     private void handleBackspace(final int spaceState) {
-        // We revert these in this method if the deletion doesn't happen.
         mDeleteCount++;
-        mExpectingUpdateSelection = true;
 
         // In many cases, we may have to put the keyboard in auto-shift state again. However
         // we want to wait a few milliseconds before doing it to avoid the keyboard flashing
@@ -2168,10 +2147,7 @@
                 }
                 final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
                 if (codePointBeforeCursor == Constants.NOT_A_CODE) {
-                    // Nothing to delete before the cursor. We have to revert the deletion states
-                    // that were updated at the beginning of this method.
-                    mDeleteCount--;
-                    mExpectingUpdateSelection = false;
+                    // Nothing to delete before the cursor.
                     return;
                 }
                 final int lengthToDelete =
@@ -2219,8 +2195,8 @@
     /*
      * Strip a trailing space if necessary and returns whether it's a swap weak space situation.
      */
-    private boolean maybeStripSpace(final int code,
-            final int spaceState, final boolean isFromSuggestionStrip) {
+    private boolean maybeStripSpace(final int code, final int spaceState,
+            final boolean isFromSuggestionStrip) {
         if (Constants.CODE_ENTER == code && SPACE_STATE_SWAP_PUNCTUATION == spaceState) {
             mConnection.removeTrailingSpace();
             return false;
@@ -2235,8 +2211,8 @@
         return false;
     }
 
-    private void handleCharacter(final int primaryCode, final int x,
-            final int y, final int spaceState) {
+    private void handleCharacter(final int primaryCode, final int x, final int y,
+            final int spaceState) {
         // TODO: refactor this method to stop flipping isComposingWord around all the time, and
         // make it shorter (possibly cut into several pieces). Also factor handleNonSpecialCharacter
         // which has the same name as other handle* methods but is not the same.
@@ -2303,8 +2279,8 @@
             }
             mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
         } else {
-            final boolean swapWeakSpace = maybeStripSpace(primaryCode,
-                    spaceState, Constants.SUGGESTION_STRIP_COORDINATE == x);
+            final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState,
+                    Constants.SUGGESTION_STRIP_COORDINATE == x);
 
             sendKeyCodePoint(primaryCode);
 
@@ -2342,9 +2318,9 @@
             if (!mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) {
                 mLastSelectionStart = mRecapitalizeStatus.getNewCursorStart();
                 mLastSelectionEnd = mRecapitalizeStatus.getNewCursorEnd();
-                mConnection.setSelection(mLastSelectionStart, mLastSelectionEnd);
             }
         }
+        mConnection.finishComposingText();
         mRecapitalizeStatus.rotate();
         final int numCharsDeleted = mLastSelectionEnd - mLastSelectionStart;
         mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
@@ -2541,6 +2517,18 @@
         }
     }
 
+    private String getPreviousWordForSuggestion(final SettingsValues currentSettings) {
+        if (currentSettings.mCurrentLanguageHasSpaces) {
+            // If we are typing in a language with spaces we can just look up the previous
+            // word from textview.
+            return mConnection.getNthPreviousWord(currentSettings.mWordSeparators,
+                    mWordComposer.isComposingWord() ? 2 : 1);
+        } else {
+            return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? null
+                    : mLastComposedWord.mCommittedWord;
+        }
+    }
+
     private void getSuggestedWords(final int sessionId, final int sequenceNumber,
             final OnGetSuggestedWordsCallback callback) {
         final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
@@ -2554,16 +2542,7 @@
         // should just skip whitespace if any, so 1.
         final SettingsValues currentSettings = mSettings.getCurrent();
         final int[] additionalFeaturesOptions = currentSettings.mAdditionalFeaturesSettingValues;
-        final String prevWord;
-        if (currentSettings.mCurrentLanguageHasSpaces) {
-            // If we are typing in a language with spaces we can just look up the previous
-            // word from textview.
-            prevWord = mConnection.getNthPreviousWord(currentSettings.mWordSeparators,
-                    mWordComposer.isComposingWord() ? 2 : 1);
-        } else {
-            prevWord = LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? null
-                    : mLastComposedWord.mCommittedWord;
-        }
+        final String prevWord = getPreviousWordForSuggestion(currentSettings);
         suggest.getSuggestedWords(mWordComposer, prevWord, keyboard.getProximityInfo(),
                 currentSettings.mBlockPotentiallyOffensive, currentSettings.mCorrectionEnabled,
                 additionalFeaturesOptions, sessionId, sequenceNumber, callback);
@@ -2681,7 +2660,6 @@
                 ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection,
                         separator, mWordComposer.isBatchMode(), suggestedWords);
             }
-            mExpectingUpdateSelection = true;
             commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
                     separator);
             if (!typedWord.equals(autoCorrection)) {
@@ -2752,7 +2730,6 @@
         // typed word.
         final String replacedWord = mWordComposer.getTypedWord();
         LatinImeLogger.logOnManualSuggestion(replacedWord, suggestion, index, suggestedWords);
-        mExpectingUpdateSelection = true;
         commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
                 LastComposedWord.NOT_A_SEPARATOR);
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -3026,8 +3003,7 @@
                 throw new RuntimeException("revertCommit, but we are composing a word");
             }
             final CharSequence wordBeforeCursor =
-                    mConnection.getTextBeforeCursor(deleteLength, 0)
-                            .subSequence(0, cancelLength);
+                    mConnection.getTextBeforeCursor(deleteLength, 0).subSequence(0, cancelLength);
             if (!TextUtils.equals(committedWord, wordBeforeCursor)) {
                 throw new RuntimeException("revertCommit check failed: we thought we were "
                         + "reverting \"" + committedWord
@@ -3228,8 +3204,8 @@
                     final Intent intent = IntentUtils.getInputLanguageSelectionIntent(
                             mRichImm.getInputMethodIdOfThisIme(),
                             Intent.FLAG_ACTIVITY_NEW_TASK
-                            | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
-                            | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                                    | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+                                    | Intent.FLAG_ACTIVITY_CLEAR_TOP);
                     startActivity(intent);
                     break;
                 case 1:
@@ -3238,9 +3214,8 @@
                 }
             }
         };
-        final AlertDialog.Builder builder = new AlertDialog.Builder(this)
-                .setItems(items, listener)
-                .setTitle(title);
+        final AlertDialog.Builder builder =
+                new AlertDialog.Builder(this).setItems(items, listener).setTitle(title);
         showOptionDialog(builder.create());
     }
 
@@ -3301,11 +3276,13 @@
 
         final Printer p = new PrintWriterPrinter(fout);
         p.println("LatinIME state :");
+        p.println("  VersionCode = " + ApplicationUtils.getVersionCode(this));
+        p.println("  VersionName = " + ApplicationUtils.getVersionName(this));
         final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
         final int keyboardMode = keyboard != null ? keyboard.mId.mMode : -1;
         p.println("  Keyboard mode = " + keyboardMode);
         final SettingsValues settingsValues = mSettings.getCurrent();
-        p.println("  mIsSuggestionsSuggestionsRequested = "
+        p.println("  mIsSuggestionsRequested = "
                 + settingsValues.isSuggestionsRequested(mDisplayOrientation));
         p.println("  mCorrectionEnabled=" + settingsValues.mCorrectionEnabled);
         p.println("  isComposingWord=" + mWordComposer.isComposingWord());
diff --git a/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java
index 9f7f502..fda97da 100644
--- a/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java
@@ -60,7 +60,8 @@
                         0 != (optionsFlags & FormatSpec.GERMAN_UMLAUT_PROCESSING_FLAG),
                         0 != (optionsFlags & FormatSpec.FRENCH_LIGATURE_PROCESSING_FLAG)),
                         new FormatOptions(version,
-                                0 != (optionsFlags & FormatSpec.SUPPORTS_DYNAMIC_UPDATE)));
+                                0 != (optionsFlags & FormatSpec.SUPPORTS_DYNAMIC_UPDATE),
+                                0 != (optionsFlags & FormatSpec.CONTAINS_TIMESTAMP_FLAG)));
         return header;
     }
 
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
index 216492b..8a8ceaa 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderUtils.java
@@ -169,6 +169,14 @@
             return size;
         }
 
+        static int getCharArraySize(final int[] chars, final int start, final int end) {
+            int size = 0;
+            for (int i = start; i < end; ++i) {
+                size += getCharSize(chars[i]);
+            }
+            return size;
+        }
+
         /**
          * Writes a char array to a byte buffer.
          *
@@ -200,8 +208,7 @@
          * @param word the string to write.
          * @return the size written, in bytes.
          */
-        static int writeString(final byte[] buffer, final int origin,
-                final String word) {
+        static int writeString(final byte[] buffer, final int origin, final String word) {
             final int length = word.length();
             int index = origin;
             for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
@@ -223,22 +230,62 @@
          *
          * This will also write the terminator byte.
          *
-         * @param buffer the OutputStream to write to.
+         * @param stream the OutputStream to write to.
          * @param word the string to write.
+         * @return the size written, in bytes.
          */
-        static void writeString(final OutputStream buffer, final String word) throws IOException {
+        static int writeString(final OutputStream stream, final String word) throws IOException {
             final int length = word.length();
+            int written = 0;
             for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
                 final int codePoint = word.codePointAt(i);
-                if (1 == getCharSize(codePoint)) {
-                    buffer.write((byte) codePoint);
+                final int charSize = getCharSize(codePoint);
+                if (1 == charSize) {
+                    stream.write((byte) codePoint);
                 } else {
-                    buffer.write((byte) (0xFF & (codePoint >> 16)));
-                    buffer.write((byte) (0xFF & (codePoint >> 8)));
-                    buffer.write((byte) (0xFF & codePoint));
+                    stream.write((byte) (0xFF & (codePoint >> 16)));
+                    stream.write((byte) (0xFF & (codePoint >> 8)));
+                    stream.write((byte) (0xFF & codePoint));
                 }
+                written += charSize;
             }
-            buffer.write(FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
+            stream.write(FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
+            written += FormatSpec.PTNODE_TERMINATOR_SIZE;
+            return written;
+        }
+
+        /**
+         * Writes an array of code points with our character format to an OutputStream.
+         *
+         * This will also write the terminator byte.
+         *
+         * @param stream the OutputStream to write to.
+         * @param codePoints the array of code points
+         * @return the size written, in bytes.
+         */
+        // TODO: Merge this method with writeCharArray and rename the various write* methods to
+        // make the difference clear.
+        static int writeCodePoints(final OutputStream stream, final int[] codePoints,
+                final int startIndex, final int endIndex)
+                throws IOException {
+            int written = 0;
+            for (int i = startIndex; i < endIndex; ++i) {
+                final int codePoint = codePoints[i];
+                final int charSize = getCharSize(codePoint);
+                if (1 == charSize) {
+                    stream.write((byte) codePoint);
+                } else {
+                    stream.write((byte) (0xFF & (codePoint >> 16)));
+                    stream.write((byte) (0xFF & (codePoint >> 8)));
+                    stream.write((byte) (0xFF & codePoint));
+                }
+                written += charSize;
+            }
+            if (endIndex - startIndex > 1) {
+                stream.write(FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
+                written += FormatSpec.PTNODE_TERMINATOR_SIZE;
+            }
+            return written;
         }
 
         /**
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
index f761829..c0dad3d 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
@@ -17,6 +17,7 @@
 package com.android.inputmethod.latin.makedict;
 
 import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
 import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
@@ -245,6 +246,26 @@
         }
     }
 
+    static void writeUIntToDictBuffer(final DictBuffer dictBuffer, final int value,
+            final int size) {
+        switch(size) {
+            case 4:
+                dictBuffer.put((byte) ((value >> 24) & 0xFF));
+                /* fall through */
+            case 3:
+                dictBuffer.put((byte) ((value >> 16) & 0xFF));
+                /* fall through */
+            case 2:
+                dictBuffer.put((byte) ((value >> 8) & 0xFF));
+                /* fall through */
+            case 1:
+                dictBuffer.put((byte) (value & 0xFF));
+                break;
+            default:
+                /* nop */
+        }
+    }
+
     // End utility methods
 
     // This method is responsible for finding a nice ordering of the nodes that favors run-time
@@ -690,6 +711,13 @@
                     + word + " is " + unigramFrequency);
             bigramFrequency = unigramFrequency;
         }
+        bigramFlags += getBigramFrequencyDiff(unigramFrequency, bigramFrequency)
+                & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY;
+        return bigramFlags;
+    }
+
+    public static int getBigramFrequencyDiff(final int unigramFrequency,
+            final int bigramFrequency) {
         // We compute the difference between 255 (which means probability = 1) and the
         // unigram score. We split this into a number of discrete steps.
         // Now, the steps are numbered 0~15; 0 represents an increase of 1 step while 15
@@ -723,9 +751,7 @@
         // include this bigram in the dictionary. For now, register as 0, and live with the
         // small over-estimation that we get in this case. TODO: actually remove this bigram
         // if discretizedFrequency < 0.
-        final int finalBigramFrequency = discretizedFrequency > 0 ? discretizedFrequency : 0;
-        bigramFlags += finalBigramFrequency & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY;
-        return bigramFlags;
+        return discretizedFrequency > 0 ? discretizedFrequency : 0;
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index 0f7d2f6..8d14e4d 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -245,8 +245,7 @@
     /**
      * @return the size written, in bytes. Always 3 bytes.
      */
-    static int writeSInt24ToBuffer(final DictBuffer dictBuffer,
-            final int value) {
+    static int writeSInt24ToBuffer(final DictBuffer dictBuffer, final int value) {
         final int absValue = Math.abs(value);
         dictBuffer.put((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF));
         dictBuffer.put((byte)((absValue >> 8) & 0xFF));
@@ -301,35 +300,6 @@
     }
 
     /**
-     * Write a string to a stream.
-     *
-     * @param destination the stream to write.
-     * @param word the string to be written.
-     * @return the size written, in bytes.
-     * @throws IOException
-     */
-    private static int writeString(final OutputStream destination, final String word)
-            throws IOException {
-        int size = 0;
-        final int length = word.length();
-        for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
-            final int codePoint = word.codePointAt(i);
-            if (CharEncoding.getCharSize(codePoint) == 1) {
-                destination.write((byte)codePoint);
-                size++;
-            } else {
-                destination.write((byte)(0xFF & (codePoint >> 16)));
-                destination.write((byte)(0xFF & (codePoint >> 8)));
-                destination.write((byte)(0xFF & codePoint));
-                size += 3;
-            }
-        }
-        destination.write((byte)FormatSpec.PTNODE_CHARACTERS_TERMINATOR);
-        size += FormatSpec.PTNODE_TERMINATOR_SIZE;
-        return size;
-    }
-
-    /**
      * Write a PtNode to an output stream from a PtNodeInfo.
      * A PtNode is an in-memory representation of a node in the patricia trie.
      * A PtNode info is a container for low-level information about how the
@@ -387,7 +357,7 @@
                 destination.write((byte)BinaryDictEncoderUtils.makeShortcutFlags(
                         shortcutIterator.hasNext(), target.mFrequency));
                 size++;
-                size += writeString(destination, target.mWord);
+                size += CharEncoding.writeString(destination, target.mWord);
             }
         }
 
@@ -445,6 +415,25 @@
     }
 
     /**
+     * Writes a PtNodeCount to the stream.
+     *
+     * @param destination the stream to write.
+     * @param ptNodeCount the count.
+     * @return the size written in bytes.
+     */
+    static int writePtNodeCount(final OutputStream destination, final int ptNodeCount)
+            throws IOException {
+        final int countSize = BinaryDictIOUtils.getPtNodeCountSize(ptNodeCount);
+        // the count must fit on one byte or two bytes.
+        // Please see comments in FormatSpec.
+        if (countSize != 1 && countSize != 2) {
+            throw new RuntimeException("Strange size from getPtNodeCountSize : " + countSize);
+        }
+        BinaryDictEncoderUtils.writeUIntToStream(destination, ptNodeCount, countSize);
+        return countSize;
+    }
+
+    /**
      * Write a node array to the stream.
      *
      * @param destination the stream to write.
@@ -454,18 +443,7 @@
      */
     static int writeNodes(final OutputStream destination, final PtNodeInfo[] infos)
             throws IOException {
-        int size = getPtNodeCountSize(infos.length);
-        switch (getPtNodeCountSize(infos.length)) {
-            case 1:
-                destination.write((byte)infos.length);
-                break;
-            case 2:
-                destination.write((byte)(infos.length >> 8));
-                destination.write((byte)(infos.length & 0xFF));
-                break;
-            default:
-                throw new RuntimeException("Invalid node count size.");
-        }
+        int size = writePtNodeCount(destination, infos.length);
         for (final PtNodeInfo info : infos) size += writePtNode(destination, info);
         writeSInt24ToStream(destination, FormatSpec.NO_FORWARD_LINK_ADDRESS);
         return size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
diff --git a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
index 3dbeee0..9154398 100644
--- a/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/DictDecoder.java
@@ -35,6 +35,7 @@
 /**
  * An interface of binary dictionary decoders.
  */
+// TODO: Straighten out responsibility for the buffer's file pointer.
 public interface DictDecoder {
 
     /**
@@ -43,7 +44,7 @@
     public FileHeader readHeader() throws IOException, UnsupportedFormatException;
 
     /**
-     * Reads PtNode from nodeAddress.
+     * Reads PtNode from ptNodePos.
      * @param ptNodePos the position of PtNode.
      * @param formatOptions the format options.
      * @return PtNodeInfo.
diff --git a/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java
index 3362771..971b4ff 100644
--- a/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java
@@ -22,6 +22,7 @@
 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.io.IOException;
 import java.io.OutputStream;
@@ -36,7 +37,7 @@
 @UsedForTesting
 public final class DynamicBinaryDictIOUtils {
     private static final boolean DBG = false;
-    private static final int MAX_JUMPS = 10000;
+    static final int MAX_JUMPS = 10000;
 
     private DynamicBinaryDictIOUtils() {
         // This utility class is not publicly instantiable.
@@ -217,6 +218,25 @@
     }
 
     /**
+     * Converts a list of WeightedString to a list of PendingAttribute.
+     */
+    public static ArrayList<PendingAttribute> resolveBigramPositions(final DictUpdater dictUpdater,
+            final ArrayList<WeightedString> bigramStrings)
+                    throws IOException, UnsupportedFormatException {
+        if (bigramStrings == null) return CollectionUtils.newArrayList();
+        final ArrayList<PendingAttribute> bigrams = CollectionUtils.newArrayList();
+        for (final WeightedString bigram : bigramStrings) {
+            final int pos = dictUpdater.getTerminalPosition(bigram.mWord);
+            if (pos == FormatSpec.NOT_VALID_WORD) {
+                // TODO: figure out what is the correct thing to do here.
+            } else {
+                bigrams.add(new PendingAttribute(bigram.mFrequency, pos));
+            }
+        }
+        return bigrams;
+    }
+
+    /**
      * Insert a word into a binary dictionary.
      *
      * @param dictUpdater the dict updater.
@@ -238,18 +258,9 @@
             final ArrayList<WeightedString> shortcuts, final boolean isNotAWord,
             final boolean isBlackListEntry)
                     throws IOException, UnsupportedFormatException {
-        final ArrayList<PendingAttribute> bigrams = new ArrayList<PendingAttribute>();
+        final ArrayList<PendingAttribute> bigrams = resolveBigramPositions(dictUpdater,
+                bigramStrings);
         final DictBuffer dictBuffer = dictUpdater.getDictBuffer();
-        if (bigramStrings != null) {
-            for (final WeightedString bigram : bigramStrings) {
-                int position = dictUpdater.getTerminalPosition(bigram.mWord);
-                if (position == FormatSpec.NOT_VALID_WORD) {
-                    // TODO: figure out what is the correct thing to do here.
-                } else {
-                    bigrams.add(new PendingAttribute(bigram.mFrequency, position));
-                }
-            }
-        }
 
         final boolean isTerminal = true;
         final boolean hasBigrams = !bigrams.isEmpty();
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index 5a5d7af..5aee135 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -37,13 +37,15 @@
      * sion
      *
      * o |
-     * p | not used                                4 bits
-     * t | has bigrams ?                           1 bit, 1 = yes, 0 = no : CONTAINS_BIGRAMS_FLAG
-     * i | FRENCH_LIGATURE_PROCESSING_FLAG
-     * o | supports dynamic updates ?              1 bit, 1 = yes, 0 = no : SUPPORTS_DYNAMIC_UPDATE
-     * n | GERMAN_UMLAUT_PROCESSING_FLAG
-     * f |
-     * lags
+     * p | not used                                3 bits
+     * t | each unigram and bigram entry has a time stamp?
+     * i |                                         1 bit, 1 = yes, 0 = no : CONTAINS_TIMESTAMP_FLAG
+     * o | has bigrams ?                           1 bit, 1 = yes, 0 = no : CONTAINS_BIGRAMS_FLAG
+     * n | FRENCH_LIGATURE_PROCESSING_FLAG
+     * f | supports dynamic updates ?              1 bit, 1 = yes, 0 = no : SUPPORTS_DYNAMIC_UPDATE
+     * l | GERMAN_UMLAUT_PROCESSING_FLAG
+     * a |
+     * gs
      *
      * h |
      * e | size of the file header, 4bytes
@@ -211,6 +213,7 @@
     static final int SUPPORTS_DYNAMIC_UPDATE = 0x2;
     static final int FRENCH_LIGATURE_PROCESSING_FLAG = 0x4;
     static final int CONTAINS_BIGRAMS_FLAG = 0x8;
+    static final int CONTAINS_TIMESTAMP_FLAG = 0x10;
 
     // TODO: Make this value adaptative to content data, store it in the header, and
     // use it in the reading code.
@@ -261,8 +264,9 @@
     static final int PTNODE_SHORTCUT_LIST_SIZE_SIZE = 2;
 
     // These values are used only by version 4 or later.
-    static final String TRIE_FILE_EXTENSION = ".trie";
+    public static final String TRIE_FILE_EXTENSION = ".trie";
     static final String FREQ_FILE_EXTENSION = ".freq";
+    static final String UNIGRAM_TIMESTAMP_FILE_EXTENSION = ".timestamp";
     // tat = Terminal Address Table
     static final String TERMINAL_ADDRESS_TABLE_FILE_EXTENSION = ".tat";
     static final String BIGRAM_FILE_EXTENSION = ".bigram";
@@ -271,19 +275,25 @@
     static final String CONTENT_TABLE_FILE_SUFFIX = "_index";
     static final int FREQUENCY_AND_FLAGS_SIZE = 2;
     static final int TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE = 3;
+    static final int UNIGRAM_TIMESTAMP_SIZE = 4;
 
     // With the English main dictionary as of October 2013, the size of bigram address table is
-    // is 584KB with the block size being 4.
-    // This is 91% of that of full address table.
-    static final int BIGRAM_ADDRESS_TABLE_BLOCK_SIZE = 4;
-    static final int BIGRAM_CONTENT_COUNT = 1;
+    // is 345KB with the block size being 16.
+    // This is 54% of that of full address table.
+    static final int BIGRAM_ADDRESS_TABLE_BLOCK_SIZE = 16;
+    static final int BIGRAM_CONTENT_COUNT = 2;
     static final int BIGRAM_FREQ_CONTENT_INDEX = 0;
+    static final int BIGRAM_TIMESTAMP_CONTENT_INDEX = 1;
     static final String BIGRAM_FREQ_CONTENT_ID = "_freq";
+    static final String BIGRAM_TIMESTAMP_CONTENT_ID = "_timestamp";
+    static final int BIGRAM_TIMESTAMP_SIZE = 4;
+    static final int BIGRAM_COUNTER_SIZE = 1;
+    static final int BIGRAM_LEVEL_SIZE = 1;
 
     static final int SHORTCUT_CONTENT_COUNT = 1;
     static final int SHORTCUT_CONTENT_INDEX = 0;
     // With the English main dictionary as of October 2013, the size of shortcut address table is
-    // 29KB with the block size being 64.
+    // 26KB with the block size being 64.
     // This is only 4.4% of that of full address table.
     static final int SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE = 64;
     static final String SHORTCUT_CONTENT_ID = "_shortcut";
@@ -321,6 +331,7 @@
         public final int mVersion;
         public final boolean mSupportsDynamicUpdate;
         public final boolean mHasTerminalId;
+        public final boolean mHasTimestamp;
         @UsedForTesting
         public FormatOptions(final int version) {
             this(version, false);
@@ -328,6 +339,11 @@
 
         @UsedForTesting
         public FormatOptions(final int version, final boolean supportsDynamicUpdate) {
+            this(version, supportsDynamicUpdate, false /* hasTimestamp */);
+        }
+
+        public FormatOptions(final int version, final boolean supportsDynamicUpdate,
+                final boolean hasTimestamp) {
             mVersion = version;
             if (version < FIRST_VERSION_WITH_DYNAMIC_UPDATE && supportsDynamicUpdate) {
                 throw new RuntimeException("Dynamic updates are only supported with versions "
@@ -335,6 +351,7 @@
             }
             mSupportsDynamicUpdate = supportsDynamicUpdate;
             mHasTerminalId = (version >= FIRST_VERSION_WITH_TERMINAL_ID);
+            mHasTimestamp = hasTimestamp;
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/makedict/SparseTableContentReader.java b/java/src/com/android/inputmethod/latin/makedict/SparseTableContentReader.java
new file mode 100644
index 0000000..06088b6
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/SparseTableContentReader.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.makedict;
+
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
+import com.android.inputmethod.latin.makedict.DictDecoder.DictionaryBufferFactory;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * An auxiliary class for reading SparseTable and data written by SparseTableContentWriter.
+ */
+public class SparseTableContentReader {
+
+    /**
+     * An interface of a function which is passed to SparseTableContentReader.read.
+     */
+    public interface SparseTableContentReaderInterface {
+        /**
+         * Reads data.
+         *
+         * @param buffer the DictBuffer. The position of the buffer is set to the head of data.
+         */
+        public void read(final DictBuffer buffer);
+    }
+
+    protected final int mContentCount;
+    protected final int mBlockSize;
+    protected final File mBaseDir;
+    protected final File mLookupTableFile;
+    protected final File[] mAddressTableFiles;
+    protected final File[] mContentFiles;
+    protected DictBuffer mLookupTableBuffer;
+    protected final DictBuffer[] mAddressTableBuffers;
+    private final DictBuffer[] mContentBuffers;
+    protected final DictionaryBufferFactory mFactory;
+
+    /**
+     * Sole constructor of SparseTableContentReader.
+     *
+     * @param name the name of SparseTable.
+     * @param blockSize the block size of the content table.
+     * @param baseDir the directory which contains the files of the content table.
+     * @param contentFilenames the file names of content files.
+     * @param contentIds the ids of contents. These ids are used for a suffix of a name of
+     * address files and content files.
+     * @param factory the DictionaryBufferFactory which is used for opening the files.
+     */
+    public SparseTableContentReader(final String name, final int blockSize, final File baseDir,
+            final String[] contentFilenames, final String[] contentIds,
+            final DictionaryBufferFactory factory) {
+        if (contentFilenames.length != contentIds.length) {
+            throw new RuntimeException("The length of contentFilenames and the length of"
+                    + " contentIds are different " + contentFilenames.length + ", "
+                    + contentIds.length);
+        }
+        mBlockSize = blockSize;
+        mBaseDir = baseDir;
+        mFactory = factory;
+        mContentCount = contentFilenames.length;
+        mLookupTableFile = new File(baseDir, name + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
+        mAddressTableFiles = new File[mContentCount];
+        mContentFiles = new File[mContentCount];
+        for (int i = 0; i < mContentCount; ++i) {
+            mAddressTableFiles[i] = new File(mBaseDir,
+                    name + FormatSpec.CONTENT_TABLE_FILE_SUFFIX + contentIds[i]);
+            mContentFiles[i] = new File(mBaseDir, contentFilenames[i] + contentIds[i]);
+        }
+        mAddressTableBuffers = new DictBuffer[mContentCount];
+        mContentBuffers = new DictBuffer[mContentCount];
+    }
+
+    public void openBuffers() throws FileNotFoundException, IOException {
+        mLookupTableBuffer = mFactory.getDictionaryBuffer(mLookupTableFile);
+        for (int i = 0; i < mContentCount; ++i) {
+            mAddressTableBuffers[i] = mFactory.getDictionaryBuffer(mAddressTableFiles[i]);
+            mContentBuffers[i] = mFactory.getDictionaryBuffer(mContentFiles[i]);
+        }
+    }
+
+    protected void read(final int contentIndex, final int index,
+            final SparseTableContentReaderInterface reader) {
+        if (index < 0 || (index / mBlockSize) * SparseTable.SIZE_OF_INT_IN_BYTES
+                >= mLookupTableBuffer.limit()) {
+            return;
+        }
+
+        mLookupTableBuffer.position((index / mBlockSize) * SparseTable.SIZE_OF_INT_IN_BYTES);
+        final int posInAddressTable = mLookupTableBuffer.readInt();
+        if (posInAddressTable == SparseTable.NOT_EXIST) {
+            return;
+        }
+
+        mAddressTableBuffers[contentIndex].position(
+                (posInAddressTable + index % mBlockSize) * SparseTable.SIZE_OF_INT_IN_BYTES);
+        final int address = mAddressTableBuffers[contentIndex].readInt();
+        if (address == SparseTable.NOT_EXIST) {
+            return;
+        }
+
+        mContentBuffers[contentIndex].position(address);
+        reader.read(mContentBuffers[contentIndex]);
+    }
+}
\ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/makedict/SparseTableContentUpdater.java b/java/src/com/android/inputmethod/latin/makedict/SparseTableContentUpdater.java
new file mode 100644
index 0000000..4518f21
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/SparseTableContentUpdater.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.makedict;
+
+import com.android.inputmethod.latin.makedict.DictDecoder.DictionaryBufferFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An auxiliary class for updating data associated with SparseTable.
+ */
+public class SparseTableContentUpdater extends SparseTableContentReader {
+    protected OutputStream mLookupTableOutStream;
+    protected OutputStream[] mAddressTableOutStreams;
+    protected OutputStream[] mContentOutStreams;
+
+    public SparseTableContentUpdater(final String name, final int blockSize,
+            final File baseDir, final String[] contentFilenames, final String[] contentIds,
+            final DictionaryBufferFactory factory) {
+        super(name, blockSize, baseDir, contentFilenames, contentIds, factory);
+        mAddressTableOutStreams = new OutputStream[mContentCount];
+        mContentOutStreams = new OutputStream[mContentCount];
+    }
+
+    protected void openStreamsAndBuffers() throws IOException {
+        openBuffers();
+        mLookupTableOutStream = new FileOutputStream(mLookupTableFile, true /* append */);
+        for (int i = 0; i < mContentCount; ++i) {
+            mAddressTableOutStreams[i] = new FileOutputStream(mAddressTableFiles[i],
+                    true /* append */);
+            mContentOutStreams[i] = new FileOutputStream(mContentFiles[i], true /* append */);
+        }
+    }
+
+    /**
+     * Set the contentIndex-th elements of contentId-th table.
+     *
+     * @param contentId the id of the content table.
+     * @param contentIndex the index where to set the valie.
+     * @param value the value to set.
+     */
+    protected void setContentValue(final int contentId, final int contentIndex, final int value)
+            throws IOException {
+        if ((contentIndex / mBlockSize) * SparseTable.SIZE_OF_INT_IN_BYTES
+                >= mLookupTableBuffer.limit()) {
+            // Need to extend the lookup table
+            final int currentSize = mLookupTableBuffer.limit()
+                    / SparseTable.SIZE_OF_INT_IN_BYTES;
+            final int target = contentIndex / mBlockSize + 1;
+            for (int i = currentSize; i < target; ++i) {
+                BinaryDictEncoderUtils.writeUIntToStream(mLookupTableOutStream,
+                        SparseTable.NOT_EXIST, SparseTable.SIZE_OF_INT_IN_BYTES);
+            }
+            // We need to reopen the byte buffer of the lookup table because a MappedByteBuffer in
+            // Java isn't expanded automatically when the underlying file is expanded.
+            reopenLookupTable();
+        }
+
+        mLookupTableBuffer.position((contentIndex / mBlockSize) * SparseTable.SIZE_OF_INT_IN_BYTES);
+        int posInAddressTable = mLookupTableBuffer.readInt();
+        if (posInAddressTable == SparseTable.NOT_EXIST) {
+            // Need to extend the address table
+            mLookupTableBuffer.position(mLookupTableBuffer.position()
+                    - SparseTable.SIZE_OF_INT_IN_BYTES);
+            posInAddressTable = mAddressTableBuffers[0].limit() / mBlockSize;
+            BinaryDictEncoderUtils.writeUIntToDictBuffer(mLookupTableBuffer,
+                    posInAddressTable, SparseTable.SIZE_OF_INT_IN_BYTES);
+            for (int i = 0; i < mContentCount; ++i) {
+                for (int j = 0; j < mBlockSize; ++j) {
+                    BinaryDictEncoderUtils.writeUIntToStream(mAddressTableOutStreams[i],
+                            SparseTable.NOT_EXIST, SparseTable.SIZE_OF_INT_IN_BYTES);
+                }
+            }
+            // We need to reopen the byte buffers of the address tables because a MappedByteBuffer
+            // in Java isn't expanded automatically when the underlying file is expanded.
+            reopenAddressTables();
+        }
+        posInAddressTable += (contentIndex % mBlockSize) * SparseTable.SIZE_OF_INT_IN_BYTES;
+
+        mAddressTableBuffers[contentId].position(posInAddressTable);
+        BinaryDictEncoderUtils.writeUIntToDictBuffer(mAddressTableBuffers[contentId],
+                value, SparseTable.SIZE_OF_INT_IN_BYTES);
+    }
+
+    private void reopenLookupTable() throws IOException {
+        mLookupTableOutStream.flush();
+        mLookupTableBuffer = mFactory.getDictionaryBuffer(mLookupTableFile);
+    }
+
+    private void reopenAddressTables() throws IOException {
+        for (int i = 0; i < mContentCount; ++i) {
+            mAddressTableOutStreams[i].flush();
+            mAddressTableBuffers[i] = mFactory.getDictionaryBuffer(mAddressTableFiles[i]);
+        }
+    }
+
+    protected void close() throws IOException {
+        mLookupTableOutStream.close();
+        for (final OutputStream stream : mAddressTableOutStreams) {
+            stream.close();
+        }
+        for (final OutputStream stream : mContentOutStreams) {
+            stream.close();
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/makedict/SparseTableContentWriter.java b/java/src/com/android/inputmethod/latin/makedict/SparseTableContentWriter.java
new file mode 100644
index 0000000..49f0fd6
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/SparseTableContentWriter.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.makedict;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An auxiliary class for writing data associated with SparseTable to files.
+ */
+public class SparseTableContentWriter {
+    public interface SparseTableContentWriterInterface {
+        public void write(final OutputStream outStream) throws IOException;
+    }
+
+    private final int mContentCount;
+    private final SparseTable mSparseTable;
+    private final File mLookupTableFile;
+    protected final File mBaseDir;
+    private final File[] mAddressTableFiles;
+    private final File[] mContentFiles;
+    protected final OutputStream[] mContentOutStreams;
+
+    /**
+     * Sole constructor of SparseTableContentWriter.
+     *
+     * @param name the name of SparseTable.
+     * @param initialCapacity the initial capacity of SparseTable.
+     * @param blockSize the block size of the content table.
+     * @param baseDir the directory which contains the files of the content table.
+     * @param contentFilenames the file names of content files.
+     * @param contentIds the ids of contents. These ids are used for a suffix of a name of address
+     * files and content files.
+     */
+    public SparseTableContentWriter(final String name, final int initialCapacity,
+            final int blockSize, final File baseDir, final String[] contentFilenames,
+            final String[] contentIds) {
+        if (contentFilenames.length != contentIds.length) {
+            throw new RuntimeException("The length of contentFilenames and the length of"
+                    + " contentIds are different " + contentFilenames.length + ", "
+                    + contentIds.length);
+        }
+        mContentCount = contentFilenames.length;
+        mSparseTable = new SparseTable(initialCapacity, blockSize, mContentCount);
+        mLookupTableFile = new File(baseDir, name + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
+        mAddressTableFiles = new File[mContentCount];
+        mContentFiles = new File[mContentCount];
+        mBaseDir = baseDir;
+        for (int i = 0; i < mContentCount; ++i) {
+            mAddressTableFiles[i] = new File(mBaseDir,
+                    name + FormatSpec.CONTENT_TABLE_FILE_SUFFIX + contentIds[i]);
+            mContentFiles[i] = new File(mBaseDir, contentFilenames[i] + contentIds[i]);
+        }
+        mContentOutStreams = new OutputStream[mContentCount];
+    }
+
+    public void openStreams() throws FileNotFoundException {
+        for (int i = 0; i < mContentCount; ++i) {
+            mContentOutStreams[i] = new FileOutputStream(mContentFiles[i]);
+        }
+    }
+
+    protected void write(final int contentIndex, final int index,
+            final SparseTableContentWriterInterface writer) throws IOException {
+        mSparseTable.set(contentIndex, index, (int) mContentFiles[contentIndex].length());
+        writer.write(mContentOutStreams[contentIndex]);
+        mContentOutStreams[contentIndex].flush();
+    }
+
+    public void closeStreams() throws IOException {
+        mSparseTable.writeToFiles(mLookupTableFile, mAddressTableFiles);
+        for (int i = 0; i < mContentCount; ++i) {
+            mContentOutStreams[i].close();
+        }
+    }
+}
\ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
index 5372907..f0fed3f 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
@@ -40,21 +40,45 @@
 public class Ver4DictDecoder extends AbstractDictDecoder {
     private static final String TAG = Ver4DictDecoder.class.getSimpleName();
 
-    private static final int FILETYPE_TRIE = 1;
-    private static final int FILETYPE_FREQUENCY = 2;
-    private static final int FILETYPE_TERMINAL_ADDRESS_TABLE = 3;
-    private static final int FILETYPE_BIGRAM_FREQ = 4;
-    private static final int FILETYPE_SHORTCUT = 5;
+    protected static final int FILETYPE_TRIE = 1;
+    protected static final int FILETYPE_FREQUENCY = 2;
+    protected static final int FILETYPE_TERMINAL_ADDRESS_TABLE = 3;
+    protected static final int FILETYPE_BIGRAM_FREQ = 4;
+    protected static final int FILETYPE_SHORTCUT = 5;
 
-    private final File mDictDirectory;
-    private final DictionaryBufferFactory mBufferFactory;
+    protected final File mDictDirectory;
+    protected final DictionaryBufferFactory mBufferFactory;
     protected DictBuffer mDictBuffer;
-    private DictBuffer mFrequencyBuffer;
-    private DictBuffer mTerminalAddressTableBuffer;
-    private DictBuffer mBigramBuffer;
-    private DictBuffer mShortcutBuffer;
-    private SparseTable mBigramAddressTable;
-    private SparseTable mShortcutAddressTable;
+    protected DictBuffer mFrequencyBuffer;
+    protected DictBuffer mTerminalAddressTableBuffer;
+    private BigramContentReader mBigramReader;
+    private ShortcutContentReader mShortcutReader;
+
+    /**
+     * Raw PtNode info straight out of a trie file in version 4 dictionary.
+     */
+    protected static final class Ver4PtNodeInfo {
+        public final int mFlags;
+        public final int[] mCharacters;
+        public final int mTerminalId;
+        public final int mChildrenPos;
+        public final int mParentPos;
+        public final int mNodeSize;
+        public int mStartIndexOfCharacters;
+        public int mEndIndexOfCharacters; // exclusive
+
+        public Ver4PtNodeInfo(final int flags, final int[] characters, final int terminalId,
+                final int childrenPos, final int parentPos, final int nodeSize) {
+            mFlags = flags;
+            mCharacters = characters;
+            mTerminalId = terminalId;
+            mChildrenPos = childrenPos;
+            mParentPos = parentPos;
+            mNodeSize = nodeSize;
+            mStartIndexOfCharacters = 0;
+            mEndIndexOfCharacters = characters.length;
+        }
+    }
 
     @UsedForTesting
     /* package */ Ver4DictDecoder(final File dictDirectory, final int factoryFlag) {
@@ -79,7 +103,7 @@
         mDictBuffer = mFrequencyBuffer = null;
     }
 
-    private File getFile(final int fileType) {
+    protected File getFile(final int fileType) {
         if (fileType == FILETYPE_TRIE) {
             return new File(mDictDirectory,
                     mDictDirectory.getName() + FormatSpec.TRIE_FILE_EXTENSION);
@@ -108,10 +132,12 @@
         mFrequencyBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_FREQUENCY));
         mTerminalAddressTableBuffer = mBufferFactory.getDictionaryBuffer(
                 getFile(FILETYPE_TERMINAL_ADDRESS_TABLE));
-        mBigramBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_BIGRAM_FREQ));
-        loadBigramAddressSparseTable();
-        mShortcutBuffer = mBufferFactory.getDictionaryBuffer(getFile(FILETYPE_SHORTCUT));
-        loadShortcutAddressSparseTable();
+        mBigramReader = new BigramContentReader(mDictDirectory.getName(),
+                mDictDirectory, mBufferFactory, false);
+        mBigramReader.openBuffers();
+        mShortcutReader = new ShortcutContentReader(mDictDirectory.getName(), mDictDirectory,
+                mBufferFactory);
+        mShortcutReader.openBuffers();
     }
 
     @Override
@@ -119,6 +145,7 @@
         return mDictBuffer != null;
     }
 
+    @UsedForTesting
     /* package */ DictBuffer getDictBuffer() {
         return mDictBuffer;
     }
@@ -136,25 +163,113 @@
         return header;
     }
 
-    private void loadBigramAddressSparseTable() throws IOException {
-        final File lookupIndexFile = new File(mDictDirectory, mDictDirectory.getName()
-                + FormatSpec.BIGRAM_FILE_EXTENSION + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
-        final File freqsFile = new File(mDictDirectory, mDictDirectory.getName()
-                + FormatSpec.BIGRAM_FILE_EXTENSION + FormatSpec.CONTENT_TABLE_FILE_SUFFIX
-                + FormatSpec.BIGRAM_FREQ_CONTENT_ID);
-        mBigramAddressTable = SparseTable.readFromFiles(lookupIndexFile, new File[] { freqsFile },
-                FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE);
+    /**
+     * An auxiliary class for reading bigrams.
+     */
+    protected static class BigramContentReader extends SparseTableContentReader {
+        private final boolean mHasTimestamp;
+
+        public BigramContentReader(final String name, final File baseDir,
+                final DictionaryBufferFactory factory, final boolean hasTimestamp) {
+            super(name + FormatSpec.BIGRAM_FILE_EXTENSION,
+                    FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE, baseDir,
+                    getContentFilenames(name, hasTimestamp), getContentIds(hasTimestamp), factory);
+            mHasTimestamp = hasTimestamp;
+        }
+
+        // TODO: Consolidate this method and BigramContentWriter.getContentFilenames.
+        protected static String[] getContentFilenames(final String name,
+                final boolean hasTimestamp) {
+            final String[] contentFilenames;
+            if (hasTimestamp) {
+                contentFilenames = new String[] { name + FormatSpec.BIGRAM_FILE_EXTENSION,
+                        name + FormatSpec.BIGRAM_FILE_EXTENSION };
+            } else {
+                contentFilenames = new String[] { name + FormatSpec.BIGRAM_FILE_EXTENSION };
+            }
+            return contentFilenames;
+        }
+
+        // TODO: Consolidate this method and BigramContentWriter.getContentIds.
+        protected static String[] getContentIds(final boolean hasTimestamp) {
+            final String[] contentIds;
+            if (hasTimestamp) {
+                contentIds = new String[] { FormatSpec.BIGRAM_FREQ_CONTENT_ID,
+                        FormatSpec.BIGRAM_TIMESTAMP_CONTENT_ID };
+            } else {
+                contentIds = new String[] { FormatSpec.BIGRAM_FREQ_CONTENT_ID };
+            }
+            return contentIds;
+        }
+
+        public ArrayList<PendingAttribute> readTargetsAndFrequencies(final int terminalId,
+                final DictBuffer terminalAddressTableBuffer) {
+            final ArrayList<PendingAttribute> bigrams = CollectionUtils.newArrayList();
+            read(FormatSpec.BIGRAM_FREQ_CONTENT_INDEX, terminalId,
+                    new SparseTableContentReaderInterface() {
+                        @Override
+                        public void read(final DictBuffer buffer) {
+                            while (bigrams.size() < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
+                                // If bigrams.size() reaches FormatSpec.MAX_BIGRAMS_IN_A_PTNODE,
+                                // remaining bigram entries are ignored.
+                                final int bigramFlags = buffer.readUnsignedByte();
+                                final int targetTerminalId = buffer.readUnsignedInt24();
+                                terminalAddressTableBuffer.position(targetTerminalId
+                                        * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE);
+                                final int targetAddress =
+                                        terminalAddressTableBuffer.readUnsignedInt24();
+                                bigrams.add(new PendingAttribute(bigramFlags
+                                        & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY,
+                                        targetAddress));
+                                if (0 == (bigramFlags
+                                        & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) {
+                                    break;
+                                }
+                            }
+                            if (bigrams.size() >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
+                                throw new RuntimeException("Too many bigrams in a PtNode ("
+                                        + bigrams.size() + " but max is "
+                                        + FormatSpec.MAX_BIGRAMS_IN_A_PTNODE + ")");
+                            }
+                        }
+                    });
+            if (bigrams.isEmpty()) return null;
+            return bigrams;
+        }
     }
 
-    // TODO: Let's have something like SparseTableContentsReader in this class.
-    private void loadShortcutAddressSparseTable() throws IOException {
-        final File lookupIndexFile = new File(mDictDirectory, mDictDirectory.getName()
-                + FormatSpec.SHORTCUT_FILE_EXTENSION + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
-        final File contentFile = new File(mDictDirectory, mDictDirectory.getName()
-                + FormatSpec.SHORTCUT_FILE_EXTENSION + FormatSpec.CONTENT_TABLE_FILE_SUFFIX
-                + FormatSpec.SHORTCUT_CONTENT_ID);
-        mShortcutAddressTable = SparseTable.readFromFiles(lookupIndexFile,
-                new File[] { contentFile }, FormatSpec.SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE);
+    /**
+     * An auxiliary class for reading shortcuts.
+     */
+    protected static class ShortcutContentReader extends SparseTableContentReader {
+        public ShortcutContentReader(final String name, final File baseDir,
+                final DictionaryBufferFactory factory) {
+            super(name + FormatSpec.SHORTCUT_FILE_EXTENSION,
+                    FormatSpec.SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE, baseDir,
+                    new String[] { name + FormatSpec.SHORTCUT_FILE_EXTENSION },
+                    new String[] { FormatSpec.SHORTCUT_CONTENT_ID }, factory);
+        }
+
+        public ArrayList<WeightedString> readShortcuts(final int terminalId) {
+            final ArrayList<WeightedString> shortcuts = CollectionUtils.newArrayList();
+            read(FormatSpec.SHORTCUT_CONTENT_INDEX, terminalId,
+                    new SparseTableContentReaderInterface() {
+                        @Override
+                        public void read(final DictBuffer buffer) {
+                            while (true) {
+                                final int flags = buffer.readUnsignedByte();
+                                final String word = CharEncoding.readString(buffer);
+                                shortcuts.add(new WeightedString(word,
+                                        flags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY));
+                                if (0 == (flags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) {
+                                    break;
+                                }
+                            }
+                        }
+                    });
+            if (shortcuts.isEmpty()) return null;
+            return shortcuts;
+        }
     }
 
     protected static class PtNodeReader extends AbstractDictDecoder.PtNodeReader {
@@ -168,102 +283,82 @@
         }
     }
 
-    private ArrayList<WeightedString> readShortcuts(final int terminalId) {
-        if (mShortcutAddressTable.get(0, terminalId) == SparseTable.NOT_EXIST) return null;
+    private final int[] mCharacterBufferForReadingVer4PtNodeInfo
+            = new int[FormatSpec.MAX_WORD_LENGTH];
 
-        final ArrayList<WeightedString> ret = CollectionUtils.newArrayList();
-        final int posOfShortcuts = mShortcutAddressTable.get(FormatSpec.SHORTCUT_CONTENT_INDEX,
-                terminalId);
-        mShortcutBuffer.position(posOfShortcuts);
-        while (true) {
-            final int flags = mShortcutBuffer.readUnsignedByte();
-            final String word = CharEncoding.readString(mShortcutBuffer);
-            ret.add(new WeightedString(word,
-                    flags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY));
-            if (0 == (flags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
-        }
-        return ret;
-    }
-
+    /**
+     * Reads PtNode from ptNodePos in the trie file and returns Ver4PtNodeInfo.
+     *
+     * @param ptNodePos the position of PtNode.
+     * @param options the format options.
+     * @return Ver4PtNodeInfo.
+     */
     // TODO: Make this buffer thread safe.
     // TODO: Support words longer than FormatSpec.MAX_WORD_LENGTH.
-    private final int[] mCharacterBuffer = new int[FormatSpec.MAX_WORD_LENGTH];
-    @Override
-    public PtNodeInfo readPtNode(int ptNodePos, FormatOptions options) {
-        int addressPointer = ptNodePos;
+    protected Ver4PtNodeInfo readVer4PtNodeInfo(final int ptNodePos, final FormatOptions options) {
+        int readingPos = ptNodePos;
         final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
-        addressPointer += FormatSpec.PTNODE_FLAGS_SIZE;
+        readingPos += FormatSpec.PTNODE_FLAGS_SIZE;
 
-        final int parentAddress = PtNodeReader.readParentAddress(mDictBuffer, options);
+        final int parentPos = PtNodeReader.readParentAddress(mDictBuffer, options);
         if (BinaryDictIOUtils.supportsDynamicUpdate(options)) {
-            addressPointer += FormatSpec.PARENT_ADDRESS_SIZE;
+            readingPos += FormatSpec.PARENT_ADDRESS_SIZE;
         }
 
         final int characters[];
         if (0 != (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS)) {
             int index = 0;
             int character = CharEncoding.readChar(mDictBuffer);
-            addressPointer += CharEncoding.getCharSize(character);
+            readingPos += CharEncoding.getCharSize(character);
             while (FormatSpec.INVALID_CHARACTER != character
                     && index < FormatSpec.MAX_WORD_LENGTH) {
-                mCharacterBuffer[index++] = character;
+                mCharacterBufferForReadingVer4PtNodeInfo[index++] = character;
                 character = CharEncoding.readChar(mDictBuffer);
-                addressPointer += CharEncoding.getCharSize(character);
+                readingPos += CharEncoding.getCharSize(character);
             }
-            characters = Arrays.copyOfRange(mCharacterBuffer, 0, index);
+            characters = Arrays.copyOfRange(mCharacterBufferForReadingVer4PtNodeInfo, 0, index);
         } else {
             final int character = CharEncoding.readChar(mDictBuffer);
-            addressPointer += CharEncoding.getCharSize(character);
+            readingPos += CharEncoding.getCharSize(character);
             characters = new int[] { character };
         }
         final int terminalId;
         if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) {
             terminalId = PtNodeReader.readTerminalId(mDictBuffer);
-            addressPointer += FormatSpec.PTNODE_TERMINAL_ID_SIZE;
+            readingPos += FormatSpec.PTNODE_TERMINAL_ID_SIZE;
         } else {
             terminalId = PtNode.NOT_A_TERMINAL;
         }
 
+        int childrenPos = PtNodeReader.readChildrenAddress(mDictBuffer, flags, options);
+        if (childrenPos != FormatSpec.NO_CHILDREN_ADDRESS) {
+            childrenPos += readingPos;
+        }
+        readingPos += BinaryDictIOUtils.getChildrenAddressSize(flags, options);
+
+        return new Ver4PtNodeInfo(flags, characters, terminalId, childrenPos, parentPos,
+                readingPos - ptNodePos);
+    }
+
+    @Override
+    public PtNodeInfo readPtNode(int ptNodePos, FormatOptions options) {
+        final Ver4PtNodeInfo nodeInfo = readVer4PtNodeInfo(ptNodePos, options);
+
         final int frequency;
-        if (0 != (FormatSpec.FLAG_IS_TERMINAL & flags)) {
-            frequency = PtNodeReader.readFrequency(mFrequencyBuffer, terminalId);
+        if (0 != (FormatSpec.FLAG_IS_TERMINAL & nodeInfo.mFlags)) {
+            frequency = PtNodeReader.readFrequency(mFrequencyBuffer, nodeInfo.mTerminalId);
         } else {
             frequency = PtNode.NOT_A_TERMINAL;
         }
-        int childrenAddress = PtNodeReader.readChildrenAddress(mDictBuffer, flags, options);
-        if (childrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) {
-            childrenAddress += addressPointer;
-        }
-        addressPointer += BinaryDictIOUtils.getChildrenAddressSize(flags, options);
-        final ArrayList<WeightedString> shortcutTargets = readShortcuts(terminalId);
 
-        final ArrayList<PendingAttribute> bigrams;
-        if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) {
-            bigrams = new ArrayList<PendingAttribute>();
-            final int posOfBigrams = mBigramAddressTable.get(0 /* contentTableIndex */, terminalId);
-            mBigramBuffer.position(posOfBigrams);
-            while (bigrams.size() < FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
-                // If bigrams.size() reaches FormatSpec.MAX_BIGRAMS_IN_A_PTNODE,
-                // remaining bigram entries are ignored.
-                final int bigramFlags = mBigramBuffer.readUnsignedByte();
-                final int targetTerminalId = mBigramBuffer.readUnsignedInt24();
-                mTerminalAddressTableBuffer.position(
-                        targetTerminalId * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE);
-                final int targetAddress = mTerminalAddressTableBuffer.readUnsignedInt24();
-                bigrams.add(new PendingAttribute(
-                        bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_FREQUENCY,
-                        targetAddress));
-                if (0 == (bigramFlags & FormatSpec.FLAG_BIGRAM_SHORTCUT_ATTR_HAS_NEXT)) break;
-            }
-            if (bigrams.size() >= FormatSpec.MAX_BIGRAMS_IN_A_PTNODE) {
-                throw new RuntimeException("Too many bigrams in a PtNode (" + bigrams.size()
-                        + " but max is " + FormatSpec.MAX_BIGRAMS_IN_A_PTNODE + ")");
-            }
-        } else {
-            bigrams = null;
-        }
-        return new PtNodeInfo(ptNodePos, addressPointer, flags, characters, frequency,
-                parentAddress, childrenAddress, shortcutTargets, bigrams);
+        final ArrayList<WeightedString> shortcutTargets = mShortcutReader.readShortcuts(
+                nodeInfo.mTerminalId);
+        final ArrayList<PendingAttribute> bigrams = mBigramReader.readTargetsAndFrequencies(
+                nodeInfo.mTerminalId, mTerminalAddressTableBuffer);
+
+        return new PtNodeInfo(ptNodePos, ptNodePos + nodeInfo.mNodeSize, nodeInfo.mFlags,
+                nodeInfo.mCharacters, frequency, nodeInfo.mParentPos, nodeInfo.mChildrenPos,
+                shortcutTargets, bigrams);
     }
 
     private void deleteDictFiles() {
@@ -314,10 +409,14 @@
 
     @Override
     public boolean readAndFollowForwardLink() {
-        final int nextAddress = mDictBuffer.readUnsignedInt24();
-        if (nextAddress >= 0 && nextAddress < mDictBuffer.limit()) {
-            mDictBuffer.position(nextAddress);
-            return true;
+        final int forwardLinkPos = mDictBuffer.position();
+        int nextRelativePos = BinaryDictDecoderUtils.readSInt24(mDictBuffer);
+        if (nextRelativePos != FormatSpec.NO_FORWARD_LINK_ADDRESS) {
+            final int nextPos = forwardLinkPos + nextRelativePos;
+            if (nextPos >= 0 && nextPos < mDictBuffer.limit()) {
+              mDictBuffer.position(nextPos);
+              return true;
+            }
         }
         return false;
     }
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
index f9dcacf..4b3acdc 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
@@ -25,6 +25,7 @@
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -32,6 +33,8 @@
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.Iterator;
 
 /**
@@ -45,6 +48,7 @@
     private int mHeaderSize;
     private OutputStream mTrieOutStream;
     private OutputStream mFreqOutStream;
+    private OutputStream mUnigramTimestampOutStream;
     private OutputStream mTerminalAddressTableOutStream;
     private File mDictDir;
     private String mBaseFilename;
@@ -56,73 +60,41 @@
         mDictPlacedDir = dictPlacedDir;
     }
 
-    private interface SparseTableContentWriterInterface {
-        public void write(final OutputStream outStream) throws IOException;
-    }
-
-    private static class SparseTableContentWriter {
-        private final int mContentCount;
-        private final SparseTable mSparseTable;
-        private final File mLookupTableFile;
-        protected final File mBaseDir;
-        private final File[] mAddressTableFiles;
-        private final File[] mContentFiles;
-        protected final OutputStream[] mContentOutStreams;
-
-        public SparseTableContentWriter(final String name, final int contentCount,
-                final int initialCapacity, final int blockSize, final File baseDir,
-                final String[] contentFilenames, final String[] contentIds) {
-            if (contentFilenames.length != contentIds.length) {
-                throw new RuntimeException("The length of contentFilenames and the length of"
-                        + " contentIds are different " + contentFilenames.length + ", "
-                        + contentIds.length);
-            }
-            mContentCount = contentCount;
-            mSparseTable = new SparseTable(initialCapacity, blockSize, contentCount);
-            mLookupTableFile = new File(baseDir, name + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX);
-            mAddressTableFiles = new File[mContentCount];
-            mContentFiles = new File[mContentCount];
-            mBaseDir = baseDir;
-            for (int i = 0; i < mContentCount; ++i) {
-                mAddressTableFiles[i] = new File(mBaseDir,
-                        name + FormatSpec.CONTENT_TABLE_FILE_SUFFIX + contentIds[i]);
-                mContentFiles[i] = new File(mBaseDir, contentFilenames[i] + contentIds[i]);
-            }
-            mContentOutStreams = new OutputStream[mContentCount];
-        }
-
-        public void openStreams() throws FileNotFoundException {
-            for (int i = 0; i < mContentCount; ++i) {
-                mContentOutStreams[i] = new FileOutputStream(mContentFiles[i]);
-            }
-        }
-
-        protected void write(final int contentIndex, final int index,
-                final SparseTableContentWriterInterface writer) throws IOException {
-            mSparseTable.set(contentIndex, index, (int) mContentFiles[contentIndex].length());
-            writer.write(mContentOutStreams[contentIndex]);
-            mContentOutStreams[contentIndex].flush();
-        }
-
-        public void closeStreams() throws IOException {
-            mSparseTable.writeToFiles(mLookupTableFile, mAddressTableFiles);
-            for (int i = 0; i < mContentCount; ++i) {
-                mContentOutStreams[i].close();
-            }
-        }
-    }
-
     private static class BigramContentWriter extends SparseTableContentWriter {
+        private final boolean mWriteTimestamp;
 
         public BigramContentWriter(final String name, final int initialCapacity,
-                final File baseDir) {
-            super(name + FormatSpec.BIGRAM_FILE_EXTENSION, FormatSpec.BIGRAM_CONTENT_COUNT,
-                    initialCapacity, FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE, baseDir,
-                    new String[] { name + FormatSpec.BIGRAM_FILE_EXTENSION },
-                    new String[] { FormatSpec.BIGRAM_FREQ_CONTENT_ID });
+                final File baseDir, final boolean writeTimestamp) {
+            super(name + FormatSpec.BIGRAM_FILE_EXTENSION, initialCapacity,
+                    FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE, baseDir,
+                    getContentFilenames(name, writeTimestamp), getContentIds(writeTimestamp));
+            mWriteTimestamp = writeTimestamp;
         }
 
-        public void writeBigramsForOneWord(final int terminalId,
+        private static String[] getContentFilenames(final String name,
+                final boolean writeTimestamp) {
+            final String[] contentFilenames;
+            if (writeTimestamp) {
+                contentFilenames = new String[] { name + FormatSpec.BIGRAM_FILE_EXTENSION,
+                        name + FormatSpec.BIGRAM_FILE_EXTENSION };
+            } else {
+                contentFilenames = new String[] { name + FormatSpec.BIGRAM_FILE_EXTENSION };
+            }
+            return contentFilenames;
+        }
+
+        private static String[] getContentIds(final boolean writeTimestamp) {
+            final String[] contentIds;
+            if (writeTimestamp) {
+                contentIds = new String[] { FormatSpec.BIGRAM_FREQ_CONTENT_ID,
+                        FormatSpec.BIGRAM_TIMESTAMP_CONTENT_ID };
+            } else {
+                contentIds = new String[] { FormatSpec.BIGRAM_FREQ_CONTENT_ID };
+            }
+            return contentIds;
+        }
+
+        public void writeBigramsForOneWord(final int terminalId, final int bigramCount,
                 final Iterator<WeightedString> bigramIterator, final FusionDictionary dict)
                         throws IOException {
             write(FormatSpec.BIGRAM_FREQ_CONTENT_INDEX, terminalId,
@@ -130,8 +102,16 @@
                         @Override
                         public void write(final OutputStream outStream) throws IOException {
                             writeBigramsForOneWordInternal(outStream, bigramIterator, dict);
-                        }
-            });
+                        }});
+            if (mWriteTimestamp) {
+                write(FormatSpec.BIGRAM_TIMESTAMP_CONTENT_INDEX, terminalId,
+                        new SparseTableContentWriterInterface() {
+                            @Override
+                            public void write(final OutputStream outStream) throws IOException {
+                                initBigramTimestampsCountersAndLevelsForOneWordInternal(outStream,
+                                        bigramCount);
+                            }});
+            }
         }
 
         private void writeBigramsForOneWordInternal(final OutputStream outStream,
@@ -151,13 +131,26 @@
                         FormatSpec.PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE);
             }
         }
+
+        private void initBigramTimestampsCountersAndLevelsForOneWordInternal(
+                final OutputStream outStream, final int bigramCount) throws IOException {
+            for (int i = 0; i < bigramCount; ++i) {
+                // TODO: Figure out what initial values should be.
+                BinaryDictEncoderUtils.writeUIntToStream(outStream, 0 /* value */,
+                        FormatSpec.BIGRAM_TIMESTAMP_SIZE);
+                BinaryDictEncoderUtils.writeUIntToStream(outStream, 0 /* value */,
+                        FormatSpec.BIGRAM_COUNTER_SIZE);
+                BinaryDictEncoderUtils.writeUIntToStream(outStream, 0 /* value */,
+                        FormatSpec.BIGRAM_LEVEL_SIZE);
+            }
+        }
     }
 
     private static class ShortcutContentWriter extends SparseTableContentWriter {
         public ShortcutContentWriter(final String name, final int initialCapacity,
                 final File baseDir) {
-            super(name + FormatSpec.SHORTCUT_FILE_EXTENSION, FormatSpec.SHORTCUT_CONTENT_COUNT,
-                    initialCapacity, FormatSpec.SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE, baseDir,
+            super(name + FormatSpec.SHORTCUT_FILE_EXTENSION, initialCapacity,
+                    FormatSpec.SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE, baseDir,
                     new String[] { name + FormatSpec.SHORTCUT_FILE_EXTENSION },
                     new String[] { FormatSpec.SHORTCUT_CONTENT_ID });
         }
@@ -193,18 +186,20 @@
         mDictDir = new File(mDictPlacedDir, mBaseFilename);
         final File trieFile = new File(mDictDir, mBaseFilename + FormatSpec.TRIE_FILE_EXTENSION);
         final File freqFile = new File(mDictDir, mBaseFilename + FormatSpec.FREQ_FILE_EXTENSION);
+        final File timestampFile = new File(mDictDir,
+                mBaseFilename + FormatSpec.UNIGRAM_TIMESTAMP_FILE_EXTENSION);
         final File terminalAddressTableFile = new File(mDictDir,
                 mBaseFilename + FormatSpec.TERMINAL_ADDRESS_TABLE_FILE_EXTENSION);
         if (!mDictDir.isDirectory()) {
             if (mDictDir.exists()) mDictDir.delete();
             mDictDir.mkdirs();
         }
-        if (!trieFile.exists()) trieFile.createNewFile();
-        if (!freqFile.exists()) freqFile.createNewFile();
-        if (!terminalAddressTableFile.exists()) terminalAddressTableFile.createNewFile();
         mTrieOutStream = new FileOutputStream(trieFile);
         mFreqOutStream = new FileOutputStream(freqFile);
         mTerminalAddressTableOutStream = new FileOutputStream(terminalAddressTableFile);
+        if (formatOptions.mHasTimestamp) {
+            mUnigramTimestampOutStream = new FileOutputStream(timestampFile);
+        }
     }
 
     private void close() throws IOException {
@@ -218,6 +213,9 @@
             if (mTerminalAddressTableOutStream != null) {
                 mTerminalAddressTableOutStream.close();
             }
+            if (mUnigramTimestampOutStream != null) {
+                mUnigramTimestampOutStream.close();
+            }
         } finally {
             mTrieOutStream = null;
             mFreqOutStream = null;
@@ -246,18 +244,41 @@
         MakedictLog.i("Flattening the tree...");
         ArrayList<PtNodeArray> flatNodes = BinaryDictEncoderUtils.flattenTree(dict.mRootNodeArray);
         int terminalCount = 0;
+        final ArrayList<PtNode> nodes = CollectionUtils.newArrayList();
         for (final PtNodeArray array : flatNodes) {
             for (final PtNode node : array.mData) {
-                if (node.isTerminal()) node.mTerminalId = terminalCount++;
+                if (node.isTerminal()) {
+                    nodes.add(node);
+                    node.mTerminalId = terminalCount++;
+                }
             }
         }
+        Collections.sort(nodes, new Comparator<PtNode>() {
+            @Override
+            public int compare(final PtNode lhs, final PtNode rhs) {
+                if (lhs.mFrequency != rhs.mFrequency) {
+                    return lhs.mFrequency < rhs.mFrequency ? -1 : 1;
+                }
+                if (lhs.mTerminalId < rhs.mTerminalId) return -1;
+                if (lhs.mTerminalId > rhs.mTerminalId) return 1;
+                return 0;
+            }
+        });
+        int count = 0;
+        for (final PtNode node : nodes) {
+            node.mTerminalId = count++;
+        }
 
         MakedictLog.i("Computing addresses...");
         BinaryDictEncoderUtils.computeAddresses(dict, flatNodes, formatOptions);
         if (MakedictLog.DBG) BinaryDictEncoderUtils.checkFlatPtNodeArrayList(flatNodes);
 
         writeTerminalData(flatNodes, terminalCount);
-        mBigramWriter = new BigramContentWriter(mBaseFilename, terminalCount, mDictDir);
+        if (formatOptions.mHasTimestamp) {
+            initUnigramTimestamps(terminalCount);
+        }
+        mBigramWriter = new BigramContentWriter(mBaseFilename, terminalCount, mDictDir,
+                formatOptions.mHasTimestamp);
         writeBigrams(flatNodes, dict);
         mShortcutWriter = new ShortcutContentWriter(mBaseFilename, terminalCount, mDictDir);
         writeShortcuts(flatNodes);
@@ -348,7 +369,7 @@
         for (final PtNodeArray nodeArray : flatNodes) {
             for (final PtNode ptNode : nodeArray.mData) {
                 if (ptNode.mBigrams != null) {
-                    mBigramWriter.writeBigramsForOneWord(ptNode.mTerminalId,
+                    mBigramWriter.writeBigramsForOneWord(ptNode.mTerminalId, ptNode.mBigrams.size(),
                             ptNode.mBigrams.iterator(), dict);
                 }
             }
@@ -408,4 +429,11 @@
         mFreqOutStream.write(freqBuf);
         mTerminalAddressTableOutStream.write(terminalAddressTableBuf);
     }
+
+    private void initUnigramTimestamps(final int terminalCount) throws IOException {
+        // Initial value of time stamps for each word is 0.
+        final byte[] unigramTimestampBuf =
+                new byte[terminalCount * FormatSpec.UNIGRAM_TIMESTAMP_SIZE];
+        mUnigramTimestampOutStream.write(unigramTimestampBuf);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java
index 3d8f186..65860ee 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictUpdater.java
@@ -17,23 +17,124 @@
 package com.android.inputmethod.latin.makedict;
 
 import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.CharEncoding;
+import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+import android.util.Log;
 
 import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
 
 /**
  * An implementation of DictUpdater for version 4 binary dictionary.
  */
 @UsedForTesting
 public class Ver4DictUpdater extends Ver4DictDecoder implements DictUpdater {
+    private static final String TAG = Ver4DictUpdater.class.getSimpleName();
+
+    private OutputStream mDictStream;
+    private final File mFrequencyFile;
 
     @UsedForTesting
     public Ver4DictUpdater(final File dictDirectory, final int factoryType) {
         // DictUpdater must have an updatable DictBuffer.
         super(dictDirectory, ((factoryType & MASK_DICTBUFFER) == USE_BYTEARRAY)
                 ? USE_BYTEARRAY : USE_WRITABLE_BYTEBUFFER);
+        mFrequencyFile = getFile(FILETYPE_FREQUENCY);
+    }
+
+    private static class BigramContentUpdater extends SparseTableContentUpdater {
+        private final boolean mHasTimestamp;
+
+        public BigramContentUpdater(final String name, final File baseDir,
+                final boolean hasTimestamp) {
+            super(name + FormatSpec.BIGRAM_FILE_EXTENSION,
+                    FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE, baseDir,
+                    BigramContentReader.getContentFilenames(name, hasTimestamp),
+                    BigramContentReader.getContentIds(hasTimestamp),
+                    new DictionaryBufferFromWritableByteBufferFactory());
+            mHasTimestamp = hasTimestamp;
+        }
+
+        public void insertBigramEntries(final int terminalId, final int frequency,
+                final ArrayList<PendingAttribute> entries) throws IOException {
+            if (terminalId < 0) {
+                throw new RuntimeException("Invalid terminal id : " + terminalId);
+            }
+            openStreamsAndBuffers();
+
+            if (entries == null || entries.isEmpty()) {
+                setContentValue(FormatSpec.BIGRAM_FREQ_CONTENT_INDEX, terminalId,
+                        SparseTable.NOT_EXIST);
+                return;
+            }
+            final int positionOfEntries =
+                    (int) mContentFiles[FormatSpec.BIGRAM_FREQ_CONTENT_INDEX].length();
+            setContentValue(FormatSpec.BIGRAM_FREQ_CONTENT_INDEX, terminalId, positionOfEntries);
+
+            final Iterator<PendingAttribute> bigramIterator = entries.iterator();
+            while (bigramIterator.hasNext()) {
+                final PendingAttribute entry = bigramIterator.next();
+                final int flags = BinaryDictEncoderUtils.makeBigramFlags(bigramIterator.hasNext(),
+                        0 /* offset */, entry.mFrequency, frequency, "" /* word */);
+                BinaryDictEncoderUtils.writeUIntToStream(
+                        mContentOutStreams[FormatSpec.BIGRAM_FREQ_CONTENT_INDEX], flags,
+                        FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
+                BinaryDictEncoderUtils.writeUIntToStream(
+                        mContentOutStreams[FormatSpec.BIGRAM_FREQ_CONTENT_INDEX], entry.mAddress,
+                        FormatSpec.PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE);
+            }
+            close();
+        }
+    }
+
+    private static class ShortcutContentUpdater extends SparseTableContentUpdater {
+        public ShortcutContentUpdater(final String name, final File baseDir) {
+            super(name + FormatSpec.SHORTCUT_FILE_EXTENSION,
+                    FormatSpec.SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE, baseDir,
+                    new String[] { name + FormatSpec.SHORTCUT_FILE_EXTENSION },
+                    new String[] { FormatSpec.SHORTCUT_CONTENT_ID },
+                    new DictionaryBufferFromWritableByteBufferFactory());
+        }
+
+        public void insertShortcuts(final int terminalId,
+                final ArrayList<WeightedString> shortcuts) throws IOException {
+            if (terminalId < 0) {
+                throw new RuntimeException("Invalid terminal id : " + terminalId);
+            }
+            openStreamsAndBuffers();
+            if (shortcuts == null || shortcuts.isEmpty()) {
+                setContentValue(FormatSpec.SHORTCUT_CONTENT_INDEX, terminalId,
+                        SparseTable.NOT_EXIST);
+                return;
+            }
+
+            final int positionOfShortcuts =
+                    (int) mContentFiles[FormatSpec.SHORTCUT_CONTENT_INDEX].length();
+            setContentValue(FormatSpec.SHORTCUT_CONTENT_INDEX, terminalId, positionOfShortcuts);
+
+            final Iterator<WeightedString> shortcutIterator = shortcuts.iterator();
+            while (shortcutIterator.hasNext()) {
+                final WeightedString target = shortcutIterator.next();
+                final int shortcutFlags = BinaryDictEncoderUtils.makeShortcutFlags(
+                        shortcutIterator.hasNext(), target.mFrequency);
+                BinaryDictEncoderUtils.writeUIntToStream(
+                        mContentOutStreams[FormatSpec.SHORTCUT_CONTENT_INDEX], shortcutFlags,
+                        FormatSpec.PTNODE_ATTRIBUTE_FLAGS_SIZE);
+                CharEncoding.writeString(mContentOutStreams[FormatSpec.SHORTCUT_CONTENT_INDEX],
+                        target.mWord);
+            }
+            close();
+        }
     }
 
     @Override
@@ -49,11 +150,622 @@
         }
     }
 
-    @Override
+    private int getNewTerminalId() {
+        // The size of frequency file is FormatSpec.FREQUENCY_AND_FLAGS_SIZE * number of terminals
+        // because each terminal always has a frequency.
+        // So we can get a fresh terminal id by this logic.
+        // CAVEAT: we are reading the file size from the disk each time: beware of race conditions,
+        // even on one thread.
+        return (int) (mFrequencyFile.length() / FormatSpec.FREQUENCY_AND_FLAGS_SIZE);
+    }
+
+    private void updateParentPosIfNotMoved(final int nodePos, final int newParentPos,
+            final FormatOptions formatOptions) {
+        final int originalPos = getPosition();
+        setPosition(nodePos);
+        final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
+        if (!BinaryDictIOUtils.isMovedPtNode(flags, formatOptions)) {
+            final int parentOffset = newParentPos - nodePos;
+            BinaryDictIOUtils.writeSInt24ToBuffer(mDictBuffer, parentOffset);
+        }
+        setPosition(originalPos);
+    }
+
+    private void updateParentPositions(final int nodeArrayPos, final int newParentPos,
+            final FormatOptions formatOptions) {
+        final int originalPos = mDictBuffer.position();
+        mDictBuffer.position(nodeArrayPos);
+        int jumpCount = 0;
+        do {
+            final int count = readPtNodeCount();
+            for (int i = 0; i < count; ++i) {
+                updateParentPosIfNotMoved(getPosition(), newParentPos, formatOptions);
+                skipPtNode(formatOptions);
+            }
+            if (!readAndFollowForwardLink()) break;
+        } while (jumpCount++ < DynamicBinaryDictIOUtils.MAX_JUMPS);
+        setPosition(originalPos);
+    }
+
+    private void updateChildrenPos(final int nodePos, final int newChildrenPos,
+            final FormatOptions options) {
+        final int originalPos = getPosition();
+        setPosition(nodePos);
+        final int flags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
+        PtNodeReader.readParentAddress(mDictBuffer, options);
+        BinaryDictIOUtils.skipString(mDictBuffer,
+                (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0);
+        if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) PtNodeReader.readTerminalId(mDictBuffer);
+        final int basePos = getPosition();
+        BinaryDictIOUtils.writeSInt24ToBuffer(mDictBuffer, newChildrenPos - basePos);
+        setPosition(originalPos);
+    }
+
+    private void updateTerminalPosition(final int terminalId, final int position) {
+        if (terminalId == PtNode.NOT_A_TERMINAL
+                || terminalId * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE
+                        >= mTerminalAddressTableBuffer.limit()) return;
+        mTerminalAddressTableBuffer.position(terminalId
+                * FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE);
+        BinaryDictEncoderUtils.writeUIntToDictBuffer(mTerminalAddressTableBuffer, position,
+                FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE);
+    }
+
+    private void updateForwardLink(final int nodeArrayPos, final int newForwardLink,
+            final FormatOptions formatOptions) {
+        final int originalPos = getPosition();
+        setPosition(nodeArrayPos);
+        int jumpCount = 0;
+        while (jumpCount++ < DynamicBinaryDictIOUtils.MAX_JUMPS) {
+            final int ptNodeCount = readPtNodeCount();
+            for (int i = 0; i < ptNodeCount; ++i) {
+                skipPtNode(formatOptions);
+            }
+            final int forwardLinkPos = getPosition();
+            if (!readAndFollowForwardLink()) {
+                setPosition(forwardLinkPos);
+                BinaryDictIOUtils.writeSInt24ToBuffer(mDictBuffer, newForwardLink - forwardLinkPos);
+                break;
+            }
+        }
+        setPosition(originalPos);
+    }
+
+    private void markPtNodeAsMoved(final int nodePos, final int newNodePos,
+            final FormatOptions options) {
+        final int originalPos = getPosition();
+        updateParentPosIfNotMoved(nodePos, newNodePos, options);
+        setPosition(nodePos);
+        final int currentFlags = PtNodeReader.readPtNodeOptionFlags(mDictBuffer);
+        setPosition(nodePos);
+        mDictBuffer.put((byte) (FormatSpec.FLAG_IS_MOVED
+                | (currentFlags & (~FormatSpec.MASK_MOVE_AND_DELETE_FLAG))));
+        final int offset = newNodePos - nodePos;
+        BinaryDictIOUtils.writeSInt24ToBuffer(mDictBuffer, offset);
+        setPosition(originalPos);
+    }
+
+    /**
+     * Writes a PtNode to an output stream from a Ver4PtNodeInfo.
+     *
+     * @param nodePos the position of the head of the PtNode.
+     * @param info the PtNode info to be written.
+     * @return the size written, in bytes.
+     */
+    private int writePtNode(final int nodePos, final Ver4PtNodeInfo info) throws IOException {
+        int written = 0;
+
+        // Write flags.
+        mDictStream.write((byte) (info.mFlags & 0xFF));
+        written += FormatSpec.PTNODE_FLAGS_SIZE;
+
+        // Write the parent position.
+        final int parentOffset = info.mParentPos == FormatSpec.NO_PARENT_ADDRESS ?
+                FormatSpec.NO_PARENT_ADDRESS : info.mParentPos - nodePos;
+        BinaryDictIOUtils.writeSInt24ToStream(mDictStream, parentOffset);
+        written += FormatSpec.PARENT_ADDRESS_SIZE;
+
+        // Write a string.
+        if (((info.mFlags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0)
+                != (info.mEndIndexOfCharacters - info.mStartIndexOfCharacters > 1)) {
+            throw new RuntimeException("Inconsistent flags : hasMultipleChars = "
+                    + ((info.mFlags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0) + ", length = "
+                    + (info.mEndIndexOfCharacters - info.mStartIndexOfCharacters));
+        }
+        written += CharEncoding.writeCodePoints(mDictStream, info.mCharacters,
+                info.mStartIndexOfCharacters, info.mEndIndexOfCharacters);
+
+        // Write the terminal id.
+        if ((info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0) {
+            BinaryDictEncoderUtils.writeUIntToStream(mDictStream, info.mTerminalId,
+                    FormatSpec.PTNODE_TERMINAL_ID_SIZE);
+            written += FormatSpec.PTNODE_TERMINAL_ID_SIZE;
+        }
+
+        // Write the children position.
+        final int childrenOffset = info.mChildrenPos == FormatSpec.NO_CHILDREN_ADDRESS
+                ? 0 : info.mChildrenPos - (nodePos + written);
+        BinaryDictIOUtils.writeSInt24ToStream(mDictStream, childrenOffset);
+        written += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
+
+        return written;
+    }
+
+    /**
+     * Helper method to split and move PtNode.
+     *
+     * @param ptNodeArrayPos the position of PtNodeArray which contains the split and moved PtNode.
+     * @param splittedPtNodeToMovePos the position of the split and moved PtNode.
+     * @param newParent the parent PtNode after splitting.
+     * @param newChildren the children PtNodes after splitting.
+     * @param newParentStartPos where to write the new parent.
+     * @param formatOptions the format options.
+     */
+    private void writeSplittedPtNodes(final int ptNodeArrayPos, final int splittedPtNodeToMovePos,
+            final Ver4PtNodeInfo newParent, final Ver4PtNodeInfo[] newChildren,
+            final int newParentStartPos,
+            final FormatOptions formatOptions) throws IOException {
+        updateTerminalPosition(newParent.mTerminalId,
+                newParentStartPos + 1 /* size of PtNodeCount */);
+        int written = writePtNodeArray(newParentStartPos, new Ver4PtNodeInfo[] { newParent },
+                FormatSpec.NO_FORWARD_LINK_ADDRESS);
+        final int childrenStartPos = newParentStartPos + written;
+        writePtNodeArray(childrenStartPos, newChildren, FormatSpec.NO_FORWARD_LINK_ADDRESS);
+        int childrenNodePos = childrenStartPos + 1 /* size of PtNodeCount */;
+        for (final Ver4PtNodeInfo info : newChildren) {
+            updateTerminalPosition(info.mTerminalId, childrenNodePos);
+            childrenNodePos += computePtNodeSize(info.mCharacters, info.mStartIndexOfCharacters,
+                    info.mEndIndexOfCharacters,
+                    (info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0);
+        }
+
+        // Mark as moved.
+        markPtNodeAsMoved(splittedPtNodeToMovePos, newParentStartPos + 1 /* size of PtNodeCount */,
+                formatOptions);
+        updateForwardLink(ptNodeArrayPos, newParentStartPos, formatOptions);
+    }
+
+    /**
+     * Writes a node array to the stream.
+     *
+     * @param nodeArrayPos the position of the head of the node array.
+     * @param infos an array of Ver4PtNodeInfo to be written.
+     * @return the written length in bytes.
+     */
+    private int writePtNodeArray(final int nodeArrayPos, final Ver4PtNodeInfo[] infos,
+            final int forwardLink) throws IOException {
+        int written = BinaryDictIOUtils.writePtNodeCount(mDictStream, infos.length);
+        for (int i = 0; i < infos.length; ++i) {
+            written += writePtNode(nodeArrayPos + written, infos[i]);
+        }
+        BinaryDictIOUtils.writeSInt24ToStream(mDictStream, forwardLink);
+        written += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
+        return written;
+    }
+
+    private int computePtNodeSize(final int[] codePoints, final int startIndex, final int endIndex,
+            final boolean isTerminal) {
+        return FormatSpec.PTNODE_FLAGS_SIZE + FormatSpec.PARENT_ADDRESS_SIZE
+                + CharEncoding.getCharArraySize(codePoints, startIndex, endIndex)
+                + (endIndex - startIndex > 1 ? FormatSpec.PTNODE_TERMINATOR_SIZE : 0)
+                + (isTerminal ? FormatSpec.PTNODE_TERMINAL_ID_SIZE : 0)
+                + FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
+    }
+
+    private void writeNewSinglePtNodeWithAttributes(final int[] codePoints,
+            final boolean hasShortcuts, final int terminalId, final boolean hasBigrams,
+            final boolean isNotAWord, final boolean isBlackListEntry, final int parentPos,
+            final FormatOptions formatOptions) throws IOException {
+        final int newNodeArrayPos = mDictBuffer.limit();
+        final int newNodeFlags = BinaryDictEncoderUtils.makePtNodeFlags(codePoints.length > 1,
+                terminalId != PtNode.NOT_A_TERMINAL, FormatSpec.FLAG_IS_NOT_MOVED, hasShortcuts,
+                hasBigrams, isNotAWord, isBlackListEntry, formatOptions);
+        final Ver4PtNodeInfo info = new Ver4PtNodeInfo(newNodeFlags, codePoints, terminalId,
+                FormatSpec.NO_CHILDREN_ADDRESS, parentPos, 0 /* nodeSize */);
+        writePtNodeArray(newNodeArrayPos, new Ver4PtNodeInfo[] { info },
+                FormatSpec.NO_FORWARD_LINK_ADDRESS);
+    }
+
+    private int setMultipleCharsInFlags(final int currentFlags, final boolean hasMultipleChars) {
+        final int flags;
+        if (hasMultipleChars) {
+            flags = currentFlags | FormatSpec.FLAG_HAS_MULTIPLE_CHARS;
+        } else {
+            flags = currentFlags & (~FormatSpec.FLAG_HAS_MULTIPLE_CHARS);
+        }
+        return flags;
+    }
+
+    private int setIsNotAWordInFlags(final int currentFlags, final boolean isNotAWord) {
+        final int flags;
+        if (isNotAWord) {
+            flags = currentFlags | FormatSpec.FLAG_IS_NOT_A_WORD;
+        } else {
+            flags = currentFlags & (~FormatSpec.FLAG_IS_NOT_A_WORD);
+        }
+        return flags;
+    }
+
+    private int setIsBlackListEntryInFlags(final int currentFlags, final boolean isBlackListEntry) {
+        final int flags;
+        if (isBlackListEntry) {
+            flags = currentFlags | FormatSpec.FLAG_IS_BLACKLISTED;
+        } else {
+            flags = currentFlags & (~FormatSpec.FLAG_IS_BLACKLISTED);
+        }
+        return flags;
+    }
+
+    /**
+     * Splits a PtNode.
+     *
+     *  abcd - ef
+     *
+     * -> inserting "abc"
+     *
+     *  abc - d - ef
+     *
+     * @param nodeArrayToSplitPos the position of PtNodeArray which contains the PtNode to split.
+     * @param nodeToSplitPos the position of the PtNode to split.
+     * @param nodeToSplitInfo the information of the PtNode to split.
+     * @param indexToSplit the index where to split in the code points array.
+     * @param parentOfNodeToSplitPos the absolute position of a parent of the node to split.
+     * @param newTerminalId the terminal id of the inserted node (corresponds to "d").
+     * @param hasShortcuts whether the inserted word should have shortcuts.
+     * @param hasBigrams whether the inserted word should have bigrams.
+     * @param isNotAWord whether the inserted word should be not a word.
+     * @param isBlackListEntry whether the inserted word should be a black list entry.
+     * @param formatOptions the format options.
+     */
+    private void splitOnly(final int nodeArrayToSplitPos, final int nodeToSplitPos,
+            final Ver4PtNodeInfo nodeToSplitInfo, final int indexToSplit,
+            final int parentOfNodeToSplitPos, final int newTerminalId, final boolean hasShortcuts,
+            final boolean hasBigrams, final boolean isNotAWord, final boolean isBlackListEntry,
+            final FormatOptions formatOptions) throws IOException {
+        final int parentNodeArrayStartPos = mDictBuffer.limit();
+        final int parentNodeStartPos = parentNodeArrayStartPos + 1 /* size of PtNodeCount */;
+        final int parentFlags = BinaryDictEncoderUtils.makePtNodeFlags(indexToSplit > 1,
+                true /* isTerminal */, FormatSpec.FLAG_IS_NOT_MOVED, hasShortcuts, hasBigrams,
+                isNotAWord, isBlackListEntry, formatOptions);
+        final Ver4PtNodeInfo parentInfo = new Ver4PtNodeInfo(parentFlags,
+                nodeToSplitInfo.mCharacters, newTerminalId, parentNodeStartPos
+                        + computePtNodeSize(nodeToSplitInfo.mCharacters, 0, indexToSplit, true)
+                        + FormatSpec.FORWARD_LINK_ADDRESS_SIZE,
+                parentOfNodeToSplitPos, 0 /* nodeSize */);
+        parentInfo.mStartIndexOfCharacters = 0;
+        parentInfo.mEndIndexOfCharacters = indexToSplit;
+
+        // Write the child.
+        final int childrenFlags = setMultipleCharsInFlags(nodeToSplitInfo.mFlags,
+                nodeToSplitInfo.mCharacters.length - indexToSplit > 1);
+        final Ver4PtNodeInfo childrenInfo = new Ver4PtNodeInfo(childrenFlags,
+                nodeToSplitInfo.mCharacters, nodeToSplitInfo.mTerminalId,
+                nodeToSplitInfo.mChildrenPos, parentNodeStartPos, 0 /* nodeSize */);
+        childrenInfo.mStartIndexOfCharacters = indexToSplit;
+        childrenInfo.mEndIndexOfCharacters = nodeToSplitInfo.mCharacters.length;
+        if (nodeToSplitInfo.mChildrenPos != FormatSpec.NO_CHILDREN_ADDRESS) {
+            updateParentPositions(nodeToSplitInfo.mChildrenPos,
+                    parentInfo.mChildrenPos + 1 /* size of PtNodeCount */, formatOptions);
+        }
+
+        writeSplittedPtNodes(nodeArrayToSplitPos, nodeToSplitPos, parentInfo,
+                new Ver4PtNodeInfo[] { childrenInfo }, parentNodeArrayStartPos, formatOptions);
+    }
+
+    /**
+     * Split and branch a PtNode.
+     *
+     *   ab - cd
+     *
+     * -> inserting "ac"
+     *
+     * a - b - cd
+     *   |
+     *   - c
+     *
+     * @param nodeArrayToSplitPos the position of PtNodeArray which contains the PtNode to split.
+     * @param nodeToSplitPos the position of the PtNode to split.
+     * @param nodeToSplitInfo the information of the PtNode to split.
+     * @param indexToSplit the index where to split in the code points array.
+     * @param parentOfNodeToSplitPos the absolute position of parent of the node to split.
+     * @param newWordSuffixCodePoints the suffix of the newly inserted word (corresponds to "c").
+     * @param startIndexOfNewWordSuffixCodePoints the start index in newWordSuffixCodePoints where
+     * the suffix starts.
+     * @param newTerminalId the terminal id of the inserted node (correspond to "c").
+     * @param hasShortcuts whether the inserted word should have shortcuts.
+     * @param hasBigrams whether the inserted word should have bigrams.
+     * @param isNotAWord whether the inserted word should be not a word.
+     * @param isBlackListEntry whether the inserted word should be a black list entry.
+     * @param formatOptions the format options.
+     */
+    private void splitAndBranch(final int nodeArrayToSplitPos, final int nodeToSplitPos,
+            final Ver4PtNodeInfo nodeToSplitInfo, final int indexToSplit,
+            final int parentOfNodeToSplitPos, final int[] newWordSuffixCodePoints,
+            final int startIndexOfNewWordSuffixCodePoints,
+            final int newTerminalId,
+            final boolean hasShortcuts, final boolean hasBigrams, final boolean isNotAWord,
+            final boolean isBlackListEntry, final FormatOptions formatOptions) throws IOException {
+        final int parentNodeArrayStartPos = mDictBuffer.limit();
+        final int parentNodeStartPos = parentNodeArrayStartPos + 1 /* size of PtNodeCount */;
+        final int parentFlags = BinaryDictEncoderUtils.makePtNodeFlags(
+                indexToSplit > 1,
+                false /* isTerminal */, FormatSpec.FLAG_IS_NOT_MOVED,
+                false /* hasShortcut */, false /* hasBigrams */,
+                false /* isNotAWord */, false /* isBlackListEntry */, formatOptions);
+        final Ver4PtNodeInfo parentInfo = new Ver4PtNodeInfo(parentFlags,
+                nodeToSplitInfo.mCharacters, PtNode.NOT_A_TERMINAL,
+                parentNodeStartPos
+                        + computePtNodeSize(nodeToSplitInfo.mCharacters, 0, indexToSplit, false)
+                        + FormatSpec.FORWARD_LINK_ADDRESS_SIZE,
+                parentOfNodeToSplitPos, 0 /* nodeSize */);
+        parentInfo.mStartIndexOfCharacters = 0;
+        parentInfo.mEndIndexOfCharacters = indexToSplit;
+
+        final int childrenNodeArrayStartPos = parentNodeStartPos
+                + computePtNodeSize(nodeToSplitInfo.mCharacters, 0, indexToSplit, false)
+                + FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
+        final int firstChildrenFlags = BinaryDictEncoderUtils.makePtNodeFlags(
+                newWordSuffixCodePoints.length - startIndexOfNewWordSuffixCodePoints > 1,
+                true /* isTerminal */, FormatSpec.FLAG_IS_NOT_MOVED, hasShortcuts, hasBigrams,
+                isNotAWord, isBlackListEntry, formatOptions);
+        final Ver4PtNodeInfo firstChildrenInfo = new Ver4PtNodeInfo(firstChildrenFlags,
+                newWordSuffixCodePoints, newTerminalId,
+                FormatSpec.NO_CHILDREN_ADDRESS, parentNodeStartPos,
+                0 /* nodeSize */);
+        firstChildrenInfo.mStartIndexOfCharacters = startIndexOfNewWordSuffixCodePoints;
+        firstChildrenInfo.mEndIndexOfCharacters = newWordSuffixCodePoints.length;
+
+        final int secondChildrenStartPos = childrenNodeArrayStartPos + 1 /* size of ptNodeCount */
+                + computePtNodeSize(newWordSuffixCodePoints, startIndexOfNewWordSuffixCodePoints,
+                        newWordSuffixCodePoints.length, true /* isTerminal */);
+        final int secondChildrenFlags = setMultipleCharsInFlags(nodeToSplitInfo.mFlags,
+                nodeToSplitInfo.mCharacters.length - indexToSplit > 1);
+        final Ver4PtNodeInfo secondChildrenInfo = new Ver4PtNodeInfo(secondChildrenFlags,
+                nodeToSplitInfo.mCharacters, nodeToSplitInfo.mTerminalId,
+                nodeToSplitInfo.mChildrenPos, parentNodeStartPos, 0 /* nodeSize */);
+        secondChildrenInfo.mStartIndexOfCharacters = indexToSplit;
+        secondChildrenInfo.mEndIndexOfCharacters = nodeToSplitInfo.mCharacters.length;
+        if (nodeToSplitInfo.mChildrenPos != FormatSpec.NO_CHILDREN_ADDRESS) {
+            updateParentPositions(nodeToSplitInfo.mChildrenPos, secondChildrenStartPos,
+                    formatOptions);
+        }
+
+        writeSplittedPtNodes(nodeArrayToSplitPos, nodeToSplitPos, parentInfo,
+                new Ver4PtNodeInfo[] { firstChildrenInfo, secondChildrenInfo },
+                parentNodeArrayStartPos, formatOptions);
+    }
+
+    /**
+     * Inserts a word into the trie file and returns the position of inserted terminal node.
+     * If the insertion is failed, returns FormatSpec.NOT_VALID_WORD.
+     */
+    @UsedForTesting
+    private int insertWordToTrie(final String word, final int newTerminalId,
+            final boolean isNotAWord, final boolean isBlackListEntry, final boolean hasBigrams,
+            final boolean hasShortcuts) throws IOException, UnsupportedFormatException {
+        setPosition(0);
+        final FileHeader header = readHeader();
+
+        final int[] codePoints = FusionDictionary.getCodePoints(word);
+        final int wordLen = codePoints.length;
+
+        int wordPos = 0;
+        for (int depth = 0; depth < FormatSpec.MAX_WORD_LENGTH; /* nop */) {
+            final int nodeArrayPos = getPosition();
+            final int ptNodeCount = readPtNodeCount();
+            boolean goToChildren = false;
+            int parentPos = FormatSpec.NO_PARENT_ADDRESS;
+            for (int i = 0; i < ptNodeCount; ++i) {
+                final int nodePos = getPosition();
+                final Ver4PtNodeInfo nodeInfo = readVer4PtNodeInfo(nodePos, header.mFormatOptions);
+                if (BinaryDictIOUtils.isMovedPtNode(nodeInfo.mFlags, header.mFormatOptions)) {
+                    continue;
+                }
+                if (nodeInfo.mParentPos != FormatSpec.NO_PARENT_ADDRESS) {
+                    parentPos = nodePos + nodeInfo.mParentPos;
+                }
+
+                final boolean firstCharacterMatched =
+                        codePoints[wordPos] == nodeInfo.mCharacters[0];
+                boolean allCharactersMatched = true;
+                int firstDifferentCharacterIndex = -1;
+                for (int p = 0; p < nodeInfo.mCharacters.length; ++p) {
+                    if (wordPos + p >= codePoints.length) break;
+                    if (codePoints[wordPos + p] != nodeInfo.mCharacters[p]) {
+                        if (firstDifferentCharacterIndex == -1) {
+                            firstDifferentCharacterIndex = p;
+                        }
+                        allCharactersMatched = false;
+                    }
+                }
+
+                if (!firstCharacterMatched) {
+                    // Go to the next sibling node.
+                    continue;
+                }
+
+                if (!allCharactersMatched) {
+                    final int parentNodeArrayStartPos = mDictBuffer.limit();
+                    splitAndBranch(nodeArrayPos, nodePos, nodeInfo, firstDifferentCharacterIndex,
+                            parentPos, codePoints, wordPos + firstDifferentCharacterIndex,
+                            newTerminalId, hasShortcuts, hasBigrams, isNotAWord,
+                            isBlackListEntry, header.mFormatOptions);
+
+                    return parentNodeArrayStartPos + computePtNodeSize(codePoints, wordPos,
+                            wordPos + firstDifferentCharacterIndex, false)
+                            + FormatSpec.FORWARD_LINK_ADDRESS_SIZE + 1 /* size of PtNodeCount */;
+                }
+
+                if (wordLen - wordPos < nodeInfo.mCharacters.length) {
+                    final int parentNodeArrayStartPos = mDictBuffer.limit();
+                    splitOnly(nodeArrayPos, nodePos, nodeInfo, wordLen - wordPos, parentPos,
+                            newTerminalId, hasShortcuts, hasBigrams, isNotAWord, isBlackListEntry,
+                            header.mFormatOptions);
+
+                    // Return the position of the inserted word.
+                    return parentNodeArrayStartPos + 1 /* size of PtNodeCount */;
+                }
+
+                wordPos += nodeInfo.mCharacters.length;
+                if (wordPos == wordLen) {
+                    // This dictionary already contains the word.
+                    Log.e(TAG, "Something went wrong. If the word is already contained, "
+                            + " there is no need to insert new PtNode.");
+                    return FormatSpec.NOT_VALID_WORD;
+                }
+                if (nodeInfo.mChildrenPos == FormatSpec.NO_CHILDREN_ADDRESS) {
+                    // There are no children.
+                    // We need to add a new node as a child of this node.
+                    final int newNodeArrayPos = mDictBuffer.limit();
+                    final int[] newNodeCodePoints = Arrays.copyOfRange(codePoints, wordPos,
+                            codePoints.length);
+                    writeNewSinglePtNodeWithAttributes(newNodeCodePoints, hasShortcuts,
+                            newTerminalId, hasBigrams, isNotAWord, isBlackListEntry, nodePos,
+                            header.mFormatOptions);
+                    updateChildrenPos(nodePos, newNodeArrayPos, header.mFormatOptions);
+                    return newNodeArrayPos + 1 /* size of PtNodeCount */;
+                } else {
+                    // Found the matched node.
+                    // Go to the children of this node.
+                    setPosition(nodeInfo.mChildrenPos);
+                    goToChildren = true;
+                    depth++;
+                    break;
+                }
+            }
+
+            if (goToChildren) continue;
+            if (!readAndFollowForwardLink()) {
+                // Add a new node that contains [wordPos, word.length()-1].
+                // and update the forward link.
+                final int newNodeArrayPos = mDictBuffer.limit();
+                final int[] newCodePoints = Arrays.copyOfRange(codePoints, wordPos,
+                        codePoints.length);
+                writeNewSinglePtNodeWithAttributes(newCodePoints, hasShortcuts, newTerminalId,
+                        hasBigrams, isNotAWord, isBlackListEntry, parentPos, header.mFormatOptions);
+                updateForwardLink(nodeArrayPos, newNodeArrayPos, header.mFormatOptions);
+                return newNodeArrayPos + 1 /* size of PtNodeCount */;
+            }
+        }
+        return FormatSpec.NOT_VALID_WORD;
+    }
+
+    private void updateFrequency(final int terminalId, final int frequency) {
+        mFrequencyBuffer.position(terminalId * FormatSpec.FREQUENCY_AND_FLAGS_SIZE);
+        BinaryDictEncoderUtils.writeUIntToDictBuffer(mFrequencyBuffer, frequency,
+                FormatSpec.FREQUENCY_AND_FLAGS_SIZE);
+    }
+
+    private void insertFrequency(final int frequency) throws IOException {
+        final OutputStream frequencyStream = new FileOutputStream(mFrequencyFile,
+                true /* append */);
+        BinaryDictEncoderUtils.writeUIntToStream(frequencyStream, frequency,
+                FormatSpec.FREQUENCY_AND_FLAGS_SIZE);
+        frequencyStream.close();
+    }
+
+    private void insertTerminalPosition(final int posOfTerminal) throws IOException {
+        final OutputStream terminalPosStream = new FileOutputStream(
+                getFile(FILETYPE_TERMINAL_ADDRESS_TABLE), true /* append */);
+        BinaryDictEncoderUtils.writeUIntToStream(terminalPosStream, posOfTerminal,
+                FormatSpec.TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE);
+        terminalPosStream.close();
+    }
+
+    private void insertBigrams(final int terminalId, final int frequency,
+            final ArrayList<PendingAttribute> bigramAddresses)
+                    throws IOException, UnsupportedFormatException {
+        openDictBuffer();
+        final BigramContentUpdater updater = new BigramContentUpdater(mDictDirectory.getName(),
+                mDictDirectory, false);
+
+        // Convert addresses to terminal ids.
+        final ArrayList<PendingAttribute> bigrams = CollectionUtils.newArrayList();
+        mDictBuffer.position(0);
+        final FileHeader header = readHeader();
+        for (PendingAttribute attr : bigramAddresses) {
+            mDictBuffer.position(attr.mAddress);
+            final Ver4PtNodeInfo info = readVer4PtNodeInfo(attr.mAddress, header.mFormatOptions);
+            if (info.mTerminalId == PtNode.NOT_A_TERMINAL) {
+                throw new RuntimeException("We can't have a bigram target that's not a terminal.");
+            }
+            bigrams.add(new PendingAttribute(frequency, info.mTerminalId));
+        }
+        updater.insertBigramEntries(terminalId, frequency, bigrams);
+        close();
+    }
+
+    private void insertShortcuts(final int terminalId, final ArrayList<WeightedString> shortcuts)
+            throws IOException {
+        final ShortcutContentUpdater updater = new ShortcutContentUpdater(mDictDirectory.getName(),
+                mDictDirectory);
+        updater.insertShortcuts(terminalId, shortcuts);
+    }
+
+    private void openBuffersAndStream() throws IOException {
+        openDictBuffer();
+        mDictStream = new FileOutputStream(getFile(FILETYPE_TRIE), true /* append */);
+    }
+
+    private void close() throws IOException {
+        if (mDictStream != null) {
+            mDictStream.close();
+            mDictStream = null;
+        }
+        mDictBuffer = null;
+        mFrequencyBuffer = null;
+        mTerminalAddressTableBuffer = null;
+    }
+
+    private void updateAttributes(final int posOfWord, final int frequency,
+            final ArrayList<WeightedString> bigramStrings,
+            final ArrayList<WeightedString> shortcuts, final boolean isNotAWord,
+            final boolean isBlackListEntry) throws IOException, UnsupportedFormatException {
+        mDictBuffer.position(0);
+        final FileHeader header = readHeader();
+        mDictBuffer.position(posOfWord);
+        final Ver4PtNodeInfo info = readVer4PtNodeInfo(posOfWord, header.mFormatOptions);
+        final int terminalId = info.mTerminalId;
+
+        // Update the flags.
+        final int newFlags = setIsNotAWordInFlags(
+                setIsBlackListEntryInFlags(info.mFlags, isBlackListEntry), isNotAWord);
+        mDictBuffer.position(posOfWord);
+        mDictBuffer.put((byte) newFlags);
+
+        updateFrequency(terminalId, frequency);
+        insertBigrams(terminalId, frequency,
+                DynamicBinaryDictIOUtils.resolveBigramPositions(this, bigramStrings));
+        insertShortcuts(terminalId, shortcuts);
+    }
+
+    @Override @UsedForTesting
     public void insertWord(final String word, final int frequency,
         final ArrayList<WeightedString> bigramStrings, final ArrayList<WeightedString> shortcuts,
         final boolean isNotAWord, final boolean isBlackListEntry)
                 throws IOException, UnsupportedFormatException {
-        // TODO: Implement this method.
+        final int newTerminalId = getNewTerminalId();
+
+        openBuffersAndStream();
+        final int posOfWord = getTerminalPosition(word);
+        if (posOfWord != FormatSpec.NOT_VALID_WORD) {
+            // The word is already contained in the dictionary.
+            updateAttributes(posOfWord, frequency, bigramStrings, shortcuts, isNotAWord,
+                    isBlackListEntry);
+            close();
+            return;
+        }
+
+        // Insert new PtNode into trie.
+        final int posOfTerminal = insertWordToTrie(word, newTerminalId, isNotAWord,
+                isBlackListEntry, bigramStrings != null && !bigramStrings.isEmpty(),
+                shortcuts != null && !shortcuts.isEmpty());
+        insertFrequency(frequency);
+        insertTerminalPosition(posOfTerminal);
+        close();
+
+        insertBigrams(newTerminalId, frequency,
+                DynamicBinaryDictIOUtils.resolveBigramPositions(this, bigramStrings));
+        insertShortcuts(newTerminalId, shortcuts);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
index 1de15a3..c817d3e 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
@@ -46,6 +46,7 @@
     private static final String TAG = DecayingExpandableBinaryDictionaryBase.class.getSimpleName();
     public static final boolean DBG_SAVE_RESTORE = false;
     private static final boolean DBG_STRESS_TEST = false;
+    private static final boolean DBG_DUMP_ON_CLOSE = false;
     private static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG;
 
     /** Any pair being typed or picked */
@@ -82,6 +83,9 @@
 
     @Override
     public void close() {
+        if (DBG_DUMP_ON_CLOSE) {
+            dumpAllWordsForDebug();
+        }
         if (!ExpandableBinaryDictionary.ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE) {
             closeBinaryDictionary();
         }
@@ -222,6 +226,55 @@
     }
 
     @UsedForTesting
+    public void dumpAllWordsForDebug() {
+        runAfterGcForDebug(new Runnable() {
+            @Override
+            public void run() {
+                dumpAllWordsForDebugLocked();
+            }
+        });
+    }
+
+    private void dumpAllWordsForDebugLocked() {
+        Log.d(TAG, "dumpAllWordsForDebug started.");
+        final OnAddWordListener listener = new OnAddWordListener() {
+            @Override
+            public void setUnigram(final String word, final String shortcutTarget,
+                    final int frequency, final int shortcutFreq) {
+                Log.d(TAG, "load unigram: " + word + "," + frequency);
+            }
+
+            @Override
+            public void setBigram(final String word0, final String word1, final int frequency) {
+                if (word0.length() < Constants.DICTIONARY_MAX_WORD_LENGTH
+                        && word1.length() < Constants.DICTIONARY_MAX_WORD_LENGTH) {
+                    Log.d(TAG, "load bigram: " + word0 + "," + word1 + "," + frequency);
+                } else {
+                    Log.d(TAG, "Skip inserting a too long bigram: " + word0 + "," + word1 + ","
+                            + frequency);
+                }
+            }
+        };
+
+        // Load the dictionary from binary file
+        final File dictFile = new File(mContext.getFilesDir(), mFileName);
+        final DictDecoder dictDecoder = FormatSpec.getDictDecoder(dictFile,
+                DictDecoder.USE_BYTEARRAY);
+        if (dictDecoder == null) {
+            // This is an expected condition: we don't have a user history dictionary for this
+            // language yet. It will be created sometime later.
+            return;
+        }
+
+        try {
+            dictDecoder.openDictBuffer();
+            UserHistoryDictIOUtils.readDictionaryBinary(dictDecoder, listener);
+        } catch (IOException e) {
+            Log.d(TAG, "IOException on opening a bytebuffer", e);
+        }
+    }
+
+    @UsedForTesting
     public void clearAndFlushDictionary() {
         // Clear the node structure on memory
         clear();
diff --git a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
index d87f6f3..fdfabbd 100644
--- a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
@@ -23,6 +23,7 @@
 
 import android.os.Build;
 import android.text.TextUtils;
+import android.util.Log;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
@@ -32,6 +33,8 @@
 import java.util.ArrayList;
 
 public final class AdditionalSubtypeUtils {
+    private static final String TAG = AdditionalSubtypeUtils.class.getSimpleName();
+
     private static final InputMethodSubtype[] EMPTY_SUBTYPE_ARRAY = new InputMethodSubtype[0];
 
     private AdditionalSubtypeUtils() {
@@ -43,6 +46,11 @@
     }
 
     private static final String LOCALE_AND_LAYOUT_SEPARATOR = ":";
+    private static final int INDEX_OF_LOCALE = 0;
+    private static final int INDEX_OF_KEYBOARD_LAYOUT = 1;
+    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_WITH_EXTRA_VALUE = (INDEX_OF_EXTRA_VALUE + 1);
     private static final String PREF_SUBTYPE_SEPARATOR = ";";
 
     public static InputMethodSubtype createAdditionalSubtype(final String localeString,
@@ -79,17 +87,6 @@
                 : basePrefSubtype + LOCALE_AND_LAYOUT_SEPARATOR + extraValue;
     }
 
-    public static InputMethodSubtype createAdditionalSubtype(final String prefSubtype) {
-        final String elems[] = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR);
-        if (elems.length < 2 || elems.length > 3) {
-            throw new RuntimeException("Unknown additional subtype specified: " + prefSubtype);
-        }
-        final String localeString = elems[0];
-        final String keyboardLayoutSetName = elems[1];
-        final String extraValue = (elems.length == 3) ? elems[2] : null;
-        return createAdditionalSubtype(localeString, keyboardLayoutSetName, extraValue);
-    }
-
     public static InputMethodSubtype[] createAdditionalSubtypesArray(final String prefSubtypes) {
         if (TextUtils.isEmpty(prefSubtypes)) {
             return EMPTY_SUBTYPE_ARRAY;
@@ -98,7 +95,19 @@
         final ArrayList<InputMethodSubtype> subtypesList =
                 CollectionUtils.newArrayList(prefSubtypeArray.length);
         for (final String prefSubtype : prefSubtypeArray) {
-            final InputMethodSubtype subtype = createAdditionalSubtype(prefSubtype);
+            final String elems[] = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR);
+            if (elems.length != LENGTH_WITHOUT_EXTRA_VALUE
+                    && elems.length != LENGTH_WITH_EXTRA_VALUE) {
+                Log.w(TAG, "Unknown additional subtype specified: " + prefSubtype + " in "
+                        + prefSubtypes);
+                continue;
+            }
+            final String localeString = elems[INDEX_OF_LOCALE];
+            final String keyboardLayoutSetName = elems[INDEX_OF_KEYBOARD_LAYOUT];
+            final String extraValue = (elems.length == LENGTH_WITH_EXTRA_VALUE)
+                    ? elems[INDEX_OF_EXTRA_VALUE] : null;
+            final InputMethodSubtype subtype = createAdditionalSubtype(
+                    localeString, keyboardLayoutSetName, extraValue);
             if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT) {
                 // Skip unknown keyboard layout subtype. This may happen when predefined keyboard
                 // layout has been removed.
diff --git a/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java b/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
index 08a2a8c..e521ec8 100644
--- a/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
@@ -62,4 +62,22 @@
         }
         return "";
     }
+
+    /**
+     * A utility method to get the application's PackageInfo.versionCode
+     * @return the application's PackageInfo.versionCode
+     */
+    public static int getVersionCode(final Context context) {
+        try {
+            if (context == null) {
+                return 0;
+            }
+            final String packageName = context.getPackageName();
+            final PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
+            return info.versionCode;
+        } catch (final NameNotFoundException e) {
+            Log.e(TAG, "Could not find version info.", e);
+        }
+        return 0;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index 102a41b..fdbe81a 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -197,7 +197,9 @@
     //  es_US spanish F  Español (EE.UU.)        exception
     //  fr    azerty  F  Français
     //  fr_CA qwerty  F  Français (Canada)
+    //  fr_CH swiss   F  Français (Suisse)
     //  de    qwertz  F  Deutsch
+    //  de_CH swiss   T  Deutsch (Schweiz)
     //  zz    qwerty  F  No language (QWERTY)    in system locale
     //  fr    qwertz  T  Français (QWERTZ)
     //  de    qwerty  T  Deutsch (QWERTY)
@@ -298,7 +300,9 @@
     //  es_US spanish F  Es  Español   Español (EE.UU.)       exception
     //  fr    azerty  F  Fr  Français  Français
     //  fr_CA qwerty  F  Fr  Français  Français (Canada)
+    //  fr_CH swiss   F  Fr  Français  Français (Suisse)
     //  de    qwertz  F  De  Deutsch   Deutsch
+    //  de_CH swiss   T  De  Deutsch   Deutsch (Schweiz)
     //  zz    qwerty  F      QWERTY    QWERTY
     //  fr    qwertz  T  Fr  Français  Français
     //  de    qwerty  T  De  Deutsch   Deutsch
diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
index 635afe7..7b81e8b 100644
--- a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
@@ -70,6 +70,7 @@
     /**
      * Writes dictionary to file.
      */
+    @UsedForTesting
     public static void writeDictionary(final DictEncoder dictEncoder,
             final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams,
             final FormatOptions formatOptions) {
diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
index 1992b2f..677035e 100644
--- a/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
@@ -20,6 +20,9 @@
 
 import java.util.concurrent.TimeUnit;
 
+import com.android.inputmethod.annotations.UsedForTesting;
+
+@UsedForTesting
 public final class UserHistoryForgettingCurveUtils {
     private static final String TAG = UserHistoryForgettingCurveUtils.class.getSimpleName();
     private static final boolean DEBUG = false;
@@ -118,18 +121,22 @@
         }
     }
 
+    @UsedForTesting
     /* package */ static  int fcToElapsedTime(byte fc) {
         return fc & 0x0F;
     }
 
+    @UsedForTesting
     /* package */ static int fcToCount(byte fc) {
         return (fc >> 4) & 0x03;
     }
 
+    @UsedForTesting
     /* package */ static int fcToLevel(byte fc) {
         return (fc >> 6) & 0x03;
     }
 
+    @UsedForTesting
     private static int calcFreq(int elapsedTime, int count, int level) {
         if (level <= 0) {
             // Reserved words, just return -1
@@ -158,6 +165,7 @@
         return calcFreq(elapsedTime, count, level);
     }
 
+    @UsedForTesting
     public static byte pushElapsedTime(byte fc) {
         int elapsedTime = fcToElapsedTime(fc);
         int count = fcToCount(fc);
@@ -173,6 +181,7 @@
         return calcFc(elapsedTime, count, level);
     }
 
+    @UsedForTesting
     public static byte pushCount(byte fc, boolean isValid) {
         final int elapsedTime = fcToElapsedTime(fc);
         int count = fcToCount(fc);
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index da9c611..ad50992 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -102,10 +102,6 @@
             && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
     private static final boolean DEBUG_REPLAY_AFTER_FEEDBACK = false
             && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
-    // Whether the TextView contents are logged at the end of the session.  true will disclose
-    // private info.
-    private static final boolean LOG_FULL_TEXTVIEW_CONTENTS = false
-            && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
     // Whether the feedback dialog preserves the editable text across invocations.  Should be false
     // for normal research builds so users do not have to delete the same feedback string they
     // entered earlier.  Should be true for builds internal to a development team so when the text
@@ -168,9 +164,6 @@
     // U+E001 is in the "private-use area"
     /* package for test */ static final String WORD_REPLACEMENT_STRING = "\uE001";
     protected static final int SUSPEND_DURATION_IN_MINUTES = 1;
-    // set when LatinIME should ignore an onUpdateSelection() callback that
-    // arises from operations in this class
-    private static boolean sLatinIMEExpectingUpdateSelection = false;
 
     // used to check whether words are not unique
     private Suggest mSuggest;
@@ -1126,12 +1119,6 @@
                 new Object[] { applicationSpecifiedCompletions });
     }
 
-    public static boolean getAndClearLatinIMEExpectingUpdateSelection() {
-        boolean returnValue = sLatinIMEExpectingUpdateSelection;
-        sLatinIMEExpectingUpdateSelection = false;
-        return returnValue;
-    }
-
     /**
      * The IME is finishing; it is either being destroyed, or is about to be hidden.
      *
@@ -1149,51 +1136,12 @@
         // if called from finishViews(), which is called from hideWindow() and onDestroy().  These
         // are the situations in which we want to finish up the researchLog.
         if (ic != null && !finishingInput) {
-            final boolean isTextTruncated;
-            final String text;
-            if (LOG_FULL_TEXTVIEW_CONTENTS) {
-                // Capture the TextView contents.  This will trigger onUpdateSelection(), so we
-                // set sLatinIMEExpectingUpdateSelection so that when onUpdateSelection() is called,
-                // it can tell that it was generated by the logging code, and not by the user, and
-                // therefore keep user-visible state as is.
-                ic.beginBatchEdit();
-                ic.performContextMenuAction(android.R.id.selectAll);
-                CharSequence charSequence = ic.getSelectedText(0);
-                if (savedSelectionStart != -1 && savedSelectionEnd != -1) {
-                    ic.setSelection(savedSelectionStart, savedSelectionEnd);
-                }
-                ic.endBatchEdit();
-                sLatinIMEExpectingUpdateSelection = true;
-                if (TextUtils.isEmpty(charSequence)) {
-                    isTextTruncated = false;
-                    text = "";
-                } else {
-                    if (charSequence.length() > MAX_INPUTVIEW_LENGTH_TO_CAPTURE) {
-                        int length = MAX_INPUTVIEW_LENGTH_TO_CAPTURE;
-                        // do not cut in the middle of a supplementary character
-                        final char c = charSequence.charAt(length - 1);
-                        if (Character.isHighSurrogate(c)) {
-                            length--;
-                        }
-                        final CharSequence truncatedCharSequence = charSequence.subSequence(0,
-                                length);
-                        isTextTruncated = true;
-                        text = truncatedCharSequence.toString();
-                    } else {
-                        isTextTruncated = false;
-                        text = charSequence.toString();
-                    }
-                }
-            } else {
-                isTextTruncated = true;
-                text = "";
-            }
             final ResearchLogger researchLogger = getInstance();
             // Assume that OUTPUT_ENTIRE_BUFFER is only true when we don't care about privacy (e.g.
             // during a live user test), so the normal isPotentiallyPrivate and
             // isPotentiallyRevealing flags do not apply
             researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONFINISHINPUTVIEWINTERNAL,
-                    isTextTruncated, text);
+                    true /* isTextTruncated */, "" /* text */);
             researchLogger.commitCurrentLogUnit();
             getInstance().stop();
         }
@@ -1213,9 +1161,7 @@
     public static void latinIME_onUpdateSelection(final int lastSelectionStart,
             final int lastSelectionEnd, final int oldSelStart, final int oldSelEnd,
             final int newSelStart, final int newSelEnd, final int composingSpanStart,
-            final int composingSpanEnd, final boolean expectingUpdateSelection,
-            final boolean expectingUpdateSelectionFromLogger,
-            final RichInputConnection connection) {
+            final int composingSpanEnd, final RichInputConnection connection) {
         String word = "";
         if (connection != null) {
             TextRange range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1);
@@ -1227,8 +1173,8 @@
         final String scrubbedWord = researchLogger.scrubWord(word);
         researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONUPDATESELECTION, lastSelectionStart,
                 lastSelectionEnd, oldSelStart, oldSelEnd, newSelStart, newSelEnd,
-                composingSpanStart, composingSpanEnd, expectingUpdateSelection,
-                expectingUpdateSelectionFromLogger, scrubbedWord);
+                composingSpanStart, composingSpanEnd, false /* expectingUpdateSelection */,
+                false /* expectingUpdateSelectionFromLogger */, scrubbedWord);
     }
 
     /**
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index ca6a779..35fa352 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -57,6 +57,7 @@
         bloom_filter.cpp \
         dictionary.cpp \
         digraph_utils.cpp \
+        error_type_utils.cpp \
         multi_bigram_map.cpp) \
     $(addprefix suggest/core/layout/, \
         additional_proximity_chars.cpp \
@@ -72,22 +73,33 @@
         header/header_policy.cpp \
         header/header_read_write_utils.cpp \
         shortcut/shortcut_list_reading_utils.cpp \
-        dictionary_structure_with_buffer_policy_factory.cpp \
+        structure/dictionary_structure_with_buffer_policy_factory.cpp) \
+    $(addprefix suggest/policyimpl/dictionary/structure/v2/, \
+        patricia_trie_policy.cpp \
+        patricia_trie_reading_utils.cpp) \
+    $(addprefix suggest/policyimpl/dictionary/structure/v3/, \
         dynamic_patricia_trie_gc_event_listeners.cpp \
         dynamic_patricia_trie_node_reader.cpp \
+        dynamic_patricia_trie_node_writer.cpp \
         dynamic_patricia_trie_policy.cpp \
         dynamic_patricia_trie_reading_helper.cpp \
         dynamic_patricia_trie_reading_utils.cpp \
+        dynamic_patricia_trie_updating_helper.cpp \
         dynamic_patricia_trie_writing_helper.cpp \
-        dynamic_patricia_trie_writing_utils.cpp \
-        patricia_trie_policy.cpp \
-        patricia_trie_reading_utils.cpp) \
+        dynamic_patricia_trie_writing_utils.cpp) \
+    $(addprefix suggest/policyimpl/dictionary/structure/v4/, \
+        ver4_dict_constants.cpp \
+        ver4_patricia_trie_node_reader.cpp \
+        ver4_patricia_trie_policy.cpp \
+        ver4_patricia_trie_reading_utils.cpp ) \
     $(addprefix suggest/policyimpl/dictionary/utils/, \
         buffer_with_extendable_buffer.cpp \
         byte_array_utils.cpp \
         dict_file_writing_utils.cpp \
         forgetting_curve_utils.cpp \
-        format_utils.cpp) \
+        format_utils.cpp \
+        mmapped_buffer.cpp \
+        sparse_table.cpp) \
     suggest/policyimpl/gesture/gesture_suggest_policy_factory.cpp \
     $(addprefix suggest/policyimpl/typing/, \
         scoring_params.cpp \
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 8f21c50..3becc7e 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -25,7 +25,7 @@
 #include "jni_common.h"
 #include "suggest/core/dictionary/dictionary.h"
 #include "suggest/core/suggest_options.h"
-#include "suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.h"
+#include "suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.h"
 #include "suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h"
 #include "utils/autocorrection_threshold_utils.h"
 
@@ -86,11 +86,11 @@
     char sourceDirChars[sourceDirUtf8Length + 1];
     env->GetStringUTFRegion(sourceDir, 0, env->GetStringLength(sourceDir), sourceDirChars);
     sourceDirChars[sourceDirUtf8Length] = '\0';
-    DictionaryStructureWithBufferPolicy *const dictionaryStructureWithBufferPolicy =
+    DictionaryStructureWithBufferPolicy::StructurePoilcyPtr dictionaryStructureWithBufferPolicy(
             DictionaryStructureWithBufferPolicyFactory::newDictionaryStructureWithBufferPolicy(
                     sourceDirChars, static_cast<int>(dictOffset), static_cast<int>(dictSize),
-                    isUpdatable == JNI_TRUE);
-    if (!dictionaryStructureWithBufferPolicy) {
+                    isUpdatable == JNI_TRUE));
+    if (!dictionaryStructureWithBufferPolicy.get()) {
         return 0;
     }
 
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index 742e388..fbcd612 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -392,24 +392,4 @@
     // Create new word with space substitution
     CT_NEW_WORD_SPACE_SUBSTITUTION,
 } CorrectionType;
-
-// ErrorType is mainly decided by CorrectionType but it is also depending on if
-// the correction has really been performed or not.
-typedef enum {
-    // Substitution, omission and transposition
-    ET_EDIT_CORRECTION,
-    // Proximity error
-    ET_PROXIMITY_CORRECTION,
-    // Completion
-    ET_COMPLETION,
-    // New word
-    // TODO: Remove.
-    // A new word error should be an edit correction error or a proximity correction error.
-    ET_NEW_WORD,
-    // Treat error as an intentional omission when the CorrectionType is omission and the node can
-    // be intentional omission.
-    ET_INTENTIONAL_OMISSION,
-    // Not treated as an error. Tracked for checking exact match
-    ET_NOT_AN_ERROR
-} ErrorType;
 #endif // LATINIME_DEFINES_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_node.h b/native/jni/src/suggest/core/dicnode/dic_node.h
index 49cfdec..0b2b4a9 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node.h
@@ -99,7 +99,7 @@
     virtual ~DicNode() {}
 
     // Init for copy
-    void initByCopy(const DicNode *dicNode) {
+    void initByCopy(const DicNode *const dicNode) {
         mIsUsed = true;
         mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
         mDicNodeProperties.init(&dicNode->mDicNodeProperties);
@@ -107,25 +107,25 @@
         PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
     }
 
-    // Init for root with prevWordNodePos which is used for bigram
-    void initAsRoot(const int rootGroupPos, const int prevWordNodePos) {
+    // Init for root with prevWordPtNodePos which is used for bigram
+    void initAsRoot(const int rootPtNodeArrayPos, const int prevWordPtNodePos) {
         mIsUsed = true;
         mIsCachedForNextSuggestion = false;
         mDicNodeProperties.init(
-                NOT_A_DICT_POS /* pos */, rootGroupPos, NOT_A_CODE_POINT /* nodeCodePoint */,
+                NOT_A_DICT_POS /* pos */, rootPtNodeArrayPos, NOT_A_CODE_POINT /* nodeCodePoint */,
                 NOT_A_PROBABILITY /* probability */, false /* isTerminal */,
                 true /* hasChildren */, false /* isBlacklistedOrNotAWord */, 0 /* depth */,
                 0 /* terminalDepth */);
-        mDicNodeState.init(prevWordNodePos);
+        mDicNodeState.init(prevWordPtNodePos);
         PROF_NODE_RESET(mProfiler);
     }
 
     // Init for root with previous word
-    void initAsRootWithPreviousWord(DicNode *dicNode, const int rootGroupPos) {
+    void initAsRootWithPreviousWord(const DicNode *const dicNode, const int rootPtNodeArrayPos) {
         mIsUsed = true;
         mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
         mDicNodeProperties.init(
-                NOT_A_DICT_POS /* pos */, rootGroupPos, NOT_A_CODE_POINT /* nodeCodePoint */,
+                NOT_A_DICT_POS /* pos */, rootPtNodeArrayPos, NOT_A_CODE_POINT /* nodeCodePoint */,
                 NOT_A_PROBABILITY /* probability */, false /* isTerminal */,
                 true /* hasChildren */, false /* isBlacklistedOrNotAWord */,  0 /* depth */,
                 0 /* terminalDepth */);
@@ -138,7 +138,7 @@
         mDicNodeState.mDicNodeStatePrevWord.init(
                 dicNode->mDicNodeState.mDicNodeStatePrevWord.getPrevWordCount() + 1,
                 dicNode->mDicNodeProperties.getProbability(),
-                dicNode->mDicNodeProperties.getPos(),
+                dicNode->mDicNodeProperties.getPtNodePos(),
                 dicNode->mDicNodeState.mDicNodeStatePrevWord.mPrevWord,
                 dicNode->mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength(),
                 dicNode->getOutputWordBuf(),
@@ -148,26 +148,27 @@
         PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
     }
 
-    void initAsPassingChild(DicNode *parentNode) {
+    void initAsPassingChild(DicNode *parentDicNode) {
         mIsUsed = true;
-        mIsCachedForNextSuggestion = parentNode->mIsCachedForNextSuggestion;
-        const int c = parentNode->getNodeTypedCodePoint();
-        mDicNodeProperties.init(&parentNode->mDicNodeProperties, c);
-        mDicNodeState.init(&parentNode->mDicNodeState);
-        PROF_NODE_COPY(&parentNode->mProfiler, mProfiler);
+        mIsCachedForNextSuggestion = parentDicNode->mIsCachedForNextSuggestion;
+        const int parentCodePoint = parentDicNode->getNodeTypedCodePoint();
+        mDicNodeProperties.init(&parentDicNode->mDicNodeProperties, parentCodePoint);
+        mDicNodeState.init(&parentDicNode->mDicNodeState);
+        PROF_NODE_COPY(&parentDicNode->mProfiler, mProfiler);
     }
 
-    void initAsChild(const DicNode *const dicNode, const int pos, const int childrenPos,
-            const int probability, const bool isTerminal, const bool hasChildren,
-            const bool isBlacklistedOrNotAWord, const uint16_t mergedNodeCodePointCount,
-            const int *const mergedNodeCodePoints) {
+    void initAsChild(const DicNode *const dicNode, const int ptNodePos,
+            const int childrenPtNodeArrayPos, const int probability, const bool isTerminal,
+            const bool hasChildren, const bool isBlacklistedOrNotAWord,
+            const uint16_t mergedNodeCodePointCount, const int *const mergedNodeCodePoints) {
         mIsUsed = true;
         uint16_t newDepth = static_cast<uint16_t>(dicNode->getNodeCodePointCount() + 1);
         mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
         const uint16_t newLeavingDepth = static_cast<uint16_t>(
                 dicNode->mDicNodeProperties.getLeavingDepth() + mergedNodeCodePointCount);
-        mDicNodeProperties.init(pos, childrenPos, mergedNodeCodePoints[0], probability,
-                isTerminal, hasChildren, isBlacklistedOrNotAWord, newDepth, newLeavingDepth);
+        mDicNodeProperties.init(ptNodePos, childrenPtNodeArrayPos, mergedNodeCodePoints[0],
+                probability, isTerminal, hasChildren, isBlacklistedOrNotAWord, newDepth,
+                newLeavingDepth);
         mDicNodeState.init(&dicNode->mDicNodeState, mergedNodeCodePointCount,
                 mergedNodeCodePoints);
         PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
@@ -234,7 +235,7 @@
     }
 
     bool isFirstWord() const {
-        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos() == NOT_A_DICT_POS;
+        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordPtNodePos() == NOT_A_DICT_POS;
     }
 
     bool isCompletion(const int inputSize) const {
@@ -246,29 +247,30 @@
     }
 
     // Used to get bigram probability in DicNodeUtils
-    int getPos() const {
-        return mDicNodeProperties.getPos();
+    int getPtNodePos() const {
+        return mDicNodeProperties.getPtNodePos();
     }
 
     // Used to get bigram probability in DicNodeUtils
-    int getPrevWordPos() const {
-        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos();
+    int getPrevWordTerminalPtNodePos() const {
+        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordPtNodePos();
     }
 
     // Used in DicNodeUtils
-    int getChildrenPos() const {
-        return mDicNodeProperties.getChildrenPos();
+    int getChildrenPtNodeArrayPos() const {
+        return mDicNodeProperties.getChildrenPtNodeArrayPos();
     }
 
     int getProbability() const {
         return mDicNodeProperties.getProbability();
     }
 
-    AK_FORCE_INLINE bool isTerminalWordNode() const {
-        const bool isTerminalNodes = mDicNodeProperties.isTerminal();
-        const int currentNodeDepth = getNodeCodePointCount();
-        const int terminalNodeDepth = mDicNodeProperties.getLeavingDepth();
-        return isTerminalNodes && currentNodeDepth > 0 && currentNodeDepth == terminalNodeDepth;
+    AK_FORCE_INLINE bool isTerminalDicNode() const {
+        const bool isTerminalPtNode = mDicNodeProperties.isTerminal();
+        const int currentDicNodeDepth = getNodeCodePointCount();
+        const int terminalDicNodeDepth = mDicNodeProperties.getLeavingDepth();
+        return isTerminalPtNode && currentDicNodeDepth > 0
+                && currentDicNodeDepth == terminalDicNodeDepth;
     }
 
     bool shouldBeFilteredBySafetyNetForBigram() const {
@@ -374,8 +376,8 @@
     }
 
     // Used to commit input partially
-    int getPrevWordNodePos() const {
-        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos();
+    int getPrevWordPtNodePos() const {
+        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordPtNodePos();
     }
 
     AK_FORCE_INLINE const int *getOutputWordBuf() const {
@@ -410,7 +412,7 @@
     // TODO: Remove once touch path is merged into ProximityInfoState
     // Note: Returned codepoint may be a digraph codepoint if the node is in a composite glyph.
     int getNodeCodePoint() const {
-        const int codePoint = mDicNodeProperties.getNodeCodePoint();
+        const int codePoint = mDicNodeProperties.getDicNodeCodePoint();
         const DigraphUtils::DigraphCodePointIndex digraphIndex =
                 mDicNodeState.mDicNodeStateScoring.getDigraphIndex();
         if (digraphIndex == DigraphUtils::NOT_A_DIGRAPH_INDEX) {
@@ -423,8 +425,8 @@
     // Utils for cost calculation //
     ////////////////////////////////
     AK_FORCE_INLINE bool isSameNodeCodePoint(const DicNode *const dicNode) const {
-        return mDicNodeProperties.getNodeCodePoint()
-                == dicNode->mDicNodeProperties.getNodeCodePoint();
+        return mDicNodeProperties.getDicNodeCodePoint()
+                == dicNode->mDicNodeProperties.getDicNodeCodePoint();
     }
 
     // TODO: remove
@@ -574,7 +576,8 @@
     // Caveat: Must not be called outside Weighting
     // This restriction is guaranteed by "friend"
     AK_FORCE_INLINE void addCost(const float spatialCost, const float languageCost,
-            const bool doNormalization, const int inputSize, const ErrorType errorType) {
+            const bool doNormalization, const int inputSize,
+            const ErrorTypeUtils::ErrorType errorType) {
         if (DEBUG_GEO_FULL) {
             LOGI_SHOW_ADD_COST_PROP;
         }
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
index ec65114..5540b6d 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
@@ -22,7 +22,6 @@
 #include "suggest/core/dicnode/dic_node_vector.h"
 #include "suggest/core/dictionary/multi_bigram_map.h"
 #include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
-#include "utils/char_utils.h"
 
 namespace latinime {
 
@@ -32,19 +31,20 @@
 
 /* static */ void DicNodeUtils::initAsRoot(
         const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
-        const int prevWordNodePos, DicNode *const newRootNode) {
-    newRootNode->initAsRoot(dictionaryStructurePolicy->getRootPosition(), prevWordNodePos);
+        const int prevWordPtNodePos, DicNode *const newRootDicNode) {
+    newRootDicNode->initAsRoot(dictionaryStructurePolicy->getRootPosition(), prevWordPtNodePos);
 }
 
 /*static */ void DicNodeUtils::initAsRootWithPreviousWord(
         const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
-        DicNode *const prevWordLastNode, DicNode *const newRootNode) {
-    newRootNode->initAsRootWithPreviousWord(
-            prevWordLastNode, dictionaryStructurePolicy->getRootPosition());
+        const DicNode *const prevWordLastDicNode, DicNode *const newRootDicNode) {
+    newRootDicNode->initAsRootWithPreviousWord(
+            prevWordLastDicNode, dictionaryStructurePolicy->getRootPosition());
 }
 
-/* static */ void DicNodeUtils::initByCopy(DicNode *srcNode, DicNode *destNode) {
-    destNode->initByCopy(srcNode);
+/* static */ void DicNodeUtils::initByCopy(const DicNode *const srcDicNode,
+        DicNode *const destDicNode) {
+    destDicNode->initByCopy(srcDicNode);
 }
 
 ///////////////////////////////////
@@ -52,14 +52,14 @@
 ///////////////////////////////////
 /* static */ void DicNodeUtils::getAllChildDicNodes(DicNode *dicNode,
         const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
-        DicNodeVector *childDicNodes) {
+        DicNodeVector *const childDicNodes) {
     if (dicNode->isTotalInputSizeExceedingLimit()) {
         return;
     }
     if (!dicNode->isLeavingNode()) {
         childDicNodes->pushPassingChild(dicNode);
     } else {
-        dictionaryStructurePolicy->createAndGetAllChildNodes(dicNode, childDicNodes);
+        dictionaryStructurePolicy->createAndGetAllChildDicNodes(dicNode, childDicNodes);
     }
 }
 
@@ -71,11 +71,11 @@
  */
 /* static */ float DicNodeUtils::getBigramNodeImprobability(
         const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
-        const DicNode *const node, MultiBigramMap *multiBigramMap) {
-    if (node->hasMultipleWords() && !node->isValidMultipleWordSuggestion()) {
+        const DicNode *const dicNode, MultiBigramMap *const multiBigramMap) {
+    if (dicNode->hasMultipleWords() && !dicNode->isValidMultipleWordSuggestion()) {
         return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
     }
-    const int probability = getBigramNodeProbability(dictionaryStructurePolicy, node,
+    const int probability = getBigramNodeProbability(dictionaryStructurePolicy, dicNode,
             multiBigramMap);
     // TODO: This equation to calculate the improbability looks unreasonable.  Investigate this.
     const float cost = static_cast<float>(MAX_PROBABILITY - probability)
@@ -85,19 +85,19 @@
 
 /* static */ int DicNodeUtils::getBigramNodeProbability(
         const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
-        const DicNode *const node, MultiBigramMap *multiBigramMap) {
-    const int unigramProbability = node->getProbability();
-    const int wordPos = node->getPos();
-    const int prevWordPos = node->getPrevWordPos();
-    if (NOT_A_DICT_POS == wordPos || NOT_A_DICT_POS == prevWordPos) {
+        const DicNode *const dicNode, MultiBigramMap *const multiBigramMap) {
+    const int unigramProbability = dicNode->getProbability();
+    const int ptNodePos = dicNode->getPtNodePos();
+    const int prevWordTerminalPtNodePos = dicNode->getPrevWordTerminalPtNodePos();
+    if (NOT_A_DICT_POS == ptNodePos || NOT_A_DICT_POS == prevWordTerminalPtNodePos) {
         // Note: Normally wordPos comes from the dictionary and should never equal
         // NOT_A_VALID_WORD_POS.
         return dictionaryStructurePolicy->getProbability(unigramProbability,
                 NOT_A_PROBABILITY);
     }
     if (multiBigramMap) {
-        return multiBigramMap->getBigramProbability(dictionaryStructurePolicy, prevWordPos,
-                wordPos, unigramProbability);
+        return multiBigramMap->getBigramProbability(dictionaryStructurePolicy,
+                prevWordTerminalPtNodePos, ptNodePos, unigramProbability);
     }
     return dictionaryStructurePolicy->getProbability(unigramProbability,
             NOT_A_PROBABILITY);
@@ -109,7 +109,7 @@
 
 // TODO: Move to char_utils?
 /* static */ int DicNodeUtils::appendTwoWords(const int *const src0, const int16_t length0,
-        const int *const src1, const int16_t length1, int *dest) {
+        const int *const src1, const int16_t length1, int *const dest) {
     int actualLength0 = 0;
     for (int i = 0; i < length0; ++i) {
         if (src0[i] == 0) {
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_utils.h b/native/jni/src/suggest/core/dicnode/dic_node_utils.h
index 3fb351a..3f1514a 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_utils.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_utils.h
@@ -31,20 +31,20 @@
 class DicNodeUtils {
  public:
     static int appendTwoWords(const int *src0, const int16_t length0, const int *src1,
-            const int16_t length1, int *dest);
+            const int16_t length1, int *const dest);
     static void initAsRoot(
             const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
-            const int prevWordNodePos, DicNode *newRootNode);
+            const int prevWordPtNodePos, DicNode *const newRootDicNode);
     static void initAsRootWithPreviousWord(
             const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
-            DicNode *prevWordLastNode, DicNode *newRootNode);
-    static void initByCopy(DicNode *srcNode, DicNode *destNode);
+            const DicNode *const prevWordLastDicNode, DicNode *const newRootDicNode);
+    static void initByCopy(const DicNode *const srcDicNode, DicNode *const destDicNode);
     static void getAllChildDicNodes(DicNode *dicNode,
             const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
             DicNodeVector *childDicNodes);
     static float getBigramNodeImprobability(
             const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
-            const DicNode *const node, MultiBigramMap *const multiBigramMap);
+            const DicNode *const dicNode, MultiBigramMap *const multiBigramMap);
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(DicNodeUtils);
@@ -53,7 +53,7 @@
 
     static int getBigramNodeProbability(
             const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
-            const DicNode *const node, MultiBigramMap *multiBigramMap);
+            const DicNode *const dicNode, MultiBigramMap *const multiBigramMap);
 };
 } // namespace latinime
 #endif // LATINIME_DIC_NODE_UTILS_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_vector.h b/native/jni/src/suggest/core/dicnode/dic_node_vector.h
index 42addae..9364e77 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_vector.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_vector.h
@@ -62,14 +62,14 @@
         mDicNodes.back().initAsPassingChild(dicNode);
     }
 
-    void pushLeavingChild(const DicNode *const dicNode, const int pos, const int childrenPos,
-            const int probability, const bool isTerminal, const bool hasChildren,
-            const bool isBlacklistedOrNotAWord, const uint16_t mergedNodeCodePointCount,
-            const int *const mergedNodeCodePoints) {
+    void pushLeavingChild(const DicNode *const dicNode, const int ptNodePos,
+            const int childrenPtNodeArrayPos, const int probability, const bool isTerminal,
+            const bool hasChildren, const bool isBlacklistedOrNotAWord,
+            const uint16_t mergedNodeCodePointCount, const int *const mergedNodeCodePoints) {
         ASSERT(!mLock);
         mDicNodes.push_back(mEmptyNode);
-        mDicNodes.back().initAsChild(dicNode, pos, childrenPos, probability, isTerminal,
-                hasChildren, isBlacklistedOrNotAWord, mergedNodeCodePointCount,
+        mDicNodes.back().initAsChild(dicNode, ptNodePos, childrenPtNodeArrayPos, probability,
+                isTerminal, hasChildren, isBlacklistedOrNotAWord, mergedNodeCodePointCount,
                 mergedNodeCodePoints);
     }
 
diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_properties.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_properties.h
index 9e0f62c..c41a724 100644
--- a/native/jni/src/suggest/core/dicnode/internal/dic_node_properties.h
+++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_properties.h
@@ -24,15 +24,14 @@
 namespace latinime {
 
 /**
- * Node for traversing the lexicon trie.
+ * PtNode information related to the DicNode from the lexicon trie.
  */
-// TODO: Introduce a dictionary node class which has attribute members required to understand the
-// dictionary structure.
 class DicNodeProperties {
  public:
     AK_FORCE_INLINE DicNodeProperties()
-            : mPos(0), mChildrenPos(0), mProbability(0), mNodeCodePoint(0), mIsTerminal(false),
-              mHasChildren(false), mIsBlacklistedOrNotAWord(false), mDepth(0), mLeavingDepth(0) {}
+            : mPtNodePos(0), mChildrenPtNodeArrayPos(0), mProbability(0), mDicNodeCodePoint(0),
+              mIsTerminal(false), mHasChildrenPtNodes(false), mIsBlacklistedOrNotAWord(false),
+              mDepth(0), mLeavingDepth(0) {}
 
     virtual ~DicNodeProperties() {}
 
@@ -40,57 +39,57 @@
     void init(const int pos, const int childrenPos, const int nodeCodePoint, const int probability,
             const bool isTerminal, const bool hasChildren, const bool isBlacklistedOrNotAWord,
             const uint16_t depth, const uint16_t leavingDepth) {
-        mPos = pos;
-        mChildrenPos = childrenPos;
-        mNodeCodePoint = nodeCodePoint;
+        mPtNodePos = pos;
+        mChildrenPtNodeArrayPos = childrenPos;
+        mDicNodeCodePoint = nodeCodePoint;
         mProbability = probability;
         mIsTerminal = isTerminal;
-        mHasChildren = hasChildren;
+        mHasChildrenPtNodes = hasChildren;
         mIsBlacklistedOrNotAWord = isBlacklistedOrNotAWord;
         mDepth = depth;
         mLeavingDepth = leavingDepth;
     }
 
     // Init for copy
-    void init(const DicNodeProperties *const nodeProp) {
-        mPos = nodeProp->mPos;
-        mChildrenPos = nodeProp->mChildrenPos;
-        mNodeCodePoint = nodeProp->mNodeCodePoint;
-        mProbability = nodeProp->mProbability;
-        mIsTerminal = nodeProp->mIsTerminal;
-        mHasChildren = nodeProp->mHasChildren;
-        mIsBlacklistedOrNotAWord = nodeProp->mIsBlacklistedOrNotAWord;
-        mDepth = nodeProp->mDepth;
-        mLeavingDepth = nodeProp->mLeavingDepth;
+    void init(const DicNodeProperties *const dicNodeProp) {
+        mPtNodePos = dicNodeProp->mPtNodePos;
+        mChildrenPtNodeArrayPos = dicNodeProp->mChildrenPtNodeArrayPos;
+        mDicNodeCodePoint = dicNodeProp->mDicNodeCodePoint;
+        mProbability = dicNodeProp->mProbability;
+        mIsTerminal = dicNodeProp->mIsTerminal;
+        mHasChildrenPtNodes = dicNodeProp->mHasChildrenPtNodes;
+        mIsBlacklistedOrNotAWord = dicNodeProp->mIsBlacklistedOrNotAWord;
+        mDepth = dicNodeProp->mDepth;
+        mLeavingDepth = dicNodeProp->mLeavingDepth;
     }
 
     // Init as passing child
-    void init(const DicNodeProperties *const nodeProp, const int codePoint) {
-        mPos = nodeProp->mPos;
-        mChildrenPos = nodeProp->mChildrenPos;
-        mNodeCodePoint = codePoint; // Overwrite the node char of a passing child
-        mProbability = nodeProp->mProbability;
-        mIsTerminal = nodeProp->mIsTerminal;
-        mHasChildren = nodeProp->mHasChildren;
-        mIsBlacklistedOrNotAWord = nodeProp->mIsBlacklistedOrNotAWord;
-        mDepth = nodeProp->mDepth + 1; // Increment the depth of a passing child
-        mLeavingDepth = nodeProp->mLeavingDepth;
+    void init(const DicNodeProperties *const dicNodeProp, const int codePoint) {
+        mPtNodePos = dicNodeProp->mPtNodePos;
+        mChildrenPtNodeArrayPos = dicNodeProp->mChildrenPtNodeArrayPos;
+        mDicNodeCodePoint = codePoint; // Overwrite the node char of a passing child
+        mProbability = dicNodeProp->mProbability;
+        mIsTerminal = dicNodeProp->mIsTerminal;
+        mHasChildrenPtNodes = dicNodeProp->mHasChildrenPtNodes;
+        mIsBlacklistedOrNotAWord = dicNodeProp->mIsBlacklistedOrNotAWord;
+        mDepth = dicNodeProp->mDepth + 1; // Increment the depth of a passing child
+        mLeavingDepth = dicNodeProp->mLeavingDepth;
     }
 
-    int getPos() const {
-        return mPos;
+    int getPtNodePos() const {
+        return mPtNodePos;
     }
 
-    int getChildrenPos() const {
-        return mChildrenPos;
+    int getChildrenPtNodeArrayPos() const {
+        return mChildrenPtNodeArrayPos;
     }
 
     int getProbability() const {
         return mProbability;
     }
 
-    int getNodeCodePoint() const {
-        return mNodeCodePoint;
+    int getDicNodeCodePoint() const {
+        return mDicNodeCodePoint;
     }
 
     uint16_t getDepth() const {
@@ -107,7 +106,7 @@
     }
 
     bool hasChildren() const {
-        return mHasChildren || mDepth != mLeavingDepth;
+        return mHasChildrenPtNodes || mDepth != mLeavingDepth;
     }
 
     bool isBlacklistedOrNotAWord() const {
@@ -118,12 +117,12 @@
     // Caution!!!
     // Use a default copy constructor and an assign operator because shallow copies are ok
     // for this class
-    int mPos;
-    int mChildrenPos;
+    int mPtNodePos;
+    int mChildrenPtNodeArrayPos;
     int mProbability;
-    int mNodeCodePoint;
+    int mDicNodeCodePoint;
     bool mIsTerminal;
-    bool mHasChildren;
+    bool mHasChildrenPtNodes;
     bool mIsBlacklistedOrNotAWord;
     uint16_t mDepth;
     uint16_t mLeavingDepth;
diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h
index b898620..dba5705 100644
--- a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h
+++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h
@@ -30,7 +30,7 @@
  public:
     AK_FORCE_INLINE DicNodeStatePrevWord()
             : mPrevWordCount(0), mPrevWordLength(0), mPrevWordStart(0), mPrevWordProbability(0),
-              mPrevWordNodePos(NOT_A_DICT_POS), mSecondWordFirstInputIndex(NOT_AN_INDEX) {
+              mPrevWordPtNodePos(NOT_A_DICT_POS), mSecondWordFirstInputIndex(NOT_AN_INDEX) {
         memset(mPrevWord, 0, sizeof(mPrevWord));
     }
 
@@ -41,7 +41,7 @@
         mPrevWordCount = 0;
         mPrevWordStart = 0;
         mPrevWordProbability = -1;
-        mPrevWordNodePos = NOT_A_DICT_POS;
+        mPrevWordPtNodePos = NOT_A_DICT_POS;
         mSecondWordFirstInputIndex = NOT_AN_INDEX;
     }
 
@@ -50,7 +50,7 @@
         mPrevWordCount = 0;
         mPrevWordStart = 0;
         mPrevWordProbability = -1;
-        mPrevWordNodePos = prevWordNodePos;
+        mPrevWordPtNodePos = prevWordNodePos;
         mSecondWordFirstInputIndex = NOT_AN_INDEX;
     }
 
@@ -60,7 +60,7 @@
         mPrevWordCount = prevWord->mPrevWordCount;
         mPrevWordStart = prevWord->mPrevWordStart;
         mPrevWordProbability = prevWord->mPrevWordProbability;
-        mPrevWordNodePos = prevWord->mPrevWordNodePos;
+        mPrevWordPtNodePos = prevWord->mPrevWordPtNodePos;
         mSecondWordFirstInputIndex = prevWord->mSecondWordFirstInputIndex;
         memcpy(mPrevWord, prevWord->mPrevWord, prevWord->mPrevWordLength * sizeof(mPrevWord[0]));
     }
@@ -71,7 +71,7 @@
             const int prevWordSecondWordFirstInputIndex, const int lastInputIndex) {
         mPrevWordCount = min(prevWordCount, static_cast<int16_t>(MAX_RESULTS));
         mPrevWordProbability = prevWordProbability;
-        mPrevWordNodePos = prevWordNodePos;
+        mPrevWordPtNodePos = prevWordNodePos;
         int twoWordsLen =
                 DicNodeUtils::appendTwoWords(src0, length0, src1, length1, mPrevWord);
         if (twoWordsLen >= MAX_WORD_LENGTH) {
@@ -116,8 +116,8 @@
         return mPrevWordStart;
     }
 
-    int getPrevWordNodePos() const {
-        return mPrevWordNodePos;
+    int getPrevWordPtNodePos() const {
+        return mPrevWordPtNodePos;
     }
 
     int getPrevWordCodePointAt(const int id) const {
@@ -147,7 +147,7 @@
     int16_t mPrevWordLength;
     int16_t mPrevWordStart;
     int16_t mPrevWordProbability;
-    int mPrevWordNodePos;
+    int mPrevWordPtNodePos;
     int mSecondWordFirstInputIndex;
 };
 } // namespace latinime
diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_scoring.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_scoring.h
index 3c85d0e..74f9eee 100644
--- a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_scoring.h
+++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_scoring.h
@@ -21,6 +21,7 @@
 
 #include "defines.h"
 #include "suggest/core/dictionary/digraph_utils.h"
+#include "suggest/core/dictionary/error_type_utils.h"
 
 namespace latinime {
 
@@ -31,7 +32,7 @@
               mDigraphIndex(DigraphUtils::NOT_A_DIGRAPH_INDEX),
               mEditCorrectionCount(0), mProximityCorrectionCount(0),
               mNormalizedCompoundDistance(0.0f), mSpatialDistance(0.0f), mLanguageDistance(0.0f),
-              mRawLength(0.0f), mExactMatch(true),
+              mRawLength(0.0f), mContainingErrorTypes(ErrorTypeUtils::NOT_AN_ERROR),
               mNormalizedCompoundDistanceAfterFirstWord(MAX_VALUE_FOR_WEIGHTING) {
     }
 
@@ -47,7 +48,7 @@
         mDoubleLetterLevel = NOT_A_DOUBLE_LETTER;
         mDigraphIndex = DigraphUtils::NOT_A_DIGRAPH_INDEX;
         mNormalizedCompoundDistanceAfterFirstWord = MAX_VALUE_FOR_WEIGHTING;
-        mExactMatch = true;
+        mContainingErrorTypes = ErrorTypeUtils::NOT_AN_ERROR;
     }
 
     AK_FORCE_INLINE void init(const DicNodeStateScoring *const scoring) {
@@ -59,34 +60,21 @@
         mRawLength = scoring->mRawLength;
         mDoubleLetterLevel = scoring->mDoubleLetterLevel;
         mDigraphIndex = scoring->mDigraphIndex;
-        mExactMatch = scoring->mExactMatch;
+        mContainingErrorTypes = scoring->mContainingErrorTypes;
         mNormalizedCompoundDistanceAfterFirstWord =
                 scoring->mNormalizedCompoundDistanceAfterFirstWord;
     }
 
     void addCost(const float spatialCost, const float languageCost, const bool doNormalization,
-            const int inputSize, const int totalInputIndex, const ErrorType errorType) {
+            const int inputSize, const int totalInputIndex,
+            const ErrorTypeUtils::ErrorType errorType) {
         addDistance(spatialCost, languageCost, doNormalization, inputSize, totalInputIndex);
-        switch (errorType) {
-            case ET_EDIT_CORRECTION:
-                ++mEditCorrectionCount;
-                mExactMatch = false;
-                break;
-            case ET_PROXIMITY_CORRECTION:
-                ++mProximityCorrectionCount;
-                mExactMatch = false;
-                break;
-            case ET_COMPLETION:
-                mExactMatch = false;
-                break;
-            case ET_NEW_WORD:
-                mExactMatch = false;
-                break;
-            case ET_INTENTIONAL_OMISSION:
-                mExactMatch = false;
-                break;
-            case ET_NOT_AN_ERROR:
-                break;
+        mContainingErrorTypes = mContainingErrorTypes | errorType;
+        if (ErrorTypeUtils::isEditCorrectionError(errorType)) {
+            ++mEditCorrectionCount;
+        }
+        if (ErrorTypeUtils::isProximityCorrectionError(errorType)) {
+            ++mProximityCorrectionCount;
         }
     }
 
@@ -182,7 +170,7 @@
     }
 
     bool isExactMatch() const {
-        return mExactMatch;
+        return ErrorTypeUtils::isExactMatch(mContainingErrorTypes);
     }
 
  private:
@@ -199,7 +187,8 @@
     float mSpatialDistance;
     float mLanguageDistance;
     float mRawLength;
-    bool mExactMatch;
+    // All accumulated error types so far
+    ErrorTypeUtils::ErrorType mContainingErrorTypes;
     float mNormalizedCompoundDistanceAfterFirstWord;
 
     AK_FORCE_INLINE void addDistance(float spatialDistance, float languageDistance,
diff --git a/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp b/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
index 71f4ef6..c2a15a3 100644
--- a/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
@@ -144,7 +144,7 @@
 int BigramDictionary::getBigramListPositionForWord(const int *prevWord, const int prevWordLength,
         const bool forceLowerCaseSearch) const {
     if (0 >= prevWordLength) return NOT_A_DICT_POS;
-    int pos = mDictionaryStructurePolicy->getTerminalNodePositionOfWord(prevWord, prevWordLength,
+    int pos = mDictionaryStructurePolicy->getTerminalPtNodePositionOfWord(prevWord, prevWordLength,
             forceLowerCaseSearch);
     if (NOT_A_DICT_POS == pos) return NOT_A_DICT_POS;
     return mDictionaryStructurePolicy->getBigramsPositionOfPtNode(pos);
@@ -155,7 +155,7 @@
     int pos = getBigramListPositionForWord(word0, length0, false /* forceLowerCaseSearch */);
     // getBigramListPositionForWord returns 0 if this word isn't in the dictionary or has no bigrams
     if (NOT_A_DICT_POS == pos) return NOT_A_PROBABILITY;
-    int nextWordPos = mDictionaryStructurePolicy->getTerminalNodePositionOfWord(word1, length1,
+    int nextWordPos = mDictionaryStructurePolicy->getTerminalPtNodePositionOfWord(word1, length1,
             false /* forceLowerCaseSearch */);
     if (NOT_A_DICT_POS == nextWordPos) return NOT_A_PROBABILITY;
 
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.cpp b/native/jni/src/suggest/core/dictionary/dictionary.cpp
index 59ead18..7b83f27 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/dictionary.cpp
@@ -21,9 +21,7 @@
 #include <stdint.h>
 
 #include "defines.h"
-#include "suggest/core/dictionary/bigram_dictionary.h"
 #include "suggest/core/policy/dictionary_header_structure_policy.h"
-#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
 #include "suggest/core/session/dic_traverse_session.h"
 #include "suggest/core/suggest.h"
 #include "suggest/core/suggest_options.h"
@@ -35,22 +33,15 @@
 
 const int Dictionary::HEADER_ATTRIBUTE_BUFFER_SIZE = 32;
 
-Dictionary::Dictionary(JNIEnv *env,
-        DictionaryStructureWithBufferPolicy *const dictionaryStructureWithBufferPolicy)
+Dictionary::Dictionary(JNIEnv *env, const DictionaryStructureWithBufferPolicy::StructurePoilcyPtr
+        &dictionaryStructureWithBufferPolicy)
         : mDictionaryStructureWithBufferPolicy(dictionaryStructureWithBufferPolicy),
-          mBigramDictionary(new BigramDictionary(mDictionaryStructureWithBufferPolicy)),
+          mBigramDictionary(new BigramDictionary(mDictionaryStructureWithBufferPolicy.get())),
           mGestureSuggest(new Suggest(GestureSuggestPolicyFactory::getGestureSuggestPolicy())),
           mTypingSuggest(new Suggest(TypingSuggestPolicyFactory::getTypingSuggestPolicy())) {
     logDictionaryInfo(env);
 }
 
-Dictionary::~Dictionary() {
-    delete mBigramDictionary;
-    delete mGestureSuggest;
-    delete mTypingSuggest;
-    delete mDictionaryStructureWithBufferPolicy;
-}
-
 int Dictionary::getSuggestions(ProximityInfo *proximityInfo, DicTraverseSession *traverseSession,
         int *xcoordinates, int *ycoordinates, int *times, int *pointerIds, int *inputCodePoints,
         int inputSize, int *prevWordCodePoints, int prevWordLength, int commitPoint,
@@ -60,7 +51,7 @@
     if (suggestOptions->isGesture()) {
         DicTraverseSession::initSessionInstance(
                 traverseSession, this, prevWordCodePoints, prevWordLength, suggestOptions);
-        result = mGestureSuggest->getSuggestions(proximityInfo, traverseSession, xcoordinates,
+        result = mGestureSuggest.get()->getSuggestions(proximityInfo, traverseSession, xcoordinates,
                 ycoordinates, times, pointerIds, inputCodePoints, inputSize, commitPoint, outWords,
                 frequencies, spaceIndices, outputTypes, outputAutoCommitFirstWordConfidence);
         if (DEBUG_DICT) {
@@ -70,7 +61,7 @@
     } else {
         DicTraverseSession::initSessionInstance(
                 traverseSession, this, prevWordCodePoints, prevWordLength, suggestOptions);
-        result = mTypingSuggest->getSuggestions(proximityInfo, traverseSession, xcoordinates,
+        result = mTypingSuggest.get()->getSuggestions(proximityInfo, traverseSession, xcoordinates,
                 ycoordinates, times, pointerIds, inputCodePoints, inputSize, commitPoint,
                 outWords, frequencies, spaceIndices, outputTypes,
                 outputAutoCommitFirstWordConfidence);
@@ -84,11 +75,12 @@
 int Dictionary::getBigrams(const int *word, int length, int *outWords, int *frequencies,
         int *outputTypes) const {
     if (length <= 0) return 0;
-    return mBigramDictionary->getPredictions(word, length, outWords, frequencies, outputTypes);
+    return mBigramDictionary.get()->getPredictions(word, length, outWords, frequencies,
+            outputTypes);
 }
 
 int Dictionary::getProbability(const int *word, int length) const {
-    int pos = getDictionaryStructurePolicy()->getTerminalNodePositionOfWord(word, length,
+    int pos = getDictionaryStructurePolicy()->getTerminalPtNodePositionOfWord(word, length,
             false /* forceLowerCaseSearch */);
     if (NOT_A_DICT_POS == pos) {
         return NOT_A_PROBABILITY;
@@ -98,39 +90,39 @@
 
 int Dictionary::getBigramProbability(const int *word0, int length0, const int *word1,
         int length1) const {
-    return mBigramDictionary->getBigramProbability(word0, length0, word1, length1);
+    return mBigramDictionary.get()->getBigramProbability(word0, length0, word1, length1);
 }
 
 void Dictionary::addUnigramWord(const int *const word, const int length, const int probability) {
-    mDictionaryStructureWithBufferPolicy->addUnigramWord(word, length, probability);
+    mDictionaryStructureWithBufferPolicy.get()->addUnigramWord(word, length, probability);
 }
 
 void Dictionary::addBigramWords(const int *const word0, const int length0, const int *const word1,
         const int length1, const int probability) {
-    mDictionaryStructureWithBufferPolicy->addBigramWords(word0, length0, word1, length1,
+    mDictionaryStructureWithBufferPolicy.get()->addBigramWords(word0, length0, word1, length1,
             probability);
 }
 
 void Dictionary::removeBigramWords(const int *const word0, const int length0,
         const int *const word1, const int length1) {
-    mDictionaryStructureWithBufferPolicy->removeBigramWords(word0, length0, word1, length1);
+    mDictionaryStructureWithBufferPolicy.get()->removeBigramWords(word0, length0, word1, length1);
 }
 
 void Dictionary::flush(const char *const filePath) {
-    mDictionaryStructureWithBufferPolicy->flush(filePath);
+    mDictionaryStructureWithBufferPolicy.get()->flush(filePath);
 }
 
 void Dictionary::flushWithGC(const char *const filePath) {
-    mDictionaryStructureWithBufferPolicy->flushWithGC(filePath);
+    mDictionaryStructureWithBufferPolicy.get()->flushWithGC(filePath);
 }
 
 bool Dictionary::needsToRunGC(const bool mindsBlockByGC) {
-    return mDictionaryStructureWithBufferPolicy->needsToRunGC(mindsBlockByGC);
+    return mDictionaryStructureWithBufferPolicy.get()->needsToRunGC(mindsBlockByGC);
 }
 
 void Dictionary::getProperty(const char *const query, char *const outResult,
         const int maxResultLength) {
-    return mDictionaryStructureWithBufferPolicy->getProperty(query, outResult, maxResultLength);
+    return mDictionaryStructureWithBufferPolicy.get()->getProperty(query, outResult, maxResultLength);
 }
 
 void Dictionary::logDictionaryInfo(JNIEnv *const env) const {
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.h b/native/jni/src/suggest/core/dictionary/dictionary.h
index 0195d5b..e52a40f 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.h
+++ b/native/jni/src/suggest/core/dictionary/dictionary.h
@@ -21,14 +21,16 @@
 
 #include "defines.h"
 #include "jni.h"
+#include "suggest/core/dictionary/bigram_dictionary.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
+#include "suggest/core/suggest_interface.h"
+#include "utils/exclusive_ownership_pointer.h"
 
 namespace latinime {
 
-class BigramDictionary;
 class DictionaryStructureWithBufferPolicy;
 class DicTraverseSession;
 class ProximityInfo;
-class SuggestInterface;
 class SuggestOptions;
 
 class Dictionary {
@@ -53,8 +55,8 @@
     static const int KIND_FLAG_POSSIBLY_OFFENSIVE = 0x80000000;
     static const int KIND_FLAG_EXACT_MATCH = 0x40000000;
 
-    Dictionary(JNIEnv *env,
-            DictionaryStructureWithBufferPolicy *const dictionaryStructureWithBufferPoilcy);
+    Dictionary(JNIEnv *env, const DictionaryStructureWithBufferPolicy::StructurePoilcyPtr
+            &dictionaryStructureWithBufferPoilcy);
 
     int getSuggestions(ProximityInfo *proximityInfo, DicTraverseSession *traverseSession,
             int *xcoordinates, int *ycoordinates, int *times, int *pointerIds, int *inputCodePoints,
@@ -87,20 +89,22 @@
             const int maxResultLength);
 
     const DictionaryStructureWithBufferPolicy *getDictionaryStructurePolicy() const {
-        return mDictionaryStructureWithBufferPolicy;
+        return mDictionaryStructureWithBufferPolicy.get();
     }
 
-    virtual ~Dictionary();
-
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(Dictionary);
 
+    typedef ExclusiveOwnershipPointer<BigramDictionary> BigramDictionaryPtr;
+    typedef ExclusiveOwnershipPointer<SuggestInterface> SuggestInterfacePtr;
+
     static const int HEADER_ATTRIBUTE_BUFFER_SIZE;
 
-    DictionaryStructureWithBufferPolicy *const mDictionaryStructureWithBufferPolicy;
-    const BigramDictionary *const mBigramDictionary;
-    const SuggestInterface *const mGestureSuggest;
-    const SuggestInterface *const mTypingSuggest;
+    const DictionaryStructureWithBufferPolicy::StructurePoilcyPtr
+            mDictionaryStructureWithBufferPolicy;
+    const BigramDictionaryPtr mBigramDictionary;
+    const SuggestInterfacePtr mGestureSuggest;
+    const SuggestInterfacePtr mTypingSuggest;
 
     void logDictionaryInfo(JNIEnv *const env) const;
 };
diff --git a/native/jni/src/suggest/core/dictionary/error_type_utils.cpp b/native/jni/src/suggest/core/dictionary/error_type_utils.cpp
new file mode 100644
index 0000000..0635fef
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/error_type_utils.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/core/dictionary/error_type_utils.h"
+
+namespace latinime {
+
+const ErrorTypeUtils::ErrorType ErrorTypeUtils::NOT_AN_ERROR = 0x0;
+const ErrorTypeUtils::ErrorType ErrorTypeUtils::MATCH_WITH_CASE_ERROR = 0x1;
+const ErrorTypeUtils::ErrorType ErrorTypeUtils::MATCH_WITH_ACCENT_ERROR = 0x2;
+const ErrorTypeUtils::ErrorType ErrorTypeUtils::MATCH_WITH_DIGRAPH = 0x4;
+const ErrorTypeUtils::ErrorType ErrorTypeUtils::INTENTIONAL_OMISSION = 0x8;
+const ErrorTypeUtils::ErrorType ErrorTypeUtils::EDIT_CORRECTION = 0x10;
+const ErrorTypeUtils::ErrorType ErrorTypeUtils::PROXIMITY_CORRECTION = 0x20;
+const ErrorTypeUtils::ErrorType ErrorTypeUtils::COMPLETION = 0x40;
+const ErrorTypeUtils::ErrorType ErrorTypeUtils::NEW_WORD = 0x80;
+
+const ErrorTypeUtils::ErrorType ErrorTypeUtils::ERRORS_TREATED_AS_AN_EXACT_MATCH =
+        NOT_AN_ERROR | MATCH_WITH_CASE_ERROR | MATCH_WITH_ACCENT_ERROR | MATCH_WITH_DIGRAPH;
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/core/dictionary/error_type_utils.h b/native/jni/src/suggest/core/dictionary/error_type_utils.h
new file mode 100644
index 0000000..ab4a65e
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/error_type_utils.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_ERROR_TYPE_UTILS_H
+#define LATINIME_ERROR_TYPE_UTILS_H
+
+#include <stdint.h>
+
+#include "defines.h"
+
+namespace latinime {
+
+class ErrorTypeUtils {
+ public:
+    // ErrorType is mainly decided by CorrectionType but it is also depending on if
+    // the correction has really been performed or not.
+    typedef uint32_t ErrorType;
+
+    static const ErrorType NOT_AN_ERROR;
+    static const ErrorType MATCH_WITH_CASE_ERROR;
+    static const ErrorType MATCH_WITH_ACCENT_ERROR;
+    static const ErrorType MATCH_WITH_DIGRAPH;
+    // Treat error as an intentional omission when the CorrectionType is omission and the node can
+    // be intentional omission.
+    static const ErrorType INTENTIONAL_OMISSION;
+    // Substitution, omission and transposition
+    static const ErrorType EDIT_CORRECTION;
+    // Proximity error
+    static const ErrorType PROXIMITY_CORRECTION;
+    // Completion
+    static const ErrorType COMPLETION;
+    // New word
+    // TODO: Remove.
+    // A new word error should be an edit correction error or a proximity correction error.
+    static const ErrorType NEW_WORD;
+
+    // TODO: Differentiate errors.
+    static bool isExactMatch(const ErrorType containingErrors) {
+        return (containingErrors & ~ERRORS_TREATED_AS_AN_EXACT_MATCH) == 0;
+    }
+
+    static bool isEditCorrectionError(const ErrorType errorType) {
+        return (errorType & EDIT_CORRECTION) != 0;
+    }
+
+    static bool isProximityCorrectionError(const ErrorType errorType) {
+        return (errorType & PROXIMITY_CORRECTION) != 0;
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ErrorTypeUtils);
+
+    static const ErrorType ERRORS_TREATED_AS_AN_EXACT_MATCH;
+};
+} // namespace latinime
+#endif // LATINIME_ERROR_TYPE_UTILS_H
diff --git a/native/jni/src/suggest/core/layout/proximity_info.cpp b/native/jni/src/suggest/core/layout/proximity_info.cpp
index e64476d..ee8e59e 100644
--- a/native/jni/src/suggest/core/layout/proximity_info.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info.cpp
@@ -71,7 +71,7 @@
                   && sweetSpotCenterYs && sweetSpotRadii),
           mProximityCharsArray(new int[GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE
                   /* proximityCharsLength */]),
-          mCodeToKeyMap() {
+          mLowerCodePointToKeyMap() {
     /* Let's check the input array length here to make sure */
     const jsize proximityCharsLength = env->GetArrayLength(proximityChars);
     if (proximityCharsLength != GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE) {
@@ -147,7 +147,14 @@
     if (keyIndex < 0 || keyIndex >= KEY_COUNT) {
         return NOT_A_CODE_POINT;
     }
-    return mKeyIndexToCodePointG[keyIndex];
+    return mKeyIndexToLowerCodePointG[keyIndex];
+}
+
+int ProximityInfo::getOriginalCodePointOf(const int keyIndex) const {
+    if (keyIndex < 0 || keyIndex >= KEY_COUNT) {
+        return NOT_A_CODE_POINT;
+    }
+    return mKeyIndexToOriginalCodePoint[keyIndex];
 }
 
 void ProximityInfo::initializeG() {
@@ -164,8 +171,9 @@
             const float gapY = sweetSpotCenterY - mCenterYsG[i];
             mSweetSpotCenterYsG[i] = static_cast<int>(mCenterYsG[i] + gapY * verticalScale);
         }
-        mCodeToKeyMap[lowerCode] = i;
-        mKeyIndexToCodePointG[i] = lowerCode;
+        mLowerCodePointToKeyMap[lowerCode] = i;
+        mKeyIndexToOriginalCodePoint[i] = code;
+        mKeyIndexToLowerCodePointG[i] = lowerCode;
     }
     for (int i = 0; i < KEY_COUNT; i++) {
         mKeyKeyDistancesG[i][i] = 0;
diff --git a/native/jni/src/suggest/core/layout/proximity_info.h b/native/jni/src/suggest/core/layout/proximity_info.h
index f259490..a91b9d6 100644
--- a/native/jni/src/suggest/core/layout/proximity_info.h
+++ b/native/jni/src/suggest/core/layout/proximity_info.h
@@ -39,6 +39,7 @@
     float getNormalizedSquaredDistanceFromCenterFloatG(
             const int keyId, const int x, const int y, const bool isGeometric) const;
     int getCodePointOf(const int keyIndex) const;
+    int getOriginalCodePointOf(const int keyIndex) const;
     bool hasSweetSpotData(const int keyIndex) const {
         // When there are no calibration data for a key,
         // the radius of the key is assigned to zero.
@@ -76,11 +77,11 @@
         ProximityInfoUtils::initializeProximities(inputCodes, inputXCoordinates, inputYCoordinates,
                 inputSize, mKeyXCoordinates, mKeyYCoordinates, mKeyWidths, mKeyHeights,
                 mProximityCharsArray, CELL_HEIGHT, CELL_WIDTH, GRID_WIDTH, MOST_COMMON_KEY_WIDTH,
-                KEY_COUNT, mLocaleStr, &mCodeToKeyMap, allInputCodes);
+                KEY_COUNT, mLocaleStr, &mLowerCodePointToKeyMap, allInputCodes);
     }
 
     AK_FORCE_INLINE int getKeyIndexOf(const int c) const {
-        return ProximityInfoUtils::getKeyIndexOf(KEY_COUNT, c, &mCodeToKeyMap);
+        return ProximityInfoUtils::getKeyIndexOf(KEY_COUNT, c, &mLowerCodePointToKeyMap);
     }
 
     AK_FORCE_INLINE bool isCodePointOnKeyboard(const int codePoint) const {
@@ -117,9 +118,9 @@
     // Sweet spots for geometric input. Note that we have extra sweet spots only for Y coordinates.
     float mSweetSpotCenterYsG[MAX_KEY_COUNT_IN_A_KEYBOARD];
     float mSweetSpotRadii[MAX_KEY_COUNT_IN_A_KEYBOARD];
-    hash_map_compat<int, int> mCodeToKeyMap;
-
-    int mKeyIndexToCodePointG[MAX_KEY_COUNT_IN_A_KEYBOARD];
+    hash_map_compat<int, int> mLowerCodePointToKeyMap;
+    int mKeyIndexToOriginalCodePoint[MAX_KEY_COUNT_IN_A_KEYBOARD];
+    int mKeyIndexToLowerCodePointG[MAX_KEY_COUNT_IN_A_KEYBOARD];
     int mCenterXsG[MAX_KEY_COUNT_IN_A_KEYBOARD];
     int mCenterYsG[MAX_KEY_COUNT_IN_A_KEYBOARD];
     int mKeyKeyDistancesG[MAX_KEY_COUNT_IN_A_KEYBOARD][MAX_KEY_COUNT_IN_A_KEYBOARD];
diff --git a/native/jni/src/suggest/core/layout/proximity_info_state.cpp b/native/jni/src/suggest/core/layout/proximity_info_state.cpp
index fbabd92..bb4b417 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_state.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info_state.cpp
@@ -30,6 +30,12 @@
 
 namespace latinime {
 
+int ProximityInfoState::getPrimaryOriginalCodePointAt(const int index) const {
+    const int primaryCodePoint = getPrimaryCodePointAt(index);
+    const int keyIndex = mProximityInfo->getKeyIndexOf(primaryCodePoint);
+    return mProximityInfo->getOriginalCodePointOf(keyIndex);
+}
+
 // TODO: Remove the dependency of "isGeometric"
 void ProximityInfoState::initInputParams(const int pointerId, const float maxPointToKeyLength,
         const ProximityInfo *proximityInfo, const int *const inputCodes, const int inputSize,
diff --git a/native/jni/src/suggest/core/layout/proximity_info_state.h b/native/jni/src/suggest/core/layout/proximity_info_state.h
index c94060f..e941e43 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_state.h
+++ b/native/jni/src/suggest/core/layout/proximity_info_state.h
@@ -65,6 +65,8 @@
         return getProximityCodePointsAt(index)[0];
     }
 
+    int getPrimaryOriginalCodePointAt(const int index) const;
+
     inline bool sameAsTyped(const int *word, int length) const {
         if (length != mSampledInputSize) {
             return false;
diff --git a/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h b/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h
index 41f8204..e649844 100644
--- a/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h
+++ b/native/jni/src/suggest/core/policy/dictionary_structure_with_buffer_policy.h
@@ -18,6 +18,7 @@
 #define LATINIME_DICTIONARY_STRUCTURE_POLICY_H
 
 #include "defines.h"
+#include "utils/exclusive_ownership_pointer.h"
 
 namespace latinime {
 
@@ -33,18 +34,20 @@
  */
 class DictionaryStructureWithBufferPolicy {
  public:
+    typedef ExclusiveOwnershipPointer<DictionaryStructureWithBufferPolicy> StructurePoilcyPtr;
+
     virtual ~DictionaryStructureWithBufferPolicy() {}
 
     virtual int getRootPosition() const = 0;
 
-    virtual void createAndGetAllChildNodes(const DicNode *const dicNode,
+    virtual void createAndGetAllChildDicNodes(const DicNode *const dicNode,
             DicNodeVector *const childDicNodes) const = 0;
 
     virtual int getCodePointsAndProbabilityAndReturnCodePointCount(
             const int nodePos, const int maxCodePointCount, int *const outCodePoints,
             int *const outUnigramProbability) const = 0;
 
-    virtual int getTerminalNodePositionOfWord(const int *const inWord,
+    virtual int getTerminalPtNodePositionOfWord(const int *const inWord,
             const int length, const bool forceLowerCaseSearch) const = 0;
 
     virtual int getProbability(const int unigramProbability,
diff --git a/native/jni/src/suggest/core/policy/weighting.cpp b/native/jni/src/suggest/core/policy/weighting.cpp
index 0c40168..c202b81 100644
--- a/native/jni/src/suggest/core/policy/weighting.cpp
+++ b/native/jni/src/suggest/core/policy/weighting.cpp
@@ -20,6 +20,7 @@
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_profiler.h"
 #include "suggest/core/dicnode/dic_node_utils.h"
+#include "suggest/core/dictionary/error_type_utils.h"
 #include "suggest/core/session/dic_traverse_session.h"
 
 namespace latinime {
@@ -82,8 +83,8 @@
             traverseSession, parentDicNode, dicNode, &inputStateG);
     const float languageCost = Weighting::getLanguageCost(weighting, correctionType,
             traverseSession, parentDicNode, dicNode, multiBigramMap);
-    const ErrorType errorType = weighting->getErrorType(correctionType, traverseSession,
-            parentDicNode, dicNode);
+    const ErrorTypeUtils::ErrorType errorType = weighting->getErrorType(correctionType,
+            traverseSession, parentDicNode, dicNode);
     profile(correctionType, dicNode);
     if (inputStateG.mNeedsToUpdateInputStateG) {
         dicNode->updateInputIndexG(&inputStateG);
diff --git a/native/jni/src/suggest/core/policy/weighting.h b/native/jni/src/suggest/core/policy/weighting.h
index 2d49e98..bd6b3cf 100644
--- a/native/jni/src/suggest/core/policy/weighting.h
+++ b/native/jni/src/suggest/core/policy/weighting.h
@@ -18,6 +18,7 @@
 #define LATINIME_WEIGHTING_H
 
 #include "defines.h"
+#include "suggest/core/dictionary/error_type_utils.h"
 
 namespace latinime {
 
@@ -84,7 +85,7 @@
     virtual float getSpaceSubstitutionCost(const DicTraverseSession *const traverseSession,
             const DicNode *const dicNode) const = 0;
 
-    virtual ErrorType getErrorType(const CorrectionType correctionType,
+    virtual ErrorTypeUtils::ErrorType getErrorType(const CorrectionType correctionType,
             const DicTraverseSession *const traverseSession,
             const DicNode *const parentDicNode, const DicNode *const dicNode) const = 0;
 
diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.cpp b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
index 50f2bbd..5070491 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.cpp
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
@@ -35,16 +35,16 @@
             ->getMultiWordCostMultiplier();
     mSuggestOptions = suggestOptions;
     if (!prevWord) {
-        mPrevWordPos = NOT_A_DICT_POS;
+        mPrevWordPtNodePos = NOT_A_DICT_POS;
         return;
     }
     // TODO: merge following similar calls to getTerminalPosition into one case-insensitive call.
-    mPrevWordPos = getDictionaryStructurePolicy()->getTerminalNodePositionOfWord(
+    mPrevWordPtNodePos = getDictionaryStructurePolicy()->getTerminalPtNodePositionOfWord(
             prevWord, prevWordLength, false /* forceLowerCaseSearch */);
-    if (mPrevWordPos == NOT_A_DICT_POS) {
+    if (mPrevWordPtNodePos == NOT_A_DICT_POS) {
         // Check bigrams for lower-cased previous word if original was not found. Useful for
         // auto-capitalized words like "The [current_word]".
-        mPrevWordPos = getDictionaryStructurePolicy()->getTerminalNodePositionOfWord(
+        mPrevWordPtNodePos = getDictionaryStructurePolicy()->getTerminalPtNodePositionOfWord(
                 prevWord, prevWordLength, true /* forceLowerCaseSearch */);
     }
 }
diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.h b/native/jni/src/suggest/core/session/dic_traverse_session.h
index e0b1c67..6e4dda4 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.h
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.h
@@ -59,7 +59,7 @@
     }
 
     AK_FORCE_INLINE DicTraverseSession(JNIEnv *env, jstring localeStr, bool usesLargeCache)
-            : mPrevWordPos(NOT_A_DICT_POS), mProximityInfo(0),
+            : mPrevWordPtNodePos(NOT_A_DICT_POS), mProximityInfo(0),
               mDictionary(0), mSuggestOptions(0), mDicNodesCache(usesLargeCache),
               mMultiBigramMap(), mInputSize(0), mPartiallyCommited(false), mMaxPointerCount(1),
               mMultiWordCostMultiplier(1.0f) {
@@ -86,11 +86,9 @@
     //--------------------
     const ProximityInfo *getProximityInfo() const { return mProximityInfo; }
     const SuggestOptions *getSuggestOptions() const { return mSuggestOptions; }
-    int getPrevWordPos() const { return mPrevWordPos; }
+    int getPrevWordPtNodePos() const { return mPrevWordPtNodePos; }
     // TODO: REMOVE
-    void setPrevWordPos(int pos) { mPrevWordPos = pos; }
-    // TODO: Use proper parameter when changed
-    int getDicRootPos() const { return 0; }
+    void setPrevWordPtNodePos(const int ptNodePos) { mPrevWordPtNodePos = ptNodePos; }
     DicNodesCache *getDicTraverseCache() { return &mDicNodesCache; }
     MultiBigramMap *getMultiBigramMap() { return &mMultiBigramMap; }
     const ProximityInfoState *getProximityInfoState(int id) const {
@@ -119,26 +117,13 @@
         return true;
     }
 
-    void getSearchKeys(const DicNode *node, std::vector<int> *const outputSearchKeyVector) const {
-        for (int i = 0; i < MAX_POINTER_COUNT_G; ++i) {
-            if (!mProximityInfoStates[i].isUsed()) {
-                continue;
-            }
-            const int pointerId = node->getInputIndex(i);
-            const std::vector<int> *const searchKeyVector =
-                    mProximityInfoStates[i].getSearchKeyVector(pointerId);
-            outputSearchKeyVector->insert(outputSearchKeyVector->end(), searchKeyVector->begin(),
-                    searchKeyVector->end());
-        }
-    }
-
-    ProximityType getProximityTypeG(const DicNode *const node, const int childCodePoint) const {
+    ProximityType getProximityTypeG(const DicNode *const dicNode, const int childCodePoint) const {
         ProximityType proximityType = UNRELATED_CHAR;
         for (int i = 0; i < MAX_POINTER_COUNT_G; ++i) {
             if (!mProximityInfoStates[i].isUsed()) {
                 continue;
             }
-            const int pointerId = node->getInputIndex(i);
+            const int pointerId = dicNode->getInputIndex(i);
             proximityType = mProximityInfoStates[i].getProximityTypeG(pointerId, childCodePoint);
             ASSERT(proximityType == UNRELATED_CHAR || proximityType == MATCH_CHAR);
             // TODO: Make this more generic
@@ -192,7 +177,7 @@
             const int *const inputYs, const int *const times, const int *const pointerIds,
             const int inputSize, const float maxSpatialDistance, const int maxPointerCount);
 
-    int mPrevWordPos;
+    int mPrevWordPtNodePos;
     const ProximityInfo *mProximityInfo;
     const Dictionary *mDictionary;
     const SuggestOptions *mSuggestOptions;
diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp
index 73ccebc..2eda414 100644
--- a/native/jni/src/suggest/core/suggest.cpp
+++ b/native/jni/src/suggest/core/suggest.cpp
@@ -98,7 +98,7 @@
             // Continue suggestion after partial commit.
             DicNode *topDicNode =
                     traverseSession->getDicTraverseCache()->setCommitPoint(commitPoint);
-            traverseSession->setPrevWordPos(topDicNode->getPrevWordNodePos());
+            traverseSession->setPrevWordPtNodePos(topDicNode->getPrevWordPtNodePos());
             traverseSession->getDicTraverseCache()->continueSearch();
             traverseSession->setPartiallyCommited();
         }
@@ -109,7 +109,7 @@
         // Create a new dic node here
         DicNode rootNode;
         DicNodeUtils::initAsRoot(traverseSession->getDictionaryStructurePolicy(),
-                traverseSession->getPrevWordPos(), &rootNode);
+                traverseSession->getPrevWordPtNodePos(), &rootNode);
         traverseSession->getDicTraverseCache()->copyPushActive(&rootNode);
     }
 }
@@ -231,7 +231,7 @@
             BinaryDictionaryShortcutIterator shortcutIt(
                     traverseSession->getDictionaryStructurePolicy()->getShortcutsStructurePolicy(),
                     traverseSession->getDictionaryStructurePolicy()
-                            ->getShortcutPositionOfPtNode(terminalDicNode->getPos()));
+                            ->getShortcutPositionOfPtNode(terminalDicNode->getPtNodePos()));
             // Shortcut is not supported for multiple words suggestions.
             // TODO: Check shortcuts during traversal for multiple words suggestions.
             const bool sameAsTyped = TRAVERSAL->sameAsTyped(traverseSession, terminalDicNode);
@@ -421,15 +421,15 @@
                         }
                         break;
                     case UNRELATED_CHAR:
-                        // Just drop this node and do nothing.
+                        // Just drop this dicNode and do nothing.
                         break;
                     default:
-                        // Just drop this node and do nothing.
+                        // Just drop this dicNode and do nothing.
                         break;
                 }
             }
 
-            // Push the node for look-ahead correction
+            // Push the dicNode for look-ahead correction
             if (allowsErrorCorrections && canDoLookAheadCorrection) {
                 traverseSession->getDicTraverseCache()->copyPushNextActive(&dicNode);
             }
@@ -442,7 +442,7 @@
     if (dicNode->getCompoundDistance() >= static_cast<float>(MAX_VALUE_FOR_WEIGHTING)) {
         return;
     }
-    if (!dicNode->isTerminalWordNode()) {
+    if (!dicNode->isTerminalDicNode()) {
         return;
     }
     if (dicNode->shouldBeFilteredBySafetyNetForBigram()) {
@@ -463,7 +463,7 @@
 
 /**
  * Adds the expanded dicNode to the next search priority queue. Also creates an additional next word
- * (by the space omission error correction) search path if input dicNode is on a terminal node.
+ * (by the space omission error correction) search path if input dicNode is on a terminal.
  */
 void Suggest::processExpandedDicNode(
         DicTraverseSession *traverseSession, DicNode *dicNode) const {
@@ -505,7 +505,7 @@
     processExpandedDicNode(traverseSession, childDicNode);
 }
 
-// Process the node codepoint as a digraph. This means that composite glyphs like the German
+// Process the DicNode codepoint as a digraph. This means that composite glyphs like the German
 // u-umlaut is expanded to the transliteration "ue". Note that this happens in parallel with
 // the normal non-digraph traversal, so both "uber" and "ueber" can be corrected to "[u-umlaut]ber".
 void Suggest::processDicNodeAsDigraph(DicTraverseSession *traverseSession,
@@ -518,7 +518,7 @@
 /**
  * Handle the dicNode as an omission error (e.g., ths => this). Skip the current letter and consider
  * matches for all possible next letters. Note that just skipping the current letter without any
- * other conditions tends to flood the search dic nodes cache with omission nodes. Instead, check
+ * other conditions tends to flood the search DicNodes cache with omission DicNodes. Instead, check
  * the possible *next* letters after the omission to better limit search to plausible omissions.
  * Note that apostrophes are handled as omissions.
  */
@@ -605,7 +605,7 @@
 }
 
 /**
- * Weight child node by aligning it to the key
+ * Weight child dicNode by aligning it to the key
  */
 void Suggest::weightChildNode(DicTraverseSession *traverseSession, DicNode *dicNode) const {
     const int inputSize = traverseSession->getInputSize();
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp
index 1926b98..de9fc9b 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp
@@ -16,7 +16,7 @@
 
 #include "suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h"
 
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_utils.h"
 #include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
 #include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.cpp
index b1170e2..83a32fb 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.cpp
@@ -17,8 +17,8 @@
 #include "suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h"
 
 #include "suggest/core/policy/dictionary_shortcuts_structure_policy.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_reader.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_helper.h"
 #include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
 #include "suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h"
 
@@ -157,8 +157,9 @@
         }
         const int bigramTargetNodePos =
                 followBigramLinkAndGetCurrentBigramPtNodePos(originalBigramPos);
-        nodeReader.fetchNodeInfoInBufferFromPtNodePos(bigramTargetNodePos);
-        if (nodeReader.isDeleted() || !nodeReader.isTerminal()
+        const PtNodeParams ptNodeParams(nodeReader.fetchNodeInfoInBufferFromPtNodePos(
+                bigramTargetNodePos));
+        if (ptNodeParams.isDeleted() || !ptNodeParams.isTerminal()
                 || bigramTargetNodePos == NOT_A_DICT_POS) {
             // The target is no longer valid terminal. Invalidate the current bigram entry.
             if (!BigramListReadWriteUtils::writeBigramEntry(mBuffer, bigramFlags,
@@ -342,20 +343,22 @@
     if (originalBigramPos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
     }
-    int currentPos = originalBigramPos;
     DynamicPatriciaTrieNodeReader nodeReader(mBuffer, this /* bigramsPolicy */, mShortcutPolicy);
-    nodeReader.fetchNodeInfoInBufferFromPtNodePos(currentPos);
+    int currentPos = NOT_A_DICT_POS;
     int bigramLinkCount = 0;
-    while (nodeReader.getBigramLinkedNodePos() != NOT_A_DICT_POS) {
-        currentPos = nodeReader.getBigramLinkedNodePos();
-        nodeReader.fetchNodeInfoInBufferFromPtNodePos(currentPos);
+    int bigramLinkedNodePos = originalBigramPos;
+    do {
+        currentPos = bigramLinkedNodePos;
+        const PtNodeParams ptNodeParams(nodeReader.fetchNodeInfoInBufferFromPtNodePos(currentPos));
+        bigramLinkedNodePos = ptNodeParams.getBigramLinkedNodePos();
         bigramLinkCount++;
         if (bigramLinkCount > CONTINUING_BIGRAM_LINK_COUNT_LIMIT) {
             AKLOGE("Bigram link is invalid. start position: %d", originalBigramPos);
             ASSERT(false);
             return NOT_A_DICT_POS;
         }
-    }
+        bigramLinkedNodePos = ptNodeParams.getBigramLinkedNodePos();
+    } while (bigramLinkedNodePos != NOT_A_DICT_POS);
     return currentPos;
 }
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h b/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h
index 0504b59..5de4566 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h
@@ -22,7 +22,7 @@
 #include "defines.h"
 #include "suggest/core/policy/dictionary_bigrams_structure_policy.h"
 #include "suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_helper.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.h b/native/jni/src/suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.h
new file mode 100644
index 0000000..875a0ff
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_VER4_BIGRAM_LIST_POLICY_H
+#define LATINIME_VER4_BIGRAM_LIST_POLICY_H
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_bigrams_structure_policy.h"
+#include "suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v4/content/bigram_dict_content.h"
+#include "suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table.h"
+
+namespace latinime {
+
+class Ver4BigramListPolicy : public DictionaryBigramsStructurePolicy {
+ public:
+    Ver4BigramListPolicy(const BigramDictContent *const bigramDictContent,
+            const TerminalPositionLookupTable *const terminalPositionLookupTable)
+            : mBigramDictContent(bigramDictContent),
+              mTerminalPositionLookupTable(terminalPositionLookupTable) {}
+
+    void getNextBigram(int *const outBigramPos, int *const outProbability,
+            bool *const outHasNext, int *const bigramEntryPos) const {
+        int bigramFlags = 0;
+        int targetTerminalId = Ver4DictConstants::NOT_A_TERMINAL_ID;
+        mBigramDictContent->getBigramEntryAndAdvancePosition(&bigramFlags, &targetTerminalId,
+                bigramEntryPos);
+        if (outProbability) {
+            *outProbability = BigramListReadWriteUtils::getProbabilityFromFlags(bigramFlags);
+        }
+        if (outHasNext) {
+            *outHasNext = BigramListReadWriteUtils::hasNext(bigramFlags);
+        }
+        if (outBigramPos) {
+            // Lookup target PtNode position.
+            *outBigramPos =
+                    mTerminalPositionLookupTable->getTerminalPtNodePosition(targetTerminalId);
+        }
+    }
+
+    void skipAllBigrams(int *const pos) const {
+        // Do nothing because we don't need to skip bigram lists in ver4 dictionaries.
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(Ver4BigramListPolicy);
+
+    const BigramDictContent *const mBigramDictContent;
+    const TerminalPositionLookupTable *const mTerminalPositionLookupTable;
+};
+} // namespace latinime
+#endif /* LATINIME_VER4_BIGRAM_LIST_POLICY_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.cpp b/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.cpp
deleted file mode 100644
index ff80dd2..0000000
--- a/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.h"
-
-#include <stdint.h>
-
-#include "defines.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h"
-#include "suggest/policyimpl/dictionary/patricia_trie_policy.h"
-#include "suggest/policyimpl/dictionary/utils/format_utils.h"
-#include "suggest/policyimpl/dictionary/utils/mmapped_buffer.h"
-
-namespace latinime {
-
-/* static */ DictionaryStructureWithBufferPolicy *DictionaryStructureWithBufferPolicyFactory
-        ::newDictionaryStructureWithBufferPolicy(const char *const path, const int bufOffset,
-                const int size, const bool isUpdatable) {
-    // Allocated buffer in MmapedBuffer::openBuffer() will be freed in the destructor of
-    // impl classes of DictionaryStructureWithBufferPolicy.
-    const MmappedBuffer *const mmapedBuffer = MmappedBuffer::openBuffer(path, bufOffset, size,
-            isUpdatable);
-    if (!mmapedBuffer) {
-        return 0;
-    }
-    switch (FormatUtils::detectFormatVersion(mmapedBuffer->getBuffer(),
-            mmapedBuffer->getBufferSize())) {
-        case FormatUtils::VERSION_2:
-            return new PatriciaTriePolicy(mmapedBuffer);
-        case FormatUtils::VERSION_3:
-            return new DynamicPatriciaTriePolicy(mmapedBuffer);
-        default:
-            AKLOGE("DICT: dictionary format is unknown, bad magic number");
-            delete mmapedBuffer;
-            ASSERT(false);
-            return 0;
-    }
-}
-
-} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.cpp
deleted file mode 100644
index 2fa3111..0000000
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.cpp
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2013, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h"
-
-#include "suggest/core/policy/dictionary_bigrams_structure_policy.h"
-#include "suggest/core/policy/dictionary_shortcuts_structure_policy.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
-#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
-
-namespace latinime {
-
-void DynamicPatriciaTrieNodeReader::fetchPtNodeInfoFromBufferAndProcessMovedPtNode(
-        const int ptNodePos, const int maxCodePointCount, int *const outCodePoints) {
-    if (ptNodePos < 0 || ptNodePos >= mBuffer->getTailPosition()) {
-        // Reading invalid position because of bug or broken dictionary.
-        AKLOGE("Fetching PtNode info from invalid dictionary position: %d, dictionary size: %d",
-                ptNodePos, mBuffer->getTailPosition());
-        ASSERT(false);
-        invalidatePtNodeInfo();
-        return;
-    }
-    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(ptNodePos);
-    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
-    int pos = ptNodePos;
-    mHeadPos = ptNodePos;
-    if (usesAdditionalBuffer) {
-        pos -= mBuffer->getOriginalBufferSize();
-    }
-    mFlags = PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictBuf, &pos);
-    const int parentPosOffset =
-            DynamicPatriciaTrieReadingUtils::getParentPtNodePosOffsetAndAdvancePosition(dictBuf,
-                    &pos);
-    mParentPos = DynamicPatriciaTrieReadingUtils::getParentPtNodePos(parentPosOffset, mHeadPos);
-    if (outCodePoints != 0) {
-        mCodePointCount = PatriciaTrieReadingUtils::getCharsAndAdvancePosition(
-                dictBuf, mFlags, maxCodePointCount, outCodePoints, &pos);
-    } else {
-        mCodePointCount = PatriciaTrieReadingUtils::skipCharacters(
-                dictBuf, mFlags, MAX_WORD_LENGTH, &pos);
-    }
-    if (isTerminal()) {
-        mProbabilityFieldPos = pos;
-        if (usesAdditionalBuffer) {
-            mProbabilityFieldPos += mBuffer->getOriginalBufferSize();
-        }
-        mProbability = PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(dictBuf, &pos);
-    } else {
-        mProbabilityFieldPos = NOT_A_DICT_POS;
-        mProbability = NOT_A_PROBABILITY;
-    }
-    mChildrenPosFieldPos = pos;
-    if (usesAdditionalBuffer) {
-        mChildrenPosFieldPos += mBuffer->getOriginalBufferSize();
-    }
-    mChildrenPos = DynamicPatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(
-            dictBuf, &pos);
-    if (usesAdditionalBuffer && mChildrenPos != NOT_A_DICT_POS) {
-        mChildrenPos += mBuffer->getOriginalBufferSize();
-    }
-    if (mSiblingPos == NOT_A_DICT_POS) {
-        if (DynamicPatriciaTrieReadingUtils::isMoved(mFlags)) {
-            mBigramLinkedNodePos = mChildrenPos;
-        } else {
-            mBigramLinkedNodePos = NOT_A_DICT_POS;
-        }
-    }
-    if (usesAdditionalBuffer) {
-        pos += mBuffer->getOriginalBufferSize();
-    }
-    if (PatriciaTrieReadingUtils::hasShortcutTargets(mFlags)) {
-        mShortcutPos = pos;
-        mShortcutsPolicy->skipAllShortcuts(&pos);
-    } else {
-        mShortcutPos = NOT_A_DICT_POS;
-    }
-    if (PatriciaTrieReadingUtils::hasBigrams(mFlags)) {
-        mBigramPos = pos;
-        mBigramsPolicy->skipAllBigrams(&pos);
-    } else {
-        mBigramPos = NOT_A_DICT_POS;
-    }
-    // Update siblingPos if needed.
-    if (mSiblingPos == NOT_A_DICT_POS) {
-        // Sibling position is the tail position of current node.
-        mSiblingPos = pos;
-    }
-    // Read destination node if the read node is a moved node.
-    if (DynamicPatriciaTrieReadingUtils::isMoved(mFlags)) {
-        // The destination position is stored at the same place as the parent position.
-        fetchPtNodeInfoFromBufferAndProcessMovedPtNode(mParentPos, maxCodePointCount,
-                outCodePoints);
-    }
-}
-
-void DynamicPatriciaTrieNodeReader::invalidatePtNodeInfo() {
-    mHeadPos = NOT_A_DICT_POS;
-    mFlags = 0;
-    mParentPos = NOT_A_DICT_POS;
-    mCodePointCount = 0;
-    mProbabilityFieldPos = NOT_A_DICT_POS;
-    mProbability = NOT_A_PROBABILITY;
-    mChildrenPosFieldPos = NOT_A_DICT_POS;
-    mChildrenPos = NOT_A_DICT_POS;
-    mBigramLinkedNodePos = NOT_A_DICT_POS;
-    mShortcutPos = NOT_A_DICT_POS;
-    mBigramPos = NOT_A_DICT_POS;
-    mSiblingPos = NOT_A_DICT_POS;
-}
-
-}
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h
deleted file mode 100644
index 3b36d42..0000000
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2013, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_DYNAMIC_PATRICIA_TRIE_NODE_READER_H
-#define LATINIME_DYNAMIC_PATRICIA_TRIE_NODE_READER_H
-
-#include <stdint.h>
-
-#include "defines.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
-#include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
-
-namespace latinime {
-
-class BufferWithExtendableBuffer;
-class DictionaryBigramsStructurePolicy;
-class DictionaryShortcutsStructurePolicy;
-
-/*
- * This class is used for helping to read nodes of dynamic patricia trie. This class handles moved
- * node and reads node attributes.
- */
-class DynamicPatriciaTrieNodeReader {
- public:
-    DynamicPatriciaTrieNodeReader(const BufferWithExtendableBuffer *const buffer,
-            const DictionaryBigramsStructurePolicy *const bigramsPolicy,
-            const DictionaryShortcutsStructurePolicy *const shortcutsPolicy)
-            : mBuffer(buffer), mBigramsPolicy(bigramsPolicy),
-              mShortcutsPolicy(shortcutsPolicy), mHeadPos(NOT_A_DICT_POS), mFlags(0),
-              mParentPos(NOT_A_DICT_POS), mCodePointCount(0), mProbabilityFieldPos(NOT_A_DICT_POS),
-              mProbability(NOT_A_PROBABILITY), mChildrenPosFieldPos(NOT_A_DICT_POS),
-              mChildrenPos(NOT_A_DICT_POS), mBigramLinkedNodePos(NOT_A_DICT_POS),
-              mShortcutPos(NOT_A_DICT_POS),  mBigramPos(NOT_A_DICT_POS),
-              mSiblingPos(NOT_A_DICT_POS) {}
-
-    ~DynamicPatriciaTrieNodeReader() {}
-
-    // Reads PtNode information from dictionary buffer and updates members with the information.
-    AK_FORCE_INLINE void fetchNodeInfoInBufferFromPtNodePos(const int ptNodePos) {
-        fetchNodeInfoInBufferFromPtNodePosAndGetNodeCodePoints(ptNodePos ,
-                0 /* maxCodePointCount */, 0 /* outCodePoints */);
-    }
-
-    AK_FORCE_INLINE void fetchNodeInfoInBufferFromPtNodePosAndGetNodeCodePoints(
-            const int ptNodePos, const int maxCodePointCount, int *const outCodePoints) {
-        mSiblingPos = NOT_A_DICT_POS;
-        mBigramLinkedNodePos = NOT_A_DICT_POS;
-        fetchPtNodeInfoFromBufferAndProcessMovedPtNode(ptNodePos, maxCodePointCount, outCodePoints);
-    }
-
-    // HeadPos is different from NodePos when the current PtNode is a moved PtNode.
-    AK_FORCE_INLINE int getHeadPos() const {
-        return mHeadPos;
-    }
-
-    // Flags
-    AK_FORCE_INLINE bool isDeleted() const {
-        return DynamicPatriciaTrieReadingUtils::isDeleted(mFlags);
-    }
-
-    AK_FORCE_INLINE bool hasChildren() const {
-        return mChildrenPos != NOT_A_DICT_POS;
-    }
-
-    AK_FORCE_INLINE bool isTerminal() const {
-        return PatriciaTrieReadingUtils::isTerminal(mFlags);
-    }
-
-    AK_FORCE_INLINE bool isBlacklisted() const {
-        return PatriciaTrieReadingUtils::isBlacklisted(mFlags);
-    }
-
-    AK_FORCE_INLINE bool isNotAWord() const {
-        return PatriciaTrieReadingUtils::isNotAWord(mFlags);
-    }
-
-    // Parent node position
-    AK_FORCE_INLINE int getParentPos() const {
-        return mParentPos;
-    }
-
-    // Number of code points
-    AK_FORCE_INLINE uint8_t getCodePointCount() const {
-        return mCodePointCount;
-    }
-
-    // Probability
-    AK_FORCE_INLINE int getProbabilityFieldPos() const {
-        return mProbabilityFieldPos;
-    }
-
-    AK_FORCE_INLINE int getProbability() const {
-        return mProbability;
-    }
-
-    // Children PtNode array position
-    AK_FORCE_INLINE int getChildrenPosFieldPos() const {
-        return mChildrenPosFieldPos;
-    }
-
-    AK_FORCE_INLINE int getChildrenPos() const {
-        return mChildrenPos;
-    }
-
-    // Bigram linked node position.
-    AK_FORCE_INLINE int getBigramLinkedNodePos() const {
-        return mBigramLinkedNodePos;
-    }
-
-    // Shortcutlist position
-    AK_FORCE_INLINE int getShortcutPos() const {
-        return mShortcutPos;
-    }
-
-    // Bigrams position
-    AK_FORCE_INLINE int getBigramsPos() const {
-        return mBigramPos;
-    }
-
-    // Sibling node position
-    AK_FORCE_INLINE int getSiblingNodePos() const {
-        return mSiblingPos;
-    }
-
- private:
-    DISALLOW_COPY_AND_ASSIGN(DynamicPatriciaTrieNodeReader);
-
-    const BufferWithExtendableBuffer *const mBuffer;
-    const DictionaryBigramsStructurePolicy *const mBigramsPolicy;
-    const DictionaryShortcutsStructurePolicy *const mShortcutsPolicy;
-    int mHeadPos;
-    DynamicPatriciaTrieReadingUtils::NodeFlags mFlags;
-    int mParentPos;
-    uint8_t mCodePointCount;
-    int mProbabilityFieldPos;
-    int mProbability;
-    int mChildrenPosFieldPos;
-    int mChildrenPos;
-    int mBigramLinkedNodePos;
-    int mShortcutPos;
-    int mBigramPos;
-    int mSiblingPos;
-
-    void fetchPtNodeInfoFromBufferAndProcessMovedPtNode(const int ptNodePos,
-            const int maxCodePointCount, int *const outCodePoints);
-
-    void invalidatePtNodeInfo();
-};
-} // namespace latinime
-#endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_NODE_READER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp
deleted file mode 100644
index 495b146..0000000
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp
+++ /dev/null
@@ -1,380 +0,0 @@
-/*
- * Copyright (C) 2013, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h"
-
-#include <cstdio>
-#include <cstring>
-#include <ctime>
-
-#include "defines.h"
-#include "suggest/core/dicnode/dic_node.h"
-#include "suggest/core/dicnode/dic_node_vector.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h"
-#include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
-#include "suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h"
-#include "suggest/policyimpl/dictionary/utils/probability_utils.h"
-
-namespace latinime {
-
-// Note that these are corresponding definitions in Java side in BinaryDictionaryTests and
-// BinaryDictionaryDecayingTests.
-const char *const DynamicPatriciaTriePolicy::UNIGRAM_COUNT_QUERY = "UNIGRAM_COUNT";
-const char *const DynamicPatriciaTriePolicy::BIGRAM_COUNT_QUERY = "BIGRAM_COUNT";
-const char *const DynamicPatriciaTriePolicy::MAX_UNIGRAM_COUNT_QUERY = "MAX_UNIGRAM_COUNT";
-const char *const DynamicPatriciaTriePolicy::MAX_BIGRAM_COUNT_QUERY = "MAX_BIGRAM_COUNT";
-const char *const DynamicPatriciaTriePolicy::SET_NEEDS_TO_DECAY_FOR_TESTING_QUERY =
-        "SET_NEEDS_TO_DECAY_FOR_TESTING";
-const int DynamicPatriciaTriePolicy::MAX_DICT_EXTENDED_REGION_SIZE = 1024 * 1024;
-const int DynamicPatriciaTriePolicy::MIN_DICT_SIZE_TO_REFUSE_DYNAMIC_OPERATIONS =
-        DynamicPatriciaTrieWritingHelper::MAX_DICTIONARY_SIZE - 1024;
-
-void DynamicPatriciaTriePolicy::createAndGetAllChildNodes(const DicNode *const dicNode,
-        DicNodeVector *const childDicNodes) const {
-    if (!dicNode->hasChildren()) {
-        return;
-    }
-    DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer,
-            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
-    readingHelper.initWithPtNodeArrayPos(dicNode->getChildrenPos());
-    const DynamicPatriciaTrieNodeReader *const nodeReader = readingHelper.getNodeReader();
-    while (!readingHelper.isEnd()) {
-        bool isTerminal = nodeReader->isTerminal() && !nodeReader->isDeleted();
-        if (isTerminal && mHeaderPolicy.isDecayingDict()) {
-            // A DecayingDict may have a terminal PtNode that has a terminal DicNode whose
-            // probability is NOT_A_PROBABILITY. In such case, we don't want to treat it as a
-            // valid terminal DicNode.
-            isTerminal = getProbability(nodeReader->getProbability(), NOT_A_PROBABILITY)
-                    != NOT_A_PROBABILITY;
-        }
-        childDicNodes->pushLeavingChild(dicNode, nodeReader->getHeadPos(),
-                nodeReader->getChildrenPos(), nodeReader->getProbability(), isTerminal,
-                nodeReader->hasChildren(), nodeReader->isBlacklisted() || nodeReader->isNotAWord(),
-                nodeReader->getCodePointCount(), readingHelper.getMergedNodeCodePoints());
-        readingHelper.readNextSiblingNode();
-    }
-}
-
-int DynamicPatriciaTriePolicy::getCodePointsAndProbabilityAndReturnCodePointCount(
-        const int ptNodePos, const int maxCodePointCount, int *const outCodePoints,
-        int *const outUnigramProbability) const {
-    // This method traverses parent nodes from the terminal by following parent pointers; thus,
-    // node code points are stored in the buffer in the reverse order.
-    int reverseCodePoints[maxCodePointCount];
-    DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer,
-            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
-    // First, read the terminal node and get its probability.
-    readingHelper.initWithPtNodePos(ptNodePos);
-    if (!readingHelper.isValidTerminalNode()) {
-        // Node at the ptNodePos is not a valid terminal node.
-        *outUnigramProbability = NOT_A_PROBABILITY;
-        return 0;
-    }
-    // Store terminal node probability.
-    *outUnigramProbability = readingHelper.getNodeReader()->getProbability();
-    // Then, following parent node link to the dictionary root and fetch node code points.
-    while (!readingHelper.isEnd()) {
-        if (readingHelper.getTotalCodePointCount() > maxCodePointCount) {
-            // The ptNodePos is not a valid terminal node position in the dictionary.
-            *outUnigramProbability = NOT_A_PROBABILITY;
-            return 0;
-        }
-        // Store node code points to buffer in the reverse order.
-        readingHelper.fetchMergedNodeCodePointsInReverseOrder(
-                readingHelper.getPrevTotalCodePointCount(), reverseCodePoints);
-        // Follow parent node toward the root node.
-        readingHelper.readParentNode();
-    }
-    if (readingHelper.isError()) {
-        // The node position or the dictionary is invalid.
-        *outUnigramProbability = NOT_A_PROBABILITY;
-        return 0;
-    }
-    // Reverse the stored code points to output them.
-    const int codePointCount = readingHelper.getTotalCodePointCount();
-    for (int i = 0; i < codePointCount; ++i) {
-        outCodePoints[i] = reverseCodePoints[codePointCount - i - 1];
-    }
-    return codePointCount;
-}
-
-int DynamicPatriciaTriePolicy::getTerminalNodePositionOfWord(const int *const inWord,
-        const int length, const bool forceLowerCaseSearch) const {
-    int searchCodePoints[length];
-    for (int i = 0; i < length; ++i) {
-        searchCodePoints[i] = forceLowerCaseSearch ? CharUtils::toLowerCase(inWord[i]) : inWord[i];
-    }
-    DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer,
-            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
-    readingHelper.initWithPtNodeArrayPos(getRootPosition());
-    const DynamicPatriciaTrieNodeReader *const nodeReader = readingHelper.getNodeReader();
-    while (!readingHelper.isEnd()) {
-        const int matchedCodePointCount = readingHelper.getPrevTotalCodePointCount();
-        if (readingHelper.getTotalCodePointCount() > length
-                || !readingHelper.isMatchedCodePoint(0 /* index */,
-                        searchCodePoints[matchedCodePointCount])) {
-            // Current node has too many code points or its first code point is different from
-            // target code point. Skip this node and read the next sibling node.
-            readingHelper.readNextSiblingNode();
-            continue;
-        }
-        // Check following merged node code points.
-        const int nodeCodePointCount = nodeReader->getCodePointCount();
-        for (int j = 1; j < nodeCodePointCount; ++j) {
-            if (!readingHelper.isMatchedCodePoint(
-                    j, searchCodePoints[matchedCodePointCount + j])) {
-                // Different code point is found. The given word is not included in the dictionary.
-                return NOT_A_DICT_POS;
-            }
-        }
-        // All characters are matched.
-        if (length == readingHelper.getTotalCodePointCount()) {
-            // Terminal position is found.
-            return nodeReader->getHeadPos();
-        }
-        if (!nodeReader->hasChildren()) {
-            return NOT_A_DICT_POS;
-        }
-        // Advance to the children nodes.
-        readingHelper.readChildNode();
-    }
-    // If we already traversed the tree further than the word is long, there means
-    // there was no match (or we would have found it).
-    return NOT_A_DICT_POS;
-}
-
-int DynamicPatriciaTriePolicy::getProbability(const int unigramProbability,
-        const int bigramProbability) const {
-    if (mHeaderPolicy.isDecayingDict()) {
-        return ForgettingCurveUtils::getProbability(unigramProbability, bigramProbability);
-    } else {
-        if (unigramProbability == NOT_A_PROBABILITY) {
-            return NOT_A_PROBABILITY;
-        } else if (bigramProbability == NOT_A_PROBABILITY) {
-            return ProbabilityUtils::backoff(unigramProbability);
-        } else {
-            return ProbabilityUtils::computeProbabilityForBigram(unigramProbability,
-                    bigramProbability);
-        }
-    }
-}
-
-int DynamicPatriciaTriePolicy::getUnigramProbabilityOfPtNode(const int ptNodePos) const {
-    if (ptNodePos == NOT_A_DICT_POS) {
-        return NOT_A_PROBABILITY;
-    }
-    DynamicPatriciaTrieNodeReader nodeReader(&mBufferWithExtendableBuffer,
-            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
-    nodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos);
-    if (nodeReader.isDeleted() || nodeReader.isBlacklisted() || nodeReader.isNotAWord()) {
-        return NOT_A_PROBABILITY;
-    }
-    return getProbability(nodeReader.getProbability(), NOT_A_PROBABILITY);
-}
-
-int DynamicPatriciaTriePolicy::getShortcutPositionOfPtNode(const int ptNodePos) const {
-    if (ptNodePos == NOT_A_DICT_POS) {
-        return NOT_A_DICT_POS;
-    }
-    DynamicPatriciaTrieNodeReader nodeReader(&mBufferWithExtendableBuffer,
-            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
-    nodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos);
-    if (nodeReader.isDeleted()) {
-        return NOT_A_DICT_POS;
-    }
-    return nodeReader.getShortcutPos();
-}
-
-int DynamicPatriciaTriePolicy::getBigramsPositionOfPtNode(const int ptNodePos) const {
-    if (ptNodePos == NOT_A_DICT_POS) {
-        return NOT_A_DICT_POS;
-    }
-    DynamicPatriciaTrieNodeReader nodeReader(&mBufferWithExtendableBuffer,
-            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
-    nodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos);
-    if (nodeReader.isDeleted()) {
-        return NOT_A_DICT_POS;
-    }
-    return nodeReader.getBigramsPos();
-}
-
-bool DynamicPatriciaTriePolicy::addUnigramWord(const int *const word, const int length,
-        const int probability) {
-    if (!mBuffer->isUpdatable()) {
-        AKLOGI("Warning: addUnigramWord() is called for non-updatable dictionary.");
-        return false;
-    }
-    if (mBufferWithExtendableBuffer.getTailPosition()
-            >= MIN_DICT_SIZE_TO_REFUSE_DYNAMIC_OPERATIONS) {
-        AKLOGE("The dictionary is too large to dynamically update.");
-        return false;
-    }
-    DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer,
-            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
-    readingHelper.initWithPtNodeArrayPos(getRootPosition());
-    DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
-            &mBigramListPolicy, &mShortcutListPolicy, mHeaderPolicy.isDecayingDict());
-    bool addedNewUnigram = false;
-    if (writingHelper.addUnigramWord(&readingHelper, word, length, probability,
-            &addedNewUnigram)) {
-        if (addedNewUnigram) {
-            mUnigramCount++;
-        }
-        return true;
-    } else {
-        return false;
-    }
-}
-
-bool DynamicPatriciaTriePolicy::addBigramWords(const int *const word0, const int length0,
-        const int *const word1, const int length1, const int probability) {
-    if (!mBuffer->isUpdatable()) {
-        AKLOGI("Warning: addBigramWords() is called for non-updatable dictionary.");
-        return false;
-    }
-    if (mBufferWithExtendableBuffer.getTailPosition()
-            >= MIN_DICT_SIZE_TO_REFUSE_DYNAMIC_OPERATIONS) {
-        AKLOGE("The dictionary is too large to dynamically update.");
-        return false;
-    }
-    const int word0Pos = getTerminalNodePositionOfWord(word0, length0,
-            false /* forceLowerCaseSearch */);
-    if (word0Pos == NOT_A_DICT_POS) {
-        return false;
-    }
-    const int word1Pos = getTerminalNodePositionOfWord(word1, length1,
-            false /* forceLowerCaseSearch */);
-    if (word1Pos == NOT_A_DICT_POS) {
-        return false;
-    }
-    DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
-            &mBigramListPolicy, &mShortcutListPolicy, mHeaderPolicy.isDecayingDict());
-    bool addedNewBigram = false;
-    if (writingHelper.addBigramWords(word0Pos, word1Pos, probability, &addedNewBigram)) {
-        if (addedNewBigram) {
-            mBigramCount++;
-        }
-        return true;
-    } else {
-        return false;
-    }
-}
-
-bool DynamicPatriciaTriePolicy::removeBigramWords(const int *const word0, const int length0,
-        const int *const word1, const int length1) {
-    if (!mBuffer->isUpdatable()) {
-        AKLOGI("Warning: removeBigramWords() is called for non-updatable dictionary.");
-        return false;
-    }
-    if (mBufferWithExtendableBuffer.getTailPosition()
-            >= MIN_DICT_SIZE_TO_REFUSE_DYNAMIC_OPERATIONS) {
-        AKLOGE("The dictionary is too large to dynamically update.");
-        return false;
-    }
-    const int word0Pos = getTerminalNodePositionOfWord(word0, length0,
-            false /* forceLowerCaseSearch */);
-    if (word0Pos == NOT_A_DICT_POS) {
-        return false;
-    }
-    const int word1Pos = getTerminalNodePositionOfWord(word1, length1,
-            false /* forceLowerCaseSearch */);
-    if (word1Pos == NOT_A_DICT_POS) {
-        return false;
-    }
-    DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
-            &mBigramListPolicy, &mShortcutListPolicy, mHeaderPolicy.isDecayingDict());
-    if (writingHelper.removeBigramWords(word0Pos, word1Pos)) {
-        mBigramCount--;
-        return true;
-    } else {
-        return false;
-    }
-}
-
-void DynamicPatriciaTriePolicy::flush(const char *const filePath) {
-    if (!mBuffer->isUpdatable()) {
-        AKLOGI("Warning: flush() is called for non-updatable dictionary.");
-        return;
-    }
-    DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
-            &mBigramListPolicy, &mShortcutListPolicy, false /* needsToDecay */);
-    writingHelper.writeToDictFile(filePath, &mHeaderPolicy, mUnigramCount, mBigramCount);
-}
-
-void DynamicPatriciaTriePolicy::flushWithGC(const char *const filePath) {
-    if (!mBuffer->isUpdatable()) {
-        AKLOGI("Warning: flushWithGC() is called for non-updatable dictionary.");
-        return;
-    }
-    const bool needsToDecay = mHeaderPolicy.isDecayingDict()
-            && (mNeedsToDecayForTesting || ForgettingCurveUtils::needsToDecay(
-                    false /* mindsBlockByDecay */, mUnigramCount, mBigramCount, &mHeaderPolicy));
-    DynamicBigramListPolicy bigramListPolicyForGC(&mHeaderPolicy, &mBufferWithExtendableBuffer,
-            &mShortcutListPolicy, needsToDecay);
-    DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
-            &bigramListPolicyForGC, &mShortcutListPolicy, needsToDecay);
-    writingHelper.writeToDictFileWithGC(getRootPosition(), filePath, &mHeaderPolicy);
-    mNeedsToDecayForTesting = false;
-}
-
-bool DynamicPatriciaTriePolicy::needsToRunGC(const bool mindsBlockByGC) const {
-    if (!mBuffer->isUpdatable()) {
-        AKLOGI("Warning: needsToRunGC() is called for non-updatable dictionary.");
-        return false;
-    }
-    if (mBufferWithExtendableBuffer.isNearSizeLimit()) {
-        // Additional buffer size is near the limit.
-        return true;
-    } else if (mHeaderPolicy.getExtendedRegionSize()
-            + mBufferWithExtendableBuffer.getUsedAdditionalBufferSize()
-                    > MAX_DICT_EXTENDED_REGION_SIZE) {
-        // Total extended region size exceeds the limit.
-        return true;
-    } else if (mBufferWithExtendableBuffer.getTailPosition()
-            >= MIN_DICT_SIZE_TO_REFUSE_DYNAMIC_OPERATIONS
-                    && mBufferWithExtendableBuffer.getUsedAdditionalBufferSize() > 0) {
-        // Needs to reduce dictionary size.
-        return true;
-    } else if (mHeaderPolicy.isDecayingDict()) {
-        return mNeedsToDecayForTesting || ForgettingCurveUtils::needsToDecay(
-                mindsBlockByGC, mUnigramCount, mBigramCount, &mHeaderPolicy);
-    }
-    return false;
-}
-
-void DynamicPatriciaTriePolicy::getProperty(const char *const query, char *const outResult,
-        const int maxResultLength) {
-    if (strncmp(query, UNIGRAM_COUNT_QUERY, maxResultLength) == 0) {
-        snprintf(outResult, maxResultLength, "%d", mUnigramCount);
-    } else if (strncmp(query, BIGRAM_COUNT_QUERY, maxResultLength) == 0) {
-        snprintf(outResult, maxResultLength, "%d", mBigramCount);
-    } else if (strncmp(query, MAX_UNIGRAM_COUNT_QUERY, maxResultLength) == 0) {
-        snprintf(outResult, maxResultLength, "%d",
-                mHeaderPolicy.isDecayingDict() ? ForgettingCurveUtils::MAX_UNIGRAM_COUNT :
-                        static_cast<int>(DynamicPatriciaTrieWritingHelper::MAX_DICTIONARY_SIZE));
-    } else if (strncmp(query, MAX_BIGRAM_COUNT_QUERY, maxResultLength) == 0) {
-        snprintf(outResult, maxResultLength, "%d",
-                mHeaderPolicy.isDecayingDict() ? ForgettingCurveUtils::MAX_BIGRAM_COUNT :
-                        static_cast<int>(DynamicPatriciaTrieWritingHelper::MAX_DICTIONARY_SIZE));
-    } else if (strncmp(query, SET_NEEDS_TO_DECAY_FOR_TESTING_QUERY, maxResultLength) == 0) {
-        mNeedsToDecayForTesting = true;
-    }
-}
-
-} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.cpp
deleted file mode 100644
index f108c21..0000000
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.cpp
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2013, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h"
-
-#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
-
-namespace latinime {
-
-// To avoid infinite loop caused by invalid or malicious forward links.
-const int DynamicPatriciaTrieReadingHelper::MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP = 100000;
-const int DynamicPatriciaTrieReadingHelper::MAX_NODE_ARRAY_COUNT_TO_AVOID_INFINITE_LOOP = 100000;
-const size_t DynamicPatriciaTrieReadingHelper::MAX_READING_STATE_STACK_SIZE = MAX_WORD_LENGTH;
-
-// Visits all PtNodes in post-order depth first manner.
-// For example, visits c -> b -> y -> x -> a for the following dictionary:
-// a _ b _ c
-//   \ x _ y
-bool DynamicPatriciaTrieReadingHelper::traverseAllPtNodesInPostorderDepthFirstManner(
-        TraversingEventListener *const listener) {
-    bool alreadyVisitedChildren = false;
-    // Descend from the root to the root PtNode array.
-    if (!listener->onDescend(getPosOfLastPtNodeArrayHead())) {
-        return false;
-    }
-    while (!isEnd()) {
-        if (!alreadyVisitedChildren) {
-            if (mNodeReader.hasChildren()) {
-                // Move to the first child.
-                if (!listener->onDescend(mNodeReader.getChildrenPos())) {
-                    return false;
-                }
-                pushReadingStateToStack();
-                readChildNode();
-            } else {
-                alreadyVisitedChildren = true;
-            }
-        } else {
-            if (!listener->onVisitingPtNode(&mNodeReader, mMergedNodeCodePoints)) {
-                return false;
-            }
-            readNextSiblingNode();
-            if (isEnd()) {
-                // All PtNodes in current linked PtNode arrays have been visited.
-                // Return to the parent.
-                if (!listener->onReadingPtNodeArrayTail()) {
-                    return false;
-                }
-                if (mReadingStateStack.size() <= 0) {
-                    break;
-                }
-                if (!listener->onAscend()) {
-                    return false;
-                }
-                popReadingStateFromStack();
-                alreadyVisitedChildren = true;
-            } else {
-                // Process sibling PtNode.
-                alreadyVisitedChildren = false;
-            }
-        }
-    }
-    // Ascend from the root PtNode array to the root.
-    if (!listener->onAscend()) {
-        return false;
-    }
-    return !isError();
-}
-
-// Visits all PtNodes in PtNode array level pre-order depth first manner, which is the same order
-// that PtNodes are written in the dictionary buffer.
-// For example, visits a -> b -> x -> c -> y for the following dictionary:
-// a _ b _ c
-//   \ x _ y
-bool DynamicPatriciaTrieReadingHelper::traverseAllPtNodesInPtNodeArrayLevelPreorderDepthFirstManner(
-        TraversingEventListener *const listener) {
-    bool alreadyVisitedAllPtNodesInArray = false;
-    bool alreadyVisitedChildren = false;
-    // Descend from the root to the root PtNode array.
-    if (!listener->onDescend(getPosOfLastPtNodeArrayHead())) {
-        return false;
-    }
-    if (isEnd()) {
-        // Empty dictionary. Needs to notify the listener of the tail of empty PtNode array.
-        if (!listener->onReadingPtNodeArrayTail()) {
-            return false;
-        }
-    }
-    pushReadingStateToStack();
-    while (!isEnd()) {
-        if (alreadyVisitedAllPtNodesInArray) {
-            if (alreadyVisitedChildren) {
-                // Move to next sibling PtNode's children.
-                readNextSiblingNode();
-                if (isEnd()) {
-                    // Return to the parent PTNode.
-                    if (!listener->onAscend()) {
-                        return false;
-                    }
-                    if (mReadingStateStack.size() <= 0) {
-                        break;
-                    }
-                    popReadingStateFromStack();
-                    alreadyVisitedChildren = true;
-                    alreadyVisitedAllPtNodesInArray = true;
-                } else {
-                    alreadyVisitedChildren = false;
-                }
-            } else {
-                if (mNodeReader.hasChildren()) {
-                    // Move to the first child.
-                    if (!listener->onDescend(mNodeReader.getChildrenPos())) {
-                        return false;
-                    }
-                    pushReadingStateToStack();
-                    readChildNode();
-                    // Push state to return the head of PtNode array.
-                    pushReadingStateToStack();
-                    alreadyVisitedAllPtNodesInArray = false;
-                    alreadyVisitedChildren = false;
-                } else {
-                    alreadyVisitedChildren = true;
-                }
-            }
-        } else {
-            if (!listener->onVisitingPtNode(&mNodeReader, mMergedNodeCodePoints)) {
-                return false;
-            }
-            readNextSiblingNode();
-            if (isEnd()) {
-                if (!listener->onReadingPtNodeArrayTail()) {
-                    return false;
-                }
-                // Return to the head of current PtNode array.
-                popReadingStateFromStack();
-                alreadyVisitedAllPtNodesInArray = true;
-            }
-        }
-    }
-    popReadingStateFromStack();
-    // Ascend from the root PtNode array to the root.
-    if (!listener->onAscend()) {
-        return false;
-    }
-    return !isError();
-}
-
-// Read node array size and process empty node arrays. Nodes and arrays are counted up in this
-// method to avoid an infinite loop.
-void DynamicPatriciaTrieReadingHelper::nextPtNodeArray() {
-    if (mReadingState.mPos < 0 || mReadingState.mPos >= mBuffer->getTailPosition()) {
-        // Reading invalid position because of a bug or a broken dictionary.
-        AKLOGE("Reading PtNode array info from invalid dictionary position: %d, dict size: %d",
-                mReadingState.mPos, mBuffer->getTailPosition());
-        ASSERT(false);
-        mIsError = true;
-        mReadingState.mPos = NOT_A_DICT_POS;
-        return;
-    }
-    mReadingState.mPosOfLastPtNodeArrayHead = mReadingState.mPos;
-    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(mReadingState.mPos);
-    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
-    if (usesAdditionalBuffer) {
-        mReadingState.mPos -= mBuffer->getOriginalBufferSize();
-    }
-    mReadingState.mNodeCount = PatriciaTrieReadingUtils::getPtNodeArraySizeAndAdvancePosition(
-            dictBuf, &mReadingState.mPos);
-    if (usesAdditionalBuffer) {
-        mReadingState.mPos += mBuffer->getOriginalBufferSize();
-    }
-    // Count up nodes and node arrays to avoid infinite loop.
-    mReadingState.mTotalNodeCount += mReadingState.mNodeCount;
-    mReadingState.mNodeArrayCount++;
-    if (mReadingState.mNodeCount < 0
-            || mReadingState.mTotalNodeCount > MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP
-            || mReadingState.mNodeArrayCount > MAX_NODE_ARRAY_COUNT_TO_AVOID_INFINITE_LOOP) {
-        // Invalid dictionary.
-        AKLOGI("Invalid dictionary. nodeCount: %d, totalNodeCount: %d, MAX_CHILD_COUNT: %d"
-                "nodeArrayCount: %d, MAX_NODE_ARRAY_COUNT: %d",
-                mReadingState.mNodeCount, mReadingState.mTotalNodeCount,
-                MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP, mReadingState.mNodeArrayCount,
-                MAX_NODE_ARRAY_COUNT_TO_AVOID_INFINITE_LOOP);
-        ASSERT(false);
-        mIsError = true;
-        mReadingState.mPos = NOT_A_DICT_POS;
-        return;
-    }
-    if (mReadingState.mNodeCount == 0) {
-        // Empty node array. Try following forward link.
-        followForwardLink();
-    }
-}
-
-// Follow the forward link and read the next node array if exists.
-void DynamicPatriciaTrieReadingHelper::followForwardLink() {
-    if (mReadingState.mPos < 0 || mReadingState.mPos >= mBuffer->getTailPosition()) {
-        // Reading invalid position because of bug or broken dictionary.
-        AKLOGE("Reading forward link from invalid dictionary position: %d, dict size: %d",
-                mReadingState.mPos, mBuffer->getTailPosition());
-        ASSERT(false);
-        mIsError = true;
-        mReadingState.mPos = NOT_A_DICT_POS;
-        return;
-    }
-    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(mReadingState.mPos);
-    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
-    if (usesAdditionalBuffer) {
-        mReadingState.mPos -= mBuffer->getOriginalBufferSize();
-    }
-    const int forwardLinkPosition =
-            DynamicPatriciaTrieReadingUtils::getForwardLinkPosition(dictBuf, mReadingState.mPos);
-    if (usesAdditionalBuffer) {
-        mReadingState.mPos += mBuffer->getOriginalBufferSize();
-    }
-    mReadingState.mPosOfLastForwardLinkField = mReadingState.mPos;
-    if (DynamicPatriciaTrieReadingUtils::isValidForwardLinkPosition(forwardLinkPosition)) {
-        // Follow the forward link.
-        mReadingState.mPos += forwardLinkPosition;
-        nextPtNodeArray();
-    } else {
-        // All node arrays have been read.
-        mReadingState.mPos = NOT_A_DICT_POS;
-    }
-}
-
-} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h
deleted file mode 100644
index a71c069..0000000
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * Copyright (C) 2013, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_DYNAMIC_PATRICIA_TRIE_READING_HELPER_H
-#define LATINIME_DYNAMIC_PATRICIA_TRIE_READING_HELPER_H
-
-#include <cstddef>
-#include <vector>
-
-#include "defines.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
-#include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
-
-namespace latinime {
-
-class BufferWithExtendableBuffer;
-class DictionaryBigramsStructurePolicy;
-class DictionaryShortcutsStructurePolicy;
-
-/*
- * This class is used for traversing dynamic patricia trie. This class supports iterating nodes and
- * dealing with additional buffer. This class counts nodes and node arrays to avoid infinite loop.
- */
-class DynamicPatriciaTrieReadingHelper {
- public:
-    class TraversingEventListener {
-     public:
-        virtual ~TraversingEventListener() {};
-
-        // Returns whether the event handling was succeeded or not.
-        virtual bool onAscend() = 0;
-
-        // Returns whether the event handling was succeeded or not.
-        virtual bool onDescend(const int ptNodeArrayPos) = 0;
-
-        // Returns whether the event handling was succeeded or not.
-        virtual bool onReadingPtNodeArrayTail() = 0;
-
-        // Returns whether the event handling was succeeded or not.
-        virtual bool onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
-                const int *const nodeCodePoints) = 0;
-
-     protected:
-        TraversingEventListener() {};
-
-     private:
-        DISALLOW_COPY_AND_ASSIGN(TraversingEventListener);
-    };
-
-    DynamicPatriciaTrieReadingHelper(const BufferWithExtendableBuffer *const buffer,
-            const DictionaryBigramsStructurePolicy *const bigramsPolicy,
-            const DictionaryShortcutsStructurePolicy *const shortcutsPolicy)
-            : mIsError(false), mReadingState(), mBuffer(buffer),
-              mNodeReader(mBuffer, bigramsPolicy, shortcutsPolicy), mReadingStateStack() {}
-
-    ~DynamicPatriciaTrieReadingHelper() {}
-
-    AK_FORCE_INLINE bool isError() const {
-        return mIsError;
-    }
-
-    AK_FORCE_INLINE bool isEnd() const {
-        return mReadingState.mPos == NOT_A_DICT_POS;
-    }
-
-    // Initialize reading state with the head position of a PtNode array.
-    AK_FORCE_INLINE void initWithPtNodeArrayPos(const int ptNodeArrayPos) {
-        if (ptNodeArrayPos == NOT_A_DICT_POS) {
-            mReadingState.mPos = NOT_A_DICT_POS;
-        } else {
-            mIsError = false;
-            mReadingState.mPos = ptNodeArrayPos;
-            mReadingState.mPrevTotalCodePointCount = 0;
-            mReadingState.mTotalNodeCount = 0;
-            mReadingState.mNodeArrayCount = 0;
-            mReadingState.mPosOfLastForwardLinkField = NOT_A_DICT_POS;
-            mReadingStateStack.clear();
-            nextPtNodeArray();
-            if (!isEnd()) {
-                fetchPtNodeInfo();
-            }
-        }
-    }
-
-    // Initialize reading state with the head position of a node.
-    AK_FORCE_INLINE void initWithPtNodePos(const int ptNodePos) {
-        if (ptNodePos == NOT_A_DICT_POS) {
-            mReadingState.mPos = NOT_A_DICT_POS;
-        } else {
-            mIsError = false;
-            mReadingState.mPos = ptNodePos;
-            mReadingState.mNodeCount = 1;
-            mReadingState.mPrevTotalCodePointCount = 0;
-            mReadingState.mTotalNodeCount = 1;
-            mReadingState.mNodeArrayCount = 1;
-            mReadingState.mPosOfLastForwardLinkField = NOT_A_DICT_POS;
-            mReadingState.mPosOfLastPtNodeArrayHead = NOT_A_DICT_POS;
-            mReadingStateStack.clear();
-            fetchPtNodeInfo();
-        }
-    }
-
-    AK_FORCE_INLINE const DynamicPatriciaTrieNodeReader* getNodeReader() const {
-        return &mNodeReader;
-    }
-
-    AK_FORCE_INLINE bool isValidTerminalNode() const {
-        return !isEnd() && !mNodeReader.isDeleted() && mNodeReader.isTerminal();
-    }
-
-    AK_FORCE_INLINE bool isMatchedCodePoint(const int index, const int codePoint) const {
-        return mMergedNodeCodePoints[index] == codePoint;
-    }
-
-    // Return code point count exclude the last read node's code points.
-    AK_FORCE_INLINE int getPrevTotalCodePointCount() const {
-        return mReadingState.mPrevTotalCodePointCount;
-    }
-
-    // Return code point count include the last read node's code points.
-    AK_FORCE_INLINE int getTotalCodePointCount() const {
-        return mReadingState.mPrevTotalCodePointCount + mNodeReader.getCodePointCount();
-    }
-
-    AK_FORCE_INLINE void fetchMergedNodeCodePointsInReverseOrder(
-            const int index, int *const outCodePoints) const {
-        const int nodeCodePointCount = mNodeReader.getCodePointCount();
-        for (int i =  0; i < nodeCodePointCount; ++i) {
-            outCodePoints[index + i] = mMergedNodeCodePoints[nodeCodePointCount - 1 - i];
-        }
-    }
-
-    AK_FORCE_INLINE const int *getMergedNodeCodePoints() const {
-        return mMergedNodeCodePoints;
-    }
-
-    AK_FORCE_INLINE void readNextSiblingNode() {
-        mReadingState.mNodeCount -= 1;
-        mReadingState.mPos = mNodeReader.getSiblingNodePos();
-        if (mReadingState.mNodeCount <= 0) {
-            // All nodes in the current node array have been read.
-            followForwardLink();
-            if (!isEnd()) {
-                fetchPtNodeInfo();
-            }
-        } else {
-            fetchPtNodeInfo();
-        }
-    }
-
-    // Read the first child node of the current node.
-    AK_FORCE_INLINE void readChildNode() {
-        if (mNodeReader.hasChildren()) {
-            mReadingState.mPrevTotalCodePointCount += mNodeReader.getCodePointCount();
-            mReadingState.mTotalNodeCount = 0;
-            mReadingState.mNodeArrayCount = 0;
-            mReadingState.mPos = mNodeReader.getChildrenPos();
-            mReadingState.mPosOfLastForwardLinkField = NOT_A_DICT_POS;
-            // Read children node array.
-            nextPtNodeArray();
-            if (!isEnd()) {
-                fetchPtNodeInfo();
-            }
-        } else {
-            mReadingState.mPos = NOT_A_DICT_POS;
-        }
-    }
-
-    // Read the parent node of the current node.
-    AK_FORCE_INLINE void readParentNode() {
-        if (mNodeReader.getParentPos() != NOT_A_DICT_POS) {
-            mReadingState.mPrevTotalCodePointCount += mNodeReader.getCodePointCount();
-            mReadingState.mTotalNodeCount = 1;
-            mReadingState.mNodeArrayCount = 1;
-            mReadingState.mNodeCount = 1;
-            mReadingState.mPos = mNodeReader.getParentPos();
-            mReadingState.mPosOfLastForwardLinkField = NOT_A_DICT_POS;
-            mReadingState.mPosOfLastPtNodeArrayHead = NOT_A_DICT_POS;
-            fetchPtNodeInfo();
-        } else {
-            mReadingState.mPos = NOT_A_DICT_POS;
-        }
-    }
-
-    AK_FORCE_INLINE int getPosOfLastForwardLinkField() const {
-        return mReadingState.mPosOfLastForwardLinkField;
-    }
-
-    AK_FORCE_INLINE int getPosOfLastPtNodeArrayHead() const {
-        return mReadingState.mPosOfLastPtNodeArrayHead;
-    }
-
-    AK_FORCE_INLINE void reloadCurrentPtNodeInfo() {
-        if (!isEnd()) {
-            fetchPtNodeInfo();
-        }
-    }
-
-    bool traverseAllPtNodesInPostorderDepthFirstManner(TraversingEventListener *const listener);
-
-    bool traverseAllPtNodesInPtNodeArrayLevelPreorderDepthFirstManner(
-            TraversingEventListener *const listener);
-
- private:
-    DISALLOW_COPY_AND_ASSIGN(DynamicPatriciaTrieReadingHelper);
-
-    class ReadingState {
-     public:
-        // Note that copy constructor and assignment operator are used for this class to use
-        // std::vector.
-        ReadingState() : mPos(NOT_A_DICT_POS), mNodeCount(0), mPrevTotalCodePointCount(0),
-                mTotalNodeCount(0), mNodeArrayCount(0), mPosOfLastForwardLinkField(NOT_A_DICT_POS),
-                mPosOfLastPtNodeArrayHead(NOT_A_DICT_POS) {}
-
-        int mPos;
-        // Node count of a node array.
-        int mNodeCount;
-        int mPrevTotalCodePointCount;
-        int mTotalNodeCount;
-        int mNodeArrayCount;
-        int mPosOfLastForwardLinkField;
-        int mPosOfLastPtNodeArrayHead;
-    };
-
-    static const int MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP;
-    static const int MAX_NODE_ARRAY_COUNT_TO_AVOID_INFINITE_LOOP;
-    static const size_t MAX_READING_STATE_STACK_SIZE;
-
-    // TODO: Introduce error code to track what caused the error.
-    bool mIsError;
-    ReadingState mReadingState;
-    const BufferWithExtendableBuffer *const mBuffer;
-    DynamicPatriciaTrieNodeReader mNodeReader;
-    int mMergedNodeCodePoints[MAX_WORD_LENGTH];
-    std::vector<ReadingState> mReadingStateStack;
-
-    void nextPtNodeArray();
-
-    void followForwardLink();
-
-    AK_FORCE_INLINE void fetchPtNodeInfo() {
-        mNodeReader.fetchNodeInfoInBufferFromPtNodePosAndGetNodeCodePoints(mReadingState.mPos,
-                MAX_WORD_LENGTH, mMergedNodeCodePoints);
-        if (mNodeReader.getCodePointCount() <= 0) {
-            // Empty node is not allowed.
-            mIsError = true;
-            mReadingState.mPos = NOT_A_DICT_POS;
-        }
-    }
-
-    AK_FORCE_INLINE void pushReadingStateToStack() {
-        if (mReadingStateStack.size() > MAX_READING_STATE_STACK_SIZE) {
-            AKLOGI("Reading state stack overflow. Max size: %zd", MAX_READING_STATE_STACK_SIZE);
-            ASSERT(false);
-            mIsError = true;
-            mReadingState.mPos = NOT_A_DICT_POS;
-        } else {
-            mReadingStateStack.push_back(mReadingState);
-        }
-    }
-
-    AK_FORCE_INLINE void popReadingStateFromStack() {
-        if (mReadingStateStack.empty()) {
-            mReadingState.mPos = NOT_A_DICT_POS;
-        } else {
-            mReadingState = mReadingStateStack.back();
-            mReadingStateStack.pop_back();
-            if (!isEnd()) {
-                fetchPtNodeInfo();
-            }
-        }
-    }
-};
-} // namespace latinime
-#endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_READING_HELPER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp
deleted file mode 100644
index 052558b..0000000
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp
+++ /dev/null
@@ -1,558 +0,0 @@
-/*
- * Copyright (C) 2013, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h"
-
-#include "suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h"
-#include "suggest/policyimpl/dictionary/header/header_policy.h"
-#include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
-#include "suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h"
-#include "suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h"
-#include "suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h"
-#include "utils/hash_map_compat.h"
-
-namespace latinime {
-
-const int DynamicPatriciaTrieWritingHelper::CHILDREN_POSITION_FIELD_SIZE = 3;
-// TODO: Make MAX_DICTIONARY_SIZE 8MB.
-const size_t DynamicPatriciaTrieWritingHelper::MAX_DICTIONARY_SIZE = 2 * 1024 * 1024;
-
-bool DynamicPatriciaTrieWritingHelper::addUnigramWord(
-        DynamicPatriciaTrieReadingHelper *const readingHelper,
-        const int *const wordCodePoints, const int codePointCount, const int probability,
-        bool *const outAddedNewUnigram) {
-    int parentPos = NOT_A_DICT_POS;
-    while (!readingHelper->isEnd()) {
-        const int matchedCodePointCount = readingHelper->getPrevTotalCodePointCount();
-        if (!readingHelper->isMatchedCodePoint(0 /* index */,
-                wordCodePoints[matchedCodePointCount])) {
-            // The first code point is different from target code point. Skip this node and read
-            // the next sibling node.
-            readingHelper->readNextSiblingNode();
-            continue;
-        }
-        // Check following merged node code points.
-        const DynamicPatriciaTrieNodeReader *const nodeReader = readingHelper->getNodeReader();
-        const int nodeCodePointCount = nodeReader->getCodePointCount();
-        for (int j = 1; j < nodeCodePointCount; ++j) {
-            const int nextIndex = matchedCodePointCount + j;
-            if (nextIndex >= codePointCount || !readingHelper->isMatchedCodePoint(j,
-                    wordCodePoints[matchedCodePointCount + j])) {
-                *outAddedNewUnigram = true;
-                return reallocatePtNodeAndAddNewPtNodes(nodeReader,
-                        readingHelper->getMergedNodeCodePoints(), j,
-                        getUpdatedProbability(NOT_A_PROBABILITY /* originalProbability */,
-                                probability),
-                        wordCodePoints + matchedCodePointCount,
-                        codePointCount - matchedCodePointCount);
-            }
-        }
-        // All characters are matched.
-        if (codePointCount == readingHelper->getTotalCodePointCount()) {
-            return setPtNodeProbability(nodeReader, probability,
-                    readingHelper->getMergedNodeCodePoints(), outAddedNewUnigram);
-        }
-        if (!nodeReader->hasChildren()) {
-            *outAddedNewUnigram = true;
-            return createChildrenPtNodeArrayAndAChildPtNode(nodeReader,
-                    getUpdatedProbability(NOT_A_PROBABILITY /* originalProbability */, probability),
-                    wordCodePoints + readingHelper->getTotalCodePointCount(),
-                    codePointCount - readingHelper->getTotalCodePointCount());
-        }
-        // Advance to the children nodes.
-        parentPos = nodeReader->getHeadPos();
-        readingHelper->readChildNode();
-    }
-    if (readingHelper->isError()) {
-        // The dictionary is invalid.
-        return false;
-    }
-    int pos = readingHelper->getPosOfLastForwardLinkField();
-    *outAddedNewUnigram = true;
-    return createAndInsertNodeIntoPtNodeArray(parentPos,
-            wordCodePoints + readingHelper->getPrevTotalCodePointCount(),
-            codePointCount - readingHelper->getPrevTotalCodePointCount(),
-            getUpdatedProbability(NOT_A_PROBABILITY /* originalProbability */, probability), &pos);
-}
-
-bool DynamicPatriciaTrieWritingHelper::addBigramWords(const int word0Pos, const int word1Pos,
-        const int probability, bool *const outAddedNewBigram) {
-    int mMergedNodeCodePoints[MAX_WORD_LENGTH];
-    DynamicPatriciaTrieNodeReader nodeReader(mBuffer, mBigramPolicy, mShortcutPolicy);
-    nodeReader.fetchNodeInfoInBufferFromPtNodePosAndGetNodeCodePoints(word0Pos, MAX_WORD_LENGTH,
-            mMergedNodeCodePoints);
-    // Move node to add bigram entry.
-    const int newNodePos = mBuffer->getTailPosition();
-    if (!markNodeAsMovedAndSetPosition(&nodeReader, newNodePos, newNodePos)) {
-        return false;
-    }
-    int writingPos = newNodePos;
-    // Write a new PtNode using original PtNode's info to the tail of the dictionary in mBuffer.
-    if (!writePtNodeToBufferByCopyingPtNodeInfo(mBuffer, &nodeReader, nodeReader.getParentPos(),
-            mMergedNodeCodePoints, nodeReader.getCodePointCount(), nodeReader.getProbability(),
-            &writingPos)) {
-        return false;
-    }
-    nodeReader.fetchNodeInfoInBufferFromPtNodePos(newNodePos);
-    if (nodeReader.getBigramsPos() != NOT_A_DICT_POS) {
-        // Insert a new bigram entry into the existing bigram list.
-        int bigramListPos = nodeReader.getBigramsPos();
-        return mBigramPolicy->addNewBigramEntryToBigramList(word1Pos, probability, &bigramListPos,
-                outAddedNewBigram);
-    } else {
-        // The PtNode doesn't have a bigram list.
-        *outAddedNewBigram = true;
-        // First, Write a bigram entry at the tail position of the PtNode.
-        if (!mBigramPolicy->writeNewBigramEntry(word1Pos, probability, &writingPos)) {
-            return false;
-        }
-        // Then, Mark as the PtNode having bigram list in the flags.
-        const PatriciaTrieReadingUtils::NodeFlags updatedFlags =
-                PatriciaTrieReadingUtils::createAndGetFlags(nodeReader.isBlacklisted(),
-                        nodeReader.isNotAWord(), nodeReader.getProbability() != NOT_A_PROBABILITY,
-                        nodeReader.getShortcutPos() != NOT_A_DICT_POS, true /* hasBigrams */,
-                        nodeReader.getCodePointCount() > 1, CHILDREN_POSITION_FIELD_SIZE);
-        writingPos = newNodePos;
-        // Write updated flags into the moved PtNode's flags field.
-        return DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(mBuffer, updatedFlags,
-                &writingPos);
-    }
-}
-
-// Remove a bigram relation from word0Pos to word1Pos.
-bool DynamicPatriciaTrieWritingHelper::removeBigramWords(const int word0Pos, const int word1Pos) {
-    DynamicPatriciaTrieNodeReader nodeReader(mBuffer, mBigramPolicy, mShortcutPolicy);
-    nodeReader.fetchNodeInfoInBufferFromPtNodePos(word0Pos);
-    if (nodeReader.getBigramsPos() == NOT_A_DICT_POS) {
-        return false;
-    }
-    return mBigramPolicy->removeBigram(nodeReader.getBigramsPos(), word1Pos);
-}
-
-void DynamicPatriciaTrieWritingHelper::writeToDictFile(const char *const fileName,
-        const HeaderPolicy *const headerPolicy, const int unigramCount, const int bigramCount) {
-    BufferWithExtendableBuffer headerBuffer(0 /* originalBuffer */, 0 /* originalBufferSize */);
-    const int extendedRegionSize = headerPolicy->getExtendedRegionSize() +
-            mBuffer->getUsedAdditionalBufferSize();
-    if (!headerPolicy->writeHeaderToBuffer(&headerBuffer, false /* updatesLastUpdatedTime */,
-            false /* updatesLastDecayedTime */, unigramCount, bigramCount, extendedRegionSize)) {
-        return;
-    }
-    DictFileWritingUtils::flushAllHeaderAndBodyToFile(fileName, &headerBuffer, mBuffer);
-}
-
-void DynamicPatriciaTrieWritingHelper::writeToDictFileWithGC(const int rootPtNodeArrayPos,
-        const char *const fileName, const HeaderPolicy *const headerPolicy) {
-    BufferWithExtendableBuffer newDictBuffer(0 /* originalBuffer */, 0 /* originalBufferSize */,
-            MAX_DICTIONARY_SIZE);
-    int unigramCount = 0;
-    int bigramCount = 0;
-    if (mNeedsToDecay) {
-        ForgettingCurveUtils::sTimeKeeper.setCurrentTime();
-    }
-    if (!runGC(rootPtNodeArrayPos, headerPolicy, &newDictBuffer, &unigramCount, &bigramCount)) {
-        return;
-    }
-    BufferWithExtendableBuffer headerBuffer(0 /* originalBuffer */, 0 /* originalBufferSize */);
-    if (!headerPolicy->writeHeaderToBuffer(&headerBuffer, true /* updatesLastUpdatedTime */,
-            mNeedsToDecay, unigramCount, bigramCount, 0 /* extendedRegionSize */)) {
-        return;
-    }
-    DictFileWritingUtils::flushAllHeaderAndBodyToFile(fileName, &headerBuffer, &newDictBuffer);
-}
-
-bool DynamicPatriciaTrieWritingHelper::markNodeAsDeleted(
-        const DynamicPatriciaTrieNodeReader *const nodeToUpdate) {
-    int pos = nodeToUpdate->getHeadPos();
-    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(pos);
-    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
-    if (usesAdditionalBuffer) {
-        pos -= mBuffer->getOriginalBufferSize();
-    }
-    // Read original flags
-    const PatriciaTrieReadingUtils::NodeFlags originalFlags =
-            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictBuf, &pos);
-    const PatriciaTrieReadingUtils::NodeFlags updatedFlags =
-            DynamicPatriciaTrieReadingUtils::updateAndGetFlags(originalFlags, false /* isMoved */,
-                    true /* isDeleted */);
-    int writingPos = nodeToUpdate->getHeadPos();
-    // Update flags.
-    return DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(mBuffer, updatedFlags,
-            &writingPos);
-}
-
-bool DynamicPatriciaTrieWritingHelper::markNodeAsMovedAndSetPosition(
-        const DynamicPatriciaTrieNodeReader *const originalNode, const int movedPos,
-        const int bigramLinkedNodePos) {
-    int pos = originalNode->getHeadPos();
-    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(pos);
-    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
-    if (usesAdditionalBuffer) {
-        pos -= mBuffer->getOriginalBufferSize();
-    }
-    // Read original flags
-    const PatriciaTrieReadingUtils::NodeFlags originalFlags =
-            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictBuf, &pos);
-    const PatriciaTrieReadingUtils::NodeFlags updatedFlags =
-            DynamicPatriciaTrieReadingUtils::updateAndGetFlags(originalFlags, true /* isMoved */,
-                    false /* isDeleted */);
-    int writingPos = originalNode->getHeadPos();
-    // Update flags.
-    if (!DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(mBuffer, updatedFlags,
-            &writingPos)) {
-        return false;
-    }
-    // Update moved position, which is stored in the parent offset field.
-    if (!DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(
-            mBuffer, movedPos, originalNode->getHeadPos(), &writingPos)) {
-        return false;
-    }
-    // Update bigram linked node position, which is stored in the children position field.
-    int childrenPosFieldPos = originalNode->getChildrenPosFieldPos();
-    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(
-            mBuffer, bigramLinkedNodePos, &childrenPosFieldPos)) {
-        return false;
-    }
-    if (originalNode->hasChildren()) {
-        // Update children's parent position.
-        DynamicPatriciaTrieReadingHelper readingHelper(mBuffer, mBigramPolicy, mShortcutPolicy);
-        const DynamicPatriciaTrieNodeReader *const nodeReader = readingHelper.getNodeReader();
-        readingHelper.initWithPtNodeArrayPos(originalNode->getChildrenPos());
-        while (!readingHelper.isEnd()) {
-            int parentOffsetFieldPos = nodeReader->getHeadPos()
-                    + DynamicPatriciaTrieWritingUtils::NODE_FLAG_FIELD_SIZE;
-            if (!DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(
-                    mBuffer, bigramLinkedNodePos, nodeReader->getHeadPos(),
-                    &parentOffsetFieldPos)) {
-                // Parent offset cannot be written because of a bug or a broken dictionary; thus,
-                // we give up to update dictionary.
-                return false;
-            }
-            readingHelper.readNextSiblingNode();
-        }
-    }
-    return true;
-}
-
-// Write new PtNode at writingPos.
-bool DynamicPatriciaTrieWritingHelper::writePtNodeWithFullInfoToBuffer(
-        BufferWithExtendableBuffer *const bufferToWrite, const bool isBlacklisted,
-        const bool isNotAWord, const int parentPos, const int *const codePoints,
-        const int codePointCount, const int probability, const int childrenPos,
-        const int originalBigramListPos, const int originalShortcutListPos,
-        int *const writingPos) {
-    const int nodePos = *writingPos;
-    // Write dummy flags. The Node flags are updated with appropriate flags at the last step of the
-    // PtNode writing.
-    if (!DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(bufferToWrite,
-            0 /* nodeFlags */, writingPos)) {
-        return false;
-    }
-    // Calculate a parent offset and write the offset.
-    if (!DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(bufferToWrite,
-            parentPos, nodePos, writingPos)) {
-        return false;
-    }
-    // Write code points
-    if (!DynamicPatriciaTrieWritingUtils::writeCodePointsAndAdvancePosition(bufferToWrite,
-            codePoints, codePointCount, writingPos)) {
-        return false;
-    }
-    // Write probability when the probability is a valid probability, which means this node is
-    // terminal.
-    if (probability != NOT_A_PROBABILITY) {
-        if (!DynamicPatriciaTrieWritingUtils::writeProbabilityAndAdvancePosition(bufferToWrite,
-                probability, writingPos)) {
-            return false;
-        }
-    }
-    // Write children position
-    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(bufferToWrite,
-            childrenPos, writingPos)) {
-        return false;
-    }
-    // Copy shortcut list when the originalShortcutListPos is valid dictionary position.
-    if (originalShortcutListPos != NOT_A_DICT_POS) {
-        int fromPos = originalShortcutListPos;
-        if (!mShortcutPolicy->copyAllShortcutsAndReturnIfSucceededOrNot(bufferToWrite, &fromPos,
-                writingPos)) {
-            return false;
-        }
-    }
-    // Copy bigram list when the originalBigramListPos is valid dictionary position.
-    int bigramCount = 0;
-    if (originalBigramListPos != NOT_A_DICT_POS) {
-        int fromPos = originalBigramListPos;
-        if (!mBigramPolicy->copyAllBigrams(bufferToWrite, &fromPos, writingPos, &bigramCount)) {
-            return false;
-        }
-    }
-    // Create node flags and write them.
-    PatriciaTrieReadingUtils::NodeFlags nodeFlags =
-            PatriciaTrieReadingUtils::createAndGetFlags(isBlacklisted, isNotAWord,
-                    probability != NOT_A_PROBABILITY /* isTerminal */,
-                    originalShortcutListPos != NOT_A_DICT_POS /* hasShortcutTargets */,
-                    bigramCount > 0 /* hasBigrams */, codePointCount > 1 /* hasMultipleChars */,
-                    CHILDREN_POSITION_FIELD_SIZE);
-    int flagsFieldPos = nodePos;
-    if (!DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(bufferToWrite, nodeFlags,
-            &flagsFieldPos)) {
-        return false;
-    }
-    return true;
-}
-
-bool DynamicPatriciaTrieWritingHelper::writePtNodeToBuffer(
-        BufferWithExtendableBuffer *const bufferToWrite, const int parentPos,
-        const int *const codePoints, const int codePointCount, const int probability,
-        int *const writingPos) {
-    return writePtNodeWithFullInfoToBuffer(bufferToWrite, false /* isBlacklisted */,
-            false /* isNotAWord */, parentPos, codePoints, codePointCount, probability,
-            NOT_A_DICT_POS /* childrenPos */, NOT_A_DICT_POS /* originalBigramsPos */,
-            NOT_A_DICT_POS /* originalShortcutPos */, writingPos);
-}
-
-bool DynamicPatriciaTrieWritingHelper::writePtNodeToBufferByCopyingPtNodeInfo(
-        BufferWithExtendableBuffer *const bufferToWrite,
-        const DynamicPatriciaTrieNodeReader *const originalNode, const int parentPos,
-        const int *const codePoints, const int codePointCount, const int probability,
-        int *const writingPos) {
-    return writePtNodeWithFullInfoToBuffer(bufferToWrite, originalNode->isBlacklisted(),
-            originalNode->isNotAWord(), parentPos, codePoints, codePointCount, probability,
-            originalNode->getChildrenPos(), originalNode->getBigramsPos(),
-            originalNode->getShortcutPos(), writingPos);
-}
-
-bool DynamicPatriciaTrieWritingHelper::createAndInsertNodeIntoPtNodeArray(const int parentPos,
-        const int *const nodeCodePoints, const int nodeCodePointCount, const int probability,
-        int *const forwardLinkFieldPos) {
-    const int newPtNodeArrayPos = mBuffer->getTailPosition();
-    if (!DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(mBuffer,
-            newPtNodeArrayPos, forwardLinkFieldPos)) {
-        return false;
-    }
-    return createNewPtNodeArrayWithAChildPtNode(parentPos, nodeCodePoints, nodeCodePointCount,
-            probability);
-}
-
-bool DynamicPatriciaTrieWritingHelper::setPtNodeProbability(
-        const DynamicPatriciaTrieNodeReader *const originalPtNode, const int probability,
-        const int *const codePoints, bool *const outAddedNewUnigram) {
-    if (originalPtNode->isTerminal()) {
-        // Overwrites the probability.
-        *outAddedNewUnigram = false;
-        const int probabilityToWrite = getUpdatedProbability(originalPtNode->getProbability(),
-                probability);
-        int probabilityFieldPos = originalPtNode->getProbabilityFieldPos();
-        if (!DynamicPatriciaTrieWritingUtils::writeProbabilityAndAdvancePosition(mBuffer,
-                probabilityToWrite, &probabilityFieldPos)) {
-            return false;
-        }
-    } else {
-        // Make the node terminal and write the probability.
-        *outAddedNewUnigram = true;
-        int movedPos = mBuffer->getTailPosition();
-        if (!markNodeAsMovedAndSetPosition(originalPtNode, movedPos, movedPos)) {
-            return false;
-        }
-        if (!writePtNodeToBufferByCopyingPtNodeInfo(mBuffer, originalPtNode,
-                originalPtNode->getParentPos(), codePoints, originalPtNode->getCodePointCount(),
-                getUpdatedProbability(NOT_A_PROBABILITY /* originalProbability */, probability),
-                &movedPos)) {
-            return false;
-        }
-    }
-    return true;
-}
-
-bool DynamicPatriciaTrieWritingHelper::createChildrenPtNodeArrayAndAChildPtNode(
-        const DynamicPatriciaTrieNodeReader *const parentNode, const int probability,
-        const int *const codePoints, const int codePointCount) {
-    const int newPtNodeArrayPos = mBuffer->getTailPosition();
-    int childrenPosFieldPos = parentNode->getChildrenPosFieldPos();
-    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(mBuffer,
-            newPtNodeArrayPos, &childrenPosFieldPos)) {
-        return false;
-    }
-    return createNewPtNodeArrayWithAChildPtNode(parentNode->getHeadPos(), codePoints,
-            codePointCount, probability);
-}
-
-bool DynamicPatriciaTrieWritingHelper::createNewPtNodeArrayWithAChildPtNode(
-        const int parentPtNodePos, const int *const nodeCodePoints, const int nodeCodePointCount,
-        const int probability) {
-    int writingPos = mBuffer->getTailPosition();
-    if (!DynamicPatriciaTrieWritingUtils::writePtNodeArraySizeAndAdvancePosition(mBuffer,
-            1 /* arraySize */, &writingPos)) {
-        return false;
-    }
-    if (!writePtNodeToBuffer(mBuffer, parentPtNodePos, nodeCodePoints, nodeCodePointCount,
-            probability, &writingPos)) {
-        return false;
-    }
-    if (!DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(mBuffer,
-            NOT_A_DICT_POS /* forwardLinkPos */, &writingPos)) {
-        return false;
-    }
-    return true;
-}
-
-// Returns whether the dictionary updating was succeeded or not.
-bool DynamicPatriciaTrieWritingHelper::reallocatePtNodeAndAddNewPtNodes(
-        const DynamicPatriciaTrieNodeReader *const reallocatingPtNode,
-        const int *const reallocatingPtNodeCodePoints, const int overlappingCodePointCount,
-        const int probabilityOfNewPtNode, const int *const newNodeCodePoints,
-        const int newNodeCodePointCount) {
-    // When addsExtraChild is true, split the reallocating PtNode and add new child.
-    // Reallocating PtNode: abcde, newNode: abcxy.
-    // abc (1st, not terminal) __ de (2nd)
-    //                         \_ xy (extra child, terminal)
-    // Otherwise, this method makes 1st part terminal and write probabilityOfNewPtNode.
-    // Reallocating PtNode: abcde, newNode: abc.
-    // abc (1st, terminal) __ de (2nd)
-    const bool addsExtraChild = newNodeCodePointCount > overlappingCodePointCount;
-    const int firstPartOfReallocatedPtNodePos = mBuffer->getTailPosition();
-    int writingPos = firstPartOfReallocatedPtNodePos;
-    // Write the 1st part of the reallocating node. The children position will be updated later
-    // with actual children position.
-    const int newProbability = addsExtraChild ? NOT_A_PROBABILITY : probabilityOfNewPtNode;
-    if (!writePtNodeToBuffer(mBuffer, reallocatingPtNode->getParentPos(),
-            reallocatingPtNodeCodePoints, overlappingCodePointCount, newProbability,
-            &writingPos)) {
-        return false;
-    }
-    const int actualChildrenPos = writingPos;
-    // Create new children PtNode array.
-    const size_t newPtNodeCount = addsExtraChild ? 2 : 1;
-    if (!DynamicPatriciaTrieWritingUtils::writePtNodeArraySizeAndAdvancePosition(mBuffer,
-            newPtNodeCount, &writingPos)) {
-        return false;
-    }
-    // Write the 2nd part of the reallocating node.
-    const int secondPartOfReallocatedPtNodePos = writingPos;
-    if (!writePtNodeToBufferByCopyingPtNodeInfo(mBuffer, reallocatingPtNode,
-            firstPartOfReallocatedPtNodePos,
-            reallocatingPtNodeCodePoints + overlappingCodePointCount,
-            reallocatingPtNode->getCodePointCount() - overlappingCodePointCount,
-            reallocatingPtNode->getProbability(), &writingPos)) {
-        return false;
-    }
-    if (addsExtraChild) {
-        if (!writePtNodeToBuffer(mBuffer, firstPartOfReallocatedPtNodePos,
-                newNodeCodePoints + overlappingCodePointCount,
-                newNodeCodePointCount - overlappingCodePointCount, probabilityOfNewPtNode,
-                &writingPos)) {
-            return false;
-        }
-    }
-    if (!DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(mBuffer,
-            NOT_A_DICT_POS /* forwardLinkPos */, &writingPos)) {
-        return false;
-    }
-    // Update original reallocatingPtNode as moved.
-    if (!markNodeAsMovedAndSetPosition(reallocatingPtNode, firstPartOfReallocatedPtNodePos,
-            secondPartOfReallocatedPtNodePos)) {
-        return false;
-    }
-    // Load node info. Information of the 1st part will be fetched.
-    DynamicPatriciaTrieNodeReader nodeReader(mBuffer, mBigramPolicy, mShortcutPolicy);
-    nodeReader.fetchNodeInfoInBufferFromPtNodePos(firstPartOfReallocatedPtNodePos);
-    // Update children position.
-    int childrenPosFieldPos = nodeReader.getChildrenPosFieldPos();
-    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(mBuffer,
-            actualChildrenPos, &childrenPosFieldPos)) {
-        return false;
-    }
-    return true;
-}
-
-bool DynamicPatriciaTrieWritingHelper::runGC(const int rootPtNodeArrayPos,
-        const HeaderPolicy *const headerPolicy, BufferWithExtendableBuffer *const bufferToWrite,
-        int *const outUnigramCount, int *const outBigramCount) {
-    DynamicPatriciaTrieReadingHelper readingHelper(mBuffer, mBigramPolicy, mShortcutPolicy);
-    readingHelper.initWithPtNodeArrayPos(rootPtNodeArrayPos);
-    DynamicPatriciaTrieGcEventListeners
-            ::TraversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted
-                    traversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted(
-                            headerPolicy, this, mBuffer, mNeedsToDecay);
-    if (!readingHelper.traverseAllPtNodesInPostorderDepthFirstManner(
-            &traversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted)) {
-        return false;
-    }
-    if (mNeedsToDecay && traversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted
-            .getValidUnigramCount() > ForgettingCurveUtils::MAX_UNIGRAM_COUNT_AFTER_GC) {
-        // TODO: Remove more unigrams.
-    }
-
-    readingHelper.initWithPtNodeArrayPos(rootPtNodeArrayPos);
-    DynamicPatriciaTrieGcEventListeners::TraversePolicyToUpdateBigramProbability
-            traversePolicyToUpdateBigramProbability(mBigramPolicy);
-    if (!readingHelper.traverseAllPtNodesInPostorderDepthFirstManner(
-            &traversePolicyToUpdateBigramProbability)) {
-        return false;
-    }
-    if (mNeedsToDecay && traversePolicyToUpdateBigramProbability.getValidBigramEntryCount()
-            > ForgettingCurveUtils::MAX_BIGRAM_COUNT_AFTER_GC) {
-        // TODO: Remove more bigrams.
-    }
-
-    // Mapping from positions in mBuffer to positions in bufferToWrite.
-    DictPositionRelocationMap dictPositionRelocationMap;
-    readingHelper.initWithPtNodeArrayPos(rootPtNodeArrayPos);
-    DynamicPatriciaTrieGcEventListeners::TraversePolicyToPlaceAndWriteValidPtNodesToBuffer
-            traversePolicyToPlaceAndWriteValidPtNodesToBuffer(this, bufferToWrite,
-                    &dictPositionRelocationMap);
-    if (!readingHelper.traverseAllPtNodesInPtNodeArrayLevelPreorderDepthFirstManner(
-            &traversePolicyToPlaceAndWriteValidPtNodesToBuffer)) {
-        return false;
-    }
-
-    // Create policy instance for the GCed dictionary.
-    DynamicShortcutListPolicy newDictShortcutPolicy(bufferToWrite);
-    DynamicBigramListPolicy newDictBigramPolicy(headerPolicy, bufferToWrite, &newDictShortcutPolicy,
-            mNeedsToDecay);
-    // Create reading helper for the GCed dictionary.
-    DynamicPatriciaTrieReadingHelper newDictReadingHelper(bufferToWrite, &newDictBigramPolicy,
-            &newDictShortcutPolicy);
-    newDictReadingHelper.initWithPtNodeArrayPos(rootPtNodeArrayPos);
-    DynamicPatriciaTrieGcEventListeners::TraversePolicyToUpdateAllPositionFields
-            traversePolicyToUpdateAllPositionFields(this, &newDictBigramPolicy, bufferToWrite,
-                    &dictPositionRelocationMap);
-    if (!newDictReadingHelper.traverseAllPtNodesInPtNodeArrayLevelPreorderDepthFirstManner(
-            &traversePolicyToUpdateAllPositionFields)) {
-        return false;
-    }
-    *outUnigramCount = traversePolicyToUpdateAllPositionFields.getUnigramCount();
-    *outBigramCount = traversePolicyToUpdateAllPositionFields.getBigramCount();
-    return true;
-}
-
-int DynamicPatriciaTrieWritingHelper::getUpdatedProbability(const int originalProbability,
-        const int newProbability) {
-    if (mNeedsToDecay) {
-        return ForgettingCurveUtils::getUpdatedEncodedProbability(originalProbability,
-                newProbability);
-    } else {
-        return newProbability;
-    }
-}
-
-} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h
deleted file mode 100644
index ca86647..0000000
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2013, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_HELPER_H
-#define LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_HELPER_H
-
-#include <stdint.h>
-
-#include "defines.h"
-#include "utils/hash_map_compat.h"
-
-namespace latinime {
-
-class BufferWithExtendableBuffer;
-class DynamicBigramListPolicy;
-class DynamicPatriciaTrieNodeReader;
-class DynamicPatriciaTrieReadingHelper;
-class DynamicShortcutListPolicy;
-class HeaderPolicy;
-
-class DynamicPatriciaTrieWritingHelper {
- public:
-    typedef hash_map_compat<int, int> PtNodeArrayPositionRelocationMap;
-    typedef hash_map_compat<int, int> PtNodePositionRelocationMap;
-    struct DictPositionRelocationMap {
-     public:
-        DictPositionRelocationMap()
-                : mPtNodeArrayPositionRelocationMap(), mPtNodePositionRelocationMap() {}
-
-        PtNodeArrayPositionRelocationMap mPtNodeArrayPositionRelocationMap;
-        PtNodePositionRelocationMap mPtNodePositionRelocationMap;
-
-     private:
-        DISALLOW_COPY_AND_ASSIGN(DictPositionRelocationMap);
-    };
-
-    static const size_t MAX_DICTIONARY_SIZE;
-
-    DynamicPatriciaTrieWritingHelper(BufferWithExtendableBuffer *const buffer,
-            DynamicBigramListPolicy *const bigramPolicy,
-            DynamicShortcutListPolicy *const shortcutPolicy, const bool needsToDecay)
-            : mBuffer(buffer), mBigramPolicy(bigramPolicy), mShortcutPolicy(shortcutPolicy),
-              mNeedsToDecay(needsToDecay) {}
-
-    ~DynamicPatriciaTrieWritingHelper() {}
-
-    // Add a word to the dictionary. If the word already exists, update the probability.
-    bool addUnigramWord(DynamicPatriciaTrieReadingHelper *const readingHelper,
-            const int *const wordCodePoints, const int codePointCount, const int probability,
-            bool *const outAddedNewUnigram);
-
-    // Add a bigram relation from word0Pos to word1Pos.
-    bool addBigramWords(const int word0Pos, const int word1Pos, const int probability,
-            bool *const outAddedNewBigram);
-
-    // Remove a bigram relation from word0Pos to word1Pos.
-    bool removeBigramWords(const int word0Pos, const int word1Pos);
-
-    void writeToDictFile(const char *const fileName, const HeaderPolicy *const headerPolicy,
-            const int unigramCount, const int bigramCount);
-
-    void writeToDictFileWithGC(const int rootPtNodeArrayPos, const char *const fileName,
-            const HeaderPolicy *const headerPolicy);
-
-    // CAVEAT: This method must be called only from inner classes of
-    // DynamicPatriciaTrieGcEventListeners.
-    bool markNodeAsDeleted(const DynamicPatriciaTrieNodeReader *const nodeToUpdate);
-
-    // CAVEAT: This method must be called only from this class or inner classes of
-    // DynamicPatriciaTrieGcEventListeners.
-    bool writePtNodeToBufferByCopyingPtNodeInfo(BufferWithExtendableBuffer *const bufferToWrite,
-            const DynamicPatriciaTrieNodeReader *const originalNode, const int parentPos,
-            const int *const codePoints, const int codePointCount, const int probability,
-            int *const writingPos);
-
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicPatriciaTrieWritingHelper);
-
-    static const int CHILDREN_POSITION_FIELD_SIZE;
-
-    BufferWithExtendableBuffer *const mBuffer;
-    DynamicBigramListPolicy *const mBigramPolicy;
-    DynamicShortcutListPolicy *const mShortcutPolicy;
-    const bool mNeedsToDecay;
-
-    bool markNodeAsMovedAndSetPosition(const DynamicPatriciaTrieNodeReader *const nodeToUpdate,
-            const int movedPos, const int bigramLinkedNodePos);
-
-    bool writePtNodeWithFullInfoToBuffer(BufferWithExtendableBuffer *const bufferToWrite,
-            const bool isBlacklisted, const bool isNotAWord,
-            const int parentPos,  const int *const codePoints, const int codePointCount,
-            const int probability, const int childrenPos, const int originalBigramListPos,
-            const int originalShortcutListPos, int *const writingPos);
-
-    bool writePtNodeToBuffer(BufferWithExtendableBuffer *const bufferToWrite,
-            const int parentPos, const int *const codePoints, const int codePointCount,
-            const int probability, int *const writingPos);
-
-    bool createAndInsertNodeIntoPtNodeArray(const int parentPos, const int *const nodeCodePoints,
-            const int nodeCodePointCount, const int probability, int *const forwardLinkFieldPos);
-
-    bool setPtNodeProbability(const DynamicPatriciaTrieNodeReader *const originalNode,
-            const int probability, const int *const codePoints, bool *const outAddedNewUnigram);
-
-    bool createChildrenPtNodeArrayAndAChildPtNode(
-            const DynamicPatriciaTrieNodeReader *const parentNode, const int probability,
-            const int *const codePoints, const int codePointCount);
-
-    bool createNewPtNodeArrayWithAChildPtNode(const int parentPos, const int *const nodeCodePoints,
-            const int nodeCodePointCount, const int probability);
-
-    bool reallocatePtNodeAndAddNewPtNodes(
-            const DynamicPatriciaTrieNodeReader *const reallocatingPtNode,
-            const int *const reallocatingPtNodeCodePoints, const int overlappingCodePointCount,
-            const int probabilityOfNewPtNode, const int *const newNodeCodePoints,
-            const int newNodeCodePointCount);
-
-    bool runGC(const int rootPtNodeArrayPos, const HeaderPolicy *const headerPolicy,
-            BufferWithExtendableBuffer *const bufferToWrite, int *const outUnigramCount,
-            int *const outBigramCount);
-
-    int getUpdatedProbability(const int originalProbability, const int newProbability);
-};
-} // namespace latinime
-#endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_HELPER_H */
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 a9c7805..7c06a71 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_policy.h
@@ -30,8 +30,8 @@
 class HeaderPolicy : public DictionaryHeaderStructurePolicy {
  public:
     // Reads information from existing dictionary buffer.
-    HeaderPolicy(const uint8_t *const dictBuf, const int dictSize)
-            : mDictFormatVersion(FormatUtils::detectFormatVersion(dictBuf, dictSize)),
+    HeaderPolicy(const uint8_t *const dictBuf, const FormatUtils::FORMAT_VERSION formatVersion)
+            : mDictFormatVersion(formatVersion),
               mDictionaryFlags(HeaderReadWriteUtils::getFlags(dictBuf)),
               mSize(HeaderReadWriteUtils::getHeaderSize(dictBuf)),
               mAttributeMap(createAttributeMapAndReadAllAttributes(dictBuf)),
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
index 5ded8f6..5ef8e50 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
@@ -118,6 +118,9 @@
         case FormatUtils::VERSION_3:
             return buffer->writeUintAndAdvancePosition(3 /* data */,
                     HEADER_DICTIONARY_VERSION_SIZE, writingPos);
+        case FormatUtils::VERSION_4:
+            return buffer->writeUintAndAdvancePosition(4 /* data */,
+                    HEADER_DICTIONARY_VERSION_SIZE, writingPos);
         default:
             return false;
     }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/shortcut/ver4_shortcut_list_policy.h b/native/jni/src/suggest/policyimpl/dictionary/shortcut/ver4_shortcut_list_policy.h
new file mode 100644
index 0000000..12d3579
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/shortcut/ver4_shortcut_list_policy.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_VER4_SHORTCUT_LIST_POLICY_H
+#define LATINIME_VER4_SHORTCUT_LIST_POLICY_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_shortcuts_structure_policy.h"
+#include "suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v4/content/shortcut_dict_content.h"
+#include "suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table.h"
+
+namespace latinime {
+
+class Ver4ShortcutListPolicy : public DictionaryShortcutsStructurePolicy {
+ public:
+    Ver4ShortcutListPolicy(const ShortcutDictContent *const shortcutDictContent,
+            const TerminalPositionLookupTable *const terminalPositionLookupTable)
+            : mShortcutDictContent(shortcutDictContent),
+              mTerminalPositionLookupTable(terminalPositionLookupTable) {}
+
+    ~Ver4ShortcutListPolicy() {}
+
+    int getStartPos(const int pos) const {
+        // The first shortcut entry is located at the head position of the shortcut list.
+        return pos;
+    }
+
+    void getNextShortcut(const int maxCodePointCount, int *const outCodePoint,
+            int *const outCodePointCount, bool *const outIsWhitelist, bool *const outHasNext,
+            int *const pos) const {
+        int shortcutFlags = 0;
+        if (outCodePoint && outCodePointCount) {
+            mShortcutDictContent->getShortcutEntryAndAdvancePosition(maxCodePointCount,
+                    outCodePoint, outCodePointCount, &shortcutFlags, pos);
+        }
+        if (outHasNext) {
+            *outHasNext = ShortcutListReadingUtils::hasNext(shortcutFlags);
+        }
+        if (outIsWhitelist) {
+            *outIsWhitelist = ShortcutListReadingUtils::isWhitelist(shortcutFlags);
+        }
+    }
+
+    void skipAllShortcuts(int *const pos) const {
+        // Do nothing because we don't need to skip shortcut lists in ver4 dictionaries.
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(Ver4ShortcutListPolicy);
+
+    const ShortcutDictContent *const mShortcutDictContent;
+    const TerminalPositionLookupTable *const mTerminalPositionLookupTable;
+};
+} // namespace latinime
+#endif // LATINIME_VER4_SHORTCUT_LIST_POLICY_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp
new file mode 100644
index 0000000..3ab6a8e
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.h"
+
+#include <stdint.h>
+#include <string>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_policy.h"
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h"
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h"
+#include "suggest/policyimpl/dictionary/utils/format_utils.h"
+#include "suggest/policyimpl/dictionary/utils/mmapped_buffer.h"
+
+namespace latinime {
+
+/* static */ DictionaryStructureWithBufferPolicy::StructurePoilcyPtr
+        DictionaryStructureWithBufferPolicyFactory
+                ::newDictionaryStructureWithBufferPolicy(const char *const path,
+                        const int bufOffset, const int size, const bool isUpdatable) {
+    // Allocated buffer in MmapedBuffer::newBuffer() will be freed in the destructor of
+    // MmappedBufferWrapper if the instance has the responsibility.
+    MmappedBuffer::MmappedBufferPtr mmappedBuffer(MmappedBuffer::openBuffer(path, bufOffset, size,
+            isUpdatable));
+    if (!mmappedBuffer.get()) {
+        return DictionaryStructureWithBufferPolicy::StructurePoilcyPtr(0);
+    }
+    switch (FormatUtils::detectFormatVersion(mmappedBuffer.get()->getBuffer(),
+            mmappedBuffer.get()->getBufferSize())) {
+        case FormatUtils::VERSION_2:
+            return DictionaryStructureWithBufferPolicy::StructurePoilcyPtr(
+                    new PatriciaTriePolicy(mmappedBuffer));
+        case FormatUtils::VERSION_3:
+            return DictionaryStructureWithBufferPolicy::StructurePoilcyPtr(
+                    new DynamicPatriciaTriePolicy(mmappedBuffer));
+        case FormatUtils::VERSION_4: {
+            std::string dictDirPath(path);
+            const std::string::size_type pos =
+                    dictDirPath.rfind(Ver4DictConstants::TRIE_FILE_EXTENSION);
+            if (pos == std::string::npos) {
+                // Dictionary file name is not valid as a version 4 dictionary.
+                return DictionaryStructureWithBufferPolicy::StructurePoilcyPtr(0);
+            }
+            // Removing extension to get the base path.
+            dictDirPath.erase(pos);
+            const Ver4DictBuffers::Ver4DictBuffersPtr dictBuffers(
+                    Ver4DictBuffers::openVer4DictBuffers(dictDirPath.c_str(), mmappedBuffer));
+            if (!dictBuffers.get()->isValid()) {
+                AKLOGE("DICT: The dictionary doesn't satisfy ver4 format requirements.");
+                ASSERT(false);
+                return DictionaryStructureWithBufferPolicy::StructurePoilcyPtr(0);
+            }
+            return DictionaryStructureWithBufferPolicy::StructurePoilcyPtr(
+                    new Ver4PatriciaTriePolicy(dictBuffers));
+        }
+        default:
+            AKLOGE("DICT: dictionary format is unknown, bad magic number");
+            ASSERT(false);
+            return DictionaryStructureWithBufferPolicy::StructurePoilcyPtr(0);
+    }
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.h b/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.h
similarity index 80%
rename from native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.h
rename to native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.h
index 8cebc3b..1359575 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/dictionary_structure_with_buffer_policy_factory.h
@@ -21,13 +21,15 @@
 
 #include "defines.h"
 #include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
+#include "utils/exclusive_ownership_pointer.h"
 
 namespace latinime {
 
 class DictionaryStructureWithBufferPolicyFactory {
  public:
-    static DictionaryStructureWithBufferPolicy *newDictionaryStructureWithBufferPolicy(
-            const char *const path, const int bufOffset, const int size, const bool isUpdatable);
+    static DictionaryStructureWithBufferPolicy::StructurePoilcyPtr
+            newDictionaryStructureWithBufferPolicy(const char *const path, const int bufOffset,
+                    const int size, const bool isUpdatable);
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(DictionaryStructureWithBufferPolicyFactory);
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h
new file mode 100644
index 0000000..6a4cfee
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_PT_NODE_PARAMS_H
+#define LATINIME_PT_NODE_PARAMS_H
+
+#include <cstring>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h"
+
+namespace latinime {
+
+// This class has information of a PtNode. This class is immutable.
+class PtNodeParams {
+ public:
+    // Invalid PtNode.
+    PtNodeParams() : mHeadPos(NOT_A_DICT_POS), mFlags(0), mParentPos(NOT_A_DICT_POS),
+            mCodePointCount(0), mCodePoints(), mTerminalIdFieldPos(NOT_A_DICT_POS),
+            mTerminalId(Ver4DictConstants::NOT_A_TERMINAL_ID), mProbabilityFieldPos(NOT_A_DICT_POS),
+            mProbability(NOT_A_PROBABILITY), mChildrenPosFieldPos(NOT_A_DICT_POS),
+            mChildrenPos(NOT_A_DICT_POS), mBigramLinkedNodePos(NOT_A_DICT_POS),
+            mShortcutPos(NOT_A_DICT_POS), mBigramPos(NOT_A_DICT_POS),
+            mSiblingPos(NOT_A_DICT_POS) {}
+
+    PtNodeParams(const PtNodeParams& ptNodeParams)
+            : mHeadPos(ptNodeParams.mHeadPos), mFlags(ptNodeParams.mFlags),
+              mParentPos(ptNodeParams.mParentPos), mCodePointCount(ptNodeParams.mCodePointCount),
+              mCodePoints(), mTerminalIdFieldPos(ptNodeParams.mTerminalIdFieldPos),
+              mTerminalId(ptNodeParams.mTerminalId),
+              mProbabilityFieldPos(ptNodeParams.mProbabilityFieldPos),
+              mProbability(ptNodeParams.mProbability),
+              mChildrenPosFieldPos(ptNodeParams.mChildrenPosFieldPos),
+              mChildrenPos(ptNodeParams.mChildrenPos),
+              mBigramLinkedNodePos(ptNodeParams.mBigramLinkedNodePos),
+              mShortcutPos(ptNodeParams.mShortcutPos), mBigramPos(ptNodeParams.mBigramPos),
+              mSiblingPos(ptNodeParams.mSiblingPos) {
+        memcpy(mCodePoints, ptNodeParams.getCodePoints(), sizeof(int) * mCodePointCount);
+    }
+
+    // PtNode without terminal id.
+    PtNodeParams(const int headPos, const PatriciaTrieReadingUtils::NodeFlags flags,
+            const int parentPos, const int codePointCount, const int *const codePoints,
+            const int probabilityFieldPos, const int probability, const int childrenPosFieldPos,
+            const int childrenPos, const int bigramLinkedNodePos, const int shortcutPos,
+            const int bigramPos, const int siblingPos)
+            : mHeadPos(headPos), mFlags(flags), mParentPos(parentPos),
+              mCodePointCount(codePointCount), mCodePoints(),
+              mTerminalIdFieldPos(NOT_A_DICT_POS),
+              mTerminalId(Ver4DictConstants::NOT_A_TERMINAL_ID),
+              mProbabilityFieldPos(probabilityFieldPos), mProbability(probability),
+              mChildrenPosFieldPos(childrenPosFieldPos), mChildrenPos(childrenPos),
+              mBigramLinkedNodePos(bigramLinkedNodePos), mShortcutPos(shortcutPos),
+              mBigramPos(bigramPos), mSiblingPos(siblingPos) {
+        memcpy(mCodePoints, codePoints, sizeof(int) * mCodePointCount);
+    }
+
+    // PtNode with a terminal id.
+    PtNodeParams(const int headPos, const PatriciaTrieReadingUtils::NodeFlags flags,
+            const int parentPos, const int codePointCount, const int *const codePoints,
+            const int terminalIdFieldPos, const int terminalId, const int probability,
+            const int childrenPosFieldPos, const int childrenPos, const int bigramLinkedNodePos,
+            const int siblingPos)
+            : mHeadPos(headPos), mFlags(flags), mParentPos(parentPos),
+              mCodePointCount(codePointCount), mCodePoints(),
+              mTerminalIdFieldPos(terminalIdFieldPos), mTerminalId(terminalId),
+              mProbabilityFieldPos(NOT_A_DICT_POS), mProbability(probability),
+              mChildrenPosFieldPos(childrenPosFieldPos), mChildrenPos(childrenPos),
+              mBigramLinkedNodePos(bigramLinkedNodePos), mShortcutPos(terminalId),
+              mBigramPos(terminalId), mSiblingPos(siblingPos) {
+        memcpy(mCodePoints, codePoints, sizeof(int) * mCodePointCount);
+    }
+
+    // Construct new params by updating existing PtNode params.
+    PtNodeParams(const PtNodeParams *const ptNodeParams,
+            const PatriciaTrieReadingUtils::NodeFlags flags, const int parentPos,
+            const int codePointCount, const int *const codePoints, const int probability)
+            : mHeadPos(ptNodeParams->getHeadPos()), mFlags(flags), mParentPos(parentPos),
+              mCodePointCount(codePointCount), mCodePoints(),
+              mTerminalIdFieldPos(ptNodeParams->getTerminalIdFieldPos()),
+              mTerminalId(ptNodeParams->getTerminalId()),
+              mProbabilityFieldPos(ptNodeParams->getProbabilityFieldPos()),
+              mProbability(probability),
+              mChildrenPosFieldPos(ptNodeParams->getChildrenPosFieldPos()),
+              mChildrenPos(ptNodeParams->getChildrenPos()),
+              mBigramLinkedNodePos(ptNodeParams->getBigramLinkedNodePos()),
+              mShortcutPos(ptNodeParams->getShortcutPos()),
+              mBigramPos(ptNodeParams->getBigramsPos()),
+              mSiblingPos(ptNodeParams->getSiblingNodePos()) {
+        memcpy(mCodePoints, codePoints, sizeof(int) * mCodePointCount);
+    }
+
+    PtNodeParams(const PatriciaTrieReadingUtils::NodeFlags flags, const int parentPos,
+            const int codePointCount, const int *const codePoints, const int probability)
+            : mHeadPos(NOT_A_DICT_POS), mFlags(flags), mParentPos(parentPos),
+              mCodePointCount(codePointCount), mCodePoints(),
+              mTerminalIdFieldPos(NOT_A_DICT_POS),
+              mTerminalId(Ver4DictConstants::NOT_A_TERMINAL_ID),
+              mProbabilityFieldPos(NOT_A_DICT_POS), mProbability(probability),
+              mChildrenPosFieldPos(NOT_A_DICT_POS), mChildrenPos(NOT_A_DICT_POS),
+              mBigramLinkedNodePos(NOT_A_DICT_POS), mShortcutPos(NOT_A_DICT_POS),
+              mBigramPos(NOT_A_DICT_POS), mSiblingPos(NOT_A_DICT_POS) {
+        memcpy(mCodePoints, codePoints, sizeof(int) * mCodePointCount);
+    }
+
+    AK_FORCE_INLINE bool isValid() const {
+        return mCodePointCount > 0;
+    }
+
+    // Head position of the PtNode
+    AK_FORCE_INLINE int getHeadPos() const {
+        return mHeadPos;
+    }
+
+    // Flags
+    AK_FORCE_INLINE bool isDeleted() const {
+        return DynamicPatriciaTrieReadingUtils::isDeleted(mFlags);
+    }
+
+    AK_FORCE_INLINE bool hasChildren() const {
+        return mChildrenPos != NOT_A_DICT_POS;
+    }
+
+    AK_FORCE_INLINE bool isTerminal() const {
+        return PatriciaTrieReadingUtils::isTerminal(mFlags);
+    }
+
+    AK_FORCE_INLINE bool isBlacklisted() const {
+        return PatriciaTrieReadingUtils::isBlacklisted(mFlags);
+    }
+
+    AK_FORCE_INLINE bool isNotAWord() const {
+        return PatriciaTrieReadingUtils::isNotAWord(mFlags);
+    }
+
+    // Parent node position
+    AK_FORCE_INLINE int getParentPos() const {
+        return mParentPos;
+    }
+
+    // Number of code points
+    AK_FORCE_INLINE uint8_t getCodePointCount() const {
+        return mCodePointCount;
+    }
+
+    AK_FORCE_INLINE const int *getCodePoints() const {
+        return mCodePoints;
+    }
+
+    // Probability
+    AK_FORCE_INLINE int getTerminalIdFieldPos() const {
+        return mTerminalIdFieldPos;
+    }
+
+    AK_FORCE_INLINE int getTerminalId() const {
+        return mTerminalId;
+    }
+
+    // Probability
+    AK_FORCE_INLINE int getProbabilityFieldPos() const {
+        return mProbabilityFieldPos;
+    }
+
+    AK_FORCE_INLINE int getProbability() const {
+        return mProbability;
+    }
+
+    // Children PtNode array position
+    AK_FORCE_INLINE int getChildrenPosFieldPos() const {
+        return mChildrenPosFieldPos;
+    }
+
+    AK_FORCE_INLINE int getChildrenPos() const {
+        return mChildrenPos;
+    }
+
+    // Bigram linked node position.
+    AK_FORCE_INLINE int getBigramLinkedNodePos() const {
+        return mBigramLinkedNodePos;
+    }
+
+    // Shortcutlist position
+    AK_FORCE_INLINE int getShortcutPos() const {
+        return mShortcutPos;
+    }
+
+    // Bigrams position
+    AK_FORCE_INLINE int getBigramsPos() const {
+        return mBigramPos;
+    }
+
+    // Sibling node position
+    AK_FORCE_INLINE int getSiblingNodePos() const {
+        return mSiblingPos;
+    }
+
+ private:
+    // This class have a public copy constructor to be used as a return value.
+
+    // Disallowing the assignment operator.
+    PtNodeParams &operator=(PtNodeParams &ptNodeParams);
+
+    const int mHeadPos;
+    const PatriciaTrieReadingUtils::NodeFlags mFlags;
+    const int mParentPos;
+    const uint8_t mCodePointCount;
+    int mCodePoints[MAX_WORD_LENGTH];
+    const int mTerminalIdFieldPos;
+    const int mTerminalId;
+    const int mProbabilityFieldPos;
+    const int mProbability;
+    const int mChildrenPosFieldPos;
+    const int mChildrenPos;
+    const int mBigramLinkedNodePos;
+    const int mShortcutPos;
+    const int mBigramPos;
+    const int mSiblingPos;
+};
+} // namespace latinime
+#endif /* LATINIME_PT_NODE_PARAMS_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_reader.h b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_reader.h
new file mode 100644
index 0000000..c6b2a8b
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_reader.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_PT_NODE_READER_H
+#define LATINIME_PT_NODE_READER_H
+
+#include "defines.h"
+
+#include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h"
+
+namespace latinime {
+
+// Interface class used to read PtNode information.
+class PtNodeReader {
+ public:
+    virtual ~PtNodeReader() {}
+    virtual const PtNodeParams fetchNodeInfoInBufferFromPtNodePos(const int ptNodePos) const = 0;
+
+ protected:
+    PtNodeReader() {};
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(PtNodeReader);
+};
+} // namespace latinime
+#endif /* LATINIME_PT_NODE_READER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_writer.h b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_writer.h
new file mode 100644
index 0000000..0f1e635
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_writer.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_PT_NODE_WRITER_H
+#define LATINIME_PT_NODE_WRITER_H
+
+#include "defines.h"
+
+#include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h"
+
+namespace latinime {
+
+// Interface class used to write PtNode information.
+class PtNodeWriter {
+ public:
+    virtual ~PtNodeWriter() {}
+
+    virtual bool markPtNodeAsDeleted(const PtNodeParams *const toBeUpdatedPtNodeParams) = 0;
+
+    virtual bool markPtNodeAsMoved(const PtNodeParams *const toBeUpdatedPtNodeParams,
+            const int movedPos, const int bigramLinkedNodePos) = 0;
+
+    virtual bool updatePtNodeProbability(const PtNodeParams *const toBeUpdatedPtNodeParams,
+            const int probability) = 0;
+
+    virtual bool updateChildrenPosition(const PtNodeParams *const toBeUpdatedPtNodeParams,
+                const int newChildrenPosition) = 0;
+
+    virtual bool writePtNodeAndAdvancePosition(const PtNodeParams *const ptNodeParams,
+            int *const ptNodeWritingPos) = 0;
+
+    virtual bool addNewBigramEntry(const PtNodeParams *const sourcePtNodeParams,
+            const PtNodeParams *const targetPtNodeParam, const int probability,
+            bool *const outAddedNewBigram) = 0;
+
+    virtual bool removeBigramEntry(const PtNodeParams *const sourcePtNodeParams,
+            const PtNodeParams *const targetPtNodeParam) = 0;
+
+ protected:
+    PtNodeWriter() {};
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(PtNodeWriter);
+};
+} // namespace latinime
+#endif /* LATINIME_PT_NODE_WRITER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
similarity index 95%
rename from native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp
rename to native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
index 8a84bd2..960c1b9 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
@@ -15,22 +15,22 @@
  */
 
 
-#include "suggest/policyimpl/dictionary/patricia_trie_policy.h"
+#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h"
 
 #include "defines.h"
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_vector.h"
-#include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h"
 #include "suggest/policyimpl/dictionary/utils/probability_utils.h"
 
 namespace latinime {
 
-void PatriciaTriePolicy::createAndGetAllChildNodes(const DicNode *const dicNode,
+void PatriciaTriePolicy::createAndGetAllChildDicNodes(const DicNode *const dicNode,
         DicNodeVector *const childDicNodes) const {
     if (!dicNode->hasChildren()) {
         return;
     }
-    int nextPos = dicNode->getChildrenPos();
+    int nextPos = dicNode->getChildrenPtNodeArrayPos();
     if (nextPos < 0 || nextPos >= mDictBufferSize) {
         AKLOGE("Children PtNode array position is invalid. pos: %d, dict size: %d",
                 nextPos, mDictBufferSize);
@@ -52,14 +52,14 @@
 
 // This retrieves code points and the probability of the word by its terminal position.
 // Due to the fact that words are ordered in the dictionary in a strict breadth-first order,
-// it is possible to check for this with advantageous complexity. For each node, we search
+// it is possible to check for this with advantageous complexity. For each PtNode array, we search
 // for PtNodes with children and compare the children position with the position we look for.
 // When we shoot the position we look for, it means the word we look for is in the children
 // of the previous PtNode. The only tricky part is the fact that if we arrive at the end of a
 // PtNode array with the last PtNode's children position still less than what we are searching for,
 // we must descend the last PtNode's children (for example, if the word we are searching for starts
 // with a z, it's the last PtNode of the root array, so all children addresses will be smaller
-// than the position we look for, and we have to descend the z node).
+// than the position we look for, and we have to descend the z PtNode).
 /* Parameters :
  * ptNodePos: the byte position of the terminal PtNode of the word we are searching for (this is
  *   what is stored as the "bigram position" in each bigram)
@@ -74,9 +74,9 @@
     int pos = getRootPosition();
     int wordPos = 0;
     // One iteration of the outer loop iterates through PtNode arrays. As stated above, we will
-    // only traverse nodes that are actually a part of the terminal we are searching, so each time
-    // we enter this loop we are one depth level further than last time.
-    // The only reason we count nodes is because we want to reduce the probability of infinite
+    // only traverse PtNodes that are actually a part of the terminal we are searching, so each
+    // time we enter this loop we are one depth level further than last time.
+    // The only reason we count PtNodes is because we want to reduce the probability of infinite
     // looping in case there is a bug. Since we know there is an upper bound to the depth we are
     // supposed to traverse, it does not hurt to count iterations.
     for (int loopCount = maxCodePointCount; loopCount > 0; --loopCount) {
@@ -140,8 +140,9 @@
                     found = true;
                 } else if (1 >= ptNodeCount) {
                     // However if we are on the LAST PtNode of this array, and we have NOT shot the
-                    // position we should descend THIS node. So we trick the lastCandidatePtNodePos
-                    // so that we will descend this PtNode, not the previous one.
+                    // position we should descend THIS PtNode. So we trick the
+                    // lastCandidatePtNodePos so that we will descend this PtNode, not the previous
+                    // one.
                     lastCandidatePtNodePos = startPos;
                     found = true;
                 } else {
@@ -149,7 +150,7 @@
                     found = false;
                 }
             } else {
-                // Even if we don't have children here, we could still be on the last PtNode of /
+                // Even if we don't have children here, we could still be on the last PtNode of
                 // this array. If this is the case, we should descend the last PtNode that had
                 // children, and their position is already in lastCandidatePtNodePos.
                 found = (1 >= ptNodeCount);
@@ -230,9 +231,9 @@
     return 0;
 }
 
-// This function gets the position of the terminal node of the exact matching word in the
+// This function gets the position of the terminal PtNode of the exact matching word in the
 // dictionary. If no match is found, it returns NOT_A_DICT_POS.
-int PatriciaTriePolicy::getTerminalNodePositionOfWord(const int *const inWord,
+int PatriciaTriePolicy::getTerminalPtNodePositionOfWord(const int *const inWord,
         const int length, const bool forceLowerCaseSearch) const {
     int pos = getRootPosition();
     int wordPos = 0;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h
similarity index 87%
rename from native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.h
rename to native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h
index 0f8662a..5d99632 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.h
@@ -24,6 +24,7 @@
 #include "suggest/policyimpl/dictionary/bigram/bigram_list_policy.h"
 #include "suggest/policyimpl/dictionary/header/header_policy.h"
 #include "suggest/policyimpl/dictionary/shortcut/shortcut_list_policy.h"
+#include "suggest/policyimpl/dictionary/utils/format_utils.h"
 #include "suggest/policyimpl/dictionary/utils/mmapped_buffer.h"
 
 namespace latinime {
@@ -33,28 +34,26 @@
 
 class PatriciaTriePolicy : public DictionaryStructureWithBufferPolicy {
  public:
-    PatriciaTriePolicy(const MmappedBuffer *const buffer)
-            : mBuffer(buffer), mHeaderPolicy(mBuffer->getBuffer(), buffer->getBufferSize()),
-              mDictRoot(mBuffer->getBuffer() + mHeaderPolicy.getSize()),
-              mDictBufferSize(mBuffer->getBufferSize() - mHeaderPolicy.getSize()),
+    PatriciaTriePolicy(const MmappedBuffer::MmappedBufferPtr &mmappedBuffer)
+            : mMmappedBuffer(mmappedBuffer),
+              mHeaderPolicy(mMmappedBuffer.get()->getBuffer(), FormatUtils::VERSION_2),
+              mDictRoot(mMmappedBuffer.get()->getBuffer() + mHeaderPolicy.getSize()),
+              mDictBufferSize(mMmappedBuffer.get()->getBufferSize()
+                      - mHeaderPolicy.getSize()),
               mBigramListPolicy(mDictRoot), mShortcutListPolicy(mDictRoot) {}
 
-    ~PatriciaTriePolicy() {
-        delete mBuffer;
-    }
-
     AK_FORCE_INLINE int getRootPosition() const {
         return 0;
     }
 
-    void createAndGetAllChildNodes(const DicNode *const dicNode,
+    void createAndGetAllChildDicNodes(const DicNode *const dicNode,
             DicNodeVector *const childDicNodes) const;
 
     int getCodePointsAndProbabilityAndReturnCodePointCount(
             const int terminalNodePos, const int maxCodePointCount, int *const outCodePoints,
             int *const outUnigramProbability) const;
 
-    int getTerminalNodePositionOfWord(const int *const inWord,
+    int getTerminalPtNodePositionOfWord(const int *const inWord,
             const int length, const bool forceLowerCaseSearch) const;
 
     int getProbability(const int unigramProbability, const int bigramProbability) const;
@@ -124,7 +123,7 @@
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(PatriciaTriePolicy);
 
-    const MmappedBuffer *const mBuffer;
+    const MmappedBuffer::MmappedBufferPtr mMmappedBuffer;
     const HeaderPolicy mHeaderPolicy;
     const uint8_t *const mDictRoot;
     const int mDictBufferSize;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.cpp
similarity index 98%
rename from native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.cpp
rename to native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.cpp
index 7df5581..82b3593 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h"
 
 #include "defines.h"
 #include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h
similarity index 100%
rename from native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.h
rename to native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_gc_event_listeners.cpp
similarity index 77%
rename from native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.cpp
rename to native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_gc_event_listeners.cpp
index 5724c5d..a252c6f 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_gc_event_listeners.cpp
@@ -14,25 +14,27 @@
  * limitations under the License.
  */
 
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_gc_event_listeners.h"
 
 #include "suggest/core/policy/dictionary_header_structure_policy.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_writer.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_utils.h"
 #include "suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h"
 
 namespace latinime {
 
 bool DynamicPatriciaTrieGcEventListeners
         ::TraversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted
-                ::onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
-                        const int *const nodeCodePoints) {
+                ::onVisitingPtNode(const PtNodeParams *const ptNodeParams) {
     // PtNode is useless when the PtNode is not a terminal and doesn't have any not useless
     // children.
-    bool isUselessPtNode = !node->isTerminal();
-    if (node->isTerminal() && mIsDecayingDict) {
+    bool isUselessPtNode = !ptNodeParams->isTerminal();
+    if (ptNodeParams->isTerminal() && mIsDecayingDict) {
         const int newProbability =
-                ForgettingCurveUtils::getEncodedProbabilityToSave(node->getProbability(),
+                ForgettingCurveUtils::getEncodedProbabilityToSave(ptNodeParams->getProbability(),
                         mHeaderPolicy);
-        int writingPos = node->getProbabilityFieldPos();
+        int writingPos = ptNodeParams->getProbabilityFieldPos();
         // Update probability.
         if (!DynamicPatriciaTrieWritingUtils::writeProbabilityAndAdvancePosition(
                 mBuffer, newProbability, &writingPos)) {
@@ -44,9 +46,9 @@
     }
     if (mChildrenValue > 0) {
         isUselessPtNode = false;
-    } else if (node->isTerminal()) {
+    } else if (ptNodeParams->isTerminal()) {
         // Remove children as all children are useless.
-        int writingPos = node->getChildrenPosFieldPos();
+        int writingPos = ptNodeParams->getChildrenPosFieldPos();
         if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(
                 mBuffer, NOT_A_DICT_POS /* childrenPosition */, &writingPos)) {
             return false;
@@ -54,12 +56,12 @@
     }
     if (isUselessPtNode) {
         // Current PtNode is no longer needed. Mark it as deleted.
-        if (!mWritingHelper->markNodeAsDeleted(node)) {
+        if (!mPtNodeWriter->markPtNodeAsDeleted(ptNodeParams)) {
             return false;
         }
     } else {
         mValueStack.back() += 1;
-        if (node->isTerminal()) {
+        if (ptNodeParams->isTerminal()) {
             mValidUnigramCount += 1;
         }
     }
@@ -67,10 +69,9 @@
 }
 
 bool DynamicPatriciaTrieGcEventListeners::TraversePolicyToUpdateBigramProbability
-        ::onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
-                const int *const nodeCodePoints) {
-    if (!node->isDeleted()) {
-        int pos = node->getBigramsPos();
+        ::onVisitingPtNode(const PtNodeParams *const ptNodeParams) {
+    if (!ptNodeParams->isDeleted()) {
+        int pos = ptNodeParams->getBigramsPos();
         if (pos != NOT_A_DICT_POS) {
             int bigramEntryCount = 0;
             if (!mBigramPolicy->updateAllBigramEntriesAndDeleteUselessEntries(&pos,
@@ -117,31 +118,27 @@
 
 // Write valid PtNode to buffer and memorize mapping from the old position to the new position.
 bool DynamicPatriciaTrieGcEventListeners::TraversePolicyToPlaceAndWriteValidPtNodesToBuffer
-        ::onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
-                const int *const nodeCodePoints) {
-    if (node->isDeleted()) {
+        ::onVisitingPtNode(const PtNodeParams *const ptNodeParams) {
+    if (ptNodeParams->isDeleted()) {
         // Current PtNode is not written in new buffer because it has been deleted.
         mDictPositionRelocationMap->mPtNodePositionRelocationMap.insert(
                 DynamicPatriciaTrieWritingHelper::PtNodePositionRelocationMap::value_type(
-                        node->getHeadPos(), NOT_A_DICT_POS));
+                        ptNodeParams->getHeadPos(), NOT_A_DICT_POS));
         return true;
     }
     int writingPos = mBufferToWrite->getTailPosition();
     mDictPositionRelocationMap->mPtNodePositionRelocationMap.insert(
             DynamicPatriciaTrieWritingHelper::PtNodePositionRelocationMap::value_type(
-                    node->getHeadPos(), writingPos));
+                    ptNodeParams->getHeadPos(), writingPos));
     mValidPtNodeCount++;
     // Writes current PtNode.
-    return mWritingHelper->writePtNodeToBufferByCopyingPtNodeInfo(mBufferToWrite, node,
-            node->getParentPos(), nodeCodePoints, node->getCodePointCount(),
-            node->getProbability(), &writingPos);
+    return mPtNodeWriter->writePtNodeAndAdvancePosition(ptNodeParams, &writingPos);
 }
 
 bool DynamicPatriciaTrieGcEventListeners::TraversePolicyToUpdateAllPositionFields
-        ::onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
-                const int *const nodeCodePoints) {
+        ::onVisitingPtNode(const PtNodeParams *const ptNodeParams) {
     // Updates parent position.
-    int parentPos = node->getParentPos();
+    int parentPos = ptNodeParams->getParentPos();
     if (parentPos != NOT_A_DICT_POS) {
         DynamicPatriciaTrieWritingHelper::PtNodePositionRelocationMap::const_iterator it =
                 mDictPositionRelocationMap->mPtNodePositionRelocationMap.find(parentPos);
@@ -149,15 +146,16 @@
             parentPos = it->second;
         }
     }
-    int writingPos = node->getHeadPos() + DynamicPatriciaTrieWritingUtils::NODE_FLAG_FIELD_SIZE;
+    int writingPos = ptNodeParams->getHeadPos()
+            + DynamicPatriciaTrieWritingUtils::NODE_FLAG_FIELD_SIZE;
     // Write updated parent offset.
     if (!DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(mBufferToWrite,
-            parentPos, node->getHeadPos(), &writingPos)) {
+            parentPos, ptNodeParams->getHeadPos(), &writingPos)) {
         return false;
     }
 
     // Updates children position.
-    int childrenPos = node->getChildrenPos();
+    int childrenPos = ptNodeParams->getChildrenPos();
     if (childrenPos != NOT_A_DICT_POS) {
         DynamicPatriciaTrieWritingHelper::PtNodeArrayPositionRelocationMap::const_iterator it =
                 mDictPositionRelocationMap->mPtNodeArrayPositionRelocationMap.find(childrenPos);
@@ -165,14 +163,14 @@
             childrenPos = it->second;
         }
     }
-    writingPos = node->getChildrenPosFieldPos();
+    writingPos = ptNodeParams->getChildrenPosFieldPos();
     if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(mBufferToWrite,
             childrenPos, &writingPos)) {
         return false;
     }
 
     // Updates bigram target PtNode positions in the bigram list.
-    int bigramsPos = node->getBigramsPos();
+    int bigramsPos = ptNodeParams->getBigramsPos();
     if (bigramsPos != NOT_A_DICT_POS) {
         int bigramEntryCount;
         if (!mBigramPolicy->updateAllBigramTargetPtNodePositions(&bigramsPos,
@@ -181,7 +179,7 @@
         }
         mBigramCount += bigramEntryCount;
     }
-    if (node->isTerminal()) {
+    if (ptNodeParams->isTerminal()) {
         mUnigramCount++;
     }
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_gc_event_listeners.h
similarity index 79%
rename from native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h
rename to native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_gc_event_listeners.h
index 9755120..a0b3b4d 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_gc_event_listeners.h
@@ -21,15 +21,16 @@
 
 #include "defines.h"
 #include "suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_helper.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_helper.h"
 #include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
 #include "utils/hash_map_compat.h"
 
 namespace latinime {
 
 class DictionaryHeaderStructurePolicy;
+class PtNodeWriter;
+class PtNodeParams;
 
 class DynamicPatriciaTrieGcEventListeners {
  public:
@@ -41,9 +42,9 @@
      public:
         TraversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted(
                 const DictionaryHeaderStructurePolicy *const headerPolicy,
-                DynamicPatriciaTrieWritingHelper *const writingHelper,
-                BufferWithExtendableBuffer *const buffer, const bool isDecayingDict)
-                : mHeaderPolicy(headerPolicy), mWritingHelper(writingHelper), mBuffer(buffer),
+                PtNodeWriter *const ptNodeWriter, BufferWithExtendableBuffer *const buffer,
+                const bool isDecayingDict)
+                : mHeaderPolicy(headerPolicy), mPtNodeWriter(ptNodeWriter), mBuffer(buffer),
                   mIsDecayingDict(isDecayingDict), mValueStack(), mChildrenValue(0),
                   mValidUnigramCount(0) {}
 
@@ -66,8 +67,7 @@
 
         bool onReadingPtNodeArrayTail() { return true; }
 
-        bool onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
-                const int *const nodeCodePoints);
+        bool onVisitingPtNode(const PtNodeParams *const ptNodeParams);
 
         int getValidUnigramCount() const {
             return mValidUnigramCount;
@@ -78,7 +78,7 @@
                 TraversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted);
 
         const DictionaryHeaderStructurePolicy *const mHeaderPolicy;
-        DynamicPatriciaTrieWritingHelper *const mWritingHelper;
+        PtNodeWriter *const mPtNodeWriter;
         BufferWithExtendableBuffer *const mBuffer;
         const bool mIsDecayingDict;
         std::vector<int> mValueStack;
@@ -101,8 +101,7 @@
 
         bool onReadingPtNodeArrayTail() { return true; }
 
-        bool onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
-                const int *const nodeCodePoints);
+        bool onVisitingPtNode(const PtNodeParams *const ptNodeParams);
 
         int getValidBigramEntryCount() const {
             return mValidBigramEntryCount;
@@ -119,11 +118,10 @@
             : public DynamicPatriciaTrieReadingHelper::TraversingEventListener {
      public:
         TraversePolicyToPlaceAndWriteValidPtNodesToBuffer(
-                DynamicPatriciaTrieWritingHelper *const writingHelper,
-                BufferWithExtendableBuffer *const bufferToWrite,
+                PtNodeWriter *const ptNodeWriter, BufferWithExtendableBuffer *const bufferToWrite,
                 DynamicPatriciaTrieWritingHelper::DictPositionRelocationMap *const
                         dictPositionRelocationMap)
-                : mWritingHelper(writingHelper), mBufferToWrite(bufferToWrite),
+                : mPtNodeWriter(ptNodeWriter), mBufferToWrite(bufferToWrite),
                   mDictPositionRelocationMap(dictPositionRelocationMap), mValidPtNodeCount(0),
                   mPtNodeArraySizeFieldPos(NOT_A_DICT_POS) {};
 
@@ -133,13 +131,12 @@
 
         bool onReadingPtNodeArrayTail();
 
-        bool onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
-                const int *const nodeCodePoints);
+        bool onVisitingPtNode(const PtNodeParams *const ptNodeParams);
 
      private:
         DISALLOW_IMPLICIT_CONSTRUCTORS(TraversePolicyToPlaceAndWriteValidPtNodesToBuffer);
 
-        DynamicPatriciaTrieWritingHelper *const mWritingHelper;
+        PtNodeWriter *const mPtNodeWriter;
         BufferWithExtendableBuffer *const mBufferToWrite;
         DynamicPatriciaTrieWritingHelper::DictPositionRelocationMap *const
                 mDictPositionRelocationMap;
@@ -151,13 +148,11 @@
             : public DynamicPatriciaTrieReadingHelper::TraversingEventListener {
      public:
         TraversePolicyToUpdateAllPositionFields(
-                DynamicPatriciaTrieWritingHelper *const writingHelper,
                 DynamicBigramListPolicy *const bigramPolicy,
                 BufferWithExtendableBuffer *const bufferToWrite,
                 const DynamicPatriciaTrieWritingHelper::DictPositionRelocationMap *const
                         dictPositionRelocationMap)
-                : mWritingHelper(writingHelper), mBigramPolicy(bigramPolicy),
-                  mBufferToWrite(bufferToWrite),
+                : mBigramPolicy(bigramPolicy), mBufferToWrite(bufferToWrite),
                   mDictPositionRelocationMap(dictPositionRelocationMap), mUnigramCount(0),
                   mBigramCount(0) {};
 
@@ -167,8 +162,7 @@
 
         bool onReadingPtNodeArrayTail() { return true; }
 
-        bool onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
-                const int *const nodeCodePoints);
+        bool onVisitingPtNode(const PtNodeParams *const ptNodeParams);
 
         int getUnigramCount() const {
             return mUnigramCount;
@@ -181,7 +175,6 @@
      private:
         DISALLOW_IMPLICIT_CONSTRUCTORS(TraversePolicyToUpdateAllPositionFields);
 
-        DynamicPatriciaTrieWritingHelper *const mWritingHelper;
         DynamicBigramListPolicy *const mBigramPolicy;
         BufferWithExtendableBuffer *const mBufferToWrite;
         const DynamicPatriciaTrieWritingHelper::DictPositionRelocationMap *const
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_reader.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_reader.cpp
new file mode 100644
index 0000000..3393ce6
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_reader.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_reader.h"
+
+#include "suggest/core/policy/dictionary_bigrams_structure_policy.h"
+#include "suggest/core/policy/dictionary_shortcuts_structure_policy.h"
+#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+
+namespace latinime {
+
+const PtNodeParams DynamicPatriciaTrieNodeReader::fetchPtNodeInfoFromBufferAndProcessMovedPtNode(
+        const int ptNodePos, const int siblingNodePos, const int bigramLinkedNodePos) const {
+    if (ptNodePos < 0 || ptNodePos >= mBuffer->getTailPosition()) {
+        // Reading invalid position because of bug or broken dictionary.
+        AKLOGE("Fetching PtNode info from invalid dictionary position: %d, dictionary size: %d",
+                ptNodePos, mBuffer->getTailPosition());
+        ASSERT(false);
+        return PtNodeParams();
+    }
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(ptNodePos);
+    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
+    int pos = ptNodePos;
+    const int headPos = ptNodePos;
+    if (usesAdditionalBuffer) {
+        pos -= mBuffer->getOriginalBufferSize();
+    }
+    const PatriciaTrieReadingUtils::NodeFlags flags =
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictBuf, &pos);
+    const int parentPosOffset =
+            DynamicPatriciaTrieReadingUtils::getParentPtNodePosOffsetAndAdvancePosition(dictBuf,
+                    &pos);
+    const int parentPos =
+            DynamicPatriciaTrieReadingUtils::getParentPtNodePos(parentPosOffset, headPos);
+    int codePoints[MAX_WORD_LENGTH];
+    const int codePonitCount = PatriciaTrieReadingUtils::getCharsAndAdvancePosition(
+            dictBuf, flags, MAX_WORD_LENGTH, codePoints, &pos);
+    int probability = NOT_A_PROBABILITY;
+    int probabilityFieldPos = NOT_A_DICT_POS;
+    if (PatriciaTrieReadingUtils::isTerminal(flags)) {
+        probabilityFieldPos = pos;
+        if (usesAdditionalBuffer) {
+            probabilityFieldPos += mBuffer->getOriginalBufferSize();
+        }
+        probability = PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(dictBuf, &pos);
+    }
+    int childrenPosFieldPos = pos;
+    if (usesAdditionalBuffer) {
+        childrenPosFieldPos += mBuffer->getOriginalBufferSize();
+    }
+    int childrenPos = DynamicPatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(
+            dictBuf, &pos);
+    if (usesAdditionalBuffer && childrenPos != NOT_A_DICT_POS) {
+        childrenPos += mBuffer->getOriginalBufferSize();
+    }
+    int newBigramLinkedNodePos = bigramLinkedNodePos;
+    if (siblingNodePos == NOT_A_DICT_POS) {
+        if (DynamicPatriciaTrieReadingUtils::isMoved(flags)) {
+            newBigramLinkedNodePos = childrenPos;
+        }
+    }
+    if (usesAdditionalBuffer) {
+        pos += mBuffer->getOriginalBufferSize();
+    }
+    int shortcutsPos = NOT_A_DICT_POS;
+    if (PatriciaTrieReadingUtils::hasShortcutTargets(flags)) {
+        shortcutsPos = pos;
+        mShortcutsPolicy->skipAllShortcuts(&pos);
+    }
+    int bigramsPos = NOT_A_DICT_POS;
+    if (PatriciaTrieReadingUtils::hasBigrams(flags)) {
+        bigramsPos = pos;
+        mBigramsPolicy->skipAllBigrams(&pos);
+    }
+    int newSiblingNodePos = siblingNodePos;
+    if (siblingNodePos == NOT_A_DICT_POS) {
+        // Sibling position is the tail position of current node.
+        newSiblingNodePos = pos;
+    }
+    // Read destination node if the read node is a moved node.
+    if (DynamicPatriciaTrieReadingUtils::isMoved(flags)) {
+        // The destination position is stored at the same place as the parent position.
+        return fetchPtNodeInfoFromBufferAndProcessMovedPtNode(parentPos, newSiblingNodePos,
+                newBigramLinkedNodePos);
+    } else {
+        return PtNodeParams(headPos, flags, parentPos, codePonitCount, codePoints,
+                probabilityFieldPos, probability, childrenPosFieldPos, childrenPos,
+                newBigramLinkedNodePos, shortcutsPos, bigramsPos, newSiblingNodePos);
+    }
+}
+
+}
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_reader.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_reader.h
new file mode 100644
index 0000000..ef0067b
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_reader.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DYNAMIC_PATRICIA_TRIE_NODE_READER_H
+#define LATINIME_DYNAMIC_PATRICIA_TRIE_NODE_READER_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_reader.h"
+
+namespace latinime {
+
+class BufferWithExtendableBuffer;
+class DictionaryBigramsStructurePolicy;
+class DictionaryShortcutsStructurePolicy;
+
+/*
+ * This class is used for helping to read nodes of dynamic patricia trie. This class handles moved
+ * node and reads node attributes.
+ */
+class DynamicPatriciaTrieNodeReader : public PtNodeReader {
+ public:
+    DynamicPatriciaTrieNodeReader(const BufferWithExtendableBuffer *const buffer,
+            const DictionaryBigramsStructurePolicy *const bigramsPolicy,
+            const DictionaryShortcutsStructurePolicy *const shortcutsPolicy)
+            : mBuffer(buffer), mBigramsPolicy(bigramsPolicy),
+              mShortcutsPolicy(shortcutsPolicy) {}
+
+    ~DynamicPatriciaTrieNodeReader() {}
+
+    virtual const PtNodeParams fetchNodeInfoInBufferFromPtNodePos(const int ptNodePos) const {
+        return fetchPtNodeInfoFromBufferAndProcessMovedPtNode(ptNodePos,
+                NOT_A_DICT_POS /* siblingNodePos */, NOT_A_DICT_POS /* bigramLinkedNodePos */);
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DynamicPatriciaTrieNodeReader);
+
+    const BufferWithExtendableBuffer *const mBuffer;
+    const DictionaryBigramsStructurePolicy *const mBigramsPolicy;
+    const DictionaryShortcutsStructurePolicy *const mShortcutsPolicy;
+
+    const PtNodeParams fetchPtNodeInfoFromBufferAndProcessMovedPtNode(const int ptNodePos,
+            const int siblingNodePos, const int bigramLinkedNodePos) const;
+};
+} // namespace latinime
+#endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_NODE_READER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_writer.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_writer.cpp
new file mode 100644
index 0000000..3b1e16c
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_writer.cpp
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_writer.h"
+
+#include "suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h"
+#include "suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h"
+#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_reader.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_utils.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+
+namespace latinime {
+
+const int DynamicPatriciaTrieNodeWriter::CHILDREN_POSITION_FIELD_SIZE = 3;
+
+bool DynamicPatriciaTrieNodeWriter::markPtNodeAsDeleted(
+        const PtNodeParams *const toBeUpdatedPtNodeParams) {
+    int pos = toBeUpdatedPtNodeParams->getHeadPos();
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(pos);
+    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
+    if (usesAdditionalBuffer) {
+        pos -= mBuffer->getOriginalBufferSize();
+    }
+    // Read original flags
+    const PatriciaTrieReadingUtils::NodeFlags originalFlags =
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictBuf, &pos);
+    const PatriciaTrieReadingUtils::NodeFlags updatedFlags =
+            DynamicPatriciaTrieReadingUtils::updateAndGetFlags(originalFlags, false /* isMoved */,
+                    true /* isDeleted */);
+    int writingPos = toBeUpdatedPtNodeParams->getHeadPos();
+    // Update flags.
+    return DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(mBuffer, updatedFlags,
+            &writingPos);
+}
+
+bool DynamicPatriciaTrieNodeWriter::markPtNodeAsMoved(
+        const PtNodeParams *const toBeUpdatedPtNodeParams,
+        const int movedPos, const int bigramLinkedNodePos) {
+    int pos = toBeUpdatedPtNodeParams->getHeadPos();
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(pos);
+    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
+    if (usesAdditionalBuffer) {
+        pos -= mBuffer->getOriginalBufferSize();
+    }
+    // Read original flags
+    const PatriciaTrieReadingUtils::NodeFlags originalFlags =
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictBuf, &pos);
+    const PatriciaTrieReadingUtils::NodeFlags updatedFlags =
+            DynamicPatriciaTrieReadingUtils::updateAndGetFlags(originalFlags, true /* isMoved */,
+                    false /* isDeleted */);
+    int writingPos = toBeUpdatedPtNodeParams->getHeadPos();
+    // Update flags.
+    if (!DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(mBuffer, updatedFlags,
+            &writingPos)) {
+        return false;
+    }
+    // Update moved position, which is stored in the parent offset field.
+    if (!DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(
+            mBuffer, movedPos, toBeUpdatedPtNodeParams->getHeadPos(), &writingPos)) {
+        return false;
+    }
+    // Update bigram linked node position, which is stored in the children position field.
+    int childrenPosFieldPos = toBeUpdatedPtNodeParams->getChildrenPosFieldPos();
+    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(
+            mBuffer, bigramLinkedNodePos, &childrenPosFieldPos)) {
+        return false;
+    }
+    if (toBeUpdatedPtNodeParams->hasChildren()) {
+        // Update children's parent position.
+        mReadingHelper.initWithPtNodeArrayPos(toBeUpdatedPtNodeParams->getChildrenPos());
+        while (!mReadingHelper.isEnd()) {
+            const PtNodeParams childPtNodeParams(mReadingHelper.getPtNodeParams());
+            int parentOffsetFieldPos = childPtNodeParams.getHeadPos()
+                    + DynamicPatriciaTrieWritingUtils::NODE_FLAG_FIELD_SIZE;
+            if (!DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(
+                    mBuffer, bigramLinkedNodePos, childPtNodeParams.getHeadPos(),
+                    &parentOffsetFieldPos)) {
+                // Parent offset cannot be written because of a bug or a broken dictionary; thus,
+                // we give up to update dictionary.
+                return false;
+            }
+            mReadingHelper.readNextSiblingNode(childPtNodeParams);
+        }
+    }
+    return true;
+}
+
+bool DynamicPatriciaTrieNodeWriter::updatePtNodeProbability(
+        const PtNodeParams *const toBeUpdatedPtNodeParams, const int newProbability) {
+    if (!toBeUpdatedPtNodeParams->isTerminal()) {
+        return false;
+    }
+    int probabilityFieldPos = toBeUpdatedPtNodeParams->getProbabilityFieldPos();
+    return DynamicPatriciaTrieWritingUtils::writeProbabilityAndAdvancePosition(mBuffer,
+            newProbability, &probabilityFieldPos);
+}
+
+bool DynamicPatriciaTrieNodeWriter::updateChildrenPosition(
+        const PtNodeParams *const toBeUpdatedPtNodeParams, const int newChildrenPosition) {
+    int childrenPosFieldPos = toBeUpdatedPtNodeParams->getChildrenPosFieldPos();
+    return DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(mBuffer,
+            newChildrenPosition, &childrenPosFieldPos);
+}
+
+bool DynamicPatriciaTrieNodeWriter::writePtNodeAndAdvancePosition(
+        const PtNodeParams *const ptNodeParams, int *const ptNodeWritingPos) {
+    const int nodePos = *ptNodeWritingPos;
+    // Write dummy flags. The Node flags are updated with appropriate flags at the last step of the
+    // PtNode writing.
+    if (!DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(mBuffer,
+            0 /* nodeFlags */, ptNodeWritingPos)) {
+        return false;
+    }
+    // Calculate a parent offset and write the offset.
+    if (!DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(mBuffer,
+            ptNodeParams->getParentPos(), nodePos, ptNodeWritingPos)) {
+        return false;
+    }
+    // Write code points
+    if (!DynamicPatriciaTrieWritingUtils::writeCodePointsAndAdvancePosition(mBuffer,
+            ptNodeParams->getCodePoints(), ptNodeParams->getCodePointCount(), ptNodeWritingPos)) {
+        return false;
+    }
+    // Write probability when the probability is a valid probability, which means this node is
+    // terminal.
+    if (ptNodeParams->getProbability() != NOT_A_PROBABILITY) {
+        if (!DynamicPatriciaTrieWritingUtils::writeProbabilityAndAdvancePosition(mBuffer,
+                ptNodeParams->getProbability(), ptNodeWritingPos)) {
+            return false;
+        }
+    }
+    // Write children position
+    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(mBuffer,
+            ptNodeParams->getChildrenPos(), ptNodeWritingPos)) {
+        return false;
+    }
+    // Copy shortcut list when the originalShortcutListPos is valid dictionary position.
+    if (ptNodeParams->getShortcutPos() != NOT_A_DICT_POS) {
+        int fromPos = ptNodeParams->getShortcutPos();
+        if (!mShortcutPolicy->copyAllShortcutsAndReturnIfSucceededOrNot(mBuffer, &fromPos,
+                ptNodeWritingPos)) {
+            return false;
+        }
+    }
+    // Copy bigram list when the originalBigramListPos is valid dictionary position.
+    int bigramCount = 0;
+    if (ptNodeParams->getBigramsPos() != NOT_A_DICT_POS) {
+        int fromPos = ptNodeParams->getBigramsPos();
+        if (!mBigramPolicy->copyAllBigrams(mBuffer, &fromPos, ptNodeWritingPos, &bigramCount)) {
+            return false;
+        }
+    }
+    // Create node flags and write them.
+    PatriciaTrieReadingUtils::NodeFlags nodeFlags =
+            PatriciaTrieReadingUtils::createAndGetFlags(ptNodeParams->isBlacklisted(),
+                    ptNodeParams->isNotAWord(),
+                    ptNodeParams->getProbability() != NOT_A_PROBABILITY /* isTerminal */,
+                    ptNodeParams->getShortcutPos() != NOT_A_DICT_POS /* hasShortcutTargets */,
+                    bigramCount > 0 /* hasBigrams */,
+                    ptNodeParams->getCodePointCount() > 1 /* hasMultipleChars */,
+                    CHILDREN_POSITION_FIELD_SIZE);
+    int flagsFieldPos = nodePos;
+    if (!DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(mBuffer, nodeFlags,
+            &flagsFieldPos)) {
+        return false;
+    }
+    return true;
+}
+
+bool DynamicPatriciaTrieNodeWriter::addNewBigramEntry(
+        const PtNodeParams *const sourcePtNodeParams,
+        const PtNodeParams *const targetPtNodeParam, const int probability,
+        bool *const outAddedNewBigram) {
+    const int newNodePos = mBuffer->getTailPosition();
+    int writingPos = newNodePos;
+    // Write a new PtNode using original PtNode's info to the tail of the dictionary in mBuffer.
+    if (!writePtNodeAndAdvancePosition(sourcePtNodeParams, &writingPos)) {
+        return false;
+    }
+    if (!markPtNodeAsMoved(sourcePtNodeParams, newNodePos, newNodePos)) {
+        return false;
+    }
+    const PtNodeParams newPtNodeParams(
+            mPtNodeReader->fetchNodeInfoInBufferFromPtNodePos(newNodePos));
+    if (newPtNodeParams.getBigramsPos() != NOT_A_DICT_POS) {
+        // Insert a new bigram entry into the existing bigram list.
+        int bigramListPos = newPtNodeParams.getBigramsPos();
+        return mBigramPolicy->addNewBigramEntryToBigramList(targetPtNodeParam->getHeadPos(),
+                probability, &bigramListPos, outAddedNewBigram);
+    } else {
+        // The PtNode doesn't have a bigram list.
+        *outAddedNewBigram = true;
+        // First, Write a bigram entry at the tail position of the PtNode.
+        if (!mBigramPolicy->writeNewBigramEntry(targetPtNodeParam->getHeadPos(), probability,
+                &writingPos)) {
+            return false;
+        }
+        // Then, Mark as the PtNode having bigram list in the flags.
+        const PatriciaTrieReadingUtils::NodeFlags updatedFlags =
+                PatriciaTrieReadingUtils::createAndGetFlags(newPtNodeParams.isBlacklisted(),
+                        newPtNodeParams.isNotAWord(),
+                        newPtNodeParams.getProbability() != NOT_A_PROBABILITY,
+                        newPtNodeParams.getShortcutPos() != NOT_A_DICT_POS, true /* hasBigrams */,
+                        newPtNodeParams.getCodePointCount() > 1, CHILDREN_POSITION_FIELD_SIZE);
+        writingPos = newNodePos;
+        // Write updated flags into the moved PtNode's flags field.
+        return DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(mBuffer, updatedFlags,
+                &writingPos);
+    }
+}
+
+bool DynamicPatriciaTrieNodeWriter::removeBigramEntry(
+        const PtNodeParams *const sourcePtNodeParams, const PtNodeParams *const targetPtNodeParam) {
+    if (sourcePtNodeParams->getBigramsPos() == NOT_A_DICT_POS) {
+        return false;
+    }
+    return mBigramPolicy->removeBigram(sourcePtNodeParams->getBigramsPos(),
+            targetPtNodeParam->getHeadPos());
+}
+
+}
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_writer.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_writer.h
new file mode 100644
index 0000000..87ed1b1
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_writer.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DYNAMIC_PATRICIA_TRIE_NODE_WRITER_H
+#define LATINIME_DYNAMIC_PATRICIA_TRIE_NODE_WRITER_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_writer.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_reader.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_helper.h"
+
+namespace latinime {
+
+class BufferWithExtendableBuffer;
+class DynamicBigramListPolicy;
+class DynamicShortcutListPolicy;
+
+/*
+ * This class is used for helping to writes nodes of dynamic patricia trie.
+ */
+class DynamicPatriciaTrieNodeWriter : public PtNodeWriter {
+ public:
+    DynamicPatriciaTrieNodeWriter(BufferWithExtendableBuffer *const buffer,
+            const DynamicPatriciaTrieNodeReader *const ptNodeReader,
+            DynamicBigramListPolicy *const bigramPolicy,
+            DynamicShortcutListPolicy *const shortcutPolicy)
+            : mBuffer(buffer), mPtNodeReader(ptNodeReader), mReadingHelper(mBuffer, ptNodeReader),
+              mBigramPolicy(bigramPolicy), mShortcutPolicy(shortcutPolicy) {}
+
+    virtual ~DynamicPatriciaTrieNodeWriter() {}
+
+    virtual bool markPtNodeAsDeleted(const PtNodeParams *const toBeUpdatedPtNodeParams);
+
+    virtual bool markPtNodeAsMoved(const PtNodeParams *const toBeUpdatedPtNodeParams,
+            const int movedPos, const int bigramLinkedNodePos);
+
+    virtual bool updatePtNodeProbability(const PtNodeParams *const toBeUpdatedPtNodeParams,
+            const int newProbability);
+
+    virtual bool updateChildrenPosition(const PtNodeParams *const toBeUpdatedPtNodeParams,
+            const int newChildrenPosition);
+
+    virtual bool writePtNodeAndAdvancePosition(const PtNodeParams *const ptNodeParams,
+            int *const ptNodeWritingPos);
+
+    virtual bool addNewBigramEntry(const PtNodeParams *const sourcePtNodeParams,
+            const PtNodeParams *const targetPtNodeParam, const int probability,
+            bool *const outAddedNewBigram);
+
+    virtual bool removeBigramEntry(const PtNodeParams *const sourcePtNodeParams,
+            const PtNodeParams *const targetPtNodeParam);
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DynamicPatriciaTrieNodeWriter);
+
+    static const int CHILDREN_POSITION_FIELD_SIZE;
+
+    BufferWithExtendableBuffer *const mBuffer;
+    const DynamicPatriciaTrieNodeReader *const mPtNodeReader;
+    DynamicPatriciaTrieReadingHelper mReadingHelper;
+    DynamicBigramListPolicy *const mBigramPolicy;
+    DynamicShortcutListPolicy *const mShortcutPolicy;
+
+};
+} // namespace latinime
+#endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_NODE_WRITER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_policy.cpp
new file mode 100644
index 0000000..6b58f5f
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_policy.cpp
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_policy.h"
+
+#include <cstdio>
+#include <cstring>
+#include <ctime>
+
+#include "defines.h"
+#include "suggest/core/dicnode/dic_node.h"
+#include "suggest/core/dicnode/dic_node_vector.h"
+#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_reader.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_helper.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_updating_helper.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_helper.h"
+#include "suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h"
+#include "suggest/policyimpl/dictionary/utils/probability_utils.h"
+
+namespace latinime {
+
+// Note that these are corresponding definitions in Java side in BinaryDictionaryTests and
+// BinaryDictionaryDecayingTests.
+const char *const DynamicPatriciaTriePolicy::UNIGRAM_COUNT_QUERY = "UNIGRAM_COUNT";
+const char *const DynamicPatriciaTriePolicy::BIGRAM_COUNT_QUERY = "BIGRAM_COUNT";
+const char *const DynamicPatriciaTriePolicy::MAX_UNIGRAM_COUNT_QUERY = "MAX_UNIGRAM_COUNT";
+const char *const DynamicPatriciaTriePolicy::MAX_BIGRAM_COUNT_QUERY = "MAX_BIGRAM_COUNT";
+const char *const DynamicPatriciaTriePolicy::SET_NEEDS_TO_DECAY_FOR_TESTING_QUERY =
+        "SET_NEEDS_TO_DECAY_FOR_TESTING";
+const int DynamicPatriciaTriePolicy::MAX_DICT_EXTENDED_REGION_SIZE = 1024 * 1024;
+const int DynamicPatriciaTriePolicy::MIN_DICT_SIZE_TO_REFUSE_DYNAMIC_OPERATIONS =
+        DynamicPatriciaTrieWritingHelper::MAX_DICTIONARY_SIZE - 1024;
+
+void DynamicPatriciaTriePolicy::createAndGetAllChildDicNodes(const DicNode *const dicNode,
+        DicNodeVector *const childDicNodes) const {
+    if (!dicNode->hasChildren()) {
+        return;
+    }
+    DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer, &mNodeReader);
+    readingHelper.initWithPtNodeArrayPos(dicNode->getChildrenPtNodeArrayPos());
+    while (!readingHelper.isEnd()) {
+        const PtNodeParams ptNodeParams(readingHelper.getPtNodeParams());
+        if (!ptNodeParams.isValid()) {
+            break;
+        }
+        bool isTerminal = ptNodeParams.isTerminal() && !ptNodeParams.isDeleted();
+        if (isTerminal && mHeaderPolicy.isDecayingDict()) {
+            // A DecayingDict may have a terminal PtNode that has a terminal DicNode whose
+            // probability is NOT_A_PROBABILITY. In such case, we don't want to treat it as a
+            // valid terminal DicNode.
+            isTerminal = getProbability(ptNodeParams.getProbability(), NOT_A_PROBABILITY)
+                    != NOT_A_PROBABILITY;
+        }
+        childDicNodes->pushLeavingChild(dicNode, ptNodeParams.getHeadPos(),
+                ptNodeParams.getChildrenPos(), ptNodeParams.getProbability(), isTerminal,
+                ptNodeParams.hasChildren(),
+                ptNodeParams.isBlacklisted() || ptNodeParams.isNotAWord(),
+                ptNodeParams.getCodePointCount(), ptNodeParams.getCodePoints());
+        readingHelper.readNextSiblingNode(ptNodeParams);
+    }
+}
+
+int DynamicPatriciaTriePolicy::getCodePointsAndProbabilityAndReturnCodePointCount(
+        const int ptNodePos, const int maxCodePointCount, int *const outCodePoints,
+        int *const outUnigramProbability) const {
+    DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer, &mNodeReader);
+    readingHelper.initWithPtNodePos(ptNodePos);
+    return readingHelper.getCodePointsAndProbabilityAndReturnCodePointCount(maxCodePointCount,
+            outCodePoints, outUnigramProbability);
+}
+
+int DynamicPatriciaTriePolicy::getTerminalPtNodePositionOfWord(const int *const inWord,
+        const int length, const bool forceLowerCaseSearch) const {
+    DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer, &mNodeReader);
+    readingHelper.initWithPtNodeArrayPos(getRootPosition());
+    return readingHelper.getTerminalPtNodePositionOfWord(inWord, length, forceLowerCaseSearch);
+}
+
+int DynamicPatriciaTriePolicy::getProbability(const int unigramProbability,
+        const int bigramProbability) const {
+    if (mHeaderPolicy.isDecayingDict()) {
+        return ForgettingCurveUtils::getProbability(unigramProbability, bigramProbability);
+    } else {
+        if (unigramProbability == NOT_A_PROBABILITY) {
+            return NOT_A_PROBABILITY;
+        } else if (bigramProbability == NOT_A_PROBABILITY) {
+            return ProbabilityUtils::backoff(unigramProbability);
+        } else {
+            return ProbabilityUtils::computeProbabilityForBigram(unigramProbability,
+                    bigramProbability);
+        }
+    }
+}
+
+int DynamicPatriciaTriePolicy::getUnigramProbabilityOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
+        return NOT_A_PROBABILITY;
+    }
+    const PtNodeParams ptNodeParams(mNodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos));
+    if (ptNodeParams.isDeleted() || ptNodeParams.isBlacklisted() || ptNodeParams.isNotAWord()) {
+        return NOT_A_PROBABILITY;
+    }
+    return getProbability(ptNodeParams.getProbability(), NOT_A_PROBABILITY);
+}
+
+int DynamicPatriciaTriePolicy::getShortcutPositionOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
+        return NOT_A_DICT_POS;
+    }
+    const PtNodeParams ptNodeParams(mNodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos));
+    if (ptNodeParams.isDeleted()) {
+        return NOT_A_DICT_POS;
+    }
+    return ptNodeParams.getShortcutPos();
+}
+
+int DynamicPatriciaTriePolicy::getBigramsPositionOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
+        return NOT_A_DICT_POS;
+    }
+    const PtNodeParams ptNodeParams(mNodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos));
+    if (ptNodeParams.isDeleted()) {
+        return NOT_A_DICT_POS;
+    }
+    return ptNodeParams.getBigramsPos();
+}
+
+bool DynamicPatriciaTriePolicy::addUnigramWord(const int *const word, const int length,
+        const int probability) {
+    if (!mMmappedBuffer.get()->isUpdatable()) {
+        AKLOGI("Warning: addUnigramWord() is called for non-updatable dictionary.");
+        return false;
+    }
+    if (mBufferWithExtendableBuffer.getTailPosition()
+            >= MIN_DICT_SIZE_TO_REFUSE_DYNAMIC_OPERATIONS) {
+        AKLOGE("The dictionary is too large to dynamically update.");
+        return false;
+    }
+    DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer, &mNodeReader);
+    readingHelper.initWithPtNodeArrayPos(getRootPosition());
+    bool addedNewUnigram = false;
+    if (mUpdatingHelper.addUnigramWord(&readingHelper, word, length, probability,
+            &addedNewUnigram)) {
+        if (addedNewUnigram) {
+            mUnigramCount++;
+        }
+        return true;
+    } else {
+        return false;
+    }
+}
+
+bool DynamicPatriciaTriePolicy::addBigramWords(const int *const word0, const int length0,
+        const int *const word1, const int length1, const int probability) {
+    if (!mMmappedBuffer.get()->isUpdatable()) {
+        AKLOGI("Warning: addBigramWords() is called for non-updatable dictionary.");
+        return false;
+    }
+    if (mBufferWithExtendableBuffer.getTailPosition()
+            >= MIN_DICT_SIZE_TO_REFUSE_DYNAMIC_OPERATIONS) {
+        AKLOGE("The dictionary is too large to dynamically update.");
+        return false;
+    }
+    const int word0Pos = getTerminalPtNodePositionOfWord(word0, length0,
+            false /* forceLowerCaseSearch */);
+    if (word0Pos == NOT_A_DICT_POS) {
+        return false;
+    }
+    const int word1Pos = getTerminalPtNodePositionOfWord(word1, length1,
+            false /* forceLowerCaseSearch */);
+    if (word1Pos == NOT_A_DICT_POS) {
+        return false;
+    }
+    bool addedNewBigram = false;
+    if (mUpdatingHelper.addBigramWords(word0Pos, word1Pos, probability, &addedNewBigram)) {
+        if (addedNewBigram) {
+            mBigramCount++;
+        }
+        return true;
+    } else {
+        return false;
+    }
+}
+
+bool DynamicPatriciaTriePolicy::removeBigramWords(const int *const word0, const int length0,
+        const int *const word1, const int length1) {
+    if (!mMmappedBuffer.get()->isUpdatable()) {
+        AKLOGI("Warning: removeBigramWords() is called for non-updatable dictionary.");
+        return false;
+    }
+    if (mBufferWithExtendableBuffer.getTailPosition()
+            >= MIN_DICT_SIZE_TO_REFUSE_DYNAMIC_OPERATIONS) {
+        AKLOGE("The dictionary is too large to dynamically update.");
+        return false;
+    }
+    const int word0Pos = getTerminalPtNodePositionOfWord(word0, length0,
+            false /* forceLowerCaseSearch */);
+    if (word0Pos == NOT_A_DICT_POS) {
+        return false;
+    }
+    const int word1Pos = getTerminalPtNodePositionOfWord(word1, length1,
+            false /* forceLowerCaseSearch */);
+    if (word1Pos == NOT_A_DICT_POS) {
+        return false;
+    }
+    if (mUpdatingHelper.removeBigramWords(word0Pos, word1Pos)) {
+        mBigramCount--;
+        return true;
+    } else {
+        return false;
+    }
+}
+
+void DynamicPatriciaTriePolicy::flush(const char *const filePath) {
+    if (!mMmappedBuffer.get()->isUpdatable()) {
+        AKLOGI("Warning: flush() is called for non-updatable dictionary.");
+        return;
+    }
+    DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer, &mNodeReader,
+            &mNodeWriter, &mBigramListPolicy, &mShortcutListPolicy, false /* needsToDecay */);
+    writingHelper.writeToDictFile(filePath, &mHeaderPolicy, mUnigramCount, mBigramCount);
+}
+
+void DynamicPatriciaTriePolicy::flushWithGC(const char *const filePath) {
+    if (!mMmappedBuffer.get()->isUpdatable()) {
+        AKLOGI("Warning: flushWithGC() is called for non-updatable dictionary.");
+        return;
+    }
+    const bool needsToDecay = mHeaderPolicy.isDecayingDict()
+            && (mNeedsToDecayForTesting || ForgettingCurveUtils::needsToDecay(
+                    false /* mindsBlockByDecay */, mUnigramCount, mBigramCount, &mHeaderPolicy));
+    DynamicBigramListPolicy bigramListPolicyForGC(&mHeaderPolicy, &mBufferWithExtendableBuffer,
+            &mShortcutListPolicy, needsToDecay);
+    DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer, &mNodeReader,
+            &mNodeWriter, &bigramListPolicyForGC, &mShortcutListPolicy, needsToDecay);
+    writingHelper.writeToDictFileWithGC(getRootPosition(), filePath, &mHeaderPolicy);
+    mNeedsToDecayForTesting = false;
+}
+
+bool DynamicPatriciaTriePolicy::needsToRunGC(const bool mindsBlockByGC) const {
+    if (!mMmappedBuffer.get()->isUpdatable()) {
+        AKLOGI("Warning: needsToRunGC() is called for non-updatable dictionary.");
+        return false;
+    }
+    if (mBufferWithExtendableBuffer.isNearSizeLimit()) {
+        // Additional buffer size is near the limit.
+        return true;
+    } else if (mHeaderPolicy.getExtendedRegionSize()
+            + mBufferWithExtendableBuffer.getUsedAdditionalBufferSize()
+                    > MAX_DICT_EXTENDED_REGION_SIZE) {
+        // Total extended region size exceeds the limit.
+        return true;
+    } else if (mBufferWithExtendableBuffer.getTailPosition()
+            >= MIN_DICT_SIZE_TO_REFUSE_DYNAMIC_OPERATIONS
+                    && mBufferWithExtendableBuffer.getUsedAdditionalBufferSize() > 0) {
+        // Needs to reduce dictionary size.
+        return true;
+    } else if (mHeaderPolicy.isDecayingDict()) {
+        return mNeedsToDecayForTesting || ForgettingCurveUtils::needsToDecay(
+                mindsBlockByGC, mUnigramCount, mBigramCount, &mHeaderPolicy);
+    }
+    return false;
+}
+
+void DynamicPatriciaTriePolicy::getProperty(const char *const query, char *const outResult,
+        const int maxResultLength) {
+    if (strncmp(query, UNIGRAM_COUNT_QUERY, maxResultLength) == 0) {
+        snprintf(outResult, maxResultLength, "%d", mUnigramCount);
+    } else if (strncmp(query, BIGRAM_COUNT_QUERY, maxResultLength) == 0) {
+        snprintf(outResult, maxResultLength, "%d", mBigramCount);
+    } else if (strncmp(query, MAX_UNIGRAM_COUNT_QUERY, maxResultLength) == 0) {
+        snprintf(outResult, maxResultLength, "%d",
+                mHeaderPolicy.isDecayingDict() ? ForgettingCurveUtils::MAX_UNIGRAM_COUNT :
+                        static_cast<int>(DynamicPatriciaTrieWritingHelper::MAX_DICTIONARY_SIZE));
+    } else if (strncmp(query, MAX_BIGRAM_COUNT_QUERY, maxResultLength) == 0) {
+        snprintf(outResult, maxResultLength, "%d",
+                mHeaderPolicy.isDecayingDict() ? ForgettingCurveUtils::MAX_BIGRAM_COUNT :
+                        static_cast<int>(DynamicPatriciaTrieWritingHelper::MAX_DICTIONARY_SIZE));
+    } else if (strncmp(query, SET_NEEDS_TO_DECAY_FOR_TESTING_QUERY, maxResultLength) == 0) {
+        mNeedsToDecayForTesting = true;
+    }
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_policy.h
similarity index 71%
rename from native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h
rename to native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_policy.h
index be97ee1..4546984 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_policy.h
@@ -22,7 +22,11 @@
 #include "suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h"
 #include "suggest/policyimpl/dictionary/header/header_policy.h"
 #include "suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_reader.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_writer.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_updating_helper.h"
 #include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+#include "suggest/policyimpl/dictionary/utils/format_utils.h"
 #include "suggest/policyimpl/dictionary/utils/mmapped_buffer.h"
 
 namespace latinime {
@@ -32,32 +36,37 @@
 
 class DynamicPatriciaTriePolicy : public DictionaryStructureWithBufferPolicy {
  public:
-    DynamicPatriciaTriePolicy(const MmappedBuffer *const buffer)
-            : mBuffer(buffer), mHeaderPolicy(mBuffer->getBuffer(), buffer->getBufferSize()),
-              mBufferWithExtendableBuffer(mBuffer->getBuffer() + mHeaderPolicy.getSize(),
-                      mBuffer->getBufferSize() - mHeaderPolicy.getSize()),
+    DynamicPatriciaTriePolicy(const MmappedBuffer::MmappedBufferPtr &mmappedBuffer)
+            : mMmappedBuffer(mmappedBuffer),
+              mHeaderPolicy(mMmappedBuffer.get()->getBuffer(), FormatUtils::VERSION_3),
+              mBufferWithExtendableBuffer(mMmappedBuffer.get()->getBuffer()
+                      + mHeaderPolicy.getSize(), mMmappedBuffer.get()->getBufferSize()
+                              - mHeaderPolicy.getSize(),
+                                      BufferWithExtendableBuffer
+                                              ::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE),
               mShortcutListPolicy(&mBufferWithExtendableBuffer),
               mBigramListPolicy(&mHeaderPolicy, &mBufferWithExtendableBuffer, &mShortcutListPolicy,
                       mHeaderPolicy.isDecayingDict()),
+              mNodeReader(&mBufferWithExtendableBuffer, &mBigramListPolicy, &mShortcutListPolicy),
+              mNodeWriter(&mBufferWithExtendableBuffer, &mNodeReader, &mBigramListPolicy,
+                      &mShortcutListPolicy),
+              mUpdatingHelper(&mBufferWithExtendableBuffer, &mNodeReader, &mNodeWriter,
+                      mHeaderPolicy.isDecayingDict()),
               mUnigramCount(mHeaderPolicy.getUnigramCount()),
               mBigramCount(mHeaderPolicy.getBigramCount()), mNeedsToDecayForTesting(false) {}
 
-    ~DynamicPatriciaTriePolicy() {
-        delete mBuffer;
-    }
-
     AK_FORCE_INLINE int getRootPosition() const {
         return 0;
     }
 
-    void createAndGetAllChildNodes(const DicNode *const dicNode,
+    void createAndGetAllChildDicNodes(const DicNode *const dicNode,
             DicNodeVector *const childDicNodes) const;
 
     int getCodePointsAndProbabilityAndReturnCodePointCount(
             const int terminalPtNodePos, const int maxCodePointCount, int *const outCodePoints,
             int *const outUnigramProbability) const;
 
-    int getTerminalNodePositionOfWord(const int *const inWord,
+    int getTerminalPtNodePositionOfWord(const int *const inWord,
             const int length, const bool forceLowerCaseSearch) const;
 
     int getProbability(const int unigramProbability, const int bigramProbability) const;
@@ -108,11 +117,14 @@
     static const int MAX_DICT_EXTENDED_REGION_SIZE;
     static const int MIN_DICT_SIZE_TO_REFUSE_DYNAMIC_OPERATIONS;
 
-    const MmappedBuffer *const mBuffer;
+    const MmappedBuffer::MmappedBufferPtr mMmappedBuffer;
     const HeaderPolicy mHeaderPolicy;
     BufferWithExtendableBuffer mBufferWithExtendableBuffer;
     DynamicShortcutListPolicy mShortcutListPolicy;
     DynamicBigramListPolicy mBigramListPolicy;
+    DynamicPatriciaTrieNodeReader mNodeReader;
+    DynamicPatriciaTrieNodeWriter mNodeWriter;
+    DynamicPatriciaTrieUpdatingHelper mUpdatingHelper;
     int mUnigramCount;
     int mBigramCount;
     int mNeedsToDecayForTesting;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_helper.cpp
new file mode 100644
index 0000000..c3fe03d
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_helper.cpp
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_helper.h"
+
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_utils.h"
+#include "utils/char_utils.h"
+
+namespace latinime {
+
+// To avoid infinite loop caused by invalid or malicious forward links.
+const int DynamicPatriciaTrieReadingHelper::MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP = 100000;
+const int DynamicPatriciaTrieReadingHelper::MAX_PT_NODE_ARRAY_COUNT_TO_AVOID_INFINITE_LOOP = 100000;
+const size_t DynamicPatriciaTrieReadingHelper::MAX_READING_STATE_STACK_SIZE = MAX_WORD_LENGTH;
+
+// Visits all PtNodes in post-order depth first manner.
+// For example, visits c -> b -> y -> x -> a for the following dictionary:
+// a _ b _ c
+//   \ x _ y
+bool DynamicPatriciaTrieReadingHelper::traverseAllPtNodesInPostorderDepthFirstManner(
+        TraversingEventListener *const listener) {
+    bool alreadyVisitedChildren = false;
+    // Descend from the root to the root PtNode array.
+    if (!listener->onDescend(getPosOfLastPtNodeArrayHead())) {
+        return false;
+    }
+    while (!isEnd()) {
+        const PtNodeParams ptNodeParams(getPtNodeParams());
+        if (!ptNodeParams.isValid()) {
+            break;
+        }
+        if (!alreadyVisitedChildren) {
+            if (ptNodeParams.hasChildren()) {
+                // Move to the first child.
+                if (!listener->onDescend(ptNodeParams.getChildrenPos())) {
+                    return false;
+                }
+                pushReadingStateToStack();
+                readChildNode(ptNodeParams);
+            } else {
+                alreadyVisitedChildren = true;
+            }
+        } else {
+            if (!listener->onVisitingPtNode(&ptNodeParams)) {
+                return false;
+            }
+            readNextSiblingNode(ptNodeParams);
+            if (isEnd()) {
+                // All PtNodes in current linked PtNode arrays have been visited.
+                // Return to the parent.
+                if (!listener->onReadingPtNodeArrayTail()) {
+                    return false;
+                }
+                if (mReadingStateStack.size() <= 0) {
+                    break;
+                }
+                if (!listener->onAscend()) {
+                    return false;
+                }
+                popReadingStateFromStack();
+                alreadyVisitedChildren = true;
+            } else {
+                // Process sibling PtNode.
+                alreadyVisitedChildren = false;
+            }
+        }
+    }
+    // Ascend from the root PtNode array to the root.
+    if (!listener->onAscend()) {
+        return false;
+    }
+    return !isError();
+}
+
+// Visits all PtNodes in PtNode array level pre-order depth first manner, which is the same order
+// that PtNodes are written in the dictionary buffer.
+// For example, visits a -> b -> x -> c -> y for the following dictionary:
+// a _ b _ c
+//   \ x _ y
+bool DynamicPatriciaTrieReadingHelper::traverseAllPtNodesInPtNodeArrayLevelPreorderDepthFirstManner(
+        TraversingEventListener *const listener) {
+    bool alreadyVisitedAllPtNodesInArray = false;
+    bool alreadyVisitedChildren = false;
+    // Descend from the root to the root PtNode array.
+    if (!listener->onDescend(getPosOfLastPtNodeArrayHead())) {
+        return false;
+    }
+    if (isEnd()) {
+        // Empty dictionary. Needs to notify the listener of the tail of empty PtNode array.
+        if (!listener->onReadingPtNodeArrayTail()) {
+            return false;
+        }
+    }
+    pushReadingStateToStack();
+    while (!isEnd()) {
+        const PtNodeParams ptNodeParams(getPtNodeParams());
+        if (!ptNodeParams.isValid()) {
+            break;
+        }
+        if (alreadyVisitedAllPtNodesInArray) {
+            if (alreadyVisitedChildren) {
+                // Move to next sibling PtNode's children.
+                readNextSiblingNode(ptNodeParams);
+                if (isEnd()) {
+                    // Return to the parent PTNode.
+                    if (!listener->onAscend()) {
+                        return false;
+                    }
+                    if (mReadingStateStack.size() <= 0) {
+                        break;
+                    }
+                    popReadingStateFromStack();
+                    alreadyVisitedChildren = true;
+                    alreadyVisitedAllPtNodesInArray = true;
+                } else {
+                    alreadyVisitedChildren = false;
+                }
+            } else {
+                if (ptNodeParams.hasChildren()) {
+                    // Move to the first child.
+                    if (!listener->onDescend(ptNodeParams.getChildrenPos())) {
+                        return false;
+                    }
+                    pushReadingStateToStack();
+                    readChildNode(ptNodeParams);
+                    // Push state to return the head of PtNode array.
+                    pushReadingStateToStack();
+                    alreadyVisitedAllPtNodesInArray = false;
+                    alreadyVisitedChildren = false;
+                } else {
+                    alreadyVisitedChildren = true;
+                }
+            }
+        } else {
+            if (!listener->onVisitingPtNode(&ptNodeParams)) {
+                return false;
+            }
+            readNextSiblingNode(ptNodeParams);
+            if (isEnd()) {
+                if (!listener->onReadingPtNodeArrayTail()) {
+                    return false;
+                }
+                // Return to the head of current PtNode array.
+                popReadingStateFromStack();
+                alreadyVisitedAllPtNodesInArray = true;
+            }
+        }
+    }
+    popReadingStateFromStack();
+    // Ascend from the root PtNode array to the root.
+    if (!listener->onAscend()) {
+        return false;
+    }
+    return !isError();
+}
+
+int DynamicPatriciaTrieReadingHelper::getCodePointsAndProbabilityAndReturnCodePointCount(
+        const int maxCodePointCount, int *const outCodePoints, int *const outUnigramProbability) {
+    // This method traverses parent nodes from the terminal by following parent pointers; thus,
+    // node code points are stored in the buffer in the reverse order.
+    int reverseCodePoints[maxCodePointCount];
+    const PtNodeParams terminalPtNodeParams(getPtNodeParams());
+    // First, read the terminal node and get its probability.
+    if (!isValidTerminalNode(terminalPtNodeParams)) {
+        // Node at the ptNodePos is not a valid terminal node.
+        *outUnigramProbability = NOT_A_PROBABILITY;
+        return 0;
+    }
+    // Store terminal node probability.
+    *outUnigramProbability = terminalPtNodeParams.getProbability();
+    // Then, following parent node link to the dictionary root and fetch node code points.
+    int totalCodePointCount = 0;
+    while (!isEnd()) {
+        const PtNodeParams ptNodeParams(getPtNodeParams());
+        totalCodePointCount = getTotalCodePointCount(ptNodeParams);
+        if (!ptNodeParams.isValid() || totalCodePointCount > maxCodePointCount) {
+            // The ptNodePos is not a valid terminal node position in the dictionary.
+            *outUnigramProbability = NOT_A_PROBABILITY;
+            return 0;
+        }
+        // Store node code points to buffer in the reverse order.
+        fetchMergedNodeCodePointsInReverseOrder(ptNodeParams, getPrevTotalCodePointCount(),
+                reverseCodePoints);
+        // Follow parent node toward the root node.
+        readParentNode(ptNodeParams);
+    }
+    if (isError()) {
+        // The node position or the dictionary is invalid.
+        *outUnigramProbability = NOT_A_PROBABILITY;
+        return 0;
+    }
+    // Reverse the stored code points to output them.
+    for (int i = 0; i < totalCodePointCount; ++i) {
+        outCodePoints[i] = reverseCodePoints[totalCodePointCount - i - 1];
+    }
+    return totalCodePointCount;
+}
+
+int DynamicPatriciaTrieReadingHelper::getTerminalPtNodePositionOfWord(const int *const inWord,
+        const int length, const bool forceLowerCaseSearch) {
+    int searchCodePoints[length];
+    for (int i = 0; i < length; ++i) {
+        searchCodePoints[i] = forceLowerCaseSearch ? CharUtils::toLowerCase(inWord[i]) : inWord[i];
+    }
+    while (!isEnd()) {
+        const PtNodeParams ptNodeParams(getPtNodeParams());
+        const int matchedCodePointCount = getPrevTotalCodePointCount();
+        if (getTotalCodePointCount(ptNodeParams) > length
+                || !isMatchedCodePoint(ptNodeParams, 0 /* index */,
+                        searchCodePoints[matchedCodePointCount])) {
+            // Current node has too many code points or its first code point is different from
+            // target code point. Skip this node and read the next sibling node.
+            readNextSiblingNode(ptNodeParams);
+            continue;
+        }
+        // Check following merged node code points.
+        const int nodeCodePointCount = ptNodeParams.getCodePointCount();
+        for (int j = 1; j < nodeCodePointCount; ++j) {
+            if (!isMatchedCodePoint(ptNodeParams, j, searchCodePoints[matchedCodePointCount + j])) {
+                // Different code point is found. The given word is not included in the dictionary.
+                return NOT_A_DICT_POS;
+            }
+        }
+        // All characters are matched.
+        if (length == getTotalCodePointCount(ptNodeParams)) {
+            // Terminal position is found.
+            return ptNodeParams.getHeadPos();
+        }
+        if (!ptNodeParams.hasChildren()) {
+            return NOT_A_DICT_POS;
+        }
+        // Advance to the children nodes.
+        readChildNode(ptNodeParams);
+    }
+    // If we already traversed the tree further than the word is long, there means
+    // there was no match (or we would have found it).
+    return NOT_A_DICT_POS;
+}
+
+// Read node array size and process empty node arrays. Nodes and arrays are counted up in this
+// method to avoid an infinite loop.
+void DynamicPatriciaTrieReadingHelper::nextPtNodeArray() {
+    if (mReadingState.mPos < 0 || mReadingState.mPos >= mBuffer->getTailPosition()) {
+        // Reading invalid position because of a bug or a broken dictionary.
+        AKLOGE("Reading PtNode array info from invalid dictionary position: %d, dict size: %d",
+                mReadingState.mPos, mBuffer->getTailPosition());
+        ASSERT(false);
+        mIsError = true;
+        mReadingState.mPos = NOT_A_DICT_POS;
+        return;
+    }
+    mReadingState.mPosOfThisPtNodeArrayHead = mReadingState.mPos;
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(mReadingState.mPos);
+    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
+    if (usesAdditionalBuffer) {
+        mReadingState.mPos -= mBuffer->getOriginalBufferSize();
+    }
+    mReadingState.mRemainingPtNodeCountInThisArray =
+            PatriciaTrieReadingUtils::getPtNodeArraySizeAndAdvancePosition(dictBuf,
+                    &mReadingState.mPos);
+    if (usesAdditionalBuffer) {
+        mReadingState.mPos += mBuffer->getOriginalBufferSize();
+    }
+    // Count up nodes and node arrays to avoid infinite loop.
+    mReadingState.mTotalPtNodeIndexInThisArrayChain +=
+            mReadingState.mRemainingPtNodeCountInThisArray;
+    mReadingState.mPtNodeArrayIndexInThisArrayChain++;
+    if (mReadingState.mRemainingPtNodeCountInThisArray < 0
+            || mReadingState.mTotalPtNodeIndexInThisArrayChain
+                    > MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP
+            || mReadingState.mPtNodeArrayIndexInThisArrayChain
+                    > MAX_PT_NODE_ARRAY_COUNT_TO_AVOID_INFINITE_LOOP) {
+        // Invalid dictionary.
+        AKLOGI("Invalid dictionary. nodeCount: %d, totalNodeCount: %d, MAX_CHILD_COUNT: %d"
+                "nodeArrayCount: %d, MAX_NODE_ARRAY_COUNT: %d",
+                mReadingState.mRemainingPtNodeCountInThisArray,
+                mReadingState.mTotalPtNodeIndexInThisArrayChain,
+                MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP,
+                mReadingState.mPtNodeArrayIndexInThisArrayChain,
+                MAX_PT_NODE_ARRAY_COUNT_TO_AVOID_INFINITE_LOOP);
+        ASSERT(false);
+        mIsError = true;
+        mReadingState.mPos = NOT_A_DICT_POS;
+        return;
+    }
+    if (mReadingState.mRemainingPtNodeCountInThisArray == 0) {
+        // Empty node array. Try following forward link.
+        followForwardLink();
+    }
+}
+
+// Follow the forward link and read the next node array if exists.
+void DynamicPatriciaTrieReadingHelper::followForwardLink() {
+    if (mReadingState.mPos < 0 || mReadingState.mPos >= mBuffer->getTailPosition()) {
+        // Reading invalid position because of bug or broken dictionary.
+        AKLOGE("Reading forward link from invalid dictionary position: %d, dict size: %d",
+                mReadingState.mPos, mBuffer->getTailPosition());
+        ASSERT(false);
+        mIsError = true;
+        mReadingState.mPos = NOT_A_DICT_POS;
+        return;
+    }
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(mReadingState.mPos);
+    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
+    if (usesAdditionalBuffer) {
+        mReadingState.mPos -= mBuffer->getOriginalBufferSize();
+    }
+    const int forwardLinkPosition =
+            DynamicPatriciaTrieReadingUtils::getForwardLinkPosition(dictBuf, mReadingState.mPos);
+    if (usesAdditionalBuffer) {
+        mReadingState.mPos += mBuffer->getOriginalBufferSize();
+    }
+    mReadingState.mPosOfLastForwardLinkField = mReadingState.mPos;
+    if (DynamicPatriciaTrieReadingUtils::isValidForwardLinkPosition(forwardLinkPosition)) {
+        // Follow the forward link.
+        mReadingState.mPos += forwardLinkPosition;
+        nextPtNodeArray();
+    } else {
+        // All node arrays have been read.
+        mReadingState.mPos = NOT_A_DICT_POS;
+    }
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_helper.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_helper.h
new file mode 100644
index 0000000..1970273
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_helper.h
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DYNAMIC_PATRICIA_TRIE_READING_HELPER_H
+#define LATINIME_DYNAMIC_PATRICIA_TRIE_READING_HELPER_H
+
+#include <cstddef>
+#include <vector>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_reader.h"
+
+namespace latinime {
+
+class BufferWithExtendableBuffer;
+class DictionaryBigramsStructurePolicy;
+class DictionaryShortcutsStructurePolicy;
+
+/*
+ * This class is used for traversing dynamic patricia trie. This class supports iterating nodes and
+ * dealing with additional buffer. This class counts nodes and node arrays to avoid infinite loop.
+ */
+// TODO: Move to pt_common.
+class DynamicPatriciaTrieReadingHelper {
+ public:
+    class TraversingEventListener {
+     public:
+        virtual ~TraversingEventListener() {};
+
+        // Returns whether the event handling was succeeded or not.
+        virtual bool onAscend() = 0;
+
+        // Returns whether the event handling was succeeded or not.
+        virtual bool onDescend(const int ptNodeArrayPos) = 0;
+
+        // Returns whether the event handling was succeeded or not.
+        virtual bool onReadingPtNodeArrayTail() = 0;
+
+        // Returns whether the event handling was succeeded or not.
+        virtual bool onVisitingPtNode(const PtNodeParams *const node) = 0;
+
+     protected:
+        TraversingEventListener() {};
+
+     private:
+        DISALLOW_COPY_AND_ASSIGN(TraversingEventListener);
+    };
+
+    DynamicPatriciaTrieReadingHelper(const BufferWithExtendableBuffer *const buffer,
+            const PtNodeReader *const ptNodeReader)
+            : mIsError(false), mReadingState(), mBuffer(buffer),
+              mPtNodeReader(ptNodeReader), mReadingStateStack() {}
+
+    ~DynamicPatriciaTrieReadingHelper() {}
+
+    AK_FORCE_INLINE bool isError() const {
+        return mIsError;
+    }
+
+    AK_FORCE_INLINE bool isEnd() const {
+        return mReadingState.mPos == NOT_A_DICT_POS;
+    }
+
+    // Initialize reading state with the head position of a PtNode array.
+    AK_FORCE_INLINE void initWithPtNodeArrayPos(const int ptNodeArrayPos) {
+        if (ptNodeArrayPos == NOT_A_DICT_POS) {
+            mReadingState.mPos = NOT_A_DICT_POS;
+        } else {
+            mIsError = false;
+            mReadingState.mPos = ptNodeArrayPos;
+            mReadingState.mTotalCodePointCountSinceInitialization = 0;
+            mReadingState.mTotalPtNodeIndexInThisArrayChain = 0;
+            mReadingState.mPtNodeArrayIndexInThisArrayChain = 0;
+            mReadingState.mPosOfLastForwardLinkField = NOT_A_DICT_POS;
+            mReadingStateStack.clear();
+            nextPtNodeArray();
+        }
+    }
+
+    // Initialize reading state with the head position of a node.
+    AK_FORCE_INLINE void initWithPtNodePos(const int ptNodePos) {
+        if (ptNodePos == NOT_A_DICT_POS) {
+            mReadingState.mPos = NOT_A_DICT_POS;
+        } else {
+            mIsError = false;
+            mReadingState.mPos = ptNodePos;
+            mReadingState.mRemainingPtNodeCountInThisArray = 1;
+            mReadingState.mTotalCodePointCountSinceInitialization = 0;
+            mReadingState.mTotalPtNodeIndexInThisArrayChain = 1;
+            mReadingState.mPtNodeArrayIndexInThisArrayChain = 1;
+            mReadingState.mPosOfLastForwardLinkField = NOT_A_DICT_POS;
+            mReadingState.mPosOfThisPtNodeArrayHead = NOT_A_DICT_POS;
+            mReadingStateStack.clear();
+        }
+    }
+
+    AK_FORCE_INLINE const PtNodeParams getPtNodeParams() const {
+        if (isEnd()) {
+            return PtNodeParams();
+        }
+        return mPtNodeReader->fetchNodeInfoInBufferFromPtNodePos(mReadingState.mPos);
+    }
+
+    AK_FORCE_INLINE bool isValidTerminalNode(const PtNodeParams &ptNodeParams) const {
+        return !isEnd() && !ptNodeParams.isDeleted() && ptNodeParams.isTerminal();
+    }
+
+    AK_FORCE_INLINE bool isMatchedCodePoint(const PtNodeParams &ptNodeParams, const int index,
+            const int codePoint) const {
+        return ptNodeParams.getCodePoints()[index] == codePoint;
+    }
+
+    // Return code point count exclude the last read node's code points.
+    AK_FORCE_INLINE int getPrevTotalCodePointCount() const {
+        return mReadingState.mTotalCodePointCountSinceInitialization;
+    }
+
+    // Return code point count include the last read node's code points.
+    AK_FORCE_INLINE int getTotalCodePointCount(const PtNodeParams &ptNodeParams) const {
+        return mReadingState.mTotalCodePointCountSinceInitialization
+                + ptNodeParams.getCodePointCount();
+    }
+
+    AK_FORCE_INLINE void fetchMergedNodeCodePointsInReverseOrder(const PtNodeParams &ptNodeParams,
+            const int index, int *const outCodePoints) const {
+        const int nodeCodePointCount = ptNodeParams.getCodePointCount();
+        const int *const nodeCodePoints = ptNodeParams.getCodePoints();
+        for (int i =  0; i < nodeCodePointCount; ++i) {
+            outCodePoints[index + i] = nodeCodePoints[nodeCodePointCount - 1 - i];
+        }
+    }
+
+    AK_FORCE_INLINE void readNextSiblingNode(const PtNodeParams &ptNodeParams) {
+        mReadingState.mRemainingPtNodeCountInThisArray -= 1;
+        mReadingState.mPos = ptNodeParams.getSiblingNodePos();
+        if (mReadingState.mRemainingPtNodeCountInThisArray <= 0) {
+            // All nodes in the current node array have been read.
+            followForwardLink();
+        }
+    }
+
+    // Read the first child node of the current node.
+    AK_FORCE_INLINE void readChildNode(const PtNodeParams &ptNodeParams) {
+        if (ptNodeParams.hasChildren()) {
+            mReadingState.mTotalCodePointCountSinceInitialization +=
+                    ptNodeParams.getCodePointCount();
+            mReadingState.mTotalPtNodeIndexInThisArrayChain = 0;
+            mReadingState.mPtNodeArrayIndexInThisArrayChain = 0;
+            mReadingState.mPos = ptNodeParams.getChildrenPos();
+            mReadingState.mPosOfLastForwardLinkField = NOT_A_DICT_POS;
+            // Read children node array.
+            nextPtNodeArray();
+        } else {
+            mReadingState.mPos = NOT_A_DICT_POS;
+        }
+    }
+
+    // Read the parent node of the current node.
+    AK_FORCE_INLINE void readParentNode(const PtNodeParams &ptNodeParams) {
+        if (ptNodeParams.getParentPos() != NOT_A_DICT_POS) {
+            mReadingState.mTotalCodePointCountSinceInitialization +=
+                    ptNodeParams.getCodePointCount();
+            mReadingState.mTotalPtNodeIndexInThisArrayChain = 1;
+            mReadingState.mPtNodeArrayIndexInThisArrayChain = 1;
+            mReadingState.mRemainingPtNodeCountInThisArray = 1;
+            mReadingState.mPos = ptNodeParams.getParentPos();
+            mReadingState.mPosOfLastForwardLinkField = NOT_A_DICT_POS;
+            mReadingState.mPosOfThisPtNodeArrayHead = NOT_A_DICT_POS;
+        } else {
+            mReadingState.mPos = NOT_A_DICT_POS;
+        }
+    }
+
+    AK_FORCE_INLINE int getPosOfLastForwardLinkField() const {
+        return mReadingState.mPosOfLastForwardLinkField;
+    }
+
+    AK_FORCE_INLINE int getPosOfLastPtNodeArrayHead() const {
+        return mReadingState.mPosOfThisPtNodeArrayHead;
+    }
+
+    bool traverseAllPtNodesInPostorderDepthFirstManner(TraversingEventListener *const listener);
+
+    bool traverseAllPtNodesInPtNodeArrayLevelPreorderDepthFirstManner(
+            TraversingEventListener *const listener);
+
+    int getCodePointsAndProbabilityAndReturnCodePointCount(const int maxCodePointCount,
+            int *const outCodePoints, int *const outUnigramProbability);
+
+    int getTerminalPtNodePositionOfWord(const int *const inWord, const int length,
+            const bool forceLowerCaseSearch);
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DynamicPatriciaTrieReadingHelper);
+
+    // This class encapsulates the reading state of a position in the dictionary. It points at a
+    // specific PtNode in the dictionary.
+    class PtNodeReadingState {
+     public:
+        // Note that copy constructor and assignment operator are used for this class to use
+        // std::vector.
+        PtNodeReadingState() : mPos(NOT_A_DICT_POS), mRemainingPtNodeCountInThisArray(0),
+                mTotalCodePointCountSinceInitialization(0), mTotalPtNodeIndexInThisArrayChain(0),
+                mPtNodeArrayIndexInThisArrayChain(0), mPosOfLastForwardLinkField(NOT_A_DICT_POS),
+                mPosOfThisPtNodeArrayHead(NOT_A_DICT_POS) {}
+
+        int mPos;
+        // Remaining node count in the current array.
+        int mRemainingPtNodeCountInThisArray;
+        int mTotalCodePointCountSinceInitialization;
+        // Counter of PtNodes used to avoid infinite loops caused by broken or malicious links.
+        int mTotalPtNodeIndexInThisArrayChain;
+        // Counter of PtNode arrays used to avoid infinite loops caused by cyclic links of empty
+        // PtNode arrays.
+        int mPtNodeArrayIndexInThisArrayChain;
+        int mPosOfLastForwardLinkField;
+        int mPosOfThisPtNodeArrayHead;
+    };
+
+    static const int MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP;
+    static const int MAX_PT_NODE_ARRAY_COUNT_TO_AVOID_INFINITE_LOOP;
+    static const size_t MAX_READING_STATE_STACK_SIZE;
+
+    // TODO: Introduce error code to track what caused the error.
+    bool mIsError;
+    PtNodeReadingState mReadingState;
+    const BufferWithExtendableBuffer *const mBuffer;
+    const PtNodeReader *const mPtNodeReader;
+    std::vector<PtNodeReadingState> mReadingStateStack;
+
+    void nextPtNodeArray();
+
+    void followForwardLink();
+
+    AK_FORCE_INLINE void pushReadingStateToStack() {
+        if (mReadingStateStack.size() > MAX_READING_STATE_STACK_SIZE) {
+            AKLOGI("Reading state stack overflow. Max size: %zd", MAX_READING_STATE_STACK_SIZE);
+            ASSERT(false);
+            mIsError = true;
+            mReadingState.mPos = NOT_A_DICT_POS;
+        } else {
+            mReadingStateStack.push_back(mReadingState);
+        }
+    }
+
+    AK_FORCE_INLINE void popReadingStateFromStack() {
+        if (mReadingStateStack.empty()) {
+            mReadingState.mPos = NOT_A_DICT_POS;
+        } else {
+            mReadingState = mReadingStateStack.back();
+            mReadingStateStack.pop_back();
+        }
+    }
+};
+} // namespace latinime
+#endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_READING_HELPER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_utils.cpp
similarity index 96%
rename from native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp
rename to native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_utils.cpp
index d68446d..e949253 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_utils.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_utils.h"
 
 #include "defines.h"
 #include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_utils.h
similarity index 100%
rename from native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h
rename to native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_utils.h
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_updating_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_updating_helper.cpp
new file mode 100644
index 0000000..cbc0d23
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_updating_helper.cpp
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_updating_helper.h"
+
+#include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_reader.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_writer.h"
+#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_helper.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_utils.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+#include "suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h"
+
+namespace latinime {
+
+const int DynamicPatriciaTrieUpdatingHelper::CHILDREN_POSITION_FIELD_SIZE = 3;
+
+bool DynamicPatriciaTrieUpdatingHelper::addUnigramWord(
+        DynamicPatriciaTrieReadingHelper *const readingHelper,
+        const int *const wordCodePoints, const int codePointCount, const int probability,
+        bool *const outAddedNewUnigram) {
+    int parentPos = NOT_A_DICT_POS;
+    while (!readingHelper->isEnd()) {
+        const PtNodeParams ptNodeParams(readingHelper->getPtNodeParams());
+        if (!ptNodeParams.isValid()) {
+            break;
+        }
+        const int matchedCodePointCount = readingHelper->getPrevTotalCodePointCount();
+        if (!readingHelper->isMatchedCodePoint(ptNodeParams, 0 /* index */,
+                wordCodePoints[matchedCodePointCount])) {
+            // The first code point is different from target code point. Skip this node and read
+            // the next sibling node.
+            readingHelper->readNextSiblingNode(ptNodeParams);
+            continue;
+        }
+        // Check following merged node code points.
+        const int nodeCodePointCount = ptNodeParams.getCodePointCount();
+        for (int j = 1; j < nodeCodePointCount; ++j) {
+            const int nextIndex = matchedCodePointCount + j;
+            if (nextIndex >= codePointCount || !readingHelper->isMatchedCodePoint(ptNodeParams, j,
+                    wordCodePoints[matchedCodePointCount + j])) {
+                *outAddedNewUnigram = true;
+                return reallocatePtNodeAndAddNewPtNodes(&ptNodeParams, j,
+                        getUpdatedProbability(NOT_A_PROBABILITY /* originalProbability */,
+                                probability),
+                        wordCodePoints + matchedCodePointCount,
+                        codePointCount - matchedCodePointCount);
+            }
+        }
+        // All characters are matched.
+        if (codePointCount == readingHelper->getTotalCodePointCount(ptNodeParams)) {
+            return setPtNodeProbability(&ptNodeParams, probability, outAddedNewUnigram);
+        }
+        if (!ptNodeParams.hasChildren()) {
+            *outAddedNewUnigram = true;
+            return createChildrenPtNodeArrayAndAChildPtNode(&ptNodeParams,
+                    getUpdatedProbability(NOT_A_PROBABILITY /* originalProbability */, probability),
+                    wordCodePoints + readingHelper->getTotalCodePointCount(ptNodeParams),
+                    codePointCount - readingHelper->getTotalCodePointCount(ptNodeParams));
+        }
+        // Advance to the children nodes.
+        parentPos = ptNodeParams.getHeadPos();
+        readingHelper->readChildNode(ptNodeParams);
+    }
+    if (readingHelper->isError()) {
+        // The dictionary is invalid.
+        return false;
+    }
+    int pos = readingHelper->getPosOfLastForwardLinkField();
+    *outAddedNewUnigram = true;
+    return createAndInsertNodeIntoPtNodeArray(parentPos,
+            wordCodePoints + readingHelper->getPrevTotalCodePointCount(),
+            codePointCount - readingHelper->getPrevTotalCodePointCount(),
+            getUpdatedProbability(NOT_A_PROBABILITY /* originalProbability */, probability), &pos);
+}
+
+bool DynamicPatriciaTrieUpdatingHelper::addBigramWords(const int word0Pos, const int word1Pos,
+        const int probability, bool *const outAddedNewBigram) {
+    const PtNodeParams sourcePtNodeParams(
+            mPtNodeReader->fetchNodeInfoInBufferFromPtNodePos(word0Pos));
+    const PtNodeParams targetPtNodeParams(
+            mPtNodeReader->fetchNodeInfoInBufferFromPtNodePos(word1Pos));
+    return mPtNodeWriter->addNewBigramEntry(&sourcePtNodeParams, &targetPtNodeParams, probability,
+            outAddedNewBigram);
+}
+
+// Remove a bigram relation from word0Pos to word1Pos.
+bool DynamicPatriciaTrieUpdatingHelper::removeBigramWords(const int word0Pos, const int word1Pos) {
+    const PtNodeParams sourcePtNodeParams(
+            mPtNodeReader->fetchNodeInfoInBufferFromPtNodePos(word0Pos));
+    const PtNodeParams targetPtNodeParams(
+            mPtNodeReader->fetchNodeInfoInBufferFromPtNodePos(word1Pos));
+    return mPtNodeWriter->removeBigramEntry(&sourcePtNodeParams, &targetPtNodeParams);
+}
+
+bool DynamicPatriciaTrieUpdatingHelper::createAndInsertNodeIntoPtNodeArray(const int parentPos,
+        const int *const nodeCodePoints, const int nodeCodePointCount, const int probability,
+        int *const forwardLinkFieldPos) {
+    const int newPtNodeArrayPos = mBuffer->getTailPosition();
+    if (!DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(mBuffer,
+            newPtNodeArrayPos, forwardLinkFieldPos)) {
+        return false;
+    }
+    return createNewPtNodeArrayWithAChildPtNode(parentPos, nodeCodePoints, nodeCodePointCount,
+            probability);
+}
+
+bool DynamicPatriciaTrieUpdatingHelper::setPtNodeProbability(
+        const PtNodeParams *const originalPtNodeParams, const int probability,
+        bool *const outAddedNewUnigram) {
+    if (originalPtNodeParams->isTerminal()) {
+        // Overwrites the probability.
+        *outAddedNewUnigram = false;
+        const int probabilityToWrite = getUpdatedProbability(
+                originalPtNodeParams->getProbability(), probability);
+        return mPtNodeWriter->updatePtNodeProbability(originalPtNodeParams, probabilityToWrite);
+    } else {
+        // Make the node terminal and write the probability.
+        *outAddedNewUnigram = true;
+        const int movedPos = mBuffer->getTailPosition();
+        int writingPos = movedPos;
+        const PtNodeParams ptNodeParamsToWrite(getUpdatedPtNodeParams(originalPtNodeParams,
+                originalPtNodeParams->getParentPos(), originalPtNodeParams->getCodePointCount(),
+                originalPtNodeParams->getCodePoints(),
+                getUpdatedProbability(NOT_A_PROBABILITY /* originalProbability */, probability)));
+        if (!mPtNodeWriter->writePtNodeAndAdvancePosition(&ptNodeParamsToWrite, &writingPos)) {
+            return false;
+        }
+        if (!mPtNodeWriter->markPtNodeAsMoved(originalPtNodeParams, movedPos, movedPos)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool DynamicPatriciaTrieUpdatingHelper::createChildrenPtNodeArrayAndAChildPtNode(
+        const PtNodeParams *const parentPtNodeParams, const int probability,
+        const int *const codePoints, const int codePointCount) {
+    const int newPtNodeArrayPos = mBuffer->getTailPosition();
+    if (!mPtNodeWriter->updateChildrenPosition(parentPtNodeParams, newPtNodeArrayPos)) {
+        return false;
+    }
+    return createNewPtNodeArrayWithAChildPtNode(parentPtNodeParams->getHeadPos(), codePoints,
+            codePointCount, probability);
+}
+
+bool DynamicPatriciaTrieUpdatingHelper::createNewPtNodeArrayWithAChildPtNode(
+        const int parentPtNodePos, const int *const nodeCodePoints, const int nodeCodePointCount,
+        const int probability) {
+    int writingPos = mBuffer->getTailPosition();
+    if (!DynamicPatriciaTrieWritingUtils::writePtNodeArraySizeAndAdvancePosition(mBuffer,
+            1 /* arraySize */, &writingPos)) {
+        return false;
+    }
+    const PtNodeParams ptNodeParamsToWrite(getPtNodeParamsForNewPtNode(
+            parentPtNodePos, nodeCodePointCount, nodeCodePoints, probability));
+    if (!mPtNodeWriter->writePtNodeAndAdvancePosition(&ptNodeParamsToWrite, &writingPos)) {
+        return false;
+    }
+    if (!DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(mBuffer,
+            NOT_A_DICT_POS /* forwardLinkPos */, &writingPos)) {
+        return false;
+    }
+    return true;
+}
+
+// Returns whether the dictionary updating was succeeded or not.
+bool DynamicPatriciaTrieUpdatingHelper::reallocatePtNodeAndAddNewPtNodes(
+        const PtNodeParams *const reallocatingPtNodeParams, const int overlappingCodePointCount,
+        const int probabilityOfNewPtNode, const int *const newNodeCodePoints,
+        const int newNodeCodePointCount) {
+    // When addsExtraChild is true, split the reallocating PtNode and add new child.
+    // Reallocating PtNode: abcde, newNode: abcxy.
+    // abc (1st, not terminal) __ de (2nd)
+    //                         \_ xy (extra child, terminal)
+    // Otherwise, this method makes 1st part terminal and write probabilityOfNewPtNode.
+    // Reallocating PtNode: abcde, newNode: abc.
+    // abc (1st, terminal) __ de (2nd)
+    const bool addsExtraChild = newNodeCodePointCount > overlappingCodePointCount;
+    const int firstPartOfReallocatedPtNodePos = mBuffer->getTailPosition();
+    int writingPos = firstPartOfReallocatedPtNodePos;
+    // Write the 1st part of the reallocating node. The children position will be updated later
+    // with actual children position.
+    const int newProbability = addsExtraChild ? NOT_A_PROBABILITY : probabilityOfNewPtNode;
+    const PtNodeParams ptNodeParamsToWrite(getPtNodeParamsForNewPtNode(
+            reallocatingPtNodeParams->getParentPos(), overlappingCodePointCount,
+            reallocatingPtNodeParams->getCodePoints(), newProbability));
+    if (!mPtNodeWriter->writePtNodeAndAdvancePosition(&ptNodeParamsToWrite, &writingPos)) {
+        return false;
+    }
+    const int actualChildrenPos = writingPos;
+    // Create new children PtNode array.
+    const size_t newPtNodeCount = addsExtraChild ? 2 : 1;
+    if (!DynamicPatriciaTrieWritingUtils::writePtNodeArraySizeAndAdvancePosition(mBuffer,
+            newPtNodeCount, &writingPos)) {
+        return false;
+    }
+    // Write the 2nd part of the reallocating node.
+    const int secondPartOfReallocatedPtNodePos = writingPos;
+    const PtNodeParams childPartPtNodeParams(getUpdatedPtNodeParams(reallocatingPtNodeParams,
+            firstPartOfReallocatedPtNodePos,
+            reallocatingPtNodeParams->getCodePointCount() - overlappingCodePointCount,
+            reallocatingPtNodeParams->getCodePoints() + overlappingCodePointCount,
+            reallocatingPtNodeParams->getProbability()));
+    if (!mPtNodeWriter->writePtNodeAndAdvancePosition(&childPartPtNodeParams, &writingPos)) {
+        return false;
+    }
+    if (addsExtraChild) {
+        const PtNodeParams extraChildPtNodeParams(getPtNodeParamsForNewPtNode(
+                firstPartOfReallocatedPtNodePos, newNodeCodePointCount - overlappingCodePointCount,
+                newNodeCodePoints + overlappingCodePointCount, probabilityOfNewPtNode));
+        if (!mPtNodeWriter->writePtNodeAndAdvancePosition(&extraChildPtNodeParams, &writingPos)) {
+            return false;
+        }
+    }
+    if (!DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(mBuffer,
+            NOT_A_DICT_POS /* forwardLinkPos */, &writingPos)) {
+        return false;
+    }
+    // Update original reallocating PtNode as moved.
+    if (!mPtNodeWriter->markPtNodeAsMoved(reallocatingPtNodeParams, firstPartOfReallocatedPtNodePos,
+            secondPartOfReallocatedPtNodePos)) {
+        return false;
+    }
+    // Load node info. Information of the 1st part will be fetched.
+    const PtNodeParams ptNodeParams(
+            mPtNodeReader->fetchNodeInfoInBufferFromPtNodePos(firstPartOfReallocatedPtNodePos));
+    // Update children position.
+    return mPtNodeWriter->updateChildrenPosition(&ptNodeParams, actualChildrenPos);
+}
+
+int DynamicPatriciaTrieUpdatingHelper::getUpdatedProbability(const int originalProbability,
+        const int newProbability) const {
+    if (mNeedsToDecay) {
+        return ForgettingCurveUtils::getUpdatedEncodedProbability(originalProbability,
+                newProbability);
+    } else {
+        return newProbability;
+    }
+}
+
+const PtNodeParams DynamicPatriciaTrieUpdatingHelper::getUpdatedPtNodeParams(
+        const PtNodeParams *const originalPtNodeParams, const int parentPos,
+        const int codePointCount, const int *const codePoints, const int probability) const {
+    const PatriciaTrieReadingUtils::NodeFlags flags = PatriciaTrieReadingUtils::createAndGetFlags(
+            originalPtNodeParams->isBlacklisted(), originalPtNodeParams->isNotAWord(),
+            probability != NOT_A_PROBABILITY /* isTerminal */,
+            originalPtNodeParams->getShortcutPos() != NOT_A_DICT_POS /* hasShortcutTargets */,
+            originalPtNodeParams->getBigramsPos() != NOT_A_DICT_POS /* hasBigrams */,
+            codePointCount > 1 /* hasMultipleChars */, CHILDREN_POSITION_FIELD_SIZE);
+    return PtNodeParams(originalPtNodeParams, flags, parentPos, codePointCount, codePoints,
+            probability);
+}
+
+const PtNodeParams DynamicPatriciaTrieUpdatingHelper::getPtNodeParamsForNewPtNode(
+        const int parentPos, const int codePointCount, const int *const codePoints,
+        const int probability) const {
+    const PatriciaTrieReadingUtils::NodeFlags flags = PatriciaTrieReadingUtils::createAndGetFlags(
+            false /* isBlacklisted */, false /* isNotAWord */,
+            probability != NOT_A_PROBABILITY /* isTerminal */,
+            false /* hasShortcutTargets */, false /* hasBigrams */,
+            codePointCount > 1 /* hasMultipleChars */, CHILDREN_POSITION_FIELD_SIZE);
+    return PtNodeParams(flags, parentPos, codePointCount, codePoints, probability);
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_updating_helper.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_updating_helper.h
new file mode 100644
index 0000000..b9800cd
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_updating_helper.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DYNAMIC_PATRICIA_TRIE_UPDATING_HELPER_H
+#define LATINIME_DYNAMIC_PATRICIA_TRIE_UPDATING_HELPER_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h"
+#include "utils/hash_map_compat.h"
+
+namespace latinime {
+
+class BufferWithExtendableBuffer;
+class DynamicPatriciaTrieReadingHelper;
+class PtNodeReader;
+class PtNodeWriter;
+
+// TODO: Move to pt_common.
+class DynamicPatriciaTrieUpdatingHelper {
+ public:
+    DynamicPatriciaTrieUpdatingHelper(BufferWithExtendableBuffer *const buffer,
+            const PtNodeReader *const ptNodeReader, PtNodeWriter *const ptNodeWriter,
+            const bool needsToDecay)
+            : mBuffer(buffer), mPtNodeReader(ptNodeReader), mPtNodeWriter(ptNodeWriter),
+              mNeedsToDecay(needsToDecay) {}
+
+    ~DynamicPatriciaTrieUpdatingHelper() {}
+
+    // Add a word to the dictionary. If the word already exists, update the probability.
+    bool addUnigramWord(DynamicPatriciaTrieReadingHelper *const readingHelper,
+            const int *const wordCodePoints, const int codePointCount, const int probability,
+            bool *const outAddedNewUnigram);
+
+    // Add a bigram relation from word0Pos to word1Pos.
+    bool addBigramWords(const int word0Pos, const int word1Pos, const int probability,
+            bool *const outAddedNewBigram);
+
+    // Remove a bigram relation from word0Pos to word1Pos.
+    bool removeBigramWords(const int word0Pos, const int word1Pos);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicPatriciaTrieUpdatingHelper);
+
+    static const int CHILDREN_POSITION_FIELD_SIZE;
+
+    BufferWithExtendableBuffer *const mBuffer;
+    const PtNodeReader *const mPtNodeReader;
+    PtNodeWriter *const mPtNodeWriter;
+    const bool mNeedsToDecay;
+
+    bool createAndInsertNodeIntoPtNodeArray(const int parentPos, const int *const nodeCodePoints,
+            const int nodeCodePointCount, const int probability, int *const forwardLinkFieldPos);
+
+    bool setPtNodeProbability(const PtNodeParams *const originalPtNodeParams, const int probability,
+            bool *const outAddedNewUnigram);
+
+    bool createChildrenPtNodeArrayAndAChildPtNode(const PtNodeParams *const parentPtNodeParams,
+            const int probability, const int *const codePoints, const int codePointCount);
+
+    bool createNewPtNodeArrayWithAChildPtNode(const int parentPos, const int *const nodeCodePoints,
+            const int nodeCodePointCount, const int probability);
+
+    bool reallocatePtNodeAndAddNewPtNodes(
+            const PtNodeParams *const reallocatingPtNodeParams, const int overlappingCodePointCount,
+            const int probabilityOfNewPtNode, const int *const newNodeCodePoints,
+            const int newNodeCodePointCount);
+
+    int getUpdatedProbability(const int originalProbability, const int newProbability) const;
+
+    const PtNodeParams getUpdatedPtNodeParams(const PtNodeParams *const originalPtNodeParams,
+            const int parentPos, const int codePointCount, const int *const codePoints,
+            const int probability) const;
+
+    const PtNodeParams getPtNodeParamsForNewPtNode(const int parentPos, const int codePointCount,
+            const int *const codePoints, const int probability) const;
+};
+} // namespace latinime
+#endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_UPDATING_HELPER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_helper.cpp
new file mode 100644
index 0000000..d97cec5
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_helper.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_helper.h"
+
+#include "suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_reader.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_writer.h"
+#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_gc_event_listeners.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_reader.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_node_writer.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_helper.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_utils.h"
+#include "suggest/policyimpl/dictionary/header/header_policy.h"
+#include "suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h"
+#include "suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h"
+#include "suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h"
+#include "utils/hash_map_compat.h"
+
+namespace latinime {
+
+// TODO: Make MAX_DICTIONARY_SIZE 8MB.
+const size_t DynamicPatriciaTrieWritingHelper::MAX_DICTIONARY_SIZE = 2 * 1024 * 1024;
+
+void DynamicPatriciaTrieWritingHelper::writeToDictFile(const char *const fileName,
+        const HeaderPolicy *const headerPolicy, const int unigramCount, const int bigramCount) {
+    BufferWithExtendableBuffer headerBuffer(
+            BufferWithExtendableBuffer::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE);
+    const int extendedRegionSize = headerPolicy->getExtendedRegionSize() +
+            mBuffer->getUsedAdditionalBufferSize();
+    if (!headerPolicy->writeHeaderToBuffer(&headerBuffer, false /* updatesLastUpdatedTime */,
+            false /* updatesLastDecayedTime */, unigramCount, bigramCount, extendedRegionSize)) {
+        return;
+    }
+    DictFileWritingUtils::flushAllHeaderAndBodyToFile(fileName, &headerBuffer, mBuffer);
+}
+
+void DynamicPatriciaTrieWritingHelper::writeToDictFileWithGC(const int rootPtNodeArrayPos,
+        const char *const fileName, const HeaderPolicy *const headerPolicy) {
+    BufferWithExtendableBuffer newDictBuffer(MAX_DICTIONARY_SIZE);
+    int unigramCount = 0;
+    int bigramCount = 0;
+    if (mNeedsToDecay) {
+        ForgettingCurveUtils::sTimeKeeper.setCurrentTime();
+    }
+    if (!runGC(rootPtNodeArrayPos, headerPolicy, &newDictBuffer, &unigramCount, &bigramCount)) {
+        return;
+    }
+    BufferWithExtendableBuffer headerBuffer(
+            BufferWithExtendableBuffer::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE);
+    if (!headerPolicy->writeHeaderToBuffer(&headerBuffer, true /* updatesLastUpdatedTime */,
+            mNeedsToDecay, unigramCount, bigramCount, 0 /* extendedRegionSize */)) {
+        return;
+    }
+    DictFileWritingUtils::flushAllHeaderAndBodyToFile(fileName, &headerBuffer, &newDictBuffer);
+}
+
+// TODO: Make this method version independent.
+bool DynamicPatriciaTrieWritingHelper::runGC(const int rootPtNodeArrayPos,
+        const HeaderPolicy *const headerPolicy, BufferWithExtendableBuffer *const bufferToWrite,
+        int *const outUnigramCount, int *const outBigramCount) {
+    DynamicPatriciaTrieNodeReader ptNodeReader(mBuffer, mBigramPolicy, mShortcutPolicy);
+    DynamicPatriciaTrieReadingHelper readingHelper(mBuffer, &ptNodeReader);
+    readingHelper.initWithPtNodeArrayPos(rootPtNodeArrayPos);
+    DynamicPatriciaTrieGcEventListeners
+            ::TraversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted
+                    traversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted(
+                            headerPolicy, mPtNodeWriter, mBuffer, mNeedsToDecay);
+    if (!readingHelper.traverseAllPtNodesInPostorderDepthFirstManner(
+            &traversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted)) {
+        return false;
+    }
+    if (mNeedsToDecay && traversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted
+            .getValidUnigramCount() > ForgettingCurveUtils::MAX_UNIGRAM_COUNT_AFTER_GC) {
+        // TODO: Remove more unigrams.
+    }
+
+    readingHelper.initWithPtNodeArrayPos(rootPtNodeArrayPos);
+    DynamicPatriciaTrieGcEventListeners::TraversePolicyToUpdateBigramProbability
+            traversePolicyToUpdateBigramProbability(mBigramPolicy);
+    if (!readingHelper.traverseAllPtNodesInPostorderDepthFirstManner(
+            &traversePolicyToUpdateBigramProbability)) {
+        return false;
+    }
+    if (mNeedsToDecay && traversePolicyToUpdateBigramProbability.getValidBigramEntryCount()
+            > ForgettingCurveUtils::MAX_BIGRAM_COUNT_AFTER_GC) {
+        // TODO: Remove more bigrams.
+    }
+
+    // Mapping from positions in mBuffer to positions in bufferToWrite.
+    DictPositionRelocationMap dictPositionRelocationMap;
+    readingHelper.initWithPtNodeArrayPos(rootPtNodeArrayPos);
+    DynamicPatriciaTrieNodeWriter newPtNodeWriter(bufferToWrite,
+            &ptNodeReader, mBigramPolicy, mShortcutPolicy);
+    DynamicPatriciaTrieGcEventListeners::TraversePolicyToPlaceAndWriteValidPtNodesToBuffer
+            traversePolicyToPlaceAndWriteValidPtNodesToBuffer(&newPtNodeWriter, bufferToWrite,
+                    &dictPositionRelocationMap);
+    if (!readingHelper.traverseAllPtNodesInPtNodeArrayLevelPreorderDepthFirstManner(
+            &traversePolicyToPlaceAndWriteValidPtNodesToBuffer)) {
+        return false;
+    }
+
+    // Create policy instance for the GCed dictionary.
+    DynamicShortcutListPolicy newDictShortcutPolicy(bufferToWrite);
+    DynamicBigramListPolicy newDictBigramPolicy(headerPolicy, bufferToWrite, &newDictShortcutPolicy,
+            mNeedsToDecay);
+    // Create reading node reader and reading helper for the GCed dictionary.
+    DynamicPatriciaTrieNodeReader newDictNodeReader(bufferToWrite, &newDictBigramPolicy,
+            &newDictShortcutPolicy);
+    DynamicPatriciaTrieReadingHelper newDictReadingHelper(bufferToWrite, &newDictNodeReader);
+    newDictReadingHelper.initWithPtNodeArrayPos(rootPtNodeArrayPos);
+    DynamicPatriciaTrieGcEventListeners::TraversePolicyToUpdateAllPositionFields
+            traversePolicyToUpdateAllPositionFields(&newDictBigramPolicy, bufferToWrite,
+                    &dictPositionRelocationMap);
+    if (!newDictReadingHelper.traverseAllPtNodesInPtNodeArrayLevelPreorderDepthFirstManner(
+            &traversePolicyToUpdateAllPositionFields)) {
+        return false;
+    }
+    *outUnigramCount = traversePolicyToUpdateAllPositionFields.getUnigramCount();
+    *outBigramCount = traversePolicyToUpdateAllPositionFields.getBigramCount();
+    return true;
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_helper.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_helper.h
new file mode 100644
index 0000000..7223dea
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_helper.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_HELPER_H
+#define LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_HELPER_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "utils/hash_map_compat.h"
+
+namespace latinime {
+
+class BufferWithExtendableBuffer;
+class DynamicBigramListPolicy;
+class DynamicPatriciaTrieReadingHelper;
+class DynamicShortcutListPolicy;
+class HeaderPolicy;
+class PtNodeReader;
+class PtNodeWriter;
+
+// TODO: Make it independent from a particular format and move to pt_common.
+class DynamicPatriciaTrieWritingHelper {
+ public:
+    typedef hash_map_compat<int, int> PtNodeArrayPositionRelocationMap;
+    typedef hash_map_compat<int, int> PtNodePositionRelocationMap;
+    struct DictPositionRelocationMap {
+     public:
+        DictPositionRelocationMap()
+                : mPtNodeArrayPositionRelocationMap(), mPtNodePositionRelocationMap() {}
+
+        PtNodeArrayPositionRelocationMap mPtNodeArrayPositionRelocationMap;
+        PtNodePositionRelocationMap mPtNodePositionRelocationMap;
+
+     private:
+        DISALLOW_COPY_AND_ASSIGN(DictPositionRelocationMap);
+    };
+
+    static const size_t MAX_DICTIONARY_SIZE;
+
+    DynamicPatriciaTrieWritingHelper(BufferWithExtendableBuffer *const buffer,
+            const PtNodeReader *const ptNodeReader, PtNodeWriter *const ptNodeWriter,
+            DynamicBigramListPolicy *const bigramPolicy,
+            DynamicShortcutListPolicy *const shortcutPolicy, const bool needsToDecay)
+            : mBuffer(buffer), mPtNodeReader(ptNodeReader), mPtNodeWriter(ptNodeWriter),
+              mBigramPolicy(bigramPolicy), mShortcutPolicy(shortcutPolicy),
+              mNeedsToDecay(needsToDecay) {}
+
+    ~DynamicPatriciaTrieWritingHelper() {}
+
+    void writeToDictFile(const char *const fileName, const HeaderPolicy *const headerPolicy,
+            const int unigramCount, const int bigramCount);
+
+    void writeToDictFileWithGC(const int rootPtNodeArrayPos, const char *const fileName,
+            const HeaderPolicy *const headerPolicy);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicPatriciaTrieWritingHelper);
+
+    BufferWithExtendableBuffer *const mBuffer;
+    const PtNodeReader *const mPtNodeReader;
+    PtNodeWriter *const mPtNodeWriter;
+    DynamicBigramListPolicy *const mBigramPolicy;
+    DynamicShortcutListPolicy *const mShortcutPolicy;
+    const bool mNeedsToDecay;
+
+    bool runGC(const int rootPtNodeArrayPos, const HeaderPolicy *const headerPolicy,
+            BufferWithExtendableBuffer *const bufferToWrite, int *const outUnigramCount,
+            int *const outBigramCount);
+};
+} // namespace latinime
+#endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_HELPER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_utils.cpp
similarity index 98%
rename from native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.cpp
rename to native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_utils.cpp
index 30ff10c..6773366 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_utils.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_utils.h"
 
 #include <cstddef>
 #include <cstdlib>
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_utils.h
similarity index 96%
rename from native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h
rename to native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_utils.h
index af76bc6..5654105 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_utils.h
@@ -20,7 +20,7 @@
 #include <cstddef>
 
 #include "defines.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_utils.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/bigram_dict_content.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/bigram_dict_content.h
new file mode 100644
index 0000000..5eed13e
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/bigram_dict_content.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_BIGRAM_DICT_CONTENT_H
+#define LATINIME_BIGRAM_DICT_CONTENT_H
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/structure/v4/content/sparse_table_dict_content.h"
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h"
+
+namespace latinime {
+
+class BigramDictContent : public SparseTableDictContent {
+ public:
+    BigramDictContent(const char *const dictDirPath, const bool isUpdatable)
+            : SparseTableDictContent(dictDirPath,
+                      Ver4DictConstants::BIGRAM_LOOKUP_TABLE_FILE_EXTENSION,
+                      Ver4DictConstants::BIGRAM_CONTENT_TABLE_FILE_EXTENSION,
+                      Ver4DictConstants::BIGRAM_FILE_EXTENSION, isUpdatable,
+                      Ver4DictConstants::BIGRAM_ADDRESS_TABLE_BLOCK_SIZE,
+                      Ver4DictConstants::BIGRAM_ADDRESS_TABLE_DATA_SIZE) {}
+
+    void getBigramEntryAndAdvancePosition(int *const outBigramFlags,
+            int *const outTargetTerminalId, int *const bigramEntryPos) const {
+        const BufferWithExtendableBuffer *const bigramListBuffer = getContentBuffer();
+        if (outBigramFlags) {
+            *outBigramFlags = bigramListBuffer->readUintAndAdvancePosition(
+                    Ver4DictConstants::BIGRAM_FLAGS_FIELD_SIZE, bigramEntryPos);
+        }
+        if (outTargetTerminalId) {
+            *outTargetTerminalId = bigramListBuffer->readUintAndAdvancePosition(
+                    Ver4DictConstants::BIGRAM_TARGET_TERMINAL_ID_FIELD_SIZE, bigramEntryPos);
+        }
+    }
+
+   // Returns head position of bigram list for a PtNode specified by terminalId.
+   int getBigramListHeadPos(const int terminalId) const {
+        const SparseTable *const addressLookupTable = getAddressLookupTable();
+        if (!addressLookupTable->contains(terminalId)) {
+            return NOT_A_DICT_POS;
+        }
+        return addressLookupTable->get(terminalId);
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(BigramDictContent);
+};
+} // namespace latinime
+#endif /* LATINIME_BIGRAM_DICT_CONTENT_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/dict_content.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/dict_content.h
new file mode 100644
index 0000000..0c2f470
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/dict_content.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DICT_CONTENT_H
+#define LATINIME_DICT_CONTENT_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class DictContent {
+ public:
+    virtual ~DictContent() {}
+    virtual bool isValid() const = 0;
+
+ protected:
+    DictContent() {}
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DictContent);
+};
+} // namespace latinime
+#endif /* LATINIME_DICT_CONTENT_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/probability_dict_content.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/probability_dict_content.h
new file mode 100644
index 0000000..daaf08f
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/probability_dict_content.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_PROBABILITY_DICT_CONTENT_H
+#define LATINIME_PROBABILITY_DICT_CONTENT_H
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/structure/v4/content/single_dict_content.h"
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h"
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+
+namespace latinime {
+
+class ProbabilityDictContent : public SingleDictContent {
+ public:
+    ProbabilityDictContent(const char *const dictDirPath, const bool isUpdatable)
+            : SingleDictContent(dictDirPath, Ver4DictConstants::FREQ_FILE_EXTENSION,
+                    isUpdatable) {}
+
+    int getProbability(const int terminalId) const {
+        if (terminalId < 0 || terminalId >= getSize()) {
+            return NOT_A_PROBABILITY;
+        }
+        return Ver4PatriciaTrieReadingUtils::getProbability(getBuffer(), terminalId);
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ProbabilityDictContent);
+
+    int getSize() const {
+        return getBuffer()->getTailPosition() / (Ver4DictConstants::PROBABILITY_SIZE
+                + Ver4DictConstants::FLAGS_IN_PROBABILITY_FILE_SIZE);
+    }
+};
+} // namespace latinime
+#endif /* LATINIME_PROBABILITY_DICT_CONTENT_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/shortcut_dict_content.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/shortcut_dict_content.h
new file mode 100644
index 0000000..c10fbcb
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/shortcut_dict_content.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_SHORTCUT_DICT_CONTENT_H
+#define LATINIME_SHORTCUT_DICT_CONTENT_H
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/structure/v4/content/sparse_table_dict_content.h"
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h"
+
+namespace latinime {
+
+class ShortcutDictContent : public SparseTableDictContent {
+ public:
+    ShortcutDictContent(const char *const dictDirPath, const bool isUpdatable)
+            : SparseTableDictContent(dictDirPath,
+                      Ver4DictConstants::SHORTCUT_LOOKUP_TABLE_FILE_EXTENSION,
+                      Ver4DictConstants::SHORTCUT_CONTENT_TABLE_FILE_EXTENSION,
+                      Ver4DictConstants::SHORTCUT_FILE_EXTENSION, isUpdatable,
+                      Ver4DictConstants::SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE,
+                      Ver4DictConstants::SHORTCUT_ADDRESS_TABLE_DATA_SIZE) {}
+
+    void getShortcutEntryAndAdvancePosition(const int maxCodePointCount,
+            int *const outCodePoint, int *const outCodePointCount, int *const outShortcutFlags,
+            int *const shortcutEntryPos) const {
+        const BufferWithExtendableBuffer *const shortcutListBuffer = getContentBuffer();
+        if (outShortcutFlags) {
+            *outShortcutFlags = shortcutListBuffer->readUintAndAdvancePosition(
+                    Ver4DictConstants::SHORTCUT_FLAGS_FIELD_SIZE, shortcutEntryPos);
+        }
+        if (outCodePoint && outCodePointCount) {
+            shortcutListBuffer->readCodePointsAndAdvancePosition(
+                    maxCodePointCount, outCodePoint, outCodePointCount, shortcutEntryPos);
+        }
+    }
+
+   // Returns head position of shortcut list for a PtNode specified by terminalId.
+   int getShortcutListHeadPos(const int terminalId) const {
+        const SparseTable *const addressLookupTable = getAddressLookupTable();
+        if (!addressLookupTable->contains(terminalId)) {
+            return NOT_A_DICT_POS;
+        }
+        return addressLookupTable->get(terminalId);
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ShortcutDictContent);
+};
+} // namespace latinime
+#endif /* LATINIME_SHORTCUT_DICT_CONTENT_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/single_dict_content.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/single_dict_content.h
new file mode 100644
index 0000000..4cb96da
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/single_dict_content.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_SINGLE_DICT_CONTENT_H
+#define LATINIME_SINGLE_DICT_CONTENT_H
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/structure/v4/content/dict_content.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+#include "suggest/policyimpl/dictionary/utils/mmapped_buffer.h"
+
+namespace latinime {
+
+class SingleDictContent : public DictContent {
+ public:
+    SingleDictContent(const char *const dictDirPath, const char *const contentFileName,
+            const bool isUpdatable)
+            : mMmappedBuffer(MmappedBuffer::openBuffer(dictDirPath, contentFileName, isUpdatable)),
+              mExpandableContentBuffer(mMmappedBuffer.get() ? mMmappedBuffer.get()->getBuffer() : 0,
+                      mMmappedBuffer.get() ? mMmappedBuffer.get()->getBufferSize() : 0,
+                      BufferWithExtendableBuffer::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE) {}
+
+    virtual ~SingleDictContent() {}
+
+    virtual bool isValid() const {
+        return mMmappedBuffer.get() != 0;
+    }
+
+ protected:
+    BufferWithExtendableBuffer *getWritableBuffer() {
+        return &mExpandableContentBuffer;
+    }
+
+    const BufferWithExtendableBuffer *getBuffer() const {
+        return &mExpandableContentBuffer;
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(SingleDictContent);
+
+    const MmappedBuffer::MmappedBufferPtr mMmappedBuffer;
+    BufferWithExtendableBuffer mExpandableContentBuffer;
+};
+} // namespace latinime
+#endif /* LATINIME_SINGLE_DICT_CONTENT_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/sparse_table_dict_content.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/sparse_table_dict_content.h
new file mode 100644
index 0000000..71868e9
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/sparse_table_dict_content.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_SPARSE_TABLE_DICT_CONTENT_H
+#define LATINIME_SPARSE_TABLE_DICT_CONTENT_H
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/structure/v4/content/dict_content.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+#include "suggest/policyimpl/dictionary/utils/mmapped_buffer.h"
+#include "suggest/policyimpl/dictionary/utils/sparse_table.h"
+
+namespace latinime {
+
+// TODO: Support multiple contents.
+class SparseTableDictContent : public DictContent {
+ public:
+    AK_FORCE_INLINE SparseTableDictContent(const char *const dictDirPath,
+            const char *const lookupTableFileName, const char *const addressTableFileName,
+            const char *const contentFileName, const bool isUpdatable,
+            const int sparseTableBlockSize, const int sparseTableDataSize)
+            : mLookupTableBuffer(
+                      MmappedBuffer::openBuffer(dictDirPath, lookupTableFileName, isUpdatable)),
+              mAddressTableBuffer(
+                      MmappedBuffer::openBuffer(dictDirPath, addressTableFileName, isUpdatable)),
+              mContentBuffer(MmappedBuffer::openBuffer(dictDirPath, contentFileName, isUpdatable)),
+              mExpandableLookupTableBuffer(
+                      mLookupTableBuffer.get() ? mLookupTableBuffer.get()->getBuffer() : 0,
+                      mLookupTableBuffer.get() ? mLookupTableBuffer.get()->getBufferSize() : 0,
+                      BufferWithExtendableBuffer::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE),
+              mExpandableAddressTableBuffer(
+                      mAddressTableBuffer.get() ? mAddressTableBuffer.get()->getBuffer() : 0,
+                      mAddressTableBuffer.get() ? mAddressTableBuffer.get()->getBufferSize() : 0,
+                      BufferWithExtendableBuffer::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE),
+              mExpandableContentBuffer(mContentBuffer.get() ? mContentBuffer.get()->getBuffer() : 0,
+                      mContentBuffer.get() ? mContentBuffer.get()->getBufferSize() : 0,
+                      BufferWithExtendableBuffer::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE),
+              mAddressLookupTable(&mExpandableLookupTableBuffer, &mExpandableAddressTableBuffer,
+                      sparseTableBlockSize, sparseTableDataSize) {}
+
+    virtual ~SparseTableDictContent() {}
+
+    virtual bool isValid() const {
+        return mLookupTableBuffer.get() != 0 && mAddressTableBuffer.get() != 0
+                && mContentBuffer.get() != 0;
+    }
+
+ protected:
+    const SparseTable *getAddressLookupTable() const {
+        return &mAddressLookupTable;
+    }
+
+    const BufferWithExtendableBuffer *getContentBuffer() const {
+        return &mExpandableContentBuffer;
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(SparseTableDictContent);
+
+    // TODO: Have sparse table.
+    const MmappedBuffer::MmappedBufferPtr mLookupTableBuffer;
+    const MmappedBuffer::MmappedBufferPtr mAddressTableBuffer;
+    const MmappedBuffer::MmappedBufferPtr mContentBuffer;
+    BufferWithExtendableBuffer mExpandableLookupTableBuffer;
+    BufferWithExtendableBuffer mExpandableAddressTableBuffer;
+    BufferWithExtendableBuffer mExpandableContentBuffer;
+    SparseTable mAddressLookupTable;
+};
+} // namespace latinime
+#endif /* LATINIME_SPARSE_TABLE_DICT_CONTENT_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table.h
new file mode 100644
index 0000000..173d0da
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_TERMINAL_POSITION_LOOKUP_TABLE_H
+#define LATINIME_TERMINAL_POSITION_LOOKUP_TABLE_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/structure/v4/content/single_dict_content.h"
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h"
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+
+namespace latinime {
+
+class TerminalPositionLookupTable : public SingleDictContent {
+ public:
+    // TODO: Quit using headerRegionSize.
+    TerminalPositionLookupTable(const char *const dictDirPath, const bool isUpdatable,
+            const int headerRegionSize)
+            : SingleDictContent(dictDirPath,
+                      Ver4DictConstants::TERMINAL_ADDRESS_TABLE_FILE_EXTENSION, isUpdatable),
+              mSize(getBuffer()->getTailPosition()
+                      / Ver4DictConstants::TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE),
+              mHeaderRegionSize(headerRegionSize) {}
+
+    int getTerminalPtNodePosition(const int terminalId) const {
+        if (terminalId < 0 || terminalId >= mSize) {
+            return NOT_A_DICT_POS;
+        }
+        const int readingPos = terminalId * Ver4DictConstants::TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE;
+        return getBuffer()->readUint(Ver4DictConstants::TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE,
+                readingPos) - mHeaderRegionSize;
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(TerminalPositionLookupTable);
+
+    const int mSize;
+    const int mHeaderRegionSize;
+};
+} // namespace latinime
+#endif // LATINIME_TERMINAL_POSITION_LOOKUP_TABLE_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h
new file mode 100644
index 0000000..6ee6e63
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_VER4_DICT_BUFFER_H
+#define LATINIME_VER4_DICT_BUFFER_H
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/header/header_read_write_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v4/content/bigram_dict_content.h"
+#include "suggest/policyimpl/dictionary/structure/v4/content/probability_dict_content.h"
+#include "suggest/policyimpl/dictionary/structure/v4/content/shortcut_dict_content.h"
+#include "suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table.h"
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+#include "suggest/policyimpl/dictionary/utils/mmapped_buffer.h"
+
+namespace latinime {
+
+class Ver4DictBuffers {
+ public:
+    typedef ExclusiveOwnershipPointer<Ver4DictBuffers> Ver4DictBuffersPtr;
+
+    static Ver4DictBuffersPtr openVer4DictBuffers(const char *const dictDirPath,
+            const MmappedBuffer::MmappedBufferPtr &dictBuffer) {
+        const bool isUpdatable = dictBuffer.get() ? dictBuffer.get()->isUpdatable() : false;
+        return Ver4DictBuffersPtr(new Ver4DictBuffers(dictDirPath, dictBuffer, isUpdatable));
+    }
+
+    AK_FORCE_INLINE bool isValid() const {
+        return mDictBuffer.get() != 0 && mProbabilityDictContent.isValid()
+                && mTerminalPositionLookupTable.isValid() && mBigramDictContent.isValid()
+                && mShortcutDictContent.isValid();
+    }
+
+    AK_FORCE_INLINE uint8_t *getRawDictBuffer() const {
+        return mDictBuffer.get()->getBuffer();
+    }
+
+    AK_FORCE_INLINE int getRawDictBufferSize() const {
+        return mDictBuffer.get()->getBufferSize();
+    }
+
+    AK_FORCE_INLINE const TerminalPositionLookupTable *getTerminalPositionLookupTable() const {
+        return &mTerminalPositionLookupTable;
+    }
+
+    AK_FORCE_INLINE const ProbabilityDictContent *getProbabilityDictContent() const {
+        return &mProbabilityDictContent;
+    }
+
+    AK_FORCE_INLINE const BigramDictContent *getBigramDictContent() const {
+        return &mBigramDictContent;
+    }
+
+    AK_FORCE_INLINE const ShortcutDictContent *getShortcutDictContent() const {
+        return &mShortcutDictContent;
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(Ver4DictBuffers);
+
+    AK_FORCE_INLINE Ver4DictBuffers(const char *const dictDirPath,
+            const MmappedBuffer::MmappedBufferPtr &dictBuffer, const bool isUpdatable)
+            : mDictBuffer(dictBuffer),
+              // TODO: Quit using getHeaderSize.
+              mTerminalPositionLookupTable(dictDirPath, isUpdatable,
+                      HeaderReadWriteUtils::getHeaderSize(mDictBuffer.get()->getBuffer())),
+              mProbabilityDictContent(dictDirPath, isUpdatable),
+              mBigramDictContent(dictDirPath, isUpdatable),
+              mShortcutDictContent(dictDirPath, isUpdatable) {}
+
+    const MmappedBuffer::MmappedBufferPtr mDictBuffer;
+    TerminalPositionLookupTable mTerminalPositionLookupTable;
+    ProbabilityDictContent mProbabilityDictContent;
+    BigramDictContent mBigramDictContent;
+    ShortcutDictContent mShortcutDictContent;
+};
+} // namespace latinime
+#endif /* LATINIME_VER4_DICT_BUFFER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.cpp
new file mode 100644
index 0000000..20adb92
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h"
+
+namespace latinime {
+
+const char *const Ver4DictConstants::TRIE_FILE_EXTENSION = ".trie";
+const char *const Ver4DictConstants::FREQ_FILE_EXTENSION = ".freq";
+// tat = Terminal Address Table
+const char *const Ver4DictConstants::TERMINAL_ADDRESS_TABLE_FILE_EXTENSION = ".tat";
+const char *const Ver4DictConstants::BIGRAM_FILE_EXTENSION = ".bigram_freq";
+const char *const Ver4DictConstants::BIGRAM_LOOKUP_TABLE_FILE_EXTENSION = ".bigram_lookup";
+const char *const Ver4DictConstants::BIGRAM_CONTENT_TABLE_FILE_EXTENSION = ".bigram_index_freq";
+const char *const Ver4DictConstants::SHORTCUT_FILE_EXTENSION = ".shortcut_shortcut";
+const char *const Ver4DictConstants::SHORTCUT_LOOKUP_TABLE_FILE_EXTENSION = ".shortcut_lookup";
+const char *const Ver4DictConstants::SHORTCUT_CONTENT_TABLE_FILE_EXTENSION =
+        ".shortcut_index_shortcut";
+
+const int Ver4DictConstants::NOT_A_TERMINAL_ID = -1;
+const int Ver4DictConstants::PROBABILITY_SIZE = 1;
+const int Ver4DictConstants::FLAGS_IN_PROBABILITY_FILE_SIZE = 1;
+const int Ver4DictConstants::TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE = 3;
+
+const int Ver4DictConstants::BIGRAM_ADDRESS_TABLE_BLOCK_SIZE = 4;
+const int Ver4DictConstants::BIGRAM_ADDRESS_TABLE_DATA_SIZE = 4;
+const int Ver4DictConstants::SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE = 16;
+const int Ver4DictConstants::SHORTCUT_ADDRESS_TABLE_DATA_SIZE = 4;
+
+const int Ver4DictConstants::BIGRAM_TARGET_TERMINAL_ID_FIELD_SIZE = 3;
+const int Ver4DictConstants::BIGRAM_FLAGS_FIELD_SIZE = 1;
+
+const int Ver4DictConstants::SHORTCUT_FLAGS_FIELD_SIZE = 1;
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h
new file mode 100644
index 0000000..5225818
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_VER4_DICT_CONSTANTS_H
+#define LATINIME_VER4_DICT_CONSTANTS_H
+
+#include "defines.h"
+
+namespace latinime {
+
+// Note that there are corresponding definitions in FormatSpec.java.
+class Ver4DictConstants {
+ public:
+    static const char *const TRIE_FILE_EXTENSION;
+    static const char *const FREQ_FILE_EXTENSION;
+    static const char *const TERMINAL_ADDRESS_TABLE_FILE_EXTENSION;
+    static const char *const BIGRAM_FILE_EXTENSION;
+    static const char *const BIGRAM_LOOKUP_TABLE_FILE_EXTENSION;
+    static const char *const BIGRAM_CONTENT_TABLE_FILE_EXTENSION;
+    static const char *const SHORTCUT_FILE_EXTENSION;
+    static const char *const SHORTCUT_LOOKUP_TABLE_FILE_EXTENSION;
+    static const char *const SHORTCUT_CONTENT_TABLE_FILE_EXTENSION;
+
+    static const int NOT_A_TERMINAL_ID;
+    static const int PROBABILITY_SIZE;
+    static const int FLAGS_IN_PROBABILITY_FILE_SIZE;
+    static const int TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE;
+
+    static const int BIGRAM_ADDRESS_TABLE_BLOCK_SIZE;
+    static const int BIGRAM_ADDRESS_TABLE_DATA_SIZE;
+    static const int SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE;
+    static const int SHORTCUT_ADDRESS_TABLE_DATA_SIZE;
+
+    static const int BIGRAM_FLAGS_FIELD_SIZE;
+    static const int BIGRAM_TARGET_TERMINAL_ID_FIELD_SIZE;
+
+    static const int SHORTCUT_FLAGS_FIELD_SIZE;
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(Ver4DictConstants);
+};
+} // namespace latinime
+#endif /* LATINIME_VER4_DICT_CONSTANTS_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.cpp
new file mode 100644
index 0000000..b18bdc9
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.h"
+
+#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v4/content/probability_dict_content.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+
+namespace latinime {
+
+const PtNodeParams Ver4PatriciaTrieNodeReader::fetchPtNodeInfoFromBufferAndProcessMovedPtNode(
+        const int ptNodePos, const int siblingNodePos, const int bigramLinkedNodePos) const {
+    if (ptNodePos < 0 || ptNodePos >= mBuffer->getTailPosition()) {
+        // Reading invalid position because of bug or broken dictionary.
+        AKLOGE("Fetching PtNode info from invalid dictionary position: %d, dictionary size: %d",
+                ptNodePos, mBuffer->getTailPosition());
+        ASSERT(false);
+        return PtNodeParams();
+    }
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(ptNodePos);
+    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
+    int pos = ptNodePos;
+    const int headPos = ptNodePos;
+    if (usesAdditionalBuffer) {
+        pos -= mBuffer->getOriginalBufferSize();
+    }
+    const PatriciaTrieReadingUtils::NodeFlags flags =
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictBuf, &pos);
+    const int parentPosOffset =
+            DynamicPatriciaTrieReadingUtils::getParentPtNodePosOffsetAndAdvancePosition(
+                    dictBuf, &pos);
+    const int parentPos =
+            DynamicPatriciaTrieReadingUtils::getParentPtNodePos(parentPosOffset, headPos);
+    int codePoints[MAX_WORD_LENGTH];
+    const int codePonitCount = PatriciaTrieReadingUtils::getCharsAndAdvancePosition(
+            dictBuf, flags, MAX_WORD_LENGTH, codePoints, &pos);
+    int terminalIdFieldPos = NOT_A_DICT_POS;
+    int terminalId = Ver4DictConstants::NOT_A_TERMINAL_ID;
+    int probability = NOT_A_PROBABILITY;
+    if (PatriciaTrieReadingUtils::isTerminal(flags)) {
+        terminalIdFieldPos = pos;
+        if (usesAdditionalBuffer) {
+            terminalIdFieldPos += mBuffer->getOriginalBufferSize();
+        }
+        terminalId = Ver4PatriciaTrieReadingUtils::getTerminalIdAndAdvancePosition(dictBuf, &pos);
+        probability = mProbabilityDictContent->getProbability(terminalId);
+    }
+    int childrenPosFieldPos = pos;
+    if (usesAdditionalBuffer) {
+        childrenPosFieldPos += mBuffer->getOriginalBufferSize();
+    }
+    int childrenPos = DynamicPatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(
+            dictBuf, &pos);
+    if (usesAdditionalBuffer && childrenPos != NOT_A_DICT_POS) {
+        childrenPos += mBuffer->getOriginalBufferSize();
+    }
+    int newBigramLinkedNodePos = bigramLinkedNodePos;
+    if (siblingNodePos == NOT_A_DICT_POS) {
+        if (DynamicPatriciaTrieReadingUtils::isMoved(flags)) {
+            newBigramLinkedNodePos = childrenPos;
+        }
+    }
+    if (usesAdditionalBuffer) {
+        pos += mBuffer->getOriginalBufferSize();
+    }
+    // Sibling position is the tail position of original PtNode.
+    int newSiblingNodePos = (siblingNodePos == NOT_A_DICT_POS) ? pos : siblingNodePos;
+    // Read destination node if the read node is a moved node.
+    if (DynamicPatriciaTrieReadingUtils::isMoved(flags)) {
+        // The destination position is stored at the same place as the parent position.
+        return fetchPtNodeInfoFromBufferAndProcessMovedPtNode(parentPos, newSiblingNodePos,
+                newBigramLinkedNodePos);
+    } else {
+        return PtNodeParams(headPos, flags, parentPos, codePonitCount, codePoints,
+                terminalIdFieldPos, terminalId, probability, childrenPosFieldPos, childrenPos,
+                newBigramLinkedNodePos, newSiblingNodePos);
+    }
+}
+
+}
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.h
new file mode 100644
index 0000000..bdae0de
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_VER4_PATRICIA_TRIE_NODE_READER_H
+#define LATINIME_VER4_PATRICIA_TRIE_NODE_READER_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_reader.h"
+
+namespace latinime {
+
+class BufferWithExtendableBuffer;
+class ProbabilityDictContent;
+
+/*
+ * This class is used for helping to read nodes of ver4 patricia trie. This class handles moved
+ * node and reads node attributes including probability form probabilityBuffer.
+ */
+class Ver4PatriciaTrieNodeReader : public PtNodeReader {
+ public:
+    Ver4PatriciaTrieNodeReader(const BufferWithExtendableBuffer *const buffer,
+            const ProbabilityDictContent *const probabilityDictContent)
+            : mBuffer(buffer), mProbabilityDictContent(probabilityDictContent) {}
+
+    ~Ver4PatriciaTrieNodeReader() {}
+
+    virtual const PtNodeParams fetchNodeInfoInBufferFromPtNodePos(const int ptNodePos) const {
+        return fetchPtNodeInfoFromBufferAndProcessMovedPtNode(ptNodePos,
+                NOT_A_DICT_POS /* siblingNodePos */, NOT_A_DICT_POS /* bigramLinkedNodePos */);
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(Ver4PatriciaTrieNodeReader);
+
+    const BufferWithExtendableBuffer *const mBuffer;
+    const ProbabilityDictContent *const mProbabilityDictContent;
+
+    const PtNodeParams fetchPtNodeInfoFromBufferAndProcessMovedPtNode(const int ptNodePos,
+            const int siblingNodePos, const int bigramLinkedNodePos) const;
+};
+} // namespace latinime
+#endif /* LATINIME_VER4_PATRICIA_TRIE_NODE_READER_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
new file mode 100644
index 0000000..ae5a094
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h"
+
+#include "suggest/core/dicnode/dic_node.h"
+#include "suggest/core/dicnode/dic_node_vector.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_reading_helper.h"
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.h"
+#include "suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h"
+#include "suggest/policyimpl/dictionary/utils/probability_utils.h"
+
+namespace latinime {
+
+void Ver4PatriciaTriePolicy::createAndGetAllChildDicNodes(const DicNode *const dicNode,
+        DicNodeVector *const childDicNodes) const {
+    if (!dicNode->hasChildren()) {
+        return;
+    }
+    DynamicPatriciaTrieReadingHelper readingHelper(&mDictBuffer, &mNodeReader);
+    readingHelper.initWithPtNodeArrayPos(dicNode->getChildrenPtNodeArrayPos());
+    while (!readingHelper.isEnd()) {
+        const PtNodeParams ptNodeParams = readingHelper.getPtNodeParams();
+        if (!ptNodeParams.isValid()) {
+            break;
+        }
+        bool isTerminal = ptNodeParams.isTerminal() && !ptNodeParams.isDeleted();
+        if (isTerminal && mHeaderPolicy.isDecayingDict()) {
+            // A DecayingDict may have a terminal PtNode that has a terminal DicNode whose
+            // probability is NOT_A_PROBABILITY. In such case, we don't want to treat it as a
+            // valid terminal DicNode.
+            isTerminal = getProbability(ptNodeParams.getProbability(), NOT_A_PROBABILITY)
+                    != NOT_A_PROBABILITY;
+        }
+        childDicNodes->pushLeavingChild(dicNode, ptNodeParams.getHeadPos(),
+                ptNodeParams.getChildrenPos(), ptNodeParams.getProbability(), isTerminal,
+                ptNodeParams.hasChildren(),
+                ptNodeParams.isBlacklisted()
+                        || ptNodeParams.isNotAWord() /* isBlacklistedOrNotAWord */,
+                ptNodeParams.getCodePointCount(), ptNodeParams.getCodePoints());
+        readingHelper.readNextSiblingNode(ptNodeParams);
+    }
+}
+
+int Ver4PatriciaTriePolicy::getCodePointsAndProbabilityAndReturnCodePointCount(
+        const int ptNodePos, const int maxCodePointCount, int *const outCodePoints,
+        int *const outUnigramProbability) const {
+    DynamicPatriciaTrieReadingHelper readingHelper(&mDictBuffer, &mNodeReader);
+    readingHelper.initWithPtNodePos(ptNodePos);
+    return readingHelper.getCodePointsAndProbabilityAndReturnCodePointCount(
+            maxCodePointCount, outCodePoints, outUnigramProbability);
+}
+
+int Ver4PatriciaTriePolicy::getTerminalPtNodePositionOfWord(const int *const inWord,
+        const int length, const bool forceLowerCaseSearch) const {
+    DynamicPatriciaTrieReadingHelper readingHelper(&mDictBuffer, &mNodeReader);
+    readingHelper.initWithPtNodeArrayPos(getRootPosition());
+    return readingHelper.getTerminalPtNodePositionOfWord(inWord, length, forceLowerCaseSearch);
+}
+
+int Ver4PatriciaTriePolicy::getProbability(const int unigramProbability,
+        const int bigramProbability) const {
+    if (mHeaderPolicy.isDecayingDict()) {
+        // Both probabilities are encoded. Decode them and get probability.
+        return ForgettingCurveUtils::getProbability(unigramProbability, bigramProbability);
+    } else {
+        if (unigramProbability == NOT_A_PROBABILITY) {
+            return NOT_A_PROBABILITY;
+        } else if (bigramProbability == NOT_A_PROBABILITY) {
+            return ProbabilityUtils::backoff(unigramProbability);
+        } else {
+            // bigramProbability is a bigram probability delta.
+            return ProbabilityUtils::computeProbabilityForBigram(unigramProbability,
+                    bigramProbability);
+        }
+    }
+}
+
+int Ver4PatriciaTriePolicy::getUnigramProbabilityOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
+        return NOT_A_PROBABILITY;
+    }
+    const PtNodeParams ptNodeParams(mNodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos));
+    if (ptNodeParams.isDeleted() || ptNodeParams.isBlacklisted() || ptNodeParams.isNotAWord()) {
+        return NOT_A_PROBABILITY;
+    }
+    return getProbability(ptNodeParams.getProbability(), NOT_A_PROBABILITY);
+}
+
+int Ver4PatriciaTriePolicy::getShortcutPositionOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
+        return NOT_A_DICT_POS;
+    }
+    const PtNodeParams ptNodeParams(mNodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos));
+    if (ptNodeParams.isDeleted()) {
+        return NOT_A_DICT_POS;
+    }
+    return mBuffers.get()->getShortcutDictContent()->getShortcutListHeadPos(
+            ptNodeParams.getTerminalId());
+}
+
+int Ver4PatriciaTriePolicy::getBigramsPositionOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
+        return NOT_A_DICT_POS;
+    }
+    const PtNodeParams ptNodeParams(mNodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos));
+    if (ptNodeParams.isDeleted()) {
+        return NOT_A_DICT_POS;
+    }
+    return mBuffers.get()->getBigramDictContent()->getBigramListHeadPos(
+            ptNodeParams.getTerminalId());
+}
+
+bool Ver4PatriciaTriePolicy::addUnigramWord(const int *const word, const int length,
+        const int probability) {
+    // TODO: Implement.
+    return false;
+}
+
+bool Ver4PatriciaTriePolicy::addBigramWords(const int *const word0, const int length0,
+        const int *const word1, const int length1, const int probability) {
+    // TODO: Implement.
+    return false;
+}
+
+bool Ver4PatriciaTriePolicy::removeBigramWords(const int *const word0, const int length0,
+        const int *const word1, const int length1) {
+    // TODO: Implement.
+    return false;
+}
+
+void Ver4PatriciaTriePolicy::flush(const char *const filePath) {
+    // TODO: Implement.
+}
+
+void Ver4PatriciaTriePolicy::flushWithGC(const char *const filePath) {
+    // TODO: Implement.
+}
+
+bool Ver4PatriciaTriePolicy::needsToRunGC(const bool mindsBlockByGC) const {
+    // TODO: Implement.
+    return false;
+}
+
+void Ver4PatriciaTriePolicy::getProperty(const char *const query, char *const outResult,
+        const int maxResultLength) {
+    // TODO: Implement.
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h
new file mode 100644
index 0000000..10b9125
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_VER4_PATRICIA_TRIE_POLICY_H
+#define LATINIME_VER4_PATRICIA_TRIE_POLICY_H
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
+#include "suggest/policyimpl/dictionary/bigram/ver4_bigram_list_policy.h"
+#include "suggest/policyimpl/dictionary/header/header_policy.h"
+#include "suggest/policyimpl/dictionary/shortcut/ver4_shortcut_list_policy.h"
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h"
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+
+namespace latinime {
+
+class DicNode;
+class DicNodeVector;
+
+// TODO: Implement.
+class Ver4PatriciaTriePolicy : public DictionaryStructureWithBufferPolicy {
+ public:
+    Ver4PatriciaTriePolicy(const Ver4DictBuffers::Ver4DictBuffersPtr &buffers)
+            : mBuffers(buffers),
+              mHeaderPolicy(mBuffers.get()->getRawDictBuffer(), FormatUtils::VERSION_4),
+              mDictBuffer(mBuffers.get()->getRawDictBuffer() + mHeaderPolicy.getSize(),
+                      mBuffers.get()->getRawDictBufferSize() - mHeaderPolicy.getSize(),
+                      BufferWithExtendableBuffer::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE),
+              mBigramPolicy(mBuffers.get()->getBigramDictContent(),
+                      mBuffers.get()->getTerminalPositionLookupTable()),
+              mShortcutPolicy(mBuffers.get()->getShortcutDictContent(),
+                      mBuffers.get()->getTerminalPositionLookupTable()),
+              mNodeReader(&mDictBuffer, mBuffers.get()->getProbabilityDictContent()) {};
+
+    AK_FORCE_INLINE int getRootPosition() const {
+        return 0;
+    }
+
+    void createAndGetAllChildDicNodes(const DicNode *const dicNode,
+            DicNodeVector *const childDicNodes) const;
+
+    int getCodePointsAndProbabilityAndReturnCodePointCount(
+            const int terminalPtNodePos, const int maxCodePointCount, int *const outCodePoints,
+            int *const outUnigramProbability) const;
+
+    int getTerminalPtNodePositionOfWord(const int *const inWord,
+            const int length, const bool forceLowerCaseSearch) const;
+
+    int getProbability(const int unigramProbability, const int bigramProbability) const;
+
+    int getUnigramProbabilityOfPtNode(const int ptNodePos) const;
+
+    int getShortcutPositionOfPtNode(const int ptNodePos) const;
+
+    int getBigramsPositionOfPtNode(const int ptNodePos) const;
+
+    const DictionaryHeaderStructurePolicy *getHeaderStructurePolicy() const {
+        return &mHeaderPolicy;
+    }
+
+    const DictionaryBigramsStructurePolicy *getBigramsStructurePolicy() const {
+        return &mBigramPolicy;
+    }
+
+    const DictionaryShortcutsStructurePolicy *getShortcutsStructurePolicy() const {
+        return &mShortcutPolicy;
+    }
+
+    bool addUnigramWord(const int *const word, const int length, const int probability);
+
+    bool addBigramWords(const int *const word0, const int length0, const int *const word1,
+            const int length1, const int probability);
+
+    bool removeBigramWords(const int *const word0, const int length0, const int *const word1,
+            const int length1);
+
+    void flush(const char *const filePath);
+
+    void flushWithGC(const char *const filePath);
+
+    bool needsToRunGC(const bool mindsBlockByGC) const;
+
+    void getProperty(const char *const query, char *const outResult,
+            const int maxResultLength);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(Ver4PatriciaTriePolicy);
+
+    const Ver4DictBuffers::Ver4DictBuffersPtr mBuffers;
+    const HeaderPolicy mHeaderPolicy;
+    BufferWithExtendableBuffer mDictBuffer;
+    const Ver4BigramListPolicy mBigramPolicy;
+    const Ver4ShortcutListPolicy mShortcutPolicy;
+    Ver4PatriciaTrieNodeReader mNodeReader;
+};
+} // namespace latinime
+#endif // LATINIME_VER4_PATRICIA_TRIE_POLICY_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_reading_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_reading_utils.cpp
new file mode 100644
index 0000000..321fd6d
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_reading_utils.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_reading_utils.h"
+
+#include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+#include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
+
+namespace latinime {
+
+/* static */ int Ver4PatriciaTrieReadingUtils::getTerminalIdAndAdvancePosition(
+        const uint8_t *const buffer, int *pos) {
+    return ByteArrayUtils::readUint32AndAdvancePosition(buffer, pos);
+}
+
+/* static */ int Ver4PatriciaTrieReadingUtils::getProbability(
+        const BufferWithExtendableBuffer *const probabilityBuffer, const int terminalId) {
+    const int pos = terminalId * (Ver4DictConstants::FLAGS_IN_PROBABILITY_FILE_SIZE
+            + Ver4DictConstants::PROBABILITY_SIZE)
+                    + Ver4DictConstants::FLAGS_IN_PROBABILITY_FILE_SIZE;
+    return probabilityBuffer->readUint(Ver4DictConstants::PROBABILITY_SIZE, pos);
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_reading_utils.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_reading_utils.h
new file mode 100644
index 0000000..19e6dd3
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_reading_utils.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_VER4_PATRICIA_TRIE_READING_UTILS_H
+#define LATINIME_VER4_PATRICIA_TRIE_READING_UTILS_H
+
+#include <stdint.h>
+
+#include "defines.h"
+
+namespace latinime {
+
+class BufferWithExtendableBuffer;
+
+class Ver4PatriciaTrieReadingUtils {
+ public:
+    static int getTerminalIdAndAdvancePosition(const uint8_t *const buffer,
+            int *const pos);
+
+    static int getProbability(const BufferWithExtendableBuffer *const probabilityBuffer,
+            const int terminalId);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(Ver4PatriciaTrieReadingUtils);
+};
+} // namespace latinime
+#endif /* LATINIME_VER4_PATRICIA_TRIE_READING_UTILS_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.cpp
index f692882..f17a0d1 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.cpp
@@ -18,11 +18,37 @@
 
 namespace latinime {
 
-const size_t BufferWithExtendableBuffer::MAX_ADDITIONAL_BUFFER_SIZE = 1024 * 1024;
+const size_t BufferWithExtendableBuffer::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE = 1024 * 1024;
 const int BufferWithExtendableBuffer::NEAR_BUFFER_LIMIT_THRESHOLD_PERCENTILE = 90;
 // TODO: Needs to allocate larger memory corresponding to the current vector size.
 const size_t BufferWithExtendableBuffer::EXTEND_ADDITIONAL_BUFFER_SIZE_STEP = 128 * 1024;
 
+uint32_t BufferWithExtendableBuffer::readUint(const int size, const int pos) const {
+    const bool readingPosIsInAdditionalBuffer = isInAdditionalBuffer(pos);
+    const int posInBuffer = readingPosIsInAdditionalBuffer ? pos - mOriginalBufferSize : pos;
+    return ByteArrayUtils::readUint(getBuffer(readingPosIsInAdditionalBuffer), size, posInBuffer);
+}
+
+uint32_t BufferWithExtendableBuffer::readUintAndAdvancePosition(const int size,
+        int *const pos) const {
+    const int value = readUint(size, *pos);
+    *pos += size;
+    return value;
+}
+
+void BufferWithExtendableBuffer::readCodePointsAndAdvancePosition(const int maxCodePointCount,
+        int *const outCodePoints, int *outCodePointCount, int *const pos) const {
+    const bool readingPosIsInAdditionalBuffer = isInAdditionalBuffer(*pos);
+    if (readingPosIsInAdditionalBuffer) {
+        *pos -= mOriginalBufferSize;
+    }
+    *outCodePointCount = ByteArrayUtils::readStringAndAdvancePosition(
+            getBuffer(readingPosIsInAdditionalBuffer), maxCodePointCount, outCodePointCount, pos);
+    if (readingPosIsInAdditionalBuffer) {
+        *pos += mOriginalBufferSize;
+    }
+}
+
 bool BufferWithExtendableBuffer::writeUintAndAdvancePosition(const uint32_t data, const int size,
         int *const pos) {
     if (!(size >= 1 && size <= 4)) {
@@ -46,7 +72,7 @@
 }
 
 bool BufferWithExtendableBuffer::writeCodePointsAndAdvancePosition(const int *const codePoints,
-        const int codePointCount, const bool writesTerminator ,int *const pos) {
+        const int codePointCount, const bool writesTerminator, int *const pos) {
     const size_t size = ByteArrayUtils::calculateRequiredByteCountToStoreCodePoints(
             codePoints, codePointCount, writesTerminator);
     if (!checkAndPrepareWriting(*pos, size)) {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h b/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h
index 9dc3482..13dce9b 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h
@@ -32,12 +32,20 @@
 // raw pointer but provides several methods that handle boundary checking for writing data.
 class BufferWithExtendableBuffer {
  public:
+    static const size_t DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE;
+
     BufferWithExtendableBuffer(uint8_t *const originalBuffer, const int originalBufferSize,
-            const int maxAdditionalBufferSize = MAX_ADDITIONAL_BUFFER_SIZE)
+            const int maxAdditionalBufferSize)
             : mOriginalBuffer(originalBuffer), mOriginalBufferSize(originalBufferSize),
               mAdditionalBuffer(EXTEND_ADDITIONAL_BUFFER_SIZE_STEP), mUsedAdditionalBufferSize(0),
               mMaxAdditionalBufferSize(maxAdditionalBufferSize) {}
 
+    // Without original buffer.
+    BufferWithExtendableBuffer(const int maxAdditionalBufferSize)
+            : mOriginalBuffer(0), mOriginalBufferSize(0),
+              mAdditionalBuffer(EXTEND_ADDITIONAL_BUFFER_SIZE_STEP), mUsedAdditionalBufferSize(0),
+              mMaxAdditionalBufferSize(maxAdditionalBufferSize) {}
+
     AK_FORCE_INLINE int getTailPosition() const {
         return mOriginalBufferSize + mUsedAdditionalBufferSize;
     }
@@ -63,6 +71,13 @@
         }
     }
 
+    uint32_t readUint(const int size, const int pos) const;
+
+    uint32_t readUintAndAdvancePosition(const int size, int *const pos) const;
+
+    void readCodePointsAndAdvancePosition(const int maxCodePointCount,
+            int *const outCodePoints, int *outCodePointCount, int *const pos) const;
+
     AK_FORCE_INLINE int getOriginalBufferSize() const {
         return mOriginalBufferSize;
     }
@@ -86,7 +101,6 @@
  private:
     DISALLOW_COPY_AND_ASSIGN(BufferWithExtendableBuffer);
 
-    static const size_t MAX_ADDITIONAL_BUFFER_SIZE;
     static const int NEAR_BUFFER_LIMIT_THRESHOLD_PERCENTILE;
     static const size_t EXTEND_ADDITIONAL_BUFFER_SIZE_STEP;
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/byte_array_utils.h b/native/jni/src/suggest/policyimpl/dictionary/utils/byte_array_utils.h
index 0c15768..ebdd523 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/byte_array_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/byte_array_utils.h
@@ -114,6 +114,24 @@
         return buffer[(*pos)++];
     }
 
+    static AK_FORCE_INLINE int readUint(const uint8_t *const buffer,
+            const int size, const int pos) {
+        // size must be in 1 to 4.
+        ASSERT(size >= 1 && size <= 4);
+        switch (size) {
+            case 1:
+                return ByteArrayUtils::readUint8(buffer, pos);
+            case 2:
+                return ByteArrayUtils::readUint16(buffer, pos);
+            case 3:
+                return ByteArrayUtils::readUint24(buffer, pos);
+            case 4:
+                return ByteArrayUtils::readUint32(buffer, pos);
+            default:
+                return 0;
+        }
+    }
+
     /**
      * Code Point Reading
      *
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp
index 994826f..b48e5b0 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp
@@ -20,7 +20,7 @@
 #include <cstring>
 
 #include "suggest/policyimpl/dictionary/header/header_policy.h"
-#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h"
+#include "suggest/policyimpl/dictionary/structure/v3/dynamic_patricia_trie_writing_utils.h"
 #include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
 #include "suggest/policyimpl/dictionary/utils/format_utils.h"
 
@@ -33,6 +33,9 @@
     switch (dictVersion) {
         case 3:
             return createEmptyV3DictFile(filePath, attributeMap);
+        case 4:
+            // TODO: Support version 4 dictionary format.
+            return false;
         default:
             // Only version 3 dictionary is supported for now.
             return false;
@@ -41,12 +44,14 @@
 
 /* static */ bool DictFileWritingUtils::createEmptyV3DictFile(const char *const filePath,
         const HeaderReadWriteUtils::AttributeMap *const attributeMap) {
-    BufferWithExtendableBuffer headerBuffer(0 /* originalBuffer */, 0 /* originalBufferSize */);
+    BufferWithExtendableBuffer headerBuffer(
+            BufferWithExtendableBuffer::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE);
     HeaderPolicy headerPolicy(FormatUtils::VERSION_3, attributeMap);
     headerPolicy.writeHeaderToBuffer(&headerBuffer, true /* updatesLastUpdatedTime */,
             true /* updatesLastDecayedTime */, 0 /* unigramCount */, 0 /* bigramCount */,
             0 /* extendedRegionSize */);
-    BufferWithExtendableBuffer bodyBuffer(0 /* originalBuffer */, 0 /* originalBufferSize */);
+    BufferWithExtendableBuffer bodyBuffer(
+            BufferWithExtendableBuffer::DEFAULT_MAX_ADDITIONAL_BUFFER_SIZE);
     if (!DynamicPatriciaTrieWritingUtils::writeEmptyDictionary(&bodyBuffer, 0 /* rootPos */)) {
         return false;
     }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/file_utils.h b/native/jni/src/suggest/policyimpl/dictionary/utils/file_utils.h
new file mode 100644
index 0000000..59b894f
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/file_utils.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_FILE_UTILS_H
+#define LATINIME_FILE_UTILS_H
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "defines.h"
+
+namespace latinime {
+
+class FileUtils {
+ public:
+    // Returns -1 on error.
+    static int getFileSize(const char *const filePath) {
+        const int fd = open(filePath, O_RDONLY);
+        if (fd == -1) {
+            return -1;
+        }
+        struct stat statBuf;
+        if (fstat(fd, &statBuf) != 0) {
+            close(fd);
+            return -1;
+        }
+        close(fd);
+        return static_cast<int>(statBuf.st_size);
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(FileUtils);
+};
+} // namespace latinime
+#endif /* LATINIME_FILE_UTILS_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp
index 1d77d5c..4843650 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.cpp
@@ -45,6 +45,8 @@
                 return VERSION_2;
             } else if (ByteArrayUtils::readUint16(dict, 4) == 3) {
                 return VERSION_3;
+            } else if (ByteArrayUtils::readUint16(dict, 4) == 4) {
+                return VERSION_4;
             } else {
                 return UNKNOWN_VERSION;
             }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h
index 79ed0de..b90393a 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/format_utils.h
@@ -31,6 +31,7 @@
     enum FORMAT_VERSION {
         VERSION_2,
         VERSION_3,
+        VERSION_4,
         UNKNOWN_VERSION
     };
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.cpp
new file mode 100644
index 0000000..28af973
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/utils/mmapped_buffer.h"
+
+#include <cerrno>
+#include <climits>
+#include <cstdio>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "suggest/policyimpl/dictionary/utils/file_utils.h"
+
+namespace latinime {
+
+/* static */ MmappedBuffer::MmappedBufferPtr MmappedBuffer::openBuffer(
+        const char *const path, const int bufferOffset, const int bufferSize,
+        const bool isUpdatable) {
+    const int mmapFd = open(path, O_RDONLY);
+    if (mmapFd < 0) {
+        AKLOGE("DICT: Can't open the source. path=%s errno=%d", path, errno);
+        return MmappedBufferPtr(0);
+    }
+    const int pagesize = getpagesize();
+    const int offset = bufferOffset % pagesize;
+    int alignedOffset = bufferOffset - offset;
+    int alignedSize = bufferSize + offset;
+    const int protMode = isUpdatable ? PROT_READ | PROT_WRITE : PROT_READ;
+    void *const mmappedBuffer = mmap(0, alignedSize, protMode, MAP_PRIVATE, mmapFd,
+            alignedOffset);
+    if (mmappedBuffer == MAP_FAILED) {
+        AKLOGE("DICT: Can't mmap dictionary. errno=%d", errno);
+        close(mmapFd);
+        return MmappedBufferPtr(0);
+    }
+    uint8_t *const buffer = static_cast<uint8_t *>(mmappedBuffer) + offset;
+    if (!buffer) {
+        AKLOGE("DICT: buffer is null");
+        close(mmapFd);
+        return MmappedBufferPtr(0);
+    }
+    return MmappedBufferPtr(new MmappedBuffer(buffer, bufferSize, mmappedBuffer, alignedSize,
+            mmapFd, isUpdatable));
+}
+
+/* static */ MmappedBuffer::MmappedBufferPtr MmappedBuffer::openBuffer(
+        const char *const path, const bool isUpdatable) {
+    const int fileSize = FileUtils::getFileSize(path);
+    if (fileSize == -1) {
+        return MmappedBufferPtr(0);
+    } else if (fileSize == 0) {
+        return MmappedBufferPtr(new MmappedBuffer(isUpdatable));
+    } else {
+        return openBuffer(path, 0 /* bufferOffset */, fileSize, isUpdatable);
+    }
+}
+
+/* static */ MmappedBuffer::MmappedBufferPtr MmappedBuffer::openBuffer(
+        const char *const dirPath, const char *const fileName, const bool isUpdatable) {
+    const int filePathBufferSize = PATH_MAX + 1 /* terminator */;
+    char filePath[filePathBufferSize];
+    const int filePathLength = snprintf(filePath, filePathBufferSize, "%s%s", dirPath,
+            fileName);
+    if (filePathLength >= filePathBufferSize) {
+        return 0;
+    }
+    return openBuffer(filePath, isUpdatable);
+}
+
+MmappedBuffer::~MmappedBuffer() {
+    if (mAlignedSize == 0) {
+        return;
+    }
+    int ret = munmap(mMmappedBuffer, mAlignedSize);
+    if (ret != 0) {
+        AKLOGE("DICT: Failure in munmap. ret=%d errno=%d", ret, errno);
+    }
+    ret = close(mMmapFd);
+    if (ret != 0) {
+        AKLOGE("DICT: Failure in close. ret=%d errno=%d", ret, errno);
+    }
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.h b/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.h
index 6b69116..73a733b 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/mmapped_buffer.h
@@ -17,58 +17,27 @@
 #ifndef LATINIME_MMAPPED_BUFFER_H
 #define LATINIME_MMAPPED_BUFFER_H
 
-#include <cerrno>
-#include <fcntl.h>
 #include <stdint.h>
-#include <sys/mman.h>
-#include <unistd.h>
 
 #include "defines.h"
+#include "utils/exclusive_ownership_pointer.h"
 
 namespace latinime {
 
 class MmappedBuffer {
  public:
-    static MmappedBuffer* openBuffer(const char *const path, const int bufferOffset,
-            const int bufferSize, const bool isUpdatable) {
-        const int openMode = isUpdatable ? O_RDWR : O_RDONLY;
-        const int mmapFd = open(path, openMode);
-        if (mmapFd < 0) {
-            AKLOGE("DICT: Can't open the source. path=%s errno=%d", path, errno);
-            return 0;
-        }
-        const int pagesize = getpagesize();
-        const int offset = bufferOffset % pagesize;
-        int alignedOffset = bufferOffset - offset;
-        int alignedSize = bufferSize + offset;
-        const int protMode = isUpdatable ? PROT_READ | PROT_WRITE : PROT_READ;
-        void *const mmappedBuffer = mmap(0, alignedSize, protMode, MAP_PRIVATE, mmapFd,
-                alignedOffset);
-        if (mmappedBuffer == MAP_FAILED) {
-            AKLOGE("DICT: Can't mmap dictionary. errno=%d", errno);
-            close(mmapFd);
-            return 0;
-        }
-        uint8_t *const buffer = static_cast<uint8_t *>(mmappedBuffer) + offset;
-        if (!buffer) {
-            AKLOGE("DICT: buffer is null");
-            close(mmapFd);
-            return 0;
-        }
-        return new MmappedBuffer(buffer, bufferSize, mmappedBuffer, alignedSize, mmapFd,
-                isUpdatable);
-    }
+    typedef ExclusiveOwnershipPointer<MmappedBuffer> MmappedBufferPtr;
 
-    ~MmappedBuffer() {
-        int ret = munmap(mMmappedBuffer, mAlignedSize);
-        if (ret != 0) {
-            AKLOGE("DICT: Failure in munmap. ret=%d errno=%d", ret, errno);
-        }
-        ret = close(mMmapFd);
-        if (ret != 0) {
-            AKLOGE("DICT: Failure in close. ret=%d errno=%d", ret, errno);
-        }
-    }
+    static MmappedBufferPtr openBuffer(const char *const path,
+            const int bufferOffset, const int bufferSize, const bool isUpdatable);
+
+    // Mmap entire file.
+    static MmappedBufferPtr openBuffer(const char *const path, const bool isUpdatable);
+
+    static MmappedBufferPtr openBuffer(const char *const dirPath, const char *const fileName,
+            const bool isUpdatable);
+
+    ~MmappedBuffer();
 
     AK_FORCE_INLINE uint8_t *getBuffer() const {
         return mBuffer;
@@ -89,6 +58,11 @@
             : mBuffer(buffer), mBufferSize(bufferSize), mMmappedBuffer(mmappedBuffer),
               mAlignedSize(alignedSize), mMmapFd(mmapFd), mIsUpdatable(isUpdatable) {}
 
+    // Empty file. We have to handle an empty file as a valid part of a dictionary.
+    AK_FORCE_INLINE MmappedBuffer(const bool isUpdatable)
+            : mBuffer(0), mBufferSize(0), mMmappedBuffer(0), mAlignedSize(0), mMmapFd(0),
+              mIsUpdatable(isUpdatable) {}
+
     DISALLOW_IMPLICIT_CONSTRUCTORS(MmappedBuffer);
 
     uint8_t *const mBuffer;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/sparse_table.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/sparse_table.cpp
new file mode 100644
index 0000000..2678b8c
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/sparse_table.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/utils/sparse_table.h"
+
+namespace latinime {
+
+const int SparseTable::NOT_EXIST = -1;
+
+bool SparseTable::contains(const int id) const {
+    const int readingPos = id / mBlockSize * mDataSize;
+    if (id < 0 || mIndexTableBuffer->getTailPosition() <= readingPos) {
+        return false;
+    }
+    const int index = mIndexTableBuffer->readUint(mDataSize, readingPos);
+    return index != NOT_EXIST;
+}
+
+uint32_t SparseTable::get(const int id) const {
+    const int indexTableIndex = id / mBlockSize;
+    int readingPos = indexTableIndex * mDataSize;
+    const int index = mIndexTableBuffer->readUint(mDataSize, readingPos);
+    const int offset = id % mBlockSize;
+    readingPos = (index * mDataSize + offset) * mBlockSize;
+    return mContentTableBuffer->readUint(mDataSize, readingPos);
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/sparse_table.h b/native/jni/src/suggest/policyimpl/dictionary/utils/sparse_table.h
new file mode 100644
index 0000000..d71756c
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/sparse_table.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_SPARSE_TABLE_H
+#define LATINIME_SPARSE_TABLE_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+
+namespace latinime {
+
+// Note that there is a corresponding implementation in SparseTable.java.
+// TODO: Support multiple content buffers.
+class SparseTable {
+ public:
+    SparseTable(BufferWithExtendableBuffer *const indexTableBuffer,
+            BufferWithExtendableBuffer *const contentTableBuffer, const int blockSize,
+            const int dataSize)
+            : mIndexTableBuffer(indexTableBuffer), mContentTableBuffer(contentTableBuffer),
+              mBlockSize(blockSize), mDataSize(dataSize) {}
+
+    bool contains(const int id) const;
+
+    uint32_t get(const int id) const;
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(SparseTable);
+
+    static const int NOT_EXIST;
+
+    BufferWithExtendableBuffer *const mIndexTableBuffer;
+    BufferWithExtendableBuffer *const mContentTableBuffer;
+    const int mBlockSize;
+    const int mDataSize;
+};
+} // namespace latinime
+#endif /* LATINIME_SPARSE_TABLE_H */
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_traversal.h b/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
index 007c19e..fd0ac9e 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
@@ -81,7 +81,7 @@
             return false;
         }
         const int point0Index = dicNode->getInputIndex(0);
-        return dicNode->isTerminalWordNode()
+        return dicNode->isTerminalDicNode()
                 && traverseSession->getProximityInfoState(0)->
                         hasSpaceProximity(point0Index);
     }
@@ -96,7 +96,7 @@
         if (dicNode->isCompletion(inputSize)) {
             return false;
         }
-        if (!dicNode->isTerminalWordNode()) {
+        if (!dicNode->isTerminalDicNode()) {
             return false;
         }
         const int16_t pointIndex = dicNode->getInputIndex(0);
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_weighting.cpp b/native/jni/src/suggest/policyimpl/typing/typing_weighting.cpp
index 5b6b5e8..54f65c7 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_weighting.cpp
+++ b/native/jni/src/suggest/policyimpl/typing/typing_weighting.cpp
@@ -23,39 +23,64 @@
 
 const TypingWeighting TypingWeighting::sInstance;
 
-ErrorType TypingWeighting::getErrorType(const CorrectionType correctionType,
+ErrorTypeUtils::ErrorType TypingWeighting::getErrorType(const CorrectionType correctionType,
         const DicTraverseSession *const traverseSession, const DicNode *const parentDicNode,
         const DicNode *const dicNode) const {
     switch (correctionType) {
         case CT_MATCH:
             if (isProximityDicNode(traverseSession, dicNode)) {
-                return ET_PROXIMITY_CORRECTION;
+                return ErrorTypeUtils::PROXIMITY_CORRECTION;
+            } else if (dicNode->isInDigraph()) {
+                return ErrorTypeUtils::MATCH_WITH_DIGRAPH;
             } else {
-                return ET_NOT_AN_ERROR;
+                // Compare the node code point with original primary code point on the keyboard.
+                const ProximityInfoState *const pInfoState =
+                        traverseSession->getProximityInfoState(0);
+                const int primaryOriginalCodePoint = pInfoState->getPrimaryOriginalCodePointAt(
+                        dicNode->getInputIndex(0));
+                const int nodeCodePoint = dicNode->getNodeCodePoint();
+                if (primaryOriginalCodePoint == nodeCodePoint) {
+                    // Node code point is same as original code point on the keyboard.
+                    return ErrorTypeUtils::NOT_AN_ERROR;
+                } else if (CharUtils::toLowerCase(primaryOriginalCodePoint) ==
+                        CharUtils::toLowerCase(nodeCodePoint)) {
+                    // Only cases of the code points are different.
+                    return ErrorTypeUtils::MATCH_WITH_CASE_ERROR;
+                } else if (CharUtils::toBaseCodePoint(primaryOriginalCodePoint) ==
+                        CharUtils::toBaseCodePoint(nodeCodePoint)) {
+                    // Node code point is a variant of original code point.
+                    return ErrorTypeUtils::MATCH_WITH_ACCENT_ERROR;
+                } else {
+                    // Node code point is a variant of original code point and the cases are also
+                    // different.
+                    return ErrorTypeUtils::MATCH_WITH_ACCENT_ERROR
+                            | ErrorTypeUtils::MATCH_WITH_CASE_ERROR;
+                }
             }
+            break;
         case CT_ADDITIONAL_PROXIMITY:
-            return ET_PROXIMITY_CORRECTION;
+            return  ErrorTypeUtils::PROXIMITY_CORRECTION;
         case CT_OMISSION:
             if (parentDicNode->canBeIntentionalOmission()) {
-                return ET_INTENTIONAL_OMISSION;
+                return ErrorTypeUtils::INTENTIONAL_OMISSION;
             } else {
-                return ET_EDIT_CORRECTION;
+                return ErrorTypeUtils::EDIT_CORRECTION;
             }
             break;
         case CT_SUBSTITUTION:
         case CT_INSERTION:
         case CT_TERMINAL_INSERTION:
         case CT_TRANSPOSITION:
-            return ET_EDIT_CORRECTION;
+            return ErrorTypeUtils::EDIT_CORRECTION;
         case CT_NEW_WORD_SPACE_OMISSION:
         case CT_NEW_WORD_SPACE_SUBSTITUTION:
-            return ET_NEW_WORD;
+            return ErrorTypeUtils::NEW_WORD;
         case CT_TERMINAL:
-            return ET_NOT_AN_ERROR;
+            return ErrorTypeUtils::NOT_AN_ERROR;
         case CT_COMPLETION:
-            return ET_COMPLETION;
+            return ErrorTypeUtils::COMPLETION;
         default:
-            return ET_NOT_AN_ERROR;
+            return ErrorTypeUtils::NOT_AN_ERROR;
     }
 }
 }  // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
index 9f0a331..41314ef 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
@@ -19,6 +19,7 @@
 
 #include "defines.h"
 #include "suggest/core/dicnode/dic_node_utils.h"
+#include "suggest/core/dictionary/error_type_utils.h"
 #include "suggest/core/layout/touch_position_correction_utils.h"
 #include "suggest/core/policy/weighting.h"
 #include "suggest/core/session/dic_traverse_session.h"
@@ -204,7 +205,7 @@
         return cost * traverseSession->getMultiWordCostMultiplier();
     }
 
-    ErrorType getErrorType(const CorrectionType correctionType,
+    ErrorTypeUtils::ErrorType getErrorType(const CorrectionType correctionType,
             const DicTraverseSession *const traverseSession,
             const DicNode *const parentDicNode, const DicNode *const dicNode) const;
 
diff --git a/native/jni/src/utils/exclusive_ownership_pointer.h b/native/jni/src/utils/exclusive_ownership_pointer.h
new file mode 100644
index 0000000..3cf7895
--- /dev/null
+++ b/native/jni/src/utils/exclusive_ownership_pointer.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_EXCLUSIVE_OWNERSHIP_POINTER_H
+#define LATINIME_EXCLUSIVE_OWNERSHIP_POINTER_H
+
+#include "defines.h"
+
+namespace latinime {
+
+template<class T>
+class ExclusiveOwnershipPointer {
+ public:
+    // This instance become an owner of the raw pointer.
+    ExclusiveOwnershipPointer(T *const rawPointer)
+            : mPointer(rawPointer),
+              mSharedOwnerPtr(new (ExclusiveOwnershipPointer<T> *)(this)) {}
+
+    // Move the ownership.
+    ExclusiveOwnershipPointer(const ExclusiveOwnershipPointer<T> &pointer)
+            : mPointer(pointer.mPointer), mSharedOwnerPtr(pointer.mSharedOwnerPtr) {
+        transferOwnership(&pointer);
+    }
+
+    ~ExclusiveOwnershipPointer() {
+        deletePointersIfHavingOwnership();
+    }
+
+    // Move the ownership.
+    ExclusiveOwnershipPointer<T> &operator=(const ExclusiveOwnershipPointer<T> &pointer) {
+        // Delete pointers when this is an owner of another pointer.
+        deletePointersIfHavingOwnership();
+        mPointer = pointer.mPointer;
+        mSharedOwnerPtr = pointer.mSharedOwnerPtr;
+        transferOwnership(pointer);
+        return *this;
+    }
+
+    T *get() const {
+        return mPointer;
+    }
+
+ private:
+    // This class allows to copy and assign and ensures only one instance has the ownership of the
+    // managed pointer.
+
+    ExclusiveOwnershipPointer() : mPointer(0), mSharedOwnerPtr(0) {}
+
+    void transferOwnership(const ExclusiveOwnershipPointer<T> *const src) {
+        if (*mSharedOwnerPtr != src) {
+           AKLOGE("Failed to transfer the ownership because src is not the current owner."
+                   "src: %p, owner: %p", src, *mSharedOwnerPtr);
+           ASSERT(false);
+           return;
+        }
+        // Transfer the ownership from src to this instance.
+        *mSharedOwnerPtr = this;
+    }
+
+    void deletePointersIfHavingOwnership() {
+        if (mSharedOwnerPtr && *mSharedOwnerPtr == this) {
+            if (mPointer) {
+                if (DEBUG_DICT) {
+                    AKLOGI("Releasing pointer: %p", mPointer);
+                }
+                delete mPointer;
+            }
+            delete mSharedOwnerPtr;
+        }
+    }
+
+    T *mPointer;
+    // mSharedOwnerPtr points a shared memory space where the instance which has the ownership is
+    // stored.
+    ExclusiveOwnershipPointer<T> **mSharedOwnerPtr;
+};
+} // namespace latinime
+#endif /* LATINIME_EXCLUSIVE_OWNERSHIP_POINTER_H */
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
index 6bc8b9d..8ad8689 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
@@ -183,7 +183,7 @@
         final String[] STRINGS_TO_TYPE =
                 new String[] { "this   ", "a+  ", "\u1F607  ", "..  ", ")  ", "(  ", "%  " };
         final String[] EXPECTED_RESULTS =
-                new String[] { "this.  ", "a+. ", "\u1F607. ", "..  ", "). ", "(  ", "%  " };
+                new String[] { "this.  ", "a+. ", "\u1F607. ", "..  ", "). ", "(  ", "%. " };
         for (int i = 0; i < STRINGS_TO_TYPE.length; ++i) {
             mEditText.setText("");
             type(STRINGS_TO_TYPE[i]);
diff --git a/tests/src/com/android/inputmethod/latin/Ver4BinaryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/Ver4BinaryDictionaryTests.java
new file mode 100644
index 0000000..e433352
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/Ver4BinaryDictionaryTests.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import com.android.inputmethod.latin.makedict.BinaryDictEncoderUtils;
+import com.android.inputmethod.latin.makedict.DictEncoder;
+import com.android.inputmethod.latin.makedict.FormatSpec;
+import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.makedict.Ver4DictEncoder;
+import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
+import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Locale;
+
+// TODO: Add a test to evaluate the speed of operations of Ver4 dictionary.
+@LargeTest
+public class Ver4BinaryDictionaryTests extends AndroidTestCase {
+    private static final String TAG = Ver4BinaryDictionaryTests.class.getSimpleName();
+    private static final String TEST_LOCALE = "test";
+    private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
+            new FormatSpec.FormatOptions(4, true /* supportsDynamicUpdate */);
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    // TODO: remove after native code support dictionary creation.
+    private DictionaryOptions getDictionaryOptions(final String id, final String version) {
+        final DictionaryOptions options = new DictionaryOptions(new HashMap<String, String>(),
+                false /* germanUmlautProcessing */, false /* frenchLigatureProcessing */);
+        options.mAttributes.put("version", version);
+        options.mAttributes.put("dictionary", id);
+        return options;
+    }
+
+    // TODO: remove after native code support dictionary creation.
+    private File getTrieFile(final String id, final String version) {
+        return new File(getContext().getCacheDir() + "/" + id + "." + version,
+                TEST_LOCALE + "." + version + FormatSpec.TRIE_FILE_EXTENSION);
+    }
+
+    public void testIsValidDictionary() {
+        final String dictVersion = Long.toString(System.currentTimeMillis());
+        final File trieFile = getTrieFile(TEST_LOCALE, dictVersion);
+
+        BinaryDictionary binaryDictionary = new BinaryDictionary(trieFile.getAbsolutePath(),
+                0 /* offset */, trieFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        assertFalse(binaryDictionary.isValidDictionary());
+
+        final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
+                getDictionaryOptions(TEST_LOCALE, dictVersion));
+        DictEncoder encoder = new Ver4DictEncoder(getContext().getCacheDir());
+        try {
+            encoder.writeDictionary(dict, FORMAT_OPTIONS);
+        } catch (IOException e) {
+            Log.e(TAG, "IOException while writing dictionary", e);
+        } catch (UnsupportedFormatException e) {
+            Log.e(TAG, "Unsupported format", e);
+        }
+
+        binaryDictionary = new BinaryDictionary(trieFile.getAbsolutePath(),
+                0 /* offset */, trieFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        assertTrue(binaryDictionary.isValidDictionary());
+    }
+
+    // TODO: Add large tests.
+    public void testReadProbability() {
+        final String dictVersion = Long.toString(System.currentTimeMillis());
+        final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
+                getDictionaryOptions(TEST_LOCALE, dictVersion));
+
+        final int frequency = 100;
+        dict.add("a", frequency, null, false /* isNotAWord */);
+        dict.add("aaa", frequency, null, false /* isNotAWord */);
+        dict.add("ab", frequency, null, false /* isNotAWord */);
+
+        DictEncoder encoder = new Ver4DictEncoder(getContext().getCacheDir());
+        try {
+            encoder.writeDictionary(dict, FORMAT_OPTIONS);
+        } catch (IOException e) {
+            Log.e(TAG, "IOException while writing dictionary", e);
+        } catch (UnsupportedFormatException e) {
+            Log.e(TAG, "Unsupported format", e);
+        }
+        File trieFile = getTrieFile(TEST_LOCALE, dictVersion);
+        BinaryDictionary binaryDictionary = new BinaryDictionary(trieFile.getAbsolutePath(),
+                0 /* offset */, trieFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        assertTrue(binaryDictionary.isValidDictionary());
+        assertEquals(frequency, binaryDictionary.getFrequency("a"));
+        assertEquals(frequency, binaryDictionary.getFrequency("aaa"));
+        assertEquals(frequency, binaryDictionary.getFrequency("ab"));
+    }
+
+    public static int getCalculatedBigramProbabiliy(BinaryDictionary binaryDictionary,
+            final int unigramFrequency, final int bigramFrequency) {
+        final int bigramFrequencyDiff = BinaryDictEncoderUtils.getBigramFrequencyDiff(
+                unigramFrequency, bigramFrequency);
+        return binaryDictionary.calculateProbability(unigramFrequency, bigramFrequencyDiff);
+    }
+
+    // TODO: Add large tests.
+    public void testReadBigrams() {
+        final String dictVersion = Long.toString(System.currentTimeMillis());
+        final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
+                getDictionaryOptions(TEST_LOCALE, dictVersion));
+
+        final int unigramFrequency = 1;
+        final int bigramFrequency0 = 150;
+        final int bigramFrequency1 = 1;
+        final int bigramFrequency2 = 255;
+        dict.add("a", unigramFrequency, null, false /* isNotAWord */);
+        dict.add("aaa", unigramFrequency, null, false /* isNotAWord */);
+        dict.add("ab", unigramFrequency, null, false /* isNotAWord */);
+        dict.setBigram("a", "aaa", bigramFrequency0);
+        dict.setBigram("a", "ab", bigramFrequency1);
+        dict.setBigram("aaa", "ab", bigramFrequency2);
+
+        DictEncoder encoder = new Ver4DictEncoder(getContext().getCacheDir());
+        try {
+            encoder.writeDictionary(dict, FORMAT_OPTIONS);
+        } catch (IOException e) {
+            Log.e(TAG, "IOException while writing dictionary", e);
+        } catch (UnsupportedFormatException e) {
+            Log.e(TAG, "Unsupported format", e);
+        }
+        File trieFile = getTrieFile(TEST_LOCALE, dictVersion);
+        BinaryDictionary binaryDictionary = new BinaryDictionary(trieFile.getAbsolutePath(),
+                0 /* offset */, trieFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        assertTrue(binaryDictionary.isValidDictionary());
+
+        assertEquals(getCalculatedBigramProbabiliy(binaryDictionary, unigramFrequency,
+                bigramFrequency0), binaryDictionary.getBigramProbability("a", "aaa"));
+        assertEquals(getCalculatedBigramProbabiliy(binaryDictionary, unigramFrequency,
+                bigramFrequency1), binaryDictionary.getBigramProbability("a", "ab"));
+        assertEquals(getCalculatedBigramProbabiliy(binaryDictionary, unigramFrequency,
+                bigramFrequency2), binaryDictionary.getBigramProbability("aaa", "ab"));
+
+        assertFalse(binaryDictionary.isValidBigram("aaa", "a"));
+        assertFalse(binaryDictionary.isValidBigram("ab", "a"));
+        assertFalse(binaryDictionary.isValidBigram("ab", "aaa"));
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
index 0189b33..d670aad 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
@@ -26,7 +26,6 @@
 import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
@@ -60,9 +59,6 @@
     private static final int NUM_OF_NODES_HAVING_SHORTCUTS = 50;
     private static final int NUM_OF_SHORTCUTS = 5;
 
-    private static final int USE_BYTE_ARRAY = 1;
-    private static final int USE_BYTE_BUFFER = 2;
-
     private static final ArrayList<String> sWords = CollectionUtils.newArrayList();
     private static final SparseArray<List<Integer>> sEmptyBigrams =
             CollectionUtils.newSparseArray();
@@ -71,18 +67,6 @@
             CollectionUtils.newSparseArray();
     private static final HashMap<String, List<String>> sShortcuts = CollectionUtils.newHashMap();
 
-    private static final FormatSpec.FormatOptions VERSION2 = new FormatSpec.FormatOptions(2);
-    private static final FormatSpec.FormatOptions VERSION3_WITHOUT_DYNAMIC_UPDATE =
-            new FormatSpec.FormatOptions(3, false /* supportsDynamicUpdate */);
-    private static final FormatSpec.FormatOptions VERSION3_WITH_DYNAMIC_UPDATE =
-            new FormatSpec.FormatOptions(3, true /* supportsDynamicUpdate */);
-    private static final FormatSpec.FormatOptions VERSION4_WITHOUT_DYNAMIC_UPDATE =
-            new FormatSpec.FormatOptions(4, false /* supportsDynamicUpdate */);
-    private static final FormatSpec.FormatOptions VERSION4_WITH_DYNAMIC_UPDATE =
-            new FormatSpec.FormatOptions(4, true /* supportsDynamicUpdate */);
-
-    private static final String TEST_DICT_FILE_EXTENSION = ".testDict";
-
     public BinaryDictDecoderEncoderTests() {
         this(System.currentTimeMillis(), DEFAULT_MAX_UNIGRAMS);
     }
@@ -121,17 +105,6 @@
         }
     }
 
-    private DictEncoder getDictEncoder(final File file, final FormatOptions formatOptions) {
-        if (formatOptions.mVersion == FormatSpec.VERSION4) {
-            return new Ver4DictEncoder(getContext().getCacheDir());
-        } else if (formatOptions.mVersion == 3 || formatOptions.mVersion == 2) {
-            return new Ver3DictEncoder(file);
-        } else {
-            throw new RuntimeException("The format option has a wrong version : "
-                    + formatOptions.mVersion);
-        }
-    }
-
     private void generateWords(final int number, final Random random, final int[] codePointSet) {
         final Set<String> wordSet = CollectionUtils.newHashSet();
         while (wordSet.size() < number) {
@@ -183,7 +156,8 @@
         long now = -1, diff = -1;
 
         try {
-            final DictEncoder dictEncoder = getDictEncoder(file, formatOptions);
+            final DictEncoder dictEncoder = BinaryDictUtils.getDictEncoder(file, formatOptions,
+                    getContext().getCacheDir());
 
             now = System.currentTimeMillis();
             // If you need to dump the dict to a textual file, uncomment the line below and the
@@ -238,54 +212,21 @@
     private String outputOptions(final int bufferType,
             final FormatSpec.FormatOptions formatOptions) {
         String result = " : buffer type = "
-                + ((bufferType == USE_BYTE_BUFFER) ? "byte buffer" : "byte array");
+                + ((bufferType == BinaryDictUtils.USE_BYTE_BUFFER) ? "byte buffer" : "byte array");
         result += " : version = " + formatOptions.mVersion;
         return result + ", supportsDynamicUpdate = " + formatOptions.mSupportsDynamicUpdate;
     }
 
-    private DictionaryOptions getDictionaryOptions(final String id, final String version) {
-        final DictionaryOptions options = new DictionaryOptions(new HashMap<String, String>(),
-                false, false);
-        options.mAttributes.put("version", version);
-        options.mAttributes.put("dictionary", id);
-        return options;
-    }
-
-    private File setUpDictionaryFile(final String name, final String version) {
-        File file = null;
-        try {
-            file = new File(getContext().getCacheDir(), name + "." + version
-                    + TEST_DICT_FILE_EXTENSION);
-            file.createNewFile();
-        } catch (IOException e) {
-            // do nothing
-        }
-        assertTrue("Failed to create the dictionary file.", file.exists());
-        return file;
-    }
-
-    private DictDecoder getDictDecoder(final File file, final int bufferType,
-            final FormatOptions formatOptions, final DictionaryOptions dictOptions) {
-        if (formatOptions.mVersion == FormatSpec.VERSION4) {
-            final FileHeader header = new FileHeader(0, dictOptions, formatOptions);
-            return FormatSpec.getDictDecoder(new File(getContext().getCacheDir(),
-                    header.getId() + "." + header.getVersion()), bufferType);
-        } else {
-            return FormatSpec.getDictDecoder(file, bufferType);
-        }
-    }
     // Tests for readDictionaryBinary and writeDictionaryBinary
 
     private long timeReadingAndCheckDict(final File file, final List<String> words,
             final SparseArray<List<Integer>> bigrams,
-            final HashMap<String, List<String>> shortcutMap, final int bufferType,
-            final FormatOptions formatOptions, final DictionaryOptions dictOptions) {
+            final HashMap<String, List<String>> shortcutMap, final int bufferType) {
         long now, diff = -1;
 
         FusionDictionary dict = null;
         try {
-            final DictDecoder dictDecoder = getDictDecoder(file, bufferType, formatOptions,
-                    dictOptions);
+            final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, bufferType);
             now = System.currentTimeMillis();
             dict = dictDecoder.readDictionaryBinary(null, false /* deleteDictIfBroken */);
             diff  = System.currentTimeMillis() - now;
@@ -307,17 +248,17 @@
 
         final String dictName = "runReadAndWrite";
         final String dictVersion = Long.toString(System.currentTimeMillis());
-        final File file = setUpDictionaryFile(dictName, dictVersion);
+        final File file = BinaryDictUtils.getDictFile(dictName, dictVersion, formatOptions,
+                getContext().getCacheDir());
 
         final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
-                getDictionaryOptions(dictName, dictVersion));
+                BinaryDictUtils.getDictionaryOptions(dictName, dictVersion));
         addUnigrams(words.size(), dict, words, shortcuts);
         addBigrams(dict, words, bigrams);
         checkDictionary(dict, words, bigrams, shortcuts);
 
         final long write = timeWritingDictToFile(file, dict, formatOptions);
-        final long read = timeReadingAndCheckDict(file, words, bigrams, shortcuts, bufferType,
-                formatOptions, dict.mOptions);
+        final long read = timeReadingAndCheckDict(file, words, bigrams, shortcuts, bufferType);
 
         return "PROF: read=" + read + "ms, write=" + write + "ms :" + message
                 + " : " + outputOptions(bufferType, formatOptions);
@@ -346,8 +287,7 @@
         final byte[] buffer = new byte[50 * 3];
         final DictBuffer dictBuffer = new ByteArrayDictBuffer(buffer);
         for (final String word : sWords) {
-            Log.d("testReadAndWriteString", "write : " + word);
-            Arrays.fill(buffer, (byte)0);
+            Arrays.fill(buffer, (byte) 0);
             CharEncoding.writeString(buffer, 0, word);
             dictBuffer.position(0);
             final String str = CharEncoding.readString(dictBuffer);
@@ -358,11 +298,18 @@
     public void testReadAndWriteWithByteBuffer() {
         final List<String> results = CollectionUtils.newArrayList();
 
-        runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION2);
-        runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION3_WITHOUT_DYNAMIC_UPDATE);
-        runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION3_WITH_DYNAMIC_UPDATE);
-        runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION4_WITHOUT_DYNAMIC_UPDATE);
-        runReadAndWriteTests(results, USE_BYTE_BUFFER, VERSION4_WITH_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION2);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION3_WITHOUT_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -372,11 +319,18 @@
     public void testReadAndWriteWithByteArray() {
         final List<String> results = CollectionUtils.newArrayList();
 
-        runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION2);
-        runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION3_WITHOUT_DYNAMIC_UPDATE);
-        runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION3_WITH_DYNAMIC_UPDATE);
-        runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION4_WITHOUT_DYNAMIC_UPDATE);
-        runReadAndWriteTests(results, USE_BYTE_ARRAY, VERSION4_WITH_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION2);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION3_WITHOUT_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
+        runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -432,8 +386,7 @@
     }
 
     private long timeAndCheckReadUnigramsAndBigramsBinary(final File file, final List<String> words,
-            final SparseArray<List<Integer>> bigrams, final int bufferType,
-            final FormatOptions formatOptions, final DictionaryOptions dictOptions) {
+            final SparseArray<List<Integer>> bigrams, final int bufferType) {
         FileInputStream inStream = null;
 
         final TreeMap<Integer, String> resultWords = CollectionUtils.newTreeMap();
@@ -443,8 +396,7 @@
 
         long now = -1, diff = -1;
         try {
-            final DictDecoder dictDecoder = getDictDecoder(file, bufferType, formatOptions,
-                    dictOptions);
+            final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, bufferType);
             now = System.currentTimeMillis();
             dictDecoder.readUnigramsAndBigramsBinary(resultWords, resultFreqs, resultBigrams);
             diff = System.currentTimeMillis() - now;
@@ -471,20 +423,20 @@
             final FormatSpec.FormatOptions formatOptions, final String message) {
         final String dictName = "runReadUnigrams";
         final String dictVersion = Long.toString(System.currentTimeMillis());
-        final File file = setUpDictionaryFile(dictName, dictVersion);
+        final File file = BinaryDictUtils.getDictFile(dictName, dictVersion, formatOptions,
+                getContext().getCacheDir());
 
         // making the dictionary from lists of words.
         final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
-                getDictionaryOptions(dictName, dictVersion));
+                BinaryDictUtils.getDictionaryOptions(dictName, dictVersion));
         addUnigrams(words.size(), dict, words, null /* shortcutMap */);
         addBigrams(dict, words, bigrams);
 
         timeWritingDictToFile(file, dict, formatOptions);
 
-        long wordMap = timeAndCheckReadUnigramsAndBigramsBinary(file, words, bigrams, bufferType,
-                formatOptions, dict.mOptions);
+        long wordMap = timeAndCheckReadUnigramsAndBigramsBinary(file, words, bigrams, bufferType);
         long fullReading = timeReadingAndCheckDict(file, words, bigrams, null /* shortcutMap */,
-                bufferType, formatOptions, dict.mOptions);
+                bufferType);
 
         return "readDictionaryBinary=" + fullReading + ", readUnigramsAndBigramsBinary=" + wordMap
                 + " : " + message + " : " + outputOptions(bufferType, formatOptions);
@@ -503,11 +455,18 @@
     public void testReadUnigramsAndBigramsBinaryWithByteBuffer() {
         final ArrayList<String> results = CollectionUtils.newArrayList();
 
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION2);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION3_WITHOUT_DYNAMIC_UPDATE);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION3_WITH_DYNAMIC_UPDATE);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION4_WITHOUT_DYNAMIC_UPDATE);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_BUFFER, VERSION4_WITH_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION2);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION3_WITHOUT_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -517,11 +476,18 @@
     public void testReadUnigramsAndBigramsBinaryWithByteArray() {
         final ArrayList<String> results = CollectionUtils.newArrayList();
 
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION2);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION3_WITHOUT_DYNAMIC_UPDATE);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION3_WITH_DYNAMIC_UPDATE);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION4_WITHOUT_DYNAMIC_UPDATE);
-        runReadUnigramsAndBigramsTests(results, USE_BYTE_ARRAY, VERSION4_WITH_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION2);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION3_WITHOUT_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
+        runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -569,16 +535,16 @@
             final FormatOptions formatOptions, final String message) {
         final String dictName = "testGetTerminalPosition";
         final String dictVersion = Long.toString(System.currentTimeMillis());
-        final File file = setUpDictionaryFile(dictName, dictVersion);
+        final File file = BinaryDictUtils.getDictFile(dictName, dictVersion, formatOptions,
+                getContext().getCacheDir());
 
         final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
-                getDictionaryOptions(dictName, dictVersion));
+                BinaryDictUtils.getDictionaryOptions(dictName, dictVersion));
         addUnigrams(sWords.size(), dict, sWords, null /* shortcutMap */);
         addBigrams(dict, words, bigrams);
         timeWritingDictToFile(file, dict, formatOptions);
 
-        final DictDecoder dictDecoder = getDictDecoder(file, DictDecoder.USE_BYTEARRAY,
-                formatOptions, dict.mOptions);
+        final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file, DictDecoder.USE_BYTEARRAY);
         try {
             dictDecoder.openDictBuffer();
         } catch (IOException e) {
@@ -629,17 +595,29 @@
     public void testGetTerminalPosition() {
         final ArrayList<String> results = CollectionUtils.newArrayList();
 
-        runGetTerminalPositionTests(USE_BYTE_ARRAY, VERSION2);
-        runGetTerminalPositionTests(USE_BYTE_ARRAY, VERSION3_WITHOUT_DYNAMIC_UPDATE);
-        runGetTerminalPositionTests(USE_BYTE_ARRAY, VERSION3_WITH_DYNAMIC_UPDATE);
-        runGetTerminalPositionTests(USE_BYTE_ARRAY, VERSION4_WITHOUT_DYNAMIC_UPDATE);
-        runGetTerminalPositionTests(USE_BYTE_ARRAY, VERSION4_WITH_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_ARRAY, BinaryDictUtils.VERSION2);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION3_WITHOUT_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_ARRAY,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP);
 
-        runGetTerminalPositionTests(USE_BYTE_BUFFER, VERSION2);
-        runGetTerminalPositionTests(USE_BYTE_BUFFER, VERSION3_WITHOUT_DYNAMIC_UPDATE);
-        runGetTerminalPositionTests(USE_BYTE_BUFFER, VERSION3_WITH_DYNAMIC_UPDATE);
-        runGetTerminalPositionTests(USE_BYTE_BUFFER, VERSION4_WITHOUT_DYNAMIC_UPDATE);
-        runGetTerminalPositionTests(USE_BYTE_BUFFER, VERSION4_WITH_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_BUFFER, BinaryDictUtils.VERSION2);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION3_WITHOUT_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION4_WITHOUT_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
+        runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_BUFFER,
+                BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP);
 
         for (final String result : results) {
             Log.d(TAG, result);
@@ -649,7 +627,8 @@
     private void runTestDeleteWord(final FormatOptions formatOptions) {
         final String dictName = "testDeleteWord";
         final String dictVersion = Long.toString(System.currentTimeMillis());
-        final File file = setUpDictionaryFile(dictName, dictVersion);
+        final File file = BinaryDictUtils.getDictFile(dictName, dictVersion, formatOptions,
+                getContext().getCacheDir());
 
         final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
                 new FusionDictionary.DictionaryOptions(
@@ -657,15 +636,7 @@
         addUnigrams(sWords.size(), dict, sWords, null /* shortcutMap */);
         timeWritingDictToFile(file, dict, formatOptions);
 
-        final DictUpdater dictUpdater;
-        if (formatOptions.mVersion == 3) {
-            dictUpdater = new Ver3DictUpdater(file, DictDecoder.USE_WRITABLE_BYTEBUFFER);
-        } else if (formatOptions.mVersion == 4) {
-            dictUpdater = new Ver4DictUpdater(file, DictDecoder.USE_WRITABLE_BYTEBUFFER);
-        } else {
-            throw new RuntimeException("DictUpdater for version " + formatOptions.mVersion
-                    + " doesn't exist.");
-        }
+        final DictUpdater dictUpdater = BinaryDictUtils.getDictUpdater(file, formatOptions);
 
         try {
             MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD,
@@ -685,7 +656,7 @@
     }
 
     public void testDeleteWord() {
-        runTestDeleteWord(VERSION3_WITH_DYNAMIC_UPDATE);
-        runTestDeleteWord(VERSION4_WITH_DYNAMIC_UPDATE);
+        runTestDeleteWord(BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runTestDeleteWord(BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
index afe5adb..8bea3c0 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
@@ -23,6 +23,7 @@
 
 import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 import com.android.inputmethod.latin.utils.CollectionUtils;
@@ -30,24 +31,16 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.Random;
 
 @LargeTest
 public class BinaryDictIOUtilsTests extends AndroidTestCase {
     private static final String TAG = BinaryDictIOUtilsTests.class.getSimpleName();
-    private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
-            new FormatSpec.FormatOptions(3, true);
 
     private static final ArrayList<String> sWords = CollectionUtils.newArrayList();
     public static final int DEFAULT_MAX_UNIGRAMS = 1500;
     private final int mMaxUnigrams;
 
-    private static final String TEST_DICT_FILE_EXTENSION = ".testDict";
-
-    private static final int VERSION3 = 3;
-    private static final int VERSION4 = 4;
-
     private static final String[] CHARACTERS = {
         "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
         "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
@@ -141,7 +134,7 @@
         int position = FormatSpec.NOT_VALID_WORD;
 
         try {
-            final Ver3DictDecoder dictDecoder = new Ver3DictDecoder(file,
+            final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file,
                     DictDecoder.USE_READONLY_BYTEBUFFER);
             position = dictDecoder.getTerminalPosition(word);
         } catch (IOException e) {
@@ -159,7 +152,7 @@
      * @throws IOException
      * @throws UnsupportedFormatException
      */
-    private static PtNodeInfo findWordByBinaryDictReader(final DictDecoder dictDecoder,
+    private static PtNodeInfo findWordByDictDecoder(final DictDecoder dictDecoder,
             final String word) throws IOException, UnsupportedFormatException {
         int position = dictDecoder.getTerminalPosition(word);
         if (position != FormatSpec.NOT_VALID_WORD) {
@@ -176,7 +169,7 @@
         PtNodeInfo info = null;
         try {
             dictDecoder.openDictBuffer();
-            info = findWordByBinaryDictReader(dictDecoder, word);
+            info = findWordByDictDecoder(dictDecoder, word);
         } catch (IOException e) {
         } catch (UnsupportedFormatException e) {
         }
@@ -186,16 +179,10 @@
     // return amount of time to insert a word
     private long insertAndCheckWord(final File file, final String word, final int frequency,
             final boolean exist, final ArrayList<WeightedString> bigrams,
-            final ArrayList<WeightedString> shortcuts, final int formatVersion) {
+            final ArrayList<WeightedString> shortcuts, final FormatOptions formatOptions) {
         long amountOfTime = -1;
         try {
-            final DictUpdater dictUpdater;
-            if (formatVersion == VERSION3) {
-                dictUpdater = new Ver3DictUpdater(file, DictDecoder.USE_WRITABLE_BYTEBUFFER);
-            } else {
-                throw new RuntimeException("DictUpdater for version " + formatVersion + " doesn't"
-                        + " exist.");
-            }
+            final DictUpdater dictUpdater = BinaryDictUtils.getDictUpdater(file, formatOptions);
 
             if (!exist) {
                 assertEquals(FormatSpec.NOT_VALID_WORD, getWordPosition(file, word));
@@ -212,18 +199,14 @@
         return amountOfTime;
     }
 
-    private void deleteWord(final File file, final String word, final int formatVersion) {
+    private void deleteWord(final File file, final String word, final FormatOptions formatOptions) {
         try {
-            final DictUpdater dictUpdater;
-            if (formatVersion == VERSION3) {
-                dictUpdater = new Ver3DictUpdater(file, DictDecoder.USE_WRITABLE_BYTEBUFFER);
-            } else {
-                throw new RuntimeException("DictUpdater for version " + formatVersion + " doesn't"
-                        + " exist.");
-            }
+            final DictUpdater dictUpdater = BinaryDictUtils.getDictUpdater(file, formatOptions);
             dictUpdater.deleteWord(word);
         } catch (IOException e) {
+            Log.e(TAG, "Raised an IOException while deleting a word", e);
         } catch (UnsupportedFormatException e) {
+            Log.e(TAG, "Raised an UnsupportedFormatException while deleting a word", e);
         }
     }
 
@@ -242,23 +225,21 @@
         }
     }
 
-    private void runTestInsertWord(final int formatVersion) {
-        File file = null;
-        try {
-            file = File.createTempFile("testInsertWord", TEST_DICT_FILE_EXTENSION,
-                    getContext().getCacheDir());
-        } catch (IOException e) {
-            fail("IOException while creating temporary file: " + e);
-        }
+    private void runTestInsertWord(final FormatOptions formatOptions) {
+        final String testName = "testInsertWord";
+        final String version = Long.toString(System.currentTimeMillis());
+        final File file = BinaryDictUtils.getDictFile(testName, version, formatOptions,
+                getContext().getCacheDir());
 
         // set an initial dictionary.
         final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
-                new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false));
+                BinaryDictUtils.getDictionaryOptions(testName, version));
         dict.add("abcd", 10, null, false);
 
         try {
-            final DictEncoder dictEncoder = new Ver3DictEncoder(file);
-            dictEncoder.writeDictionary(dict, FORMAT_OPTIONS);
+            final DictEncoder dictEncoder = BinaryDictUtils.getDictEncoder(file, formatOptions,
+                    getContext().getCacheDir());
+            dictEncoder.writeDictionary(dict, formatOptions);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         } catch (UnsupportedFormatException e) {
@@ -266,54 +247,68 @@
         }
 
         MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD, getWordPosition(file, "abcd"));
-        insertAndCheckWord(file, "abcde", 10, false, null, null, formatVersion);
+        insertAndCheckWord(file, "abcde", 10, false, null, null, formatOptions);
+        checkReverseLookup(file, "abcde", getWordPosition(file, "abcde"));
 
-        insertAndCheckWord(file, "abcdefghijklmn", 10, false, null, null, formatVersion);
+        insertAndCheckWord(file, "abcdefghijklmn", 10, false, null, null, formatOptions);
         checkReverseLookup(file, "abcdefghijklmn", getWordPosition(file, "abcdefghijklmn"));
 
-        insertAndCheckWord(file, "abcdabcd", 10, false, null, null, formatVersion);
+        insertAndCheckWord(file, "abcdabcd", 10, false, null, null, formatOptions);
         checkReverseLookup(file, "abcdabcd", getWordPosition(file, "abcdabcd"));
 
         // update the existing word.
-        insertAndCheckWord(file, "abcdabcd", 15, true, null, null, formatVersion);
+        insertAndCheckWord(file, "abcdabcd", 15, true, null, null, formatOptions);
+        checkReverseLookup(file, "abcdabcd", getWordPosition(file, "abcdabcd"));
 
-        // split 1
-        insertAndCheckWord(file, "ab", 20, false, null, null, formatVersion);
+        // Testing splitOnly
+        insertAndCheckWord(file, "ab", 20, false, null, null, formatOptions);
+        checkReverseLookup(file, "ab", getWordPosition(file, "ab"));
+        checkReverseLookup(file, "abcdabcd", getWordPosition(file, "abcdabcd"));
+        checkReverseLookup(file, "abcde", getWordPosition(file, "abcde"));
+        checkReverseLookup(file, "abcdefghijklmn", getWordPosition(file, "abcdefghijklmn"));
 
-        // split 2
-        insertAndCheckWord(file, "ami", 30, false, null, null, formatVersion);
+        // Testing splitAndBranch
+        insertAndCheckWord(file, "ami", 30, false, null, null, formatOptions);
+        checkReverseLookup(file, "ami", getWordPosition(file, "ami"));
+        checkReverseLookup(file, "ab", getWordPosition(file, "ab"));
+        checkReverseLookup(file, "abcdabcd", getWordPosition(file, "abcdabcd"));
+        checkReverseLookup(file, "abcde", getWordPosition(file, "abcde"));
+        checkReverseLookup(file, "abcdefghijklmn", getWordPosition(file, "abcdefghijklmn"));
+        checkReverseLookup(file, "ami", getWordPosition(file, "ami"));
 
-        deleteWord(file, "ami", formatVersion);
+        insertAndCheckWord(file, "abcdefzzzz", 40, false, null, null, formatOptions);
+        checkReverseLookup(file, "abcdefzzzz", getWordPosition(file, "abcdefzzzz"));
+
+        deleteWord(file, "ami", formatOptions);
         assertEquals(FormatSpec.NOT_VALID_WORD, getWordPosition(file, "ami"));
 
-        insertAndCheckWord(file, "abcdabfg", 30, false, null, null, formatVersion);
+        insertAndCheckWord(file, "abcdabfg", 30, false, null, null, formatOptions);
 
-        deleteWord(file, "abcd", formatVersion);
+        deleteWord(file, "abcd", formatOptions);
         assertEquals(FormatSpec.NOT_VALID_WORD, getWordPosition(file, "abcd"));
     }
 
     public void testInsertWord() {
-        runTestInsertWord(VERSION3);
+        runTestInsertWord(BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runTestInsertWord(BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
     }
 
-    private void runTestInsertWordWithBigrams(final int formatVersion) {
-        File file = null;
-        try {
-            file = File.createTempFile("testInsertWordWithBigrams", TEST_DICT_FILE_EXTENSION,
-                    getContext().getCacheDir());
-        } catch (IOException e) {
-            fail("IOException while creating temporary file: " + e);
-        }
+    private void runTestInsertWordWithBigrams(final FormatOptions formatOptions) {
+        final String testName = "testInsertWordWithBigrams";
+        final String version = Long.toString(System.currentTimeMillis());
+        File file = BinaryDictUtils.getDictFile(testName, version, formatOptions,
+                getContext().getCacheDir());
 
         // set an initial dictionary.
         final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
-                new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false));
+                BinaryDictUtils.getDictionaryOptions(testName, version));
         dict.add("abcd", 10, null, false);
         dict.add("efgh", 15, null, false);
 
         try {
-            final DictEncoder dictEncoder = new Ver3DictEncoder(file); 
-            dictEncoder.writeDictionary(dict, FORMAT_OPTIONS);
+            final DictEncoder dictEncoder = BinaryDictUtils.getDictEncoder(file, formatOptions,
+                    getContext().getCacheDir());
+            dictEncoder.writeDictionary(dict, formatOptions);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
         } catch (UnsupportedFormatException e) {
@@ -323,8 +318,8 @@
         final ArrayList<WeightedString> banana = new ArrayList<WeightedString>();
         banana.add(new WeightedString("banana", 10));
 
-        insertAndCheckWord(file, "banana", 0, false, null, null, formatVersion);
-        insertAndCheckWord(file, "recursive", 60, true, banana, null, formatVersion);
+        insertAndCheckWord(file, "banana", 0, false, null, null, formatOptions);
+        insertAndCheckWord(file, "recursive", 60, true, banana, null, formatOptions);
 
         final PtNodeInfo info = findWordFromFile(file, "recursive");
         int bananaPos = getWordPosition(file, "banana");
@@ -334,27 +329,25 @@
     }
 
     public void testInsertWordWithBigrams() {
-        runTestInsertWordWithBigrams(VERSION3);
+        runTestInsertWordWithBigrams(BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runTestInsertWordWithBigrams(BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
     }
 
-    private void runTestRandomWords(final int formatVersion) {
-        File file = null;
-        try {
-            file = File.createTempFile("testRandomWord", TEST_DICT_FILE_EXTENSION,
-                    getContext().getCacheDir());
-        } catch (IOException e) {
-        }
-        assertNotNull(file);
+    private void runTestRandomWords(final FormatOptions formatOptions) {
+        final String testName = "testRandomWord";
+        final String version = Long.toString(System.currentTimeMillis());
+        final File file = BinaryDictUtils.getDictFile(testName, version, formatOptions,
+                getContext().getCacheDir());
 
         // set an initial dictionary.
         final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
-                new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false,
-                        false));
+                BinaryDictUtils.getDictionaryOptions(testName, version));
         dict.add("initial", 10, null, false);
 
         try {
-            final DictEncoder dictEncoder = new Ver3DictEncoder(file);
-            dictEncoder.writeDictionary(dict, FORMAT_OPTIONS);
+            final DictEncoder dictEncoder = BinaryDictUtils.getDictEncoder(file, formatOptions,
+                    getContext().getCacheDir());
+            dictEncoder.writeDictionary(dict, formatOptions);
         } catch (IOException e) {
             assertTrue(false);
         } catch (UnsupportedFormatException e) {
@@ -366,7 +359,7 @@
         int cnt = 0;
         for (final String word : sWords) {
             final long diff = insertAndCheckWord(file, word,
-                    cnt % FormatSpec.MAX_TERMINAL_FREQUENCY, false, null, null, formatVersion);
+                    cnt % FormatSpec.MAX_TERMINAL_FREQUENCY, false, null, null, formatOptions);
             maxTimeToInsert = Math.max(maxTimeToInsert, diff);
             minTimeToInsert = Math.min(minTimeToInsert, diff);
             sum += diff;
@@ -377,13 +370,14 @@
             MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD, getWordPosition(file, word));
         }
 
-        Log.d(TAG, "Test version " + formatVersion);
+        Log.d(TAG, "Test version " + formatOptions.mVersion);
         Log.d(TAG, "max = " + ((double)maxTimeToInsert/1000000) + " ms.");
         Log.d(TAG, "min = " + ((double)minTimeToInsert/1000000) + " ms.");
         Log.d(TAG, "avg = " + ((double)sum/mMaxUnigrams/1000000) + " ms.");
     }
 
     public void testRandomWords() {
-        runTestRandomWords(VERSION3);
+        runTestRandomWords(BinaryDictUtils.VERSION3_WITH_DYNAMIC_UPDATE);
+        runTestRandomWords(BinaryDictUtils.VERSION4_WITH_DYNAMIC_UPDATE);
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictUtils.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictUtils.java
new file mode 100644
index 0000000..f476738
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictUtils.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.makedict;
+
+import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
+
+import java.io.File;
+import java.util.HashMap;
+
+public class BinaryDictUtils {
+    public static final int USE_BYTE_ARRAY = 1;
+    public static final int USE_BYTE_BUFFER = 2;
+
+    public static final String TEST_DICT_FILE_EXTENSION = ".testDict";
+
+    public static final FormatSpec.FormatOptions VERSION2 = new FormatSpec.FormatOptions(2);
+    public static final FormatSpec.FormatOptions VERSION3_WITHOUT_DYNAMIC_UPDATE =
+            new FormatSpec.FormatOptions(3, false /* supportsDynamicUpdate */);
+    public static final FormatSpec.FormatOptions VERSION3_WITH_DYNAMIC_UPDATE =
+            new FormatSpec.FormatOptions(3, true /* supportsDynamicUpdate */);
+    public static final FormatSpec.FormatOptions VERSION4_WITHOUT_DYNAMIC_UPDATE =
+            new FormatSpec.FormatOptions(4, false /* supportsDynamicUpdate */);
+    public static final FormatSpec.FormatOptions VERSION4_WITH_DYNAMIC_UPDATE =
+            new FormatSpec.FormatOptions(4, true /* supportsDynamicUpdate */);
+    public static final FormatSpec.FormatOptions VERSION4_WITH_DYNAMIC_UPDATE_AND_TIMESTAMP =
+            new FormatSpec.FormatOptions(4, true /* supportsDynamicUpdate */,
+                    true /* hasTimestamp */);
+
+    public static DictionaryOptions getDictionaryOptions(final String id, final String version) {
+        final DictionaryOptions options = new DictionaryOptions(new HashMap<String, String>(),
+                false /* germanUmlautProcessing */, false /* frenchLigatureProcessing */);
+        options.mAttributes.put("dictionary", id);
+        options.mAttributes.put("version", version);
+        return options;
+    }
+
+    public static File getDictFile(final String name, final String version,
+            final FormatOptions formatOptions, final File directory) {
+        if (formatOptions.mVersion == 2 || formatOptions.mVersion == 3) {
+            return new File(directory, name + "." + version + TEST_DICT_FILE_EXTENSION);
+        } else if (formatOptions.mVersion == 4) {
+            return new File(directory, name + "." + version);
+        } else {
+            throw new RuntimeException("the format option has a wrong version : "
+                    + formatOptions.mVersion);
+        }
+    }
+
+    public static DictEncoder getDictEncoder(final File file, final FormatOptions formatOptions,
+            final File cacheDir) {
+        if (formatOptions.mVersion == FormatSpec.VERSION4) {
+            return new Ver4DictEncoder(cacheDir);
+        } else if (formatOptions.mVersion == 3 || formatOptions.mVersion == 2) {
+            return new Ver3DictEncoder(file);
+        } else {
+            throw new RuntimeException("The format option has a wrong version : "
+                    + formatOptions.mVersion);
+        }
+    }
+
+    public static DictUpdater getDictUpdater(final File file, final FormatOptions formatOptions) {
+        if (formatOptions.mVersion == FormatSpec.VERSION4) {
+            return new Ver4DictUpdater(file, DictDecoder.USE_WRITABLE_BYTEBUFFER);
+        } else if (formatOptions.mVersion == 3) {
+            return new Ver3DictUpdater(file, DictDecoder.USE_WRITABLE_BYTEBUFFER);
+        } else {
+            throw new RuntimeException("The format option has a wrong version : "
+                    + formatOptions.mVersion);
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
index 856b2db..ccdd567 100644
--- a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
@@ -41,7 +41,9 @@
     InputMethodSubtype ES_US;
     InputMethodSubtype FR;
     InputMethodSubtype FR_CA;
+    InputMethodSubtype FR_CH;
     InputMethodSubtype DE;
+    InputMethodSubtype DE_CH;
     InputMethodSubtype ZZ;
     InputMethodSubtype DE_QWERTY;
     InputMethodSubtype FR_QWERTZ;
@@ -70,8 +72,12 @@
                 Locale.FRENCH.toString(), "azerty");
         FR_CA = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
                 Locale.CANADA_FRENCH.toString(), "qwerty");
+        FR_CH = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                "fr_CH", "swiss");
         DE = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
                 Locale.GERMAN.toString(), "qwertz");
+        DE_CH = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                "de_CH", "swiss");
         ZZ = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
                 SubtypeLocaleUtils.NO_LANGUAGE, "qwerty");
         DE_QWERTY = AdditionalSubtypeUtils.createAdditionalSubtype(
@@ -112,7 +118,9 @@
         assertEquals("es_US", "spanish", SubtypeLocaleUtils.getKeyboardLayoutSetName(ES_US));
         assertEquals("fr   ", "azerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR));
         assertEquals("fr_CA", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR_CA));
+        assertEquals("fr_CH", "swiss", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR_CH));
         assertEquals("de   ", "qwertz", SubtypeLocaleUtils.getKeyboardLayoutSetName(DE));
+        assertEquals("de_CH", "swiss", SubtypeLocaleUtils.getKeyboardLayoutSetName(DE_CH));
         assertEquals("zz   ", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(ZZ));
     }
 
@@ -125,7 +133,9 @@
     //  es_US spanish F  Spanish (US)            exception
     //  fr    azerty  F  French
     //  fr_CA qwerty  F  French (Canada)
+    //  fr_CH swiss   F  French (Switzerland)
     //  de    qwertz  F  German
+    //  de_CH swiss   F  German (Switzerland)
     //  zz    qwerty  F  Alphabet (QWERTY)
     //  fr    qwertz  T  French (QWERTZ)
     //  de    qwerty  T  German (QWERTY)
@@ -148,8 +158,12 @@
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR));
                 assertEquals("fr_CA", "French (Canada)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_CA));
+                assertEquals("fr_CH", "French (Switzerland)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_CH));
                 assertEquals("de   ", "German",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE));
+                assertEquals("de_CH", "German (Switzerland)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE_CH));
                 assertEquals("zz   ", "Alphabet (QWERTY)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ));
                 return null;
@@ -189,7 +203,9 @@
     //  es_US spanish F  Espagnol (États-Unis)            exception
     //  fr    azerty  F  Français
     //  fr_CA qwerty  F  Français (Canada)
+    //  fr_CH swiss   F  Français (Suisse)
     //  de    qwertz  F  Allemand
+    //  de_CH swiss   F  Allemand (Suisse)
     //  zz    qwerty  F  Aucune langue (QWERTY)
     //  fr    qwertz  T  Français (QWERTZ)
     //  de    qwerty  T  Allemand (QWERTY)
@@ -212,8 +228,12 @@
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR));
                 assertEquals("fr_CA", "Français (Canada)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_CA));
+                assertEquals("fr_CH", "Français (Suisse)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_CH));
                 assertEquals("de   ", "Allemand",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE));
+                assertEquals("de_CH", "Allemand (Suisse)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE_CH));
                 assertEquals("zz   ", "Alphabet latin (QWERTY)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ));
                 return null;
@@ -300,7 +320,9 @@
     //  es_US spanish F  Es  Español   Español (EE.UU.)       exception
     //  fr    azerty  F  Fr  Français  Français
     //  fr_CA qwerty  F  Fr  Français  Français (Canada)
+    //  fr_CH swiss   F  Fr  Français  Français (Suisse)
     //  de    qwertz  F  De  Deutsch   Deutsch
+    //  de_CH swiss   F  De  Deutsch   Deutsch (Schweiz)
     //  zz    qwerty  F      QWERTY    QWERTY
     //  fr    qwertz  T  Fr  Français  Français
     //  de    qwerty  T  De  Deutsch   Deutsch
@@ -317,7 +339,11 @@
             assertEquals("fr   ", "Français",     SubtypeLocaleUtils.getFullDisplayName(FR));
             assertEquals("fr_CA", "Français (Canada)",
                     SubtypeLocaleUtils.getFullDisplayName(FR_CA));
+            assertEquals("fr_CH", "Français (Suisse)",
+                    SubtypeLocaleUtils.getFullDisplayName(FR_CH));
             assertEquals("de   ", "Deutsch",      SubtypeLocaleUtils.getFullDisplayName(DE));
+            assertEquals("de_CH", "Deutsch (Schweiz)",
+                    SubtypeLocaleUtils.getFullDisplayName(DE_CH));
             assertEquals("zz   ", "QWERTY",       SubtypeLocaleUtils.getFullDisplayName(ZZ));
 
             assertEquals("en_US", "English",  SubtypeLocaleUtils.getMiddleDisplayName(EN_US));
@@ -325,7 +351,9 @@
             assertEquals("es_US", "Español",  SubtypeLocaleUtils.getMiddleDisplayName(ES_US));
             assertEquals("fr   ", "Français", SubtypeLocaleUtils.getMiddleDisplayName(FR));
             assertEquals("fr_CA", "Français", SubtypeLocaleUtils.getMiddleDisplayName(FR_CA));
+            assertEquals("fr_CH", "Français", SubtypeLocaleUtils.getMiddleDisplayName(FR_CH));
             assertEquals("de   ", "Deutsch",  SubtypeLocaleUtils.getMiddleDisplayName(DE));
+            assertEquals("de_CH", "Deutsch",  SubtypeLocaleUtils.getMiddleDisplayName(DE_CH));
             assertEquals("zz   ", "QWERTY",   SubtypeLocaleUtils.getMiddleDisplayName(ZZ));
 
             assertEquals("en_US", "En", SubtypeLocaleUtils.getShortDisplayName(EN_US));
@@ -333,7 +361,9 @@
             assertEquals("es_US", "Es", SubtypeLocaleUtils.getShortDisplayName(ES_US));
             assertEquals("fr   ", "Fr", SubtypeLocaleUtils.getShortDisplayName(FR));
             assertEquals("fr_CA", "Fr", SubtypeLocaleUtils.getShortDisplayName(FR_CA));
+            assertEquals("fr_CH", "Fr", SubtypeLocaleUtils.getShortDisplayName(FR_CH));
             assertEquals("de   ", "De", SubtypeLocaleUtils.getShortDisplayName(DE));
+            assertEquals("de_CH", "De", SubtypeLocaleUtils.getShortDisplayName(DE_CH));
             assertEquals("zz   ", "",   SubtypeLocaleUtils.getShortDisplayName(ZZ));
             return null;
         }
diff --git a/tools/make-keyboard-text/res/values-de/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-de/donottranslate-more-keys.xml
index 9dc8717..bb8bb72 100644
--- a/tools/make-keyboard-text/res/values-de/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-de/donottranslate-more-keys.xml
@@ -55,6 +55,18 @@
     <!-- U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
          U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE -->
     <string name="more_keys_for_n">&#x00F1;,&#x0144;</string>
+    <!-- U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS -->
+    <string name="keylabel_for_swiss_row1_11">&#x00FC;</string>
+    <!-- U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE -->
+    <string name="more_keys_for_swiss_row1_11">&#x00E8;</string>
+    <!-- U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS -->
+    <string name="keylabel_for_swiss_row2_10">&#x00F6;</string>
+    <!-- U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE -->
+    <string name="more_keys_for_swiss_row2_10">&#x00E9;</string>
+    <!-- U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS -->
+    <string name="keylabel_for_swiss_row2_11">&#x00E4;</string>
+    <!-- U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE -->
+    <string name="more_keys_for_swiss_row2_11">&#x00E0;</string>
     <string name="single_quotes">!text/single_9qm_lqm</string>
     <string name="double_quotes">!text/double_9qm_lqm</string>
     <string name="single_angle_quotes">!text/single_raqm_laqm</string>
diff --git a/tools/make-keyboard-text/res/values-fr/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-fr/donottranslate-more-keys.xml
index 7b11a18..6656776 100644
--- a/tools/make-keyboard-text/res/values-fr/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values-fr/donottranslate-more-keys.xml
@@ -65,4 +65,16 @@
     <string name="more_keys_for_c">&#x00E7;,&#x0107;,&#x010D;</string>
     <!-- U+00FF: "ÿ" LATIN SMALL LETTER Y WITH DIAERESIS -->
     <string name="more_keys_for_y">%,&#x00FF;</string>
+    <!-- U+00E8: "è" LATIN SMALL LETTER E WITH GRAVE -->
+    <string name="keylabel_for_swiss_row1_11">&#x00E8;</string>
+    <!-- U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS -->
+    <string name="more_keys_for_swiss_row1_11">&#x00FC;</string>
+    <!-- U+00E9: "é" LATIN SMALL LETTER E WITH ACUTE -->
+    <string name="keylabel_for_swiss_row2_10">&#x00E9;</string>
+    <!-- U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS -->
+    <string name="more_keys_for_swiss_row2_10">&#x00F6;</string>
+    <!-- U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE -->
+    <string name="keylabel_for_swiss_row2_11">&#x00E0;</string>
+    <!-- U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS -->
+    <string name="more_keys_for_swiss_row2_11">&#x00E4;</string>
 </resources>
diff --git a/tools/make-keyboard-text/res/values/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values/donottranslate-more-keys.xml
index 64396b1..008ef30 100644
--- a/tools/make-keyboard-text/res/values/donottranslate-more-keys.xml
+++ b/tools/make-keyboard-text/res/values/donottranslate-more-keys.xml
@@ -63,6 +63,12 @@
     <string name="keylabel_for_south_slavic_row3_8"></string>
     <string name="more_keys_for_cyrillic_ie"></string>
     <string name="more_keys_for_cyrillic_i"></string>
+    <string name="keylabel_for_swiss_row1_11"></string>
+    <string name="keylabel_for_swiss_row2_10"></string>
+    <string name="keylabel_for_swiss_row2_11"></string>
+    <string name="more_keys_for_swiss_row1_11"></string>
+    <string name="more_keys_for_swiss_row2_10"></string>
+    <string name="more_keys_for_swiss_row2_11"></string>
     <!-- Label for "switch to alphabetic" key. -->
     <string name="label_to_alpha_key">ABC</string>
     <string name="single_quotes">!text/single_lqm_rqm</string>