added support for custom numerals in the language definitions
This commit is contained in:
parent
afa509cee0
commit
c28c9f053e
11 changed files with 56 additions and 34 deletions
|
|
@ -60,6 +60,7 @@ To support a new language one needs to:
|
|||
- `hasSpaceBetweenWords` _(optional)_ set to `no` when the language does not use spaces between words. For example: Thai, Chinese, Japanese, Korean, and so on. The default is `yes`.
|
||||
- `hasUpperCase` _(optional)_ set to `no` when the language has no upper- and lowercase letters. For example: Arabic, Hebrew, East Asian languages, and so on. The default is `yes`.
|
||||
- `name` _(optional)_ is automatically generated and equals the native name of the language (e.g. "English", "Deutsch", "Українська"). However, sometimes, the automatically selected name may be ambiguous. For example, both Portuguese in Portugal and Brazil will default to "Português", so assigning "Português brasileiro" would make it clear it's the language used in Brazil.
|
||||
-`numerals` _(optional)_ can be used to set a custom list of numerals. The list must contain exactly 10 characters equivalent to the digits from 0 to 9. For example, in Arabic you could use: `numerals: [٠,١,٢,٣,٤,٥,٦,٧,٨,٩]`.
|
||||
- `sounds` _(mandatory for non-alphabetic languages)_ is an array of elements in the format: `[sound,digits]`. It is used for East Asian or other languages where there are thousands of different characters, that can not be described in the layout property. `sounds` contains all possible vowel and consonant sounds and their respective digit combinations. There must be no repeating sounds. If a single Latin letter stands for a sound, the letter must be capital. If more than one letter is necessary to represent the sound, the first letter must be capital, while the rest must be small. For example, "A", "P", "Wo", "Ei", "Dd". The sounds are then used in the dictionary format with phonetic transcriptions. See `Korean.yml` and the respective dictionary file for an example.
|
||||
|
||||
### Dictionary Formats
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ currency: ﷼
|
|||
dictionaryFile: ar-utf8.csv
|
||||
abcString: أﺏﺕ
|
||||
hasUpperCase: no
|
||||
numerals: [٠,١,٢,٣,٤,٥,٦,٧,٨,٩]
|
||||
layout:
|
||||
- [SPECIAL] # 0
|
||||
- [PUNCTUATION_AR] # 1
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class ModeABC extends InputMode {
|
|||
autoAcceptTimeout = 0;
|
||||
digitSequence = String.valueOf(number);
|
||||
shouldSelectNextLetter = false;
|
||||
suggestions.add(language.getKeyNumber(number));
|
||||
suggestions.add(language.getKeyNumeral(number));
|
||||
} else if (repeat > 0) {
|
||||
autoAcceptTimeout = settings.getAbcAutoAcceptTimeout();
|
||||
shouldSelectNextLetter = true;
|
||||
|
|
@ -56,7 +56,7 @@ class ModeABC extends InputMode {
|
|||
digitSequence = String.valueOf(number);
|
||||
shouldSelectNextLetter = false;
|
||||
suggestions.addAll(KEY_CHARACTERS.size() > number ? KEY_CHARACTERS.get(number) : settings.getOrderedKeyChars(language, number));
|
||||
suggestions.add(language.getKeyNumber(number));
|
||||
suggestions.add(language.getKeyNumeral(number));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -86,7 +86,7 @@ class ModeABC extends InputMode {
|
|||
return false;
|
||||
}
|
||||
|
||||
suggestions.add(language.getKeyNumber(digitSequence.charAt(0) - '0'));
|
||||
suggestions.add(language.getKeyNumeral(digitSequence.charAt(0) - '0'));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ class ModeCheonjiin extends InputMode {
|
|||
digitSequence = PUNCTUATION_SEQUENCE;
|
||||
} else {
|
||||
autoAcceptTimeout = 0;
|
||||
suggestions.add(language.getKeyNumber(number));
|
||||
suggestions.add(language.getKeyNumeral(number));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ class ModeWords extends ModeCheonjiin {
|
|||
@Override
|
||||
protected void onNumberHold(int number) {
|
||||
autoAcceptTimeout = 0;
|
||||
suggestions.add(language.getKeyNumber(number));
|
||||
suggestions.add(language.getKeyNumeral(number));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ abstract public class Language {
|
|||
return getKeyCharacters(key, 0);
|
||||
}
|
||||
|
||||
@NonNull public String getKeyNumber(int key) {
|
||||
@NonNull public String getKeyNumeral(int key) {
|
||||
return String.valueOf(key);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import androidx.annotation.Nullable;
|
|||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import io.github.sspanak.tt9.BuildConfig;
|
||||
import io.github.sspanak.tt9.util.AssetFile;
|
||||
|
|
@ -29,6 +30,7 @@ public class LanguageDefinition {
|
|||
public ArrayList<ArrayList<String>> layout = new ArrayList<>();
|
||||
public String locale = "";
|
||||
public String name = "";
|
||||
@NonNull public HashMap<Integer, String> numerals = new HashMap<>();
|
||||
|
||||
private boolean inLayout = false;
|
||||
|
||||
|
|
@ -125,28 +127,31 @@ public class LanguageDefinition {
|
|||
switch (key) {
|
||||
case "abcString":
|
||||
abcString = value;
|
||||
break;
|
||||
return;
|
||||
case "currency":
|
||||
currency = value;
|
||||
break;
|
||||
return;
|
||||
case "dictionaryFile":
|
||||
dictionaryFile = value.replaceFirst("\\.\\w+$", "." + BuildConfig.DICTIONARY_EXTENSION);
|
||||
break;
|
||||
return;
|
||||
case "hasSpaceBetweenWords":
|
||||
hasSpaceBetweenWords = parseYamlBoolean(value);
|
||||
break;
|
||||
return;
|
||||
case "hasUpperCase":
|
||||
hasUpperCase = parseYamlBoolean(value);
|
||||
break;
|
||||
return;
|
||||
case "sounds":
|
||||
isSyllabary = true;
|
||||
break;
|
||||
return;
|
||||
case "locale":
|
||||
locale = value;
|
||||
break;
|
||||
return;
|
||||
case "name":
|
||||
name = value;
|
||||
break;
|
||||
return;
|
||||
case "numerals":
|
||||
setNumerals(value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +164,7 @@ public class LanguageDefinition {
|
|||
return inLayout = "layout:".equals(line);
|
||||
}
|
||||
|
||||
ArrayList<String> layoutEntry = getLayoutEntryFromYamlLine(line);
|
||||
ArrayList<String> layoutEntry = parseList(line);
|
||||
if (layoutEntry == null) {
|
||||
inLayout = false;
|
||||
} else {
|
||||
|
|
@ -171,13 +176,25 @@ public class LanguageDefinition {
|
|||
}
|
||||
|
||||
|
||||
private void setNumerals(@NonNull String yamlList) {
|
||||
ArrayList<String> numberList = parseList(yamlList);
|
||||
if (numberList == null || numberList.size() != 10) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
numerals.put(i, numberList.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* getLayoutEntryFromYamlLine
|
||||
* Validates a YAML line as an array and returns the character list to be assigned to a given key (a layout entry).
|
||||
* If the YAML line is invalid, NULL will be returned.
|
||||
*/
|
||||
@Nullable
|
||||
private ArrayList<String> getLayoutEntryFromYamlLine(@NonNull String yamlLine) {
|
||||
private ArrayList<String> parseList(@NonNull String yamlLine) {
|
||||
int start = yamlLine.indexOf('[');
|
||||
int end = yamlLine.indexOf(']');
|
||||
if (start == -1 || end == -1 || start >= end) {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ public class NaturalLanguage extends Language implements Comparable<NaturalLangu
|
|||
|
||||
protected final ArrayList<ArrayList<String>> layout = new ArrayList<>();
|
||||
private final HashMap<Character, String> characterKeyMap = new HashMap<>();
|
||||
@NonNull private HashMap<Integer, String> numerals = new HashMap<>();
|
||||
|
||||
|
||||
public static NaturalLanguage fromDefinition(LanguageDefinition definition) throws Exception {
|
||||
|
|
@ -37,6 +38,7 @@ public class NaturalLanguage extends Language implements Comparable<NaturalLangu
|
|||
lang.hasUpperCase = definition.hasUpperCase;
|
||||
lang.isSyllabary = definition.isSyllabary;
|
||||
lang.name = definition.name.isEmpty() ? lang.name : definition.name;
|
||||
lang.numerals = definition.numerals;
|
||||
lang.setLocale(definition);
|
||||
lang.setLayout(definition);
|
||||
|
||||
|
|
@ -195,7 +197,7 @@ public class NaturalLanguage extends Language implements Comparable<NaturalLangu
|
|||
private void generateCharacterKeyMap() {
|
||||
characterKeyMap.clear();
|
||||
for (int digit = 0; digit <= 9; digit++) {
|
||||
characterKeyMap.put(getKeyNumber(digit).charAt(0), String.valueOf(digit));
|
||||
characterKeyMap.put(getKeyNumeral(digit).charAt(0), String.valueOf(digit));
|
||||
for (String keyChar : getKeyCharacters(digit)) {
|
||||
characterKeyMap.put(keyChar.charAt(0), String.valueOf(digit));
|
||||
}
|
||||
|
|
@ -224,8 +226,9 @@ public class NaturalLanguage extends Language implements Comparable<NaturalLangu
|
|||
|
||||
|
||||
@NonNull
|
||||
public String getKeyNumber(int key) {
|
||||
return key >= 0 && key < 10 && LanguageKind.isArabic(this) ? Characters.ArabicNumbers.get(key) : super.getKeyNumber(key);
|
||||
public String getKeyNumeral(int key) {
|
||||
String digit = numerals.containsKey(key) ? numerals.get(key) : null;
|
||||
return digit != null ? digit : super.getKeyNumeral(key);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -85,8 +85,8 @@ public class SoftKeyNumber extends BaseSoftKeyWithIcons {
|
|||
|
||||
|
||||
protected String getLocalizedNumber(int number) {
|
||||
if (isArabicNumber() && tt9 != null && tt9.getLanguage() != null) {
|
||||
return tt9.getLanguage().getKeyNumber(number);
|
||||
if (tt9 != null && !tt9.isInputModeNumeric() && tt9.getLanguage() != null) {
|
||||
return tt9.getLanguage().getKeyNumeral(number);
|
||||
} else {
|
||||
return String.valueOf(number);
|
||||
}
|
||||
|
|
@ -98,9 +98,4 @@ public class SoftKeyNumber extends BaseSoftKeyWithIcons {
|
|||
float defaultScale = super.getHoldElementScale();
|
||||
return tt9 != null && LanguageKind.isArabic(tt9.getLanguage()) ? defaultScale * 1.25f : defaultScale;
|
||||
}
|
||||
|
||||
|
||||
private boolean isArabicNumber() {
|
||||
return tt9 != null && !tt9.isInputModeNumeric() && LanguageKind.isArabic(tt9.getLanguage());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,6 @@ class Punctuation {
|
|||
public static final String ZWNJ = "\u200C";
|
||||
public static final String ZWNJ_GRAPHIC = "ZWNJ";
|
||||
|
||||
final public static ArrayList<String> ArabicNumbers = new ArrayList<>(Arrays.asList(
|
||||
"٠", "١", "٢", "٣", "٤", "٥", "٦", "٧", "٨", "٩"
|
||||
));
|
||||
|
||||
final public static ArrayList<Character> CombiningPunctuation = new ArrayList<>(Arrays.asList(
|
||||
',', '-', '\'', ':', ';', '!', '?', '.'
|
||||
));
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ ext.parseLanguageDefintion = { File languageFile, String dictionariesDir ->
|
|||
|
||||
boolean hasLayout = false
|
||||
boolean hasSounds = false
|
||||
boolean isLocaleValid = false
|
||||
boolean areNumeralsValid = true
|
||||
String localeString = ""
|
||||
String dictionaryFileName = ""
|
||||
|
||||
|
|
@ -69,6 +69,7 @@ ext.parseLanguageDefintion = { File languageFile, String dictionariesDir ->
|
|||
&& !rawLine.startsWith("layout")
|
||||
&& !rawLine.startsWith("locale")
|
||||
&& !rawLine.startsWith("name")
|
||||
&& !rawLine.startsWith("numerals")
|
||||
&& !rawLine.startsWith("sounds")
|
||||
) {
|
||||
def parts = rawLine.split(":")
|
||||
|
|
@ -90,6 +91,10 @@ ext.parseLanguageDefintion = { File languageFile, String dictionariesDir ->
|
|||
errorMsg += "Language '${languageFile.name}' is invalid. Unrecognized '${property}' value: '${invalidVal}'. Only 'yes' and 'no' are allowed.\n"
|
||||
}
|
||||
|
||||
if (line.startsWith("numerals")) {
|
||||
areNumeralsValid = line.matches("^numerals:\\s*\\[(.,\\s*?){9}.\\]")
|
||||
}
|
||||
|
||||
if (line.startsWith("layout")) {
|
||||
hasLayout = true
|
||||
}
|
||||
|
|
@ -100,7 +105,6 @@ ext.parseLanguageDefintion = { File languageFile, String dictionariesDir ->
|
|||
|
||||
if (line.startsWith("locale")) {
|
||||
localeString = line.replace("locale:", "").trim()
|
||||
isLocaleValid = localeString.matches("^[a-z]{2,3}(?:-[A-Z]{2})?\$")
|
||||
}
|
||||
|
||||
if (line.startsWith("dictionaryFile")) {
|
||||
|
|
@ -146,12 +150,17 @@ ext.parseLanguageDefintion = { File languageFile, String dictionariesDir ->
|
|||
errorMsg += "Language '${languageFile.name}' is invalid. 'sounds' property must contain series of phonetic transcriptions per digit sequence in the format: ' - [Yae,1221]' and so on.\n"
|
||||
}
|
||||
|
||||
if (!isLocaleValid) {
|
||||
if (!localeString.matches("^[a-z]{2,3}(?:-[A-Z]{2})?\$")) {
|
||||
errorCount++
|
||||
def msg = localeString.isEmpty() ? "Missing 'locale' property." : "Unrecognized locale format: '${localeString}'"
|
||||
errorMsg += "Language '${languageFile.name}' is invalid. ${msg}\n"
|
||||
}
|
||||
|
||||
if (!areNumeralsValid) {
|
||||
errorCount++
|
||||
errorMsg += "Language '${languageFile.name}' is invalid. 'numerals' property must contain a comma-separated list of 10 characters representing the digits from 0 to 9.\n"
|
||||
}
|
||||
|
||||
dictionaryFile = new File("$dictionariesDir/${dictionaryFileName}")
|
||||
if (dictionaryFileName.isEmpty() || !dictionaryFile.exists()) {
|
||||
errorCount++
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue