Add Thai language support (#629)
* added Thai language * the SoftKeyNumber now displays abbreviated letter list when there are too many letters on a single key * updated the language validation rules to detect single letters in Asian languages * added a 'no space between words' language YAML option --------- Co-authored-by: sspanak <doftor.livain@gmail.com>
This commit is contained in:
parent
e5b9beb84e
commit
d5fc1fe4b1
16 changed files with 19709 additions and 97 deletions
|
|
@ -58,6 +58,7 @@ To support a new language one needs to:
|
|||
- For 1-key, you could use `[PUNCTUATION]` and have standard English/computer punctuation; or `[PUNCTUATION_FR]` that includes the French quotation marks: `«`, `»`; or `[PUNCTUATION_DE]` that includes the German quotation marks: `„`, `“`. And if the language has extra punctuation marks, like Spanish, you could complement the list like this: `[PUNCTUATION, ¡, ¿]`. Or you could define your own list, like for 0-key.
|
||||
- Keys 2 through 9, just contain the possible letters.
|
||||
- `abcString` _(optional)_. A custom string to display in ABC mode. By default, the first three letters on 2-key are used (e.g. "ABC" or "АБВ"). Set this if the first letters of the alphabet are _not_ on 2-key, like in Hebrew, or if a different string makes more sense.
|
||||
- `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.
|
||||
|
||||
|
|
|
|||
17
app/languages/definitions/Thai.yml
Normal file
17
app/languages/definitions/Thai.yml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
locale: th-TH
|
||||
dictionaryFile: th-utf8.csv
|
||||
abcString: กขค
|
||||
hasSpaceBetweenWords: no
|
||||
hasUpperCase: no
|
||||
name: ภาษาไทย
|
||||
layout:
|
||||
- [SPECIAL] # 0
|
||||
- [PUNCTUATION] # 1
|
||||
- [ก, ข, ฃ, ค, ฅ, ฆ, ง, จ, ฉ] # 2
|
||||
- [ช, ซ, ฌ, ญ, ฎ, ฏ, ฐ, ฑ, ฒ, ณ] # 3
|
||||
- [ด, ต, ถ, ท, ธ, น] # 4
|
||||
- [บ, ป, ผ, ฝ, พ, ฟ] # 5
|
||||
- [ภ, ม, ย, ร, ล, ว] # 6
|
||||
- [ศ, ษ, ส, ห, ฬ, อ, ฮ] # 7
|
||||
- [ิ, ี, ึ, ื, ุ, ู, ั, ่, ้, ๊, ๋, ็, ์] # 8
|
||||
- [ะ, า, โ, ไ, ใ, เ, แ, ำ, ๅ, ๆ, ฯ, ฤ, ฦ] # 9
|
||||
19476
app/languages/dictionaries/th-utf8.csv
Normal file
19476
app/languages/dictionaries/th-utf8.csv
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -46,21 +46,19 @@ public class ModePredictive extends InputMode {
|
|||
|
||||
|
||||
ModePredictive(SettingsStore settings, InputType inputType, TextField textField, Language lang) {
|
||||
changeLanguage(lang);
|
||||
defaultTextCase();
|
||||
|
||||
autoSpace = new AutoSpace(settings);
|
||||
autoSpace = new AutoSpace(settings, lang).setLanguage(lang);
|
||||
autoTextCase = new AutoTextCase(settings);
|
||||
predictions = new Predictions(settings, textField);
|
||||
|
||||
this.settings = settings;
|
||||
|
||||
digitSequence = "";
|
||||
predictions = new Predictions(settings, textField);
|
||||
this.settings = settings;
|
||||
|
||||
if (inputType.isEmail()) {
|
||||
KEY_CHARACTERS.add(new ArrayList<>(Characters.Email.get(0)));
|
||||
KEY_CHARACTERS.add(new ArrayList<>(Characters.Email.get(1)));
|
||||
}
|
||||
|
||||
changeLanguage(lang);
|
||||
defaultTextCase();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -113,6 +111,8 @@ public class ModePredictive extends InputMode {
|
|||
public void changeLanguage(Language language) {
|
||||
super.changeLanguage(language);
|
||||
|
||||
autoSpace.setLanguage(language);
|
||||
|
||||
allowedTextCases.clear();
|
||||
allowedTextCases.add(CASE_LOWER);
|
||||
if (language.hasUpperCase()) {
|
||||
|
|
@ -124,6 +124,10 @@ public class ModePredictive extends InputMode {
|
|||
|
||||
@Override
|
||||
public boolean recompose(String word) {
|
||||
if (!language.hasSpaceBetweenWords()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (word == null || word.length() < 2 || word.contains(" ")) {
|
||||
Logger.d(LOG_TAG, "Not recomposing invalid word: '" + word + "'");
|
||||
textCase = CASE_CAPITALIZE;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package io.github.sspanak.tt9.ime.modes.helpers;
|
|||
|
||||
import io.github.sspanak.tt9.hacks.InputType;
|
||||
import io.github.sspanak.tt9.ime.helpers.TextField;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
||||
import io.github.sspanak.tt9.util.Characters;
|
||||
import io.github.sspanak.tt9.util.Text;
|
||||
|
|
@ -12,9 +13,11 @@ public class AutoSpace {
|
|||
private InputType inputType;
|
||||
private TextField textField;
|
||||
private String lastWord;
|
||||
private boolean isLanguageWithSpaceBetweenWords;
|
||||
|
||||
public AutoSpace(SettingsStore settingsStore) {
|
||||
public AutoSpace(SettingsStore settingsStore, Language language) {
|
||||
settings = settingsStore;
|
||||
isLanguageWithSpaceBetweenWords = true;
|
||||
}
|
||||
|
||||
public AutoSpace setInputType(InputType inputType) {
|
||||
|
|
@ -32,6 +35,11 @@ public class AutoSpace {
|
|||
return this;
|
||||
}
|
||||
|
||||
public AutoSpace setLanguage(Language language) {
|
||||
isLanguageWithSpaceBetweenWords = language != null && language.hasSpaceBetweenWords();
|
||||
return this;
|
||||
}
|
||||
|
||||
public AutoSpace setLastSequence() {
|
||||
return this;
|
||||
}
|
||||
|
|
@ -40,11 +48,13 @@ public class AutoSpace {
|
|||
* shouldAddAutoSpace
|
||||
* When the "auto-space" settings is enabled, this determines whether to automatically add a space
|
||||
* at the end of a sentence or after accepting a suggestion. This allows faster typing, without
|
||||
* pressing space.
|
||||
*
|
||||
* See the helper functions for the list of rules.
|
||||
* pressing space. See the helper functions for the list of rules.
|
||||
*/
|
||||
public boolean shouldAddAutoSpace(boolean isWordAcceptedManually, int nextKey) {
|
||||
if (!isLanguageWithSpaceBetweenWords) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String previousChars = textField.getStringBeforeCursor(2);
|
||||
Text nextChars = textField.getTextAfterCursor(2);
|
||||
|
||||
|
|
@ -114,7 +124,8 @@ public class AutoSpace {
|
|||
*/
|
||||
public boolean shouldDeletePrecedingSpace() {
|
||||
return
|
||||
settings.getAutoSpace()
|
||||
isLanguageWithSpaceBetweenWords
|
||||
&& settings.getAutoSpace()
|
||||
&& (
|
||||
lastWord.equals(".")
|
||||
|| lastWord.equals(",")
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ abstract public class Language {
|
|||
protected String dictionaryFile;
|
||||
protected Locale locale = Locale.ROOT;
|
||||
protected String name;
|
||||
protected boolean hasSpaceBetweenWords = true;
|
||||
protected boolean hasUpperCase = true;
|
||||
|
||||
|
||||
|
|
@ -51,6 +52,10 @@ abstract public class Language {
|
|||
return name;
|
||||
}
|
||||
|
||||
final public boolean hasSpaceBetweenWords() {
|
||||
return hasSpaceBetweenWords;
|
||||
}
|
||||
|
||||
final public boolean hasUpperCase() {
|
||||
return hasUpperCase;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ public class LanguageDefinition {
|
|||
|
||||
public String abcString = "";
|
||||
public String dictionaryFile = "";
|
||||
public boolean hasSpaceBetweenWords = true;
|
||||
public boolean hasUpperCase = true;
|
||||
public ArrayList<ArrayList<String>> layout = new ArrayList<>();
|
||||
public String locale = "";
|
||||
|
|
@ -83,27 +84,14 @@ public class LanguageDefinition {
|
|||
@NonNull
|
||||
private static LanguageDefinition parse(ArrayList<String> yaml) {
|
||||
LanguageDefinition definition = new LanguageDefinition();
|
||||
String value;
|
||||
|
||||
value = getPropertyFromYaml(yaml, "abcString");
|
||||
definition.abcString = value != null ? value : definition.abcString;
|
||||
|
||||
value = getPropertyFromYaml(yaml, "dictionaryFile");
|
||||
definition.dictionaryFile = value != null ? value : definition.dictionaryFile;
|
||||
|
||||
value = getPropertyFromYaml(yaml, "locale");
|
||||
definition.locale = value != null ? value : definition.locale;
|
||||
|
||||
value = getPropertyFromYaml(yaml, "name");
|
||||
definition.name = value != null ? value : definition.name;
|
||||
|
||||
definition.abcString = getPropertyFromYaml(yaml, "abcString", definition.abcString);
|
||||
definition.dictionaryFile = getPropertyFromYaml(yaml, "dictionaryFile", definition.dictionaryFile);
|
||||
definition.hasSpaceBetweenWords = getPropertyFromYaml(yaml, "hasSpaceBetweenWords", definition.hasSpaceBetweenWords);
|
||||
definition.hasUpperCase = getPropertyFromYaml(yaml, "hasUpperCase", definition.hasUpperCase);
|
||||
definition.layout = getLayoutFromYaml(yaml);
|
||||
|
||||
value = getPropertyFromYaml(yaml, "hasUpperCase");
|
||||
if (value != null) {
|
||||
value = value.toLowerCase();
|
||||
definition.hasUpperCase = value.equals("true") || value.equals("on") || value.equals("yes") || value.equals("y");
|
||||
}
|
||||
definition.locale = getPropertyFromYaml(yaml, "locale", definition.locale);
|
||||
definition.name = getPropertyFromYaml(yaml, "name", definition.name);
|
||||
|
||||
return definition;
|
||||
}
|
||||
|
|
@ -112,10 +100,10 @@ public class LanguageDefinition {
|
|||
/**
|
||||
* getPropertyFromYaml
|
||||
* Finds "property" in the "yaml" and returns its value.
|
||||
* Optional properties are allowed. NULL will be returned when they are missing.
|
||||
* Optional properties are allowed. If the property is not found, "defaultValue" will be returned.
|
||||
*/
|
||||
@Nullable
|
||||
private static String getPropertyFromYaml(ArrayList<String> yaml, String property) {
|
||||
private static String getPropertyFromYaml(ArrayList<String> yaml, String property, String defaultValue) {
|
||||
for (String line : yaml) {
|
||||
line = line.replaceAll("#.+$", "").trim();
|
||||
String[] parts = line.split(":");
|
||||
|
|
@ -128,7 +116,22 @@ public class LanguageDefinition {
|
|||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The boolean variant of getPropertyFromYaml. It returns true if the property is found and is:
|
||||
* "true", "on", "yes" or "y".
|
||||
*/
|
||||
private static boolean getPropertyFromYaml(ArrayList<String> yaml, String property, boolean defaultValue) {
|
||||
String value = getPropertyFromYaml(yaml, property, null);
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
value = value.toLowerCase();
|
||||
return value.equals("true") || value.equals("on") || value.equals("yes") || value.equals("y");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package io.github.sspanak.tt9.languages;
|
|||
|
||||
public class LanguageKind {
|
||||
public static boolean isArabic(Language language) { return language != null && language.getKeyCharacters(3).contains("ا"); }
|
||||
public static boolean isBulgarian(Language language) { return language != null && language.getKeyCharacters(4).contains("ѝ"); }
|
||||
public static boolean isCyrillic(Language language) { return language != null && language.getKeyCharacters(2).contains("а"); }
|
||||
public static boolean isHebrew(Language language) { return language != null && language.getKeyCharacters(3).contains("א"); }
|
||||
public static boolean isGreek(Language language) { return language != null && language.getKeyCharacters(2).contains("α"); }
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ public class NaturalLanguage extends Language implements Comparable<NaturalLangu
|
|||
NaturalLanguage lang = new NaturalLanguage();
|
||||
lang.abcString = definition.abcString.isEmpty() ? null : definition.abcString;
|
||||
lang.dictionaryFile = definition.getDictionaryFile();
|
||||
lang.hasSpaceBetweenWords = definition.hasSpaceBetweenWords;
|
||||
lang.hasUpperCase = definition.hasUpperCase;
|
||||
lang.name = definition.name.isEmpty() ? lang.name : definition.name;
|
||||
lang.setLocale(definition);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ public class SettingsStore extends SettingsUI {
|
|||
public final static byte SLOW_QUERY_TIME = 50; // ms
|
||||
public final static int SOFT_KEY_DOUBLE_CLICK_DELAY = 500; // ms
|
||||
public final static int SOFT_KEY_REPEAT_DELAY = 40; // ms
|
||||
public final static int SOFT_KEY_TITLE_MAX_CHARS = 5;
|
||||
public final static int SOFT_KEY_TITLE_SIZE = 18; // sp
|
||||
public final static float SOFT_KEY_COMPLEX_LABEL_TITLE_RELATIVE_SIZE = 0.55f;
|
||||
public final static float SOFT_KEY_COMPLEX_LABEL_ARABIC_TITLE_RELATIVE_SIZE = 0.72f;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,16 @@ package io.github.sspanak.tt9.ui.main.keys;
|
|||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.SparseArray;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.ime.TraditionalT9;
|
||||
import io.github.sspanak.tt9.ime.helpers.Key;
|
||||
import io.github.sspanak.tt9.ime.modes.InputMode;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
|
|
@ -14,8 +19,34 @@ import io.github.sspanak.tt9.languages.LanguageKind;
|
|||
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
||||
import io.github.sspanak.tt9.ui.Vibration;
|
||||
import io.github.sspanak.tt9.util.Logger;
|
||||
import io.github.sspanak.tt9.util.TextTools;
|
||||
|
||||
public class SoftKeyNumber extends SoftKey {
|
||||
private final static SparseArray<Integer> NUMBERS = new SparseArray<Integer>() {{
|
||||
put(R.id.soft_key_0, 0);
|
||||
put(R.id.soft_key_1, 1);
|
||||
put(R.id.soft_key_2, 2);
|
||||
put(R.id.soft_key_3, 3);
|
||||
put(R.id.soft_key_4, 4);
|
||||
put(R.id.soft_key_5, 5);
|
||||
put(R.id.soft_key_6, 6);
|
||||
put(R.id.soft_key_7, 7);
|
||||
put(R.id.soft_key_8, 8);
|
||||
put(R.id.soft_key_9, 9);
|
||||
}};
|
||||
|
||||
private final static SparseArray<Integer> UPSIDE_DOWN_NUMBERS = new SparseArray<Integer>() {{
|
||||
put(1, 7);
|
||||
put(2, 8);
|
||||
put(3, 9);
|
||||
put(7, 1);
|
||||
put(8, 2);
|
||||
put(9, 3);
|
||||
}};
|
||||
|
||||
private static final String PUNCTUATION_LABEL = ",:-)";
|
||||
|
||||
|
||||
public SoftKeyNumber(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
|
@ -28,6 +59,7 @@ public class SoftKeyNumber extends SoftKey {
|
|||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void handleHold() {
|
||||
preventRepeat();
|
||||
|
|
@ -42,6 +74,7 @@ public class SoftKeyNumber extends SoftKey {
|
|||
tt9.onKeyUp(keyCode, new KeyEvent(KeyEvent.ACTION_UP, keyCode));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean handleRelease() {
|
||||
int keyCode = Key.numberToCode(getUpsideDownNumber(getId()));
|
||||
|
|
@ -55,6 +88,23 @@ public class SoftKeyNumber extends SoftKey {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected int getNumber(int keyId) {
|
||||
return NUMBERS.get(keyId, -1);
|
||||
}
|
||||
|
||||
|
||||
protected int getUpsideDownNumber(int keyId) {
|
||||
int number = getNumber(keyId);
|
||||
|
||||
if (tt9 == null || !tt9.getSettings().getUpsideDownKeys()) {
|
||||
return number;
|
||||
}
|
||||
|
||||
return UPSIDE_DOWN_NUMBERS.get(number, number);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected String getTitle() {
|
||||
int number = getNumber(getId());
|
||||
|
|
@ -68,6 +118,7 @@ public class SoftKeyNumber extends SoftKey {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected String getSubTitle() {
|
||||
if (tt9 == null) {
|
||||
|
|
@ -76,88 +127,105 @@ public class SoftKeyNumber extends SoftKey {
|
|||
|
||||
int number = getNumber(getId());
|
||||
|
||||
// 0
|
||||
if (number == 0) {
|
||||
if (tt9.isNumericModeSigned()) {
|
||||
return "+/-";
|
||||
} else if (tt9.isNumericModeStrict()) {
|
||||
return null;
|
||||
} else if (tt9.isInputModeNumeric()) {
|
||||
return "+";
|
||||
} else {
|
||||
complexLabelSubTitleSize = 1;
|
||||
return "␣";
|
||||
}
|
||||
switch (number) {
|
||||
case 0:
|
||||
return getSpecialCharList(tt9);
|
||||
case 1:
|
||||
return tt9.isNumericModeStrict() ? null : PUNCTUATION_LABEL;
|
||||
default:
|
||||
return getKeyCharList(tt9, number);
|
||||
}
|
||||
}
|
||||
|
||||
// 1
|
||||
if (number == 1) {
|
||||
return tt9.isNumericModeStrict() ? null : ",:-)";
|
||||
}
|
||||
|
||||
// no other special labels in 123 mode
|
||||
if (tt9.isInputModeNumeric()) {
|
||||
private String getSpecialCharList(@NonNull TraditionalT9 tt9) {
|
||||
if (tt9.isNumericModeSigned()) {
|
||||
return "+/-";
|
||||
} else if (tt9.isNumericModeStrict()) {
|
||||
return null;
|
||||
} else if (tt9.isInputModeNumeric()) {
|
||||
return "+";
|
||||
} else {
|
||||
complexLabelSubTitleSize = 1;
|
||||
return "␣";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String getKeyCharList(@NonNull TraditionalT9 tt9, int number) {
|
||||
if (tt9.isInputModeNumeric()) {
|
||||
return null; // no special labels in 123 mode
|
||||
}
|
||||
|
||||
// 2-9
|
||||
Language language = tt9.getLanguage();
|
||||
if (language == null) {
|
||||
Logger.d("SoftKeyNumber.getLabel", "Cannot generate a label when the language is NULL.");
|
||||
return "";
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean isLatinBased = LanguageKind.isLatinBased(language);
|
||||
boolean isGreekBased = LanguageKind.isGreek(language);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
ArrayList<String> chars = language.getKeyCharacters(number);
|
||||
for (int i = 0; sb.length() < 5 && i < chars.size(); i++) {
|
||||
String currentLetter = chars.get(i);
|
||||
if (
|
||||
(isLatinBased && currentLetter.charAt(0) > 'z')
|
||||
|| (isGreekBased && (currentLetter.charAt(0) < 'α' || currentLetter.charAt(0) > 'ω'))
|
||||
) {
|
||||
// As suggested by the community, there is no need to display the accented letters.
|
||||
// People are used to seeing just A-Z.
|
||||
boolean isBulgarian = LanguageKind.isBulgarian(language);
|
||||
boolean isGreek = LanguageKind.isGreek(language);
|
||||
boolean isLatinBased = LanguageKind.isLatinBased(language);
|
||||
boolean isUkrainian = LanguageKind.isUkrainian(language);
|
||||
boolean isUppercase = tt9.getTextCase() == InputMode.CASE_UPPER;
|
||||
|
||||
if (
|
||||
isBulgarian
|
||||
|| isGreek
|
||||
|| isLatinBased
|
||||
|| (isUkrainian && number == 2)
|
||||
|| chars.size() < SettingsStore.SOFT_KEY_TITLE_MAX_CHARS) {
|
||||
return getDefaultCharList(chars, language.getLocale(), isGreek, isLatinBased, isUppercase);
|
||||
} else {
|
||||
return abbreviateCharList(chars, language.getLocale(), isUppercase);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Joins the key characters into a single string, skipping accented characters
|
||||
* when neccessary
|
||||
*/
|
||||
private String getDefaultCharList(ArrayList<String> chars, Locale locale, boolean isGreek, boolean isLatinBased, boolean isUppercase) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String currentLetter : chars) {
|
||||
if (shouldSkipAccents(currentLetter.charAt(0), isGreek, isLatinBased)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sb.append(
|
||||
tt9.getTextCase() == InputMode.CASE_UPPER ? currentLetter.toUpperCase(language.getLocale()) : currentLetter
|
||||
isUppercase ? currentLetter.toUpperCase(locale) : currentLetter
|
||||
);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
protected int getNumber(int keyId) {
|
||||
if (keyId == R.id.soft_key_0) return 0;
|
||||
if (keyId == R.id.soft_key_1) return 1;
|
||||
if (keyId == R.id.soft_key_2) return 2;
|
||||
if (keyId == R.id.soft_key_3) return 3;
|
||||
if (keyId == R.id.soft_key_4) return 4;
|
||||
if (keyId == R.id.soft_key_5) return 5;
|
||||
if (keyId == R.id.soft_key_6) return 6;
|
||||
if (keyId == R.id.soft_key_7) return 7;
|
||||
if (keyId == R.id.soft_key_8) return 8;
|
||||
if (keyId == R.id.soft_key_9) return 9;
|
||||
|
||||
return -1;
|
||||
/**
|
||||
* In some languages there are many characters for a single key. Naturally, they can not all fit
|
||||
* on one key. As suggested by the community, we could display them as "A-Z".
|
||||
* @see <a href="https://github.com/sspanak/tt9/issues/628">Issue #628</a>
|
||||
*/
|
||||
private String abbreviateCharList(ArrayList<String> chars, Locale locale, boolean isUppercase) {
|
||||
boolean containsCombiningChars = TextTools.isCombining(chars.get(0)) || TextTools.isCombining(chars.get(chars.size() - 1));
|
||||
return
|
||||
(isUppercase ? chars.get(0).toUpperCase(locale) : chars.get(0))
|
||||
+ (containsCombiningChars ? "– " : "–")
|
||||
+ (isUppercase ? chars.get(chars.size() - 1).toUpperCase(locale) : chars.get(chars.size() - 1));
|
||||
}
|
||||
|
||||
protected int getUpsideDownNumber(int keyId) {
|
||||
int number = getNumber(keyId);
|
||||
|
||||
if (tt9 != null && tt9.getSettings().getUpsideDownKeys()) {
|
||||
if (number == 1) return 7;
|
||||
if (number == 2) return 8;
|
||||
if (number == 3) return 9;
|
||||
if (number == 7) return 1;
|
||||
if (number == 8) return 2;
|
||||
if (number == 9) return 3;
|
||||
}
|
||||
|
||||
return number;
|
||||
/**
|
||||
* As suggested by the community, there is no need to display the accented letters.
|
||||
* People are used to seeing just "ABC", "DEF", etc.
|
||||
*/
|
||||
private boolean shouldSkipAccents(char currentLetter, boolean isGreek, boolean isLatinBased) {
|
||||
return
|
||||
currentLetter == 'ѝ'
|
||||
|| currentLetter == 'ґ'
|
||||
|| (isLatinBased && currentLetter > 'z')
|
||||
|| (isGreek && (currentLetter < 'α' || currentLetter > 'ω'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,16 +8,22 @@ import java.util.regex.Pattern;
|
|||
|
||||
public class TextTools {
|
||||
private static final Pattern containsOtherThan1 = Pattern.compile("[02-9]");
|
||||
private static final Pattern combiningString = Pattern.compile("^\\p{M}+$");
|
||||
private static final Pattern nextIsPunctuation = Pattern.compile("^\\p{Punct}");
|
||||
private static final Pattern nextToWord = Pattern.compile("\\b$");
|
||||
private static final Pattern previousIsLetter = Pattern.compile("\\p{L}$");
|
||||
private static final Pattern startOfSentence = Pattern.compile("(?<!\\.)(^|[.?!؟¿¡])\\s+$");
|
||||
|
||||
|
||||
|
||||
public static boolean containsOtherThan1(String str) {
|
||||
return str != null && containsOtherThan1.matcher(str).find();
|
||||
}
|
||||
|
||||
public static boolean isCombining(String str) {
|
||||
return str != null && combiningString.matcher(str).find();
|
||||
}
|
||||
|
||||
|
||||
public static boolean isGraphic(String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ static def validateDictionaryWord(String word, int lineNumber, String validChara
|
|||
errors += "${errorMsgPrefix}. Found a garbage word: '${word}' on line ${lineNumber}.\n"
|
||||
}
|
||||
|
||||
if (word.matches("^.\$")) {
|
||||
if (word.matches("^(.|\\p{L}\\p{M}?)\$")) {
|
||||
errorCount++
|
||||
errors += "${errorMsgPrefix}. Found a single letter: '${word}' on line ${lineNumber}. Only uppercase single letters are allowed. The rest of the alphabet will be added automatically.\n"
|
||||
}
|
||||
|
|
@ -65,6 +65,7 @@ static def parseLanguageFile(File languageFile, String dictionariesDir) {
|
|||
line.matches("^[a-zA-Z].*")
|
||||
&& !line.startsWith("abcString")
|
||||
&& !line.startsWith("dictionaryFile")
|
||||
&& !line.startsWith("hasSpaceBetweenWords")
|
||||
&& !line.startsWith("hasUpperCase")
|
||||
&& !line.startsWith("layout")
|
||||
&& !line.startsWith("locale")
|
||||
|
|
@ -77,10 +78,14 @@ static def parseLanguageFile(File languageFile, String dictionariesDir) {
|
|||
errorMsg += "Language '${languageFile.name}' is invalid. Found unknown property: '${property}'.\n"
|
||||
}
|
||||
|
||||
if (line.startsWith("hasUpperCase") && !line.endsWith("yes") && !line.endsWith("no")) {
|
||||
if (
|
||||
(line.startsWith("hasUpperCase") || line.startsWith("hasSpaceBetweenWords"))
|
||||
&& !line.endsWith("yes") && !line.endsWith("no")
|
||||
) {
|
||||
def property = line.replaceAll(":.*\$", "")
|
||||
def invalidVal = line.replace("hasUpperCase:", "").trim()
|
||||
errorCount++
|
||||
errorMsg += "Language '${languageFile.name}' is invalid. Unrecognized 'hasUpperCase' value: '${invalidVal}'. Only 'yes' and 'no' are allowed.\n"
|
||||
errorMsg += "Language '${languageFile.name}' is invalid. Unrecognized '${property}' value: '${invalidVal}'. Only 'yes' and 'no' are allowed.\n"
|
||||
}
|
||||
|
||||
if (line.startsWith("layout")) {
|
||||
|
|
|
|||
13
docs/dictionaries/thWordlistReadme.txt
Normal file
13
docs/dictionaries/thWordlistReadme.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
Thai word list 1 from Thai National Corpus (a project from Faculty of Arts, Chulalongkorn University), provided by mmmmmob
|
||||
Source: https://github.com/sspanak/tt9/issues/628
|
||||
Source: https://github.com/sspanak/tt9/pull/629
|
||||
|
||||
Thai wordlist 2 by: nv23
|
||||
Version: 75c1aac522b1ad2c73bd990c891e9d1418df3f3e (2015-07-04)
|
||||
Source: https://github.com/nv23/thai-wordlist
|
||||
License: (MIT) https://github.com/nv23/thai-wordlist/blob/master/LICENSE
|
||||
|
||||
Thai word frequencies obtained from: AnySoftKeyboard project
|
||||
Version: 5f50aadb034deb39aed629534b55bdf50cb89d60 (2020-07-07)
|
||||
Source: https://github.com/AnySoftKeyboard/AnySoftKeyboard
|
||||
License: (Apache 2.0) https://github.com/AnySoftKeyboard/AnySoftKeyboard/blob/main/LICENSE
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
TT9 е 12-клавишна Т9 клавиатура за устройства с физически бутони. Поддържа подскаващ текст на повече от 25 езика и бързи клавиши, а виртуалната клавиатура може да превърне всеки смартфон в Нокия от 2000 година. И най-хубавото — не Ви шпионира!
|
||||
|
||||
Поддържани езици: английски, арабски, български, виетнамски, гръцки, датски, идиш, иврит, индонезийски, испански, италиански, каталонски, кисуахили, немски, норвежки, полски, португалски (европейски и бразилски), румънски, руски, унгарски, украински, финландски, френски, холандски, хърватски, чешки, шведски, турски.
|
||||
Поддържани езици: английски, арабски, български, виетнамски, гръцки, датски, идиш, иврит, индонезийски, испански, италиански, каталонски, кисуахили, немски, норвежки, полски, португалски (европейски и бразилски), румънски, руски, тайски, унгарски, украински, финландски, френски, холандски, хърватски, чешки, шведски, турски.
|
||||
|
||||
Философия и защита не личните данни:
|
||||
- Без реклами, специални или платени функции. Всичко е напълно безплатно.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
TT9 is a 12-key T9 keyboard for devices with a hardware numpad. It supports predictive text typing in 25+ languages, configurable hotkeys, and an on-screen keypad that can turn your smartphone into a Nokia from the 2000s. And, best of all, it doesn't spy on you!
|
||||
|
||||
Supported languages: Arabic, Bulgarian, Catalan, Croatian, Czech, Danish, Dutch, English, Finnish, French, German, Greek, Hebrew, Hungarian, Indonesian, Italian, Kiswahili, Norwegian, Polish, Portuguese (European and Brazilian), Romanian, Russian, Spanish, Swedish, Turkish, Ukrainian, Vietnamese, Yiddish.
|
||||
Supported languages: Arabic, Bulgarian, Catalan, Croatian, Czech, Danish, Dutch, English, Finnish, French, German, Greek, Hebrew, Hungarian, Indonesian, Italian, Kiswahili, Norwegian, Polish, Portuguese (European and Brazilian), Romanian, Russian, Spanish, Swedish, Thai, Turkish, Ukrainian, Vietnamese, Yiddish.
|
||||
|
||||
Privacy Policy and Philosophy:
|
||||
- No ads, no premium or paid features. It's all free.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue