Dictionary import (#586)
This commit is contained in:
parent
b9c4d71968
commit
4c7c941e44
35 changed files with 858 additions and 156 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
package io.github.sspanak.tt9.db.exporter;
|
package io.github.sspanak.tt9.db.customWords;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
|
|
@ -16,19 +16,13 @@ import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
import io.github.sspanak.tt9.util.ConsumerCompat;
|
|
||||||
import io.github.sspanak.tt9.util.Permissions;
|
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 FILE_EXTENSION = ".csv";
|
||||||
protected static String MIME_TYPE = "text/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 outputFile;
|
||||||
private String statusMessage = "";
|
|
||||||
|
|
||||||
|
|
||||||
private void writeAndroid10(Activity activity) throws Exception {
|
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;
|
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() {
|
protected void sendSuccess() {
|
||||||
if (successHandler != null) {
|
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 String generateFileName();
|
||||||
@NonNull abstract protected byte[] getFileContents(Activity activity) throws Exception;
|
@NonNull abstract protected byte[] getFileContents(Activity activity) throws Exception;
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package io.github.sspanak.tt9.db.exporter;
|
package io.github.sspanak.tt9.db.customWords;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
|
@ -23,7 +23,7 @@ public class CustomWordsExporter extends AbstractExporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void exportSync(Activity activity) {
|
protected void runSync(Activity activity) {
|
||||||
try {
|
try {
|
||||||
sendStart(activity.getString(R.string.dictionary_export_generating_csv));
|
sendStart(activity.getString(R.string.dictionary_export_generating_csv));
|
||||||
write(activity);
|
write(activity);
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package io.github.sspanak.tt9.db.exporter;
|
package io.github.sspanak.tt9.db.customWords;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
|
@ -31,7 +31,7 @@ public class DictionaryExporter extends AbstractExporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void exportSync(Activity activity) {
|
protected void runSync(Activity activity) {
|
||||||
if (languages == null || languages.isEmpty()) {
|
if (languages == null || languages.isEmpty()) {
|
||||||
Logger.d(LOG_TAG, "Nothing to do");
|
Logger.d(LOG_TAG, "Nothing to do");
|
||||||
return;
|
return;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package io.github.sspanak.tt9.db.exporter;
|
package io.github.sspanak.tt9.db.customWords;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ public class LogcatExporter extends AbstractExporter {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void exportSync(Activity activity) {
|
protected void runSync(Activity activity) {
|
||||||
try {
|
try {
|
||||||
sendStart("Exporting logs...");
|
sendStart("Exporting logs...");
|
||||||
write(activity);
|
write(activity);
|
||||||
|
|
@ -5,7 +5,6 @@ import android.content.Context;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import io.github.sspanak.tt9.R;
|
import io.github.sspanak.tt9.R;
|
||||||
import io.github.sspanak.tt9.languages.Language;
|
|
||||||
|
|
||||||
public class AddWordResult {
|
public class AddWordResult {
|
||||||
public static final int CODE_SUCCESS = 0;
|
public static final int CODE_SUCCESS = 0;
|
||||||
|
|
|
||||||
|
|
@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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] : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,6 @@ import java.net.URL;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import io.github.sspanak.tt9.BuildConfig;
|
|
||||||
import io.github.sspanak.tt9.R;
|
import io.github.sspanak.tt9.R;
|
||||||
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
||||||
import io.github.sspanak.tt9.util.Logger;
|
import io.github.sspanak.tt9.util.Logger;
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
public ArrayList<String> getCustomWords(@NonNull SQLiteDatabase db, @NonNull Language language, @NonNull String wordFilter) {
|
||||||
ArrayList<String> words = new ArrayList<>();
|
ArrayList<String> words = new ArrayList<>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,88 +3,30 @@ package io.github.sspanak.tt9.preferences.items;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
import io.github.sspanak.tt9.R;
|
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.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) {
|
public ItemExportAbstract(Preference item, PreferencesActivity activity, Runnable onStart, Runnable onFinish) {
|
||||||
super(item);
|
super(item, activity, onStart, onFinish);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract protected AbstractExporter getExporter();
|
|
||||||
|
|
||||||
|
|
||||||
public ItemExportAbstract refreshStatus() {
|
|
||||||
if (item != null) {
|
|
||||||
if (getExporter().isRunning()) {
|
|
||||||
setLoadingStatus();
|
|
||||||
} else {
|
|
||||||
setReadyStatus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onClick(Preference p) {
|
protected String getFailureTitle() {
|
||||||
setLoadingStatus();
|
return activity.getString(R.string.dictionary_export_failed);
|
||||||
if (!onStartExporting()) {
|
|
||||||
setReadyStatus();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
abstract protected boolean onStartExporting();
|
protected String getFailureMessage() {
|
||||||
|
return activity.getString(R.string.dictionary_export_failed_more_info);
|
||||||
|
|
||||||
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 void setLoadingStatus() {
|
protected String getSuccessTitle() {
|
||||||
if (onStart != null) onStart.run();
|
return activity.getString(R.string.dictionary_export_finished);
|
||||||
disable();
|
|
||||||
|
|
||||||
String loadingMessage = getExporter().getStatusMessage();
|
|
||||||
item.setSummary(loadingMessage);
|
|
||||||
DictionaryProgressNotification.getInstance(activity).showLoadingMessage(loadingMessage, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setReadyStatus() {
|
protected String getSuccessMessage(String fileName) {
|
||||||
enable();
|
return activity.getString(R.string.dictionary_export_finished_more_info, fileName);
|
||||||
if (onFinish != null) onFinish.run();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ package io.github.sspanak.tt9.preferences.screens.debug;
|
||||||
import androidx.preference.SwitchPreferenceCompat;
|
import androidx.preference.SwitchPreferenceCompat;
|
||||||
|
|
||||||
import io.github.sspanak.tt9.R;
|
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.hacks.DeviceInfo;
|
||||||
import io.github.sspanak.tt9.preferences.PreferencesActivity;
|
import io.github.sspanak.tt9.preferences.PreferencesActivity;
|
||||||
import io.github.sspanak.tt9.preferences.items.ItemText;
|
import io.github.sspanak.tt9.preferences.items.ItemText;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package io.github.sspanak.tt9.preferences.screens.debug;
|
||||||
|
|
||||||
import androidx.preference.Preference;
|
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.PreferencesActivity;
|
||||||
import io.github.sspanak.tt9.preferences.items.ItemExportAbstract;
|
import io.github.sspanak.tt9.preferences.items.ItemExportAbstract;
|
||||||
import io.github.sspanak.tt9.ui.notifications.DictionaryProgressNotification;
|
import io.github.sspanak.tt9.ui.notifications.DictionaryProgressNotification;
|
||||||
|
|
@ -15,17 +15,17 @@ public class ItemExportLogcat extends ItemExportAbstract {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected LogcatExporter getExporter() {
|
protected LogcatExporter getProcessor() {
|
||||||
return LogcatExporter.getInstance();
|
return LogcatExporter.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onStartExporting() {
|
protected boolean onStartProcessing() {
|
||||||
return getExporter().setIncludeSystemLogs(activity.getSettings().getEnableSystemLogs()).export(activity);
|
return getProcessor().setIncludeSystemLogs(activity.getSettings().getEnableSystemLogs()).run(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onFinishExporting(String outputFile) {
|
protected void onFinishProcessing(String outputFile) {
|
||||||
activity.runOnUiThread(() -> {
|
activity.runOnUiThread(() -> {
|
||||||
DictionaryProgressNotification.getInstance(activity).hide();
|
DictionaryProgressNotification.getInstance(activity).hide();
|
||||||
setReadyStatus();
|
setReadyStatus();
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package io.github.sspanak.tt9.preferences.screens.languages;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
import io.github.sspanak.tt9.R;
|
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.PreferencesActivity;
|
||||||
import io.github.sspanak.tt9.preferences.items.ItemExportAbstract;
|
import io.github.sspanak.tt9.preferences.items.ItemExportAbstract;
|
||||||
|
|
||||||
|
|
@ -15,12 +15,12 @@ class ItemExportCustomWords extends ItemExportAbstract {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CustomWordsExporter getExporter() {
|
protected CustomWordsExporter getProcessor() {
|
||||||
return CustomWordsExporter.getInstance();
|
return CustomWordsExporter.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean onStartExporting() {
|
protected boolean onStartProcessing() {
|
||||||
return CustomWordsExporter.getInstance().export(activity);
|
return CustomWordsExporter.getInstance().run(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setReadyStatus() {
|
public void setReadyStatus() {
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@ package io.github.sspanak.tt9.preferences.screens.languages;
|
||||||
|
|
||||||
import androidx.preference.Preference;
|
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.languages.LanguageCollection;
|
||||||
import io.github.sspanak.tt9.preferences.PreferencesActivity;
|
import io.github.sspanak.tt9.preferences.PreferencesActivity;
|
||||||
import io.github.sspanak.tt9.preferences.items.ItemExportAbstract;
|
import io.github.sspanak.tt9.preferences.items.ItemExportAbstract;
|
||||||
|
import io.github.sspanak.tt9.preferences.items.ItemProcessCustomWordsAbstract;
|
||||||
import io.github.sspanak.tt9.util.Logger;
|
import io.github.sspanak.tt9.util.Logger;
|
||||||
|
|
||||||
class ItemExportDictionary extends ItemExportAbstract {
|
class ItemExportDictionary extends ItemExportAbstract {
|
||||||
|
|
@ -16,7 +17,7 @@ class ItemExportDictionary extends ItemExportAbstract {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ItemExportAbstract refreshStatus() {
|
public ItemProcessCustomWordsAbstract refreshStatus() {
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
item.setVisible(Logger.isDebugLevel());
|
item.setVisible(Logger.isDebugLevel());
|
||||||
}
|
}
|
||||||
|
|
@ -24,14 +25,14 @@ class ItemExportDictionary extends ItemExportAbstract {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DictionaryExporter getExporter() {
|
protected DictionaryExporter getProcessor() {
|
||||||
return DictionaryExporter.getInstance();
|
return DictionaryExporter.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean onStartExporting() {
|
protected boolean onStartProcessing() {
|
||||||
return DictionaryExporter.getInstance()
|
return DictionaryExporter.getInstance()
|
||||||
.setLanguages(LanguageCollection.getAll(activity, activity.getSettings().getEnabledLanguageIds()))
|
.setLanguages(LanguageCollection.getAll(activity, activity.getSettings().getEnabledLanguageIds()))
|
||||||
.export(activity);
|
.run(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setReadyStatus() {
|
public void setReadyStatus() {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
package io.github.sspanak.tt9.preferences.screens.languages;
|
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 java.util.ArrayList;
|
||||||
|
|
||||||
import io.github.sspanak.tt9.R;
|
import io.github.sspanak.tt9.R;
|
||||||
import io.github.sspanak.tt9.db.DictionaryLoader;
|
import io.github.sspanak.tt9.db.DictionaryLoader;
|
||||||
import io.github.sspanak.tt9.db.exporter.CustomWordsExporter;
|
import io.github.sspanak.tt9.db.customWords.CustomWordsExporter;
|
||||||
import io.github.sspanak.tt9.db.exporter.DictionaryExporter;
|
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.PreferencesActivity;
|
||||||
import io.github.sspanak.tt9.preferences.items.ItemClickable;
|
import io.github.sspanak.tt9.preferences.items.ItemClickable;
|
||||||
import io.github.sspanak.tt9.preferences.screens.BaseScreenFragment;
|
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 final ArrayList<ItemClickable> clickables = new ArrayList<>();
|
||||||
|
|
||||||
private ItemLoadDictionary loadItem;
|
private ItemLoadDictionary loadItem;
|
||||||
|
private ItemImportCustomWords importCustomWordsItem;
|
||||||
private ItemExportDictionary exportDictionaryItem;
|
private ItemExportDictionary exportDictionaryItem;
|
||||||
private ItemExportCustomWords exportCustomWordsItem;
|
private ItemExportCustomWords exportCustomWordsItem;
|
||||||
|
|
||||||
|
|
@ -39,13 +46,15 @@ public class LanguagesScreen extends BaseScreenFragment {
|
||||||
.populate()
|
.populate()
|
||||||
.enableClickHandler();
|
.enableClickHandler();
|
||||||
|
|
||||||
loadItem = new ItemLoadDictionary(findPreference(ItemLoadDictionary.NAME),
|
loadItem = new ItemLoadDictionary(
|
||||||
|
findPreference(ItemLoadDictionary.NAME),
|
||||||
activity,
|
activity,
|
||||||
() -> ItemClickable.disableOthers(clickables, loadItem),
|
() -> ItemClickable.disableOthers(clickables, loadItem),
|
||||||
this::onActionFinish
|
this::onActionFinish
|
||||||
);
|
);
|
||||||
|
|
||||||
exportDictionaryItem = new ItemExportDictionary(findPreference(ItemExportDictionary.NAME),
|
exportDictionaryItem = new ItemExportDictionary(
|
||||||
|
findPreference(ItemExportDictionary.NAME),
|
||||||
activity,
|
activity,
|
||||||
this::onActionStart,
|
this::onActionStart,
|
||||||
this::onActionFinish
|
this::onActionFinish
|
||||||
|
|
@ -74,14 +83,23 @@ public class LanguagesScreen extends BaseScreenFragment {
|
||||||
findPreference(ItemExportCustomWords.NAME),
|
findPreference(ItemExportCustomWords.NAME),
|
||||||
activity,
|
activity,
|
||||||
this::onActionStart,
|
this::onActionStart,
|
||||||
this::onActionFinish);
|
this::onActionFinish
|
||||||
|
);
|
||||||
clickables.add(exportCustomWordsItem);
|
clickables.add(exportCustomWordsItem);
|
||||||
|
|
||||||
|
importCustomWordsItem = new ItemImportCustomWords(
|
||||||
|
findPreference(ItemImportCustomWords.NAME),
|
||||||
|
activity,
|
||||||
|
this::onActionStart,
|
||||||
|
this::onActionFinish
|
||||||
|
);
|
||||||
|
clickables.add(importCustomWordsItem);
|
||||||
|
|
||||||
ItemClickable.enableAllClickHandlers(clickables);
|
ItemClickable.enableAllClickHandlers(clickables);
|
||||||
refreshItems();
|
refreshItems();
|
||||||
|
|
||||||
resetFontSize(false);
|
resetFontSize(false);
|
||||||
|
createBrowseFilesLauncher();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -96,11 +114,16 @@ public class LanguagesScreen extends BaseScreenFragment {
|
||||||
loadItem.refreshStatus();
|
loadItem.refreshStatus();
|
||||||
exportDictionaryItem.refreshStatus();
|
exportDictionaryItem.refreshStatus();
|
||||||
exportCustomWordsItem.refreshStatus();
|
exportCustomWordsItem.refreshStatus();
|
||||||
|
importCustomWordsItem.refreshStatus();
|
||||||
|
|
||||||
if (DictionaryLoader.getInstance(activity).isRunning()) {
|
if (DictionaryLoader.getInstance(activity).isRunning()) {
|
||||||
loadItem.refreshStatus();
|
loadItem.refreshStatus();
|
||||||
ItemClickable.disableOthers(clickables, loadItem);
|
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();
|
onActionStart();
|
||||||
} else {
|
} else {
|
||||||
onActionFinish();
|
onActionFinish();
|
||||||
|
|
@ -115,4 +138,13 @@ public class LanguagesScreen extends BaseScreenFragment {
|
||||||
private void onActionFinish() {
|
private void onActionFinish() {
|
||||||
ItemClickable.enableAll(clickables);
|
ItemClickable.enableAll(clickables);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createBrowseFilesLauncher() {
|
||||||
|
ActivityResultLauncher<Intent> launcher = registerForActivityResult(
|
||||||
|
new ActivityResultContracts.StartActivityForResult(),
|
||||||
|
result -> importCustomWordsItem.onFileSelected(result)
|
||||||
|
);
|
||||||
|
|
||||||
|
importCustomWordsItem.setBrowseFilesLauncher(launcher);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ public class SettingsStore extends SettingsUI {
|
||||||
/************* internal settings *************/
|
/************* internal settings *************/
|
||||||
public final static int CLIPBOARD_PREVIEW_LENGTH = 20;
|
public final static int CLIPBOARD_PREVIEW_LENGTH = 20;
|
||||||
public final static int DELETE_WORDS_SEARCH_DELAY = 500; // ms
|
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_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_CONNECTION_TIMEOUT = 10000; // ms
|
||||||
public final static int DICTIONARY_DOWNLOAD_READ_TIMEOUT = 10000; // ms
|
public final static int DICTIONARY_DOWNLOAD_READ_TIMEOUT = 10000; // ms
|
||||||
|
|
|
||||||
|
|
@ -50,12 +50,17 @@ public class DictionaryProgressNotification extends DictionaryNotification {
|
||||||
|
|
||||||
|
|
||||||
public void showLoadingMessage(@NonNull String title, @NonNull String message) {
|
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.title = title;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
messageLong = "";
|
messageLong = "";
|
||||||
indeterminate = true;
|
this.progress = progress;
|
||||||
progress = 1;
|
this.maxProgress = maxProgress;
|
||||||
maxProgress = 2;
|
indeterminate = (progress <= 0 && maxProgress <= 0);
|
||||||
renderMessage();
|
renderMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,13 @@
|
||||||
<string name="dictionary_export_custom_words_summary">Експортиране на CSV с всички добавени думи в: „%1$s“.</string>
|
<string name="dictionary_export_custom_words_summary">Експортиране на CSV с всички добавени думи в: „%1$s“.</string>
|
||||||
<string name="dictionary_export_failed">Неуспешно експортиране</string>
|
<string name="dictionary_export_failed">Неуспешно експортиране</string>
|
||||||
<string name="dictionary_export_failed_more_info">За повече информация, активирайте режима за отстраняване на грешки и прегледайте журнала.</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_delete">Изтрий</string>
|
||||||
<string name="delete_words_link_summary">Намери и изтрий на неправилно написани или ненужни думи.</string>
|
<string name="delete_words_link_summary">Намери и изтрий на неправилно написани или ненужни думи.</string>
|
||||||
<string name="delete_words_search_placeholder">Търси думи</string>
|
<string name="delete_words_search_placeholder">Търси думи</string>
|
||||||
|
|
@ -154,4 +161,8 @@
|
||||||
<string name="function_select_keyboard">Избор на клавиатура</string>
|
<string name="function_select_keyboard">Избор на клавиатура</string>
|
||||||
<string name="function_voice_input">Гласово въвеждане</string>
|
<string name="function_voice_input">Гласово въвеждане</string>
|
||||||
<string name="add_word_no_confirmation">Добавяне без потвърждение</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>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -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_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">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_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_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_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>
|
<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="virtual_numpad_alignment_right">Rechts</string>
|
||||||
<string name="function_select_keyboard">Tastaturauswahl</string>
|
<string name="function_select_keyboard">Tastaturauswahl</string>
|
||||||
<string name="add_word_no_confirmation">Ohne Bestätigung hinzufügen</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>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -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_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">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_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_delete">Eliminar</string>
|
||||||
<string name="delete_words_link_summary">Buscar y eliminar palabras mal escritas o innecesarias.</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>
|
<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="virtual_numpad_alignment_right">A la derecha</string>
|
||||||
<string name="function_select_keyboard">Cambiar el teclado</string>
|
<string name="function_select_keyboard">Cambiar el teclado</string>
|
||||||
<string name="add_word_no_confirmation">Añadir sin confirmación</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>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -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_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">É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_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_delete">Supprimer</string>
|
||||||
<string name="delete_words_link_summary">Trouver et supprimer des mots mal orthographiés ou inutiles.</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>
|
<string name="delete_words_search_placeholder">Rechercher des mots</string>
|
||||||
|
|
@ -150,4 +157,8 @@
|
||||||
<string name="virtual_numpad_alignment_right">À droite</string>
|
<string name="virtual_numpad_alignment_right">À droite</string>
|
||||||
<string name="function_select_keyboard">Choisir le clavier</string>
|
<string name="function_select_keyboard">Choisir le clavier</string>
|
||||||
<string name="add_word_no_confirmation">Ajouter sans confirmation</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>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -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_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">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_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_delete">Elimina</string>
|
||||||
<string name="delete_words_link_summary">Trova ed elimina parole errate o non necessarie.</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>
|
<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_left">A sinistra</string>
|
||||||
<string name="virtual_numpad_alignment_right">A destra</string>
|
<string name="virtual_numpad_alignment_right">A destra</string>
|
||||||
<string name="add_word_no_confirmation">Aggiungere senza conferma</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>
|
</resources>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,13 @@
|
||||||
<string name="dictionary_export_custom_words_summary">ייצוא CSV עם כל המילים שנוספו ל: \"%1$s\".</string>
|
<string name="dictionary_export_custom_words_summary">ייצוא CSV עם כל המילים שנוספו ל: \"%1$s\".</string>
|
||||||
<string name="dictionary_export_failed">נכשל בייצוא</string>
|
<string name="dictionary_export_failed">נכשל בייצוא</string>
|
||||||
<string name="dictionary_export_failed_more_info">"למידע נוסף, הפעל מצב איתור באגים וראה את הלוגים. "</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_delete">מחיקה</string>
|
||||||
<string name="delete_words_link_summary">מצא ומחק מילים שכתובות בטעות או שאינן נדרשות.</string>
|
<string name="delete_words_link_summary">מצא ומחק מילים שכתובות בטעות או שאינן נדרשות.</string>
|
||||||
<string name="delete_words_search_placeholder">חיפוש מילים</string>
|
<string name="delete_words_search_placeholder">חיפוש מילים</string>
|
||||||
|
|
@ -155,4 +162,8 @@
|
||||||
<string name="virtual_numpad_alignment_right">ימינה</string>
|
<string name="virtual_numpad_alignment_right">ימינה</string>
|
||||||
<string name="function_select_keyboard">בחירת מקלדת</string>
|
<string name="function_select_keyboard">בחירת מקלדת</string>
|
||||||
<string name="add_word_no_confirmation">להוסיף ללא אישור</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>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,13 @@
|
||||||
<string name="dictionary_export_finished_more_info">Žodžiai eksportuoti į: „%1$s“.</string>
|
<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">Eksportuojama CSV…</string>
|
||||||
<string name="dictionary_export_generating_csv_for_language">Eksportuojama CSV (%1$s)…</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_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_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>
|
<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="virtual_numpad_alignment_right">Dešinėje</string>
|
||||||
<string name="function_select_keyboard">Keisti klaviatūrą</string>
|
<string name="function_select_keyboard">Keisti klaviatūrą</string>
|
||||||
<string name="add_word_no_confirmation">Pridėti be patvirtinimo</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>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -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_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">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_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_delete">Verwijderen</string>
|
||||||
<string name="delete_words_link_summary">Zoek en verwijder verkeerd gespelde of onnodige woorden.</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>
|
<string name="delete_words_search_placeholder">Zoeken naar woorden</string>
|
||||||
|
|
@ -141,4 +148,8 @@
|
||||||
<string name="virtual_numpad_alignment_right">Rechts</string>
|
<string name="virtual_numpad_alignment_right">Rechts</string>
|
||||||
<string name="function_select_keyboard">Toetsenbordkeuze</string>
|
<string name="function_select_keyboard">Toetsenbordkeuze</string>
|
||||||
<string name="add_word_no_confirmation">Toevoegen zonder bevestiging</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>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -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_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">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_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_delete">Excluir</string>
|
||||||
<string name="delete_words_link_summary">Encontrar e excluir palavras escritas incorretamente ou desnecessárias.</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>
|
<string name="delete_words_search_placeholder">Buscar palavras</string>
|
||||||
|
|
@ -155,4 +162,8 @@
|
||||||
<string name="virtual_numpad_alignment_right">À direita</string>
|
<string name="virtual_numpad_alignment_right">À direita</string>
|
||||||
<string name="function_select_keyboard">Mude o teclado</string>
|
<string name="function_select_keyboard">Mude o teclado</string>
|
||||||
<string name="add_word_no_confirmation">Adicionar sem confirmação</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>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,13 @@
|
||||||
<string name="dictionary_export_custom_words_summary">Экспорт CSV со всеми добавленными словами в: «%1$s».</string>
|
<string name="dictionary_export_custom_words_summary">Экспорт CSV со всеми добавленными словами в: «%1$s».</string>
|
||||||
<string name="dictionary_export_failed">Ошибка экспорта</string>
|
<string name="dictionary_export_failed">Ошибка экспорта</string>
|
||||||
<string name="dictionary_export_failed_more_info">Для получения дополнительной информации включите режим отладки и просмотрите журналы.</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_delete">Удалить</string>
|
||||||
<string name="delete_words_link_summary">Найти и удалить ошибочно написанные или ненужные слова.</string>
|
<string name="delete_words_link_summary">Найти и удалить ошибочно написанные или ненужные слова.</string>
|
||||||
<string name="delete_words_search_placeholder">Поиск слов</string>
|
<string name="delete_words_search_placeholder">Поиск слов</string>
|
||||||
|
|
@ -152,4 +159,8 @@
|
||||||
<string name="virtual_numpad_alignment_right">Направо</string>
|
<string name="virtual_numpad_alignment_right">Направо</string>
|
||||||
<string name="function_select_keyboard">Выбор клавиатуры</string>
|
<string name="function_select_keyboard">Выбор клавиатуры</string>
|
||||||
<string name="add_word_no_confirmation">Добавить без подтверждения</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>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -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_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">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ı açıp kayıtları kontrol edin.</string>
|
<string name="dictionary_export_failed_more_info">Daha fazla bilgi için Hata Ayıklamayı açı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_delete">Sil</string>
|
||||||
<string name="delete_words_link_summary">Yanlış yazılan ya da gereksiz kelimeleri bulun ve silin.</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>
|
<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="virtual_numpad_alignment_right">Sağa</string>
|
||||||
<string name="function_select_keyboard">Klavye Seçimi</string>
|
<string name="function_select_keyboard">Klavye Seçimi</string>
|
||||||
<string name="add_word_no_confirmation">Onay olmadan ekle</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>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,13 @@
|
||||||
<string name="dictionary_export_custom_words_summary">Експорт CSV з усіма доданими словами в: \"%1$s\".</string>
|
<string name="dictionary_export_custom_words_summary">Експорт CSV з усіма доданими словами в: \"%1$s\".</string>
|
||||||
<string name="dictionary_export_failed">Помилка експорту</string>
|
<string name="dictionary_export_failed">Помилка експорту</string>
|
||||||
<string name="dictionary_export_failed_more_info">Для отримання додаткової інформації увімкніть режим відлагодження та перегляньте журнали.</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_delete">Видалити</string>
|
||||||
<string name="delete_words_link_summary">Знайти та видалити неправильно написані або зайві слова.</string>
|
<string name="delete_words_link_summary">Знайти та видалити неправильно написані або зайві слова.</string>
|
||||||
<string name="delete_words_search_placeholder">Пошук слів</string>
|
<string name="delete_words_search_placeholder">Пошук слів</string>
|
||||||
|
|
@ -163,4 +170,8 @@
|
||||||
<string name="virtual_numpad_alignment_right">Праворуч</string>
|
<string name="virtual_numpad_alignment_right">Праворуч</string>
|
||||||
<string name="function_select_keyboard">Змінити клавіатуру</string>
|
<string name="function_select_keyboard">Змінити клавіатуру</string>
|
||||||
<string name="add_word_no_confirmation">Додати без підтвердження</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>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,18 @@
|
||||||
<string name="dictionary_export_generating_csv">Exporting CSV…</string>
|
<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_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_delete">Delete</string>
|
||||||
<string name="delete_words_link_summary">Find and delete misspelled or unneeded words.</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>
|
<string name="delete_words_search_placeholder">Search for Words</string>
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,11 @@
|
||||||
app:key="add_word_no_confirmation"
|
app:key="add_word_no_confirmation"
|
||||||
app:title="@string/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
|
<Preference
|
||||||
app:key="dictionary_export_custom"
|
app:key="dictionary_export_custom"
|
||||||
app:title="@string/dictionary_export_custom_words" />
|
app:title="@string/dictionary_export_custom_words" />
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue