1
0
Fork 0

Dictionary import (#586)

This commit is contained in:
Dimo Karaivanov 2024-07-31 11:52:05 +03:00 committed by GitHub
parent b9c4d71968
commit 4c7c941e44
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 858 additions and 156 deletions

View file

@ -1,4 +1,4 @@
package io.github.sspanak.tt9.db.exporter;
package io.github.sspanak.tt9.db.customWords;
import android.app.Activity;
import android.content.ContentResolver;
@ -16,19 +16,13 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import io.github.sspanak.tt9.util.ConsumerCompat;
import io.github.sspanak.tt9.util.Permissions;
public abstract class AbstractExporter {
public abstract class AbstractExporter extends AbstractFileProcessor {
protected static String FILE_EXTENSION = ".csv";
protected static String MIME_TYPE = "text/csv";
protected Runnable failureHandler;
protected Runnable startHandler;
protected ConsumerCompat<String> successHandler;
private Thread processThread;
private String outputFile;
private String statusMessage = "";
private void writeAndroid10(Activity activity) throws Exception {
@ -107,18 +101,6 @@ public abstract class AbstractExporter {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ? Environment.DIRECTORY_DOCUMENTS : Environment.DIRECTORY_DOWNLOADS;
}
protected void sendFailure() {
if (failureHandler != null) {
failureHandler.run();
}
}
protected void sendStart(@NonNull String message) {
if (startHandler != null) {
statusMessage = message;
startHandler.run();
}
}
protected void sendSuccess() {
if (successHandler != null) {
@ -126,39 +108,7 @@ public abstract class AbstractExporter {
}
}
public boolean export(@NonNull Activity activity) {
if (isRunning()) {
return false;
}
processThread = new Thread(() -> exportSync(activity));
processThread.start();
return true;
}
public boolean isRunning() {
return processThread != null && processThread.isAlive();
}
public String getStatusMessage() {
return statusMessage;
}
public void setFailureHandler(Runnable handler) {
failureHandler = handler;
}
public void setStartHandler(Runnable handler) {
startHandler = handler;
}
public void setSuccessHandler(ConsumerCompat<String> handler) {
successHandler = handler;
}
abstract protected void exportSync(Activity activity);
@NonNull abstract protected String generateFileName();
@NonNull abstract protected byte[] getFileContents(Activity activity) throws Exception;
}

View file

@ -0,0 +1,63 @@
package io.github.sspanak.tt9.db.customWords;
import android.app.Activity;
import androidx.annotation.NonNull;
import io.github.sspanak.tt9.util.ConsumerCompat;
abstract public class AbstractFileProcessor {
protected Runnable failureHandler;
protected Runnable startHandler;
protected ConsumerCompat<String> successHandler;
private Thread processThread;
protected String statusMessage = "";
public boolean isRunning() {
return processThread != null && processThread.isAlive();
}
public String getStatusMessage() {
return statusMessage;
}
protected void sendFailure() {
if (failureHandler != null) {
failureHandler.run();
}
}
protected void sendStart(@NonNull String message) {
if (startHandler != null) {
statusMessage = message;
startHandler.run();
}
}
public void setFailureHandler(Runnable handler) {
failureHandler = handler;
}
public void setStartHandler(Runnable handler) {
startHandler = handler;
}
public void setSuccessHandler(ConsumerCompat<String> handler) {
successHandler = handler;
}
public boolean run(@NonNull Activity activity) {
if (isRunning()) {
return false;
}
processThread = new Thread(() -> runSync(activity));
processThread.start();
return true;
}
abstract protected void sendSuccess();
abstract protected void runSync(Activity activity);
}

View file

@ -1,4 +1,4 @@
package io.github.sspanak.tt9.db.exporter;
package io.github.sspanak.tt9.db.customWords;
import android.app.Activity;
import android.database.sqlite.SQLiteDatabase;
@ -23,7 +23,7 @@ public class CustomWordsExporter extends AbstractExporter {
}
@Override
protected void exportSync(Activity activity) {
protected void runSync(Activity activity) {
try {
sendStart(activity.getString(R.string.dictionary_export_generating_csv));
write(activity);

View file

@ -0,0 +1,213 @@
package io.github.sspanak.tt9.db.customWords;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import androidx.annotation.NonNull;
import java.io.BufferedReader;
import java.io.IOException;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.entities.CustomWord;
import io.github.sspanak.tt9.db.entities.CustomWordFile;
import io.github.sspanak.tt9.db.sqlite.InsertOps;
import io.github.sspanak.tt9.db.sqlite.ReadOps;
import io.github.sspanak.tt9.db.sqlite.SQLiteOpener;
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
import io.github.sspanak.tt9.util.ConsumerCompat;
import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.util.Timer;
public class CustomWordsImporter extends AbstractFileProcessor {
private static CustomWordsImporter self;
private ConsumerCompat<Integer> progressHandler;
private ConsumerCompat<String> failureHandler;
private final Context context;
private CustomWordFile file;
private final Resources resources;
private SQLiteOpener sqlite;
private long lastProgressUpdate = 0;
public static CustomWordsImporter getInstance(Context context) {
if (self == null) {
self = new CustomWordsImporter(context);
}
return self;
}
private CustomWordsImporter(Context context) {
super();
this.context = context;
this.resources = context.getResources();
}
public void setProgressHandler(ConsumerCompat<Integer> handler) {
progressHandler = handler;
}
public void setFailureHandler(ConsumerCompat<String> handler) {
failureHandler = handler;
}
private void sendProgress(int progress) {
long now = System.currentTimeMillis();
if (lastProgressUpdate + SettingsStore.DICTIONARY_IMPORT_PROGRESS_UPDATE_TIME < now) {
progressHandler.accept(progress);
lastProgressUpdate = now;
}
}
@Override
protected void sendSuccess() {
if (successHandler != null) {
successHandler.accept(file.getName());
}
}
private void sendFailure(String errorMessage) {
if (failureHandler != null) {
failureHandler.accept(errorMessage);
}
}
public boolean run(@NonNull Activity activity, @NonNull CustomWordFile file) {
this.file = file;
return super.run(activity);
}
@Override
protected void runSync(Activity activity) {
Timer.start(getClass().getSimpleName());
sendStart(resources.getString(R.string.dictionary_import_running));
if (isFileValid() && isThereRoomForMoreWords() && insertWords()) {
sendSuccess();
Logger.i(getClass().getSimpleName(), "Imported " + file.getName() + " in " + Timer.get(getClass().getSimpleName()) + " ms");
} else {
Logger.e(getClass().getSimpleName(), "Failed to import " + file.getName());
}
}
private boolean openDb() {
sqlite = SQLiteOpener.getInstance(context);
if (sqlite.getDb() != null) {
return true;
}
Logger.e(getClass().getSimpleName(), "Could not open database");
sendFailure(resources.getString(R.string.dictionary_import_failed));
return false;
}
private boolean insertWords() {
ReadOps readOps = new ReadOps();
int ignoredWords = 0;
int lineCount = 1;
try (BufferedReader reader = file.getReader()) {
sqlite.beginTransaction();
for (String line; (line = reader.readLine()) != null; lineCount++) {
if (!isLineCountValid(lineCount)) {
sqlite.failTransaction();
return false;
}
CustomWord customWord = createCustomWord(line, lineCount);
if (customWord == null) {
sqlite.failTransaction();
return false;
}
if (readOps.exists(sqlite.getDb(), customWord.language, customWord.word)) {
ignoredWords++;
} else {
InsertOps.insertCustomWord(sqlite.getDb(), customWord.language, customWord.sequence, customWord.word);
}
if (file.getSize() > 20) {
sendProgress(lineCount * 100 / file.getSize());
}
}
sqlite.finishTransaction();
} catch (IOException e) {
sqlite.failTransaction();
Logger.e(getClass().getSimpleName(), "Error opening the file. " + e.getMessage());
sendFailure(resources.getString(R.string.dictionary_import_error_cannot_read_file));
return false;
}
if (ignoredWords > 0) {
Logger.i(getClass().getSimpleName(), "Skipped " + ignoredWords + " word(s) that are already in the dictionary.");
}
return true;
}
private boolean isFileValid() {
if (file != null) {
return true;
}
Logger.e(getClass().getSimpleName(), "Can not read a NULL file");
sendFailure(resources.getString(R.string.dictionary_import_error_cannot_read_file));
return false;
}
private boolean isThereRoomForMoreWords() {
if (!openDb()) {
return false;
}
if ((new ReadOps()).countCustomWords(sqlite.getDb()) > SettingsStore.CUSTOM_WORDS_MAX) {
sendFailure(resources.getString(R.string.dictionary_import_error_too_many_words));
return false;
}
return true;
}
private boolean isLineCountValid(int lineCount) {
if (lineCount <= SettingsStore.CUSTOM_WORDS_IMPORT_MAX_LINES) {
return true;
}
sendFailure(resources.getString(R.string.dictionary_import_error_file_too_long, SettingsStore.CUSTOM_WORDS_IMPORT_MAX_LINES));
return false;
}
private CustomWord createCustomWord(String line, int lineCount) {
try {
return new CustomWord(
CustomWordFile.getWord(line),
CustomWordFile.getLanguage(context, line)
);
} catch (Exception e) {
String linePreview = line.length() > 50 ? line.substring(0, 50) + "..." : line;
sendFailure(resources.getString(R.string.dictionary_import_error_malformed_line, linePreview, lineCount));
return null;
}
}
}

View file

@ -1,4 +1,4 @@
package io.github.sspanak.tt9.db.exporter;
package io.github.sspanak.tt9.db.customWords;
import android.app.Activity;
import android.database.sqlite.SQLiteDatabase;
@ -31,7 +31,7 @@ public class DictionaryExporter extends AbstractExporter {
}
@Override
protected void exportSync(Activity activity) {
protected void runSync(Activity activity) {
if (languages == null || languages.isEmpty()) {
Logger.d(LOG_TAG, "Nothing to do");
return;

View file

@ -1,4 +1,4 @@
package io.github.sspanak.tt9.db.exporter;
package io.github.sspanak.tt9.db.customWords;
import android.app.Activity;
@ -60,7 +60,7 @@ public class LogcatExporter extends AbstractExporter {
@Override
protected void exportSync(Activity activity) {
protected void runSync(Activity activity) {
try {
sendStart("Exporting logs...");
write(activity);

View file

@ -5,7 +5,6 @@ import android.content.Context;
import androidx.annotation.NonNull;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.languages.Language;
public class AddWordResult {
public static final int CODE_SUCCESS = 0;

View file

@ -0,0 +1,26 @@
package io.github.sspanak.tt9.db.entities;
import androidx.annotation.NonNull;
import io.github.sspanak.tt9.languages.NaturalLanguage;
import io.github.sspanak.tt9.languages.exceptions.InvalidLanguageCharactersException;
public class CustomWord {
public final NaturalLanguage language;
public final String word;
public final String sequence;
public CustomWord(@NonNull String word, NaturalLanguage language) throws InvalidLanguageCharactersException, IllegalArgumentException {
if (word.isEmpty() || language == null) {
throw new IllegalArgumentException("Word and language must be provided.");
}
this.word = word;
this.language = language;
this.sequence = language.getDigitSequenceForWord(word);
if (sequence.contains("1") || sequence.contains("0")) {
throw new IllegalArgumentException("Custom word: '" + word + "' contains punctuation.");
}
}
}

View file

@ -0,0 +1,89 @@
package io.github.sspanak.tt9.db.entities;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import io.github.sspanak.tt9.languages.LanguageCollection;
import io.github.sspanak.tt9.languages.NaturalLanguage;
import io.github.sspanak.tt9.util.Logger;
public class CustomWordFile {
public static final String MIME_TYPE = "text/*"; // for some reason, text/csv does not work as a filter when browsing
private final ContentResolver contentResolver;
private final Uri fileUri;
private int size = -1;
public CustomWordFile(Uri fileUri, @NonNull ContentResolver contentResolver) {
this.contentResolver = contentResolver;
this.fileUri = fileUri;
}
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(contentResolver.openInputStream(fileUri)));
}
public String getName() {
return fileUri.getLastPathSegment();
}
public boolean exists() {
try {
return getReader() != null;
} catch (IOException e) {
return false;
}
}
public int getSize() {
if (size < 0) {
calculateSize();
}
return size;
}
private void calculateSize() {
size = 0;
try {
BufferedReader reader = getReader();
while (reader.readLine() != null) {
size++;
}
} catch (IOException e) {
Logger.w(getClass().getSimpleName(), "Failed to read file size. " + e.getMessage());
}
}
public static NaturalLanguage getLanguage(@NonNull Context context, String line) {
if (line == null) {
return null;
}
String[] parts = WordFile.splitLine(line);
if (parts == null || parts.length < 2) {
return null;
}
try {
return LanguageCollection.getLanguage(context, Integer.parseInt(parts[1]));
} catch (NumberFormatException e) {
return null;
}
}
@NonNull public static String getWord(String line) {
String[] parts = WordFile.splitLine(line);
return parts != null && parts.length > 0 ? parts[0] : "";
}
}

View file

@ -11,7 +11,6 @@ import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import io.github.sspanak.tt9.BuildConfig;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
import io.github.sspanak.tt9.util.Logger;

View file

@ -66,6 +66,11 @@ public class ReadOps {
}
public long countCustomWords(@NonNull SQLiteDatabase db) {
return CompiledQueryCache.simpleQueryForLong(db, "SELECT COUNT(*) FROM " + Tables.CUSTOM_WORDS, 0);
}
public ArrayList<String> getCustomWords(@NonNull SQLiteDatabase db, @NonNull Language language, @NonNull String wordFilter) {
ArrayList<String> words = new ArrayList<>();

View file

@ -3,88 +3,30 @@ package io.github.sspanak.tt9.preferences.items;
import androidx.preference.Preference;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.exporter.AbstractExporter;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.ui.notifications.DictionaryProgressNotification;
abstract public class ItemExportAbstract extends ItemClickable {
final protected PreferencesActivity activity;
final private Runnable onStart;
final private Runnable onFinish;
abstract public class ItemExportAbstract extends ItemProcessCustomWordsAbstract {
public ItemExportAbstract(Preference item, PreferencesActivity activity, Runnable onStart, Runnable onFinish) {
super(item);
this.activity = activity;
this.onStart = onStart;
this.onFinish = onFinish;
AbstractExporter exporter = getExporter();
exporter.setFailureHandler(() -> onFinishExporting(null));
exporter.setStartHandler(() -> activity.runOnUiThread(this::setLoadingStatus));
exporter.setSuccessHandler(this::onFinishExporting);
refreshStatus();
super(item, activity, onStart, onFinish);
}
abstract protected AbstractExporter getExporter();
public ItemExportAbstract refreshStatus() {
if (item != null) {
if (getExporter().isRunning()) {
setLoadingStatus();
} else {
setReadyStatus();
}
}
return this;
}
@Override
protected boolean onClick(Preference p) {
setLoadingStatus();
if (!onStartExporting()) {
setReadyStatus();
}
return true;
protected String getFailureTitle() {
return activity.getString(R.string.dictionary_export_failed);
}
abstract protected boolean onStartExporting();
protected void onFinishExporting(String outputFile) {
activity.runOnUiThread(() -> {
setReadyStatus();
if (outputFile == null) {
DictionaryProgressNotification.getInstance(activity).showError(
activity.getString(R.string.dictionary_export_failed),
activity.getString(R.string.dictionary_export_failed_more_info)
);
} else {
DictionaryProgressNotification.getInstance(activity).showMessage(
activity.getString(R.string.dictionary_export_finished),
activity.getString(R.string.dictionary_export_finished_more_info, outputFile),
activity.getString(R.string.dictionary_export_finished_more_info, outputFile)
);
}
});
@Override
protected String getFailureMessage() {
return activity.getString(R.string.dictionary_export_failed_more_info);
}
protected void setLoadingStatus() {
if (onStart != null) onStart.run();
disable();
String loadingMessage = getExporter().getStatusMessage();
item.setSummary(loadingMessage);
DictionaryProgressNotification.getInstance(activity).showLoadingMessage(loadingMessage, "");
@Override
protected String getSuccessTitle() {
return activity.getString(R.string.dictionary_export_finished);
}
public void setReadyStatus() {
enable();
if (onFinish != null) onFinish.run();
@Override
protected String getSuccessMessage(String fileName) {
return activity.getString(R.string.dictionary_export_finished_more_info, fileName);
}
}

View file

@ -0,0 +1,97 @@
package io.github.sspanak.tt9.preferences.items;
import androidx.preference.Preference;
import io.github.sspanak.tt9.db.customWords.AbstractFileProcessor;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.ui.notifications.DictionaryProgressNotification;
abstract public class ItemProcessCustomWordsAbstract extends ItemClickable {
final protected PreferencesActivity activity;
final private Runnable onStart;
final private Runnable onFinish;
public ItemProcessCustomWordsAbstract(Preference item, PreferencesActivity activity, Runnable onStart, Runnable onFinish) {
super(item);
this.activity = activity;
this.onStart = onStart;
this.onFinish = onFinish;
AbstractFileProcessor processor = getProcessor();
processor.setFailureHandler(() -> onFinishProcessing(null));
processor.setStartHandler(() -> activity.runOnUiThread(this::setLoadingStatus));
processor.setSuccessHandler(this::onFinishProcessing);
refreshStatus();
}
abstract protected AbstractFileProcessor getProcessor();
public ItemProcessCustomWordsAbstract refreshStatus() {
if (item != null) {
if (getProcessor().isRunning()) {
setLoadingStatus();
} else {
setReadyStatus();
}
}
return this;
}
@Override
protected boolean onClick(Preference p) {
setLoadingStatus();
if (!onStartProcessing()) {
setReadyStatus();
}
return true;
}
abstract protected boolean onStartProcessing();
protected void onFinishProcessing(String fileName) {
activity.runOnUiThread(() -> {
setReadyStatus();
if (fileName == null) {
DictionaryProgressNotification.getInstance(activity).showError(
getFailureTitle(),
getFailureMessage()
);
} else {
DictionaryProgressNotification.getInstance(activity).showMessage(
getSuccessTitle(),
getSuccessMessage(fileName),
getSuccessMessage(fileName)
);
}
});
}
abstract protected String getFailureMessage();
abstract protected String getFailureTitle();
abstract protected String getSuccessMessage(String fileName);
abstract protected String getSuccessTitle();
protected void setLoadingStatus() {
if (onStart != null) onStart.run();
disable();
String loadingMessage = getProcessor().getStatusMessage();
item.setSummary(loadingMessage);
DictionaryProgressNotification.getInstance(activity).showLoadingMessage(loadingMessage, "");
}
public void setReadyStatus() {
enable();
if (onFinish != null) onFinish.run();
}
}

View file

@ -3,7 +3,7 @@ package io.github.sspanak.tt9.preferences.screens.debug;
import androidx.preference.SwitchPreferenceCompat;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.exporter.LogcatExporter;
import io.github.sspanak.tt9.db.customWords.LogcatExporter;
import io.github.sspanak.tt9.hacks.DeviceInfo;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.preferences.items.ItemText;

View file

@ -2,7 +2,7 @@ package io.github.sspanak.tt9.preferences.screens.debug;
import androidx.preference.Preference;
import io.github.sspanak.tt9.db.exporter.LogcatExporter;
import io.github.sspanak.tt9.db.customWords.LogcatExporter;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.preferences.items.ItemExportAbstract;
import io.github.sspanak.tt9.ui.notifications.DictionaryProgressNotification;
@ -15,17 +15,17 @@ public class ItemExportLogcat extends ItemExportAbstract {
}
@Override
protected LogcatExporter getExporter() {
protected LogcatExporter getProcessor() {
return LogcatExporter.getInstance();
}
@Override
protected boolean onStartExporting() {
return getExporter().setIncludeSystemLogs(activity.getSettings().getEnableSystemLogs()).export(activity);
protected boolean onStartProcessing() {
return getProcessor().setIncludeSystemLogs(activity.getSettings().getEnableSystemLogs()).run(activity);
}
@Override
protected void onFinishExporting(String outputFile) {
protected void onFinishProcessing(String outputFile) {
activity.runOnUiThread(() -> {
DictionaryProgressNotification.getInstance(activity).hide();
setReadyStatus();

View file

@ -3,7 +3,7 @@ package io.github.sspanak.tt9.preferences.screens.languages;
import androidx.preference.Preference;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.exporter.CustomWordsExporter;
import io.github.sspanak.tt9.db.customWords.CustomWordsExporter;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.preferences.items.ItemExportAbstract;
@ -15,12 +15,12 @@ class ItemExportCustomWords extends ItemExportAbstract {
}
@Override
protected CustomWordsExporter getExporter() {
protected CustomWordsExporter getProcessor() {
return CustomWordsExporter.getInstance();
}
protected boolean onStartExporting() {
return CustomWordsExporter.getInstance().export(activity);
protected boolean onStartProcessing() {
return CustomWordsExporter.getInstance().run(activity);
}
public void setReadyStatus() {

View file

@ -2,10 +2,11 @@ package io.github.sspanak.tt9.preferences.screens.languages;
import androidx.preference.Preference;
import io.github.sspanak.tt9.db.exporter.DictionaryExporter;
import io.github.sspanak.tt9.db.customWords.DictionaryExporter;
import io.github.sspanak.tt9.languages.LanguageCollection;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.preferences.items.ItemExportAbstract;
import io.github.sspanak.tt9.preferences.items.ItemProcessCustomWordsAbstract;
import io.github.sspanak.tt9.util.Logger;
class ItemExportDictionary extends ItemExportAbstract {
@ -16,7 +17,7 @@ class ItemExportDictionary extends ItemExportAbstract {
}
@Override
public ItemExportAbstract refreshStatus() {
public ItemProcessCustomWordsAbstract refreshStatus() {
if (item != null) {
item.setVisible(Logger.isDebugLevel());
}
@ -24,14 +25,14 @@ class ItemExportDictionary extends ItemExportAbstract {
}
@Override
protected DictionaryExporter getExporter() {
protected DictionaryExporter getProcessor() {
return DictionaryExporter.getInstance();
}
protected boolean onStartExporting() {
protected boolean onStartProcessing() {
return DictionaryExporter.getInstance()
.setLanguages(LanguageCollection.getAll(activity, activity.getSettings().getEnabledLanguageIds()))
.export(activity);
.run(activity);
}
public void setReadyStatus() {

View file

@ -0,0 +1,130 @@
package io.github.sspanak.tt9.preferences.screens.languages;
import android.app.Activity;
import android.content.Intent;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultLauncher;
import androidx.preference.Preference;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.customWords.CustomWordsImporter;
import io.github.sspanak.tt9.db.entities.CustomWordFile;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.preferences.items.ItemProcessCustomWordsAbstract;
import io.github.sspanak.tt9.ui.notifications.DictionaryProgressNotification;
import io.github.sspanak.tt9.util.Logger;
public class ItemImportCustomWords extends ItemProcessCustomWordsAbstract {
final public static String NAME = "dictionary_import_custom";
private ActivityResultLauncher<Intent> importCustomWordsLauncher;
private CustomWordsImporter importer;
private String lastError;
public ItemImportCustomWords(Preference item, PreferencesActivity activity, Runnable onStart, Runnable onFinish) {
super(item, activity, onStart, onFinish);
}
@Override
protected CustomWordsImporter getProcessor() {
if (importer == null) {
importer = CustomWordsImporter.getInstance(activity);
importer.setFailureHandler(this::onFailure);
importer.setProgressHandler(this::onProgress);
}
return importer;
}
@Override
protected boolean onClick(Preference p) {
browseFiles();
return true;
}
@Override
protected boolean onStartProcessing() {
lastError = "";
return false;
}
private void onProgress(int progress) {
String loadingMsg = activity.getString(R.string.dictionary_import_progress, progress + "%");
DictionaryProgressNotification.getInstance(activity).showLoadingMessage(loadingMsg, "", progress, 100);
activity.runOnUiThread(() -> item.setSummary(loadingMsg));
}
private void onFailure(String error) {
lastError = error;
onFinishProcessing(null);
}
@Override
protected String getFailureMessage() {
return lastError;
}
@Override
protected String getFailureTitle() {
return activity.getString(R.string.dictionary_import_failed);
}
@Override
protected String getSuccessMessage(String fileName) {
return "";
}
@Override
protected String getSuccessTitle() {
return activity.getString(R.string.dictionary_import_finished);
}
@Override
public void setReadyStatus() {
item.setSummary(R.string.dictionary_import_custom_words_summary);
super.setReadyStatus();
}
void setBrowseFilesLauncher(ActivityResultLauncher<Intent> launcher) {
if (item != null) {
item.setEnabled(true);
}
importCustomWordsLauncher = launcher;
}
private void browseFiles() {
if (importCustomWordsLauncher == null) {
Logger.w(getClass().getSimpleName(), "No file browser launcher set");
return;
}
Intent intent = new Intent()
.addCategory(Intent.CATEGORY_OPENABLE)
.setType(CustomWordFile.MIME_TYPE)
.setAction(Intent.ACTION_GET_CONTENT);
importCustomWordsLauncher.launch(intent);
}
void onFileSelected(ActivityResult result) {
if (result.getResultCode() != Activity.RESULT_OK) {
onFailure(activity.getString(R.string.dictionary_import_error_browsing_error));
Logger.e(getClass().getSimpleName(), "File picker activity failed with code: " + result.getResultCode());
return;
}
CustomWordFile file = new CustomWordFile(
result.getData() != null ? result.getData().getData() : null,
activity.getContentResolver()
);
if (!file.exists()) {
onFailure(activity.getString(R.string.dictionary_import_error_cannot_read_file));
return;
}
getProcessor().run(activity, file);
}
}

View file

@ -1,11 +1,17 @@
package io.github.sspanak.tt9.preferences.screens.languages;
import android.content.Intent;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import java.util.ArrayList;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.DictionaryLoader;
import io.github.sspanak.tt9.db.exporter.CustomWordsExporter;
import io.github.sspanak.tt9.db.exporter.DictionaryExporter;
import io.github.sspanak.tt9.db.customWords.CustomWordsExporter;
import io.github.sspanak.tt9.db.customWords.CustomWordsImporter;
import io.github.sspanak.tt9.db.customWords.DictionaryExporter;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.preferences.items.ItemClickable;
import io.github.sspanak.tt9.preferences.screens.BaseScreenFragment;
@ -16,6 +22,7 @@ public class LanguagesScreen extends BaseScreenFragment {
private final ArrayList<ItemClickable> clickables = new ArrayList<>();
private ItemLoadDictionary loadItem;
private ItemImportCustomWords importCustomWordsItem;
private ItemExportDictionary exportDictionaryItem;
private ItemExportCustomWords exportCustomWordsItem;
@ -39,13 +46,15 @@ public class LanguagesScreen extends BaseScreenFragment {
.populate()
.enableClickHandler();
loadItem = new ItemLoadDictionary(findPreference(ItemLoadDictionary.NAME),
loadItem = new ItemLoadDictionary(
findPreference(ItemLoadDictionary.NAME),
activity,
() -> ItemClickable.disableOthers(clickables, loadItem),
this::onActionFinish
);
exportDictionaryItem = new ItemExportDictionary(findPreference(ItemExportDictionary.NAME),
exportDictionaryItem = new ItemExportDictionary(
findPreference(ItemExportDictionary.NAME),
activity,
this::onActionStart,
this::onActionFinish
@ -74,14 +83,23 @@ public class LanguagesScreen extends BaseScreenFragment {
findPreference(ItemExportCustomWords.NAME),
activity,
this::onActionStart,
this::onActionFinish);
this::onActionFinish
);
clickables.add(exportCustomWordsItem);
importCustomWordsItem = new ItemImportCustomWords(
findPreference(ItemImportCustomWords.NAME),
activity,
this::onActionStart,
this::onActionFinish
);
clickables.add(importCustomWordsItem);
ItemClickable.enableAllClickHandlers(clickables);
refreshItems();
resetFontSize(false);
createBrowseFilesLauncher();
}
@ -96,11 +114,16 @@ public class LanguagesScreen extends BaseScreenFragment {
loadItem.refreshStatus();
exportDictionaryItem.refreshStatus();
exportCustomWordsItem.refreshStatus();
importCustomWordsItem.refreshStatus();
if (DictionaryLoader.getInstance(activity).isRunning()) {
loadItem.refreshStatus();
ItemClickable.disableOthers(clickables, loadItem);
} else if (CustomWordsExporter.getInstance().isRunning() || DictionaryExporter.getInstance().isRunning()) {
} else if (
CustomWordsExporter.getInstance().isRunning()
|| DictionaryExporter.getInstance().isRunning()
|| CustomWordsImporter.getInstance(activity).isRunning()
) {
onActionStart();
} else {
onActionFinish();
@ -115,4 +138,13 @@ public class LanguagesScreen extends BaseScreenFragment {
private void onActionFinish() {
ItemClickable.enableAll(clickables);
}
private void createBrowseFilesLauncher() {
ActivityResultLauncher<Intent> launcher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> importCustomWordsItem.onFileSelected(result)
);
importCustomWordsItem.setBrowseFilesLauncher(launcher);
}
}

View file

@ -9,6 +9,8 @@ public class SettingsStore extends SettingsUI {
/************* internal settings *************/
public final static int CLIPBOARD_PREVIEW_LENGTH = 20;
public final static int DELETE_WORDS_SEARCH_DELAY = 500; // ms
public final static int CUSTOM_WORDS_IMPORT_MAX_LINES = 250;
public final static int CUSTOM_WORDS_MAX = 1000;
public final static int DICTIONARY_AUTO_LOAD_COOLDOWN_TIME = 1200000; // 20 minutes in ms
public final static int DICTIONARY_DOWNLOAD_CONNECTION_TIMEOUT = 10000; // ms
public final static int DICTIONARY_DOWNLOAD_READ_TIMEOUT = 10000; // ms

View file

@ -50,12 +50,17 @@ public class DictionaryProgressNotification extends DictionaryNotification {
public void showLoadingMessage(@NonNull String title, @NonNull String message) {
showLoadingMessage(title, message, 0, 1);
}
public void showLoadingMessage(@NonNull String title, @NonNull String message, int progress, int maxProgress) {
this.title = title;
this.message = message;
messageLong = "";
indeterminate = true;
progress = 1;
maxProgress = 2;
this.progress = progress;
this.maxProgress = maxProgress;
indeterminate = (progress <= 0 && maxProgress <= 0);
renderMessage();
}

View file

@ -54,6 +54,13 @@
<string name="dictionary_export_custom_words_summary">Експортиране на CSV с всички добавени думи в: „%1$s“.</string>
<string name="dictionary_export_failed">Неуспешно експортиране</string>
<string name="dictionary_export_failed_more_info">За повече информация, активирайте режима за отстраняване на грешки и прегледайте журнала.</string>
<string name="dictionary_import_failed">Неуспешно импортиране.</string>
<string name="dictionary_import_finished">Импортирането завърши.</string>
<string name="dictionary_import_error_browsing_error">Грешка при избора на файлове в Android.</string>
<string name="dictionary_import_error_cannot_read_file">Избраният файл не може да бъде отворен.</string>
<string name="dictionary_import_error_file_too_long">Думите надвишават максимално допустимия брой от %1$d.</string>
<string name="dictionary_import_error_malformed_line">Неочакван формат на дума: „%1$s“ на ред %2$d.</string>
<string name="dictionary_import_error_too_many_words">Хранилището за добавени думи е пълно. Не можете да импортирате повече думи.</string>
<string name="delete_words_delete">Изтрий</string>
<string name="delete_words_link_summary">Намери и изтрий на неправилно написани или ненужни думи.</string>
<string name="delete_words_search_placeholder">Търси думи</string>
@ -154,4 +161,8 @@
<string name="function_select_keyboard">Избор на клавиатура</string>
<string name="function_voice_input">Гласово въвеждане</string>
<string name="add_word_no_confirmation">Добавяне без потвърждение</string>
<string name="dictionary_import_custom_words">Импортиране</string>
<string name="dictionary_import_custom_words_summary">Импортиране на думи от по-рано експортирано CSV.</string>
<string name="dictionary_import_progress">Импортиране на CSV (%1$s)…</string>
<string name="dictionary_import_running">Импортиране на CSV…</string>
</resources>

View file

@ -92,6 +92,13 @@
<string name="dictionary_export_custom_words_summary">Exportiere ein CSV mit allen hinzugefügten Wörtern nach: „%1$s“.</string>
<string name="dictionary_export_failed">Export fehlgeschlagen</string>
<string name="dictionary_export_failed_more_info">Für weitere Informationen, aktivieren Sie den Debug-Modus und sehen Sie sich die Protokolle an.</string>
<string name="dictionary_import_failed">Importieren fehlgeschlagen.</string>
<string name="dictionary_import_finished">Importieren abgeschlossen.</string>
<string name="dictionary_import_error_browsing_error">Fehler beim Dateiauswahl von Android.</string>
<string name="dictionary_import_error_cannot_read_file">Die ausgewählte Datei konnte nicht geöffnet werden.</string>
<string name="dictionary_import_error_file_too_long">Die Anzahl der Wörter überschreitet die maximal erlaubte Anzahl von %1$d.</string>
<string name="dictionary_import_error_malformed_line">Unerwartetes Wortformat: \"%1$s\" in Zeile %2$d.</string>
<string name="dictionary_import_error_too_many_words">Der Speicher für hinzugefügte Wörter ist voll. Sie können keine weiteren Wörter importieren.</string>
<string name="delete_words_delete">Löschen</string>
<string name="delete_words_link_summary">Finde und lösche falsch geschriebene oder überflüssige Wörter.</string>
<string name="delete_words_search_placeholder">Nach Wörtern suchen</string>
@ -143,4 +150,8 @@
<string name="virtual_numpad_alignment_right">Rechts</string>
<string name="function_select_keyboard">Tastaturauswahl</string>
<string name="add_word_no_confirmation">Ohne Bestätigung hinzufügen</string>
<string name="dictionary_import_custom_words">Importieren</string>
<string name="dictionary_import_custom_words_summary">Wörter aus einer zuvor exportierten CSV-Datei importieren.</string>
<string name="dictionary_import_progress">CSV importieren (%1$s)…</string>
<string name="dictionary_import_running">CSV importieren…</string>
</resources>

View file

@ -105,6 +105,13 @@
<string name="dictionary_export_custom_words_summary">Exportar un CSV con todas las palabras añadidas a: \"%1$s\".</string>
<string name="dictionary_export_failed">Fallo en la exportación</string>
<string name="dictionary_export_failed_more_info">Para obtener más información, habilita el modo de depuración y consulta los registros.</string>
<string name="dictionary_import_failed">La importación falló.</string>
<string name="dictionary_import_finished">La importación se completó.</string>
<string name="dictionary_import_error_browsing_error">Error del selector de archivos de Android.</string>
<string name="dictionary_import_error_cannot_read_file">No se pudo abrir el archivo seleccionado.</string>
<string name="dictionary_import_error_file_too_long">El número de palabras excede el límite máximo permitido de %1$d.</string>
<string name="dictionary_import_error_malformed_line">Formato de palabra inesperado: \"%1$s\" en la línea %2$d.</string>
<string name="dictionary_import_error_too_many_words">El almacenamiento de palabras añadidas está lleno. No puede importar más palabras.</string>
<string name="delete_words_delete">Eliminar</string>
<string name="delete_words_link_summary">Buscar y eliminar palabras mal escritas o innecesarias.</string>
<string name="delete_words_search_placeholder">Buscar palabras</string>
@ -152,4 +159,8 @@
<string name="virtual_numpad_alignment_right">A la derecha</string>
<string name="function_select_keyboard">Cambiar el teclado</string>
<string name="add_word_no_confirmation">Añadir sin confirmación</string>
<string name="dictionary_import_custom_words">Importar</string>
<string name="dictionary_import_custom_words_summary">Importar palabras de un CSV previamente exportado.</string>
<string name="dictionary_import_progress">Importando CSV (%1$s)…</string>
<string name="dictionary_import_running">Importando CSV…</string>
</resources>

View file

@ -107,6 +107,13 @@
<string name="dictionary_export_custom_words_summary">Exporter un CSV avec tous les mots ajoutés vers : «%1$s».</string>
<string name="dictionary_export_failed">Échec de l\'exportation</string>
<string name="dictionary_export_failed_more_info">Pour plus d\'informations, activez le mode de débogage et consultez les journaux.</string>
<string name="dictionary_import_failed">L\'importation a échoué.</string>
<string name="dictionary_import_finished">Importation terminée.</string>
<string name="dictionary_import_error_browsing_error">Erreur du sélecteur de fichiers d\'Android.</string>
<string name="dictionary_import_error_cannot_read_file">Impossible d\'ouvrir le fichier sélectionné.</string>
<string name="dictionary_import_error_file_too_long">Le nombre de mots dépasse le nombre maximal autorisé de %1$d.</string>
<string name="dictionary_import_error_malformed_line">Format de mot inattendu : « %1$s » à la ligne %2$d.</string>
<string name="dictionary_import_error_too_many_words">Le stockage des mots ajoutés est plein. Vous ne pouvez plus importer de mots.</string>
<string name="delete_words_delete">Supprimer</string>
<string name="delete_words_link_summary">Trouver et supprimer des mots mal orthographiés ou inutiles.</string>
<string name="delete_words_search_placeholder">Rechercher des mots</string>
@ -150,4 +157,8 @@
<string name="virtual_numpad_alignment_right">À droite</string>
<string name="function_select_keyboard">Choisir le clavier</string>
<string name="add_word_no_confirmation">Ajouter sans confirmation</string>
<string name="dictionary_import_custom_words">Importer</string>
<string name="dictionary_import_custom_words_summary">Importer des mots à partir d\'un fichier CSV précédemment exporté.</string>
<string name="dictionary_import_progress">Importation de CSV (%1$s)…</string>
<string name="dictionary_import_running">Importation de CSV…</string>
</resources>

View file

@ -92,6 +92,13 @@
<string name="dictionary_export_custom_words_summary">Esporta un CSV con tutte le parole aggiunte su: \"%1$s\".</string>
<string name="dictionary_export_failed">Esportazione fallita</string>
<string name="dictionary_export_failed_more_info">Per ulteriori informazioni, abilita la modalità di debug e consulta i log.</string>
<string name="dictionary_import_failed">Importazione fallita.</string>
<string name="dictionary_import_finished">Importazione completata.</string>
<string name="dictionary_import_error_browsing_error">Errore del selettore di file di Android.</string>
<string name="dictionary_import_error_cannot_read_file">Impossibile aprire il file selezionato.</string>
<string name="dictionary_import_error_file_too_long">Le parole superano il numero massimo consentito di %1$d.</string>
<string name="dictionary_import_error_malformed_line">Formato di parola imprevisto: \"%1$s\" alla linea %2$d.</string>
<string name="dictionary_import_error_too_many_words">Lo spazio di archiviazione delle parole aggiunte è pieno. Non è possibile importare altre parole.</string>
<string name="delete_words_delete">Elimina</string>
<string name="delete_words_link_summary">Trova ed elimina parole errate o non necessarie.</string>
<string name="delete_words_search_placeholder">Ricerca di parole</string>
@ -142,5 +149,9 @@
<string name="virtual_numpad_alignment_left">A sinistra</string>
<string name="virtual_numpad_alignment_right">A destra</string>
<string name="add_word_no_confirmation">Aggiungere senza conferma</string>
<string name="dictionary_import_custom_words">Importare</string>
<string name="dictionary_import_custom_words_summary">Importare parole da un CSV precedentemente esportato.</string>
<string name="dictionary_import_progress">Importazione CSV (%1$s)…</string>
<string name="dictionary_import_running">Importazione CSV…</string>
</resources>

View file

@ -105,6 +105,13 @@
<string name="dictionary_export_custom_words_summary">ייצוא CSV עם כל המילים שנוספו ל: \"%1$s\".</string>
<string name="dictionary_export_failed">נכשל בייצוא</string>
<string name="dictionary_export_failed_more_info">"למידע נוסף, הפעל מצב איתור באגים וראה את הלוגים. "</string>
<string name="dictionary_import_failed">הייבוא נכשל.</string>
<string name="dictionary_import_finished">הייבוא הושלם.</string>
<string name="dictionary_import_error_browsing_error">שגיאת בורר הקבצים של Android.</string>
<string name="dictionary_import_error_cannot_read_file">לא ניתן לפתוח את הקובץ שנבחר.</string>
<string name="dictionary_import_error_file_too_long">מספר המילים חורג מהמכסה המקסימלית של %1$d.</string>
<string name="dictionary_import_error_malformed_line">תבנית מילה בלתי צפויה: \"%1$s\" בשורה %2$d.</string>
<string name="dictionary_import_error_too_many_words">אחסון המילים שהתווספו מלא. אינך יכול לייבא עוד מילים.</string>
<string name="delete_words_delete">מחיקה</string>
<string name="delete_words_link_summary">מצא ומחק מילים שכתובות בטעות או שאינן נדרשות.</string>
<string name="delete_words_search_placeholder">חיפוש מילים</string>
@ -155,4 +162,8 @@
<string name="virtual_numpad_alignment_right">ימינה</string>
<string name="function_select_keyboard">בחירת מקלדת</string>
<string name="add_word_no_confirmation">להוסיף ללא אישור</string>
<string name="dictionary_import_custom_words">לְיַבֵּא</string>
<string name="dictionary_import_custom_words_summary">ייבוא מילים מקובץ CSV שיוצא קודם לכן.</string>
<string name="dictionary_import_progress">מייבא CSV (%1$s)…</string>
<string name="dictionary_import_running">מייבא CSV…</string>
</resources>

View file

@ -124,6 +124,13 @@
<string name="dictionary_export_finished_more_info">Žodžiai eksportuoti į: „%1$s“.</string>
<string name="dictionary_export_generating_csv">Eksportuojama CSV…</string>
<string name="dictionary_export_generating_csv_for_language">Eksportuojama CSV (%1$s)…</string>
<string name="dictionary_import_failed">Importuoti nepavyko.</string>
<string name="dictionary_import_finished">Importavimas baigtas.</string>
<string name="dictionary_import_error_browsing_error">Android failų pasirinkimo klaida.</string>
<string name="dictionary_import_error_cannot_read_file">Nepavyko atidaryti pasirinkto failo.</string>
<string name="dictionary_import_error_file_too_long">Žodžių skaičius viršija leistiną maksimumą %1$d.</string>
<string name="dictionary_import_error_malformed_line">Nenumatytas žodžio formatas: „%1$s“ eilutėje %2$d.</string>
<string name="dictionary_import_error_too_many_words">Pridėtų žodžių saugykla yra pilna. Daugiau žodžių importuoti negalite.</string>
<string name="delete_words_delete">Ištrinti</string>
<string name="delete_words_link_summary">Raskite ir ištrinkite neteisingai parašytus arba nereikalingus žodžius.</string>
<string name="delete_words_search_placeholder">Ieškoti žodžių</string>
@ -161,4 +168,8 @@
<string name="virtual_numpad_alignment_right">Dešinėje</string>
<string name="function_select_keyboard">Keisti klaviatūrą</string>
<string name="add_word_no_confirmation">Pridėti be patvirtinimo</string>
<string name="dictionary_import_custom_words">Importuoti</string>
<string name="dictionary_import_custom_words_summary">Importuoti žodžius iš anksčiau eksportuoto CSV.</string>
<string name="dictionary_import_progress">Importuojamas CSV (%1$s)…</string>
<string name="dictionary_import_running">Importuojamas CSV…</string>
</resources>

View file

@ -90,6 +90,13 @@
<string name="dictionary_export_custom_words_summary">Exporteer een CSV met alle toegevoegde woorden naar: \"%1$s\".</string>
<string name="dictionary_export_failed">Exporteren mislukt</string>
<string name="dictionary_export_failed_more_info">Voor meer informatie, schakel de debug-modus in en bekijk de logs.</string>
<string name="dictionary_import_failed">Importeren mislukt.</string>
<string name="dictionary_import_finished">Importeren voltooid.</string>
<string name="dictionary_import_error_browsing_error">Fout bij het selecteren van bestanden in Android.</string>
<string name="dictionary_import_error_cannot_read_file">Kan het geselecteerde bestand niet openen.</string>
<string name="dictionary_import_error_file_too_long">Woorden overschrijden het maximaal toegestane aantal van %1$d.</string>
<string name="dictionary_import_error_malformed_line">Onverwacht woordformaat: \"%1$s\" op regel %2$d.</string>
<string name="dictionary_import_error_too_many_words">De opslag voor toegevoegde woorden is vol. U kunt geen woorden meer importeren.</string>
<string name="delete_words_delete">Verwijderen</string>
<string name="delete_words_link_summary">Zoek en verwijder verkeerd gespelde of onnodige woorden.</string>
<string name="delete_words_search_placeholder">Zoeken naar woorden</string>
@ -141,4 +148,8 @@
<string name="virtual_numpad_alignment_right">Rechts</string>
<string name="function_select_keyboard">Toetsenbordkeuze</string>
<string name="add_word_no_confirmation">Toevoegen zonder bevestiging</string>
<string name="dictionary_import_custom_words">Importeren</string>
<string name="dictionary_import_custom_words_summary">Woorden importeren uit een eerder geëxporteerde CSV.</string>
<string name="dictionary_import_progress">CSV importeren (%1$s)…</string>
<string name="dictionary_import_running">CSV importeren…</string>
</resources>

View file

@ -105,6 +105,13 @@
<string name="dictionary_export_custom_words_summary">Exportar um CSV com todas as palavras adicionadas para: \"%1$s\".</string>
<string name="dictionary_export_failed">Falha na exportação</string>
<string name="dictionary_export_failed_more_info">Para mais informações, ative o modo de depuração e veja os registros.</string>
<string name="dictionary_import_failed">Importação falhou.</string>
<string name="dictionary_import_finished">Importação concluída.</string>
<string name="dictionary_import_error_browsing_error">Erro no seletor de arquivos do Android.</string>
<string name="dictionary_import_error_cannot_read_file">Não foi possível abrir o arquivo selecionado.</string>
<string name="dictionary_import_error_file_too_long">O número de palavras excede o máximo permitido de %1$d.</string>
<string name="dictionary_import_error_malformed_line">Formato de palavra inesperado: \"%1$s\" na linha %2$d.</string>
<string name="dictionary_import_error_too_many_words">O armazenamento de palavras adicionadas está cheio. Você não pode importar mais palavras.</string>
<string name="delete_words_delete">Excluir</string>
<string name="delete_words_link_summary">Encontrar e excluir palavras escritas incorretamente ou desnecessárias.</string>
<string name="delete_words_search_placeholder">Buscar palavras</string>
@ -155,4 +162,8 @@
<string name="virtual_numpad_alignment_right">À direita</string>
<string name="function_select_keyboard">Mude o teclado</string>
<string name="add_word_no_confirmation">Adicionar sem confirmação</string>
<string name="dictionary_import_custom_words">Importar</string>
<string name="dictionary_import_custom_words_summary">Importar palavras de um CSV previamente exportado.</string>
<string name="dictionary_import_progress">Importando CSV (%1$s)…</string>
<string name="dictionary_import_running">Importando CSV…</string>
</resources>

View file

@ -106,6 +106,13 @@
<string name="dictionary_export_custom_words_summary">Экспорт CSV со всеми добавленными словами в: «%1$s».</string>
<string name="dictionary_export_failed">Ошибка экспорта</string>
<string name="dictionary_export_failed_more_info">Для получения дополнительной информации включите режим отладки и просмотрите журналы.</string>
<string name="dictionary_import_failed">Импорт не выполнен.</string>
<string name="dictionary_import_finished">Импорт завершен.</string>
<string name="dictionary_import_error_browsing_error">Ошибка выбора файлов в Android.</string>
<string name="dictionary_import_error_cannot_read_file">Не удалось открыть выбранный файл.</string>
<string name="dictionary_import_error_file_too_long">Количество слов превышает максимально допустимое значение %1$d.</string>
<string name="dictionary_import_error_malformed_line">Неожиданный формат слова: «%1$s» на строке %2$d.</string>
<string name="dictionary_import_error_too_many_words">Хранилище добавленных слов заполнено. Вы не можете импортировать больше слов.</string>
<string name="delete_words_delete">Удалить</string>
<string name="delete_words_link_summary">Найти и удалить ошибочно написанные или ненужные слова.</string>
<string name="delete_words_search_placeholder">Поиск слов</string>
@ -152,4 +159,8 @@
<string name="virtual_numpad_alignment_right">Направо</string>
<string name="function_select_keyboard">Выбор клавиатуры</string>
<string name="add_word_no_confirmation">Добавить без подтверждения</string>
<string name="dictionary_import_custom_words">Импортировать</string>
<string name="dictionary_import_custom_words_summary">Импортировать слова из ранее экспортированного CSV.</string>
<string name="dictionary_import_progress">Импортирование CSV (%1$s)…</string>
<string name="dictionary_import_running">Импортирование CSV…</string>
</resources>

View file

@ -90,6 +90,13 @@
<string name="dictionary_export_custom_words_summary">Seçilenleri CSV dosyası olarak „%1$s“ dizinine dışa aktar.</string>
<string name="dictionary_export_failed">Dışa aktarım başarısız oldu.</string>
<string name="dictionary_export_failed_more_info">Daha fazla bilgi için Hata Ayıklamayııp kayıtları kontrol edin.</string>
<string name="dictionary_import_failed">İçe aktarma başarısız oldu.</string>
<string name="dictionary_import_finished">İçe aktarma tamamlandı.</string>
<string name="dictionary_import_error_browsing_error">Android dosya seçici hatası.</string>
<string name="dictionary_import_error_cannot_read_file">Seçilen dosya açılamadı.</string>
<string name="dictionary_import_error_file_too_long">Kelime sayısı izin verilen maksimum %1$d\'i aşıyor.</string>
<string name="dictionary_import_error_malformed_line">Beklenmeyen kelime formatı: \"%1$s\" satır %2$d\'de.</string>
<string name="dictionary_import_error_too_many_words">Eklenen kelime depolama alanı dolu. Daha fazla kelime içe aktaramazsınız.</string>
<string name="delete_words_delete">Sil</string>
<string name="delete_words_link_summary">Yanlış yazılan ya da gereksiz kelimeleri bulun ve silin.</string>
<string name="delete_words_search_placeholder">Aramak istediğiniz kelimeyi yazın…</string>
@ -155,4 +162,8 @@
<string name="virtual_numpad_alignment_right">Sağa</string>
<string name="function_select_keyboard">Klavye Seçimi</string>
<string name="add_word_no_confirmation">Onay olmadan ekle</string>
<string name="dictionary_import_custom_words">İçe Aktar</string>
<string name="dictionary_import_custom_words_summary">Daha önce dışa aktarılan bir CSV\'den kelimeleri içe aktar.</string>
<string name="dictionary_import_progress">CSV İçe aktarılıyor (%1$s)…</string>
<string name="dictionary_import_running">CSV İçe aktarılıyor…</string>
</resources>

View file

@ -89,6 +89,13 @@
<string name="dictionary_export_custom_words_summary">Експорт CSV з усіма доданими словами в: \"%1$s\".</string>
<string name="dictionary_export_failed">Помилка експорту</string>
<string name="dictionary_export_failed_more_info">Для отримання додаткової інформації увімкніть режим відлагодження та перегляньте журнали.</string>
<string name="dictionary_import_failed">Імпорт не вдався.</string>
<string name="dictionary_import_finished">Імпорт завершено.</string>
<string name="dictionary_import_error_browsing_error">Помилка вибору файлів Android.</string>
<string name="dictionary_import_error_cannot_read_file">Не вдалося відкрити вибраний файл.</string>
<string name="dictionary_import_error_file_too_long">Кількість слів перевищує максимально дозволену кількість %1$d.</string>
<string name="dictionary_import_error_malformed_line">"Несподіваний формат слова: \"%1$s\" у рядку %2$d. "</string>
<string name="dictionary_import_error_too_many_words">Сховище доданих слів заповнено. Ви не можете імпортувати більше слів.</string>
<string name="delete_words_delete">Видалити</string>
<string name="delete_words_link_summary">Знайти та видалити неправильно написані або зайві слова.</string>
<string name="delete_words_search_placeholder">Пошук слів</string>
@ -163,4 +170,8 @@
<string name="virtual_numpad_alignment_right">Праворуч</string>
<string name="function_select_keyboard">Змінити клавіатуру</string>
<string name="add_word_no_confirmation">Додати без підтвердження</string>
<string name="dictionary_import_custom_words">Імпортувати</string>
<string name="dictionary_import_custom_words_summary">Імпортувати слова з раніше експортованого CSV.</string>
<string name="dictionary_import_progress">Імпорт CSV (%1$s)…</string>
<string name="dictionary_import_running">Імпорт CSV…</string>
</resources>

View file

@ -111,6 +111,18 @@
<string name="dictionary_export_generating_csv">Exporting CSV…</string>
<string name="dictionary_export_generating_csv_for_language">Exporting CSV (%1$s)…</string>
<string name="dictionary_import_custom_words">Import</string>
<string name="dictionary_import_custom_words_summary">Import words from a previously exported CSV.</string>
<string name="dictionary_import_failed">Importing failed.</string>
<string name="dictionary_import_finished">Importing completed.</string>
<string name="dictionary_import_error_browsing_error">Android file picker error.</string>
<string name="dictionary_import_error_cannot_read_file">Could not open the selected file.</string>
<string name="dictionary_import_error_file_too_long">Words exceed the maximum allowed count of %1$d.</string>
<string name="dictionary_import_error_malformed_line">Unexpected word format: \"%1$s\" on line %2$d.</string>
<string name="dictionary_import_error_too_many_words">The added word storage is full. You can not import any more words.</string>
<string name="dictionary_import_progress">Importing CSV (%1$s)…</string>
<string name="dictionary_import_running">Importing CSV…</string>
<string name="delete_words_delete">Delete</string>
<string name="delete_words_link_summary">Find and delete misspelled or unneeded words.</string>
<string name="delete_words_search_placeholder">Search for Words</string>

View file

@ -36,6 +36,11 @@
app:key="add_word_no_confirmation"
app:title="@string/add_word_no_confirmation" />
<Preference
app:key="dictionary_import_custom"
app:title="@string/dictionary_import_custom_words"
app:enabled="false"/>
<Preference
app:key="dictionary_export_custom"
app:title="@string/dictionary_export_custom_words" />