New dictionary format (#662)
* new dictionary format that supports syllabaries * optimized the dictionary build cache significantly to truly build only the changed language files * code style fixes
This commit is contained in:
parent
56b355631a
commit
da5b4f17b7
62 changed files with 871 additions and 397 deletions
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:tools="http://schemas.android.com/tools"
|
||||
android:versionCode="774"
|
||||
android:versionName="40.0"
|
||||
android:versionCode="775"
|
||||
android:versionName="40.1"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <!-- allows displaying notifications on Android >= 13 -->
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ public class CustomWordFile {
|
|||
return null;
|
||||
}
|
||||
|
||||
String[] parts = WordFile.splitLine(line);
|
||||
String[] parts = WordFile.getLineData(line);
|
||||
if (parts == null || parts.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -79,7 +79,7 @@ public class CustomWordFile {
|
|||
}
|
||||
|
||||
@NonNull public static String getWord(String line) {
|
||||
String[] parts = WordFile.splitLine(line);
|
||||
String[] parts = WordFile.getLineData(line);
|
||||
return parts != null && parts.length > 0 ? parts[0] : "";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,16 +4,14 @@ import androidx.annotation.NonNull;
|
|||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import io.github.sspanak.tt9.languages.exceptions.InvalidLanguageCharactersException;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.exceptions.InvalidLanguageCharactersException;
|
||||
|
||||
public class WordBatch {
|
||||
@NonNull private final Language language;
|
||||
@NonNull private final ArrayList<Word> words;
|
||||
@NonNull private final ArrayList<WordPosition> positions;
|
||||
|
||||
private WordPosition lastWordPosition;
|
||||
|
||||
public WordBatch(@NonNull Language language, int size) {
|
||||
this.language = language;
|
||||
words = size > 0 ? new ArrayList<>(size) : new ArrayList<>();
|
||||
|
|
@ -24,31 +22,25 @@ public class WordBatch {
|
|||
this(language, 0);
|
||||
}
|
||||
|
||||
public boolean add(@NonNull String word, int frequency, int position) throws InvalidLanguageCharactersException {
|
||||
public void add(String word, int frequency, int position) throws InvalidLanguageCharactersException {
|
||||
words.add(Word.create(word, frequency, position));
|
||||
positions.add(WordPosition.create(language.getDigitSequenceForWord(word), position, position));
|
||||
}
|
||||
|
||||
public void add(@NonNull ArrayList<String> words, @NonNull String digitSequence, int position) {
|
||||
if (words.isEmpty() || digitSequence.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0, size = words.size(); i < size; i++) {
|
||||
this.words.add(Word.create(words.get(i), size - i, position + i));
|
||||
}
|
||||
|
||||
if (position == 0) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
String sequence = language.getDigitSequenceForWord(word);
|
||||
|
||||
if (position == 1 || lastWordPosition == null) {
|
||||
lastWordPosition = WordPosition.create(sequence, position);
|
||||
} else {
|
||||
lastWordPosition.end = position;
|
||||
}
|
||||
|
||||
if (!sequence.equals(lastWordPosition.sequence)) {
|
||||
lastWordPosition.end--;
|
||||
positions.add(lastWordPosition);
|
||||
|
||||
lastWordPosition = WordPosition.create(sequence, position);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
positions.add(WordPosition.create(digitSequence, position, position + words.size() - 1));
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
|
|
|
|||
|
|
@ -4,13 +4,17 @@ import android.content.Context;
|
|||
import android.content.res.AssetManager;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
||||
|
|
@ -21,26 +25,34 @@ public class WordFile {
|
|||
|
||||
private final AssetManager assets;
|
||||
private final Context context;
|
||||
private final String name;
|
||||
private final String path;
|
||||
|
||||
private int lastCharCode;
|
||||
private BufferedReader reader;
|
||||
|
||||
private String hash = null;
|
||||
private String downloadUrl = null;
|
||||
private int totalLines = -1;
|
||||
private int words = -1;
|
||||
private long size = -1;
|
||||
private int sequences = -1;
|
||||
|
||||
|
||||
public WordFile(Context context, String name, AssetManager assets) {
|
||||
public WordFile(Context context, String path, AssetManager assets) {
|
||||
this.assets = assets;
|
||||
this.context = context;
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
|
||||
lastCharCode = 0;
|
||||
reader = null;
|
||||
}
|
||||
|
||||
|
||||
public static String[] splitLine(String line) {
|
||||
String[] parts = { line, "" };
|
||||
public static String[] getLineData(String line) {
|
||||
String[] parts = {line, ""};
|
||||
|
||||
// This is faster than String.split() by around 10%, so it's worth having it.
|
||||
// It runs very often, so any other optimizations are welcome.
|
||||
for (int i = 0 ; i < line.length(); i++) {
|
||||
for (int i = 0; i < line.length(); i++) {
|
||||
if (line.charAt(i) == ' ') { // the delimiter is TAB
|
||||
parts[0] = line.substring(0, i);
|
||||
parts[1] = i < line.length() - 1 ? line.substring(i + 1) : "";
|
||||
|
|
@ -52,18 +64,9 @@ public class WordFile {
|
|||
}
|
||||
|
||||
|
||||
public static short getFrequencyFromLineParts(String[] frequencyParts) {
|
||||
try {
|
||||
return Short.parseShort(frequencyParts[1]);
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean exists() {
|
||||
try {
|
||||
assets.open(name).close();
|
||||
assets.open(path).close();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
|
|
@ -80,8 +83,17 @@ public class WordFile {
|
|||
|
||||
|
||||
public BufferedReader getReader() throws IOException {
|
||||
InputStream stream = exists() ? assets.open(name) : getRemoteStream();
|
||||
return new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
|
||||
if (reader != null) {
|
||||
return reader;
|
||||
}
|
||||
|
||||
InputStream stream = exists() ? assets.open(path) : getRemoteStream();
|
||||
ZipInputStream zipStream = new ZipInputStream(stream);
|
||||
ZipEntry entry = zipStream.getNextEntry();
|
||||
if (entry == null) {
|
||||
throw new IOException("Dictionary ZIP file: " + path + " is empty.");
|
||||
}
|
||||
return reader = new BufferedReader(new InputStreamReader(zipStream, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -99,12 +111,20 @@ public class WordFile {
|
|||
return;
|
||||
}
|
||||
|
||||
String revision = rawValue == null || rawValue.isEmpty() ? "" : rawValue;
|
||||
downloadUrl = revision.isEmpty() ? null : context.getString(R.string.dictionary_url, revision, name);
|
||||
downloadUrl = null;
|
||||
|
||||
String revision = rawValue == null || rawValue.isEmpty() ? "" : rawValue;
|
||||
if (revision.isEmpty()) {
|
||||
Logger.w(LOG_TAG, "Invalid 'revision' property of: " + name + ". Expecting a string, got: '" + rawValue + "'.");
|
||||
Logger.w(LOG_TAG, "Invalid 'revision' property of: " + path + ". Expecting a string, got: '" + rawValue + "'.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (path == null || path.isEmpty()) {
|
||||
Logger.w(LOG_TAG, "Cannot generate a download URL for an empty path.");
|
||||
return;
|
||||
}
|
||||
|
||||
downloadUrl = context.getString(R.string.dictionary_url, revision, new File(path).getName());
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -125,39 +145,62 @@ public class WordFile {
|
|||
hash = rawValue == null || rawValue.isEmpty() ? "" : rawValue;
|
||||
|
||||
if (hash.isEmpty()) {
|
||||
Logger.w(LOG_TAG, "Invalid 'hash' property of: " + name + ". Expecting a string, got: '" + rawValue + "'.");
|
||||
Logger.w(LOG_TAG, "Invalid 'hash' property of: " + path + ". Expecting a string, got: '" + rawValue + "'.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int getTotalLines() {
|
||||
if (totalLines < 0) {
|
||||
public int getSequences() {
|
||||
if (sequences < 0) {
|
||||
loadProperties();
|
||||
}
|
||||
|
||||
return totalLines;
|
||||
return sequences;
|
||||
}
|
||||
|
||||
|
||||
public String getFormattedTotalLines(String suffix) {
|
||||
if (getTotalLines() > 1000000) {
|
||||
return String.format(Locale.ROOT, "%1.2fM %s", getTotalLines() / 1000000.0, suffix);
|
||||
} else {
|
||||
return getTotalLines() / 1000 + "k " + suffix;
|
||||
private void setSequences(String rawProperty, String rawValue) {
|
||||
if (!rawProperty.equals("sequences")) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
sequences = Integer.parseInt(rawValue);
|
||||
} catch (Exception e) {
|
||||
Logger.w(LOG_TAG, "Invalid 'sequences' property of: " + path + ". Expecting an integer, got: '" + rawValue + "'.");
|
||||
sequences = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void setTotalLines(String rawProperty, String rawValue) {
|
||||
public int getWords() {
|
||||
if (words < 0) {
|
||||
loadProperties();
|
||||
}
|
||||
|
||||
return words;
|
||||
}
|
||||
|
||||
|
||||
public String getFormattedWords(String suffix) {
|
||||
if (getWords() > 1000000) {
|
||||
return String.format(Locale.ROOT, "%1.2fM %s", getWords() / 1000000.0, suffix);
|
||||
} else {
|
||||
return getWords() / 1000 + "k " + suffix;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void setWords(String rawProperty, String rawValue) {
|
||||
if (!rawProperty.equals("words")) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
totalLines = Integer.parseInt(rawValue);
|
||||
words = Integer.parseInt(rawValue);
|
||||
} catch (Exception e) {
|
||||
Logger.w(LOG_TAG, "Invalid 'words' property of: " + name + ". Expecting an integer, got: '" + rawValue + "'.");
|
||||
totalLines = 0;
|
||||
Logger.w(LOG_TAG, "Invalid 'words' property of: " + path + ". Expecting an integer, got: '" + rawValue + "'.");
|
||||
words = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -184,14 +227,14 @@ public class WordFile {
|
|||
try {
|
||||
size = Long.parseLong(rawValue);
|
||||
} catch (Exception e) {
|
||||
Logger.w(LOG_TAG, "Invalid 'size' property of: " + name + ". Expecting an integer, got: '" + rawValue + "'.");
|
||||
Logger.w(LOG_TAG, "Invalid 'size' property of: " + path + ". Expecting an integer, got: '" + rawValue + "'.");
|
||||
size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void loadProperties() {
|
||||
String propertyFilename = name + ".props.yml";
|
||||
String propertyFilename = path.replaceFirst("\\.\\w+$", "") + ".props.yml";
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(assets.open(propertyFilename)))) {
|
||||
for (String line; (line = reader.readLine()) != null; ) {
|
||||
|
|
@ -202,11 +245,102 @@ public class WordFile {
|
|||
|
||||
setDownloadUrl(parts[0], parts[1]);
|
||||
setHash(parts[0], parts[1]);
|
||||
setTotalLines(parts[0], parts[1]);
|
||||
setWords(parts[0], parts[1]);
|
||||
setSequences(parts[0], parts[1]);
|
||||
setSize(parts[0], parts[1]);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.w(LOG_TAG, "Could not read the property file: " + propertyFilename + ". " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public String getNextSequence() throws IOException {
|
||||
if (reader == null || !notEOF()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder sequence = new StringBuilder();
|
||||
|
||||
// use the last char from getNextWords() if it's a digit
|
||||
if (Character.isDigit(lastCharCode)) {
|
||||
sequence.append((char) lastCharCode);
|
||||
}
|
||||
|
||||
while ((lastCharCode = reader.read()) != -1) {
|
||||
if (Character.isDigit(lastCharCode)) {
|
||||
sequence.append((char) lastCharCode);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sequence.length() == 0) {
|
||||
throw new IOException("Could not find next sequence. Unexpected end of file.");
|
||||
}
|
||||
|
||||
return sequence.toString();
|
||||
}
|
||||
|
||||
|
||||
public ArrayList<String> getNextWords(String digitSequence) throws IOException {
|
||||
ArrayList<String> words = new ArrayList<>();
|
||||
|
||||
if (reader == null || !notEOF()) {
|
||||
return words;
|
||||
}
|
||||
|
||||
boolean areWordsSeparated = false;
|
||||
StringBuilder word = new StringBuilder();
|
||||
|
||||
// If the word string starts with a space, it means there are words longer than the sequence.
|
||||
// We must make sure to extract them correctly.
|
||||
if (lastCharCode == ' ') {
|
||||
areWordsSeparated = true;
|
||||
}
|
||||
// use the last char from getNextSequence() if it's a letter
|
||||
else if (!Character.isDigit(lastCharCode)) {
|
||||
word.append((char) lastCharCode);
|
||||
}
|
||||
|
||||
int sequenceLength = digitSequence.length();
|
||||
|
||||
// start extracting the words
|
||||
int wordLength = word.length();
|
||||
while ((lastCharCode = reader.read()) != -1) {
|
||||
if (Character.isDigit(lastCharCode)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (lastCharCode == ' ') {
|
||||
areWordsSeparated = true;
|
||||
} else {
|
||||
word.append((char) lastCharCode);
|
||||
wordLength++;
|
||||
}
|
||||
|
||||
if ((areWordsSeparated && lastCharCode == ' ' && wordLength > 0) || (!areWordsSeparated && wordLength == sequenceLength)) {
|
||||
words.add(word.toString());
|
||||
wordLength = 0;
|
||||
word.setLength(wordLength);
|
||||
}
|
||||
}
|
||||
|
||||
if ((areWordsSeparated && wordLength > 0) || (!areWordsSeparated && wordLength == sequenceLength)) {
|
||||
words.add(word.toString());
|
||||
} else if (wordLength > 0) {
|
||||
throw new IOException("Unexpected end of file. Word: '" + word + "' length (" + wordLength + ") differs from the length of sequence: " + digitSequence);
|
||||
}
|
||||
|
||||
if (words.isEmpty()) {
|
||||
throw new IOException("Could not find any words for sequence: " + digitSequence);
|
||||
}
|
||||
|
||||
return words;
|
||||
}
|
||||
|
||||
|
||||
public boolean notEOF() {
|
||||
return lastCharCode != -1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@ public class WordPosition {
|
|||
public int start;
|
||||
public int end;
|
||||
|
||||
public static WordPosition create(@NonNull String sequence, int start) {
|
||||
public static WordPosition create(@NonNull String sequence, int start, int end) {
|
||||
WordPosition position = new WordPosition();
|
||||
position.sequence = sequence;
|
||||
position.start = start;
|
||||
position.end = end;
|
||||
|
||||
return position;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
package io.github.sspanak.tt9.db.exceptions;
|
||||
|
||||
public class DictionaryImportException extends Exception {
|
||||
public final String word;
|
||||
public final long line;
|
||||
|
||||
public DictionaryImportException(String word, long line) {
|
||||
super("Dictionary import failed");
|
||||
this.word = word;
|
||||
public DictionaryImportException(String message, long line) {
|
||||
super(message);
|
||||
this.line = line;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import android.os.Bundle;
|
|||
import android.os.Handler;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
|
|
@ -79,32 +80,32 @@ public class DictionaryLoader {
|
|||
return true;
|
||||
}
|
||||
|
||||
loadThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
currentFile = 0;
|
||||
Timer.start(IMPORT_TIMER);
|
||||
|
||||
sendStartMessage(languages.size());
|
||||
|
||||
// SQLite does not support parallel queries, so let's import them one by one
|
||||
for (Language lang : languages) {
|
||||
if (isInterrupted()) {
|
||||
break;
|
||||
}
|
||||
importAll(context, lang);
|
||||
currentFile++;
|
||||
}
|
||||
|
||||
Timer.stop(IMPORT_TIMER);
|
||||
}
|
||||
};
|
||||
|
||||
loadThread = new Thread(() -> loadSync(context, languages));
|
||||
loadThread.start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void loadSync(Context context, ArrayList<Language> languages) {
|
||||
currentFile = 0;
|
||||
Timer.start(IMPORT_TIMER);
|
||||
|
||||
sendStartMessage(languages.size());
|
||||
|
||||
// SQLite does not support parallel queries, so let's import them one by one
|
||||
for (Language lang : languages) {
|
||||
if (loadThread.isInterrupted()) {
|
||||
break;
|
||||
}
|
||||
importAll(context, lang);
|
||||
currentFile++;
|
||||
}
|
||||
|
||||
Timer.stop(IMPORT_TIMER);
|
||||
}
|
||||
|
||||
|
||||
public static void load(Context context, Language language) {
|
||||
DictionaryLoadingBar progressBar = DictionaryLoadingBar.getInstance(context);
|
||||
getInstance(context).setOnStatusChange(status -> progressBar.show(context, status));
|
||||
|
|
@ -210,13 +211,11 @@ public class DictionaryLoader {
|
|||
} catch (DictionaryImportException e) {
|
||||
stop();
|
||||
sqlite.failTransaction();
|
||||
sendImportError(DictionaryImportException.class.getSimpleName(), language.getId(), e.line, e.word);
|
||||
sendImportError(DictionaryImportException.class.getSimpleName(), language.getId(), e.line);
|
||||
|
||||
Logger.e(
|
||||
LOG_TAG,
|
||||
" Invalid word: '" + e.word
|
||||
+ "' in dictionary: '" + language.getDictionaryFile() + "'"
|
||||
+ " on line " + e.line
|
||||
" Invalid word in dictionary: '" + language.getDictionaryFile() + "'"
|
||||
+ " of language '" + language.getName() + "'. "
|
||||
+ e.getMessage()
|
||||
);
|
||||
|
|
@ -256,34 +255,32 @@ public class DictionaryLoader {
|
|||
|
||||
private void importWordFile(Context context, Language language, int positionShift, float minProgress, float maxProgress) throws Exception {
|
||||
WordFile wordFile = new WordFile(context, language.getDictionaryFile(), assets);
|
||||
WordBatch batch = new WordBatch(language, wordFile.getTotalLines());
|
||||
int currentLine = 1;
|
||||
float progressRatio = (maxProgress - minProgress) / wordFile.getTotalLines();
|
||||
WordBatch batch = new WordBatch(language, SettingsStore.DICTIONARY_IMPORT_BATCH_SIZE + 1);
|
||||
float progressRatio = (maxProgress - minProgress) / wordFile.getWords();
|
||||
int wordCount = 0;
|
||||
|
||||
try (BufferedReader br = wordFile.getReader()) {
|
||||
for (String line; (line = br.readLine()) != null; currentLine++) {
|
||||
try (BufferedReader ignored = wordFile.getReader()) {
|
||||
while (wordFile.notEOF()) {
|
||||
if (loadThread.isInterrupted()) {
|
||||
sendProgressMessage(language, 0, 0);
|
||||
throw new DictionaryImportAbortedException();
|
||||
}
|
||||
|
||||
String[] parts = WordFile.splitLine(line);
|
||||
String word = parts[0];
|
||||
short frequency = WordFile.getFrequencyFromLineParts(parts);
|
||||
|
||||
try {
|
||||
boolean isFinalized = batch.add(word, frequency, currentLine + positionShift);
|
||||
if (isFinalized && batch.getWords().size() > SettingsStore.DICTIONARY_IMPORT_BATCH_SIZE) {
|
||||
String digitSequence = wordFile.getNextSequence();
|
||||
ArrayList<String> words = wordFile.getNextWords(digitSequence);
|
||||
batch.add(words, digitSequence, wordCount + positionShift);
|
||||
wordCount += words.size();
|
||||
|
||||
if (batch.getWords().size() > SettingsStore.DICTIONARY_IMPORT_BATCH_SIZE) {
|
||||
saveWordBatch(batch);
|
||||
batch.clear();
|
||||
}
|
||||
} catch (InvalidLanguageCharactersException e) {
|
||||
throw new DictionaryImportException(word, currentLine);
|
||||
} catch (IOException e) {
|
||||
throw new DictionaryImportException(e.getMessage(), wordCount);
|
||||
}
|
||||
|
||||
if (wordFile.getTotalLines() > 0) {
|
||||
sendProgressMessage(language, minProgress + progressRatio * currentLine, SettingsStore.DICTIONARY_IMPORT_PROGRESS_UPDATE_TIME);
|
||||
}
|
||||
sendProgressMessage(language, minProgress + progressRatio * wordCount, SettingsStore.DICTIONARY_IMPORT_PROGRESS_UPDATE_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -353,7 +350,7 @@ public class DictionaryLoader {
|
|||
}
|
||||
|
||||
|
||||
private void sendImportError(String message, int langId, long fileLine, String word) {
|
||||
private void sendImportError(String message, int langId, long fileLine) {
|
||||
if (onStatusChange == null) {
|
||||
Logger.w(LOG_TAG, "Cannot send an import error without a status Handler. Ignoring message.");
|
||||
return;
|
||||
|
|
@ -363,14 +360,13 @@ public class DictionaryLoader {
|
|||
errorMsg.putString("error", message);
|
||||
errorMsg.putLong("fileLine", fileLine + 1);
|
||||
errorMsg.putInt("languageId", langId);
|
||||
errorMsg.putString("word", word);
|
||||
asyncHandler.post(() -> onStatusChange.accept(errorMsg));
|
||||
}
|
||||
|
||||
|
||||
private void logLoadingStep(String message, Language language, long time) {
|
||||
if (Logger.isDebugLevel()) {
|
||||
Logger.d(LOG_TAG, message + " for language '" + language.getName() + "' in: " + time + " ms");
|
||||
Logger.d(LOG_TAG, message + " for language '" + language.getName() + "' (" + language.getId() + ") in: " + time + " ms");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import io.github.sspanak.tt9.BuildConfig;
|
||||
import io.github.sspanak.tt9.util.Logger;
|
||||
|
||||
public class LanguageDefinition {
|
||||
|
|
@ -93,6 +94,9 @@ public class LanguageDefinition {
|
|||
definition.locale = getPropertyFromYaml(yaml, "locale", definition.locale);
|
||||
definition.name = getPropertyFromYaml(yaml, "name", definition.name);
|
||||
|
||||
if (definition.dictionaryFile != null) {
|
||||
definition.dictionaryFile = definition.dictionaryFile.replaceFirst("\\.\\w+$", "." + BuildConfig.DICTIONARY_EXTENSION);
|
||||
}
|
||||
return definition;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ public class PreferenceSwitchLanguage extends SwitchPreferenceCompat {
|
|||
summary
|
||||
.append(", ")
|
||||
.append(
|
||||
wordFile.getFormattedTotalLines(activity.getString(R.string.language_selection_words))
|
||||
wordFile.getFormattedWords(activity.getString(R.string.language_selection_words))
|
||||
);
|
||||
|
||||
// download size
|
||||
|
|
|
|||
|
|
@ -75,8 +75,7 @@ public class DictionaryLoadingBar extends DictionaryProgressNotification {
|
|||
context,
|
||||
error,
|
||||
data.getInt("languageId", -1),
|
||||
data.getLong("fileLine", -1),
|
||||
data.getString("word", "")
|
||||
data.getLong("fileLine", -1)
|
||||
);
|
||||
} else if (progress >= 0) {
|
||||
hasFailed = false;
|
||||
|
|
@ -133,13 +132,13 @@ public class DictionaryLoadingBar extends DictionaryProgressNotification {
|
|||
}
|
||||
|
||||
|
||||
private void showError(Context context, String errorType, int langId, long line, String word) {
|
||||
private void showError(Context context, String errorType, int langId, long line) {
|
||||
Language lang = LanguageCollection.getLanguage(context, langId);
|
||||
|
||||
if (lang == null || errorType.equals(InvalidLanguageException.class.getSimpleName())) {
|
||||
message = resources.getString(R.string.add_word_invalid_language);
|
||||
} else if (errorType.equals(DictionaryImportException.class.getSimpleName()) || errorType.equals(InvalidLanguageCharactersException.class.getSimpleName())) {
|
||||
message = resources.getString(R.string.dictionary_load_bad_char, word, line, lang.getName());
|
||||
message = resources.getString(R.string.dictionary_load_bad_char, line, lang.getName());
|
||||
} else if (errorType.equals(UnknownHostException.class.getSimpleName()) || errorType.equals(SocketException.class.getSimpleName())) {
|
||||
message = resources.getString(R.string.dictionary_load_no_internet, lang.getName());
|
||||
} else if (errorType.equals(IOException.class.getSimpleName()) || errorType.equals(FileNotFoundException.class.getSimpleName())) {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
<string name="pref_status_icon">Икона за състояние</string>
|
||||
<string name="pref_status_icon_summary">Показвай икона, когато въвеждането с клавиатура е активно.</string>
|
||||
<string name="dictionary_cancel_load">Отмени зареждането</string>
|
||||
<string name="dictionary_load_bad_char">Неуспешно зареждане. Невалидна дума „%1$s“ на ред %2$d за език „%3$s“.</string>
|
||||
<string name="dictionary_load_bad_char">Неуспешно зареждане. Невалидна дума на ред %1$d за език „%2$s“.</string>
|
||||
<string name="dictionary_load_error">Несупешно зареждане на речник за език „%1$s“ (%2$s).</string>
|
||||
<string name="dictionary_loaded">Зареждането на речник приключи.</string>
|
||||
<string name="dictionary_loading">Зареждане на речник (%1$s)…</string>
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@
|
|||
<string name="pref_upside_down_keys">Die Reihenfolge der Tasten umkehren</string>
|
||||
<string name="pref_upside_down_keys_summary">Aktivieren Sie, wenn die Tastatur in der ersten Zeile 7–8–9 anstelle von 1–2–3 hat.</string>
|
||||
<string name="dictionary_cancel_load">Laden abbrechen</string>
|
||||
<string name="dictionary_load_bad_char">Laden fehlgeschlagen. Ungültiges Wort „%1$s“ in Zeile %2$d der Sprache „%3$s“.</string>
|
||||
<string name="dictionary_load_bad_char">Laden fehlgeschlagen. Ungültiges Wort in Zeile %1$d der Sprache „%2$s“.</string>
|
||||
<string name="dictionary_load_error">Fehler beim Laden des Wörterbuchs für die Sprache „%1$s“ (%2$s).</string>
|
||||
<string name="dictionary_load_no_internet">Fehler beim Herunterladen des Wörterbuchs für die Sprache „%1$s“. Überprüfen Sie die Internetverbindung.</string>
|
||||
<string name="dictionary_load_cancelled">Laden abgebrochen.</string>
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@
|
|||
<string name="pref_auto_space">Espacio automático</string>
|
||||
<string name="pref_auto_space_summary">Insertar un espacio automático después de palabras y signos de puntuación.</string>
|
||||
<string name="pref_double_zero_char">Carácter cuando se presiona \"0\" dos veces</string>
|
||||
<string name="dictionary_load_bad_char">Error al cargar. Palabra inválida \"%1$s\" en la línea %2$d del idioma \"%3$s\".</string>
|
||||
<string name="dictionary_load_bad_char">Error al cargar. Palabra inválida en la línea %1$d del idioma \"%2$s\".</string>
|
||||
<string name="dictionary_load_error">Error al cargar el diccionario para el idioma \"%1$s\" (%2$s).</string>
|
||||
<string name="dictionary_load_no_internet">Error al descargar el diccionario para el idioma \"%1$s\". Verifique la conexión a Internet.</string>
|
||||
<string name="dictionary_load_cancelled">Carga del diccionario cancelada.</string>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
<string name="pref_category_delete_words">Supprimer des mots ajoutés</string>
|
||||
<string name="pref_category_hacks">Compatibilité</string>
|
||||
<string name="pref_category_appearance">Apparance</string>
|
||||
<string name="dictionary_load_bad_char">Echec du chargement. Mot inadmissible «%1$s» à la ligne %2$d de langue «%3$s».</string>
|
||||
<string name="dictionary_load_bad_char">Echec du chargement. Mot inadmissible à la ligne %1$d de langue «%2$s».</string>
|
||||
<string name="dictionary_truncated">Le dictionaire est supprimé avec succès.</string>
|
||||
<string name="pref_hack_fb_messenger">Envoyer avec «OK» dans Facebook Messenger</string>
|
||||
<string name="pref_hack_always_on_top">Toujours au premier plan</string>
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
<string name="pref_category_predictive_mode">Scrittura facilitata</string>
|
||||
<string name="pref_category_function_keys">Scorciatoie da tastiera</string>
|
||||
<string name="dictionary_loading_indeterminate">Caricamento del dizionario</string>
|
||||
<string name="dictionary_load_bad_char">Caricamento non riuscito. Parola non valida \"%1$s\" alla riga %2$d della lingua \"%3$s\".</string>
|
||||
<string name="dictionary_load_bad_char">Caricamento non riuscito. Parola non valida alla riga %1$d della lingua \"%2$s\".</string>
|
||||
<string name="dictionary_load_error">Caricamento del dizionario per la lingua \"%1$s\" non riuscito (%2$s).</string>
|
||||
<string name="dictionary_load_no_internet">"Download del dizionario per la lingua \"%1$s\" non riuscito. Controlla la connessione Internet. "</string>
|
||||
<string name="dictionary_load_cancelled">Caricamento annullato.</string>
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@
|
|||
<string name="pref_upside_down_keys">להפוך את סדר המקשים</string>
|
||||
<string name="pref_upside_down_keys_summary">הפעל את ההגדרה אם המקלדת כוללת את המספרים 7-8-9 בשורה הראשונה, במקום 1-2-3.</string>
|
||||
<string name="dictionary_cancel_load">ביטול טעינה</string>
|
||||
<string name="dictionary_load_bad_char">הטעינה נכשלה. מילה לא חוקית \"%1$s\" בשורה %2$d עבור \"%3$s\".</string>
|
||||
<string name="dictionary_load_bad_char">הטעינה נכשלה. מילה לא חוקית בשורה %1$d עבור \"%2$s\".</string>
|
||||
<string name="dictionary_load_error">נכשלה טעינת המילון עבור \"%1$s\" (%2$s).</string>
|
||||
<string name="dictionary_load_no_internet">נכשל בהורדת המילון עבור השפה \"%1$s\". בדוק את חיבור האינטרנט.</string>
|
||||
<string name="dictionary_load_cancelled">טעינת המילון בוטלה</string>
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@
|
|||
<string name="pref_status_icon">Būsenos piktograma</string>
|
||||
<string name="pref_status_icon_summary">Rodyti piktogramą, kai aktyvus klaviatūros įvedimas</string>
|
||||
<string name="dictionary_cancel_load">Atšaukti įkėlimą</string>
|
||||
<string name="dictionary_load_bad_char">Įkelti \"%3$s\" kalbos nepavyko. Klaida %2$d eilutėje, netinkamas žodis - \"%1$s\".</string>
|
||||
<string name="dictionary_load_bad_char">Įkelti \"%2$s\" kalbos nepavyko. Klaida %1$d eilutėje, netinkamas žodis.</string>
|
||||
<string name="dictionary_load_error">Klaida įkeliant \"%1$s\" kalbos žodyną (%2$s).</string>
|
||||
<string name="dictionary_load_no_internet">Nepavyko atsisiųsti žodyno kalbai „%1$s“. Patikrinkite interneto ryšį.</string>
|
||||
<string name="dictionary_load_cancelled">Žodyno įkėlimas atšauktas.</string>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
<string name="pref_upside_down_keys">De volgorde van de toetsen omkeren</string>
|
||||
<string name="pref_upside_down_keys_summary">Activeer als het toetsenbord 7–8–9 op de eerste rij heeft, in plaats van 1–2–3.</string>
|
||||
<string name="dictionary_cancel_load">Laden annuleren</string>
|
||||
<string name="dictionary_load_bad_char">Laden mislukt. Ongeldig woord \"%1$s\" op regel %2$d van taal \"%3$s\".</string>
|
||||
<string name="dictionary_load_bad_char">Laden mislukt. Ongeldig woord op regel %1$d van taal \"%2$s\".</string>
|
||||
<string name="dictionary_load_error">Het laden van het woordenboek voor de taal \"%1$s\" is mislukt (%2$s).</string>
|
||||
<string name="dictionary_load_no_internet">Het downloaden van het woordenboek voor de taal \"%1$s\" is mislukt. Controleer de internetverbinding.</string>
|
||||
<string name="dictionary_load_cancelled">Laden geannuleerd.</string>
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@
|
|||
<string name="pref_status_icon">Ícone de status</string>
|
||||
<string name="pref_status_icon_summary">Mostrar um ícone quando a digitação estiver ativa.</string>
|
||||
<string name="dictionary_cancel_load">Cancelar Carregamento</string>
|
||||
<string name="dictionary_load_bad_char">Falha no carregamento. \"%1$s\" na linha %2$d do idioma \"%3$s\".</string>
|
||||
<string name="dictionary_load_bad_char">Falha no carregamento. Palavra inválida na linha %1$d do idioma \"%2$s\".</string>
|
||||
<string name="dictionary_load_error">Falha no carregamento do dicionário para o idioma \"%1$s\" (%2$s).</string>
|
||||
<string name="dictionary_load_no_internet">Falha ao baixar o dicionário para o idioma \"%1$s\". Verifique a conexão com a Internet.</string>
|
||||
<string name="dictionary_load_cancelled">Carregamento de dicionário cancelado.</string>
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
<string name="pref_auto_text_case_summary">Автоматически начинать предложение с заглавной буквы.</string>
|
||||
<string name="pref_double_zero_char">Символ при двойном нажатии клавиши 0</string>
|
||||
<string name="pref_hack_fb_messenger">Отправка с «ОК» в Messenger</string>
|
||||
<string name="dictionary_load_bad_char">Не удалось загрузить словарь. Проблема в слове «%1$s» в строке %2$d для языка «%3$s».</string>
|
||||
<string name="dictionary_load_bad_char">Не удалось загрузить словарь. Проблема в слове в строке %1$d для языка «%2$s».</string>
|
||||
<string name="function_backspace">Стереть</string>
|
||||
<string name="dictionary_no_notifications">Уведомления словаря</string>
|
||||
<string name="dictionary_no_notifications_summary">Получать уведомления о обновлениях словаря и о процессе загрузки.</string>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
<string name="pref_status_icon">Durum</string>
|
||||
<string name="pref_status_icon_summary">Klavye girişi etkin olduğunda bir simge göster.</string>
|
||||
<string name="dictionary_cancel_load">Yüklemeyi İptal Et</string>
|
||||
<string name="dictionary_load_bad_char">Yükleme başarısız. \"%1$s\" sözcüğü \"%3$s\" dilinin %2$d satırında geçersiz.</string>
|
||||
<string name="dictionary_load_bad_char">Yükleme başarısız. \"%2$s\" dilinde %1$d. satırda geçersiz kelime.</string>
|
||||
<string name="dictionary_load_error">“%1$s” dili için sözlük yüklenemedi (%2$s).</string>
|
||||
<string name="dictionary_load_no_internet">“%1$s” dili için sözlük indirilemedi. İnternet bağlantısını kontrol edin.</string>
|
||||
<string name="dictionary_load_cancelled">Yükleme iptal edildi.</string>
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@
|
|||
<string name="pref_status_icon">Іконка статусу</string>
|
||||
<string name="pref_status_icon_summary">Показати іконку, коли активне введення з клавіатури.</string>
|
||||
<string name="dictionary_cancel_load">Скасувати завантаження</string>
|
||||
<string name="dictionary_load_bad_char">Завантаження не вдалося. Невірне слово \"%1$s\" у рядку %2$d мови \"%3$s\".</string>
|
||||
<string name="dictionary_load_bad_char">Завантаження не вдалося. Невірне слово у рядку %1$d мови \"%2$s\".</string>
|
||||
<string name="dictionary_load_error">Не вдалося завантажити словник для мови \"%1$s\" (%2$s).</string>
|
||||
<string name="dictionary_load_no_internet">Не вдалося завантажити словник для мови \"%1$s\". Перевірте підключення до Інтернету.</string>
|
||||
<string name="dictionary_load_cancelled">Завантаження словника скасовано.</string>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string translatable="false" name="dictionary_url">https://raw.githubusercontent.com/sspanak/tt9/%1$s/app/%2$s</string>
|
||||
<string translatable="false" name="dictionary_url">https://raw.githubusercontent.com/sspanak/tt9/%1$s/downloads/%2$s</string>
|
||||
<string name="app_name" translatable="false">Traditional T9</string>
|
||||
<string name="app_name_short" translatable="false">TT9</string>
|
||||
<string name="app_settings">TT9 Settings</string>
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
<string name="pref_upside_down_keys_summary">Enable if the keypad has 7–8–9 on the first row, instead of 1–2–3.</string>
|
||||
|
||||
<string name="dictionary_cancel_load">Cancel Loading</string>
|
||||
<string name="dictionary_load_bad_char">Loading failed. Invalid word \"%1$s\" on line %2$d of language \"%3$s\".</string>
|
||||
<string name="dictionary_load_bad_char">Loading failed. Invalid word on line %1$d of language \"%2$s\".</string>
|
||||
<string name="dictionary_load_error">Failed loading the dictionary for language \"%1$s\" (%2$s).</string>
|
||||
<string name="dictionary_load_no_internet">Failed downloading the dictionary for language \"%1$s\". Check the Internet connection.</string>
|
||||
<string name="dictionary_load_cancelled">Dictionary load cancelled.</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue