mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-05-19 16:30:19 +00:00
allow users to select dictionaries
https://github.com/openboard-team/openboard/pull/569 https://github.com/openboard-team/openboard/pull/578 modified so dictionaries use the correct type instead of always main
This commit is contained in:
parent
62e55fd70b
commit
29c252066d
62 changed files with 502 additions and 16 deletions
|
@ -9,8 +9,8 @@ Plan / to do:
|
||||||
* ~upgrade dependencies~
|
* ~upgrade dependencies~
|
||||||
* upgrade NDK, https://github.com/openboard-team/openboard/issues/782
|
* upgrade NDK, https://github.com/openboard-team/openboard/issues/782
|
||||||
* maybe: rename (package, app, icon), so it can be installed parallel to OpenBoard, and published on F-Droid
|
* maybe: rename (package, app, icon), so it can be installed parallel to OpenBoard, and published on F-Droid
|
||||||
* user-selectable dictionaries, https://github.com/openboard-team/openboard/pull/578
|
* ~user-selectable dictionaries, https://github.com/openboard-team/openboard/pull/578~
|
||||||
* make additional dictionaries available for download, and link from app
|
* make additional dictionaries available for download (from OpenBoard PRs)
|
||||||
* multi-lingual typing, https://github.com/openboard-team/openboard/pull/593
|
* multi-lingual typing, https://github.com/openboard-team/openboard/pull/593
|
||||||
* suggestion fixes, https://github.com/openboard-team/openboard/pull/694, https://github.com/openboard-team/openboard/issues/795, https://github.com/openboard-team/openboard/issues/660
|
* suggestion fixes, https://github.com/openboard-team/openboard/pull/694, https://github.com/openboard-team/openboard/issues/795, https://github.com/openboard-team/openboard/issues/660
|
||||||
* improve auto-space insertion, https://github.com/openboard-team/openboard/pull/576
|
* improve auto-space insertion, https://github.com/openboard-team/openboard/pull/576
|
||||||
|
@ -23,6 +23,10 @@ Plan / to do:
|
||||||
Changes:
|
Changes:
|
||||||
* Updated dependencies
|
* Updated dependencies
|
||||||
* Debug version can be installed along OpenBoard
|
* Debug version can be installed along OpenBoard
|
||||||
|
* Allow users to add and replace built-in dictionaries
|
||||||
|
* modified / improved from https://github.com/openboard-team/openboard/pull/569 and https://github.com/openboard-team/openboard/pull/578
|
||||||
|
* dictionaries are available at https://github.com/Helium314/openboard/dictionaries/dict
|
||||||
|
* dictionary files starting with "main_" replace the built-in dictionary for the language, all other names work as add-on dictionaries
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,8 @@ android {
|
||||||
|
|
||||||
ndkVersion '23.2.8568313'
|
ndkVersion '23.2.8568313'
|
||||||
androidResources {
|
androidResources {
|
||||||
noCompress 'dict'
|
noCompress 'main.dict'
|
||||||
|
noCompress 'empty.dict'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import android.content.SharedPreferences;
|
||||||
import android.content.res.AssetFileDescriptor;
|
import android.content.res.AssetFileDescriptor;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.dslul.openboard.inputmethod.latin.common.FileUtils;
|
||||||
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils;
|
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils;
|
||||||
import org.dslul.openboard.inputmethod.latin.define.DecoderSpecificConstants;
|
import org.dslul.openboard.inputmethod.latin.define.DecoderSpecificConstants;
|
||||||
import org.dslul.openboard.inputmethod.latin.makedict.DictionaryHeader;
|
import org.dslul.openboard.inputmethod.latin.makedict.DictionaryHeader;
|
||||||
|
@ -62,6 +63,8 @@ final public class BinaryDictionaryGetter {
|
||||||
public static final String MAIN_DICTIONARY_CATEGORY = "main";
|
public static final String MAIN_DICTIONARY_CATEGORY = "main";
|
||||||
public static final String ID_CATEGORY_SEPARATOR = ":";
|
public static final String ID_CATEGORY_SEPARATOR = ":";
|
||||||
|
|
||||||
|
public static final String ASSETS_DICTIONARY_FOLDER = "dicts";
|
||||||
|
|
||||||
// The key considered to read the version attribute in a dictionary file.
|
// The key considered to read the version attribute in a dictionary file.
|
||||||
private static String VERSION_KEY = "version";
|
private static String VERSION_KEY = "version";
|
||||||
|
|
||||||
|
@ -170,8 +173,8 @@ final public class BinaryDictionaryGetter {
|
||||||
for (File directory : directoryList) {
|
for (File directory : directoryList) {
|
||||||
if (!directory.isDirectory()) continue;
|
if (!directory.isDirectory()) continue;
|
||||||
final String dirLocale =
|
final String dirLocale =
|
||||||
DictionaryInfoUtils.getWordListIdFromFileName(directory.getName());
|
DictionaryInfoUtils.getWordListIdFromFileName(directory.getName()).toLowerCase(Locale.ENGLISH);
|
||||||
final int matchLevel = LocaleUtils.getMatchLevel(dirLocale, locale);
|
final int matchLevel = LocaleUtils.getMatchLevel(dirLocale, locale.toLowerCase(Locale.ENGLISH));
|
||||||
if (LocaleUtils.isMatch(matchLevel)) {
|
if (LocaleUtils.isMatch(matchLevel)) {
|
||||||
final File[] wordLists = directory.listFiles();
|
final File[] wordLists = directory.listFiles();
|
||||||
if (null != wordLists) {
|
if (null != wordLists) {
|
||||||
|
@ -265,9 +268,16 @@ final public class BinaryDictionaryGetter {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!foundMainDict && dictPackSettings.isWordListActive(mainDictId)) {
|
if (!foundMainDict && dictPackSettings.isWordListActive(mainDictId)) {
|
||||||
|
final File dict = loadDictionaryFromAssets(locale.toString(), context);
|
||||||
|
final AssetFileAddress fallbackAsset;
|
||||||
|
if (dict == null) {
|
||||||
|
// fall back to the old way (maybe remove? will not work if files are compressed)
|
||||||
final int fallbackResId =
|
final int fallbackResId =
|
||||||
DictionaryInfoUtils.getMainDictionaryResourceId(context.getResources(), locale);
|
DictionaryInfoUtils.getMainDictionaryResourceId(context.getResources(), locale);
|
||||||
final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId);
|
fallbackAsset = loadFallbackResource(context, fallbackResId);
|
||||||
|
} else {
|
||||||
|
fallbackAsset = AssetFileAddress.makeFromFileName(dict.getPath());
|
||||||
|
}
|
||||||
if (null != fallbackAsset) {
|
if (null != fallbackAsset) {
|
||||||
fileList.add(fallbackAsset);
|
fileList.add(fallbackAsset);
|
||||||
}
|
}
|
||||||
|
@ -275,4 +285,75 @@ final public class BinaryDictionaryGetter {
|
||||||
|
|
||||||
return fileList;
|
return fileList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the best matching main dictionary from assets.
|
||||||
|
*
|
||||||
|
* Actually copies the dictionary to cache folder, and then returns that file. This allows
|
||||||
|
* the dictionaries to be stored in a compressed way, reducing APK size.
|
||||||
|
* On next load, the dictionary in cache folder is found by getCachedWordLists
|
||||||
|
*
|
||||||
|
* Returns null on IO errors or if no matching dictionary is found
|
||||||
|
*/
|
||||||
|
public static File loadDictionaryFromAssets(final String locale, final Context context) {
|
||||||
|
final String[] dictionaryList = getAssetsDictionaryList(context);
|
||||||
|
if (null == dictionaryList) return null;
|
||||||
|
String bestMatchName = null;
|
||||||
|
int bestMatchLevel = 0;
|
||||||
|
for (String dictionary : dictionaryList) {
|
||||||
|
final String dictLocale =
|
||||||
|
extractLocaleFromAssetsDictionaryFile(dictionary);
|
||||||
|
if (dictLocale == null) continue;
|
||||||
|
// assets files may contain the locale in lowercase, but dictionary headers usually
|
||||||
|
// have an upper case country code, so we compare lowercase here
|
||||||
|
final int matchLevel = LocaleUtils.getMatchLevel(dictLocale.toLowerCase(Locale.ENGLISH), locale.toLowerCase(Locale.ENGLISH));
|
||||||
|
if (LocaleUtils.isMatch(matchLevel) && matchLevel > bestMatchLevel) {
|
||||||
|
bestMatchName = dictionary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bestMatchName == null) return null;
|
||||||
|
|
||||||
|
// we have a match, now copy contents of the dictionary to cached word lists folder
|
||||||
|
final String bestMatchLocale = extractLocaleFromAssetsDictionaryFile(bestMatchName);
|
||||||
|
if (bestMatchLocale == null) return null;
|
||||||
|
File dictFile = new File(DictionaryInfoUtils.getCacheDirectoryForLocale(bestMatchLocale, context) +
|
||||||
|
File.separator + DictionaryInfoUtils.getMainDictFilename(bestMatchLocale));
|
||||||
|
try {
|
||||||
|
FileUtils.copyStreamToNewFile(
|
||||||
|
context.getAssets().open(ASSETS_DICTIONARY_FOLDER + File.separator + bestMatchName),
|
||||||
|
dictFile);
|
||||||
|
return dictFile;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "exception while looking for locale " + locale, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the locale for a dictionary file name stored in assets.
|
||||||
|
*
|
||||||
|
* Assumes file name main_[locale].dict
|
||||||
|
*
|
||||||
|
* Returns the locale, or null if file name does not match the pattern
|
||||||
|
*/
|
||||||
|
public static String extractLocaleFromAssetsDictionaryFile(final String dictionaryFileName) {
|
||||||
|
if (dictionaryFileName.startsWith(DictionaryInfoUtils.MAIN_DICT_PREFIX)
|
||||||
|
&& dictionaryFileName.endsWith(".dict")) {
|
||||||
|
return dictionaryFileName.substring(
|
||||||
|
DictionaryInfoUtils.MAIN_DICT_PREFIX.length(),
|
||||||
|
dictionaryFileName.lastIndexOf('.')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String[] getAssetsDictionaryList(final Context context) {
|
||||||
|
final String[] dictionaryList;
|
||||||
|
try {
|
||||||
|
dictionaryList = context.getAssets().list(ASSETS_DICTIONARY_FOLDER);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return dictionaryList;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,10 @@
|
||||||
package org.dslul.openboard.inputmethod.latin.common;
|
package org.dslul.openboard.inputmethod.latin.common;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.FilenameFilter;
|
import java.io.FilenameFilter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple class to help with removing directories recursively.
|
* A simple class to help with removing directories recursively.
|
||||||
|
@ -58,4 +61,19 @@ public class FileUtils {
|
||||||
toFile.delete();
|
toFile.delete();
|
||||||
return fromFile.renameTo(toFile);
|
return fromFile.renameTo(toFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void copyStreamToNewFile(InputStream in, File outfile) throws IOException {
|
||||||
|
File parentFile = outfile.getParentFile();
|
||||||
|
if (parentFile == null || (!parentFile.exists() && !parentFile.mkdirs())) {
|
||||||
|
throw new IOException("could not create parent folder");
|
||||||
|
}
|
||||||
|
FileOutputStream out = new FileOutputStream(outfile);
|
||||||
|
byte[] buf = new byte[1024];
|
||||||
|
int len;
|
||||||
|
while ((len = in.read(buf)) > 0) {
|
||||||
|
out.write(buf, 0, len);
|
||||||
|
}
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,329 @@
|
||||||
|
package org.dslul.openboard.inputmethod.latin.settings
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.AlertDialog
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.preference.Preference
|
||||||
|
import android.text.Html
|
||||||
|
import android.text.method.LinkMovementMethod
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants
|
||||||
|
import org.dslul.openboard.inputmethod.latin.BinaryDictionaryGetter
|
||||||
|
import org.dslul.openboard.inputmethod.latin.R
|
||||||
|
import org.dslul.openboard.inputmethod.latin.common.FileUtils
|
||||||
|
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils
|
||||||
|
import org.dslul.openboard.inputmethod.latin.utils.DialogUtils
|
||||||
|
import org.dslul.openboard.inputmethod.latin.utils.DictionaryInfoUtils
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class DictionarySettingsFragment : SubScreenFragment() {
|
||||||
|
|
||||||
|
// dict for which dialog is currently open (if any)
|
||||||
|
private var currentDictLocale: Locale? = null
|
||||||
|
private var currentDictState: Int? = null
|
||||||
|
|
||||||
|
private val cachedDictionaryFile by lazy { File(activity.cacheDir.path + File.separator + "temp_dict") }
|
||||||
|
private val currentDictExistsForUser get() = currentDictState == DICT_INTERNAL_AND_USER || currentDictState == DICT_USER_ONLY
|
||||||
|
private val currentDictExistsInternal get() = currentDictState == DICT_INTERNAL_AND_USER || currentDictState == DICT_INTERNAL_ONLY
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
addPreferencesFromResource(R.xml.additional_subtype_settings)
|
||||||
|
reloadDictionaries()
|
||||||
|
// + button to add dictionary
|
||||||
|
setHasOptionsMenu(true)
|
||||||
|
activity.actionBar?.setTitle(R.string.dictionary_settings_category)
|
||||||
|
}
|
||||||
|
|
||||||
|
// shows existing dictionaries as preferences
|
||||||
|
private fun reloadDictionaries() {
|
||||||
|
val screen = preferenceScreen ?: return
|
||||||
|
screen.removeAll()
|
||||||
|
val userDicts = mutableSetOf<Locale>()
|
||||||
|
val internalDicts = mutableSetOf<Locale>()
|
||||||
|
// get available dictionaries
|
||||||
|
// cached (internal in use and user dicts)
|
||||||
|
DictionaryInfoUtils.getCachedDirectoryList(activity)?.forEach { dir ->
|
||||||
|
if (!dir.isDirectory)
|
||||||
|
return@forEach
|
||||||
|
dir.list()?.forEach {
|
||||||
|
if (it.endsWith(USER_DICTIONARY_SUFFIX))
|
||||||
|
userDicts.add(dir.name.toLocale())
|
||||||
|
else if (it.startsWith(DictionaryInfoUtils.MAIN_DICT_PREFIX))
|
||||||
|
internalDicts.add(dir.name.toLocale())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// internal only
|
||||||
|
BinaryDictionaryGetter.getAssetsDictionaryList(activity)?.forEach { dictFile ->
|
||||||
|
BinaryDictionaryGetter.extractLocaleFromAssetsDictionaryFile(dictFile)?.let {
|
||||||
|
internalDicts.add(it.toLocale())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// first show user-added dictionaries
|
||||||
|
userDicts.sortedBy { it.displayName() }.forEach { dict ->
|
||||||
|
val pref = Preference(activity).apply {
|
||||||
|
title = dict.displayName()
|
||||||
|
setSummary(R.string.user_dictionary_summary)
|
||||||
|
setOnPreferenceClickListener {
|
||||||
|
// open dialog for update or delete / reset
|
||||||
|
currentDictLocale = dict
|
||||||
|
currentDictState = if (internalDicts.contains(dict)) DICT_INTERNAL_AND_USER else DICT_USER_ONLY
|
||||||
|
showUpdateDialog()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
screen.addPreference(pref)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: only show if language is actually used?
|
||||||
|
internalDicts.sortedBy { it.displayName() }.forEach { dict ->
|
||||||
|
if (userDicts.contains(dict)) return@forEach // don't show a second time
|
||||||
|
val pref = Preference(activity).apply {
|
||||||
|
title = dict.displayName()
|
||||||
|
setSummary(R.string.internal_dictionary_summary)
|
||||||
|
setOnPreferenceClickListener {
|
||||||
|
// open dialog for update, maybe disabling if i can make it work?
|
||||||
|
currentDictLocale = dict
|
||||||
|
currentDictState = DICT_INTERNAL_ONLY
|
||||||
|
showUpdateDialog()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
screen.addPreference(pref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showUpdateDialog() {
|
||||||
|
// -1: adding new dict, don't know where it may exist
|
||||||
|
// 0: user only -> offer delete
|
||||||
|
// 1: internal only -> only update (and maybe later: disable)
|
||||||
|
// 2: user and internal -> offer reset to internal
|
||||||
|
|
||||||
|
if (currentDictState == null) return
|
||||||
|
if (currentDictLocale == null && currentDictState != DICT_NEW)
|
||||||
|
return
|
||||||
|
|
||||||
|
val link = "<a href='$DICTIONARY_URL'>" +
|
||||||
|
resources.getString(R.string.dictionary_link_text) + "</a>"
|
||||||
|
val message = if (currentDictState == DICT_NEW)
|
||||||
|
Html.fromHtml(resources.getString(R.string.add_new_dictionary, link))
|
||||||
|
else
|
||||||
|
Html.fromHtml(resources.getString(R.string.update_dictionary, link))
|
||||||
|
val title = if (currentDictState == DICT_NEW) R.string.add_new_dictionary_title
|
||||||
|
else R.string.dictionary_settings_category
|
||||||
|
val updateButtonTitle = if (currentDictExistsForUser) R.string.update_dictionary_button
|
||||||
|
else R.string.user_dict_settings_add_menu_title
|
||||||
|
|
||||||
|
val builder = AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(activity))
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.setMessage(message)
|
||||||
|
.setTitle(title)
|
||||||
|
.setPositiveButton(updateButtonTitle) { _, _ ->
|
||||||
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||||
|
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
.setType("application/octet-stream")
|
||||||
|
startActivityForResult(intent, DICTIONARY_REQUEST_CODE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow removing dictionaries
|
||||||
|
if (currentDictExistsForUser) {
|
||||||
|
builder.setNeutralButton(if (currentDictExistsInternal) R.string.reset_dictionary else R.string.delete_dict) { _, _ ->
|
||||||
|
AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(activity))
|
||||||
|
.setTitle(R.string.remove_dictionary_title)
|
||||||
|
.setMessage(resources.getString(R.string.remove_dictionary_message, currentDictLocale?.displayName()))
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.setPositiveButton(R.string.delete_dict) { _,_ ->
|
||||||
|
currentDictLocale?.getUserDictFilenames()?.let { files ->
|
||||||
|
var parent: File? = null
|
||||||
|
files.forEach {
|
||||||
|
val f = File(it)
|
||||||
|
parent = f.parentFile
|
||||||
|
f.delete()
|
||||||
|
}
|
||||||
|
if (parent?.list()?.isEmpty() == true)
|
||||||
|
parent?.delete()
|
||||||
|
}
|
||||||
|
reloadDictionaries()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val dialog = builder.create()
|
||||||
|
dialog.show()
|
||||||
|
// make links in the HTML text work
|
||||||
|
(dialog.findViewById<View>(android.R.id.message) as TextView).movementMethod =
|
||||||
|
LinkMovementMethod.getInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
// copied from CustomInputStyleSettingsFragment
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
|
inflater.inflate(R.menu.add_style, menu)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
val value = TypedValue()
|
||||||
|
activity.theme.resolveAttribute(android.R.attr.colorForeground, value, true)
|
||||||
|
menu.findItem(R.id.action_add_style).icon?.setTint(value.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
val itemId = item.itemId
|
||||||
|
if (itemId == R.id.action_add_style) {
|
||||||
|
currentDictLocale = null
|
||||||
|
currentDictState = DICT_NEW
|
||||||
|
showUpdateDialog()
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
|
||||||
|
if (requestCode == DICTIONARY_REQUEST_CODE) onDictionaryFileSelected(resultCode, resultData)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onDictionaryFileSelected(resultCode: Int, resultData: Intent?) {
|
||||||
|
if (resultCode != Activity.RESULT_OK || resultData == null) {
|
||||||
|
onDictionaryLoadingError(R.string.dictionary_load_error.resString())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val uri = resultData.data ?: return onDictionaryLoadingError(R.string.dictionary_load_error.resString())
|
||||||
|
|
||||||
|
cachedDictionaryFile.delete()
|
||||||
|
try {
|
||||||
|
FileUtils.copyStreamToNewFile(
|
||||||
|
activity.contentResolver.openInputStream(uri),
|
||||||
|
cachedDictionaryFile
|
||||||
|
)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
onDictionaryLoadingError(R.string.dictionary_load_error.resString())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val newHeader = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(cachedDictionaryFile, 0, cachedDictionaryFile.length())
|
||||||
|
if (newHeader == null) {
|
||||||
|
cachedDictionaryFile.delete()
|
||||||
|
onDictionaryLoadingError(R.string.dictionary_file_error.resString())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val locale = newHeader.mLocaleString.toLocale()
|
||||||
|
if (currentDictLocale != null && locale != currentDictLocale) {
|
||||||
|
cachedDictionaryFile.delete()
|
||||||
|
onDictionaryLoadingError(resources.getString(R.string.dictionary_file_wrong_locale, locale.displayName(), currentDictLocale?.displayName()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// idString is content of 'dictionary' key, in format <type>:<locale>
|
||||||
|
val dictionaryType = newHeader.mIdString.substringBefore(":")
|
||||||
|
|
||||||
|
val userDictFile = File(locale.getUserDictFilename(dictionaryType))
|
||||||
|
// ask for user confirmation if it would be a version downgrade or if user pressed add new,
|
||||||
|
// but we already have a user dictionary for the same locale
|
||||||
|
val shouldAskMessageId = if (userDictFile.exists()) {
|
||||||
|
val oldHeader = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(userDictFile, 0, userDictFile.length())
|
||||||
|
if (oldHeader != null && oldHeader.mVersionString.toInt() > newHeader.mVersionString.toInt())
|
||||||
|
R.string.overwrite_old_dicitonary_messsage
|
||||||
|
else if (currentDictState == DICT_NEW && currentDictLocale == null)
|
||||||
|
R.string.replace_dictionary_message
|
||||||
|
else 0
|
||||||
|
} else 0
|
||||||
|
if (shouldAskMessageId != 0)
|
||||||
|
showConfirmReplaceDialog(locale, dictionaryType, shouldAskMessageId)
|
||||||
|
else
|
||||||
|
moveCachedFileToDictionaries(locale, dictionaryType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showConfirmReplaceDialog(locale: Locale, dictionaryType: String, messageId: Int) {
|
||||||
|
AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(activity))
|
||||||
|
.setTitle(R.string.replace_dictionary)
|
||||||
|
.setMessage(resources.getString(messageId, locale.displayName()))
|
||||||
|
.setCancelable(false)
|
||||||
|
.setNegativeButton(R.string.cancel, ) { _,_ ->
|
||||||
|
cachedDictionaryFile.delete()
|
||||||
|
}
|
||||||
|
.setPositiveButton(R.string.replace_dictionary) { _,_ ->
|
||||||
|
moveCachedFileToDictionaries(locale, dictionaryType)
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun moveCachedFileToDictionaries(locale: Locale, dictionaryType: String) {
|
||||||
|
val dictFile = File(locale.getUserDictFilename(dictionaryType))
|
||||||
|
if (!cachedDictionaryFile.renameTo(dictFile)) {
|
||||||
|
cachedDictionaryFile.delete()
|
||||||
|
onDictionaryLoadingError(R.string.dictionary_load_error.resString())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// success, now remove internal dictionary file if a main dictionary was added
|
||||||
|
if (dictionaryType == DictionaryInfoUtils.DEFAULT_MAIN_DICT)
|
||||||
|
File(locale.getInternalDictFilename()).delete()
|
||||||
|
|
||||||
|
// inform user about success
|
||||||
|
val successMessageForLocale = resources
|
||||||
|
.getString(R.string.dictionary_load_success, locale.displayName())
|
||||||
|
Toast.makeText(activity, successMessageForLocale, Toast.LENGTH_LONG).show()
|
||||||
|
|
||||||
|
// inform LatinIME about new dictionary
|
||||||
|
val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)
|
||||||
|
activity.sendBroadcast(newDictBroadcast)
|
||||||
|
reloadDictionaries()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onDictionaryLoadingError(message: String) {
|
||||||
|
AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(activity))
|
||||||
|
.setNegativeButton(android.R.string.ok, null)
|
||||||
|
.setMessage(message)
|
||||||
|
.setTitle("loading error")
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Locale.getUserDictFilename(dictionaryType: String) =
|
||||||
|
DictionaryInfoUtils.getCacheDirectoryForLocale(this.toString(), activity) + File.separator + dictionaryType + "_" + USER_DICTIONARY_SUFFIX
|
||||||
|
|
||||||
|
private fun Locale.getUserDictFilenames(): List<String> {
|
||||||
|
val dicts = mutableListOf<String>()
|
||||||
|
val p = DictionaryInfoUtils.getCacheDirectoryForLocale(this.toString(), activity)
|
||||||
|
DictionaryInfoUtils.getCachedDirectoryList(activity)?.forEach { dir ->
|
||||||
|
if (!dir.isDirectory)
|
||||||
|
return@forEach
|
||||||
|
dir.list()?.forEach {
|
||||||
|
if (it.endsWith(USER_DICTIONARY_SUFFIX))
|
||||||
|
dicts.add(p + File.separator + it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dicts
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Locale.getInternalDictFilename() =
|
||||||
|
DictionaryInfoUtils.getCacheDirectoryForLocale(this.toString(), activity) + File.separator + DictionaryInfoUtils.getMainDictFilename(this.toString())
|
||||||
|
|
||||||
|
private fun String.displayName() = LocaleUtils.constructLocaleFromString(this).displayName()
|
||||||
|
|
||||||
|
private fun String.toLocale() = LocaleUtils.constructLocaleFromString(this)
|
||||||
|
|
||||||
|
private fun Locale.displayName() = getDisplayName(resources.configuration.locale)
|
||||||
|
|
||||||
|
private fun Int.resString() = resources.getString(this)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val DICTIONARY_REQUEST_CODE = 96834
|
||||||
|
private const val DICTIONARY_URL =
|
||||||
|
"https://github.com/Helium314/openboard/dictionaries/dict"
|
||||||
|
private const val USER_DICTIONARY_SUFFIX = "user.dict"
|
||||||
|
|
||||||
|
private const val DICT_INTERNAL_AND_USER = 2
|
||||||
|
private const val DICT_INTERNAL_ONLY = 1
|
||||||
|
private const val DICT_USER_ONLY = 0
|
||||||
|
private const val DICT_NEW = -1
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -65,6 +65,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
||||||
public static final String PREF_VOICE_INPUT_KEY = "pref_voice_input_key";
|
public static final String PREF_VOICE_INPUT_KEY = "pref_voice_input_key";
|
||||||
public static final String PREF_CLIPBOARD_CLIPBOARD_KEY = "pref_clipboard_clipboard_key";
|
public static final String PREF_CLIPBOARD_CLIPBOARD_KEY = "pref_clipboard_clipboard_key";
|
||||||
public static final String PREF_EDIT_PERSONAL_DICTIONARY = "edit_personal_dictionary";
|
public static final String PREF_EDIT_PERSONAL_DICTIONARY = "edit_personal_dictionary";
|
||||||
|
public static final String PREF_ADD_DICTIONARY = "add_dictionary";
|
||||||
public static final String PREF_AUTO_CORRECTION = "pref_key_auto_correction";
|
public static final String PREF_AUTO_CORRECTION = "pref_key_auto_correction";
|
||||||
public static final String PREF_AUTO_CORRECTION_CONFIDENCE = "pref_key_auto_correction_confidence";
|
public static final String PREF_AUTO_CORRECTION_CONFIDENCE = "pref_key_auto_correction_confidence";
|
||||||
// PREF_SHOW_SUGGESTIONS_SETTING_OBSOLETE is obsolete. Use PREF_SHOW_SUGGESTIONS instead.
|
// PREF_SHOW_SUGGESTIONS_SETTING_OBSOLETE is obsolete. Use PREF_SHOW_SUGGESTIONS instead.
|
||||||
|
|
|
@ -52,8 +52,9 @@ import javax.annotation.Nullable;
|
||||||
public class DictionaryInfoUtils {
|
public class DictionaryInfoUtils {
|
||||||
private static final String TAG = DictionaryInfoUtils.class.getSimpleName();
|
private static final String TAG = DictionaryInfoUtils.class.getSimpleName();
|
||||||
public static final String RESOURCE_PACKAGE_NAME = R.class.getPackage().getName();
|
public static final String RESOURCE_PACKAGE_NAME = R.class.getPackage().getName();
|
||||||
private static final String DEFAULT_MAIN_DICT = "main";
|
public static final String DEFAULT_MAIN_DICT = "main";
|
||||||
private static final String MAIN_DICT_PREFIX = "main_";
|
public static final String MAIN_DICT_PREFIX = DEFAULT_MAIN_DICT + "_";
|
||||||
|
private static final String DICTIONARY_CATEGORY_SEPARATOR_EXPRESSION = "[" + BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + "_]";
|
||||||
private static final String DECODER_DICT_SUFFIX = DecoderSpecificConstants.DECODER_DICT_SUFFIX;
|
private static final String DECODER_DICT_SUFFIX = DecoderSpecificConstants.DECODER_DICT_SUFFIX;
|
||||||
// 6 digits - unicode is limited to 21 bits
|
// 6 digits - unicode is limited to 21 bits
|
||||||
private static final int MAX_HEX_DIGITS_FOR_CODEPOINT = 6;
|
private static final int MAX_HEX_DIGITS_FOR_CODEPOINT = 6;
|
||||||
|
@ -151,7 +152,7 @@ public class DictionaryInfoUtils {
|
||||||
/**
|
/**
|
||||||
* Helper method to get the top level cache directory.
|
* Helper method to get the top level cache directory.
|
||||||
*/
|
*/
|
||||||
private static String getWordListCacheDirectory(final Context context) {
|
public static String getWordListCacheDirectory(final Context context) {
|
||||||
return context.getFilesDir() + File.separator + "dicts";
|
return context.getFilesDir() + File.separator + "dicts";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,10 +213,12 @@ public class DictionaryInfoUtils {
|
||||||
@Nullable
|
@Nullable
|
||||||
public static String getCategoryFromFileName(@Nonnull final String fileName) {
|
public static String getCategoryFromFileName(@Nonnull final String fileName) {
|
||||||
final String id = getWordListIdFromFileName(fileName);
|
final String id = getWordListIdFromFileName(fileName);
|
||||||
final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR);
|
final String[] idArray = id.split(DICTIONARY_CATEGORY_SEPARATOR_EXPRESSION);
|
||||||
// An id is supposed to be in format category:locale, so splitting on the separator
|
// An id is supposed to be in format category:locale, so splitting on the separator
|
||||||
// should yield a 2-elements array
|
// should yield a 2-elements array
|
||||||
if (2 != idArray.length) {
|
// Also allow '_' as separator, this is ok for locales like pt_br because
|
||||||
|
// we're interested in the part before first separator anyway
|
||||||
|
if (1 == idArray.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return idArray[0];
|
return idArray[0];
|
||||||
|
@ -225,7 +228,7 @@ public class DictionaryInfoUtils {
|
||||||
* Find out the cache directory associated with a specific locale.
|
* Find out the cache directory associated with a specific locale.
|
||||||
*/
|
*/
|
||||||
public static String getCacheDirectoryForLocale(final String locale, final Context context) {
|
public static String getCacheDirectoryForLocale(final String locale, final Context context) {
|
||||||
final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale);
|
final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale).toLowerCase(Locale.ENGLISH);
|
||||||
final String absoluteDirectoryName = getWordListCacheDirectory(context) + File.separator
|
final String absoluteDirectoryName = getWordListCacheDirectory(context) + File.separator
|
||||||
+ relativeDirectoryName;
|
+ relativeDirectoryName;
|
||||||
final File directory = new File(absoluteDirectoryName);
|
final File directory = new File(absoluteDirectoryName);
|
||||||
|
@ -238,10 +241,12 @@ public class DictionaryInfoUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isMainWordListId(final String id) {
|
public static boolean isMainWordListId(final String id) {
|
||||||
final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR);
|
final String[] idArray = id.split(DICTIONARY_CATEGORY_SEPARATOR_EXPRESSION);
|
||||||
// An id is supposed to be in format category:locale, so splitting on the separator
|
// An id is supposed to be in format category:locale, so splitting on the separator
|
||||||
// should yield a 2-elements array
|
// should yield a 2-elements array
|
||||||
if (2 != idArray.length) {
|
// Also allow '_' as separator, this is ok for locales like pt_br because
|
||||||
|
// we're interested in the part before first separator anyway
|
||||||
|
if (1 == idArray.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
|
return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
|
||||||
|
@ -318,6 +323,10 @@ public class DictionaryInfoUtils {
|
||||||
BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale.toString().toLowerCase();
|
BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale.toString().toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getMainDictFilename(@Nonnull final String locale) {
|
||||||
|
return MAIN_DICT_PREFIX + locale.toLowerCase(Locale.ENGLISH) + ".dict";
|
||||||
|
}
|
||||||
|
|
||||||
public static DictionaryHeader getDictionaryFileHeaderOrNull(final File file,
|
public static DictionaryHeader getDictionaryFileHeaderOrNull(final File file,
|
||||||
final long offset, final long length) {
|
final long offset, final long length) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -467,6 +467,44 @@ disposition rather than other common dispositions for Latin languages. [CHAR LIM
|
||||||
<string name="dictionary_settings_title">Add-on dictionaries</string>
|
<string name="dictionary_settings_title">Add-on dictionaries</string>
|
||||||
<!-- Title for the prompt dialog which informs the user that a dictionary is available for the current language and asks to decide whether to download it over 3g -->
|
<!-- Title for the prompt dialog which informs the user that a dictionary is available for the current language and asks to decide whether to download it over 3g -->
|
||||||
<string name="dictionary_settings_summary">Settings for dictionaries</string>
|
<string name="dictionary_settings_summary">Settings for dictionaries</string>
|
||||||
|
<!-- Title for dictionaries preference screen -->
|
||||||
|
<string name="dictionary_settings_category">Dictionaries</string>
|
||||||
|
<!-- Summary text for dictionary added by user -->
|
||||||
|
<string name="user_dictionary_summary">User-added dictionary</string>
|
||||||
|
<!-- Summary text for built-in dictionary -->
|
||||||
|
<string name="internal_dictionary_summary">Built-in dictionary</string>
|
||||||
|
<!-- Title for the adding new user dictionary dialog -->
|
||||||
|
<string name="add_new_dictionary_title">"Add dictionary from file"</string>
|
||||||
|
<!-- Button text for updating a dictionary -->
|
||||||
|
<string name="update_dictionary_button">"Update"</string>
|
||||||
|
<!-- Message for user dictionary replacement dialog -->
|
||||||
|
<string name="replace_dictionary_message">"Really replace existing user-added dictionary for %s?"</string>
|
||||||
|
<!-- Message when trying to replace user dictionary with older version -->
|
||||||
|
<string name="overwrite_old_dicitonary_messsage">"New dictionary file contains older version code than current file. Really replace current dictionary for %s?"</string>
|
||||||
|
<!-- Title and confirm button text for user dictionary replacement dialog -->
|
||||||
|
<string name="replace_dictionary">"Replace dictionary"</string>
|
||||||
|
<!-- Message for user dictionary remove dialog -->
|
||||||
|
<string name="remove_dictionary_message">"Really remove user-added dictionaries for %s?"</string>
|
||||||
|
<!-- Title for user dictionary remove dialog -->
|
||||||
|
<string name="remove_dictionary_title">"Remove dictionary"</string>
|
||||||
|
<!-- Button text in user dictionary remove dialog for resetting to default -->
|
||||||
|
<string name="reset_dictionary">"Reset to default"</string>
|
||||||
|
<!-- Message for the user dictionary selection dialog. This string will be interpreted as HTML -->
|
||||||
|
<string name="update_dictionary">"Select a dictionary to replace the current main dictionary. Dictionaries can be downloaded at %s."</string>
|
||||||
|
<!-- Message for the user dictionary selection dialog. This string will be interpreted as HTML -->
|
||||||
|
<string name="add_new_dictionary">"Select a new dictionary to be added to the list. Dictionaries can be downloaded at the %s."</string>
|
||||||
|
<!-- Title of the link to the download page inserted into selection message (above) -->
|
||||||
|
<string name="dictionary_link_text">"project repository"</string>
|
||||||
|
<!-- Button text for dictionary file selection -->
|
||||||
|
<string name="load_dictionary_file">"Load dictionary"</string>
|
||||||
|
<!-- Toast text shown when dictionary file for a locale was added successfully -->
|
||||||
|
<string name="dictionary_load_success">"Dictionary for %s added"</string>
|
||||||
|
<!-- Text shown when dictionary file could not be read -->
|
||||||
|
<string name="dictionary_file_error">"Error: Selected file is not a valid dictionary file"</string>
|
||||||
|
<!-- Text shown when dictionary file is not for the selected locale -->
|
||||||
|
<string name="dictionary_file_wrong_locale">"Error: Selected file is for %1$s, but %2$s was expected"</string>
|
||||||
|
<!-- Text shown on other errors when loading dictionary file -->
|
||||||
|
<string name="dictionary_load_error">"Error loading dictionary file"</string>
|
||||||
<!-- Name of the user dictionaries settings category -->
|
<!-- Name of the user dictionaries settings category -->
|
||||||
<string name="user_dictionaries">User dictionaries</string>
|
<string name="user_dictionaries">User dictionaries</string>
|
||||||
<!-- Name for the "user dictionary" preference item when there is only one -->
|
<!-- Name for the "user dictionary" preference item when there is only one -->
|
||||||
|
|
|
@ -36,6 +36,11 @@
|
||||||
android:title="@string/settings_screen_correction"
|
android:title="@string/settings_screen_correction"
|
||||||
android:key="screen_correction"
|
android:key="screen_correction"
|
||||||
android:icon="@drawable/ic_settings_correction"/>
|
android:icon="@drawable/ic_settings_correction"/>
|
||||||
|
<PreferenceScreen
|
||||||
|
android:fragment="org.dslul.openboard.inputmethod.latin.settings.DictionarySettingsFragment"
|
||||||
|
android:title="@string/dictionary_settings_category"
|
||||||
|
android:key="add_dictionary"
|
||||||
|
android:icon="@drawable/ic_notify_dictionary"/>
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
android:fragment="org.dslul.openboard.inputmethod.latin.settings.AdvancedSettingsFragment"
|
android:fragment="org.dslul.openboard.inputmethod.latin.settings.AdvancedSettingsFragment"
|
||||||
android:title="@string/settings_screen_advanced"
|
android:title="@string/settings_screen_advanced"
|
||||||
|
|
BIN
dictionaries/dict/emoji_fr.dict
Normal file
BIN
dictionaries/dict/emoji_fr.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_bg.dict
Normal file
BIN
dictionaries/dict/main_bg.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_cs.dict
Normal file
BIN
dictionaries/dict/main_cs.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_da.dict
Normal file
BIN
dictionaries/dict/main_da.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_de.dict
Normal file
BIN
dictionaries/dict/main_de.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_el.dict
Normal file
BIN
dictionaries/dict/main_el.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_en_au.dict
Normal file
BIN
dictionaries/dict/main_en_au.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_en_gb.dict
Normal file
BIN
dictionaries/dict/main_en_gb.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_en_us.dict
Normal file
BIN
dictionaries/dict/main_en_us.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_eo.dict
Normal file
BIN
dictionaries/dict/main_eo.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_es.dict
Normal file
BIN
dictionaries/dict/main_es.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_fi.dict
Normal file
BIN
dictionaries/dict/main_fi.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_fr.dict
Normal file
BIN
dictionaries/dict/main_fr.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_hr.dict
Normal file
BIN
dictionaries/dict/main_hr.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_hu.dict
Normal file
BIN
dictionaries/dict/main_hu.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_hy.dict
Normal file
BIN
dictionaries/dict/main_hy.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_it.dict
Normal file
BIN
dictionaries/dict/main_it.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_iw.dict
Normal file
BIN
dictionaries/dict/main_iw.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_ka.dict
Normal file
BIN
dictionaries/dict/main_ka.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_lb.dict
Normal file
BIN
dictionaries/dict/main_lb.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_lt.dict
Normal file
BIN
dictionaries/dict/main_lt.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_lv.dict
Normal file
BIN
dictionaries/dict/main_lv.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_nb.dict
Normal file
BIN
dictionaries/dict/main_nb.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_nl.dict
Normal file
BIN
dictionaries/dict/main_nl.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_pl.dict
Normal file
BIN
dictionaries/dict/main_pl.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_pt_br.dict
Normal file
BIN
dictionaries/dict/main_pt_br.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_pt_pt.dict
Normal file
BIN
dictionaries/dict/main_pt_pt.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_ro.dict
Normal file
BIN
dictionaries/dict/main_ro.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_ru.dict
Normal file
BIN
dictionaries/dict/main_ru.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_sl.dict
Normal file
BIN
dictionaries/dict/main_sl.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_sr.dict
Normal file
BIN
dictionaries/dict/main_sr.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_sv.dict
Normal file
BIN
dictionaries/dict/main_sv.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_tr.dict
Normal file
BIN
dictionaries/dict/main_tr.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_uk.dict
Normal file
BIN
dictionaries/dict/main_uk.dict
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue