diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 07fb2051..e5206af0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,6 +59,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. + - `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 @@ -102,6 +103,35 @@ fifth 3 ... ``` +#### CSV Containing Words and Phonetic Transcriptions +The third accepted format is suitable for East Asian and other languages with many different characters. Each character or word has a phonetic representation using Latin letters. Frequencies are not applicable in this format. + +Constraints: +- No header. +- The separator is `TAB`. +- The first element is the language character or word +- The second element is the phonetic representation with Latin letters. It must be a combination of the `sounds` in the respective YAML definition. + +Example definition: +```yaml +# ... +- sounds: + - [We,123] + - [Tt,221] + - [Wo,48] +``` + +Example dictionary: +```csv +다 WeWo +줘 TtWoWe +와 Wo +``` + +Using the above example, when the user types "221-48-123", it will result in: "줘". + +See `Korean.yml` and `ko-utf8.csv` for more examples. + ## Translating the UI To translate Traditional T9 menus and messages in your language, add: `res/values-your-lang/strings.xml`. Then use the Android Studio translation editor. It is very handy. diff --git a/app/build-dictionary.gradle b/app/build-dictionary.gradle index a596c5ff..c6a04807 100644 --- a/app/build-dictionary.gradle +++ b/app/build-dictionary.gradle @@ -8,13 +8,13 @@ ext.convertDictionaries = { definitionsInputDir, dictionariesInputDir, dictionar int errorCount = 0 def errorStream = fileTree(dir: definitionsInputDir).getFiles().parallelStream().map { definition -> - def (_, sounds, __, locale, dictionaryFile, langFileErrorCount, langFileErrorMsg) = parseLanguageDefintion(definition, dictionariesInputDir) + def (_, sounds, noSyllables, locale, dictionaryFile, langFileErrorCount, langFileErrorMsg) = parseLanguageDefintion(definition, dictionariesInputDir) errorCount += langFileErrorCount if (!langFileErrorMsg.isEmpty()) { return langFileErrorMsg } - def (conversionErrorCount, conversionErrorMessages) = convertDictionary(definition, dictionaryFile, dictionariesOutputDir, dictionariesMetaDir, DICTIONARY_OUTPUT_EXTENSION, sounds, locale, MAX_ERRORS, CSV_DELIMITER) + def (conversionErrorCount, conversionErrorMessages) = convertDictionary(definition, dictionaryFile, dictionariesOutputDir, dictionariesMetaDir, DICTIONARY_OUTPUT_EXTENSION, sounds, noSyllables, locale, MAX_ERRORS, CSV_DELIMITER) errorCount += conversionErrorCount if (!conversionErrorMessages.isEmpty()) { return conversionErrorMessages @@ -31,7 +31,7 @@ ext.convertDictionaries = { definitionsInputDir, dictionariesInputDir, dictionar // this cannot be static, because DictionaryTools will not be visible -def convertDictionary(File definition, File csvDictionary, String dictionariesOutputDir, String dictionariesMetaDir, String outputDictionaryExtension, HashMap sounds, Locale locale, int maxErrors, String csvDelimiter) { +def convertDictionary(File definition, File csvDictionary, String dictionariesOutputDir, String dictionariesMetaDir, String outputDictionaryExtension, HashMap sounds, boolean noSyllables, Locale locale, int maxErrors, String csvDelimiter) { if (isDictionaryUpToDate(definition, csvDictionary, dictionariesMetaDir)) { return [0, ""] } @@ -70,7 +70,7 @@ def convertDictionary(File definition, File csvDictionary, String dictionariesOu outputDictionary = sortDictionary(outputDictionary) - def (assetError, zippedDictionary) = writeZippedDictionary(dictionariesOutputDir, csvDictionary, outputDictionary, outputDictionaryExtension) + def (assetError, zippedDictionary) = writeZippedDictionary(dictionariesOutputDir, csvDictionary, outputDictionary, outputDictionaryExtension, noSyllables) if (assetError) { errorCount++ errorMsg += assetError @@ -88,12 +88,12 @@ def convertDictionary(File definition, File csvDictionary, String dictionariesOu //////////////////// DICTIONARY PROCESSING //////////////////// -static byte[] compressDictionaryLine(String digitSequence, List words) { +static byte[] compressDictionaryLine(String digitSequence, List words, boolean noSyllables) { if (words.isEmpty()) { throw new IllegalArgumentException("No words for digit sequence: ${digitSequence}") } - boolean shouldSeparateWords = false + boolean shouldSeparateWords = !noSyllables for (def i = 0; i < words.size(); i++) { if (words.get(i).length() != digitSequence.length()) { @@ -104,7 +104,7 @@ static byte[] compressDictionaryLine(String digitSequence, List words) { return ( digitSequence + - (shouldSeparateWords ? ' ' : '') + + (shouldSeparateWords && noSyllables ? ' ' : '') + // if the language definition has sounds (aka the characters are syllables), we separate the words for sure, so the initial hint is not needed words.join(shouldSeparateWords ? ' ' : null) ).getBytes(StandardCharsets.UTF_8) } @@ -166,7 +166,7 @@ static getZipDictionaryFile(dictionariesOutputDir, csvDictionary, outputDictiona /** * Zipping the text files results in a smaller APK in comparison to the uncompressed text files. */ -static def writeZippedDictionary(dictionariesOutputDir, csvDictionaryFile, outputDictionary, outputDictionaryExtension) { +static def writeZippedDictionary(dictionariesOutputDir, csvDictionaryFile, outputDictionary, outputDictionaryExtension, noSyllables) { def fileName = getDictionaryFileName(csvDictionaryFile) def outputFile = getZipDictionaryFile(dictionariesOutputDir, csvDictionaryFile, outputDictionaryExtension) @@ -174,7 +174,7 @@ static def writeZippedDictionary(dictionariesOutputDir, csvDictionaryFile, outpu def zipOutputStream = new ZipOutputStream(new FileOutputStream(outputFile)) zipOutputStream.putNextEntry(new ZipEntry("${fileName}.txt")) outputDictionary.each { digitSequence, words -> - zipOutputStream.write(compressDictionaryLine(digitSequence, words)) + zipOutputStream.write(compressDictionaryLine(digitSequence, words, noSyllables)) } zipOutputStream.closeEntry() zipOutputStream.close() diff --git a/app/dictionary-tools.gradle b/app/dictionary-tools.gradle index c9c1b80f..a6cf901d 100644 --- a/app/dictionary-tools.gradle +++ b/app/dictionary-tools.gradle @@ -17,7 +17,7 @@ class Wrapper { static def wordToDigitSequence(Locale locale, String word, HashMap sounds, boolean isTranscribed) { - String sequence = "" + def sequence = new StringBuilder() final String normalizedWord = isTranscribed ? word : word.toUpperCase(locale) String currentSound = "" @@ -32,7 +32,7 @@ class Wrapper { // charAt(i) returns "ΐ" as three separate characters, but they must be treated as one. if ( locale.getLanguage() == "el" - && (nextCharType == Character.NON_SPACING_MARK || nextCharType == Character.ENCLOSING_MARK || nextCharType == Character.COMBINING_SPACING_MARK) + && (nextCharType == Character.NON_SPACING_MARK || nextCharType == Character.ENCLOSING_MARK || nextCharType == Character.COMBINING_SPACING_MARK) ) { continue } @@ -41,7 +41,7 @@ class Wrapper { if (!sounds.containsKey(currentSound)) { throw new IllegalArgumentException("Sound or layout entry '${currentSound}' does not belong to the language sound list: ${sounds}.") } else { - sequence += sounds.get(currentSound) + sequence << sounds.get(currentSound) currentSound = "" } } @@ -51,7 +51,7 @@ class Wrapper { throw new IllegalArgumentException("The word does not contain any valid sounds.") } - return sequence + return sequence.toString() } diff --git a/app/languages/definitions/Korean.yml b/app/languages/definitions/Korean.yml new file mode 100644 index 00000000..2a3bcc9a --- /dev/null +++ b/app/languages/definitions/Korean.yml @@ -0,0 +1,72 @@ +locale: ko-KR +dictionaryFile: ko-utf8.csv +hasUpperCase: no +layout: # only used for the virtual key labels + - [ㅇ,ㅁ,SPECIAL] # 0 + - [ㅣ,PUNCTUATION_KR] # 1 + - [ㆍ,:] # 2 + - [ㅡ] # 3 + - [ㄱ,ㅋ,ㄲ] # 4 + - [ㄴ,ㄹ] # 5 + - [ㄷ,ㅌ,ㄸ] # 6 + - [ㅂ,ㅍ,ㅃ] # 7 + - [ㅅ,ㅎ,ㅆ] # 8 + - [ㅈ,ㅊ,ㅉ] # 9 +sounds: + ########## initial consonants ########## + - [G,4] + - [K,44] + - [Gg,444] + - [N,5] + - [L,55] # also: "R", but we need a unique identifier, so we use "L" + - [D,6] + - [T,66] + - [Dd,666] + - [B,7] + - [P,77] + - [Bb,777] + - [S,8] + - [H,88] + - [Ss,888] + - [J,9] + - [C,99] # also transcribed as "Ch" + - [Jj,999] + - [Z,0] # "ㅇ" when zero initial consonant + - [Ng,0] + - [M,00] + ########## vowels ########## + - [I,1] + - [A,12] + - [Ae,121] + - [Ya,122] + - [Yae,1221] + - [Q,2] # "Q" is just some random unique identifier + - [Qq, 22] + - [Eo,21] + - [E,211] + - [Yeo,221] + - [Ye,2211] + - [Yo,223] + - [O,23] + - [Oe,231] + - [Wa,2312] + - [Wae,23121] + - [Eu,3] + - [Ui,31] + - [U,32] + - [Wi,321] + - [Yu,322] + - [Wo,3221] + - [We,32211] + ########## final consonants ########## + - [Gs,48] + - [Lg,554] + - [Lb,557] + - [Ls,558] + - [Lt,5566] + - [Lp,5577] + - [Lh,5588] + - [Lm,5500] + - [Nh,588] + - [Nj,59] + - [Bs,78] diff --git a/app/languages/dictionaries/ko-utf8.csv b/app/languages/dictionaries/ko-utf8.csv new file mode 100644 index 00000000..9be0f86b --- /dev/null +++ b/app/languages/dictionaries/ko-utf8.csv @@ -0,0 +1,11248 @@ +ㅇ Z +ㅁ M +ㅣ I +ㆍ Q +: Qq +ㅡ Eu +ㄱ G +ㅋ K +ㄲ Gg +ㄴ N +ㄹ L +ㄷ D +ㅌ T +ㄸ Dd +ㅂ B +ㅍ P +ㅃ Bb +ㅅ S +ㅎ H +ㅆ Ss +ㅈ J +ㅊ C +ㅉ Jj +ㅏ A +ㅓ Eo +ㅗ O +ㅜ U +ㅐ Ae +ㅑ Ya +ㅒ Yae +ㅔ E +ㅕ Yeo +ㅖ Ye +ㅛ Yo +ㅘ Wa +ㅙ Wae +ㅚ Oe +ㅢ Ui +ㅝ Wo +ㅞ We +ㅟ Wi +ㅠ Yu +ㄱㆍ GQ +ㄱ: GQq +ㅋㆍ KQ +ㅋ: KQq +ㄲㆍ GgQ +ㄲ: GgQq +ㄴㆍ NQ +ㄴ: NQq +ㄹㆍ LQ +ㄹ: LQq +ㄷㆍ DQ +ㄷ: DQq +ㅌㆍ TQ +ㅌ: TQq +ㄸㆍ DdQ +ㄸ: DdQq +ㅂㆍ BQ +ㅂ: BQq +ㅍㆍ PQ +ㅍ: PQq +ㅃㆍ BbQ +ㅃ: BbQq +ㅅㆍ SQ +ㅅ: SQq +ㅎㆍ HQ +ㅎ: HQq +ㅆㆍ SsQ +ㅆ: SsQq +ㅈㆍ JQ +ㅈ: JQq +ㅊㆍ CQ +ㅊ: CQq +ㅉㆍ JjQ +ㅉ: JjQq +가 GA +각 GAG +갂 GAGg +갃 GAGs +간 GAN +갅 GANj +갆 GANh +갇 GAD +갈 GAL +갉 GALg +갊 GALm +갋 GALb +갌 GALs +갍 GALt +갎 GALp +갏 GALh +감 GAM +갑 GAB +값 GABs +갓 GAS +갔 GASs +강 GANg +갖 GAJ +갗 GAC +갘 GAK +같 GAT +갚 GAP +갛 GAH +개 GAe +객 GAeG +갞 GAeGg +갟 GAeGs +갠 GAeN +갡 GAeNj +갢 GAeNh +갣 GAeD +갤 GAeL +갥 GAeLg +갦 GAeLm +갧 GAeLb +갨 GAeLs +갩 GAeLt +갪 GAeLp +갫 GAeLh +갬 GAeM +갭 GAeB +갮 GAeBs +갯 GAeS +갰 GAeSs +갱 GAeNg +갲 GAeJ +갳 GAeC +갴 GAeK +갵 GAeT +갶 GAeP +갷 GAeH +갸 GYa +갹 GYaG +갺 GYaGg +갻 GYaGs +갼 GYaN +갽 GYaNj +갾 GYaNh +갿 GYaD +걀 GYaL +걁 GYaLg +걂 GYaLm +걃 GYaLb +걄 GYaLs +걅 GYaLt +걆 GYaLp +걇 GYaLh +걈 GYaM +걉 GYaB +걊 GYaBs +걋 GYaS +걌 GYaSs +걍 GYaNg +걎 GYaJ +걏 GYaC +걐 GYaK +걑 GYaT +걒 GYaP +걓 GYaH +걔 GYae +걕 GYaeG +걖 GYaeGg +걗 GYaeGs +걘 GYaeN +걙 GYaeNj +걚 GYaeNh +걛 GYaeD +걜 GYaeL +걝 GYaeLg +걞 GYaeLm +걟 GYaeLb +걠 GYaeLs +걡 GYaeLt +걢 GYaeLp +걣 GYaeLh +걤 GYaeM +걥 GYaeB +걦 GYaeBs +걧 GYaeS +걨 GYaeSs +걩 GYaeNg +걪 GYaeJ +걫 GYaeC +걬 GYaeK +걭 GYaeT +걮 GYaeP +걯 GYaeH +거 GEo +걱 GEoG +걲 GEoGg +걳 GEoGs +건 GEoN +걵 GEoNj +걶 GEoNh +걷 GEoD +걸 GEoL +걹 GEoLg +걺 GEoLm +걻 GEoLb +걼 GEoLs +걽 GEoLt +걾 GEoLp +걿 GEoLh +검 GEoM +겁 GEoB +겂 GEoBs +것 GEoS +겄 GEoSs +겅 GEoNg +겆 GEoJ +겇 GEoC +겈 GEoK +겉 GEoT +겊 GEoP +겋 GEoH +게 GE +겍 GEG +겎 GEGg +겏 GEGs +겐 GEN +겑 GENj +겒 GENh +겓 GED +겔 GEL +겕 GELg +겖 GELm +겗 GELb +겘 GELs +겙 GELt +겚 GELp +겛 GELh +겜 GEM +겝 GEB +겞 GEBs +겟 GES +겠 GESs +겡 GENg +겢 GEJ +겣 GEC +겤 GEK +겥 GET +겦 GEP +겧 GEH +겨 GYeo +격 GYeoG +겪 GYeoGg +겫 GYeoGs +견 GYeoN +겭 GYeoNj +겮 GYeoNh +겯 GYeoD +결 GYeoL +겱 GYeoLg +겲 GYeoLm +겳 GYeoLb +겴 GYeoLs +겵 GYeoLt +겶 GYeoLp +겷 GYeoLh +겸 GYeoM +겹 GYeoB +겺 GYeoBs +겻 GYeoS +겼 GYeoSs +경 GYeoNg +겾 GYeoJ +겿 GYeoC +곀 GYeoK +곁 GYeoT +곂 GYeoP +곃 GYeoH +계 GYe +곅 GYeG +곆 GYeGg +곇 GYeGs +곈 GYeN +곉 GYeNj +곊 GYeNh +곋 GYeD +곌 GYeL +곍 GYeLg +곎 GYeLm +곏 GYeLb +곐 GYeLs +곑 GYeLt +곒 GYeLp +곓 GYeLh +곔 GYeM +곕 GYeB +곖 GYeBs +곗 GYeS +곘 GYeSs +곙 GYeNg +곚 GYeJ +곛 GYeC +곜 GYeK +곝 GYeT +곞 GYeP +곟 GYeH +고 GO +곡 GOG +곢 GOGg +곣 GOGs +곤 GON +곥 GONj +곦 GONh +곧 GOD +골 GOL +곩 GOLg +곪 GOLm +곫 GOLb +곬 GOLs +곭 GOLt +곮 GOLp +곯 GOLh +곰 GOM +곱 GOB +곲 GOBs +곳 GOS +곴 GOSs +공 GONg +곶 GOJ +곷 GOC +곸 GOK +곹 GOT +곺 GOP +곻 GOH +과 GWa +곽 GWaG +곾 GWaGg +곿 GWaGs +관 GWaN +괁 GWaNj +괂 GWaNh +괃 GWaD +괄 GWaL +괅 GWaLg +괆 GWaLm +괇 GWaLb +괈 GWaLs +괉 GWaLt +괊 GWaLp +괋 GWaLh +괌 GWaM +괍 GWaB +괎 GWaBs +괏 GWaS +괐 GWaSs +광 GWaNg +괒 GWaJ +괓 GWaC +괔 GWaK +괕 GWaT +괖 GWaP +괗 GWaH +괘 GWae +괙 GWaeG +괚 GWaeGg +괛 GWaeGs +괜 GWaeN +괝 GWaeNj +괞 GWaeNh +괟 GWaeD +괠 GWaeL +괡 GWaeLg +괢 GWaeLm +괣 GWaeLb +괤 GWaeLs +괥 GWaeLt +괦 GWaeLp +괧 GWaeLh +괨 GWaeM +괩 GWaeB +괪 GWaeBs +괫 GWaeS +괬 GWaeSs +괭 GWaeNg +괮 GWaeJ +괯 GWaeC +괰 GWaeK +괱 GWaeT +괲 GWaeP +괳 GWaeH +괴 GOe +괵 GOeG +괶 GOeGg +괷 GOeGs +괸 GOeN +괹 GOeNj +괺 GOeNh +괻 GOeD +괼 GOeL +괽 GOeLg +괾 GOeLm +괿 GOeLb +굀 GOeLs +굁 GOeLt +굂 GOeLp +굃 GOeLh +굄 GOeM +굅 GOeB +굆 GOeBs +굇 GOeS +굈 GOeSs +굉 GOeNg +굊 GOeJ +굋 GOeC +굌 GOeK +굍 GOeT +굎 GOeP +굏 GOeH +교 GYo +굑 GYoG +굒 GYoGg +굓 GYoGs +굔 GYoN +굕 GYoNj +굖 GYoNh +굗 GYoD +굘 GYoL +굙 GYoLg +굚 GYoLm +굛 GYoLb +굜 GYoLs +굝 GYoLt +굞 GYoLp +굟 GYoLh +굠 GYoM +굡 GYoB +굢 GYoBs +굣 GYoS +굤 GYoSs +굥 GYoNg +굦 GYoJ +굧 GYoC +굨 GYoK +굩 GYoT +굪 GYoP +굫 GYoH +구 GU +국 GUG +굮 GUGg +굯 GUGs +군 GUN +굱 GUNj +굲 GUNh +굳 GUD +굴 GUL +굵 GULg +굶 GULm +굷 GULb +굸 GULs +굹 GULt +굺 GULp +굻 GULh +굼 GUM +굽 GUB +굾 GUBs +굿 GUS +궀 GUSs +궁 GUNg +궂 GUJ +궃 GUC +궄 GUK +궅 GUT +궆 GUP +궇 GUH +궈 GWo +궉 GWoG +궊 GWoGg +궋 GWoGs +권 GWoN +궍 GWoNj +궎 GWoNh +궏 GWoD +궐 GWoL +궑 GWoLg +궒 GWoLm +궓 GWoLb +궔 GWoLs +궕 GWoLt +궖 GWoLp +궗 GWoLh +궘 GWoM +궙 GWoB +궚 GWoBs +궛 GWoS +궜 GWoSs +궝 GWoNg +궞 GWoJ +궟 GWoC +궠 GWoK +궡 GWoT +궢 GWoP +궣 GWoH +궤 GWe +궥 GWeG +궦 GWeGg +궧 GWeGs +궨 GWeN +궩 GWeNj +궪 GWeNh +궫 GWeD +궬 GWeL +궭 GWeLg +궮 GWeLm +궯 GWeLb +궰 GWeLs +궱 GWeLt +궲 GWeLp +궳 GWeLh +궴 GWeM +궵 GWeB +궶 GWeBs +궷 GWeS +궸 GWeSs +궹 GWeNg +궺 GWeJ +궻 GWeC +궼 GWeK +궽 GWeT +궾 GWeP +궿 GWeH +귀 GWi +귁 GWiG +귂 GWiGg +귃 GWiGs +귄 GWiN +귅 GWiNj +귆 GWiNh +귇 GWiD +귈 GWiL +귉 GWiLg +귊 GWiLm +귋 GWiLb +귌 GWiLs +귍 GWiLt +귎 GWiLp +귏 GWiLh +귐 GWiM +귑 GWiB +귒 GWiBs +귓 GWiS +귔 GWiSs +귕 GWiNg +귖 GWiJ +귗 GWiC +귘 GWiK +귙 GWiT +귚 GWiP +귛 GWiH +규 GYu +귝 GYuG +귞 GYuGg +귟 GYuGs +균 GYuN +귡 GYuNj +귢 GYuNh +귣 GYuD +귤 GYuL +귥 GYuLg +귦 GYuLm +귧 GYuLb +귨 GYuLs +귩 GYuLt +귪 GYuLp +귫 GYuLh +귬 GYuM +귭 GYuB +귮 GYuBs +귯 GYuS +귰 GYuSs +귱 GYuNg +귲 GYuJ +귳 GYuC +귴 GYuK +귵 GYuT +귶 GYuP +귷 GYuH +그 GEu +극 GEuG +귺 GEuGg +귻 GEuGs +근 GEuN +귽 GEuNj +귾 GEuNh +귿 GEuD +글 GEuL +긁 GEuLg +긂 GEuLm +긃 GEuLb +긄 GEuLs +긅 GEuLt +긆 GEuLp +긇 GEuLh +금 GEuM +급 GEuB +긊 GEuBs +긋 GEuS +긌 GEuSs +긍 GEuNg +긎 GEuJ +긏 GEuC +긐 GEuK +긑 GEuT +긒 GEuP +긓 GEuH +긔 GUi +긕 GUiG +긖 GUiGg +긗 GUiGs +긘 GUiN +긙 GUiNj +긚 GUiNh +긛 GUiD +긜 GUiL +긝 GUiLg +긞 GUiLm +긟 GUiLb +긠 GUiLs +긡 GUiLt +긢 GUiLp +긣 GUiLh +긤 GUiM +긥 GUiB +긦 GUiBs +긧 GUiS +긨 GUiSs +긩 GUiNg +긪 GUiJ +긫 GUiC +긬 GUiK +긭 GUiT +긮 GUiP +긯 GUiH +기 GI +긱 GIG +긲 GIGg +긳 GIGs +긴 GIN +긵 GINj +긶 GINh +긷 GID +길 GIL +긹 GILg +긺 GILm +긻 GILb +긼 GILs +긽 GILt +긾 GILp +긿 GILh +김 GIM +깁 GIB +깂 GIBs +깃 GIS +깄 GISs +깅 GINg +깆 GIJ +깇 GIC +깈 GIK +깉 GIT +깊 GIP +깋 GIH +까 GgA +깍 GgAG +깎 GgAGg +깏 GgAGs +깐 GgAN +깑 GgANj +깒 GgANh +깓 GgAD +깔 GgAL +깕 GgALg +깖 GgALm +깗 GgALb +깘 GgALs +깙 GgALt +깚 GgALp +깛 GgALh +깜 GgAM +깝 GgAB +깞 GgABs +깟 GgAS +깠 GgASs +깡 GgANg +깢 GgAJ +깣 GgAC +깤 GgAK +깥 GgAT +깦 GgAP +깧 GgAH +깨 GgAe +깩 GgAeG +깪 GgAeGg +깫 GgAeGs +깬 GgAeN +깭 GgAeNj +깮 GgAeNh +깯 GgAeD +깰 GgAeL +깱 GgAeLg +깲 GgAeLm +깳 GgAeLb +깴 GgAeLs +깵 GgAeLt +깶 GgAeLp +깷 GgAeLh +깸 GgAeM +깹 GgAeB +깺 GgAeBs +깻 GgAeS +깼 GgAeSs +깽 GgAeNg +깾 GgAeJ +깿 GgAeC +꺀 GgAeK +꺁 GgAeT +꺂 GgAeP +꺃 GgAeH +꺄 GgYa +꺅 GgYaG +꺆 GgYaGg +꺇 GgYaGs +꺈 GgYaN +꺉 GgYaNj +꺊 GgYaNh +꺋 GgYaD +꺌 GgYaL +꺍 GgYaLg +꺎 GgYaLm +꺏 GgYaLb +꺐 GgYaLs +꺑 GgYaLt +꺒 GgYaLp +꺓 GgYaLh +꺔 GgYaM +꺕 GgYaB +꺖 GgYaBs +꺗 GgYaS +꺘 GgYaSs +꺙 GgYaNg +꺚 GgYaJ +꺛 GgYaC +꺜 GgYaK +꺝 GgYaT +꺞 GgYaP +꺟 GgYaH +꺠 GgYae +꺡 GgYaeG +꺢 GgYaeGg +꺣 GgYaeGs +꺤 GgYaeN +꺥 GgYaeNj +꺦 GgYaeNh +꺧 GgYaeD +꺨 GgYaeL +꺩 GgYaeLg +꺪 GgYaeLm +꺫 GgYaeLb +꺬 GgYaeLs +꺭 GgYaeLt +꺮 GgYaeLp +꺯 GgYaeLh +꺰 GgYaeM +꺱 GgYaeB +꺲 GgYaeBs +꺳 GgYaeS +꺴 GgYaeSs +꺵 GgYaeNg +꺶 GgYaeJ +꺷 GgYaeC +꺸 GgYaeK +꺹 GgYaeT +꺺 GgYaeP +꺻 GgYaeH +꺼 GgEo +꺽 GgEoG +꺾 GgEoGg +꺿 GgEoGs +껀 GgEoN +껁 GgEoNj +껂 GgEoNh +껃 GgEoD +껄 GgEoL +껅 GgEoLg +껆 GgEoLm +껇 GgEoLb +껈 GgEoLs +껉 GgEoLt +껊 GgEoLp +껋 GgEoLh +껌 GgEoM +껍 GgEoB +껎 GgEoBs +껏 GgEoS +껐 GgEoSs +껑 GgEoNg +껒 GgEoJ +껓 GgEoC +껔 GgEoK +껕 GgEoT +껖 GgEoP +껗 GgEoH +께 GgE +껙 GgEG +껚 GgEGg +껛 GgEGs +껜 GgEN +껝 GgENj +껞 GgENh +껟 GgED +껠 GgEL +껡 GgELg +껢 GgELm +껣 GgELb +껤 GgELs +껥 GgELt +껦 GgELp +껧 GgELh +껨 GgEM +껩 GgEB +껪 GgEBs +껫 GgES +껬 GgESs +껭 GgENg +껮 GgEJ +껯 GgEC +껰 GgEK +껱 GgET +껲 GgEP +껳 GgEH +껴 GgYeo +껵 GgYeoG +껶 GgYeoGg +껷 GgYeoGs +껸 GgYeoN +껹 GgYeoNj +껺 GgYeoNh +껻 GgYeoD +껼 GgYeoL +껽 GgYeoLg +껾 GgYeoLm +껿 GgYeoLb +꼀 GgYeoLs +꼁 GgYeoLt +꼂 GgYeoLp +꼃 GgYeoLh +꼄 GgYeoM +꼅 GgYeoB +꼆 GgYeoBs +꼇 GgYeoS +꼈 GgYeoSs +꼉 GgYeoNg +꼊 GgYeoJ +꼋 GgYeoC +꼌 GgYeoK +꼍 GgYeoT +꼎 GgYeoP +꼏 GgYeoH +꼐 GgYe +꼑 GgYeG +꼒 GgYeGg +꼓 GgYeGs +꼔 GgYeN +꼕 GgYeNj +꼖 GgYeNh +꼗 GgYeD +꼘 GgYeL +꼙 GgYeLg +꼚 GgYeLm +꼛 GgYeLb +꼜 GgYeLs +꼝 GgYeLt +꼞 GgYeLp +꼟 GgYeLh +꼠 GgYeM +꼡 GgYeB +꼢 GgYeBs +꼣 GgYeS +꼤 GgYeSs +꼥 GgYeNg +꼦 GgYeJ +꼧 GgYeC +꼨 GgYeK +꼩 GgYeT +꼪 GgYeP +꼫 GgYeH +꼬 GgO +꼭 GgOG +꼮 GgOGg +꼯 GgOGs +꼰 GgON +꼱 GgONj +꼲 GgONh +꼳 GgOD +꼴 GgOL +꼵 GgOLg +꼶 GgOLm +꼷 GgOLb +꼸 GgOLs +꼹 GgOLt +꼺 GgOLp +꼻 GgOLh +꼼 GgOM +꼽 GgOB +꼾 GgOBs +꼿 GgOS +꽀 GgOSs +꽁 GgONg +꽂 GgOJ +꽃 GgOC +꽄 GgOK +꽅 GgOT +꽆 GgOP +꽇 GgOH +꽈 GgWa +꽉 GgWaG +꽊 GgWaGg +꽋 GgWaGs +꽌 GgWaN +꽍 GgWaNj +꽎 GgWaNh +꽏 GgWaD +꽐 GgWaL +꽑 GgWaLg +꽒 GgWaLm +꽓 GgWaLb +꽔 GgWaLs +꽕 GgWaLt +꽖 GgWaLp +꽗 GgWaLh +꽘 GgWaM +꽙 GgWaB +꽚 GgWaBs +꽛 GgWaS +꽜 GgWaSs +꽝 GgWaNg +꽞 GgWaJ +꽟 GgWaC +꽠 GgWaK +꽡 GgWaT +꽢 GgWaP +꽣 GgWaH +꽤 GgWae +꽥 GgWaeG +꽦 GgWaeGg +꽧 GgWaeGs +꽨 GgWaeN +꽩 GgWaeNj +꽪 GgWaeNh +꽫 GgWaeD +꽬 GgWaeL +꽭 GgWaeLg +꽮 GgWaeLm +꽯 GgWaeLb +꽰 GgWaeLs +꽱 GgWaeLt +꽲 GgWaeLp +꽳 GgWaeLh +꽴 GgWaeM +꽵 GgWaeB +꽶 GgWaeBs +꽷 GgWaeS +꽸 GgWaeSs +꽹 GgWaeNg +꽺 GgWaeJ +꽻 GgWaeC +꽼 GgWaeK +꽽 GgWaeT +꽾 GgWaeP +꽿 GgWaeH +꾀 GgOe +꾁 GgOeG +꾂 GgOeGg +꾃 GgOeGs +꾄 GgOeN +꾅 GgOeNj +꾆 GgOeNh +꾇 GgOeD +꾈 GgOeL +꾉 GgOeLg +꾊 GgOeLm +꾋 GgOeLb +꾌 GgOeLs +꾍 GgOeLt +꾎 GgOeLp +꾏 GgOeLh +꾐 GgOeM +꾑 GgOeB +꾒 GgOeBs +꾓 GgOeS +꾔 GgOeSs +꾕 GgOeNg +꾖 GgOeJ +꾗 GgOeC +꾘 GgOeK +꾙 GgOeT +꾚 GgOeP +꾛 GgOeH +꾜 GgYo +꾝 GgYoG +꾞 GgYoGg +꾟 GgYoGs +꾠 GgYoN +꾡 GgYoNj +꾢 GgYoNh +꾣 GgYoD +꾤 GgYoL +꾥 GgYoLg +꾦 GgYoLm +꾧 GgYoLb +꾨 GgYoLs +꾩 GgYoLt +꾪 GgYoLp +꾫 GgYoLh +꾬 GgYoM +꾭 GgYoB +꾮 GgYoBs +꾯 GgYoS +꾰 GgYoSs +꾱 GgYoNg +꾲 GgYoJ +꾳 GgYoC +꾴 GgYoK +꾵 GgYoT +꾶 GgYoP +꾷 GgYoH +꾸 GgU +꾹 GgUG +꾺 GgUGg +꾻 GgUGs +꾼 GgUN +꾽 GgUNj +꾾 GgUNh +꾿 GgUD +꿀 GgUL +꿁 GgULg +꿂 GgULm +꿃 GgULb +꿄 GgULs +꿅 GgULt +꿆 GgULp +꿇 GgULh +꿈 GgUM +꿉 GgUB +꿊 GgUBs +꿋 GgUS +꿌 GgUSs +꿍 GgUNg +꿎 GgUJ +꿏 GgUC +꿐 GgUK +꿑 GgUT +꿒 GgUP +꿓 GgUH +꿔 GgWo +꿕 GgWoG +꿖 GgWoGg +꿗 GgWoGs +꿘 GgWoN +꿙 GgWoNj +꿚 GgWoNh +꿛 GgWoD +꿜 GgWoL +꿝 GgWoLg +꿞 GgWoLm +꿟 GgWoLb +꿠 GgWoLs +꿡 GgWoLt +꿢 GgWoLp +꿣 GgWoLh +꿤 GgWoM +꿥 GgWoB +꿦 GgWoBs +꿧 GgWoS +꿨 GgWoSs +꿩 GgWoNg +꿪 GgWoJ +꿫 GgWoC +꿬 GgWoK +꿭 GgWoT +꿮 GgWoP +꿯 GgWoH +꿰 GgWe +꿱 GgWeG +꿲 GgWeGg +꿳 GgWeGs +꿴 GgWeN +꿵 GgWeNj +꿶 GgWeNh +꿷 GgWeD +꿸 GgWeL +꿹 GgWeLg +꿺 GgWeLm +꿻 GgWeLb +꿼 GgWeLs +꿽 GgWeLt +꿾 GgWeLp +꿿 GgWeLh +뀀 GgWeM +뀁 GgWeB +뀂 GgWeBs +뀃 GgWeS +뀄 GgWeSs +뀅 GgWeNg +뀆 GgWeJ +뀇 GgWeC +뀈 GgWeK +뀉 GgWeT +뀊 GgWeP +뀋 GgWeH +뀌 GgWi +뀍 GgWiG +뀎 GgWiGg +뀏 GgWiGs +뀐 GgWiN +뀑 GgWiNj +뀒 GgWiNh +뀓 GgWiD +뀔 GgWiL +뀕 GgWiLg +뀖 GgWiLm +뀗 GgWiLb +뀘 GgWiLs +뀙 GgWiLt +뀚 GgWiLp +뀛 GgWiLh +뀜 GgWiM +뀝 GgWiB +뀞 GgWiBs +뀟 GgWiS +뀠 GgWiSs +뀡 GgWiNg +뀢 GgWiJ +뀣 GgWiC +뀤 GgWiK +뀥 GgWiT +뀦 GgWiP +뀧 GgWiH +뀨 GgYu +뀩 GgYuG +뀪 GgYuGg +뀫 GgYuGs +뀬 GgYuN +뀭 GgYuNj +뀮 GgYuNh +뀯 GgYuD +뀰 GgYuL +뀱 GgYuLg +뀲 GgYuLm +뀳 GgYuLb +뀴 GgYuLs +뀵 GgYuLt +뀶 GgYuLp +뀷 GgYuLh +뀸 GgYuM +뀹 GgYuB +뀺 GgYuBs +뀻 GgYuS +뀼 GgYuSs +뀽 GgYuNg +뀾 GgYuJ +뀿 GgYuC +끀 GgYuK +끁 GgYuT +끂 GgYuP +끃 GgYuH +끄 GgEu +끅 GgEuG +끆 GgEuGg +끇 GgEuGs +끈 GgEuN +끉 GgEuNj +끊 GgEuNh +끋 GgEuD +끌 GgEuL +끍 GgEuLg +끎 GgEuLm +끏 GgEuLb +끐 GgEuLs +끑 GgEuLt +끒 GgEuLp +끓 GgEuLh +끔 GgEuM +끕 GgEuB +끖 GgEuBs +끗 GgEuS +끘 GgEuSs +끙 GgEuNg +끚 GgEuJ +끛 GgEuC +끜 GgEuK +끝 GgEuT +끞 GgEuP +끟 GgEuH +끠 GgUi +끡 GgUiG +끢 GgUiGg +끣 GgUiGs +끤 GgUiN +끥 GgUiNj +끦 GgUiNh +끧 GgUiD +끨 GgUiL +끩 GgUiLg +끪 GgUiLm +끫 GgUiLb +끬 GgUiLs +끭 GgUiLt +끮 GgUiLp +끯 GgUiLh +끰 GgUiM +끱 GgUiB +끲 GgUiBs +끳 GgUiS +끴 GgUiSs +끵 GgUiNg +끶 GgUiJ +끷 GgUiC +끸 GgUiK +끹 GgUiT +끺 GgUiP +끻 GgUiH +끼 GgI +끽 GgIG +끾 GgIGg +끿 GgIGs +낀 GgIN +낁 GgINj +낂 GgINh +낃 GgID +낄 GgIL +낅 GgILg +낆 GgILm +낇 GgILb +낈 GgILs +낉 GgILt +낊 GgILp +낋 GgILh +낌 GgIM +낍 GgIB +낎 GgIBs +낏 GgIS +낐 GgISs +낑 GgINg +낒 GgIJ +낓 GgIC +낔 GgIK +낕 GgIT +낖 GgIP +낗 GgIH +나 NA +낙 NAG +낚 NAGg +낛 NAGs +난 NAN +낝 NANj +낞 NANh +낟 NAD +날 NAL +낡 NALg +낢 NALm +낣 NALb +낤 NALs +낥 NALt +낦 NALp +낧 NALh +남 NAM +납 NAB +낪 NABs +낫 NAS +났 NASs +낭 NANg +낮 NAJ +낯 NAC +낰 NAK +낱 NAT +낲 NAP +낳 NAH +내 NAe +낵 NAeG +낶 NAeGg +낷 NAeGs +낸 NAeN +낹 NAeNj +낺 NAeNh +낻 NAeD +낼 NAeL +낽 NAeLg +낾 NAeLm +낿 NAeLb +냀 NAeLs +냁 NAeLt +냂 NAeLp +냃 NAeLh +냄 NAeM +냅 NAeB +냆 NAeBs +냇 NAeS +냈 NAeSs +냉 NAeNg +냊 NAeJ +냋 NAeC +냌 NAeK +냍 NAeT +냎 NAeP +냏 NAeH +냐 NYa +냑 NYaG +냒 NYaGg +냓 NYaGs +냔 NYaN +냕 NYaNj +냖 NYaNh +냗 NYaD +냘 NYaL +냙 NYaLg +냚 NYaLm +냛 NYaLb +냜 NYaLs +냝 NYaLt +냞 NYaLp +냟 NYaLh +냠 NYaM +냡 NYaB +냢 NYaBs +냣 NYaS +냤 NYaSs +냥 NYaNg +냦 NYaJ +냧 NYaC +냨 NYaK +냩 NYaT +냪 NYaP +냫 NYaH +냬 NYae +냭 NYaeG +냮 NYaeGg +냯 NYaeGs +냰 NYaeN +냱 NYaeNj +냲 NYaeNh +냳 NYaeD +냴 NYaeL +냵 NYaeLg +냶 NYaeLm +냷 NYaeLb +냸 NYaeLs +냹 NYaeLt +냺 NYaeLp +냻 NYaeLh +냼 NYaeM +냽 NYaeB +냾 NYaeBs +냿 NYaeS +넀 NYaeSs +넁 NYaeNg +넂 NYaeJ +넃 NYaeC +넄 NYaeK +넅 NYaeT +넆 NYaeP +넇 NYaeH +너 NEo +넉 NEoG +넊 NEoGg +넋 NEoGs +넌 NEoN +넍 NEoNj +넎 NEoNh +넏 NEoD +널 NEoL +넑 NEoLg +넒 NEoLm +넓 NEoLb +넔 NEoLs +넕 NEoLt +넖 NEoLp +넗 NEoLh +넘 NEoM +넙 NEoB +넚 NEoBs +넛 NEoS +넜 NEoSs +넝 NEoNg +넞 NEoJ +넟 NEoC +넠 NEoK +넡 NEoT +넢 NEoP +넣 NEoH +네 NE +넥 NEG +넦 NEGg +넧 NEGs +넨 NEN +넩 NENj +넪 NENh +넫 NED +넬 NEL +넭 NELg +넮 NELm +넯 NELb +넰 NELs +넱 NELt +넲 NELp +넳 NELh +넴 NEM +넵 NEB +넶 NEBs +넷 NES +넸 NESs +넹 NENg +넺 NEJ +넻 NEC +넼 NEK +넽 NET +넾 NEP +넿 NEH +녀 NYeo +녁 NYeoG +녂 NYeoGg +녃 NYeoGs +년 NYeoN +녅 NYeoNj +녆 NYeoNh +녇 NYeoD +녈 NYeoL +녉 NYeoLg +녊 NYeoLm +녋 NYeoLb +녌 NYeoLs +녍 NYeoLt +녎 NYeoLp +녏 NYeoLh +념 NYeoM +녑 NYeoB +녒 NYeoBs +녓 NYeoS +녔 NYeoSs +녕 NYeoNg +녖 NYeoJ +녗 NYeoC +녘 NYeoK +녙 NYeoT +녚 NYeoP +녛 NYeoH +녜 NYe +녝 NYeG +녞 NYeGg +녟 NYeGs +녠 NYeN +녡 NYeNj +녢 NYeNh +녣 NYeD +녤 NYeL +녥 NYeLg +녦 NYeLm +녧 NYeLb +녨 NYeLs +녩 NYeLt +녪 NYeLp +녫 NYeLh +녬 NYeM +녭 NYeB +녮 NYeBs +녯 NYeS +녰 NYeSs +녱 NYeNg +녲 NYeJ +녳 NYeC +녴 NYeK +녵 NYeT +녶 NYeP +녷 NYeH +노 NO +녹 NOG +녺 NOGg +녻 NOGs +논 NON +녽 NONj +녾 NONh +녿 NOD +놀 NOL +놁 NOLg +놂 NOLm +놃 NOLb +놄 NOLs +놅 NOLt +놆 NOLp +놇 NOLh +놈 NOM +놉 NOB +놊 NOBs +놋 NOS +놌 NOSs +농 NONg +놎 NOJ +놏 NOC +놐 NOK +놑 NOT +높 NOP +놓 NOH +놔 NWa +놕 NWaG +놖 NWaGg +놗 NWaGs +놘 NWaN +놙 NWaNj +놚 NWaNh +놛 NWaD +놜 NWaL +놝 NWaLg +놞 NWaLm +놟 NWaLb +놠 NWaLs +놡 NWaLt +놢 NWaLp +놣 NWaLh +놤 NWaM +놥 NWaB +놦 NWaBs +놧 NWaS +놨 NWaSs +놩 NWaNg +놪 NWaJ +놫 NWaC +놬 NWaK +놭 NWaT +놮 NWaP +놯 NWaH +놰 NWae +놱 NWaeG +놲 NWaeGg +놳 NWaeGs +놴 NWaeN +놵 NWaeNj +놶 NWaeNh +놷 NWaeD +놸 NWaeL +놹 NWaeLg +놺 NWaeLm +놻 NWaeLb +놼 NWaeLs +놽 NWaeLt +놾 NWaeLp +놿 NWaeLh +뇀 NWaeM +뇁 NWaeB +뇂 NWaeBs +뇃 NWaeS +뇄 NWaeSs +뇅 NWaeNg +뇆 NWaeJ +뇇 NWaeC +뇈 NWaeK +뇉 NWaeT +뇊 NWaeP +뇋 NWaeH +뇌 NOe +뇍 NOeG +뇎 NOeGg +뇏 NOeGs +뇐 NOeN +뇑 NOeNj +뇒 NOeNh +뇓 NOeD +뇔 NOeL +뇕 NOeLg +뇖 NOeLm +뇗 NOeLb +뇘 NOeLs +뇙 NOeLt +뇚 NOeLp +뇛 NOeLh +뇜 NOeM +뇝 NOeB +뇞 NOeBs +뇟 NOeS +뇠 NOeSs +뇡 NOeNg +뇢 NOeJ +뇣 NOeC +뇤 NOeK +뇥 NOeT +뇦 NOeP +뇧 NOeH +뇨 NYo +뇩 NYoG +뇪 NYoGg +뇫 NYoGs +뇬 NYoN +뇭 NYoNj +뇮 NYoNh +뇯 NYoD +뇰 NYoL +뇱 NYoLg +뇲 NYoLm +뇳 NYoLb +뇴 NYoLs +뇵 NYoLt +뇶 NYoLp +뇷 NYoLh +뇸 NYoM +뇹 NYoB +뇺 NYoBs +뇻 NYoS +뇼 NYoSs +뇽 NYoNg +뇾 NYoJ +뇿 NYoC +눀 NYoK +눁 NYoT +눂 NYoP +눃 NYoH +누 NU +눅 NUG +눆 NUGg +눇 NUGs +눈 NUN +눉 NUNj +눊 NUNh +눋 NUD +눌 NUL +눍 NULg +눎 NULm +눏 NULb +눐 NULs +눑 NULt +눒 NULp +눓 NULh +눔 NUM +눕 NUB +눖 NUBs +눗 NUS +눘 NUSs +눙 NUNg +눚 NUJ +눛 NUC +눜 NUK +눝 NUT +눞 NUP +눟 NUH +눠 NWo +눡 NWoG +눢 NWoGg +눣 NWoGs +눤 NWoN +눥 NWoNj +눦 NWoNh +눧 NWoD +눨 NWoL +눩 NWoLg +눪 NWoLm +눫 NWoLb +눬 NWoLs +눭 NWoLt +눮 NWoLp +눯 NWoLh +눰 NWoM +눱 NWoB +눲 NWoBs +눳 NWoS +눴 NWoSs +눵 NWoNg +눶 NWoJ +눷 NWoC +눸 NWoK +눹 NWoT +눺 NWoP +눻 NWoH +눼 NWe +눽 NWeG +눾 NWeGg +눿 NWeGs +뉀 NWeN +뉁 NWeNj +뉂 NWeNh +뉃 NWeD +뉄 NWeL +뉅 NWeLg +뉆 NWeLm +뉇 NWeLb +뉈 NWeLs +뉉 NWeLt +뉊 NWeLp +뉋 NWeLh +뉌 NWeM +뉍 NWeB +뉎 NWeBs +뉏 NWeS +뉐 NWeSs +뉑 NWeNg +뉒 NWeJ +뉓 NWeC +뉔 NWeK +뉕 NWeT +뉖 NWeP +뉗 NWeH +뉘 NWi +뉙 NWiG +뉚 NWiGg +뉛 NWiGs +뉜 NWiN +뉝 NWiNj +뉞 NWiNh +뉟 NWiD +뉠 NWiL +뉡 NWiLg +뉢 NWiLm +뉣 NWiLb +뉤 NWiLs +뉥 NWiLt +뉦 NWiLp +뉧 NWiLh +뉨 NWiM +뉩 NWiB +뉪 NWiBs +뉫 NWiS +뉬 NWiSs +뉭 NWiNg +뉮 NWiJ +뉯 NWiC +뉰 NWiK +뉱 NWiT +뉲 NWiP +뉳 NWiH +뉴 NYu +뉵 NYuG +뉶 NYuGg +뉷 NYuGs +뉸 NYuN +뉹 NYuNj +뉺 NYuNh +뉻 NYuD +뉼 NYuL +뉽 NYuLg +뉾 NYuLm +뉿 NYuLb +늀 NYuLs +늁 NYuLt +늂 NYuLp +늃 NYuLh +늄 NYuM +늅 NYuB +늆 NYuBs +늇 NYuS +늈 NYuSs +늉 NYuNg +늊 NYuJ +늋 NYuC +늌 NYuK +늍 NYuT +늎 NYuP +늏 NYuH +느 NEu +늑 NEuG +늒 NEuGg +늓 NEuGs +는 NEuN +늕 NEuNj +늖 NEuNh +늗 NEuD +늘 NEuL +늙 NEuLg +늚 NEuLm +늛 NEuLb +늜 NEuLs +늝 NEuLt +늞 NEuLp +늟 NEuLh +늠 NEuM +늡 NEuB +늢 NEuBs +늣 NEuS +늤 NEuSs +능 NEuNg +늦 NEuJ +늧 NEuC +늨 NEuK +늩 NEuT +늪 NEuP +늫 NEuH +늬 NUi +늭 NUiG +늮 NUiGg +늯 NUiGs +늰 NUiN +늱 NUiNj +늲 NUiNh +늳 NUiD +늴 NUiL +늵 NUiLg +늶 NUiLm +늷 NUiLb +늸 NUiLs +늹 NUiLt +늺 NUiLp +늻 NUiLh +늼 NUiM +늽 NUiB +늾 NUiBs +늿 NUiS +닀 NUiSs +닁 NUiNg +닂 NUiJ +닃 NUiC +닄 NUiK +닅 NUiT +닆 NUiP +닇 NUiH +니 NI +닉 NIG +닊 NIGg +닋 NIGs +닌 NIN +닍 NINj +닎 NINh +닏 NID +닐 NIL +닑 NILg +닒 NILm +닓 NILb +닔 NILs +닕 NILt +닖 NILp +닗 NILh +님 NIM +닙 NIB +닚 NIBs +닛 NIS +닜 NISs +닝 NINg +닞 NIJ +닟 NIC +닠 NIK +닡 NIT +닢 NIP +닣 NIH +다 DA +닥 DAG +닦 DAGg +닧 DAGs +단 DAN +닩 DANj +닪 DANh +닫 DAD +달 DAL +닭 DALg +닮 DALm +닯 DALb +닰 DALs +닱 DALt +닲 DALp +닳 DALh +담 DAM +답 DAB +닶 DABs +닷 DAS +닸 DASs +당 DANg +닺 DAJ +닻 DAC +닼 DAK +닽 DAT +닾 DAP +닿 DAH +대 DAe +댁 DAeG +댂 DAeGg +댃 DAeGs +댄 DAeN +댅 DAeNj +댆 DAeNh +댇 DAeD +댈 DAeL +댉 DAeLg +댊 DAeLm +댋 DAeLb +댌 DAeLs +댍 DAeLt +댎 DAeLp +댏 DAeLh +댐 DAeM +댑 DAeB +댒 DAeBs +댓 DAeS +댔 DAeSs +댕 DAeNg +댖 DAeJ +댗 DAeC +댘 DAeK +댙 DAeT +댚 DAeP +댛 DAeH +댜 DYa +댝 DYaG +댞 DYaGg +댟 DYaGs +댠 DYaN +댡 DYaNj +댢 DYaNh +댣 DYaD +댤 DYaL +댥 DYaLg +댦 DYaLm +댧 DYaLb +댨 DYaLs +댩 DYaLt +댪 DYaLp +댫 DYaLh +댬 DYaM +댭 DYaB +댮 DYaBs +댯 DYaS +댰 DYaSs +댱 DYaNg +댲 DYaJ +댳 DYaC +댴 DYaK +댵 DYaT +댶 DYaP +댷 DYaH +댸 DYae +댹 DYaeG +댺 DYaeGg +댻 DYaeGs +댼 DYaeN +댽 DYaeNj +댾 DYaeNh +댿 DYaeD +덀 DYaeL +덁 DYaeLg +덂 DYaeLm +덃 DYaeLb +덄 DYaeLs +덅 DYaeLt +덆 DYaeLp +덇 DYaeLh +덈 DYaeM +덉 DYaeB +덊 DYaeBs +덋 DYaeS +덌 DYaeSs +덍 DYaeNg +덎 DYaeJ +덏 DYaeC +덐 DYaeK +덑 DYaeT +덒 DYaeP +덓 DYaeH +더 DEo +덕 DEoG +덖 DEoGg +덗 DEoGs +던 DEoN +덙 DEoNj +덚 DEoNh +덛 DEoD +덜 DEoL +덝 DEoLg +덞 DEoLm +덟 DEoLb +덠 DEoLs +덡 DEoLt +덢 DEoLp +덣 DEoLh +덤 DEoM +덥 DEoB +덦 DEoBs +덧 DEoS +덨 DEoSs +덩 DEoNg +덪 DEoJ +덫 DEoC +덬 DEoK +덭 DEoT +덮 DEoP +덯 DEoH +데 DE +덱 DEG +덲 DEGg +덳 DEGs +덴 DEN +덵 DENj +덶 DENh +덷 DED +델 DEL +덹 DELg +덺 DELm +덻 DELb +덼 DELs +덽 DELt +덾 DELp +덿 DELh +뎀 DEM +뎁 DEB +뎂 DEBs +뎃 DES +뎄 DESs +뎅 DENg +뎆 DEJ +뎇 DEC +뎈 DEK +뎉 DET +뎊 DEP +뎋 DEH +뎌 DYeo +뎍 DYeoG +뎎 DYeoGg +뎏 DYeoGs +뎐 DYeoN +뎑 DYeoNj +뎒 DYeoNh +뎓 DYeoD +뎔 DYeoL +뎕 DYeoLg +뎖 DYeoLm +뎗 DYeoLb +뎘 DYeoLs +뎙 DYeoLt +뎚 DYeoLp +뎛 DYeoLh +뎜 DYeoM +뎝 DYeoB +뎞 DYeoBs +뎟 DYeoS +뎠 DYeoSs +뎡 DYeoNg +뎢 DYeoJ +뎣 DYeoC +뎤 DYeoK +뎥 DYeoT +뎦 DYeoP +뎧 DYeoH +뎨 DYe +뎩 DYeG +뎪 DYeGg +뎫 DYeGs +뎬 DYeN +뎭 DYeNj +뎮 DYeNh +뎯 DYeD +뎰 DYeL +뎱 DYeLg +뎲 DYeLm +뎳 DYeLb +뎴 DYeLs +뎵 DYeLt +뎶 DYeLp +뎷 DYeLh +뎸 DYeM +뎹 DYeB +뎺 DYeBs +뎻 DYeS +뎼 DYeSs +뎽 DYeNg +뎾 DYeJ +뎿 DYeC +돀 DYeK +돁 DYeT +돂 DYeP +돃 DYeH +도 DO +독 DOG +돆 DOGg +돇 DOGs +돈 DON +돉 DONj +돊 DONh +돋 DOD +돌 DOL +돍 DOLg +돎 DOLm +돏 DOLb +돐 DOLs +돑 DOLt +돒 DOLp +돓 DOLh +돔 DOM +돕 DOB +돖 DOBs +돗 DOS +돘 DOSs +동 DONg +돚 DOJ +돛 DOC +돜 DOK +돝 DOT +돞 DOP +돟 DOH +돠 DWa +돡 DWaG +돢 DWaGg +돣 DWaGs +돤 DWaN +돥 DWaNj +돦 DWaNh +돧 DWaD +돨 DWaL +돩 DWaLg +돪 DWaLm +돫 DWaLb +돬 DWaLs +돭 DWaLt +돮 DWaLp +돯 DWaLh +돰 DWaM +돱 DWaB +돲 DWaBs +돳 DWaS +돴 DWaSs +돵 DWaNg +돶 DWaJ +돷 DWaC +돸 DWaK +돹 DWaT +돺 DWaP +돻 DWaH +돼 DWae +돽 DWaeG +돾 DWaeGg +돿 DWaeGs +됀 DWaeN +됁 DWaeNj +됂 DWaeNh +됃 DWaeD +됄 DWaeL +됅 DWaeLg +됆 DWaeLm +됇 DWaeLb +됈 DWaeLs +됉 DWaeLt +됊 DWaeLp +됋 DWaeLh +됌 DWaeM +됍 DWaeB +됎 DWaeBs +됏 DWaeS +됐 DWaeSs +됑 DWaeNg +됒 DWaeJ +됓 DWaeC +됔 DWaeK +됕 DWaeT +됖 DWaeP +됗 DWaeH +되 DOe +됙 DOeG +됚 DOeGg +됛 DOeGs +된 DOeN +됝 DOeNj +됞 DOeNh +됟 DOeD +될 DOeL +됡 DOeLg +됢 DOeLm +됣 DOeLb +됤 DOeLs +됥 DOeLt +됦 DOeLp +됧 DOeLh +됨 DOeM +됩 DOeB +됪 DOeBs +됫 DOeS +됬 DOeSs +됭 DOeNg +됮 DOeJ +됯 DOeC +됰 DOeK +됱 DOeT +됲 DOeP +됳 DOeH +됴 DYo +됵 DYoG +됶 DYoGg +됷 DYoGs +됸 DYoN +됹 DYoNj +됺 DYoNh +됻 DYoD +됼 DYoL +됽 DYoLg +됾 DYoLm +됿 DYoLb +둀 DYoLs +둁 DYoLt +둂 DYoLp +둃 DYoLh +둄 DYoM +둅 DYoB +둆 DYoBs +둇 DYoS +둈 DYoSs +둉 DYoNg +둊 DYoJ +둋 DYoC +둌 DYoK +둍 DYoT +둎 DYoP +둏 DYoH +두 DU +둑 DUG +둒 DUGg +둓 DUGs +둔 DUN +둕 DUNj +둖 DUNh +둗 DUD +둘 DUL +둙 DULg +둚 DULm +둛 DULb +둜 DULs +둝 DULt +둞 DULp +둟 DULh +둠 DUM +둡 DUB +둢 DUBs +둣 DUS +둤 DUSs +둥 DUNg +둦 DUJ +둧 DUC +둨 DUK +둩 DUT +둪 DUP +둫 DUH +둬 DWo +둭 DWoG +둮 DWoGg +둯 DWoGs +둰 DWoN +둱 DWoNj +둲 DWoNh +둳 DWoD +둴 DWoL +둵 DWoLg +둶 DWoLm +둷 DWoLb +둸 DWoLs +둹 DWoLt +둺 DWoLp +둻 DWoLh +둼 DWoM +둽 DWoB +둾 DWoBs +둿 DWoS +뒀 DWoSs +뒁 DWoNg +뒂 DWoJ +뒃 DWoC +뒄 DWoK +뒅 DWoT +뒆 DWoP +뒇 DWoH +뒈 DWe +뒉 DWeG +뒊 DWeGg +뒋 DWeGs +뒌 DWeN +뒍 DWeNj +뒎 DWeNh +뒏 DWeD +뒐 DWeL +뒑 DWeLg +뒒 DWeLm +뒓 DWeLb +뒔 DWeLs +뒕 DWeLt +뒖 DWeLp +뒗 DWeLh +뒘 DWeM +뒙 DWeB +뒚 DWeBs +뒛 DWeS +뒜 DWeSs +뒝 DWeNg +뒞 DWeJ +뒟 DWeC +뒠 DWeK +뒡 DWeT +뒢 DWeP +뒣 DWeH +뒤 DWi +뒥 DWiG +뒦 DWiGg +뒧 DWiGs +뒨 DWiN +뒩 DWiNj +뒪 DWiNh +뒫 DWiD +뒬 DWiL +뒭 DWiLg +뒮 DWiLm +뒯 DWiLb +뒰 DWiLs +뒱 DWiLt +뒲 DWiLp +뒳 DWiLh +뒴 DWiM +뒵 DWiB +뒶 DWiBs +뒷 DWiS +뒸 DWiSs +뒹 DWiNg +뒺 DWiJ +뒻 DWiC +뒼 DWiK +뒽 DWiT +뒾 DWiP +뒿 DWiH +듀 DYu +듁 DYuG +듂 DYuGg +듃 DYuGs +듄 DYuN +듅 DYuNj +듆 DYuNh +듇 DYuD +듈 DYuL +듉 DYuLg +듊 DYuLm +듋 DYuLb +듌 DYuLs +듍 DYuLt +듎 DYuLp +듏 DYuLh +듐 DYuM +듑 DYuB +듒 DYuBs +듓 DYuS +듔 DYuSs +듕 DYuNg +듖 DYuJ +듗 DYuC +듘 DYuK +듙 DYuT +듚 DYuP +듛 DYuH +드 DEu +득 DEuG +듞 DEuGg +듟 DEuGs +든 DEuN +듡 DEuNj +듢 DEuNh +듣 DEuD +들 DEuL +듥 DEuLg +듦 DEuLm +듧 DEuLb +듨 DEuLs +듩 DEuLt +듪 DEuLp +듫 DEuLh +듬 DEuM +듭 DEuB +듮 DEuBs +듯 DEuS +듰 DEuSs +등 DEuNg +듲 DEuJ +듳 DEuC +듴 DEuK +듵 DEuT +듶 DEuP +듷 DEuH +듸 DUi +듹 DUiG +듺 DUiGg +듻 DUiGs +듼 DUiN +듽 DUiNj +듾 DUiNh +듿 DUiD +딀 DUiL +딁 DUiLg +딂 DUiLm +딃 DUiLb +딄 DUiLs +딅 DUiLt +딆 DUiLp +딇 DUiLh +딈 DUiM +딉 DUiB +딊 DUiBs +딋 DUiS +딌 DUiSs +딍 DUiNg +딎 DUiJ +딏 DUiC +딐 DUiK +딑 DUiT +딒 DUiP +딓 DUiH +디 DI +딕 DIG +딖 DIGg +딗 DIGs +딘 DIN +딙 DINj +딚 DINh +딛 DID +딜 DIL +딝 DILg +딞 DILm +딟 DILb +딠 DILs +딡 DILt +딢 DILp +딣 DILh +딤 DIM +딥 DIB +딦 DIBs +딧 DIS +딨 DISs +딩 DINg +딪 DIJ +딫 DIC +딬 DIK +딭 DIT +딮 DIP +딯 DIH +따 DdA +딱 DdAG +딲 DdAGg +딳 DdAGs +딴 DdAN +딵 DdANj +딶 DdANh +딷 DdAD +딸 DdAL +딹 DdALg +딺 DdALm +딻 DdALb +딼 DdALs +딽 DdALt +딾 DdALp +딿 DdALh +땀 DdAM +땁 DdAB +땂 DdABs +땃 DdAS +땄 DdASs +땅 DdANg +땆 DdAJ +땇 DdAC +땈 DdAK +땉 DdAT +땊 DdAP +땋 DdAH +때 DdAe +땍 DdAeG +땎 DdAeGg +땏 DdAeGs +땐 DdAeN +땑 DdAeNj +땒 DdAeNh +땓 DdAeD +땔 DdAeL +땕 DdAeLg +땖 DdAeLm +땗 DdAeLb +땘 DdAeLs +땙 DdAeLt +땚 DdAeLp +땛 DdAeLh +땜 DdAeM +땝 DdAeB +땞 DdAeBs +땟 DdAeS +땠 DdAeSs +땡 DdAeNg +땢 DdAeJ +땣 DdAeC +땤 DdAeK +땥 DdAeT +땦 DdAeP +땧 DdAeH +땨 DdYa +땩 DdYaG +땪 DdYaGg +땫 DdYaGs +땬 DdYaN +땭 DdYaNj +땮 DdYaNh +땯 DdYaD +땰 DdYaL +땱 DdYaLg +땲 DdYaLm +땳 DdYaLb +땴 DdYaLs +땵 DdYaLt +땶 DdYaLp +땷 DdYaLh +땸 DdYaM +땹 DdYaB +땺 DdYaBs +땻 DdYaS +땼 DdYaSs +땽 DdYaNg +땾 DdYaJ +땿 DdYaC +떀 DdYaK +떁 DdYaT +떂 DdYaP +떃 DdYaH +떄 DdYae +떅 DdYaeG +떆 DdYaeGg +떇 DdYaeGs +떈 DdYaeN +떉 DdYaeNj +떊 DdYaeNh +떋 DdYaeD +떌 DdYaeL +떍 DdYaeLg +떎 DdYaeLm +떏 DdYaeLb +떐 DdYaeLs +떑 DdYaeLt +떒 DdYaeLp +떓 DdYaeLh +떔 DdYaeM +떕 DdYaeB +떖 DdYaeBs +떗 DdYaeS +떘 DdYaeSs +떙 DdYaeNg +떚 DdYaeJ +떛 DdYaeC +떜 DdYaeK +떝 DdYaeT +떞 DdYaeP +떟 DdYaeH +떠 DdEo +떡 DdEoG +떢 DdEoGg +떣 DdEoGs +떤 DdEoN +떥 DdEoNj +떦 DdEoNh +떧 DdEoD +떨 DdEoL +떩 DdEoLg +떪 DdEoLm +떫 DdEoLb +떬 DdEoLs +떭 DdEoLt +떮 DdEoLp +떯 DdEoLh +떰 DdEoM +떱 DdEoB +떲 DdEoBs +떳 DdEoS +떴 DdEoSs +떵 DdEoNg +떶 DdEoJ +떷 DdEoC +떸 DdEoK +떹 DdEoT +떺 DdEoP +떻 DdEoH +떼 DdE +떽 DdEG +떾 DdEGg +떿 DdEGs +뗀 DdEN +뗁 DdENj +뗂 DdENh +뗃 DdED +뗄 DdEL +뗅 DdELg +뗆 DdELm +뗇 DdELb +뗈 DdELs +뗉 DdELt +뗊 DdELp +뗋 DdELh +뗌 DdEM +뗍 DdEB +뗎 DdEBs +뗏 DdES +뗐 DdESs +뗑 DdENg +뗒 DdEJ +뗓 DdEC +뗔 DdEK +뗕 DdET +뗖 DdEP +뗗 DdEH +뗘 DdYeo +뗙 DdYeoG +뗚 DdYeoGg +뗛 DdYeoGs +뗜 DdYeoN +뗝 DdYeoNj +뗞 DdYeoNh +뗟 DdYeoD +뗠 DdYeoL +뗡 DdYeoLg +뗢 DdYeoLm +뗣 DdYeoLb +뗤 DdYeoLs +뗥 DdYeoLt +뗦 DdYeoLp +뗧 DdYeoLh +뗨 DdYeoM +뗩 DdYeoB +뗪 DdYeoBs +뗫 DdYeoS +뗬 DdYeoSs +뗭 DdYeoNg +뗮 DdYeoJ +뗯 DdYeoC +뗰 DdYeoK +뗱 DdYeoT +뗲 DdYeoP +뗳 DdYeoH +뗴 DdYe +뗵 DdYeG +뗶 DdYeGg +뗷 DdYeGs +뗸 DdYeN +뗹 DdYeNj +뗺 DdYeNh +뗻 DdYeD +뗼 DdYeL +뗽 DdYeLg +뗾 DdYeLm +뗿 DdYeLb +똀 DdYeLs +똁 DdYeLt +똂 DdYeLp +똃 DdYeLh +똄 DdYeM +똅 DdYeB +똆 DdYeBs +똇 DdYeS +똈 DdYeSs +똉 DdYeNg +똊 DdYeJ +똋 DdYeC +똌 DdYeK +똍 DdYeT +똎 DdYeP +똏 DdYeH +또 DdO +똑 DdOG +똒 DdOGg +똓 DdOGs +똔 DdON +똕 DdONj +똖 DdONh +똗 DdOD +똘 DdOL +똙 DdOLg +똚 DdOLm +똛 DdOLb +똜 DdOLs +똝 DdOLt +똞 DdOLp +똟 DdOLh +똠 DdOM +똡 DdOB +똢 DdOBs +똣 DdOS +똤 DdOSs +똥 DdONg +똦 DdOJ +똧 DdOC +똨 DdOK +똩 DdOT +똪 DdOP +똫 DdOH +똬 DdWa +똭 DdWaG +똮 DdWaGg +똯 DdWaGs +똰 DdWaN +똱 DdWaNj +똲 DdWaNh +똳 DdWaD +똴 DdWaL +똵 DdWaLg +똶 DdWaLm +똷 DdWaLb +똸 DdWaLs +똹 DdWaLt +똺 DdWaLp +똻 DdWaLh +똼 DdWaM +똽 DdWaB +똾 DdWaBs +똿 DdWaS +뙀 DdWaSs +뙁 DdWaNg +뙂 DdWaJ +뙃 DdWaC +뙄 DdWaK +뙅 DdWaT +뙆 DdWaP +뙇 DdWaH +뙈 DdWae +뙉 DdWaeG +뙊 DdWaeGg +뙋 DdWaeGs +뙌 DdWaeN +뙍 DdWaeNj +뙎 DdWaeNh +뙏 DdWaeD +뙐 DdWaeL +뙑 DdWaeLg +뙒 DdWaeLm +뙓 DdWaeLb +뙔 DdWaeLs +뙕 DdWaeLt +뙖 DdWaeLp +뙗 DdWaeLh +뙘 DdWaeM +뙙 DdWaeB +뙚 DdWaeBs +뙛 DdWaeS +뙜 DdWaeSs +뙝 DdWaeNg +뙞 DdWaeJ +뙟 DdWaeC +뙠 DdWaeK +뙡 DdWaeT +뙢 DdWaeP +뙣 DdWaeH +뙤 DdOe +뙥 DdOeG +뙦 DdOeGg +뙧 DdOeGs +뙨 DdOeN +뙩 DdOeNj +뙪 DdOeNh +뙫 DdOeD +뙬 DdOeL +뙭 DdOeLg +뙮 DdOeLm +뙯 DdOeLb +뙰 DdOeLs +뙱 DdOeLt +뙲 DdOeLp +뙳 DdOeLh +뙴 DdOeM +뙵 DdOeB +뙶 DdOeBs +뙷 DdOeS +뙸 DdOeSs +뙹 DdOeNg +뙺 DdOeJ +뙻 DdOeC +뙼 DdOeK +뙽 DdOeT +뙾 DdOeP +뙿 DdOeH +뚀 DdYo +뚁 DdYoG +뚂 DdYoGg +뚃 DdYoGs +뚄 DdYoN +뚅 DdYoNj +뚆 DdYoNh +뚇 DdYoD +뚈 DdYoL +뚉 DdYoLg +뚊 DdYoLm +뚋 DdYoLb +뚌 DdYoLs +뚍 DdYoLt +뚎 DdYoLp +뚏 DdYoLh +뚐 DdYoM +뚑 DdYoB +뚒 DdYoBs +뚓 DdYoS +뚔 DdYoSs +뚕 DdYoNg +뚖 DdYoJ +뚗 DdYoC +뚘 DdYoK +뚙 DdYoT +뚚 DdYoP +뚛 DdYoH +뚜 DdU +뚝 DdUG +뚞 DdUGg +뚟 DdUGs +뚠 DdUN +뚡 DdUNj +뚢 DdUNh +뚣 DdUD +뚤 DdUL +뚥 DdULg +뚦 DdULm +뚧 DdULb +뚨 DdULs +뚩 DdULt +뚪 DdULp +뚫 DdULh +뚬 DdUM +뚭 DdUB +뚮 DdUBs +뚯 DdUS +뚰 DdUSs +뚱 DdUNg +뚲 DdUJ +뚳 DdUC +뚴 DdUK +뚵 DdUT +뚶 DdUP +뚷 DdUH +뚸 DdWo +뚹 DdWoG +뚺 DdWoGg +뚻 DdWoGs +뚼 DdWoN +뚽 DdWoNj +뚾 DdWoNh +뚿 DdWoD +뛀 DdWoL +뛁 DdWoLg +뛂 DdWoLm +뛃 DdWoLb +뛄 DdWoLs +뛅 DdWoLt +뛆 DdWoLp +뛇 DdWoLh +뛈 DdWoM +뛉 DdWoB +뛊 DdWoBs +뛋 DdWoS +뛌 DdWoSs +뛍 DdWoNg +뛎 DdWoJ +뛏 DdWoC +뛐 DdWoK +뛑 DdWoT +뛒 DdWoP +뛓 DdWoH +뛔 DdWe +뛕 DdWeG +뛖 DdWeGg +뛗 DdWeGs +뛘 DdWeN +뛙 DdWeNj +뛚 DdWeNh +뛛 DdWeD +뛜 DdWeL +뛝 DdWeLg +뛞 DdWeLm +뛟 DdWeLb +뛠 DdWeLs +뛡 DdWeLt +뛢 DdWeLp +뛣 DdWeLh +뛤 DdWeM +뛥 DdWeB +뛦 DdWeBs +뛧 DdWeS +뛨 DdWeSs +뛩 DdWeNg +뛪 DdWeJ +뛫 DdWeC +뛬 DdWeK +뛭 DdWeT +뛮 DdWeP +뛯 DdWeH +뛰 DdWi +뛱 DdWiG +뛲 DdWiGg +뛳 DdWiGs +뛴 DdWiN +뛵 DdWiNj +뛶 DdWiNh +뛷 DdWiD +뛸 DdWiL +뛹 DdWiLg +뛺 DdWiLm +뛻 DdWiLb +뛼 DdWiLs +뛽 DdWiLt +뛾 DdWiLp +뛿 DdWiLh +뜀 DdWiM +뜁 DdWiB +뜂 DdWiBs +뜃 DdWiS +뜄 DdWiSs +뜅 DdWiNg +뜆 DdWiJ +뜇 DdWiC +뜈 DdWiK +뜉 DdWiT +뜊 DdWiP +뜋 DdWiH +뜌 DdYu +뜍 DdYuG +뜎 DdYuGg +뜏 DdYuGs +뜐 DdYuN +뜑 DdYuNj +뜒 DdYuNh +뜓 DdYuD +뜔 DdYuL +뜕 DdYuLg +뜖 DdYuLm +뜗 DdYuLb +뜘 DdYuLs +뜙 DdYuLt +뜚 DdYuLp +뜛 DdYuLh +뜜 DdYuM +뜝 DdYuB +뜞 DdYuBs +뜟 DdYuS +뜠 DdYuSs +뜡 DdYuNg +뜢 DdYuJ +뜣 DdYuC +뜤 DdYuK +뜥 DdYuT +뜦 DdYuP +뜧 DdYuH +뜨 DdEu +뜩 DdEuG +뜪 DdEuGg +뜫 DdEuGs +뜬 DdEuN +뜭 DdEuNj +뜮 DdEuNh +뜯 DdEuD +뜰 DdEuL +뜱 DdEuLg +뜲 DdEuLm +뜳 DdEuLb +뜴 DdEuLs +뜵 DdEuLt +뜶 DdEuLp +뜷 DdEuLh +뜸 DdEuM +뜹 DdEuB +뜺 DdEuBs +뜻 DdEuS +뜼 DdEuSs +뜽 DdEuNg +뜾 DdEuJ +뜿 DdEuC +띀 DdEuK +띁 DdEuT +띂 DdEuP +띃 DdEuH +띄 DdUi +띅 DdUiG +띆 DdUiGg +띇 DdUiGs +띈 DdUiN +띉 DdUiNj +띊 DdUiNh +띋 DdUiD +띌 DdUiL +띍 DdUiLg +띎 DdUiLm +띏 DdUiLb +띐 DdUiLs +띑 DdUiLt +띒 DdUiLp +띓 DdUiLh +띔 DdUiM +띕 DdUiB +띖 DdUiBs +띗 DdUiS +띘 DdUiSs +띙 DdUiNg +띚 DdUiJ +띛 DdUiC +띜 DdUiK +띝 DdUiT +띞 DdUiP +띟 DdUiH +띠 DdI +띡 DdIG +띢 DdIGg +띣 DdIGs +띤 DdIN +띥 DdINj +띦 DdINh +띧 DdID +띨 DdIL +띩 DdILg +띪 DdILm +띫 DdILb +띬 DdILs +띭 DdILt +띮 DdILp +띯 DdILh +띰 DdIM +띱 DdIB +띲 DdIBs +띳 DdIS +띴 DdISs +띵 DdINg +띶 DdIJ +띷 DdIC +띸 DdIK +띹 DdIT +띺 DdIP +띻 DdIH +라 LA +락 LAG +띾 LAGg +띿 LAGs +란 LAN +랁 LANj +랂 LANh +랃 LAD +랄 LAL +랅 LALg +랆 LALm +랇 LALb +랈 LALs +랉 LALt +랊 LALp +랋 LALh +람 LAM +랍 LAB +랎 LABs +랏 LAS +랐 LASs +랑 LANg +랒 LAJ +랓 LAC +랔 LAK +랕 LAT +랖 LAP +랗 LAH +래 LAe +랙 LAeG +랚 LAeGg +랛 LAeGs +랜 LAeN +랝 LAeNj +랞 LAeNh +랟 LAeD +랠 LAeL +랡 LAeLg +랢 LAeLm +랣 LAeLb +랤 LAeLs +랥 LAeLt +랦 LAeLp +랧 LAeLh +램 LAeM +랩 LAeB +랪 LAeBs +랫 LAeS +랬 LAeSs +랭 LAeNg +랮 LAeJ +랯 LAeC +랰 LAeK +랱 LAeT +랲 LAeP +랳 LAeH +랴 LYa +략 LYaG +랶 LYaGg +랷 LYaGs +랸 LYaN +랹 LYaNj +랺 LYaNh +랻 LYaD +랼 LYaL +랽 LYaLg +랾 LYaLm +랿 LYaLb +럀 LYaLs +럁 LYaLt +럂 LYaLp +럃 LYaLh +럄 LYaM +럅 LYaB +럆 LYaBs +럇 LYaS +럈 LYaSs +량 LYaNg +럊 LYaJ +럋 LYaC +럌 LYaK +럍 LYaT +럎 LYaP +럏 LYaH +럐 LYae +럑 LYaeG +럒 LYaeGg +럓 LYaeGs +럔 LYaeN +럕 LYaeNj +럖 LYaeNh +럗 LYaeD +럘 LYaeL +럙 LYaeLg +럚 LYaeLm +럛 LYaeLb +럜 LYaeLs +럝 LYaeLt +럞 LYaeLp +럟 LYaeLh +럠 LYaeM +럡 LYaeB +럢 LYaeBs +럣 LYaeS +럤 LYaeSs +럥 LYaeNg +럦 LYaeJ +럧 LYaeC +럨 LYaeK +럩 LYaeT +럪 LYaeP +럫 LYaeH +러 LEo +럭 LEoG +럮 LEoGg +럯 LEoGs +런 LEoN +럱 LEoNj +럲 LEoNh +럳 LEoD +럴 LEoL +럵 LEoLg +럶 LEoLm +럷 LEoLb +럸 LEoLs +럹 LEoLt +럺 LEoLp +럻 LEoLh +럼 LEoM +럽 LEoB +럾 LEoBs +럿 LEoS +렀 LEoSs +렁 LEoNg +렂 LEoJ +렃 LEoC +렄 LEoK +렅 LEoT +렆 LEoP +렇 LEoH +레 LE +렉 LEG +렊 LEGg +렋 LEGs +렌 LEN +렍 LENj +렎 LENh +렏 LED +렐 LEL +렑 LELg +렒 LELm +렓 LELb +렔 LELs +렕 LELt +렖 LELp +렗 LELh +렘 LEM +렙 LEB +렚 LEBs +렛 LES +렜 LESs +렝 LENg +렞 LEJ +렟 LEC +렠 LEK +렡 LET +렢 LEP +렣 LEH +려 LYeo +력 LYeoG +렦 LYeoGg +렧 LYeoGs +련 LYeoN +렩 LYeoNj +렪 LYeoNh +렫 LYeoD +렬 LYeoL +렭 LYeoLg +렮 LYeoLm +렯 LYeoLb +렰 LYeoLs +렱 LYeoLt +렲 LYeoLp +렳 LYeoLh +렴 LYeoM +렵 LYeoB +렶 LYeoBs +렷 LYeoS +렸 LYeoSs +령 LYeoNg +렺 LYeoJ +렻 LYeoC +렼 LYeoK +렽 LYeoT +렾 LYeoP +렿 LYeoH +례 LYe +롁 LYeG +롂 LYeGg +롃 LYeGs +롄 LYeN +롅 LYeNj +롆 LYeNh +롇 LYeD +롈 LYeL +롉 LYeLg +롊 LYeLm +롋 LYeLb +롌 LYeLs +롍 LYeLt +롎 LYeLp +롏 LYeLh +롐 LYeM +롑 LYeB +롒 LYeBs +롓 LYeS +롔 LYeSs +롕 LYeNg +롖 LYeJ +롗 LYeC +롘 LYeK +롙 LYeT +롚 LYeP +롛 LYeH +로 LO +록 LOG +롞 LOGg +롟 LOGs +론 LON +롡 LONj +롢 LONh +롣 LOD +롤 LOL +롥 LOLg +롦 LOLm +롧 LOLb +롨 LOLs +롩 LOLt +롪 LOLp +롫 LOLh +롬 LOM +롭 LOB +롮 LOBs +롯 LOS +롰 LOSs +롱 LONg +롲 LOJ +롳 LOC +롴 LOK +롵 LOT +롶 LOP +롷 LOH +롸 LWa +롹 LWaG +롺 LWaGg +롻 LWaGs +롼 LWaN +롽 LWaNj +롾 LWaNh +롿 LWaD +뢀 LWaL +뢁 LWaLg +뢂 LWaLm +뢃 LWaLb +뢄 LWaLs +뢅 LWaLt +뢆 LWaLp +뢇 LWaLh +뢈 LWaM +뢉 LWaB +뢊 LWaBs +뢋 LWaS +뢌 LWaSs +뢍 LWaNg +뢎 LWaJ +뢏 LWaC +뢐 LWaK +뢑 LWaT +뢒 LWaP +뢓 LWaH +뢔 LWae +뢕 LWaeG +뢖 LWaeGg +뢗 LWaeGs +뢘 LWaeN +뢙 LWaeNj +뢚 LWaeNh +뢛 LWaeD +뢜 LWaeL +뢝 LWaeLg +뢞 LWaeLm +뢟 LWaeLb +뢠 LWaeLs +뢡 LWaeLt +뢢 LWaeLp +뢣 LWaeLh +뢤 LWaeM +뢥 LWaeB +뢦 LWaeBs +뢧 LWaeS +뢨 LWaeSs +뢩 LWaeNg +뢪 LWaeJ +뢫 LWaeC +뢬 LWaeK +뢭 LWaeT +뢮 LWaeP +뢯 LWaeH +뢰 LOe +뢱 LOeG +뢲 LOeGg +뢳 LOeGs +뢴 LOeN +뢵 LOeNj +뢶 LOeNh +뢷 LOeD +뢸 LOeL +뢹 LOeLg +뢺 LOeLm +뢻 LOeLb +뢼 LOeLs +뢽 LOeLt +뢾 LOeLp +뢿 LOeLh +룀 LOeM +룁 LOeB +룂 LOeBs +룃 LOeS +룄 LOeSs +룅 LOeNg +룆 LOeJ +룇 LOeC +룈 LOeK +룉 LOeT +룊 LOeP +룋 LOeH +료 LYo +룍 LYoG +룎 LYoGg +룏 LYoGs +룐 LYoN +룑 LYoNj +룒 LYoNh +룓 LYoD +룔 LYoL +룕 LYoLg +룖 LYoLm +룗 LYoLb +룘 LYoLs +룙 LYoLt +룚 LYoLp +룛 LYoLh +룜 LYoM +룝 LYoB +룞 LYoBs +룟 LYoS +룠 LYoSs +룡 LYoNg +룢 LYoJ +룣 LYoC +룤 LYoK +룥 LYoT +룦 LYoP +룧 LYoH +루 LU +룩 LUG +룪 LUGg +룫 LUGs +룬 LUN +룭 LUNj +룮 LUNh +룯 LUD +룰 LUL +룱 LULg +룲 LULm +룳 LULb +룴 LULs +룵 LULt +룶 LULp +룷 LULh +룸 LUM +룹 LUB +룺 LUBs +룻 LUS +룼 LUSs +룽 LUNg +룾 LUJ +룿 LUC +뤀 LUK +뤁 LUT +뤂 LUP +뤃 LUH +뤄 LWo +뤅 LWoG +뤆 LWoGg +뤇 LWoGs +뤈 LWoN +뤉 LWoNj +뤊 LWoNh +뤋 LWoD +뤌 LWoL +뤍 LWoLg +뤎 LWoLm +뤏 LWoLb +뤐 LWoLs +뤑 LWoLt +뤒 LWoLp +뤓 LWoLh +뤔 LWoM +뤕 LWoB +뤖 LWoBs +뤗 LWoS +뤘 LWoSs +뤙 LWoNg +뤚 LWoJ +뤛 LWoC +뤜 LWoK +뤝 LWoT +뤞 LWoP +뤟 LWoH +뤠 LWe +뤡 LWeG +뤢 LWeGg +뤣 LWeGs +뤤 LWeN +뤥 LWeNj +뤦 LWeNh +뤧 LWeD +뤨 LWeL +뤩 LWeLg +뤪 LWeLm +뤫 LWeLb +뤬 LWeLs +뤭 LWeLt +뤮 LWeLp +뤯 LWeLh +뤰 LWeM +뤱 LWeB +뤲 LWeBs +뤳 LWeS +뤴 LWeSs +뤵 LWeNg +뤶 LWeJ +뤷 LWeC +뤸 LWeK +뤹 LWeT +뤺 LWeP +뤻 LWeH +뤼 LWi +뤽 LWiG +뤾 LWiGg +뤿 LWiGs +륀 LWiN +륁 LWiNj +륂 LWiNh +륃 LWiD +륄 LWiL +륅 LWiLg +륆 LWiLm +륇 LWiLb +륈 LWiLs +륉 LWiLt +륊 LWiLp +륋 LWiLh +륌 LWiM +륍 LWiB +륎 LWiBs +륏 LWiS +륐 LWiSs +륑 LWiNg +륒 LWiJ +륓 LWiC +륔 LWiK +륕 LWiT +륖 LWiP +륗 LWiH +류 LYu +륙 LYuG +륚 LYuGg +륛 LYuGs +륜 LYuN +륝 LYuNj +륞 LYuNh +륟 LYuD +률 LYuL +륡 LYuLg +륢 LYuLm +륣 LYuLb +륤 LYuLs +륥 LYuLt +륦 LYuLp +륧 LYuLh +륨 LYuM +륩 LYuB +륪 LYuBs +륫 LYuS +륬 LYuSs +륭 LYuNg +륮 LYuJ +륯 LYuC +륰 LYuK +륱 LYuT +륲 LYuP +륳 LYuH +르 LEu +륵 LEuG +륶 LEuGg +륷 LEuGs +른 LEuN +륹 LEuNj +륺 LEuNh +륻 LEuD +를 LEuL +륽 LEuLg +륾 LEuLm +륿 LEuLb +릀 LEuLs +릁 LEuLt +릂 LEuLp +릃 LEuLh +름 LEuM +릅 LEuB +릆 LEuBs +릇 LEuS +릈 LEuSs +릉 LEuNg +릊 LEuJ +릋 LEuC +릌 LEuK +릍 LEuT +릎 LEuP +릏 LEuH +릐 LUi +릑 LUiG +릒 LUiGg +릓 LUiGs +릔 LUiN +릕 LUiNj +릖 LUiNh +릗 LUiD +릘 LUiL +릙 LUiLg +릚 LUiLm +릛 LUiLb +릜 LUiLs +릝 LUiLt +릞 LUiLp +릟 LUiLh +릠 LUiM +릡 LUiB +릢 LUiBs +릣 LUiS +릤 LUiSs +릥 LUiNg +릦 LUiJ +릧 LUiC +릨 LUiK +릩 LUiT +릪 LUiP +릫 LUiH +리 LI +릭 LIG +릮 LIGg +릯 LIGs +린 LIN +릱 LINj +릲 LINh +릳 LID +릴 LIL +릵 LILg +릶 LILm +릷 LILb +릸 LILs +릹 LILt +릺 LILp +릻 LILh +림 LIM +립 LIB +릾 LIBs +릿 LIS +맀 LISs +링 LINg +맂 LIJ +맃 LIC +맄 LIK +맅 LIT +맆 LIP +맇 LIH +마 MA +막 MAG +맊 MAGg +맋 MAGs +만 MAN +맍 MANj +많 MANh +맏 MAD +말 MAL +맑 MALg +맒 MALm +맓 MALb +맔 MALs +맕 MALt +맖 MALp +맗 MALh +맘 MAM +맙 MAB +맚 MABs +맛 MAS +맜 MASs +망 MANg +맞 MAJ +맟 MAC +맠 MAK +맡 MAT +맢 MAP +맣 MAH +매 MAe +맥 MAeG +맦 MAeGg +맧 MAeGs +맨 MAeN +맩 MAeNj +맪 MAeNh +맫 MAeD +맬 MAeL +맭 MAeLg +맮 MAeLm +맯 MAeLb +맰 MAeLs +맱 MAeLt +맲 MAeLp +맳 MAeLh +맴 MAeM +맵 MAeB +맶 MAeBs +맷 MAeS +맸 MAeSs +맹 MAeNg +맺 MAeJ +맻 MAeC +맼 MAeK +맽 MAeT +맾 MAeP +맿 MAeH +먀 MYa +먁 MYaG +먂 MYaGg +먃 MYaGs +먄 MYaN +먅 MYaNj +먆 MYaNh +먇 MYaD +먈 MYaL +먉 MYaLg +먊 MYaLm +먋 MYaLb +먌 MYaLs +먍 MYaLt +먎 MYaLp +먏 MYaLh +먐 MYaM +먑 MYaB +먒 MYaBs +먓 MYaS +먔 MYaSs +먕 MYaNg +먖 MYaJ +먗 MYaC +먘 MYaK +먙 MYaT +먚 MYaP +먛 MYaH +먜 MYae +먝 MYaeG +먞 MYaeGg +먟 MYaeGs +먠 MYaeN +먡 MYaeNj +먢 MYaeNh +먣 MYaeD +먤 MYaeL +먥 MYaeLg +먦 MYaeLm +먧 MYaeLb +먨 MYaeLs +먩 MYaeLt +먪 MYaeLp +먫 MYaeLh +먬 MYaeM +먭 MYaeB +먮 MYaeBs +먯 MYaeS +먰 MYaeSs +먱 MYaeNg +먲 MYaeJ +먳 MYaeC +먴 MYaeK +먵 MYaeT +먶 MYaeP +먷 MYaeH +머 MEo +먹 MEoG +먺 MEoGg +먻 MEoGs +먼 MEoN +먽 MEoNj +먾 MEoNh +먿 MEoD +멀 MEoL +멁 MEoLg +멂 MEoLm +멃 MEoLb +멄 MEoLs +멅 MEoLt +멆 MEoLp +멇 MEoLh +멈 MEoM +멉 MEoB +멊 MEoBs +멋 MEoS +멌 MEoSs +멍 MEoNg +멎 MEoJ +멏 MEoC +멐 MEoK +멑 MEoT +멒 MEoP +멓 MEoH +메 ME +멕 MEG +멖 MEGg +멗 MEGs +멘 MEN +멙 MENj +멚 MENh +멛 MED +멜 MEL +멝 MELg +멞 MELm +멟 MELb +멠 MELs +멡 MELt +멢 MELp +멣 MELh +멤 MEM +멥 MEB +멦 MEBs +멧 MES +멨 MESs +멩 MENg +멪 MEJ +멫 MEC +멬 MEK +멭 MET +멮 MEP +멯 MEH +며 MYeo +멱 MYeoG +멲 MYeoGg +멳 MYeoGs +면 MYeoN +멵 MYeoNj +멶 MYeoNh +멷 MYeoD +멸 MYeoL +멹 MYeoLg +멺 MYeoLm +멻 MYeoLb +멼 MYeoLs +멽 MYeoLt +멾 MYeoLp +멿 MYeoLh +몀 MYeoM +몁 MYeoB +몂 MYeoBs +몃 MYeoS +몄 MYeoSs +명 MYeoNg +몆 MYeoJ +몇 MYeoC +몈 MYeoK +몉 MYeoT +몊 MYeoP +몋 MYeoH +몌 MYe +몍 MYeG +몎 MYeGg +몏 MYeGs +몐 MYeN +몑 MYeNj +몒 MYeNh +몓 MYeD +몔 MYeL +몕 MYeLg +몖 MYeLm +몗 MYeLb +몘 MYeLs +몙 MYeLt +몚 MYeLp +몛 MYeLh +몜 MYeM +몝 MYeB +몞 MYeBs +몟 MYeS +몠 MYeSs +몡 MYeNg +몢 MYeJ +몣 MYeC +몤 MYeK +몥 MYeT +몦 MYeP +몧 MYeH +모 MO +목 MOG +몪 MOGg +몫 MOGs +몬 MON +몭 MONj +몮 MONh +몯 MOD +몰 MOL +몱 MOLg +몲 MOLm +몳 MOLb +몴 MOLs +몵 MOLt +몶 MOLp +몷 MOLh +몸 MOM +몹 MOB +몺 MOBs +못 MOS +몼 MOSs +몽 MONg +몾 MOJ +몿 MOC +뫀 MOK +뫁 MOT +뫂 MOP +뫃 MOH +뫄 MWa +뫅 MWaG +뫆 MWaGg +뫇 MWaGs +뫈 MWaN +뫉 MWaNj +뫊 MWaNh +뫋 MWaD +뫌 MWaL +뫍 MWaLg +뫎 MWaLm +뫏 MWaLb +뫐 MWaLs +뫑 MWaLt +뫒 MWaLp +뫓 MWaLh +뫔 MWaM +뫕 MWaB +뫖 MWaBs +뫗 MWaS +뫘 MWaSs +뫙 MWaNg +뫚 MWaJ +뫛 MWaC +뫜 MWaK +뫝 MWaT +뫞 MWaP +뫟 MWaH +뫠 MWae +뫡 MWaeG +뫢 MWaeGg +뫣 MWaeGs +뫤 MWaeN +뫥 MWaeNj +뫦 MWaeNh +뫧 MWaeD +뫨 MWaeL +뫩 MWaeLg +뫪 MWaeLm +뫫 MWaeLb +뫬 MWaeLs +뫭 MWaeLt +뫮 MWaeLp +뫯 MWaeLh +뫰 MWaeM +뫱 MWaeB +뫲 MWaeBs +뫳 MWaeS +뫴 MWaeSs +뫵 MWaeNg +뫶 MWaeJ +뫷 MWaeC +뫸 MWaeK +뫹 MWaeT +뫺 MWaeP +뫻 MWaeH +뫼 MOe +뫽 MOeG +뫾 MOeGg +뫿 MOeGs +묀 MOeN +묁 MOeNj +묂 MOeNh +묃 MOeD +묄 MOeL +묅 MOeLg +묆 MOeLm +묇 MOeLb +묈 MOeLs +묉 MOeLt +묊 MOeLp +묋 MOeLh +묌 MOeM +묍 MOeB +묎 MOeBs +묏 MOeS +묐 MOeSs +묑 MOeNg +묒 MOeJ +묓 MOeC +묔 MOeK +묕 MOeT +묖 MOeP +묗 MOeH +묘 MYo +묙 MYoG +묚 MYoGg +묛 MYoGs +묜 MYoN +묝 MYoNj +묞 MYoNh +묟 MYoD +묠 MYoL +묡 MYoLg +묢 MYoLm +묣 MYoLb +묤 MYoLs +묥 MYoLt +묦 MYoLp +묧 MYoLh +묨 MYoM +묩 MYoB +묪 MYoBs +묫 MYoS +묬 MYoSs +묭 MYoNg +묮 MYoJ +묯 MYoC +묰 MYoK +묱 MYoT +묲 MYoP +묳 MYoH +무 MU +묵 MUG +묶 MUGg +묷 MUGs +문 MUN +묹 MUNj +묺 MUNh +묻 MUD +물 MUL +묽 MULg +묾 MULm +묿 MULb +뭀 MULs +뭁 MULt +뭂 MULp +뭃 MULh +뭄 MUM +뭅 MUB +뭆 MUBs +뭇 MUS +뭈 MUSs +뭉 MUNg +뭊 MUJ +뭋 MUC +뭌 MUK +뭍 MUT +뭎 MUP +뭏 MUH +뭐 MWo +뭑 MWoG +뭒 MWoGg +뭓 MWoGs +뭔 MWoN +뭕 MWoNj +뭖 MWoNh +뭗 MWoD +뭘 MWoL +뭙 MWoLg +뭚 MWoLm +뭛 MWoLb +뭜 MWoLs +뭝 MWoLt +뭞 MWoLp +뭟 MWoLh +뭠 MWoM +뭡 MWoB +뭢 MWoBs +뭣 MWoS +뭤 MWoSs +뭥 MWoNg +뭦 MWoJ +뭧 MWoC +뭨 MWoK +뭩 MWoT +뭪 MWoP +뭫 MWoH +뭬 MWe +뭭 MWeG +뭮 MWeGg +뭯 MWeGs +뭰 MWeN +뭱 MWeNj +뭲 MWeNh +뭳 MWeD +뭴 MWeL +뭵 MWeLg +뭶 MWeLm +뭷 MWeLb +뭸 MWeLs +뭹 MWeLt +뭺 MWeLp +뭻 MWeLh +뭼 MWeM +뭽 MWeB +뭾 MWeBs +뭿 MWeS +뮀 MWeSs +뮁 MWeNg +뮂 MWeJ +뮃 MWeC +뮄 MWeK +뮅 MWeT +뮆 MWeP +뮇 MWeH +뮈 MWi +뮉 MWiG +뮊 MWiGg +뮋 MWiGs +뮌 MWiN +뮍 MWiNj +뮎 MWiNh +뮏 MWiD +뮐 MWiL +뮑 MWiLg +뮒 MWiLm +뮓 MWiLb +뮔 MWiLs +뮕 MWiLt +뮖 MWiLp +뮗 MWiLh +뮘 MWiM +뮙 MWiB +뮚 MWiBs +뮛 MWiS +뮜 MWiSs +뮝 MWiNg +뮞 MWiJ +뮟 MWiC +뮠 MWiK +뮡 MWiT +뮢 MWiP +뮣 MWiH +뮤 MYu +뮥 MYuG +뮦 MYuGg +뮧 MYuGs +뮨 MYuN +뮩 MYuNj +뮪 MYuNh +뮫 MYuD +뮬 MYuL +뮭 MYuLg +뮮 MYuLm +뮯 MYuLb +뮰 MYuLs +뮱 MYuLt +뮲 MYuLp +뮳 MYuLh +뮴 MYuM +뮵 MYuB +뮶 MYuBs +뮷 MYuS +뮸 MYuSs +뮹 MYuNg +뮺 MYuJ +뮻 MYuC +뮼 MYuK +뮽 MYuT +뮾 MYuP +뮿 MYuH +므 MEu +믁 MEuG +믂 MEuGg +믃 MEuGs +믄 MEuN +믅 MEuNj +믆 MEuNh +믇 MEuD +믈 MEuL +믉 MEuLg +믊 MEuLm +믋 MEuLb +믌 MEuLs +믍 MEuLt +믎 MEuLp +믏 MEuLh +믐 MEuM +믑 MEuB +믒 MEuBs +믓 MEuS +믔 MEuSs +믕 MEuNg +믖 MEuJ +믗 MEuC +믘 MEuK +믙 MEuT +믚 MEuP +믛 MEuH +믜 MUi +믝 MUiG +믞 MUiGg +믟 MUiGs +믠 MUiN +믡 MUiNj +믢 MUiNh +믣 MUiD +믤 MUiL +믥 MUiLg +믦 MUiLm +믧 MUiLb +믨 MUiLs +믩 MUiLt +믪 MUiLp +믫 MUiLh +믬 MUiM +믭 MUiB +믮 MUiBs +믯 MUiS +믰 MUiSs +믱 MUiNg +믲 MUiJ +믳 MUiC +믴 MUiK +믵 MUiT +믶 MUiP +믷 MUiH +미 MI +믹 MIG +믺 MIGg +믻 MIGs +민 MIN +믽 MINj +믾 MINh +믿 MID +밀 MIL +밁 MILg +밂 MILm +밃 MILb +밄 MILs +밅 MILt +밆 MILp +밇 MILh +밈 MIM +밉 MIB +밊 MIBs +밋 MIS +밌 MISs +밍 MINg +밎 MIJ +및 MIC +밐 MIK +밑 MIT +밒 MIP +밓 MIH +바 BA +박 BAG +밖 BAGg +밗 BAGs +반 BAN +밙 BANj +밚 BANh +받 BAD +발 BAL +밝 BALg +밞 BALm +밟 BALb +밠 BALs +밡 BALt +밢 BALp +밣 BALh +밤 BAM +밥 BAB +밦 BABs +밧 BAS +밨 BASs +방 BANg +밪 BAJ +밫 BAC +밬 BAK +밭 BAT +밮 BAP +밯 BAH +배 BAe +백 BAeG +밲 BAeGg +밳 BAeGs +밴 BAeN +밵 BAeNj +밶 BAeNh +밷 BAeD +밸 BAeL +밹 BAeLg +밺 BAeLm +밻 BAeLb +밼 BAeLs +밽 BAeLt +밾 BAeLp +밿 BAeLh +뱀 BAeM +뱁 BAeB +뱂 BAeBs +뱃 BAeS +뱄 BAeSs +뱅 BAeNg +뱆 BAeJ +뱇 BAeC +뱈 BAeK +뱉 BAeT +뱊 BAeP +뱋 BAeH +뱌 BYa +뱍 BYaG +뱎 BYaGg +뱏 BYaGs +뱐 BYaN +뱑 BYaNj +뱒 BYaNh +뱓 BYaD +뱔 BYaL +뱕 BYaLg +뱖 BYaLm +뱗 BYaLb +뱘 BYaLs +뱙 BYaLt +뱚 BYaLp +뱛 BYaLh +뱜 BYaM +뱝 BYaB +뱞 BYaBs +뱟 BYaS +뱠 BYaSs +뱡 BYaNg +뱢 BYaJ +뱣 BYaC +뱤 BYaK +뱥 BYaT +뱦 BYaP +뱧 BYaH +뱨 BYae +뱩 BYaeG +뱪 BYaeGg +뱫 BYaeGs +뱬 BYaeN +뱭 BYaeNj +뱮 BYaeNh +뱯 BYaeD +뱰 BYaeL +뱱 BYaeLg +뱲 BYaeLm +뱳 BYaeLb +뱴 BYaeLs +뱵 BYaeLt +뱶 BYaeLp +뱷 BYaeLh +뱸 BYaeM +뱹 BYaeB +뱺 BYaeBs +뱻 BYaeS +뱼 BYaeSs +뱽 BYaeNg +뱾 BYaeJ +뱿 BYaeC +벀 BYaeK +벁 BYaeT +벂 BYaeP +벃 BYaeH +버 BEo +벅 BEoG +벆 BEoGg +벇 BEoGs +번 BEoN +벉 BEoNj +벊 BEoNh +벋 BEoD +벌 BEoL +벍 BEoLg +벎 BEoLm +벏 BEoLb +벐 BEoLs +벑 BEoLt +벒 BEoLp +벓 BEoLh +범 BEoM +법 BEoB +벖 BEoBs +벗 BEoS +벘 BEoSs +벙 BEoNg +벚 BEoJ +벛 BEoC +벜 BEoK +벝 BEoT +벞 BEoP +벟 BEoH +베 BE +벡 BEG +벢 BEGg +벣 BEGs +벤 BEN +벥 BENj +벦 BENh +벧 BED +벨 BEL +벩 BELg +벪 BELm +벫 BELb +벬 BELs +벭 BELt +벮 BELp +벯 BELh +벰 BEM +벱 BEB +벲 BEBs +벳 BES +벴 BESs +벵 BENg +벶 BEJ +벷 BEC +벸 BEK +벹 BET +벺 BEP +벻 BEH +벼 BYeo +벽 BYeoG +벾 BYeoGg +벿 BYeoGs +변 BYeoN +볁 BYeoNj +볂 BYeoNh +볃 BYeoD +별 BYeoL +볅 BYeoLg +볆 BYeoLm +볇 BYeoLb +볈 BYeoLs +볉 BYeoLt +볊 BYeoLp +볋 BYeoLh +볌 BYeoM +볍 BYeoB +볎 BYeoBs +볏 BYeoS +볐 BYeoSs +병 BYeoNg +볒 BYeoJ +볓 BYeoC +볔 BYeoK +볕 BYeoT +볖 BYeoP +볗 BYeoH +볘 BYe +볙 BYeG +볚 BYeGg +볛 BYeGs +볜 BYeN +볝 BYeNj +볞 BYeNh +볟 BYeD +볠 BYeL +볡 BYeLg +볢 BYeLm +볣 BYeLb +볤 BYeLs +볥 BYeLt +볦 BYeLp +볧 BYeLh +볨 BYeM +볩 BYeB +볪 BYeBs +볫 BYeS +볬 BYeSs +볭 BYeNg +볮 BYeJ +볯 BYeC +볰 BYeK +볱 BYeT +볲 BYeP +볳 BYeH +보 BO +복 BOG +볶 BOGg +볷 BOGs +본 BON +볹 BONj +볺 BONh +볻 BOD +볼 BOL +볽 BOLg +볾 BOLm +볿 BOLb +봀 BOLs +봁 BOLt +봂 BOLp +봃 BOLh +봄 BOM +봅 BOB +봆 BOBs +봇 BOS +봈 BOSs +봉 BONg +봊 BOJ +봋 BOC +봌 BOK +봍 BOT +봎 BOP +봏 BOH +봐 BWa +봑 BWaG +봒 BWaGg +봓 BWaGs +봔 BWaN +봕 BWaNj +봖 BWaNh +봗 BWaD +봘 BWaL +봙 BWaLg +봚 BWaLm +봛 BWaLb +봜 BWaLs +봝 BWaLt +봞 BWaLp +봟 BWaLh +봠 BWaM +봡 BWaB +봢 BWaBs +봣 BWaS +봤 BWaSs +봥 BWaNg +봦 BWaJ +봧 BWaC +봨 BWaK +봩 BWaT +봪 BWaP +봫 BWaH +봬 BWae +봭 BWaeG +봮 BWaeGg +봯 BWaeGs +봰 BWaeN +봱 BWaeNj +봲 BWaeNh +봳 BWaeD +봴 BWaeL +봵 BWaeLg +봶 BWaeLm +봷 BWaeLb +봸 BWaeLs +봹 BWaeLt +봺 BWaeLp +봻 BWaeLh +봼 BWaeM +봽 BWaeB +봾 BWaeBs +봿 BWaeS +뵀 BWaeSs +뵁 BWaeNg +뵂 BWaeJ +뵃 BWaeC +뵄 BWaeK +뵅 BWaeT +뵆 BWaeP +뵇 BWaeH +뵈 BOe +뵉 BOeG +뵊 BOeGg +뵋 BOeGs +뵌 BOeN +뵍 BOeNj +뵎 BOeNh +뵏 BOeD +뵐 BOeL +뵑 BOeLg +뵒 BOeLm +뵓 BOeLb +뵔 BOeLs +뵕 BOeLt +뵖 BOeLp +뵗 BOeLh +뵘 BOeM +뵙 BOeB +뵚 BOeBs +뵛 BOeS +뵜 BOeSs +뵝 BOeNg +뵞 BOeJ +뵟 BOeC +뵠 BOeK +뵡 BOeT +뵢 BOeP +뵣 BOeH +뵤 BYo +뵥 BYoG +뵦 BYoGg +뵧 BYoGs +뵨 BYoN +뵩 BYoNj +뵪 BYoNh +뵫 BYoD +뵬 BYoL +뵭 BYoLg +뵮 BYoLm +뵯 BYoLb +뵰 BYoLs +뵱 BYoLt +뵲 BYoLp +뵳 BYoLh +뵴 BYoM +뵵 BYoB +뵶 BYoBs +뵷 BYoS +뵸 BYoSs +뵹 BYoNg +뵺 BYoJ +뵻 BYoC +뵼 BYoK +뵽 BYoT +뵾 BYoP +뵿 BYoH +부 BU +북 BUG +붂 BUGg +붃 BUGs +분 BUN +붅 BUNj +붆 BUNh +붇 BUD +불 BUL +붉 BULg +붊 BULm +붋 BULb +붌 BULs +붍 BULt +붎 BULp +붏 BULh +붐 BUM +붑 BUB +붒 BUBs +붓 BUS +붔 BUSs +붕 BUNg +붖 BUJ +붗 BUC +붘 BUK +붙 BUT +붚 BUP +붛 BUH +붜 BWo +붝 BWoG +붞 BWoGg +붟 BWoGs +붠 BWoN +붡 BWoNj +붢 BWoNh +붣 BWoD +붤 BWoL +붥 BWoLg +붦 BWoLm +붧 BWoLb +붨 BWoLs +붩 BWoLt +붪 BWoLp +붫 BWoLh +붬 BWoM +붭 BWoB +붮 BWoBs +붯 BWoS +붰 BWoSs +붱 BWoNg +붲 BWoJ +붳 BWoC +붴 BWoK +붵 BWoT +붶 BWoP +붷 BWoH +붸 BWe +붹 BWeG +붺 BWeGg +붻 BWeGs +붼 BWeN +붽 BWeNj +붾 BWeNh +붿 BWeD +뷀 BWeL +뷁 BWeLg +뷂 BWeLm +뷃 BWeLb +뷄 BWeLs +뷅 BWeLt +뷆 BWeLp +뷇 BWeLh +뷈 BWeM +뷉 BWeB +뷊 BWeBs +뷋 BWeS +뷌 BWeSs +뷍 BWeNg +뷎 BWeJ +뷏 BWeC +뷐 BWeK +뷑 BWeT +뷒 BWeP +뷓 BWeH +뷔 BWi +뷕 BWiG +뷖 BWiGg +뷗 BWiGs +뷘 BWiN +뷙 BWiNj +뷚 BWiNh +뷛 BWiD +뷜 BWiL +뷝 BWiLg +뷞 BWiLm +뷟 BWiLb +뷠 BWiLs +뷡 BWiLt +뷢 BWiLp +뷣 BWiLh +뷤 BWiM +뷥 BWiB +뷦 BWiBs +뷧 BWiS +뷨 BWiSs +뷩 BWiNg +뷪 BWiJ +뷫 BWiC +뷬 BWiK +뷭 BWiT +뷮 BWiP +뷯 BWiH +뷰 BYu +뷱 BYuG +뷲 BYuGg +뷳 BYuGs +뷴 BYuN +뷵 BYuNj +뷶 BYuNh +뷷 BYuD +뷸 BYuL +뷹 BYuLg +뷺 BYuLm +뷻 BYuLb +뷼 BYuLs +뷽 BYuLt +뷾 BYuLp +뷿 BYuLh +븀 BYuM +븁 BYuB +븂 BYuBs +븃 BYuS +븄 BYuSs +븅 BYuNg +븆 BYuJ +븇 BYuC +븈 BYuK +븉 BYuT +븊 BYuP +븋 BYuH +브 BEu +븍 BEuG +븎 BEuGg +븏 BEuGs +븐 BEuN +븑 BEuNj +븒 BEuNh +븓 BEuD +블 BEuL +븕 BEuLg +븖 BEuLm +븗 BEuLb +븘 BEuLs +븙 BEuLt +븚 BEuLp +븛 BEuLh +븜 BEuM +븝 BEuB +븞 BEuBs +븟 BEuS +븠 BEuSs +븡 BEuNg +븢 BEuJ +븣 BEuC +븤 BEuK +븥 BEuT +븦 BEuP +븧 BEuH +븨 BUi +븩 BUiG +븪 BUiGg +븫 BUiGs +븬 BUiN +븭 BUiNj +븮 BUiNh +븯 BUiD +븰 BUiL +븱 BUiLg +븲 BUiLm +븳 BUiLb +븴 BUiLs +븵 BUiLt +븶 BUiLp +븷 BUiLh +븸 BUiM +븹 BUiB +븺 BUiBs +븻 BUiS +븼 BUiSs +븽 BUiNg +븾 BUiJ +븿 BUiC +빀 BUiK +빁 BUiT +빂 BUiP +빃 BUiH +비 BI +빅 BIG +빆 BIGg +빇 BIGs +빈 BIN +빉 BINj +빊 BINh +빋 BID +빌 BIL +빍 BILg +빎 BILm +빏 BILb +빐 BILs +빑 BILt +빒 BILp +빓 BILh +빔 BIM +빕 BIB +빖 BIBs +빗 BIS +빘 BISs +빙 BINg +빚 BIJ +빛 BIC +빜 BIK +빝 BIT +빞 BIP +빟 BIH +빠 BbA +빡 BbAG +빢 BbAGg +빣 BbAGs +빤 BbAN +빥 BbANj +빦 BbANh +빧 BbAD +빨 BbAL +빩 BbALg +빪 BbALm +빫 BbALb +빬 BbALs +빭 BbALt +빮 BbALp +빯 BbALh +빰 BbAM +빱 BbAB +빲 BbABs +빳 BbAS +빴 BbASs +빵 BbANg +빶 BbAJ +빷 BbAC +빸 BbAK +빹 BbAT +빺 BbAP +빻 BbAH +빼 BbAe +빽 BbAeG +빾 BbAeGg +빿 BbAeGs +뺀 BbAeN +뺁 BbAeNj +뺂 BbAeNh +뺃 BbAeD +뺄 BbAeL +뺅 BbAeLg +뺆 BbAeLm +뺇 BbAeLb +뺈 BbAeLs +뺉 BbAeLt +뺊 BbAeLp +뺋 BbAeLh +뺌 BbAeM +뺍 BbAeB +뺎 BbAeBs +뺏 BbAeS +뺐 BbAeSs +뺑 BbAeNg +뺒 BbAeJ +뺓 BbAeC +뺔 BbAeK +뺕 BbAeT +뺖 BbAeP +뺗 BbAeH +뺘 BbYa +뺙 BbYaG +뺚 BbYaGg +뺛 BbYaGs +뺜 BbYaN +뺝 BbYaNj +뺞 BbYaNh +뺟 BbYaD +뺠 BbYaL +뺡 BbYaLg +뺢 BbYaLm +뺣 BbYaLb +뺤 BbYaLs +뺥 BbYaLt +뺦 BbYaLp +뺧 BbYaLh +뺨 BbYaM +뺩 BbYaB +뺪 BbYaBs +뺫 BbYaS +뺬 BbYaSs +뺭 BbYaNg +뺮 BbYaJ +뺯 BbYaC +뺰 BbYaK +뺱 BbYaT +뺲 BbYaP +뺳 BbYaH +뺴 BbYae +뺵 BbYaeG +뺶 BbYaeGg +뺷 BbYaeGs +뺸 BbYaeN +뺹 BbYaeNj +뺺 BbYaeNh +뺻 BbYaeD +뺼 BbYaeL +뺽 BbYaeLg +뺾 BbYaeLm +뺿 BbYaeLb +뻀 BbYaeLs +뻁 BbYaeLt +뻂 BbYaeLp +뻃 BbYaeLh +뻄 BbYaeM +뻅 BbYaeB +뻆 BbYaeBs +뻇 BbYaeS +뻈 BbYaeSs +뻉 BbYaeNg +뻊 BbYaeJ +뻋 BbYaeC +뻌 BbYaeK +뻍 BbYaeT +뻎 BbYaeP +뻏 BbYaeH +뻐 BbEo +뻑 BbEoG +뻒 BbEoGg +뻓 BbEoGs +뻔 BbEoN +뻕 BbEoNj +뻖 BbEoNh +뻗 BbEoD +뻘 BbEoL +뻙 BbEoLg +뻚 BbEoLm +뻛 BbEoLb +뻜 BbEoLs +뻝 BbEoLt +뻞 BbEoLp +뻟 BbEoLh +뻠 BbEoM +뻡 BbEoB +뻢 BbEoBs +뻣 BbEoS +뻤 BbEoSs +뻥 BbEoNg +뻦 BbEoJ +뻧 BbEoC +뻨 BbEoK +뻩 BbEoT +뻪 BbEoP +뻫 BbEoH +뻬 BbE +뻭 BbEG +뻮 BbEGg +뻯 BbEGs +뻰 BbEN +뻱 BbENj +뻲 BbENh +뻳 BbED +뻴 BbEL +뻵 BbELg +뻶 BbELm +뻷 BbELb +뻸 BbELs +뻹 BbELt +뻺 BbELp +뻻 BbELh +뻼 BbEM +뻽 BbEB +뻾 BbEBs +뻿 BbES +뼀 BbESs +뼁 BbENg +뼂 BbEJ +뼃 BbEC +뼄 BbEK +뼅 BbET +뼆 BbEP +뼇 BbEH +뼈 BbYeo +뼉 BbYeoG +뼊 BbYeoGg +뼋 BbYeoGs +뼌 BbYeoN +뼍 BbYeoNj +뼎 BbYeoNh +뼏 BbYeoD +뼐 BbYeoL +뼑 BbYeoLg +뼒 BbYeoLm +뼓 BbYeoLb +뼔 BbYeoLs +뼕 BbYeoLt +뼖 BbYeoLp +뼗 BbYeoLh +뼘 BbYeoM +뼙 BbYeoB +뼚 BbYeoBs +뼛 BbYeoS +뼜 BbYeoSs +뼝 BbYeoNg +뼞 BbYeoJ +뼟 BbYeoC +뼠 BbYeoK +뼡 BbYeoT +뼢 BbYeoP +뼣 BbYeoH +뼤 BbYe +뼥 BbYeG +뼦 BbYeGg +뼧 BbYeGs +뼨 BbYeN +뼩 BbYeNj +뼪 BbYeNh +뼫 BbYeD +뼬 BbYeL +뼭 BbYeLg +뼮 BbYeLm +뼯 BbYeLb +뼰 BbYeLs +뼱 BbYeLt +뼲 BbYeLp +뼳 BbYeLh +뼴 BbYeM +뼵 BbYeB +뼶 BbYeBs +뼷 BbYeS +뼸 BbYeSs +뼹 BbYeNg +뼺 BbYeJ +뼻 BbYeC +뼼 BbYeK +뼽 BbYeT +뼾 BbYeP +뼿 BbYeH +뽀 BbO +뽁 BbOG +뽂 BbOGg +뽃 BbOGs +뽄 BbON +뽅 BbONj +뽆 BbONh +뽇 BbOD +뽈 BbOL +뽉 BbOLg +뽊 BbOLm +뽋 BbOLb +뽌 BbOLs +뽍 BbOLt +뽎 BbOLp +뽏 BbOLh +뽐 BbOM +뽑 BbOB +뽒 BbOBs +뽓 BbOS +뽔 BbOSs +뽕 BbONg +뽖 BbOJ +뽗 BbOC +뽘 BbOK +뽙 BbOT +뽚 BbOP +뽛 BbOH +뽜 BbWa +뽝 BbWaG +뽞 BbWaGg +뽟 BbWaGs +뽠 BbWaN +뽡 BbWaNj +뽢 BbWaNh +뽣 BbWaD +뽤 BbWaL +뽥 BbWaLg +뽦 BbWaLm +뽧 BbWaLb +뽨 BbWaLs +뽩 BbWaLt +뽪 BbWaLp +뽫 BbWaLh +뽬 BbWaM +뽭 BbWaB +뽮 BbWaBs +뽯 BbWaS +뽰 BbWaSs +뽱 BbWaNg +뽲 BbWaJ +뽳 BbWaC +뽴 BbWaK +뽵 BbWaT +뽶 BbWaP +뽷 BbWaH +뽸 BbWae +뽹 BbWaeG +뽺 BbWaeGg +뽻 BbWaeGs +뽼 BbWaeN +뽽 BbWaeNj +뽾 BbWaeNh +뽿 BbWaeD +뾀 BbWaeL +뾁 BbWaeLg +뾂 BbWaeLm +뾃 BbWaeLb +뾄 BbWaeLs +뾅 BbWaeLt +뾆 BbWaeLp +뾇 BbWaeLh +뾈 BbWaeM +뾉 BbWaeB +뾊 BbWaeBs +뾋 BbWaeS +뾌 BbWaeSs +뾍 BbWaeNg +뾎 BbWaeJ +뾏 BbWaeC +뾐 BbWaeK +뾑 BbWaeT +뾒 BbWaeP +뾓 BbWaeH +뾔 BbOe +뾕 BbOeG +뾖 BbOeGg +뾗 BbOeGs +뾘 BbOeN +뾙 BbOeNj +뾚 BbOeNh +뾛 BbOeD +뾜 BbOeL +뾝 BbOeLg +뾞 BbOeLm +뾟 BbOeLb +뾠 BbOeLs +뾡 BbOeLt +뾢 BbOeLp +뾣 BbOeLh +뾤 BbOeM +뾥 BbOeB +뾦 BbOeBs +뾧 BbOeS +뾨 BbOeSs +뾩 BbOeNg +뾪 BbOeJ +뾫 BbOeC +뾬 BbOeK +뾭 BbOeT +뾮 BbOeP +뾯 BbOeH +뾰 BbYo +뾱 BbYoG +뾲 BbYoGg +뾳 BbYoGs +뾴 BbYoN +뾵 BbYoNj +뾶 BbYoNh +뾷 BbYoD +뾸 BbYoL +뾹 BbYoLg +뾺 BbYoLm +뾻 BbYoLb +뾼 BbYoLs +뾽 BbYoLt +뾾 BbYoLp +뾿 BbYoLh +뿀 BbYoM +뿁 BbYoB +뿂 BbYoBs +뿃 BbYoS +뿄 BbYoSs +뿅 BbYoNg +뿆 BbYoJ +뿇 BbYoC +뿈 BbYoK +뿉 BbYoT +뿊 BbYoP +뿋 BbYoH +뿌 BbU +뿍 BbUG +뿎 BbUGg +뿏 BbUGs +뿐 BbUN +뿑 BbUNj +뿒 BbUNh +뿓 BbUD +뿔 BbUL +뿕 BbULg +뿖 BbULm +뿗 BbULb +뿘 BbULs +뿙 BbULt +뿚 BbULp +뿛 BbULh +뿜 BbUM +뿝 BbUB +뿞 BbUBs +뿟 BbUS +뿠 BbUSs +뿡 BbUNg +뿢 BbUJ +뿣 BbUC +뿤 BbUK +뿥 BbUT +뿦 BbUP +뿧 BbUH +뿨 BbWo +뿩 BbWoG +뿪 BbWoGg +뿫 BbWoGs +뿬 BbWoN +뿭 BbWoNj +뿮 BbWoNh +뿯 BbWoD +뿰 BbWoL +뿱 BbWoLg +뿲 BbWoLm +뿳 BbWoLb +뿴 BbWoLs +뿵 BbWoLt +뿶 BbWoLp +뿷 BbWoLh +뿸 BbWoM +뿹 BbWoB +뿺 BbWoBs +뿻 BbWoS +뿼 BbWoSs +뿽 BbWoNg +뿾 BbWoJ +뿿 BbWoC +쀀 BbWoK +쀁 BbWoT +쀂 BbWoP +쀃 BbWoH +쀄 BbWe +쀅 BbWeG +쀆 BbWeGg +쀇 BbWeGs +쀈 BbWeN +쀉 BbWeNj +쀊 BbWeNh +쀋 BbWeD +쀌 BbWeL +쀍 BbWeLg +쀎 BbWeLm +쀏 BbWeLb +쀐 BbWeLs +쀑 BbWeLt +쀒 BbWeLp +쀓 BbWeLh +쀔 BbWeM +쀕 BbWeB +쀖 BbWeBs +쀗 BbWeS +쀘 BbWeSs +쀙 BbWeNg +쀚 BbWeJ +쀛 BbWeC +쀜 BbWeK +쀝 BbWeT +쀞 BbWeP +쀟 BbWeH +쀠 BbWi +쀡 BbWiG +쀢 BbWiGg +쀣 BbWiGs +쀤 BbWiN +쀥 BbWiNj +쀦 BbWiNh +쀧 BbWiD +쀨 BbWiL +쀩 BbWiLg +쀪 BbWiLm +쀫 BbWiLb +쀬 BbWiLs +쀭 BbWiLt +쀮 BbWiLp +쀯 BbWiLh +쀰 BbWiM +쀱 BbWiB +쀲 BbWiBs +쀳 BbWiS +쀴 BbWiSs +쀵 BbWiNg +쀶 BbWiJ +쀷 BbWiC +쀸 BbWiK +쀹 BbWiT +쀺 BbWiP +쀻 BbWiH +쀼 BbYu +쀽 BbYuG +쀾 BbYuGg +쀿 BbYuGs +쁀 BbYuN +쁁 BbYuNj +쁂 BbYuNh +쁃 BbYuD +쁄 BbYuL +쁅 BbYuLg +쁆 BbYuLm +쁇 BbYuLb +쁈 BbYuLs +쁉 BbYuLt +쁊 BbYuLp +쁋 BbYuLh +쁌 BbYuM +쁍 BbYuB +쁎 BbYuBs +쁏 BbYuS +쁐 BbYuSs +쁑 BbYuNg +쁒 BbYuJ +쁓 BbYuC +쁔 BbYuK +쁕 BbYuT +쁖 BbYuP +쁗 BbYuH +쁘 BbEu +쁙 BbEuG +쁚 BbEuGg +쁛 BbEuGs +쁜 BbEuN +쁝 BbEuNj +쁞 BbEuNh +쁟 BbEuD +쁠 BbEuL +쁡 BbEuLg +쁢 BbEuLm +쁣 BbEuLb +쁤 BbEuLs +쁥 BbEuLt +쁦 BbEuLp +쁧 BbEuLh +쁨 BbEuM +쁩 BbEuB +쁪 BbEuBs +쁫 BbEuS +쁬 BbEuSs +쁭 BbEuNg +쁮 BbEuJ +쁯 BbEuC +쁰 BbEuK +쁱 BbEuT +쁲 BbEuP +쁳 BbEuH +쁴 BbUi +쁵 BbUiG +쁶 BbUiGg +쁷 BbUiGs +쁸 BbUiN +쁹 BbUiNj +쁺 BbUiNh +쁻 BbUiD +쁼 BbUiL +쁽 BbUiLg +쁾 BbUiLm +쁿 BbUiLb +삀 BbUiLs +삁 BbUiLt +삂 BbUiLp +삃 BbUiLh +삄 BbUiM +삅 BbUiB +삆 BbUiBs +삇 BbUiS +삈 BbUiSs +삉 BbUiNg +삊 BbUiJ +삋 BbUiC +삌 BbUiK +삍 BbUiT +삎 BbUiP +삏 BbUiH +삐 BbI +삑 BbIG +삒 BbIGg +삓 BbIGs +삔 BbIN +삕 BbINj +삖 BbINh +삗 BbID +삘 BbIL +삙 BbILg +삚 BbILm +삛 BbILb +삜 BbILs +삝 BbILt +삞 BbILp +삟 BbILh +삠 BbIM +삡 BbIB +삢 BbIBs +삣 BbIS +삤 BbISs +삥 BbINg +삦 BbIJ +삧 BbIC +삨 BbIK +삩 BbIT +삪 BbIP +삫 BbIH +사 SA +삭 SAG +삮 SAGg +삯 SAGs +산 SAN +삱 SANj +삲 SANh +삳 SAD +살 SAL +삵 SALg +삶 SALm +삷 SALb +삸 SALs +삹 SALt +삺 SALp +삻 SALh +삼 SAM +삽 SAB +삾 SABs +삿 SAS +샀 SASs +상 SANg +샂 SAJ +샃 SAC +샄 SAK +샅 SAT +샆 SAP +샇 SAH +새 SAe +색 SAeG +샊 SAeGg +샋 SAeGs +샌 SAeN +샍 SAeNj +샎 SAeNh +샏 SAeD +샐 SAeL +샑 SAeLg +샒 SAeLm +샓 SAeLb +샔 SAeLs +샕 SAeLt +샖 SAeLp +샗 SAeLh +샘 SAeM +샙 SAeB +샚 SAeBs +샛 SAeS +샜 SAeSs +생 SAeNg +샞 SAeJ +샟 SAeC +샠 SAeK +샡 SAeT +샢 SAeP +샣 SAeH +샤 SYa +샥 SYaG +샦 SYaGg +샧 SYaGs +샨 SYaN +샩 SYaNj +샪 SYaNh +샫 SYaD +샬 SYaL +샭 SYaLg +샮 SYaLm +샯 SYaLb +샰 SYaLs +샱 SYaLt +샲 SYaLp +샳 SYaLh +샴 SYaM +샵 SYaB +샶 SYaBs +샷 SYaS +샸 SYaSs +샹 SYaNg +샺 SYaJ +샻 SYaC +샼 SYaK +샽 SYaT +샾 SYaP +샿 SYaH +섀 SYae +섁 SYaeG +섂 SYaeGg +섃 SYaeGs +섄 SYaeN +섅 SYaeNj +섆 SYaeNh +섇 SYaeD +섈 SYaeL +섉 SYaeLg +섊 SYaeLm +섋 SYaeLb +섌 SYaeLs +섍 SYaeLt +섎 SYaeLp +섏 SYaeLh +섐 SYaeM +섑 SYaeB +섒 SYaeBs +섓 SYaeS +섔 SYaeSs +섕 SYaeNg +섖 SYaeJ +섗 SYaeC +섘 SYaeK +섙 SYaeT +섚 SYaeP +섛 SYaeH +서 SEo +석 SEoG +섞 SEoGg +섟 SEoGs +선 SEoN +섡 SEoNj +섢 SEoNh +섣 SEoD +설 SEoL +섥 SEoLg +섦 SEoLm +섧 SEoLb +섨 SEoLs +섩 SEoLt +섪 SEoLp +섫 SEoLh +섬 SEoM +섭 SEoB +섮 SEoBs +섯 SEoS +섰 SEoSs +성 SEoNg +섲 SEoJ +섳 SEoC +섴 SEoK +섵 SEoT +섶 SEoP +섷 SEoH +세 SE +섹 SEG +섺 SEGg +섻 SEGs +센 SEN +섽 SENj +섾 SENh +섿 SED +셀 SEL +셁 SELg +셂 SELm +셃 SELb +셄 SELs +셅 SELt +셆 SELp +셇 SELh +셈 SEM +셉 SEB +셊 SEBs +셋 SES +셌 SESs +셍 SENg +셎 SEJ +셏 SEC +셐 SEK +셑 SET +셒 SEP +셓 SEH +셔 SYeo +셕 SYeoG +셖 SYeoGg +셗 SYeoGs +션 SYeoN +셙 SYeoNj +셚 SYeoNh +셛 SYeoD +셜 SYeoL +셝 SYeoLg +셞 SYeoLm +셟 SYeoLb +셠 SYeoLs +셡 SYeoLt +셢 SYeoLp +셣 SYeoLh +셤 SYeoM +셥 SYeoB +셦 SYeoBs +셧 SYeoS +셨 SYeoSs +셩 SYeoNg +셪 SYeoJ +셫 SYeoC +셬 SYeoK +셭 SYeoT +셮 SYeoP +셯 SYeoH +셰 SYe +셱 SYeG +셲 SYeGg +셳 SYeGs +셴 SYeN +셵 SYeNj +셶 SYeNh +셷 SYeD +셸 SYeL +셹 SYeLg +셺 SYeLm +셻 SYeLb +셼 SYeLs +셽 SYeLt +셾 SYeLp +셿 SYeLh +솀 SYeM +솁 SYeB +솂 SYeBs +솃 SYeS +솄 SYeSs +솅 SYeNg +솆 SYeJ +솇 SYeC +솈 SYeK +솉 SYeT +솊 SYeP +솋 SYeH +소 SO +속 SOG +솎 SOGg +솏 SOGs +손 SON +솑 SONj +솒 SONh +솓 SOD +솔 SOL +솕 SOLg +솖 SOLm +솗 SOLb +솘 SOLs +솙 SOLt +솚 SOLp +솛 SOLh +솜 SOM +솝 SOB +솞 SOBs +솟 SOS +솠 SOSs +송 SONg +솢 SOJ +솣 SOC +솤 SOK +솥 SOT +솦 SOP +솧 SOH +솨 SWa +솩 SWaG +솪 SWaGg +솫 SWaGs +솬 SWaN +솭 SWaNj +솮 SWaNh +솯 SWaD +솰 SWaL +솱 SWaLg +솲 SWaLm +솳 SWaLb +솴 SWaLs +솵 SWaLt +솶 SWaLp +솷 SWaLh +솸 SWaM +솹 SWaB +솺 SWaBs +솻 SWaS +솼 SWaSs +솽 SWaNg +솾 SWaJ +솿 SWaC +쇀 SWaK +쇁 SWaT +쇂 SWaP +쇃 SWaH +쇄 SWae +쇅 SWaeG +쇆 SWaeGg +쇇 SWaeGs +쇈 SWaeN +쇉 SWaeNj +쇊 SWaeNh +쇋 SWaeD +쇌 SWaeL +쇍 SWaeLg +쇎 SWaeLm +쇏 SWaeLb +쇐 SWaeLs +쇑 SWaeLt +쇒 SWaeLp +쇓 SWaeLh +쇔 SWaeM +쇕 SWaeB +쇖 SWaeBs +쇗 SWaeS +쇘 SWaeSs +쇙 SWaeNg +쇚 SWaeJ +쇛 SWaeC +쇜 SWaeK +쇝 SWaeT +쇞 SWaeP +쇟 SWaeH +쇠 SOe +쇡 SOeG +쇢 SOeGg +쇣 SOeGs +쇤 SOeN +쇥 SOeNj +쇦 SOeNh +쇧 SOeD +쇨 SOeL +쇩 SOeLg +쇪 SOeLm +쇫 SOeLb +쇬 SOeLs +쇭 SOeLt +쇮 SOeLp +쇯 SOeLh +쇰 SOeM +쇱 SOeB +쇲 SOeBs +쇳 SOeS +쇴 SOeSs +쇵 SOeNg +쇶 SOeJ +쇷 SOeC +쇸 SOeK +쇹 SOeT +쇺 SOeP +쇻 SOeH +쇼 SYo +쇽 SYoG +쇾 SYoGg +쇿 SYoGs +숀 SYoN +숁 SYoNj +숂 SYoNh +숃 SYoD +숄 SYoL +숅 SYoLg +숆 SYoLm +숇 SYoLb +숈 SYoLs +숉 SYoLt +숊 SYoLp +숋 SYoLh +숌 SYoM +숍 SYoB +숎 SYoBs +숏 SYoS +숐 SYoSs +숑 SYoNg +숒 SYoJ +숓 SYoC +숔 SYoK +숕 SYoT +숖 SYoP +숗 SYoH +수 SU +숙 SUG +숚 SUGg +숛 SUGs +순 SUN +숝 SUNj +숞 SUNh +숟 SUD +술 SUL +숡 SULg +숢 SULm +숣 SULb +숤 SULs +숥 SULt +숦 SULp +숧 SULh +숨 SUM +숩 SUB +숪 SUBs +숫 SUS +숬 SUSs +숭 SUNg +숮 SUJ +숯 SUC +숰 SUK +숱 SUT +숲 SUP +숳 SUH +숴 SWo +숵 SWoG +숶 SWoGg +숷 SWoGs +숸 SWoN +숹 SWoNj +숺 SWoNh +숻 SWoD +숼 SWoL +숽 SWoLg +숾 SWoLm +숿 SWoLb +쉀 SWoLs +쉁 SWoLt +쉂 SWoLp +쉃 SWoLh +쉄 SWoM +쉅 SWoB +쉆 SWoBs +쉇 SWoS +쉈 SWoSs +쉉 SWoNg +쉊 SWoJ +쉋 SWoC +쉌 SWoK +쉍 SWoT +쉎 SWoP +쉏 SWoH +쉐 SWe +쉑 SWeG +쉒 SWeGg +쉓 SWeGs +쉔 SWeN +쉕 SWeNj +쉖 SWeNh +쉗 SWeD +쉘 SWeL +쉙 SWeLg +쉚 SWeLm +쉛 SWeLb +쉜 SWeLs +쉝 SWeLt +쉞 SWeLp +쉟 SWeLh +쉠 SWeM +쉡 SWeB +쉢 SWeBs +쉣 SWeS +쉤 SWeSs +쉥 SWeNg +쉦 SWeJ +쉧 SWeC +쉨 SWeK +쉩 SWeT +쉪 SWeP +쉫 SWeH +쉬 SWi +쉭 SWiG +쉮 SWiGg +쉯 SWiGs +쉰 SWiN +쉱 SWiNj +쉲 SWiNh +쉳 SWiD +쉴 SWiL +쉵 SWiLg +쉶 SWiLm +쉷 SWiLb +쉸 SWiLs +쉹 SWiLt +쉺 SWiLp +쉻 SWiLh +쉼 SWiM +쉽 SWiB +쉾 SWiBs +쉿 SWiS +슀 SWiSs +슁 SWiNg +슂 SWiJ +슃 SWiC +슄 SWiK +슅 SWiT +슆 SWiP +슇 SWiH +슈 SYu +슉 SYuG +슊 SYuGg +슋 SYuGs +슌 SYuN +슍 SYuNj +슎 SYuNh +슏 SYuD +슐 SYuL +슑 SYuLg +슒 SYuLm +슓 SYuLb +슔 SYuLs +슕 SYuLt +슖 SYuLp +슗 SYuLh +슘 SYuM +슙 SYuB +슚 SYuBs +슛 SYuS +슜 SYuSs +슝 SYuNg +슞 SYuJ +슟 SYuC +슠 SYuK +슡 SYuT +슢 SYuP +슣 SYuH +스 SEu +슥 SEuG +슦 SEuGg +슧 SEuGs +슨 SEuN +슩 SEuNj +슪 SEuNh +슫 SEuD +슬 SEuL +슭 SEuLg +슮 SEuLm +슯 SEuLb +슰 SEuLs +슱 SEuLt +슲 SEuLp +슳 SEuLh +슴 SEuM +습 SEuB +슶 SEuBs +슷 SEuS +슸 SEuSs +승 SEuNg +슺 SEuJ +슻 SEuC +슼 SEuK +슽 SEuT +슾 SEuP +슿 SEuH +싀 SUi +싁 SUiG +싂 SUiGg +싃 SUiGs +싄 SUiN +싅 SUiNj +싆 SUiNh +싇 SUiD +싈 SUiL +싉 SUiLg +싊 SUiLm +싋 SUiLb +싌 SUiLs +싍 SUiLt +싎 SUiLp +싏 SUiLh +싐 SUiM +싑 SUiB +싒 SUiBs +싓 SUiS +싔 SUiSs +싕 SUiNg +싖 SUiJ +싗 SUiC +싘 SUiK +싙 SUiT +싚 SUiP +싛 SUiH +시 SI +식 SIG +싞 SIGg +싟 SIGs +신 SIN +싡 SINj +싢 SINh +싣 SID +실 SIL +싥 SILg +싦 SILm +싧 SILb +싨 SILs +싩 SILt +싪 SILp +싫 SILh +심 SIM +십 SIB +싮 SIBs +싯 SIS +싰 SISs +싱 SINg +싲 SIJ +싳 SIC +싴 SIK +싵 SIT +싶 SIP +싷 SIH +싸 SsA +싹 SsAG +싺 SsAGg +싻 SsAGs +싼 SsAN +싽 SsANj +싾 SsANh +싿 SsAD +쌀 SsAL +쌁 SsALg +쌂 SsALm +쌃 SsALb +쌄 SsALs +쌅 SsALt +쌆 SsALp +쌇 SsALh +쌈 SsAM +쌉 SsAB +쌊 SsABs +쌋 SsAS +쌌 SsASs +쌍 SsANg +쌎 SsAJ +쌏 SsAC +쌐 SsAK +쌑 SsAT +쌒 SsAP +쌓 SsAH +쌔 SsAe +쌕 SsAeG +쌖 SsAeGg +쌗 SsAeGs +쌘 SsAeN +쌙 SsAeNj +쌚 SsAeNh +쌛 SsAeD +쌜 SsAeL +쌝 SsAeLg +쌞 SsAeLm +쌟 SsAeLb +쌠 SsAeLs +쌡 SsAeLt +쌢 SsAeLp +쌣 SsAeLh +쌤 SsAeM +쌥 SsAeB +쌦 SsAeBs +쌧 SsAeS +쌨 SsAeSs +쌩 SsAeNg +쌪 SsAeJ +쌫 SsAeC +쌬 SsAeK +쌭 SsAeT +쌮 SsAeP +쌯 SsAeH +쌰 SsYa +쌱 SsYaG +쌲 SsYaGg +쌳 SsYaGs +쌴 SsYaN +쌵 SsYaNj +쌶 SsYaNh +쌷 SsYaD +쌸 SsYaL +쌹 SsYaLg +쌺 SsYaLm +쌻 SsYaLb +쌼 SsYaLs +쌽 SsYaLt +쌾 SsYaLp +쌿 SsYaLh +썀 SsYaM +썁 SsYaB +썂 SsYaBs +썃 SsYaS +썄 SsYaSs +썅 SsYaNg +썆 SsYaJ +썇 SsYaC +썈 SsYaK +썉 SsYaT +썊 SsYaP +썋 SsYaH +썌 SsYae +썍 SsYaeG +썎 SsYaeGg +썏 SsYaeGs +썐 SsYaeN +썑 SsYaeNj +썒 SsYaeNh +썓 SsYaeD +썔 SsYaeL +썕 SsYaeLg +썖 SsYaeLm +썗 SsYaeLb +썘 SsYaeLs +썙 SsYaeLt +썚 SsYaeLp +썛 SsYaeLh +썜 SsYaeM +썝 SsYaeB +썞 SsYaeBs +썟 SsYaeS +썠 SsYaeSs +썡 SsYaeNg +썢 SsYaeJ +썣 SsYaeC +썤 SsYaeK +썥 SsYaeT +썦 SsYaeP +썧 SsYaeH +써 SsEo +썩 SsEoG +썪 SsEoGg +썫 SsEoGs +썬 SsEoN +썭 SsEoNj +썮 SsEoNh +썯 SsEoD +썰 SsEoL +썱 SsEoLg +썲 SsEoLm +썳 SsEoLb +썴 SsEoLs +썵 SsEoLt +썶 SsEoLp +썷 SsEoLh +썸 SsEoM +썹 SsEoB +썺 SsEoBs +썻 SsEoS +썼 SsEoSs +썽 SsEoNg +썾 SsEoJ +썿 SsEoC +쎀 SsEoK +쎁 SsEoT +쎂 SsEoP +쎃 SsEoH +쎄 SsE +쎅 SsEG +쎆 SsEGg +쎇 SsEGs +쎈 SsEN +쎉 SsENj +쎊 SsENh +쎋 SsED +쎌 SsEL +쎍 SsELg +쎎 SsELm +쎏 SsELb +쎐 SsELs +쎑 SsELt +쎒 SsELp +쎓 SsELh +쎔 SsEM +쎕 SsEB +쎖 SsEBs +쎗 SsES +쎘 SsESs +쎙 SsENg +쎚 SsEJ +쎛 SsEC +쎜 SsEK +쎝 SsET +쎞 SsEP +쎟 SsEH +쎠 SsYeo +쎡 SsYeoG +쎢 SsYeoGg +쎣 SsYeoGs +쎤 SsYeoN +쎥 SsYeoNj +쎦 SsYeoNh +쎧 SsYeoD +쎨 SsYeoL +쎩 SsYeoLg +쎪 SsYeoLm +쎫 SsYeoLb +쎬 SsYeoLs +쎭 SsYeoLt +쎮 SsYeoLp +쎯 SsYeoLh +쎰 SsYeoM +쎱 SsYeoB +쎲 SsYeoBs +쎳 SsYeoS +쎴 SsYeoSs +쎵 SsYeoNg +쎶 SsYeoJ +쎷 SsYeoC +쎸 SsYeoK +쎹 SsYeoT +쎺 SsYeoP +쎻 SsYeoH +쎼 SsYe +쎽 SsYeG +쎾 SsYeGg +쎿 SsYeGs +쏀 SsYeN +쏁 SsYeNj +쏂 SsYeNh +쏃 SsYeD +쏄 SsYeL +쏅 SsYeLg +쏆 SsYeLm +쏇 SsYeLb +쏈 SsYeLs +쏉 SsYeLt +쏊 SsYeLp +쏋 SsYeLh +쏌 SsYeM +쏍 SsYeB +쏎 SsYeBs +쏏 SsYeS +쏐 SsYeSs +쏑 SsYeNg +쏒 SsYeJ +쏓 SsYeC +쏔 SsYeK +쏕 SsYeT +쏖 SsYeP +쏗 SsYeH +쏘 SsO +쏙 SsOG +쏚 SsOGg +쏛 SsOGs +쏜 SsON +쏝 SsONj +쏞 SsONh +쏟 SsOD +쏠 SsOL +쏡 SsOLg +쏢 SsOLm +쏣 SsOLb +쏤 SsOLs +쏥 SsOLt +쏦 SsOLp +쏧 SsOLh +쏨 SsOM +쏩 SsOB +쏪 SsOBs +쏫 SsOS +쏬 SsOSs +쏭 SsONg +쏮 SsOJ +쏯 SsOC +쏰 SsOK +쏱 SsOT +쏲 SsOP +쏳 SsOH +쏴 SsWa +쏵 SsWaG +쏶 SsWaGg +쏷 SsWaGs +쏸 SsWaN +쏹 SsWaNj +쏺 SsWaNh +쏻 SsWaD +쏼 SsWaL +쏽 SsWaLg +쏾 SsWaLm +쏿 SsWaLb +쐀 SsWaLs +쐁 SsWaLt +쐂 SsWaLp +쐃 SsWaLh +쐄 SsWaM +쐅 SsWaB +쐆 SsWaBs +쐇 SsWaS +쐈 SsWaSs +쐉 SsWaNg +쐊 SsWaJ +쐋 SsWaC +쐌 SsWaK +쐍 SsWaT +쐎 SsWaP +쐏 SsWaH +쐐 SsWae +쐑 SsWaeG +쐒 SsWaeGg +쐓 SsWaeGs +쐔 SsWaeN +쐕 SsWaeNj +쐖 SsWaeNh +쐗 SsWaeD +쐘 SsWaeL +쐙 SsWaeLg +쐚 SsWaeLm +쐛 SsWaeLb +쐜 SsWaeLs +쐝 SsWaeLt +쐞 SsWaeLp +쐟 SsWaeLh +쐠 SsWaeM +쐡 SsWaeB +쐢 SsWaeBs +쐣 SsWaeS +쐤 SsWaeSs +쐥 SsWaeNg +쐦 SsWaeJ +쐧 SsWaeC +쐨 SsWaeK +쐩 SsWaeT +쐪 SsWaeP +쐫 SsWaeH +쐬 SsOe +쐭 SsOeG +쐮 SsOeGg +쐯 SsOeGs +쐰 SsOeN +쐱 SsOeNj +쐲 SsOeNh +쐳 SsOeD +쐴 SsOeL +쐵 SsOeLg +쐶 SsOeLm +쐷 SsOeLb +쐸 SsOeLs +쐹 SsOeLt +쐺 SsOeLp +쐻 SsOeLh +쐼 SsOeM +쐽 SsOeB +쐾 SsOeBs +쐿 SsOeS +쑀 SsOeSs +쑁 SsOeNg +쑂 SsOeJ +쑃 SsOeC +쑄 SsOeK +쑅 SsOeT +쑆 SsOeP +쑇 SsOeH +쑈 SsYo +쑉 SsYoG +쑊 SsYoGg +쑋 SsYoGs +쑌 SsYoN +쑍 SsYoNj +쑎 SsYoNh +쑏 SsYoD +쑐 SsYoL +쑑 SsYoLg +쑒 SsYoLm +쑓 SsYoLb +쑔 SsYoLs +쑕 SsYoLt +쑖 SsYoLp +쑗 SsYoLh +쑘 SsYoM +쑙 SsYoB +쑚 SsYoBs +쑛 SsYoS +쑜 SsYoSs +쑝 SsYoNg +쑞 SsYoJ +쑟 SsYoC +쑠 SsYoK +쑡 SsYoT +쑢 SsYoP +쑣 SsYoH +쑤 SsU +쑥 SsUG +쑦 SsUGg +쑧 SsUGs +쑨 SsUN +쑩 SsUNj +쑪 SsUNh +쑫 SsUD +쑬 SsUL +쑭 SsULg +쑮 SsULm +쑯 SsULb +쑰 SsULs +쑱 SsULt +쑲 SsULp +쑳 SsULh +쑴 SsUM +쑵 SsUB +쑶 SsUBs +쑷 SsUS +쑸 SsUSs +쑹 SsUNg +쑺 SsUJ +쑻 SsUC +쑼 SsUK +쑽 SsUT +쑾 SsUP +쑿 SsUH +쒀 SsWo +쒁 SsWoG +쒂 SsWoGg +쒃 SsWoGs +쒄 SsWoN +쒅 SsWoNj +쒆 SsWoNh +쒇 SsWoD +쒈 SsWoL +쒉 SsWoLg +쒊 SsWoLm +쒋 SsWoLb +쒌 SsWoLs +쒍 SsWoLt +쒎 SsWoLp +쒏 SsWoLh +쒐 SsWoM +쒑 SsWoB +쒒 SsWoBs +쒓 SsWoS +쒔 SsWoSs +쒕 SsWoNg +쒖 SsWoJ +쒗 SsWoC +쒘 SsWoK +쒙 SsWoT +쒚 SsWoP +쒛 SsWoH +쒜 SsWe +쒝 SsWeG +쒞 SsWeGg +쒟 SsWeGs +쒠 SsWeN +쒡 SsWeNj +쒢 SsWeNh +쒣 SsWeD +쒤 SsWeL +쒥 SsWeLg +쒦 SsWeLm +쒧 SsWeLb +쒨 SsWeLs +쒩 SsWeLt +쒪 SsWeLp +쒫 SsWeLh +쒬 SsWeM +쒭 SsWeB +쒮 SsWeBs +쒯 SsWeS +쒰 SsWeSs +쒱 SsWeNg +쒲 SsWeJ +쒳 SsWeC +쒴 SsWeK +쒵 SsWeT +쒶 SsWeP +쒷 SsWeH +쒸 SsWi +쒹 SsWiG +쒺 SsWiGg +쒻 SsWiGs +쒼 SsWiN +쒽 SsWiNj +쒾 SsWiNh +쒿 SsWiD +쓀 SsWiL +쓁 SsWiLg +쓂 SsWiLm +쓃 SsWiLb +쓄 SsWiLs +쓅 SsWiLt +쓆 SsWiLp +쓇 SsWiLh +쓈 SsWiM +쓉 SsWiB +쓊 SsWiBs +쓋 SsWiS +쓌 SsWiSs +쓍 SsWiNg +쓎 SsWiJ +쓏 SsWiC +쓐 SsWiK +쓑 SsWiT +쓒 SsWiP +쓓 SsWiH +쓔 SsYu +쓕 SsYuG +쓖 SsYuGg +쓗 SsYuGs +쓘 SsYuN +쓙 SsYuNj +쓚 SsYuNh +쓛 SsYuD +쓜 SsYuL +쓝 SsYuLg +쓞 SsYuLm +쓟 SsYuLb +쓠 SsYuLs +쓡 SsYuLt +쓢 SsYuLp +쓣 SsYuLh +쓤 SsYuM +쓥 SsYuB +쓦 SsYuBs +쓧 SsYuS +쓨 SsYuSs +쓩 SsYuNg +쓪 SsYuJ +쓫 SsYuC +쓬 SsYuK +쓭 SsYuT +쓮 SsYuP +쓯 SsYuH +쓰 SsEu +쓱 SsEuG +쓲 SsEuGg +쓳 SsEuGs +쓴 SsEuN +쓵 SsEuNj +쓶 SsEuNh +쓷 SsEuD +쓸 SsEuL +쓹 SsEuLg +쓺 SsEuLm +쓻 SsEuLb +쓼 SsEuLs +쓽 SsEuLt +쓾 SsEuLp +쓿 SsEuLh +씀 SsEuM +씁 SsEuB +씂 SsEuBs +씃 SsEuS +씄 SsEuSs +씅 SsEuNg +씆 SsEuJ +씇 SsEuC +씈 SsEuK +씉 SsEuT +씊 SsEuP +씋 SsEuH +씌 SsUi +씍 SsUiG +씎 SsUiGg +씏 SsUiGs +씐 SsUiN +씑 SsUiNj +씒 SsUiNh +씓 SsUiD +씔 SsUiL +씕 SsUiLg +씖 SsUiLm +씗 SsUiLb +씘 SsUiLs +씙 SsUiLt +씚 SsUiLp +씛 SsUiLh +씜 SsUiM +씝 SsUiB +씞 SsUiBs +씟 SsUiS +씠 SsUiSs +씡 SsUiNg +씢 SsUiJ +씣 SsUiC +씤 SsUiK +씥 SsUiT +씦 SsUiP +씧 SsUiH +씨 SsI +씩 SsIG +씪 SsIGg +씫 SsIGs +씬 SsIN +씭 SsINj +씮 SsINh +씯 SsID +씰 SsIL +씱 SsILg +씲 SsILm +씳 SsILb +씴 SsILs +씵 SsILt +씶 SsILp +씷 SsILh +씸 SsIM +씹 SsIB +씺 SsIBs +씻 SsIS +씼 SsISs +씽 SsINg +씾 SsIJ +씿 SsIC +앀 SsIK +앁 SsIT +앂 SsIP +앃 SsIH +아 ZA +악 ZAG +앆 ZAGg +앇 ZAGs +안 ZAN +앉 ZANj +않 ZANh +앋 ZAD +알 ZAL +앍 ZALg +앎 ZALm +앏 ZALb +앐 ZALs +앑 ZALt +앒 ZALp +앓 ZALh +암 ZAM +압 ZAB +앖 ZABs +앗 ZAS +았 ZASs +앙 ZANg +앚 ZAJ +앛 ZAC +앜 ZAK +앝 ZAT +앞 ZAP +앟 ZAH +애 ZAe +액 ZAeG +앢 ZAeGg +앣 ZAeGs +앤 ZAeN +앥 ZAeNj +앦 ZAeNh +앧 ZAeD +앨 ZAeL +앩 ZAeLg +앪 ZAeLm +앫 ZAeLb +앬 ZAeLs +앭 ZAeLt +앮 ZAeLp +앯 ZAeLh +앰 ZAeM +앱 ZAeB +앲 ZAeBs +앳 ZAeS +앴 ZAeSs +앵 ZAeNg +앶 ZAeJ +앷 ZAeC +앸 ZAeK +앹 ZAeT +앺 ZAeP +앻 ZAeH +야 ZYa +약 ZYaG +앾 ZYaGg +앿 ZYaGs +얀 ZYaN +얁 ZYaNj +얂 ZYaNh +얃 ZYaD +얄 ZYaL +얅 ZYaLg +얆 ZYaLm +얇 ZYaLb +얈 ZYaLs +얉 ZYaLt +얊 ZYaLp +얋 ZYaLh +얌 ZYaM +얍 ZYaB +얎 ZYaBs +얏 ZYaS +얐 ZYaSs +양 ZYaNg +얒 ZYaJ +얓 ZYaC +얔 ZYaK +얕 ZYaT +얖 ZYaP +얗 ZYaH +얘 ZYae +얙 ZYaeG +얚 ZYaeGg +얛 ZYaeGs +얜 ZYaeN +얝 ZYaeNj +얞 ZYaeNh +얟 ZYaeD +얠 ZYaeL +얡 ZYaeLg +얢 ZYaeLm +얣 ZYaeLb +얤 ZYaeLs +얥 ZYaeLt +얦 ZYaeLp +얧 ZYaeLh +얨 ZYaeM +얩 ZYaeB +얪 ZYaeBs +얫 ZYaeS +얬 ZYaeSs +얭 ZYaeNg +얮 ZYaeJ +얯 ZYaeC +얰 ZYaeK +얱 ZYaeT +얲 ZYaeP +얳 ZYaeH +어 ZEo +억 ZEoG +얶 ZEoGg +얷 ZEoGs +언 ZEoN +얹 ZEoNj +얺 ZEoNh +얻 ZEoD +얼 ZEoL +얽 ZEoLg +얾 ZEoLm +얿 ZEoLb +엀 ZEoLs +엁 ZEoLt +엂 ZEoLp +엃 ZEoLh +엄 ZEoM +업 ZEoB +없 ZEoBs +엇 ZEoS +었 ZEoSs +엉 ZEoNg +엊 ZEoJ +엋 ZEoC +엌 ZEoK +엍 ZEoT +엎 ZEoP +엏 ZEoH +에 ZE +엑 ZEG +엒 ZEGg +엓 ZEGs +엔 ZEN +엕 ZENj +엖 ZENh +엗 ZED +엘 ZEL +엙 ZELg +엚 ZELm +엛 ZELb +엜 ZELs +엝 ZELt +엞 ZELp +엟 ZELh +엠 ZEM +엡 ZEB +엢 ZEBs +엣 ZES +엤 ZESs +엥 ZENg +엦 ZEJ +엧 ZEC +엨 ZEK +엩 ZET +엪 ZEP +엫 ZEH +여 ZYeo +역 ZYeoG +엮 ZYeoGg +엯 ZYeoGs +연 ZYeoN +엱 ZYeoNj +엲 ZYeoNh +엳 ZYeoD +열 ZYeoL +엵 ZYeoLg +엶 ZYeoLm +엷 ZYeoLb +엸 ZYeoLs +엹 ZYeoLt +엺 ZYeoLp +엻 ZYeoLh +염 ZYeoM +엽 ZYeoB +엾 ZYeoBs +엿 ZYeoS +였 ZYeoSs +영 ZYeoNg +옂 ZYeoJ +옃 ZYeoC +옄 ZYeoK +옅 ZYeoT +옆 ZYeoP +옇 ZYeoH +예 ZYe +옉 ZYeG +옊 ZYeGg +옋 ZYeGs +옌 ZYeN +옍 ZYeNj +옎 ZYeNh +옏 ZYeD +옐 ZYeL +옑 ZYeLg +옒 ZYeLm +옓 ZYeLb +옔 ZYeLs +옕 ZYeLt +옖 ZYeLp +옗 ZYeLh +옘 ZYeM +옙 ZYeB +옚 ZYeBs +옛 ZYeS +옜 ZYeSs +옝 ZYeNg +옞 ZYeJ +옟 ZYeC +옠 ZYeK +옡 ZYeT +옢 ZYeP +옣 ZYeH +오 ZO +옥 ZOG +옦 ZOGg +옧 ZOGs +온 ZON +옩 ZONj +옪 ZONh +옫 ZOD +올 ZOL +옭 ZOLg +옮 ZOLm +옯 ZOLb +옰 ZOLs +옱 ZOLt +옲 ZOLp +옳 ZOLh +옴 ZOM +옵 ZOB +옶 ZOBs +옷 ZOS +옸 ZOSs +옹 ZONg +옺 ZOJ +옻 ZOC +옼 ZOK +옽 ZOT +옾 ZOP +옿 ZOH +와 ZWa +왁 ZWaG +왂 ZWaGg +왃 ZWaGs +완 ZWaN +왅 ZWaNj +왆 ZWaNh +왇 ZWaD +왈 ZWaL +왉 ZWaLg +왊 ZWaLm +왋 ZWaLb +왌 ZWaLs +왍 ZWaLt +왎 ZWaLp +왏 ZWaLh +왐 ZWaM +왑 ZWaB +왒 ZWaBs +왓 ZWaS +왔 ZWaSs +왕 ZWaNg +왖 ZWaJ +왗 ZWaC +왘 ZWaK +왙 ZWaT +왚 ZWaP +왛 ZWaH +왜 ZWae +왝 ZWaeG +왞 ZWaeGg +왟 ZWaeGs +왠 ZWaeN +왡 ZWaeNj +왢 ZWaeNh +왣 ZWaeD +왤 ZWaeL +왥 ZWaeLg +왦 ZWaeLm +왧 ZWaeLb +왨 ZWaeLs +왩 ZWaeLt +왪 ZWaeLp +왫 ZWaeLh +왬 ZWaeM +왭 ZWaeB +왮 ZWaeBs +왯 ZWaeS +왰 ZWaeSs +왱 ZWaeNg +왲 ZWaeJ +왳 ZWaeC +왴 ZWaeK +왵 ZWaeT +왶 ZWaeP +왷 ZWaeH +외 ZOe +왹 ZOeG +왺 ZOeGg +왻 ZOeGs +왼 ZOeN +왽 ZOeNj +왾 ZOeNh +왿 ZOeD +욀 ZOeL +욁 ZOeLg +욂 ZOeLm +욃 ZOeLb +욄 ZOeLs +욅 ZOeLt +욆 ZOeLp +욇 ZOeLh +욈 ZOeM +욉 ZOeB +욊 ZOeBs +욋 ZOeS +욌 ZOeSs +욍 ZOeNg +욎 ZOeJ +욏 ZOeC +욐 ZOeK +욑 ZOeT +욒 ZOeP +욓 ZOeH +요 ZYo +욕 ZYoG +욖 ZYoGg +욗 ZYoGs +욘 ZYoN +욙 ZYoNj +욚 ZYoNh +욛 ZYoD +욜 ZYoL +욝 ZYoLg +욞 ZYoLm +욟 ZYoLb +욠 ZYoLs +욡 ZYoLt +욢 ZYoLp +욣 ZYoLh +욤 ZYoM +욥 ZYoB +욦 ZYoBs +욧 ZYoS +욨 ZYoSs +용 ZYoNg +욪 ZYoJ +욫 ZYoC +욬 ZYoK +욭 ZYoT +욮 ZYoP +욯 ZYoH +우 ZU +욱 ZUG +욲 ZUGg +욳 ZUGs +운 ZUN +욵 ZUNj +욶 ZUNh +욷 ZUD +울 ZUL +욹 ZULg +욺 ZULm +욻 ZULb +욼 ZULs +욽 ZULt +욾 ZULp +욿 ZULh +움 ZUM +웁 ZUB +웂 ZUBs +웃 ZUS +웄 ZUSs +웅 ZUNg +웆 ZUJ +웇 ZUC +웈 ZUK +웉 ZUT +웊 ZUP +웋 ZUH +워 ZWo +웍 ZWoG +웎 ZWoGg +웏 ZWoGs +원 ZWoN +웑 ZWoNj +웒 ZWoNh +웓 ZWoD +월 ZWoL +웕 ZWoLg +웖 ZWoLm +웗 ZWoLb +웘 ZWoLs +웙 ZWoLt +웚 ZWoLp +웛 ZWoLh +웜 ZWoM +웝 ZWoB +웞 ZWoBs +웟 ZWoS +웠 ZWoSs +웡 ZWoNg +웢 ZWoJ +웣 ZWoC +웤 ZWoK +웥 ZWoT +웦 ZWoP +웧 ZWoH +웨 ZWe +웩 ZWeG +웪 ZWeGg +웫 ZWeGs +웬 ZWeN +웭 ZWeNj +웮 ZWeNh +웯 ZWeD +웰 ZWeL +웱 ZWeLg +웲 ZWeLm +웳 ZWeLb +웴 ZWeLs +웵 ZWeLt +웶 ZWeLp +웷 ZWeLh +웸 ZWeM +웹 ZWeB +웺 ZWeBs +웻 ZWeS +웼 ZWeSs +웽 ZWeNg +웾 ZWeJ +웿 ZWeC +윀 ZWeK +윁 ZWeT +윂 ZWeP +윃 ZWeH +위 ZWi +윅 ZWiG +윆 ZWiGg +윇 ZWiGs +윈 ZWiN +윉 ZWiNj +윊 ZWiNh +윋 ZWiD +윌 ZWiL +윍 ZWiLg +윎 ZWiLm +윏 ZWiLb +윐 ZWiLs +윑 ZWiLt +윒 ZWiLp +윓 ZWiLh +윔 ZWiM +윕 ZWiB +윖 ZWiBs +윗 ZWiS +윘 ZWiSs +윙 ZWiNg +윚 ZWiJ +윛 ZWiC +윜 ZWiK +윝 ZWiT +윞 ZWiP +윟 ZWiH +유 ZYu +육 ZYuG +윢 ZYuGg +윣 ZYuGs +윤 ZYuN +윥 ZYuNj +윦 ZYuNh +윧 ZYuD +율 ZYuL +윩 ZYuLg +윪 ZYuLm +윫 ZYuLb +윬 ZYuLs +윭 ZYuLt +윮 ZYuLp +윯 ZYuLh +윰 ZYuM +윱 ZYuB +윲 ZYuBs +윳 ZYuS +윴 ZYuSs +융 ZYuNg +윶 ZYuJ +윷 ZYuC +윸 ZYuK +윹 ZYuT +윺 ZYuP +윻 ZYuH +으 ZEu +윽 ZEuG +윾 ZEuGg +윿 ZEuGs +은 ZEuN +읁 ZEuNj +읂 ZEuNh +읃 ZEuD +을 ZEuL +읅 ZEuLg +읆 ZEuLm +읇 ZEuLb +읈 ZEuLs +읉 ZEuLt +읊 ZEuLp +읋 ZEuLh +음 ZEuM +읍 ZEuB +읎 ZEuBs +읏 ZEuS +읐 ZEuSs +응 ZEuNg +읒 ZEuJ +읓 ZEuC +읔 ZEuK +읕 ZEuT +읖 ZEuP +읗 ZEuH +의 ZUi +읙 ZUiG +읚 ZUiGg +읛 ZUiGs +읜 ZUiN +읝 ZUiNj +읞 ZUiNh +읟 ZUiD +읠 ZUiL +읡 ZUiLg +읢 ZUiLm +읣 ZUiLb +읤 ZUiLs +읥 ZUiLt +읦 ZUiLp +읧 ZUiLh +읨 ZUiM +읩 ZUiB +읪 ZUiBs +읫 ZUiS +읬 ZUiSs +읭 ZUiNg +읮 ZUiJ +읯 ZUiC +읰 ZUiK +읱 ZUiT +읲 ZUiP +읳 ZUiH +이 ZI +익 ZIG +읶 ZIGg +읷 ZIGs +인 ZIN +읹 ZINj +읺 ZINh +읻 ZID +일 ZIL +읽 ZILg +읾 ZILm +읿 ZILb +잀 ZILs +잁 ZILt +잂 ZILp +잃 ZILh +임 ZIM +입 ZIB +잆 ZIBs +잇 ZIS +있 ZISs +잉 ZINg +잊 ZIJ +잋 ZIC +잌 ZIK +잍 ZIT +잎 ZIP +잏 ZIH +자 JA +작 JAG +잒 JAGg +잓 JAGs +잔 JAN +잕 JANj +잖 JANh +잗 JAD +잘 JAL +잙 JALg +잚 JALm +잛 JALb +잜 JALs +잝 JALt +잞 JALp +잟 JALh +잠 JAM +잡 JAB +잢 JABs +잣 JAS +잤 JASs +장 JANg +잦 JAJ +잧 JAC +잨 JAK +잩 JAT +잪 JAP +잫 JAH +재 JAe +잭 JAeG +잮 JAeGg +잯 JAeGs +잰 JAeN +잱 JAeNj +잲 JAeNh +잳 JAeD +잴 JAeL +잵 JAeLg +잶 JAeLm +잷 JAeLb +잸 JAeLs +잹 JAeLt +잺 JAeLp +잻 JAeLh +잼 JAeM +잽 JAeB +잾 JAeBs +잿 JAeS +쟀 JAeSs +쟁 JAeNg +쟂 JAeJ +쟃 JAeC +쟄 JAeK +쟅 JAeT +쟆 JAeP +쟇 JAeH +쟈 JYa +쟉 JYaG +쟊 JYaGg +쟋 JYaGs +쟌 JYaN +쟍 JYaNj +쟎 JYaNh +쟏 JYaD +쟐 JYaL +쟑 JYaLg +쟒 JYaLm +쟓 JYaLb +쟔 JYaLs +쟕 JYaLt +쟖 JYaLp +쟗 JYaLh +쟘 JYaM +쟙 JYaB +쟚 JYaBs +쟛 JYaS +쟜 JYaSs +쟝 JYaNg +쟞 JYaJ +쟟 JYaC +쟠 JYaK +쟡 JYaT +쟢 JYaP +쟣 JYaH +쟤 JYae +쟥 JYaeG +쟦 JYaeGg +쟧 JYaeGs +쟨 JYaeN +쟩 JYaeNj +쟪 JYaeNh +쟫 JYaeD +쟬 JYaeL +쟭 JYaeLg +쟮 JYaeLm +쟯 JYaeLb +쟰 JYaeLs +쟱 JYaeLt +쟲 JYaeLp +쟳 JYaeLh +쟴 JYaeM +쟵 JYaeB +쟶 JYaeBs +쟷 JYaeS +쟸 JYaeSs +쟹 JYaeNg +쟺 JYaeJ +쟻 JYaeC +쟼 JYaeK +쟽 JYaeT +쟾 JYaeP +쟿 JYaeH +저 JEo +적 JEoG +젂 JEoGg +젃 JEoGs +전 JEoN +젅 JEoNj +젆 JEoNh +젇 JEoD +절 JEoL +젉 JEoLg +젊 JEoLm +젋 JEoLb +젌 JEoLs +젍 JEoLt +젎 JEoLp +젏 JEoLh +점 JEoM +접 JEoB +젒 JEoBs +젓 JEoS +젔 JEoSs +정 JEoNg +젖 JEoJ +젗 JEoC +젘 JEoK +젙 JEoT +젚 JEoP +젛 JEoH +제 JE +젝 JEG +젞 JEGg +젟 JEGs +젠 JEN +젡 JENj +젢 JENh +젣 JED +젤 JEL +젥 JELg +젦 JELm +젧 JELb +젨 JELs +젩 JELt +젪 JELp +젫 JELh +젬 JEM +젭 JEB +젮 JEBs +젯 JES +젰 JESs +젱 JENg +젲 JEJ +젳 JEC +젴 JEK +젵 JET +젶 JEP +젷 JEH +져 JYeo +젹 JYeoG +젺 JYeoGg +젻 JYeoGs +젼 JYeoN +젽 JYeoNj +젾 JYeoNh +젿 JYeoD +졀 JYeoL +졁 JYeoLg +졂 JYeoLm +졃 JYeoLb +졄 JYeoLs +졅 JYeoLt +졆 JYeoLp +졇 JYeoLh +졈 JYeoM +졉 JYeoB +졊 JYeoBs +졋 JYeoS +졌 JYeoSs +졍 JYeoNg +졎 JYeoJ +졏 JYeoC +졐 JYeoK +졑 JYeoT +졒 JYeoP +졓 JYeoH +졔 JYe +졕 JYeG +졖 JYeGg +졗 JYeGs +졘 JYeN +졙 JYeNj +졚 JYeNh +졛 JYeD +졜 JYeL +졝 JYeLg +졞 JYeLm +졟 JYeLb +졠 JYeLs +졡 JYeLt +졢 JYeLp +졣 JYeLh +졤 JYeM +졥 JYeB +졦 JYeBs +졧 JYeS +졨 JYeSs +졩 JYeNg +졪 JYeJ +졫 JYeC +졬 JYeK +졭 JYeT +졮 JYeP +졯 JYeH +조 JO +족 JOG +졲 JOGg +졳 JOGs +존 JON +졵 JONj +졶 JONh +졷 JOD +졸 JOL +졹 JOLg +졺 JOLm +졻 JOLb +졼 JOLs +졽 JOLt +졾 JOLp +졿 JOLh +좀 JOM +좁 JOB +좂 JOBs +좃 JOS +좄 JOSs +종 JONg +좆 JOJ +좇 JOC +좈 JOK +좉 JOT +좊 JOP +좋 JOH +좌 JWa +좍 JWaG +좎 JWaGg +좏 JWaGs +좐 JWaN +좑 JWaNj +좒 JWaNh +좓 JWaD +좔 JWaL +좕 JWaLg +좖 JWaLm +좗 JWaLb +좘 JWaLs +좙 JWaLt +좚 JWaLp +좛 JWaLh +좜 JWaM +좝 JWaB +좞 JWaBs +좟 JWaS +좠 JWaSs +좡 JWaNg +좢 JWaJ +좣 JWaC +좤 JWaK +좥 JWaT +좦 JWaP +좧 JWaH +좨 JWae +좩 JWaeG +좪 JWaeGg +좫 JWaeGs +좬 JWaeN +좭 JWaeNj +좮 JWaeNh +좯 JWaeD +좰 JWaeL +좱 JWaeLg +좲 JWaeLm +좳 JWaeLb +좴 JWaeLs +좵 JWaeLt +좶 JWaeLp +좷 JWaeLh +좸 JWaeM +좹 JWaeB +좺 JWaeBs +좻 JWaeS +좼 JWaeSs +좽 JWaeNg +좾 JWaeJ +좿 JWaeC +죀 JWaeK +죁 JWaeT +죂 JWaeP +죃 JWaeH +죄 JOe +죅 JOeG +죆 JOeGg +죇 JOeGs +죈 JOeN +죉 JOeNj +죊 JOeNh +죋 JOeD +죌 JOeL +죍 JOeLg +죎 JOeLm +죏 JOeLb +죐 JOeLs +죑 JOeLt +죒 JOeLp +죓 JOeLh +죔 JOeM +죕 JOeB +죖 JOeBs +죗 JOeS +죘 JOeSs +죙 JOeNg +죚 JOeJ +죛 JOeC +죜 JOeK +죝 JOeT +죞 JOeP +죟 JOeH +죠 JYo +죡 JYoG +죢 JYoGg +죣 JYoGs +죤 JYoN +죥 JYoNj +죦 JYoNh +죧 JYoD +죨 JYoL +죩 JYoLg +죪 JYoLm +죫 JYoLb +죬 JYoLs +죭 JYoLt +죮 JYoLp +죯 JYoLh +죰 JYoM +죱 JYoB +죲 JYoBs +죳 JYoS +죴 JYoSs +죵 JYoNg +죶 JYoJ +죷 JYoC +죸 JYoK +죹 JYoT +죺 JYoP +죻 JYoH +주 JU +죽 JUG +죾 JUGg +죿 JUGs +준 JUN +줁 JUNj +줂 JUNh +줃 JUD +줄 JUL +줅 JULg +줆 JULm +줇 JULb +줈 JULs +줉 JULt +줊 JULp +줋 JULh +줌 JUM +줍 JUB +줎 JUBs +줏 JUS +줐 JUSs +중 JUNg +줒 JUJ +줓 JUC +줔 JUK +줕 JUT +줖 JUP +줗 JUH +줘 JWo +줙 JWoG +줚 JWoGg +줛 JWoGs +줜 JWoN +줝 JWoNj +줞 JWoNh +줟 JWoD +줠 JWoL +줡 JWoLg +줢 JWoLm +줣 JWoLb +줤 JWoLs +줥 JWoLt +줦 JWoLp +줧 JWoLh +줨 JWoM +줩 JWoB +줪 JWoBs +줫 JWoS +줬 JWoSs +줭 JWoNg +줮 JWoJ +줯 JWoC +줰 JWoK +줱 JWoT +줲 JWoP +줳 JWoH +줴 JWe +줵 JWeG +줶 JWeGg +줷 JWeGs +줸 JWeN +줹 JWeNj +줺 JWeNh +줻 JWeD +줼 JWeL +줽 JWeLg +줾 JWeLm +줿 JWeLb +쥀 JWeLs +쥁 JWeLt +쥂 JWeLp +쥃 JWeLh +쥄 JWeM +쥅 JWeB +쥆 JWeBs +쥇 JWeS +쥈 JWeSs +쥉 JWeNg +쥊 JWeJ +쥋 JWeC +쥌 JWeK +쥍 JWeT +쥎 JWeP +쥏 JWeH +쥐 JWi +쥑 JWiG +쥒 JWiGg +쥓 JWiGs +쥔 JWiN +쥕 JWiNj +쥖 JWiNh +쥗 JWiD +쥘 JWiL +쥙 JWiLg +쥚 JWiLm +쥛 JWiLb +쥜 JWiLs +쥝 JWiLt +쥞 JWiLp +쥟 JWiLh +쥠 JWiM +쥡 JWiB +쥢 JWiBs +쥣 JWiS +쥤 JWiSs +쥥 JWiNg +쥦 JWiJ +쥧 JWiC +쥨 JWiK +쥩 JWiT +쥪 JWiP +쥫 JWiH +쥬 JYu +쥭 JYuG +쥮 JYuGg +쥯 JYuGs +쥰 JYuN +쥱 JYuNj +쥲 JYuNh +쥳 JYuD +쥴 JYuL +쥵 JYuLg +쥶 JYuLm +쥷 JYuLb +쥸 JYuLs +쥹 JYuLt +쥺 JYuLp +쥻 JYuLh +쥼 JYuM +쥽 JYuB +쥾 JYuBs +쥿 JYuS +즀 JYuSs +즁 JYuNg +즂 JYuJ +즃 JYuC +즄 JYuK +즅 JYuT +즆 JYuP +즇 JYuH +즈 JEu +즉 JEuG +즊 JEuGg +즋 JEuGs +즌 JEuN +즍 JEuNj +즎 JEuNh +즏 JEuD +즐 JEuL +즑 JEuLg +즒 JEuLm +즓 JEuLb +즔 JEuLs +즕 JEuLt +즖 JEuLp +즗 JEuLh +즘 JEuM +즙 JEuB +즚 JEuBs +즛 JEuS +즜 JEuSs +증 JEuNg +즞 JEuJ +즟 JEuC +즠 JEuK +즡 JEuT +즢 JEuP +즣 JEuH +즤 JUi +즥 JUiG +즦 JUiGg +즧 JUiGs +즨 JUiN +즩 JUiNj +즪 JUiNh +즫 JUiD +즬 JUiL +즭 JUiLg +즮 JUiLm +즯 JUiLb +즰 JUiLs +즱 JUiLt +즲 JUiLp +즳 JUiLh +즴 JUiM +즵 JUiB +즶 JUiBs +즷 JUiS +즸 JUiSs +즹 JUiNg +즺 JUiJ +즻 JUiC +즼 JUiK +즽 JUiT +즾 JUiP +즿 JUiH +지 JI +직 JIG +짂 JIGg +짃 JIGs +진 JIN +짅 JINj +짆 JINh +짇 JID +질 JIL +짉 JILg +짊 JILm +짋 JILb +짌 JILs +짍 JILt +짎 JILp +짏 JILh +짐 JIM +집 JIB +짒 JIBs +짓 JIS +짔 JISs +징 JINg +짖 JIJ +짗 JIC +짘 JIK +짙 JIT +짚 JIP +짛 JIH +짜 JjA +짝 JjAG +짞 JjAGg +짟 JjAGs +짠 JjAN +짡 JjANj +짢 JjANh +짣 JjAD +짤 JjAL +짥 JjALg +짦 JjALm +짧 JjALb +짨 JjALs +짩 JjALt +짪 JjALp +짫 JjALh +짬 JjAM +짭 JjAB +짮 JjABs +짯 JjAS +짰 JjASs +짱 JjANg +짲 JjAJ +짳 JjAC +짴 JjAK +짵 JjAT +짶 JjAP +짷 JjAH +째 JjAe +짹 JjAeG +짺 JjAeGg +짻 JjAeGs +짼 JjAeN +짽 JjAeNj +짾 JjAeNh +짿 JjAeD +쨀 JjAeL +쨁 JjAeLg +쨂 JjAeLm +쨃 JjAeLb +쨄 JjAeLs +쨅 JjAeLt +쨆 JjAeLp +쨇 JjAeLh +쨈 JjAeM +쨉 JjAeB +쨊 JjAeBs +쨋 JjAeS +쨌 JjAeSs +쨍 JjAeNg +쨎 JjAeJ +쨏 JjAeC +쨐 JjAeK +쨑 JjAeT +쨒 JjAeP +쨓 JjAeH +쨔 JjYa +쨕 JjYaG +쨖 JjYaGg +쨗 JjYaGs +쨘 JjYaN +쨙 JjYaNj +쨚 JjYaNh +쨛 JjYaD +쨜 JjYaL +쨝 JjYaLg +쨞 JjYaLm +쨟 JjYaLb +쨠 JjYaLs +쨡 JjYaLt +쨢 JjYaLp +쨣 JjYaLh +쨤 JjYaM +쨥 JjYaB +쨦 JjYaBs +쨧 JjYaS +쨨 JjYaSs +쨩 JjYaNg +쨪 JjYaJ +쨫 JjYaC +쨬 JjYaK +쨭 JjYaT +쨮 JjYaP +쨯 JjYaH +쨰 JjYae +쨱 JjYaeG +쨲 JjYaeGg +쨳 JjYaeGs +쨴 JjYaeN +쨵 JjYaeNj +쨶 JjYaeNh +쨷 JjYaeD +쨸 JjYaeL +쨹 JjYaeLg +쨺 JjYaeLm +쨻 JjYaeLb +쨼 JjYaeLs +쨽 JjYaeLt +쨾 JjYaeLp +쨿 JjYaeLh +쩀 JjYaeM +쩁 JjYaeB +쩂 JjYaeBs +쩃 JjYaeS +쩄 JjYaeSs +쩅 JjYaeNg +쩆 JjYaeJ +쩇 JjYaeC +쩈 JjYaeK +쩉 JjYaeT +쩊 JjYaeP +쩋 JjYaeH +쩌 JjEo +쩍 JjEoG +쩎 JjEoGg +쩏 JjEoGs +쩐 JjEoN +쩑 JjEoNj +쩒 JjEoNh +쩓 JjEoD +쩔 JjEoL +쩕 JjEoLg +쩖 JjEoLm +쩗 JjEoLb +쩘 JjEoLs +쩙 JjEoLt +쩚 JjEoLp +쩛 JjEoLh +쩜 JjEoM +쩝 JjEoB +쩞 JjEoBs +쩟 JjEoS +쩠 JjEoSs +쩡 JjEoNg +쩢 JjEoJ +쩣 JjEoC +쩤 JjEoK +쩥 JjEoT +쩦 JjEoP +쩧 JjEoH +쩨 JjE +쩩 JjEG +쩪 JjEGg +쩫 JjEGs +쩬 JjEN +쩭 JjENj +쩮 JjENh +쩯 JjED +쩰 JjEL +쩱 JjELg +쩲 JjELm +쩳 JjELb +쩴 JjELs +쩵 JjELt +쩶 JjELp +쩷 JjELh +쩸 JjEM +쩹 JjEB +쩺 JjEBs +쩻 JjES +쩼 JjESs +쩽 JjENg +쩾 JjEJ +쩿 JjEC +쪀 JjEK +쪁 JjET +쪂 JjEP +쪃 JjEH +쪄 JjYeo +쪅 JjYeoG +쪆 JjYeoGg +쪇 JjYeoGs +쪈 JjYeoN +쪉 JjYeoNj +쪊 JjYeoNh +쪋 JjYeoD +쪌 JjYeoL +쪍 JjYeoLg +쪎 JjYeoLm +쪏 JjYeoLb +쪐 JjYeoLs +쪑 JjYeoLt +쪒 JjYeoLp +쪓 JjYeoLh +쪔 JjYeoM +쪕 JjYeoB +쪖 JjYeoBs +쪗 JjYeoS +쪘 JjYeoSs +쪙 JjYeoNg +쪚 JjYeoJ +쪛 JjYeoC +쪜 JjYeoK +쪝 JjYeoT +쪞 JjYeoP +쪟 JjYeoH +쪠 JjYe +쪡 JjYeG +쪢 JjYeGg +쪣 JjYeGs +쪤 JjYeN +쪥 JjYeNj +쪦 JjYeNh +쪧 JjYeD +쪨 JjYeL +쪩 JjYeLg +쪪 JjYeLm +쪫 JjYeLb +쪬 JjYeLs +쪭 JjYeLt +쪮 JjYeLp +쪯 JjYeLh +쪰 JjYeM +쪱 JjYeB +쪲 JjYeBs +쪳 JjYeS +쪴 JjYeSs +쪵 JjYeNg +쪶 JjYeJ +쪷 JjYeC +쪸 JjYeK +쪹 JjYeT +쪺 JjYeP +쪻 JjYeH +쪼 JjO +쪽 JjOG +쪾 JjOGg +쪿 JjOGs +쫀 JjON +쫁 JjONj +쫂 JjONh +쫃 JjOD +쫄 JjOL +쫅 JjOLg +쫆 JjOLm +쫇 JjOLb +쫈 JjOLs +쫉 JjOLt +쫊 JjOLp +쫋 JjOLh +쫌 JjOM +쫍 JjOB +쫎 JjOBs +쫏 JjOS +쫐 JjOSs +쫑 JjONg +쫒 JjOJ +쫓 JjOC +쫔 JjOK +쫕 JjOT +쫖 JjOP +쫗 JjOH +쫘 JjWa +쫙 JjWaG +쫚 JjWaGg +쫛 JjWaGs +쫜 JjWaN +쫝 JjWaNj +쫞 JjWaNh +쫟 JjWaD +쫠 JjWaL +쫡 JjWaLg +쫢 JjWaLm +쫣 JjWaLb +쫤 JjWaLs +쫥 JjWaLt +쫦 JjWaLp +쫧 JjWaLh +쫨 JjWaM +쫩 JjWaB +쫪 JjWaBs +쫫 JjWaS +쫬 JjWaSs +쫭 JjWaNg +쫮 JjWaJ +쫯 JjWaC +쫰 JjWaK +쫱 JjWaT +쫲 JjWaP +쫳 JjWaH +쫴 JjWae +쫵 JjWaeG +쫶 JjWaeGg +쫷 JjWaeGs +쫸 JjWaeN +쫹 JjWaeNj +쫺 JjWaeNh +쫻 JjWaeD +쫼 JjWaeL +쫽 JjWaeLg +쫾 JjWaeLm +쫿 JjWaeLb +쬀 JjWaeLs +쬁 JjWaeLt +쬂 JjWaeLp +쬃 JjWaeLh +쬄 JjWaeM +쬅 JjWaeB +쬆 JjWaeBs +쬇 JjWaeS +쬈 JjWaeSs +쬉 JjWaeNg +쬊 JjWaeJ +쬋 JjWaeC +쬌 JjWaeK +쬍 JjWaeT +쬎 JjWaeP +쬏 JjWaeH +쬐 JjOe +쬑 JjOeG +쬒 JjOeGg +쬓 JjOeGs +쬔 JjOeN +쬕 JjOeNj +쬖 JjOeNh +쬗 JjOeD +쬘 JjOeL +쬙 JjOeLg +쬚 JjOeLm +쬛 JjOeLb +쬜 JjOeLs +쬝 JjOeLt +쬞 JjOeLp +쬟 JjOeLh +쬠 JjOeM +쬡 JjOeB +쬢 JjOeBs +쬣 JjOeS +쬤 JjOeSs +쬥 JjOeNg +쬦 JjOeJ +쬧 JjOeC +쬨 JjOeK +쬩 JjOeT +쬪 JjOeP +쬫 JjOeH +쬬 JjYo +쬭 JjYoG +쬮 JjYoGg +쬯 JjYoGs +쬰 JjYoN +쬱 JjYoNj +쬲 JjYoNh +쬳 JjYoD +쬴 JjYoL +쬵 JjYoLg +쬶 JjYoLm +쬷 JjYoLb +쬸 JjYoLs +쬹 JjYoLt +쬺 JjYoLp +쬻 JjYoLh +쬼 JjYoM +쬽 JjYoB +쬾 JjYoBs +쬿 JjYoS +쭀 JjYoSs +쭁 JjYoNg +쭂 JjYoJ +쭃 JjYoC +쭄 JjYoK +쭅 JjYoT +쭆 JjYoP +쭇 JjYoH +쭈 JjU +쭉 JjUG +쭊 JjUGg +쭋 JjUGs +쭌 JjUN +쭍 JjUNj +쭎 JjUNh +쭏 JjUD +쭐 JjUL +쭑 JjULg +쭒 JjULm +쭓 JjULb +쭔 JjULs +쭕 JjULt +쭖 JjULp +쭗 JjULh +쭘 JjUM +쭙 JjUB +쭚 JjUBs +쭛 JjUS +쭜 JjUSs +쭝 JjUNg +쭞 JjUJ +쭟 JjUC +쭠 JjUK +쭡 JjUT +쭢 JjUP +쭣 JjUH +쭤 JjWo +쭥 JjWoG +쭦 JjWoGg +쭧 JjWoGs +쭨 JjWoN +쭩 JjWoNj +쭪 JjWoNh +쭫 JjWoD +쭬 JjWoL +쭭 JjWoLg +쭮 JjWoLm +쭯 JjWoLb +쭰 JjWoLs +쭱 JjWoLt +쭲 JjWoLp +쭳 JjWoLh +쭴 JjWoM +쭵 JjWoB +쭶 JjWoBs +쭷 JjWoS +쭸 JjWoSs +쭹 JjWoNg +쭺 JjWoJ +쭻 JjWoC +쭼 JjWoK +쭽 JjWoT +쭾 JjWoP +쭿 JjWoH +쮀 JjWe +쮁 JjWeG +쮂 JjWeGg +쮃 JjWeGs +쮄 JjWeN +쮅 JjWeNj +쮆 JjWeNh +쮇 JjWeD +쮈 JjWeL +쮉 JjWeLg +쮊 JjWeLm +쮋 JjWeLb +쮌 JjWeLs +쮍 JjWeLt +쮎 JjWeLp +쮏 JjWeLh +쮐 JjWeM +쮑 JjWeB +쮒 JjWeBs +쮓 JjWeS +쮔 JjWeSs +쮕 JjWeNg +쮖 JjWeJ +쮗 JjWeC +쮘 JjWeK +쮙 JjWeT +쮚 JjWeP +쮛 JjWeH +쮜 JjWi +쮝 JjWiG +쮞 JjWiGg +쮟 JjWiGs +쮠 JjWiN +쮡 JjWiNj +쮢 JjWiNh +쮣 JjWiD +쮤 JjWiL +쮥 JjWiLg +쮦 JjWiLm +쮧 JjWiLb +쮨 JjWiLs +쮩 JjWiLt +쮪 JjWiLp +쮫 JjWiLh +쮬 JjWiM +쮭 JjWiB +쮮 JjWiBs +쮯 JjWiS +쮰 JjWiSs +쮱 JjWiNg +쮲 JjWiJ +쮳 JjWiC +쮴 JjWiK +쮵 JjWiT +쮶 JjWiP +쮷 JjWiH +쮸 JjYu +쮹 JjYuG +쮺 JjYuGg +쮻 JjYuGs +쮼 JjYuN +쮽 JjYuNj +쮾 JjYuNh +쮿 JjYuD +쯀 JjYuL +쯁 JjYuLg +쯂 JjYuLm +쯃 JjYuLb +쯄 JjYuLs +쯅 JjYuLt +쯆 JjYuLp +쯇 JjYuLh +쯈 JjYuM +쯉 JjYuB +쯊 JjYuBs +쯋 JjYuS +쯌 JjYuSs +쯍 JjYuNg +쯎 JjYuJ +쯏 JjYuC +쯐 JjYuK +쯑 JjYuT +쯒 JjYuP +쯓 JjYuH +쯔 JjEu +쯕 JjEuG +쯖 JjEuGg +쯗 JjEuGs +쯘 JjEuN +쯙 JjEuNj +쯚 JjEuNh +쯛 JjEuD +쯜 JjEuL +쯝 JjEuLg +쯞 JjEuLm +쯟 JjEuLb +쯠 JjEuLs +쯡 JjEuLt +쯢 JjEuLp +쯣 JjEuLh +쯤 JjEuM +쯥 JjEuB +쯦 JjEuBs +쯧 JjEuS +쯨 JjEuSs +쯩 JjEuNg +쯪 JjEuJ +쯫 JjEuC +쯬 JjEuK +쯭 JjEuT +쯮 JjEuP +쯯 JjEuH +쯰 JjUi +쯱 JjUiG +쯲 JjUiGg +쯳 JjUiGs +쯴 JjUiN +쯵 JjUiNj +쯶 JjUiNh +쯷 JjUiD +쯸 JjUiL +쯹 JjUiLg +쯺 JjUiLm +쯻 JjUiLb +쯼 JjUiLs +쯽 JjUiLt +쯾 JjUiLp +쯿 JjUiLh +찀 JjUiM +찁 JjUiB +찂 JjUiBs +찃 JjUiS +찄 JjUiSs +찅 JjUiNg +찆 JjUiJ +찇 JjUiC +찈 JjUiK +찉 JjUiT +찊 JjUiP +찋 JjUiH +찌 JjI +찍 JjIG +찎 JjIGg +찏 JjIGs +찐 JjIN +찑 JjINj +찒 JjINh +찓 JjID +찔 JjIL +찕 JjILg +찖 JjILm +찗 JjILb +찘 JjILs +찙 JjILt +찚 JjILp +찛 JjILh +찜 JjIM +찝 JjIB +찞 JjIBs +찟 JjIS +찠 JjISs +찡 JjINg +찢 JjIJ +찣 JjIC +찤 JjIK +찥 JjIT +찦 JjIP +찧 JjIH +차 CA +착 CAG +찪 CAGg +찫 CAGs +찬 CAN +찭 CANj +찮 CANh +찯 CAD +찰 CAL +찱 CALg +찲 CALm +찳 CALb +찴 CALs +찵 CALt +찶 CALp +찷 CALh +참 CAM +찹 CAB +찺 CABs +찻 CAS +찼 CASs +창 CANg +찾 CAJ +찿 CAC +챀 CAK +챁 CAT +챂 CAP +챃 CAH +채 CAe +책 CAeG +챆 CAeGg +챇 CAeGs +챈 CAeN +챉 CAeNj +챊 CAeNh +챋 CAeD +챌 CAeL +챍 CAeLg +챎 CAeLm +챏 CAeLb +챐 CAeLs +챑 CAeLt +챒 CAeLp +챓 CAeLh +챔 CAeM +챕 CAeB +챖 CAeBs +챗 CAeS +챘 CAeSs +챙 CAeNg +챚 CAeJ +챛 CAeC +챜 CAeK +챝 CAeT +챞 CAeP +챟 CAeH +챠 CYa +챡 CYaG +챢 CYaGg +챣 CYaGs +챤 CYaN +챥 CYaNj +챦 CYaNh +챧 CYaD +챨 CYaL +챩 CYaLg +챪 CYaLm +챫 CYaLb +챬 CYaLs +챭 CYaLt +챮 CYaLp +챯 CYaLh +챰 CYaM +챱 CYaB +챲 CYaBs +챳 CYaS +챴 CYaSs +챵 CYaNg +챶 CYaJ +챷 CYaC +챸 CYaK +챹 CYaT +챺 CYaP +챻 CYaH +챼 CYae +챽 CYaeG +챾 CYaeGg +챿 CYaeGs +첀 CYaeN +첁 CYaeNj +첂 CYaeNh +첃 CYaeD +첄 CYaeL +첅 CYaeLg +첆 CYaeLm +첇 CYaeLb +첈 CYaeLs +첉 CYaeLt +첊 CYaeLp +첋 CYaeLh +첌 CYaeM +첍 CYaeB +첎 CYaeBs +첏 CYaeS +첐 CYaeSs +첑 CYaeNg +첒 CYaeJ +첓 CYaeC +첔 CYaeK +첕 CYaeT +첖 CYaeP +첗 CYaeH +처 CEo +척 CEoG +첚 CEoGg +첛 CEoGs +천 CEoN +첝 CEoNj +첞 CEoNh +첟 CEoD +철 CEoL +첡 CEoLg +첢 CEoLm +첣 CEoLb +첤 CEoLs +첥 CEoLt +첦 CEoLp +첧 CEoLh +첨 CEoM +첩 CEoB +첪 CEoBs +첫 CEoS +첬 CEoSs +청 CEoNg +첮 CEoJ +첯 CEoC +첰 CEoK +첱 CEoT +첲 CEoP +첳 CEoH +체 CE +첵 CEG +첶 CEGg +첷 CEGs +첸 CEN +첹 CENj +첺 CENh +첻 CED +첼 CEL +첽 CELg +첾 CELm +첿 CELb +쳀 CELs +쳁 CELt +쳂 CELp +쳃 CELh +쳄 CEM +쳅 CEB +쳆 CEBs +쳇 CES +쳈 CESs +쳉 CENg +쳊 CEJ +쳋 CEC +쳌 CEK +쳍 CET +쳎 CEP +쳏 CEH +쳐 CYeo +쳑 CYeoG +쳒 CYeoGg +쳓 CYeoGs +쳔 CYeoN +쳕 CYeoNj +쳖 CYeoNh +쳗 CYeoD +쳘 CYeoL +쳙 CYeoLg +쳚 CYeoLm +쳛 CYeoLb +쳜 CYeoLs +쳝 CYeoLt +쳞 CYeoLp +쳟 CYeoLh +쳠 CYeoM +쳡 CYeoB +쳢 CYeoBs +쳣 CYeoS +쳤 CYeoSs +쳥 CYeoNg +쳦 CYeoJ +쳧 CYeoC +쳨 CYeoK +쳩 CYeoT +쳪 CYeoP +쳫 CYeoH +쳬 CYe +쳭 CYeG +쳮 CYeGg +쳯 CYeGs +쳰 CYeN +쳱 CYeNj +쳲 CYeNh +쳳 CYeD +쳴 CYeL +쳵 CYeLg +쳶 CYeLm +쳷 CYeLb +쳸 CYeLs +쳹 CYeLt +쳺 CYeLp +쳻 CYeLh +쳼 CYeM +쳽 CYeB +쳾 CYeBs +쳿 CYeS +촀 CYeSs +촁 CYeNg +촂 CYeJ +촃 CYeC +촄 CYeK +촅 CYeT +촆 CYeP +촇 CYeH +초 CO +촉 COG +촊 COGg +촋 COGs +촌 CON +촍 CONj +촎 CONh +촏 COD +촐 COL +촑 COLg +촒 COLm +촓 COLb +촔 COLs +촕 COLt +촖 COLp +촗 COLh +촘 COM +촙 COB +촚 COBs +촛 COS +촜 COSs +총 CONg +촞 COJ +촟 COC +촠 COK +촡 COT +촢 COP +촣 COH +촤 CWa +촥 CWaG +촦 CWaGg +촧 CWaGs +촨 CWaN +촩 CWaNj +촪 CWaNh +촫 CWaD +촬 CWaL +촭 CWaLg +촮 CWaLm +촯 CWaLb +촰 CWaLs +촱 CWaLt +촲 CWaLp +촳 CWaLh +촴 CWaM +촵 CWaB +촶 CWaBs +촷 CWaS +촸 CWaSs +촹 CWaNg +촺 CWaJ +촻 CWaC +촼 CWaK +촽 CWaT +촾 CWaP +촿 CWaH +쵀 CWae +쵁 CWaeG +쵂 CWaeGg +쵃 CWaeGs +쵄 CWaeN +쵅 CWaeNj +쵆 CWaeNh +쵇 CWaeD +쵈 CWaeL +쵉 CWaeLg +쵊 CWaeLm +쵋 CWaeLb +쵌 CWaeLs +쵍 CWaeLt +쵎 CWaeLp +쵏 CWaeLh +쵐 CWaeM +쵑 CWaeB +쵒 CWaeBs +쵓 CWaeS +쵔 CWaeSs +쵕 CWaeNg +쵖 CWaeJ +쵗 CWaeC +쵘 CWaeK +쵙 CWaeT +쵚 CWaeP +쵛 CWaeH +최 COe +쵝 COeG +쵞 COeGg +쵟 COeGs +쵠 COeN +쵡 COeNj +쵢 COeNh +쵣 COeD +쵤 COeL +쵥 COeLg +쵦 COeLm +쵧 COeLb +쵨 COeLs +쵩 COeLt +쵪 COeLp +쵫 COeLh +쵬 COeM +쵭 COeB +쵮 COeBs +쵯 COeS +쵰 COeSs +쵱 COeNg +쵲 COeJ +쵳 COeC +쵴 COeK +쵵 COeT +쵶 COeP +쵷 COeH +쵸 CYo +쵹 CYoG +쵺 CYoGg +쵻 CYoGs +쵼 CYoN +쵽 CYoNj +쵾 CYoNh +쵿 CYoD +춀 CYoL +춁 CYoLg +춂 CYoLm +춃 CYoLb +춄 CYoLs +춅 CYoLt +춆 CYoLp +춇 CYoLh +춈 CYoM +춉 CYoB +춊 CYoBs +춋 CYoS +춌 CYoSs +춍 CYoNg +춎 CYoJ +춏 CYoC +춐 CYoK +춑 CYoT +춒 CYoP +춓 CYoH +추 CU +축 CUG +춖 CUGg +춗 CUGs +춘 CUN +춙 CUNj +춚 CUNh +춛 CUD +출 CUL +춝 CULg +춞 CULm +춟 CULb +춠 CULs +춡 CULt +춢 CULp +춣 CULh +춤 CUM +춥 CUB +춦 CUBs +춧 CUS +춨 CUSs +충 CUNg +춪 CUJ +춫 CUC +춬 CUK +춭 CUT +춮 CUP +춯 CUH +춰 CWo +춱 CWoG +춲 CWoGg +춳 CWoGs +춴 CWoN +춵 CWoNj +춶 CWoNh +춷 CWoD +춸 CWoL +춹 CWoLg +춺 CWoLm +춻 CWoLb +춼 CWoLs +춽 CWoLt +춾 CWoLp +춿 CWoLh +췀 CWoM +췁 CWoB +췂 CWoBs +췃 CWoS +췄 CWoSs +췅 CWoNg +췆 CWoJ +췇 CWoC +췈 CWoK +췉 CWoT +췊 CWoP +췋 CWoH +췌 CWe +췍 CWeG +췎 CWeGg +췏 CWeGs +췐 CWeN +췑 CWeNj +췒 CWeNh +췓 CWeD +췔 CWeL +췕 CWeLg +췖 CWeLm +췗 CWeLb +췘 CWeLs +췙 CWeLt +췚 CWeLp +췛 CWeLh +췜 CWeM +췝 CWeB +췞 CWeBs +췟 CWeS +췠 CWeSs +췡 CWeNg +췢 CWeJ +췣 CWeC +췤 CWeK +췥 CWeT +췦 CWeP +췧 CWeH +취 CWi +췩 CWiG +췪 CWiGg +췫 CWiGs +췬 CWiN +췭 CWiNj +췮 CWiNh +췯 CWiD +췰 CWiL +췱 CWiLg +췲 CWiLm +췳 CWiLb +췴 CWiLs +췵 CWiLt +췶 CWiLp +췷 CWiLh +췸 CWiM +췹 CWiB +췺 CWiBs +췻 CWiS +췼 CWiSs +췽 CWiNg +췾 CWiJ +췿 CWiC +츀 CWiK +츁 CWiT +츂 CWiP +츃 CWiH +츄 CYu +츅 CYuG +츆 CYuGg +츇 CYuGs +츈 CYuN +츉 CYuNj +츊 CYuNh +츋 CYuD +츌 CYuL +츍 CYuLg +츎 CYuLm +츏 CYuLb +츐 CYuLs +츑 CYuLt +츒 CYuLp +츓 CYuLh +츔 CYuM +츕 CYuB +츖 CYuBs +츗 CYuS +츘 CYuSs +츙 CYuNg +츚 CYuJ +츛 CYuC +츜 CYuK +츝 CYuT +츞 CYuP +츟 CYuH +츠 CEu +측 CEuG +츢 CEuGg +츣 CEuGs +츤 CEuN +츥 CEuNj +츦 CEuNh +츧 CEuD +츨 CEuL +츩 CEuLg +츪 CEuLm +츫 CEuLb +츬 CEuLs +츭 CEuLt +츮 CEuLp +츯 CEuLh +츰 CEuM +츱 CEuB +츲 CEuBs +츳 CEuS +츴 CEuSs +층 CEuNg +츶 CEuJ +츷 CEuC +츸 CEuK +츹 CEuT +츺 CEuP +츻 CEuH +츼 CUi +츽 CUiG +츾 CUiGg +츿 CUiGs +칀 CUiN +칁 CUiNj +칂 CUiNh +칃 CUiD +칄 CUiL +칅 CUiLg +칆 CUiLm +칇 CUiLb +칈 CUiLs +칉 CUiLt +칊 CUiLp +칋 CUiLh +칌 CUiM +칍 CUiB +칎 CUiBs +칏 CUiS +칐 CUiSs +칑 CUiNg +칒 CUiJ +칓 CUiC +칔 CUiK +칕 CUiT +칖 CUiP +칗 CUiH +치 CI +칙 CIG +칚 CIGg +칛 CIGs +친 CIN +칝 CINj +칞 CINh +칟 CID +칠 CIL +칡 CILg +칢 CILm +칣 CILb +칤 CILs +칥 CILt +칦 CILp +칧 CILh +침 CIM +칩 CIB +칪 CIBs +칫 CIS +칬 CISs +칭 CINg +칮 CIJ +칯 CIC +칰 CIK +칱 CIT +칲 CIP +칳 CIH +카 KA +칵 KAG +칶 KAGg +칷 KAGs +칸 KAN +칹 KANj +칺 KANh +칻 KAD +칼 KAL +칽 KALg +칾 KALm +칿 KALb +캀 KALs +캁 KALt +캂 KALp +캃 KALh +캄 KAM +캅 KAB +캆 KABs +캇 KAS +캈 KASs +캉 KANg +캊 KAJ +캋 KAC +캌 KAK +캍 KAT +캎 KAP +캏 KAH +캐 KAe +캑 KAeG +캒 KAeGg +캓 KAeGs +캔 KAeN +캕 KAeNj +캖 KAeNh +캗 KAeD +캘 KAeL +캙 KAeLg +캚 KAeLm +캛 KAeLb +캜 KAeLs +캝 KAeLt +캞 KAeLp +캟 KAeLh +캠 KAeM +캡 KAeB +캢 KAeBs +캣 KAeS +캤 KAeSs +캥 KAeNg +캦 KAeJ +캧 KAeC +캨 KAeK +캩 KAeT +캪 KAeP +캫 KAeH +캬 KYa +캭 KYaG +캮 KYaGg +캯 KYaGs +캰 KYaN +캱 KYaNj +캲 KYaNh +캳 KYaD +캴 KYaL +캵 KYaLg +캶 KYaLm +캷 KYaLb +캸 KYaLs +캹 KYaLt +캺 KYaLp +캻 KYaLh +캼 KYaM +캽 KYaB +캾 KYaBs +캿 KYaS +컀 KYaSs +컁 KYaNg +컂 KYaJ +컃 KYaC +컄 KYaK +컅 KYaT +컆 KYaP +컇 KYaH +컈 KYae +컉 KYaeG +컊 KYaeGg +컋 KYaeGs +컌 KYaeN +컍 KYaeNj +컎 KYaeNh +컏 KYaeD +컐 KYaeL +컑 KYaeLg +컒 KYaeLm +컓 KYaeLb +컔 KYaeLs +컕 KYaeLt +컖 KYaeLp +컗 KYaeLh +컘 KYaeM +컙 KYaeB +컚 KYaeBs +컛 KYaeS +컜 KYaeSs +컝 KYaeNg +컞 KYaeJ +컟 KYaeC +컠 KYaeK +컡 KYaeT +컢 KYaeP +컣 KYaeH +커 KEo +컥 KEoG +컦 KEoGg +컧 KEoGs +컨 KEoN +컩 KEoNj +컪 KEoNh +컫 KEoD +컬 KEoL +컭 KEoLg +컮 KEoLm +컯 KEoLb +컰 KEoLs +컱 KEoLt +컲 KEoLp +컳 KEoLh +컴 KEoM +컵 KEoB +컶 KEoBs +컷 KEoS +컸 KEoSs +컹 KEoNg +컺 KEoJ +컻 KEoC +컼 KEoK +컽 KEoT +컾 KEoP +컿 KEoH +케 KE +켁 KEG +켂 KEGg +켃 KEGs +켄 KEN +켅 KENj +켆 KENh +켇 KED +켈 KEL +켉 KELg +켊 KELm +켋 KELb +켌 KELs +켍 KELt +켎 KELp +켏 KELh +켐 KEM +켑 KEB +켒 KEBs +켓 KES +켔 KESs +켕 KENg +켖 KEJ +켗 KEC +켘 KEK +켙 KET +켚 KEP +켛 KEH +켜 KYeo +켝 KYeoG +켞 KYeoGg +켟 KYeoGs +켠 KYeoN +켡 KYeoNj +켢 KYeoNh +켣 KYeoD +켤 KYeoL +켥 KYeoLg +켦 KYeoLm +켧 KYeoLb +켨 KYeoLs +켩 KYeoLt +켪 KYeoLp +켫 KYeoLh +켬 KYeoM +켭 KYeoB +켮 KYeoBs +켯 KYeoS +켰 KYeoSs +켱 KYeoNg +켲 KYeoJ +켳 KYeoC +켴 KYeoK +켵 KYeoT +켶 KYeoP +켷 KYeoH +켸 KYe +켹 KYeG +켺 KYeGg +켻 KYeGs +켼 KYeN +켽 KYeNj +켾 KYeNh +켿 KYeD +콀 KYeL +콁 KYeLg +콂 KYeLm +콃 KYeLb +콄 KYeLs +콅 KYeLt +콆 KYeLp +콇 KYeLh +콈 KYeM +콉 KYeB +콊 KYeBs +콋 KYeS +콌 KYeSs +콍 KYeNg +콎 KYeJ +콏 KYeC +콐 KYeK +콑 KYeT +콒 KYeP +콓 KYeH +코 KO +콕 KOG +콖 KOGg +콗 KOGs +콘 KON +콙 KONj +콚 KONh +콛 KOD +콜 KOL +콝 KOLg +콞 KOLm +콟 KOLb +콠 KOLs +콡 KOLt +콢 KOLp +콣 KOLh +콤 KOM +콥 KOB +콦 KOBs +콧 KOS +콨 KOSs +콩 KONg +콪 KOJ +콫 KOC +콬 KOK +콭 KOT +콮 KOP +콯 KOH +콰 KWa +콱 KWaG +콲 KWaGg +콳 KWaGs +콴 KWaN +콵 KWaNj +콶 KWaNh +콷 KWaD +콸 KWaL +콹 KWaLg +콺 KWaLm +콻 KWaLb +콼 KWaLs +콽 KWaLt +콾 KWaLp +콿 KWaLh +쾀 KWaM +쾁 KWaB +쾂 KWaBs +쾃 KWaS +쾄 KWaSs +쾅 KWaNg +쾆 KWaJ +쾇 KWaC +쾈 KWaK +쾉 KWaT +쾊 KWaP +쾋 KWaH +쾌 KWae +쾍 KWaeG +쾎 KWaeGg +쾏 KWaeGs +쾐 KWaeN +쾑 KWaeNj +쾒 KWaeNh +쾓 KWaeD +쾔 KWaeL +쾕 KWaeLg +쾖 KWaeLm +쾗 KWaeLb +쾘 KWaeLs +쾙 KWaeLt +쾚 KWaeLp +쾛 KWaeLh +쾜 KWaeM +쾝 KWaeB +쾞 KWaeBs +쾟 KWaeS +쾠 KWaeSs +쾡 KWaeNg +쾢 KWaeJ +쾣 KWaeC +쾤 KWaeK +쾥 KWaeT +쾦 KWaeP +쾧 KWaeH +쾨 KOe +쾩 KOeG +쾪 KOeGg +쾫 KOeGs +쾬 KOeN +쾭 KOeNj +쾮 KOeNh +쾯 KOeD +쾰 KOeL +쾱 KOeLg +쾲 KOeLm +쾳 KOeLb +쾴 KOeLs +쾵 KOeLt +쾶 KOeLp +쾷 KOeLh +쾸 KOeM +쾹 KOeB +쾺 KOeBs +쾻 KOeS +쾼 KOeSs +쾽 KOeNg +쾾 KOeJ +쾿 KOeC +쿀 KOeK +쿁 KOeT +쿂 KOeP +쿃 KOeH +쿄 KYo +쿅 KYoG +쿆 KYoGg +쿇 KYoGs +쿈 KYoN +쿉 KYoNj +쿊 KYoNh +쿋 KYoD +쿌 KYoL +쿍 KYoLg +쿎 KYoLm +쿏 KYoLb +쿐 KYoLs +쿑 KYoLt +쿒 KYoLp +쿓 KYoLh +쿔 KYoM +쿕 KYoB +쿖 KYoBs +쿗 KYoS +쿘 KYoSs +쿙 KYoNg +쿚 KYoJ +쿛 KYoC +쿜 KYoK +쿝 KYoT +쿞 KYoP +쿟 KYoH +쿠 KU +쿡 KUG +쿢 KUGg +쿣 KUGs +쿤 KUN +쿥 KUNj +쿦 KUNh +쿧 KUD +쿨 KUL +쿩 KULg +쿪 KULm +쿫 KULb +쿬 KULs +쿭 KULt +쿮 KULp +쿯 KULh +쿰 KUM +쿱 KUB +쿲 KUBs +쿳 KUS +쿴 KUSs +쿵 KUNg +쿶 KUJ +쿷 KUC +쿸 KUK +쿹 KUT +쿺 KUP +쿻 KUH +쿼 KWo +쿽 KWoG +쿾 KWoGg +쿿 KWoGs +퀀 KWoN +퀁 KWoNj +퀂 KWoNh +퀃 KWoD +퀄 KWoL +퀅 KWoLg +퀆 KWoLm +퀇 KWoLb +퀈 KWoLs +퀉 KWoLt +퀊 KWoLp +퀋 KWoLh +퀌 KWoM +퀍 KWoB +퀎 KWoBs +퀏 KWoS +퀐 KWoSs +퀑 KWoNg +퀒 KWoJ +퀓 KWoC +퀔 KWoK +퀕 KWoT +퀖 KWoP +퀗 KWoH +퀘 KWe +퀙 KWeG +퀚 KWeGg +퀛 KWeGs +퀜 KWeN +퀝 KWeNj +퀞 KWeNh +퀟 KWeD +퀠 KWeL +퀡 KWeLg +퀢 KWeLm +퀣 KWeLb +퀤 KWeLs +퀥 KWeLt +퀦 KWeLp +퀧 KWeLh +퀨 KWeM +퀩 KWeB +퀪 KWeBs +퀫 KWeS +퀬 KWeSs +퀭 KWeNg +퀮 KWeJ +퀯 KWeC +퀰 KWeK +퀱 KWeT +퀲 KWeP +퀳 KWeH +퀴 KWi +퀵 KWiG +퀶 KWiGg +퀷 KWiGs +퀸 KWiN +퀹 KWiNj +퀺 KWiNh +퀻 KWiD +퀼 KWiL +퀽 KWiLg +퀾 KWiLm +퀿 KWiLb +큀 KWiLs +큁 KWiLt +큂 KWiLp +큃 KWiLh +큄 KWiM +큅 KWiB +큆 KWiBs +큇 KWiS +큈 KWiSs +큉 KWiNg +큊 KWiJ +큋 KWiC +큌 KWiK +큍 KWiT +큎 KWiP +큏 KWiH +큐 KYu +큑 KYuG +큒 KYuGg +큓 KYuGs +큔 KYuN +큕 KYuNj +큖 KYuNh +큗 KYuD +큘 KYuL +큙 KYuLg +큚 KYuLm +큛 KYuLb +큜 KYuLs +큝 KYuLt +큞 KYuLp +큟 KYuLh +큠 KYuM +큡 KYuB +큢 KYuBs +큣 KYuS +큤 KYuSs +큥 KYuNg +큦 KYuJ +큧 KYuC +큨 KYuK +큩 KYuT +큪 KYuP +큫 KYuH +크 KEu +큭 KEuG +큮 KEuGg +큯 KEuGs +큰 KEuN +큱 KEuNj +큲 KEuNh +큳 KEuD +클 KEuL +큵 KEuLg +큶 KEuLm +큷 KEuLb +큸 KEuLs +큹 KEuLt +큺 KEuLp +큻 KEuLh +큼 KEuM +큽 KEuB +큾 KEuBs +큿 KEuS +킀 KEuSs +킁 KEuNg +킂 KEuJ +킃 KEuC +킄 KEuK +킅 KEuT +킆 KEuP +킇 KEuH +킈 KUi +킉 KUiG +킊 KUiGg +킋 KUiGs +킌 KUiN +킍 KUiNj +킎 KUiNh +킏 KUiD +킐 KUiL +킑 KUiLg +킒 KUiLm +킓 KUiLb +킔 KUiLs +킕 KUiLt +킖 KUiLp +킗 KUiLh +킘 KUiM +킙 KUiB +킚 KUiBs +킛 KUiS +킜 KUiSs +킝 KUiNg +킞 KUiJ +킟 KUiC +킠 KUiK +킡 KUiT +킢 KUiP +킣 KUiH +키 KI +킥 KIG +킦 KIGg +킧 KIGs +킨 KIN +킩 KINj +킪 KINh +킫 KID +킬 KIL +킭 KILg +킮 KILm +킯 KILb +킰 KILs +킱 KILt +킲 KILp +킳 KILh +킴 KIM +킵 KIB +킶 KIBs +킷 KIS +킸 KISs +킹 KINg +킺 KIJ +킻 KIC +킼 KIK +킽 KIT +킾 KIP +킿 KIH +타 TA +탁 TAG +탂 TAGg +탃 TAGs +탄 TAN +탅 TANj +탆 TANh +탇 TAD +탈 TAL +탉 TALg +탊 TALm +탋 TALb +탌 TALs +탍 TALt +탎 TALp +탏 TALh +탐 TAM +탑 TAB +탒 TABs +탓 TAS +탔 TASs +탕 TANg +탖 TAJ +탗 TAC +탘 TAK +탙 TAT +탚 TAP +탛 TAH +태 TAe +택 TAeG +탞 TAeGg +탟 TAeGs +탠 TAeN +탡 TAeNj +탢 TAeNh +탣 TAeD +탤 TAeL +탥 TAeLg +탦 TAeLm +탧 TAeLb +탨 TAeLs +탩 TAeLt +탪 TAeLp +탫 TAeLh +탬 TAeM +탭 TAeB +탮 TAeBs +탯 TAeS +탰 TAeSs +탱 TAeNg +탲 TAeJ +탳 TAeC +탴 TAeK +탵 TAeT +탶 TAeP +탷 TAeH +탸 TYa +탹 TYaG +탺 TYaGg +탻 TYaGs +탼 TYaN +탽 TYaNj +탾 TYaNh +탿 TYaD +턀 TYaL +턁 TYaLg +턂 TYaLm +턃 TYaLb +턄 TYaLs +턅 TYaLt +턆 TYaLp +턇 TYaLh +턈 TYaM +턉 TYaB +턊 TYaBs +턋 TYaS +턌 TYaSs +턍 TYaNg +턎 TYaJ +턏 TYaC +턐 TYaK +턑 TYaT +턒 TYaP +턓 TYaH +턔 TYae +턕 TYaeG +턖 TYaeGg +턗 TYaeGs +턘 TYaeN +턙 TYaeNj +턚 TYaeNh +턛 TYaeD +턜 TYaeL +턝 TYaeLg +턞 TYaeLm +턟 TYaeLb +턠 TYaeLs +턡 TYaeLt +턢 TYaeLp +턣 TYaeLh +턤 TYaeM +턥 TYaeB +턦 TYaeBs +턧 TYaeS +턨 TYaeSs +턩 TYaeNg +턪 TYaeJ +턫 TYaeC +턬 TYaeK +턭 TYaeT +턮 TYaeP +턯 TYaeH +터 TEo +턱 TEoG +턲 TEoGg +턳 TEoGs +턴 TEoN +턵 TEoNj +턶 TEoNh +턷 TEoD +털 TEoL +턹 TEoLg +턺 TEoLm +턻 TEoLb +턼 TEoLs +턽 TEoLt +턾 TEoLp +턿 TEoLh +텀 TEoM +텁 TEoB +텂 TEoBs +텃 TEoS +텄 TEoSs +텅 TEoNg +텆 TEoJ +텇 TEoC +텈 TEoK +텉 TEoT +텊 TEoP +텋 TEoH +테 TE +텍 TEG +텎 TEGg +텏 TEGs +텐 TEN +텑 TENj +텒 TENh +텓 TED +텔 TEL +텕 TELg +텖 TELm +텗 TELb +텘 TELs +텙 TELt +텚 TELp +텛 TELh +템 TEM +텝 TEB +텞 TEBs +텟 TES +텠 TESs +텡 TENg +텢 TEJ +텣 TEC +텤 TEK +텥 TET +텦 TEP +텧 TEH +텨 TYeo +텩 TYeoG +텪 TYeoGg +텫 TYeoGs +텬 TYeoN +텭 TYeoNj +텮 TYeoNh +텯 TYeoD +텰 TYeoL +텱 TYeoLg +텲 TYeoLm +텳 TYeoLb +텴 TYeoLs +텵 TYeoLt +텶 TYeoLp +텷 TYeoLh +텸 TYeoM +텹 TYeoB +텺 TYeoBs +텻 TYeoS +텼 TYeoSs +텽 TYeoNg +텾 TYeoJ +텿 TYeoC +톀 TYeoK +톁 TYeoT +톂 TYeoP +톃 TYeoH +톄 TYe +톅 TYeG +톆 TYeGg +톇 TYeGs +톈 TYeN +톉 TYeNj +톊 TYeNh +톋 TYeD +톌 TYeL +톍 TYeLg +톎 TYeLm +톏 TYeLb +톐 TYeLs +톑 TYeLt +톒 TYeLp +톓 TYeLh +톔 TYeM +톕 TYeB +톖 TYeBs +톗 TYeS +톘 TYeSs +톙 TYeNg +톚 TYeJ +톛 TYeC +톜 TYeK +톝 TYeT +톞 TYeP +톟 TYeH +토 TO +톡 TOG +톢 TOGg +톣 TOGs +톤 TON +톥 TONj +톦 TONh +톧 TOD +톨 TOL +톩 TOLg +톪 TOLm +톫 TOLb +톬 TOLs +톭 TOLt +톮 TOLp +톯 TOLh +톰 TOM +톱 TOB +톲 TOBs +톳 TOS +톴 TOSs +통 TONg +톶 TOJ +톷 TOC +톸 TOK +톹 TOT +톺 TOP +톻 TOH +톼 TWa +톽 TWaG +톾 TWaGg +톿 TWaGs +퇀 TWaN +퇁 TWaNj +퇂 TWaNh +퇃 TWaD +퇄 TWaL +퇅 TWaLg +퇆 TWaLm +퇇 TWaLb +퇈 TWaLs +퇉 TWaLt +퇊 TWaLp +퇋 TWaLh +퇌 TWaM +퇍 TWaB +퇎 TWaBs +퇏 TWaS +퇐 TWaSs +퇑 TWaNg +퇒 TWaJ +퇓 TWaC +퇔 TWaK +퇕 TWaT +퇖 TWaP +퇗 TWaH +퇘 TWae +퇙 TWaeG +퇚 TWaeGg +퇛 TWaeGs +퇜 TWaeN +퇝 TWaeNj +퇞 TWaeNh +퇟 TWaeD +퇠 TWaeL +퇡 TWaeLg +퇢 TWaeLm +퇣 TWaeLb +퇤 TWaeLs +퇥 TWaeLt +퇦 TWaeLp +퇧 TWaeLh +퇨 TWaeM +퇩 TWaeB +퇪 TWaeBs +퇫 TWaeS +퇬 TWaeSs +퇭 TWaeNg +퇮 TWaeJ +퇯 TWaeC +퇰 TWaeK +퇱 TWaeT +퇲 TWaeP +퇳 TWaeH +퇴 TOe +퇵 TOeG +퇶 TOeGg +퇷 TOeGs +퇸 TOeN +퇹 TOeNj +퇺 TOeNh +퇻 TOeD +퇼 TOeL +퇽 TOeLg +퇾 TOeLm +퇿 TOeLb +툀 TOeLs +툁 TOeLt +툂 TOeLp +툃 TOeLh +툄 TOeM +툅 TOeB +툆 TOeBs +툇 TOeS +툈 TOeSs +툉 TOeNg +툊 TOeJ +툋 TOeC +툌 TOeK +툍 TOeT +툎 TOeP +툏 TOeH +툐 TYo +툑 TYoG +툒 TYoGg +툓 TYoGs +툔 TYoN +툕 TYoNj +툖 TYoNh +툗 TYoD +툘 TYoL +툙 TYoLg +툚 TYoLm +툛 TYoLb +툜 TYoLs +툝 TYoLt +툞 TYoLp +툟 TYoLh +툠 TYoM +툡 TYoB +툢 TYoBs +툣 TYoS +툤 TYoSs +툥 TYoNg +툦 TYoJ +툧 TYoC +툨 TYoK +툩 TYoT +툪 TYoP +툫 TYoH +투 TU +툭 TUG +툮 TUGg +툯 TUGs +툰 TUN +툱 TUNj +툲 TUNh +툳 TUD +툴 TUL +툵 TULg +툶 TULm +툷 TULb +툸 TULs +툹 TULt +툺 TULp +툻 TULh +툼 TUM +툽 TUB +툾 TUBs +툿 TUS +퉀 TUSs +퉁 TUNg +퉂 TUJ +퉃 TUC +퉄 TUK +퉅 TUT +퉆 TUP +퉇 TUH +퉈 TWo +퉉 TWoG +퉊 TWoGg +퉋 TWoGs +퉌 TWoN +퉍 TWoNj +퉎 TWoNh +퉏 TWoD +퉐 TWoL +퉑 TWoLg +퉒 TWoLm +퉓 TWoLb +퉔 TWoLs +퉕 TWoLt +퉖 TWoLp +퉗 TWoLh +퉘 TWoM +퉙 TWoB +퉚 TWoBs +퉛 TWoS +퉜 TWoSs +퉝 TWoNg +퉞 TWoJ +퉟 TWoC +퉠 TWoK +퉡 TWoT +퉢 TWoP +퉣 TWoH +퉤 TWe +퉥 TWeG +퉦 TWeGg +퉧 TWeGs +퉨 TWeN +퉩 TWeNj +퉪 TWeNh +퉫 TWeD +퉬 TWeL +퉭 TWeLg +퉮 TWeLm +퉯 TWeLb +퉰 TWeLs +퉱 TWeLt +퉲 TWeLp +퉳 TWeLh +퉴 TWeM +퉵 TWeB +퉶 TWeBs +퉷 TWeS +퉸 TWeSs +퉹 TWeNg +퉺 TWeJ +퉻 TWeC +퉼 TWeK +퉽 TWeT +퉾 TWeP +퉿 TWeH +튀 TWi +튁 TWiG +튂 TWiGg +튃 TWiGs +튄 TWiN +튅 TWiNj +튆 TWiNh +튇 TWiD +튈 TWiL +튉 TWiLg +튊 TWiLm +튋 TWiLb +튌 TWiLs +튍 TWiLt +튎 TWiLp +튏 TWiLh +튐 TWiM +튑 TWiB +튒 TWiBs +튓 TWiS +튔 TWiSs +튕 TWiNg +튖 TWiJ +튗 TWiC +튘 TWiK +튙 TWiT +튚 TWiP +튛 TWiH +튜 TYu +튝 TYuG +튞 TYuGg +튟 TYuGs +튠 TYuN +튡 TYuNj +튢 TYuNh +튣 TYuD +튤 TYuL +튥 TYuLg +튦 TYuLm +튧 TYuLb +튨 TYuLs +튩 TYuLt +튪 TYuLp +튫 TYuLh +튬 TYuM +튭 TYuB +튮 TYuBs +튯 TYuS +튰 TYuSs +튱 TYuNg +튲 TYuJ +튳 TYuC +튴 TYuK +튵 TYuT +튶 TYuP +튷 TYuH +트 TEu +특 TEuG +튺 TEuGg +튻 TEuGs +튼 TEuN +튽 TEuNj +튾 TEuNh +튿 TEuD +틀 TEuL +틁 TEuLg +틂 TEuLm +틃 TEuLb +틄 TEuLs +틅 TEuLt +틆 TEuLp +틇 TEuLh +틈 TEuM +틉 TEuB +틊 TEuBs +틋 TEuS +틌 TEuSs +틍 TEuNg +틎 TEuJ +틏 TEuC +틐 TEuK +틑 TEuT +틒 TEuP +틓 TEuH +틔 TUi +틕 TUiG +틖 TUiGg +틗 TUiGs +틘 TUiN +틙 TUiNj +틚 TUiNh +틛 TUiD +틜 TUiL +틝 TUiLg +틞 TUiLm +틟 TUiLb +틠 TUiLs +틡 TUiLt +틢 TUiLp +틣 TUiLh +틤 TUiM +틥 TUiB +틦 TUiBs +틧 TUiS +틨 TUiSs +틩 TUiNg +틪 TUiJ +틫 TUiC +틬 TUiK +틭 TUiT +틮 TUiP +틯 TUiH +티 TI +틱 TIG +틲 TIGg +틳 TIGs +틴 TIN +틵 TINj +틶 TINh +틷 TID +틸 TIL +틹 TILg +틺 TILm +틻 TILb +틼 TILs +틽 TILt +틾 TILp +틿 TILh +팀 TIM +팁 TIB +팂 TIBs +팃 TIS +팄 TISs +팅 TINg +팆 TIJ +팇 TIC +팈 TIK +팉 TIT +팊 TIP +팋 TIH +파 PA +팍 PAG +팎 PAGg +팏 PAGs +판 PAN +팑 PANj +팒 PANh +팓 PAD +팔 PAL +팕 PALg +팖 PALm +팗 PALb +팘 PALs +팙 PALt +팚 PALp +팛 PALh +팜 PAM +팝 PAB +팞 PABs +팟 PAS +팠 PASs +팡 PANg +팢 PAJ +팣 PAC +팤 PAK +팥 PAT +팦 PAP +팧 PAH +패 PAe +팩 PAeG +팪 PAeGg +팫 PAeGs +팬 PAeN +팭 PAeNj +팮 PAeNh +팯 PAeD +팰 PAeL +팱 PAeLg +팲 PAeLm +팳 PAeLb +팴 PAeLs +팵 PAeLt +팶 PAeLp +팷 PAeLh +팸 PAeM +팹 PAeB +팺 PAeBs +팻 PAeS +팼 PAeSs +팽 PAeNg +팾 PAeJ +팿 PAeC +퍀 PAeK +퍁 PAeT +퍂 PAeP +퍃 PAeH +퍄 PYa +퍅 PYaG +퍆 PYaGg +퍇 PYaGs +퍈 PYaN +퍉 PYaNj +퍊 PYaNh +퍋 PYaD +퍌 PYaL +퍍 PYaLg +퍎 PYaLm +퍏 PYaLb +퍐 PYaLs +퍑 PYaLt +퍒 PYaLp +퍓 PYaLh +퍔 PYaM +퍕 PYaB +퍖 PYaBs +퍗 PYaS +퍘 PYaSs +퍙 PYaNg +퍚 PYaJ +퍛 PYaC +퍜 PYaK +퍝 PYaT +퍞 PYaP +퍟 PYaH +퍠 PYae +퍡 PYaeG +퍢 PYaeGg +퍣 PYaeGs +퍤 PYaeN +퍥 PYaeNj +퍦 PYaeNh +퍧 PYaeD +퍨 PYaeL +퍩 PYaeLg +퍪 PYaeLm +퍫 PYaeLb +퍬 PYaeLs +퍭 PYaeLt +퍮 PYaeLp +퍯 PYaeLh +퍰 PYaeM +퍱 PYaeB +퍲 PYaeBs +퍳 PYaeS +퍴 PYaeSs +퍵 PYaeNg +퍶 PYaeJ +퍷 PYaeC +퍸 PYaeK +퍹 PYaeT +퍺 PYaeP +퍻 PYaeH +퍼 PEo +퍽 PEoG +퍾 PEoGg +퍿 PEoGs +펀 PEoN +펁 PEoNj +펂 PEoNh +펃 PEoD +펄 PEoL +펅 PEoLg +펆 PEoLm +펇 PEoLb +펈 PEoLs +펉 PEoLt +펊 PEoLp +펋 PEoLh +펌 PEoM +펍 PEoB +펎 PEoBs +펏 PEoS +펐 PEoSs +펑 PEoNg +펒 PEoJ +펓 PEoC +펔 PEoK +펕 PEoT +펖 PEoP +펗 PEoH +페 PE +펙 PEG +펚 PEGg +펛 PEGs +펜 PEN +펝 PENj +펞 PENh +펟 PED +펠 PEL +펡 PELg +펢 PELm +펣 PELb +펤 PELs +펥 PELt +펦 PELp +펧 PELh +펨 PEM +펩 PEB +펪 PEBs +펫 PES +펬 PESs +펭 PENg +펮 PEJ +펯 PEC +펰 PEK +펱 PET +펲 PEP +펳 PEH +펴 PYeo +펵 PYeoG +펶 PYeoGg +펷 PYeoGs +편 PYeoN +펹 PYeoNj +펺 PYeoNh +펻 PYeoD +펼 PYeoL +펽 PYeoLg +펾 PYeoLm +펿 PYeoLb +폀 PYeoLs +폁 PYeoLt +폂 PYeoLp +폃 PYeoLh +폄 PYeoM +폅 PYeoB +폆 PYeoBs +폇 PYeoS +폈 PYeoSs +평 PYeoNg +폊 PYeoJ +폋 PYeoC +폌 PYeoK +폍 PYeoT +폎 PYeoP +폏 PYeoH +폐 PYe +폑 PYeG +폒 PYeGg +폓 PYeGs +폔 PYeN +폕 PYeNj +폖 PYeNh +폗 PYeD +폘 PYeL +폙 PYeLg +폚 PYeLm +폛 PYeLb +폜 PYeLs +폝 PYeLt +폞 PYeLp +폟 PYeLh +폠 PYeM +폡 PYeB +폢 PYeBs +폣 PYeS +폤 PYeSs +폥 PYeNg +폦 PYeJ +폧 PYeC +폨 PYeK +폩 PYeT +폪 PYeP +폫 PYeH +포 PO +폭 POG +폮 POGg +폯 POGs +폰 PON +폱 PONj +폲 PONh +폳 POD +폴 POL +폵 POLg +폶 POLm +폷 POLb +폸 POLs +폹 POLt +폺 POLp +폻 POLh +폼 POM +폽 POB +폾 POBs +폿 POS +퐀 POSs +퐁 PONg +퐂 POJ +퐃 POC +퐄 POK +퐅 POT +퐆 POP +퐇 POH +퐈 PWa +퐉 PWaG +퐊 PWaGg +퐋 PWaGs +퐌 PWaN +퐍 PWaNj +퐎 PWaNh +퐏 PWaD +퐐 PWaL +퐑 PWaLg +퐒 PWaLm +퐓 PWaLb +퐔 PWaLs +퐕 PWaLt +퐖 PWaLp +퐗 PWaLh +퐘 PWaM +퐙 PWaB +퐚 PWaBs +퐛 PWaS +퐜 PWaSs +퐝 PWaNg +퐞 PWaJ +퐟 PWaC +퐠 PWaK +퐡 PWaT +퐢 PWaP +퐣 PWaH +퐤 PWae +퐥 PWaeG +퐦 PWaeGg +퐧 PWaeGs +퐨 PWaeN +퐩 PWaeNj +퐪 PWaeNh +퐫 PWaeD +퐬 PWaeL +퐭 PWaeLg +퐮 PWaeLm +퐯 PWaeLb +퐰 PWaeLs +퐱 PWaeLt +퐲 PWaeLp +퐳 PWaeLh +퐴 PWaeM +퐵 PWaeB +퐶 PWaeBs +퐷 PWaeS +퐸 PWaeSs +퐹 PWaeNg +퐺 PWaeJ +퐻 PWaeC +퐼 PWaeK +퐽 PWaeT +퐾 PWaeP +퐿 PWaeH +푀 POe +푁 POeG +푂 POeGg +푃 POeGs +푄 POeN +푅 POeNj +푆 POeNh +푇 POeD +푈 POeL +푉 POeLg +푊 POeLm +푋 POeLb +푌 POeLs +푍 POeLt +푎 POeLp +푏 POeLh +푐 POeM +푑 POeB +푒 POeBs +푓 POeS +푔 POeSs +푕 POeNg +푖 POeJ +푗 POeC +푘 POeK +푙 POeT +푚 POeP +푛 POeH +표 PYo +푝 PYoG +푞 PYoGg +푟 PYoGs +푠 PYoN +푡 PYoNj +푢 PYoNh +푣 PYoD +푤 PYoL +푥 PYoLg +푦 PYoLm +푧 PYoLb +푨 PYoLs +푩 PYoLt +푪 PYoLp +푫 PYoLh +푬 PYoM +푭 PYoB +푮 PYoBs +푯 PYoS +푰 PYoSs +푱 PYoNg +푲 PYoJ +푳 PYoC +푴 PYoK +푵 PYoT +푶 PYoP +푷 PYoH +푸 PU +푹 PUG +푺 PUGg +푻 PUGs +푼 PUN +푽 PUNj +푾 PUNh +푿 PUD +풀 PUL +풁 PULg +풂 PULm +풃 PULb +풄 PULs +풅 PULt +풆 PULp +풇 PULh +품 PUM +풉 PUB +풊 PUBs +풋 PUS +풌 PUSs +풍 PUNg +풎 PUJ +풏 PUC +풐 PUK +풑 PUT +풒 PUP +풓 PUH +풔 PWo +풕 PWoG +풖 PWoGg +풗 PWoGs +풘 PWoN +풙 PWoNj +풚 PWoNh +풛 PWoD +풜 PWoL +풝 PWoLg +풞 PWoLm +풟 PWoLb +풠 PWoLs +풡 PWoLt +풢 PWoLp +풣 PWoLh +풤 PWoM +풥 PWoB +풦 PWoBs +풧 PWoS +풨 PWoSs +풩 PWoNg +풪 PWoJ +풫 PWoC +풬 PWoK +풭 PWoT +풮 PWoP +풯 PWoH +풰 PWe +풱 PWeG +풲 PWeGg +풳 PWeGs +풴 PWeN +풵 PWeNj +풶 PWeNh +풷 PWeD +풸 PWeL +풹 PWeLg +풺 PWeLm +풻 PWeLb +풼 PWeLs +풽 PWeLt +풾 PWeLp +풿 PWeLh +퓀 PWeM +퓁 PWeB +퓂 PWeBs +퓃 PWeS +퓄 PWeSs +퓅 PWeNg +퓆 PWeJ +퓇 PWeC +퓈 PWeK +퓉 PWeT +퓊 PWeP +퓋 PWeH +퓌 PWi +퓍 PWiG +퓎 PWiGg +퓏 PWiGs +퓐 PWiN +퓑 PWiNj +퓒 PWiNh +퓓 PWiD +퓔 PWiL +퓕 PWiLg +퓖 PWiLm +퓗 PWiLb +퓘 PWiLs +퓙 PWiLt +퓚 PWiLp +퓛 PWiLh +퓜 PWiM +퓝 PWiB +퓞 PWiBs +퓟 PWiS +퓠 PWiSs +퓡 PWiNg +퓢 PWiJ +퓣 PWiC +퓤 PWiK +퓥 PWiT +퓦 PWiP +퓧 PWiH +퓨 PYu +퓩 PYuG +퓪 PYuGg +퓫 PYuGs +퓬 PYuN +퓭 PYuNj +퓮 PYuNh +퓯 PYuD +퓰 PYuL +퓱 PYuLg +퓲 PYuLm +퓳 PYuLb +퓴 PYuLs +퓵 PYuLt +퓶 PYuLp +퓷 PYuLh +퓸 PYuM +퓹 PYuB +퓺 PYuBs +퓻 PYuS +퓼 PYuSs +퓽 PYuNg +퓾 PYuJ +퓿 PYuC +픀 PYuK +픁 PYuT +픂 PYuP +픃 PYuH +프 PEu +픅 PEuG +픆 PEuGg +픇 PEuGs +픈 PEuN +픉 PEuNj +픊 PEuNh +픋 PEuD +플 PEuL +픍 PEuLg +픎 PEuLm +픏 PEuLb +픐 PEuLs +픑 PEuLt +픒 PEuLp +픓 PEuLh +픔 PEuM +픕 PEuB +픖 PEuBs +픗 PEuS +픘 PEuSs +픙 PEuNg +픚 PEuJ +픛 PEuC +픜 PEuK +픝 PEuT +픞 PEuP +픟 PEuH +픠 PUi +픡 PUiG +픢 PUiGg +픣 PUiGs +픤 PUiN +픥 PUiNj +픦 PUiNh +픧 PUiD +픨 PUiL +픩 PUiLg +픪 PUiLm +픫 PUiLb +픬 PUiLs +픭 PUiLt +픮 PUiLp +픯 PUiLh +픰 PUiM +픱 PUiB +픲 PUiBs +픳 PUiS +픴 PUiSs +픵 PUiNg +픶 PUiJ +픷 PUiC +픸 PUiK +픹 PUiT +픺 PUiP +픻 PUiH +피 PI +픽 PIG +픾 PIGg +픿 PIGs +핀 PIN +핁 PINj +핂 PINh +핃 PID +필 PIL +핅 PILg +핆 PILm +핇 PILb +핈 PILs +핉 PILt +핊 PILp +핋 PILh +핌 PIM +핍 PIB +핎 PIBs +핏 PIS +핐 PISs +핑 PINg +핒 PIJ +핓 PIC +핔 PIK +핕 PIT +핖 PIP +핗 PIH +하 HA +학 HAG +핚 HAGg +핛 HAGs +한 HAN +핝 HANj +핞 HANh +핟 HAD +할 HAL +핡 HALg +핢 HALm +핣 HALb +핤 HALs +핥 HALt +핦 HALp +핧 HALh +함 HAM +합 HAB +핪 HABs +핫 HAS +핬 HASs +항 HANg +핮 HAJ +핯 HAC +핰 HAK +핱 HAT +핲 HAP +핳 HAH +해 HAe +핵 HAeG +핶 HAeGg +핷 HAeGs +핸 HAeN +핹 HAeNj +핺 HAeNh +핻 HAeD +핼 HAeL +핽 HAeLg +핾 HAeLm +핿 HAeLb +햀 HAeLs +햁 HAeLt +햂 HAeLp +햃 HAeLh +햄 HAeM +햅 HAeB +햆 HAeBs +햇 HAeS +했 HAeSs +행 HAeNg +햊 HAeJ +햋 HAeC +햌 HAeK +햍 HAeT +햎 HAeP +햏 HAeH +햐 HYa +햑 HYaG +햒 HYaGg +햓 HYaGs +햔 HYaN +햕 HYaNj +햖 HYaNh +햗 HYaD +햘 HYaL +햙 HYaLg +햚 HYaLm +햛 HYaLb +햜 HYaLs +햝 HYaLt +햞 HYaLp +햟 HYaLh +햠 HYaM +햡 HYaB +햢 HYaBs +햣 HYaS +햤 HYaSs +향 HYaNg +햦 HYaJ +햧 HYaC +햨 HYaK +햩 HYaT +햪 HYaP +햫 HYaH +햬 HYae +햭 HYaeG +햮 HYaeGg +햯 HYaeGs +햰 HYaeN +햱 HYaeNj +햲 HYaeNh +햳 HYaeD +햴 HYaeL +햵 HYaeLg +햶 HYaeLm +햷 HYaeLb +햸 HYaeLs +햹 HYaeLt +햺 HYaeLp +햻 HYaeLh +햼 HYaeM +햽 HYaeB +햾 HYaeBs +햿 HYaeS +헀 HYaeSs +헁 HYaeNg +헂 HYaeJ +헃 HYaeC +헄 HYaeK +헅 HYaeT +헆 HYaeP +헇 HYaeH +허 HEo +헉 HEoG +헊 HEoGg +헋 HEoGs +헌 HEoN +헍 HEoNj +헎 HEoNh +헏 HEoD +헐 HEoL +헑 HEoLg +헒 HEoLm +헓 HEoLb +헔 HEoLs +헕 HEoLt +헖 HEoLp +헗 HEoLh +험 HEoM +헙 HEoB +헚 HEoBs +헛 HEoS +헜 HEoSs +헝 HEoNg +헞 HEoJ +헟 HEoC +헠 HEoK +헡 HEoT +헢 HEoP +헣 HEoH +헤 HE +헥 HEG +헦 HEGg +헧 HEGs +헨 HEN +헩 HENj +헪 HENh +헫 HED +헬 HEL +헭 HELg +헮 HELm +헯 HELb +헰 HELs +헱 HELt +헲 HELp +헳 HELh +헴 HEM +헵 HEB +헶 HEBs +헷 HES +헸 HESs +헹 HENg +헺 HEJ +헻 HEC +헼 HEK +헽 HET +헾 HEP +헿 HEH +혀 HYeo +혁 HYeoG +혂 HYeoGg +혃 HYeoGs +현 HYeoN +혅 HYeoNj +혆 HYeoNh +혇 HYeoD +혈 HYeoL +혉 HYeoLg +혊 HYeoLm +혋 HYeoLb +혌 HYeoLs +혍 HYeoLt +혎 HYeoLp +혏 HYeoLh +혐 HYeoM +협 HYeoB +혒 HYeoBs +혓 HYeoS +혔 HYeoSs +형 HYeoNg +혖 HYeoJ +혗 HYeoC +혘 HYeoK +혙 HYeoT +혚 HYeoP +혛 HYeoH +혜 HYe +혝 HYeG +혞 HYeGg +혟 HYeGs +혠 HYeN +혡 HYeNj +혢 HYeNh +혣 HYeD +혤 HYeL +혥 HYeLg +혦 HYeLm +혧 HYeLb +혨 HYeLs +혩 HYeLt +혪 HYeLp +혫 HYeLh +혬 HYeM +혭 HYeB +혮 HYeBs +혯 HYeS +혰 HYeSs +혱 HYeNg +혲 HYeJ +혳 HYeC +혴 HYeK +혵 HYeT +혶 HYeP +혷 HYeH +호 HO +혹 HOG +혺 HOGg +혻 HOGs +혼 HON +혽 HONj +혾 HONh +혿 HOD +홀 HOL +홁 HOLg +홂 HOLm +홃 HOLb +홄 HOLs +홅 HOLt +홆 HOLp +홇 HOLh +홈 HOM +홉 HOB +홊 HOBs +홋 HOS +홌 HOSs +홍 HONg +홎 HOJ +홏 HOC +홐 HOK +홑 HOT +홒 HOP +홓 HOH +화 HWa +확 HWaG +홖 HWaGg +홗 HWaGs +환 HWaN +홙 HWaNj +홚 HWaNh +홛 HWaD +활 HWaL +홝 HWaLg +홞 HWaLm +홟 HWaLb +홠 HWaLs +홡 HWaLt +홢 HWaLp +홣 HWaLh +홤 HWaM +홥 HWaB +홦 HWaBs +홧 HWaS +홨 HWaSs +황 HWaNg +홪 HWaJ +홫 HWaC +홬 HWaK +홭 HWaT +홮 HWaP +홯 HWaH +홰 HWae +홱 HWaeG +홲 HWaeGg +홳 HWaeGs +홴 HWaeN +홵 HWaeNj +홶 HWaeNh +홷 HWaeD +홸 HWaeL +홹 HWaeLg +홺 HWaeLm +홻 HWaeLb +홼 HWaeLs +홽 HWaeLt +홾 HWaeLp +홿 HWaeLh +횀 HWaeM +횁 HWaeB +횂 HWaeBs +횃 HWaeS +횄 HWaeSs +횅 HWaeNg +횆 HWaeJ +횇 HWaeC +횈 HWaeK +횉 HWaeT +횊 HWaeP +횋 HWaeH +회 HOe +획 HOeG +횎 HOeGg +횏 HOeGs +횐 HOeN +횑 HOeNj +횒 HOeNh +횓 HOeD +횔 HOeL +횕 HOeLg +횖 HOeLm +횗 HOeLb +횘 HOeLs +횙 HOeLt +횚 HOeLp +횛 HOeLh +횜 HOeM +횝 HOeB +횞 HOeBs +횟 HOeS +횠 HOeSs +횡 HOeNg +횢 HOeJ +횣 HOeC +횤 HOeK +횥 HOeT +횦 HOeP +횧 HOeH +효 HYo +횩 HYoG +횪 HYoGg +횫 HYoGs +횬 HYoN +횭 HYoNj +횮 HYoNh +횯 HYoD +횰 HYoL +횱 HYoLg +횲 HYoLm +횳 HYoLb +횴 HYoLs +횵 HYoLt +횶 HYoLp +횷 HYoLh +횸 HYoM +횹 HYoB +횺 HYoBs +횻 HYoS +횼 HYoSs +횽 HYoNg +횾 HYoJ +횿 HYoC +훀 HYoK +훁 HYoT +훂 HYoP +훃 HYoH +후 HU +훅 HUG +훆 HUGg +훇 HUGs +훈 HUN +훉 HUNj +훊 HUNh +훋 HUD +훌 HUL +훍 HULg +훎 HULm +훏 HULb +훐 HULs +훑 HULt +훒 HULp +훓 HULh +훔 HUM +훕 HUB +훖 HUBs +훗 HUS +훘 HUSs +훙 HUNg +훚 HUJ +훛 HUC +훜 HUK +훝 HUT +훞 HUP +훟 HUH +훠 HWo +훡 HWoG +훢 HWoGg +훣 HWoGs +훤 HWoN +훥 HWoNj +훦 HWoNh +훧 HWoD +훨 HWoL +훩 HWoLg +훪 HWoLm +훫 HWoLb +훬 HWoLs +훭 HWoLt +훮 HWoLp +훯 HWoLh +훰 HWoM +훱 HWoB +훲 HWoBs +훳 HWoS +훴 HWoSs +훵 HWoNg +훶 HWoJ +훷 HWoC +훸 HWoK +훹 HWoT +훺 HWoP +훻 HWoH +훼 HWe +훽 HWeG +훾 HWeGg +훿 HWeGs +휀 HWeN +휁 HWeNj +휂 HWeNh +휃 HWeD +휄 HWeL +휅 HWeLg +휆 HWeLm +휇 HWeLb +휈 HWeLs +휉 HWeLt +휊 HWeLp +휋 HWeLh +휌 HWeM +휍 HWeB +휎 HWeBs +휏 HWeS +휐 HWeSs +휑 HWeNg +휒 HWeJ +휓 HWeC +휔 HWeK +휕 HWeT +휖 HWeP +휗 HWeH +휘 HWi +휙 HWiG +휚 HWiGg +휛 HWiGs +휜 HWiN +휝 HWiNj +휞 HWiNh +휟 HWiD +휠 HWiL +휡 HWiLg +휢 HWiLm +휣 HWiLb +휤 HWiLs +휥 HWiLt +휦 HWiLp +휧 HWiLh +휨 HWiM +휩 HWiB +휪 HWiBs +휫 HWiS +휬 HWiSs +휭 HWiNg +휮 HWiJ +휯 HWiC +휰 HWiK +휱 HWiT +휲 HWiP +휳 HWiH +휴 HYu +휵 HYuG +휶 HYuGg +휷 HYuGs +휸 HYuN +휹 HYuNj +휺 HYuNh +휻 HYuD +휼 HYuL +휽 HYuLg +휾 HYuLm +휿 HYuLb +흀 HYuLs +흁 HYuLt +흂 HYuLp +흃 HYuLh +흄 HYuM +흅 HYuB +흆 HYuBs +흇 HYuS +흈 HYuSs +흉 HYuNg +흊 HYuJ +흋 HYuC +흌 HYuK +흍 HYuT +흎 HYuP +흏 HYuH +흐 HEu +흑 HEuG +흒 HEuGg +흓 HEuGs +흔 HEuN +흕 HEuNj +흖 HEuNh +흗 HEuD +흘 HEuL +흙 HEuLg +흚 HEuLm +흛 HEuLb +흜 HEuLs +흝 HEuLt +흞 HEuLp +흟 HEuLh +흠 HEuM +흡 HEuB +흢 HEuBs +흣 HEuS +흤 HEuSs +흥 HEuNg +흦 HEuJ +흧 HEuC +흨 HEuK +흩 HEuT +흪 HEuP +흫 HEuH +희 HUi +흭 HUiG +흮 HUiGg +흯 HUiGs +흰 HUiN +흱 HUiNj +흲 HUiNh +흳 HUiD +흴 HUiL +흵 HUiLg +흶 HUiLm +흷 HUiLb +흸 HUiLs +흹 HUiLt +흺 HUiLp +흻 HUiLh +흼 HUiM +흽 HUiB +흾 HUiBs +흿 HUiS +힀 HUiSs +힁 HUiNg +힂 HUiJ +힃 HUiC +힄 HUiK +힅 HUiT +힆 HUiP +힇 HUiH +히 HI +힉 HIG +힊 HIGg +힋 HIGs +힌 HIN +힍 HINj +힎 HINh +힏 HID +힐 HIL +힑 HILg +힒 HILm +힓 HILb +힔 HILs +힕 HILt +힖 HILp +힗 HILh +힘 HIM +힙 HIB +힚 HIBs +힛 HIS +힜 HISs +힝 HINg +힞 HIJ +힟 HIC +힠 HIK +힡 HIT +힢 HIP +힣 HIH \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f4b1c7d5..e8a0ec5c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ diff --git a/app/src/main/java/io/github/sspanak/tt9/db/DataStore.java b/app/src/main/java/io/github/sspanak/tt9/db/DataStore.java index 3666d881..5a23392c 100644 --- a/app/src/main/java/io/github/sspanak/tt9/db/DataStore.java +++ b/app/src/main/java/io/github/sspanak/tt9/db/DataStore.java @@ -112,7 +112,7 @@ public class DataStore { private static void getWordsSync(ConsumerCompat> dataHandler, Language language, String sequence, String filter, int minWords, int maxWords) { try { - ArrayList data = words.getSimilar(getWordsCancellationSignal, language, sequence, filter, minWords, maxWords); + ArrayList data = words.getMany(getWordsCancellationSignal, language, sequence, filter, minWords, maxWords); asyncReturn.post(() -> dataHandler.accept(data)); } catch (Exception e) { Logger.e(LOG_TAG, "Error fetching words: " + e.getMessage()); diff --git a/app/src/main/java/io/github/sspanak/tt9/db/customWords/CustomWordsImporter.java b/app/src/main/java/io/github/sspanak/tt9/db/customWords/CustomWordsImporter.java index 8c725f89..eb5ad580 100644 --- a/app/src/main/java/io/github/sspanak/tt9/db/customWords/CustomWordsImporter.java +++ b/app/src/main/java/io/github/sspanak/tt9/db/customWords/CustomWordsImporter.java @@ -93,7 +93,7 @@ public class CustomWordsImporter extends AbstractFileProcessor { Timer.start(getClass().getSimpleName()); sendStart(resources.getString(R.string.dictionary_import_running)); - if (isFileValid() && isThereRoomForMoreWords(activity) && insertWords(activity)) { + if (isFileValid() && isThereRoomForMoreWords(activity) && insertWords()) { sendSuccess(); Logger.i(getClass().getSimpleName(), "Imported " + file.getName() + " in " + Timer.get(getClass().getSimpleName()) + " ms"); } else { @@ -114,7 +114,7 @@ public class CustomWordsImporter extends AbstractFileProcessor { } - private boolean insertWords(Context context) { + private boolean insertWords() { ReadOps readOps = new ReadOps(); int ignoredWords = 0; int lineCount = 1; @@ -128,13 +128,13 @@ public class CustomWordsImporter extends AbstractFileProcessor { return false; } - CustomWord customWord = createCustomWord(context, line, lineCount); + CustomWord customWord = createCustomWord(line, lineCount); if (customWord == null) { sqlite.failTransaction(); return false; } - if (readOps.exists(sqlite.getDb(), customWord.language, customWord.word)) { + if (customWord.language == null || customWord.language.isSyllabary() || readOps.exists(sqlite.getDb(), customWord.language, customWord.word)) { ignoredWords++; } else { InsertOps.insertCustomWord(sqlite.getDb(), customWord.language, customWord.sequence, customWord.word); @@ -154,7 +154,10 @@ public class CustomWordsImporter extends AbstractFileProcessor { } if (ignoredWords > 0) { - Logger.i(getClass().getSimpleName(), "Skipped " + ignoredWords + " word(s) that are already in the dictionary."); + Logger.i( + getClass().getSimpleName(), + "Skipped " + ignoredWords + " word(s) that are already in the dictionary or do not belong to an alphabetic language." + ); } return true; @@ -196,11 +199,11 @@ public class CustomWordsImporter extends AbstractFileProcessor { } - private CustomWord createCustomWord(Context context, String line, int lineCount) { + private CustomWord createCustomWord(String line, int lineCount) { try { return new CustomWord( CustomWordFile.getWord(line), - CustomWordFile.getLanguage(context, line) + CustomWordFile.getLanguage(line) ); } catch (Exception e) { String linePreview = line.length() > 50 ? line.substring(0, 50) + "..." : line; diff --git a/app/src/main/java/io/github/sspanak/tt9/db/entities/CustomWordFile.java b/app/src/main/java/io/github/sspanak/tt9/db/entities/CustomWordFile.java index 5f62a97e..454a136f 100644 --- a/app/src/main/java/io/github/sspanak/tt9/db/entities/CustomWordFile.java +++ b/app/src/main/java/io/github/sspanak/tt9/db/entities/CustomWordFile.java @@ -1,7 +1,6 @@ package io.github.sspanak.tt9.db.entities; import android.content.ContentResolver; -import android.content.Context; import android.net.Uri; import androidx.annotation.NonNull; @@ -65,7 +64,7 @@ public class CustomWordFile { } - public static NaturalLanguage getLanguage(@NonNull Context context, String line) { + public static NaturalLanguage getLanguage(String line) { if (line == null) { return null; } @@ -75,7 +74,7 @@ public class CustomWordFile { return null; } - return LanguageCollection.getLanguage(context, parts[1]); + return LanguageCollection.getLanguage(parts[1]); } @NonNull public static String getWord(String line) { diff --git a/app/src/main/java/io/github/sspanak/tt9/db/entities/WordFile.java b/app/src/main/java/io/github/sspanak/tt9/db/entities/WordFile.java index 00dc59a0..29a302be 100644 --- a/app/src/main/java/io/github/sspanak/tt9/db/entities/WordFile.java +++ b/app/src/main/java/io/github/sspanak/tt9/db/entities/WordFile.java @@ -3,6 +3,8 @@ package io.github.sspanak.tt9.db.entities; import android.content.Context; import android.content.res.AssetManager; +import androidx.annotation.NonNull; + import java.io.BufferedReader; import java.io.File; import java.io.IOException; @@ -17,6 +19,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import io.github.sspanak.tt9.R; +import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.util.AssetFile; import io.github.sspanak.tt9.util.Logger; @@ -25,6 +28,7 @@ public class WordFile extends AssetFile { private static final String LOG_TAG = WordFile.class.getSimpleName(); private final Context context; + private final boolean hasSyllables; private int lastCharCode; private BufferedReader reader; @@ -36,9 +40,10 @@ public class WordFile extends AssetFile { private int sequences = -1; - public WordFile(Context context, String path, AssetManager assets) { - super(assets, path); + public WordFile(@NonNull Context context, Language language, AssetManager assets) { + super(assets, language != null ? language.getDictionaryFile() : ""); this.context = context; + hasSyllables = language != null && language.isSyllabary(); lastCharCode = 0; reader = null; @@ -138,15 +143,6 @@ public class WordFile extends AssetFile { } - public int getSequences() { - if (sequences < 0) { - loadProperties(); - } - - return sequences; - } - - private void setSequences(String rawProperty, String rawValue) { if (!rawProperty.equals("sequences")) { return; @@ -278,7 +274,7 @@ public class WordFile extends AssetFile { return words; } - boolean areWordsSeparated = false; + boolean areWordsSeparated = hasSyllables; // if the language chars are syllables, there is no leading space to hint word separation StringBuilder word = new StringBuilder(); // If the word string starts with a space, it means there are words longer than the sequence. diff --git a/app/src/main/java/io/github/sspanak/tt9/db/sqlite/ReadOps.java b/app/src/main/java/io/github/sspanak/tt9/db/sqlite/ReadOps.java index 464004af..0ec4fe01 100644 --- a/app/src/main/java/io/github/sspanak/tt9/db/sqlite/ReadOps.java +++ b/app/src/main/java/io/github/sspanak/tt9/db/sqlite/ReadOps.java @@ -17,6 +17,7 @@ import io.github.sspanak.tt9.db.entities.WordList; import io.github.sspanak.tt9.db.entities.WordPositionsStringBuilder; import io.github.sspanak.tt9.db.wordPairs.WordPair; import io.github.sspanak.tt9.db.words.SlowQueryStats; +import io.github.sspanak.tt9.db.words.WordStore; import io.github.sspanak.tt9.languages.EmojiLanguage; import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.util.Logger; @@ -127,6 +128,9 @@ public class ReadOps { return new WordList(); } + // EXACT_MATCHES concerns only the positions query + filter = filter.equals(WordStore.FILTER_EXACT_MATCHES_ONLY) ? "" : filter; + String wordsQuery = getWordsQuery(language, positions, filter, maximumWords, fullOutput); if (wordsQuery.isEmpty() || (cancel != null && cancel.isCanceled())) { return new WordList(); @@ -151,11 +155,17 @@ public class ReadOps { public String getSimilarWordPositions(@NonNull SQLiteDatabase db, @NonNull CancellationSignal cancel, @NonNull Language language, @NonNull String sequence, String wordFilter, int minPositions) { - int generations = switch (sequence.length()) { - case 2 -> wordFilter.isEmpty() ? 1 : 10; - case 3, 4 -> wordFilter.isEmpty() ? 2 : 10; - default -> 10; - }; + int generations; + + if (wordFilter.equals(WordStore.FILTER_EXACT_MATCHES_ONLY)) { + generations = 0; + } else { + generations = switch (sequence.length()) { + case 2 -> wordFilter.isEmpty() ? 1 : 10; + case 3, 4 -> wordFilter.isEmpty() ? 2 : 10; + default -> 10; + }; + } return getWordPositions(db, cancel, language, sequence, generations, minPositions, wordFilter); } @@ -215,26 +225,33 @@ public class ReadOps { } - @NonNull private String getFactoryWordPositionsQuery(@NonNull Language language, @NonNull String sequence, int generations) { + /** + * Generates a query to search for positions in the dictionary words table. It supports sequences + * that start with a "0" (searches them as strings). + */ + @NonNull + private String getFactoryWordPositionsQuery(@NonNull Language language, @NonNull String sequence, int generations) { StringBuilder sql = new StringBuilder("SELECT `start`, `end` FROM ") .append(Tables.getWordPositions(language.getId())) .append(" WHERE "); if (generations >= 0 && generations < 10) { - sql.append(" sequence IN(").append(sequence); + sql.append(" sequence IN('").append(sequence); int lastChild = (int)Math.pow(10, generations) - 1; for (int seqEnd = 1; seqEnd <= lastChild; seqEnd++) { if (seqEnd % 10 != 0) { - sql.append(",").append(sequence).append(seqEnd); + sql.append("','").append(sequence).append(seqEnd); } } - sql.append(")"); + sql.append("')"); } else { String rangeEnd = generations == 10 ? "9" : "999999"; - sql.append(" sequence = ").append(sequence).append(" OR sequence BETWEEN ").append(sequence).append("1 AND ").append(sequence).append(rangeEnd); + sql.append(" sequence = '") + .append(sequence) + .append("' OR sequence BETWEEN '").append(sequence).append("1' AND '").append(sequence).append(rangeEnd).append("'"); sql.append(" ORDER BY `start` "); sql.append(" LIMIT 100"); } @@ -245,7 +262,12 @@ public class ReadOps { } - @NonNull private String getCustomWordPositionsQuery(@NonNull Language language, @NonNull String sequence, int generations) { + /** + * Generates a query to search for custom word positions. This does NOT support sequences that + * start with a "0" (searches them as integers). + */ + @NonNull + private String getCustomWordPositionsQuery(@NonNull Language language, @NonNull String sequence, int generations) { String sql = "SELECT -id as `start`, -id as `end` FROM " + Tables.CUSTOM_WORDS + " WHERE langId = " + language.getId() + " AND (sequence = " + sequence; diff --git a/app/src/main/java/io/github/sspanak/tt9/db/sqlite/SQLiteOpener.java b/app/src/main/java/io/github/sspanak/tt9/db/sqlite/SQLiteOpener.java index 7f3a446a..0e9a9450 100644 --- a/app/src/main/java/io/github/sspanak/tt9/db/sqlite/SQLiteOpener.java +++ b/app/src/main/java/io/github/sspanak/tt9/db/sqlite/SQLiteOpener.java @@ -24,7 +24,7 @@ public class SQLiteOpener extends SQLiteOpenHelper { private SQLiteOpener(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); - allLanguages = new ArrayList<>(LanguageCollection.getAll(context)); + allLanguages = new ArrayList<>(LanguageCollection.getAll()); allLanguages.add(new EmojiLanguage()); } diff --git a/app/src/main/java/io/github/sspanak/tt9/db/wordPairs/WordPairStore.java b/app/src/main/java/io/github/sspanak/tt9/db/wordPairs/WordPairStore.java index 962e1365..5b383e87 100644 --- a/app/src/main/java/io/github/sspanak/tt9/db/wordPairs/WordPairStore.java +++ b/app/src/main/java/io/github/sspanak/tt9/db/wordPairs/WordPairStore.java @@ -137,6 +137,11 @@ public class WordPairStore extends BaseSyncStore { int totalPairs = 0; for (Language language : languages) { + if (language.isSyllabary()) { + Logger.d(LOG_TAG, "Not loading word pairs for syllabary language: " + language.getId()); + continue; + } + HashMap wordPairs = pairs.get(language.getId()); if (wordPairs == null) { wordPairs = new HashMap<>(); diff --git a/app/src/main/java/io/github/sspanak/tt9/db/words/DictionaryLoader.java b/app/src/main/java/io/github/sspanak/tt9/db/words/DictionaryLoader.java index 5d3eb148..a83c8d31 100644 --- a/app/src/main/java/io/github/sspanak/tt9/db/words/DictionaryLoader.java +++ b/app/src/main/java/io/github/sspanak/tt9/db/words/DictionaryLoader.java @@ -23,6 +23,7 @@ import io.github.sspanak.tt9.db.sqlite.SQLiteOpener; import io.github.sspanak.tt9.db.sqlite.Tables; import io.github.sspanak.tt9.languages.EmojiLanguage; import io.github.sspanak.tt9.languages.Language; +import io.github.sspanak.tt9.languages.LanguageKind; import io.github.sspanak.tt9.languages.exceptions.InvalidLanguageCharactersException; import io.github.sspanak.tt9.languages.exceptions.InvalidLanguageException; import io.github.sspanak.tt9.preferences.settings.SettingsStore; @@ -108,7 +109,7 @@ public class DictionaryLoader { public static void load(Context context, Language language) { DictionaryLoadingBar progressBar = DictionaryLoadingBar.getInstance(context); - getInstance(context).setOnStatusChange(status -> progressBar.show(context, status)); + getInstance(context).setOnStatusChange(progressBar::show); self.load(context, new ArrayList<>() {{ add(language); }}); } @@ -133,7 +134,7 @@ public class DictionaryLoader { load(context, language); } // or if the database is outdated, compared to the dictionary file, ask for confirmation and load - else if (!hash.equals(new WordFile(context, language.getDictionaryFile(), self.assets).getHash())) { + else if (!hash.equals(new WordFile(context, language, self.assets).getHash())) { new DictionaryUpdateNotification(context, language).show(); } }, @@ -235,8 +236,12 @@ public class DictionaryLoader { private int importLetters(Language language) throws InvalidLanguageCharactersException { + if (language.isSyllabary()) { + return 0; + } + int lettersCount = 0; - boolean isEnglish = language.getLocale().equals(Locale.ENGLISH); + boolean isEnglish = LanguageKind.isEnglish(language); WordBatch letters = new WordBatch(language); for (int key = 2; key <= 9; key++) { @@ -254,7 +259,7 @@ 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); + WordFile wordFile = new WordFile(context, language, assets); WordBatch batch = new WordBatch(language, SettingsStore.DICTIONARY_IMPORT_BATCH_SIZE + 1); float progressRatio = (maxProgress - minProgress) / wordFile.getWords(); int wordCount = 0; diff --git a/app/src/main/java/io/github/sspanak/tt9/db/words/WordStore.java b/app/src/main/java/io/github/sspanak/tt9/db/words/WordStore.java index 6bbff4fd..d85f474c 100644 --- a/app/src/main/java/io/github/sspanak/tt9/db/words/WordStore.java +++ b/app/src/main/java/io/github/sspanak/tt9/db/words/WordStore.java @@ -26,6 +26,7 @@ import io.github.sspanak.tt9.util.Timer; public class WordStore extends BaseSyncStore { + public static final String FILTER_EXACT_MATCHES_ONLY = "__exact__"; private final String LOG_TAG = "sqlite.WordStore"; private final ReadOps readOps; @@ -59,9 +60,10 @@ public class WordStore extends BaseSyncStore { /** * Loads words matching and similar to a given digit sequence * For example: "7655" -> "roll" (exact match), but also: "rolled", "roller", "rolling", ... - * and other similar. + * and other similar. When "wordFilter" is set to FILTER_EXACT_MATCHES_ONLY, the word list is + * constrained only to the words with length equal to the digit sequence length (exact matches). */ - public ArrayList getSimilar(@NonNull CancellationSignal cancel, Language language, String sequence, String wordFilter, int minimumWords, int maximumWords) { + public ArrayList getMany(@NonNull CancellationSignal cancel, Language language, String sequence, String wordFilter, int minimumWords, int maximumWords) { if (!checkOrNotify()) { return new ArrayList<>(); } @@ -89,7 +91,7 @@ public class WordStore extends BaseSyncStore { long wordsTime = Timer.stop("get_words"); printLoadingSummary(sequence, words, positionsTime, wordsTime); - if (!cancel.isCanceled()) { // do not store empty results from aborted queries in the cache + if (!cancel.isCanceled()) { // do not cache empty results from aborted queries SlowQueryStats.add(SlowQueryStats.generateKey(language, sequence, wordFilter, minWords), (int) (positionsTime + wordsTime), positions); } diff --git a/app/src/main/java/io/github/sspanak/tt9/hacks/DeviceInfo.java b/app/src/main/java/io/github/sspanak/tt9/hacks/DeviceInfo.java index 60e586ff..505e5daf 100644 --- a/app/src/main/java/io/github/sspanak/tt9/hacks/DeviceInfo.java +++ b/app/src/main/java/io/github/sspanak/tt9/hacks/DeviceInfo.java @@ -18,7 +18,7 @@ public class DeviceInfo { return context.getResources().getDisplayMetrics().heightPixels; } - public static boolean noBackspaceKey(Context context) { + public static boolean noBackspaceKey() { return !KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DEL) && !KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_CLEAR); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/CommandHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/CommandHandler.java index 19b71958..17d05398 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/CommandHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/CommandHandler.java @@ -6,6 +6,7 @@ import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.db.DataStore; import io.github.sspanak.tt9.db.words.DictionaryLoader; import io.github.sspanak.tt9.ime.modes.InputMode; +import io.github.sspanak.tt9.ime.modes.InputModeKind; import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.ui.UI; import io.github.sspanak.tt9.ui.dialogs.AddWordDialog; @@ -88,6 +89,11 @@ abstract public class CommandHandler extends TextEditingHandler { return; } + if (mLanguage.isSyllabary()) { + UI.toastShortSingle(this, R.string.function_add_word_not_available); + return; + } + if (DictionaryLoader.getInstance(this).isRunning()) { UI.toastShortSingle(this, R.string.dictionary_loading_please_wait); return; @@ -134,10 +140,10 @@ abstract public class CommandHandler extends TextEditingHandler { protected void nextInputMode() { - if (mInputMode.isPassthrough() || voiceInputOps.isListening()) { + if (InputModeKind.isPassthrough(mInputMode) || voiceInputOps.isListening()) { return; } else if (allowedInputModes.size() == 1 && allowedInputModes.contains(InputMode.MODE_123)) { - mInputMode = !mInputMode.is123() ? InputMode.getInstance(settings, mLanguage, inputType, textField, InputMode.MODE_123) : mInputMode; + mInputMode = !InputModeKind.is123(mInputMode) ? InputMode.getInstance(settings, mLanguage, inputType, textField, InputMode.MODE_123) : mInputMode; } else { suggestionOps.cancelDelayedAccept(); mInputMode.onAcceptSuggestion(suggestionOps.acceptIncomplete()); @@ -159,30 +165,34 @@ abstract public class CommandHandler extends TextEditingHandler { // select the next language int previous = mEnabledLanguages.indexOf(mLanguage.getId()); int next = (previous + 1) % mEnabledLanguages.size(); - mLanguage = LanguageCollection.getLanguage(getApplicationContext(), mEnabledLanguages.get(next)); + mLanguage = LanguageCollection.getLanguage(mEnabledLanguages.get(next)); // validate and save it for the next time validateLanguages(); } - protected void nextTextCase() { + protected boolean nextTextCase() { if (suggestionOps.isEmpty() || mInputMode.getSuggestions().isEmpty()) { // When there are no suggestions, there is no need to execute the code for // adjusting them below. if (mInputMode.nextTextCase()) { settings.saveTextCase(mInputMode.getTextCase()); + return true; + } else { + return false; } - return; } // When we are in AUTO mode and current dictionary word is in uppercase, // the mode would switch to UPPERCASE, but visually, the word would not change. // This is why we retry, until there is a visual change. + boolean isChanged = false; String before = suggestionOps.get(0); for (int retries = 0; retries < 2 && mInputMode.nextTextCase(); retries++) { String after = mInputMode.getSuggestions().get(0); if (!after.equals(before)) { + isChanged = true; break; } } @@ -201,6 +211,8 @@ abstract public class CommandHandler extends TextEditingHandler { textField.setComposingText(suggestionOps.getCurrent()); settings.saveTextCase(mInputMode.getTextCase()); + + return isChanged; } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java index b511ee63..91517554 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java @@ -2,9 +2,11 @@ package io.github.sspanak.tt9.ime; import android.view.KeyEvent; +import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.db.words.DictionaryLoader; import io.github.sspanak.tt9.ime.helpers.TextField; -import io.github.sspanak.tt9.ime.modes.ModePredictive; +import io.github.sspanak.tt9.ime.modes.InputMode; +import io.github.sspanak.tt9.ime.modes.InputModeKind; import io.github.sspanak.tt9.preferences.helpers.Hotkeys; import io.github.sspanak.tt9.ui.UI; import io.github.sspanak.tt9.util.Ternary; @@ -103,7 +105,15 @@ public abstract class HotkeyHandler extends CommandHandler { } if (keyCode == settings.getKeyShift()) { - return onKeyNextTextCase(validateOnly); + return + onKeyNextTextCase(validateOnly) + // when "Shift" and "Korean Space" share the same key, allow typing a space, when there + // are no special characters to shift + || (keyCode == settings.getKeySpaceKorean() && onKeySpaceKorean(validateOnly)); + } + + if (keyCode == settings.getKeySpaceKorean()) { + return onText(" ", validateOnly); } if (keyCode == settings.getKeyShowSettings()) { @@ -176,7 +186,7 @@ public abstract class HotkeyHandler extends CommandHandler { public boolean onKeyFilterClear(boolean validateOnly) { - if (suggestionOps.isEmpty()) { + if (suggestionOps.isEmpty() || mLanguage.isSyllabary()) { return false; } @@ -205,6 +215,11 @@ public abstract class HotkeyHandler extends CommandHandler { return false; } + if (mLanguage.isSyllabary()) { + UI.toastShortSingle(this, R.string.function_filter_suggestions_not_available); + return true; // prevent the default key action to acknowledge we have processed the event + } + if (validateOnly) { return true; } @@ -247,7 +262,7 @@ public abstract class HotkeyHandler extends CommandHandler { public boolean onKeyNextLanguage(boolean validateOnly) { - if (mInputMode.isNumeric() || mEnabledLanguages.size() < 2) { + if (InputModeKind.isNumeric(mInputMode) || mEnabledLanguages.size() < 2) { return false; } @@ -257,17 +272,21 @@ public abstract class HotkeyHandler extends CommandHandler { suggestionOps.cancelDelayedAccept(); nextLang(); - mInputMode.changeLanguage(mLanguage); - mInputMode.clearWordStem(); - getSuggestions(); + // for languages that do not have ABC or Predictive, make sure we remain in valid state + if (!mInputMode.changeLanguage(mLanguage)) { + mInputMode = InputMode.getInstance(settings, mLanguage, inputType, textField, determineInputModeId()); + } + mInputMode.clearWordStem(); + + getSuggestions(); statusBar.setText(mInputMode); mainView.render(); if (!suggestionOps.isEmpty() || settings.isMainLayoutStealth()) { UI.toastShortSingle(this, mInputMode.getClass().getSimpleName(), mInputMode.toString()); } - if (mInputMode instanceof ModePredictive) { + if (InputModeKind.isPredictive(mInputMode)) { DictionaryLoader.autoLoad(this, mLanguage); } @@ -309,7 +328,9 @@ public abstract class HotkeyHandler extends CommandHandler { } suggestionOps.scheduleDelayedAccept(mInputMode.getAutoAcceptTimeout()); // restart the timer - nextTextCase(); + if (!nextTextCase()) { + return false; + } statusBar.setText(mInputMode); mainView.render(); @@ -333,6 +354,7 @@ public abstract class HotkeyHandler extends CommandHandler { return true; } + private boolean onKeyShowSettings(boolean validateOnly) { if (!isInputViewShown() || shouldBeOff()) { return false; @@ -345,6 +367,26 @@ public abstract class HotkeyHandler extends CommandHandler { return true; } + + public boolean onKeySpaceKorean(boolean validateOnly) { + if (shouldBeOff() || !InputModeKind.isCheonjiin(mInputMode)) { + return false; + } + + // type a space when there is nothing to accept + if (suggestionOps.isEmpty() && !onText(" ", validateOnly)) { + return false; + } + + // simulate accept with OK when there are suggestions + if (!suggestionOps.isEmpty()) { + onAcceptSuggestionManually(suggestionOps.acceptCurrent(), KeyEvent.KEYCODE_ENTER); + } + + return true; + } + + private boolean onKeyVoiceInput(boolean validateOnly) { if (!isInputViewShown() || shouldBeOff() || !voiceInputOps.isAvailable()) { return false; diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/MainViewHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/MainViewHandler.java index 71334d60..f9ae508b 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/MainViewHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/MainViewHandler.java @@ -1,9 +1,10 @@ package io.github.sspanak.tt9.ime; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.github.sspanak.tt9.ime.helpers.OrientationListener; -import io.github.sspanak.tt9.ime.modes.ModeABC; +import io.github.sspanak.tt9.ime.modes.InputModeKind; import io.github.sspanak.tt9.ime.voice.VoiceInputOps; import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.preferences.settings.SettingsStore; @@ -35,32 +36,28 @@ abstract public class MainViewHandler extends HotkeyHandler { } } - public int getTextCase() { - return mInputMode.getTextCase(); - } - public boolean isInputLimited() { return inputType.isLimited(); } public boolean isInputModeABC() { - return mInputMode.getClass().equals(ModeABC.class); + return InputModeKind.isABC(mInputMode); } public boolean isInputModeNumeric() { - return mInputMode.is123(); + return InputModeKind.isNumeric(mInputMode); } public boolean isNumericModeStrict() { - return mInputMode.is123() && inputType.isNumeric() && !inputType.isPhoneNumber(); + return InputModeKind.is123(mInputMode) && inputType.isNumeric() && !inputType.isPhoneNumber(); } public boolean isNumericModeSigned() { - return mInputMode.is123() && inputType.isSignedNumber(); + return InputModeKind.is123(mInputMode) && inputType.isSignedNumber(); } public boolean isInputModePhone() { - return mInputMode.is123() && inputType.isPhoneNumber(); + return InputModeKind.is123(mInputMode) && inputType.isPhoneNumber(); } public boolean isTextEditingActive() { @@ -75,6 +72,29 @@ abstract public class MainViewHandler extends HotkeyHandler { return !(new VoiceInputOps(this, null, null, null)).isAvailable(); } + public boolean notLanguageSyllabary() { + return mLanguage == null || !mLanguage.isSyllabary(); + } + + public String getABCString() { + return mLanguage == null || mLanguage.isSyllabary() ? "ABC" : mLanguage.getAbcString().toUpperCase(mLanguage.getLocale()); + } + + @NonNull + public String getInputModeName() { + if (InputModeKind.isPredictive(mInputMode)) { + return "T9"; + } else if (InputModeKind.isNumeric(mInputMode)){ + return "123"; + } else { + return getABCString(); + } + } + + public int getTextCase() { + return mInputMode.getTextCase(); + } + @Nullable public Language getLanguage() { return mLanguage; diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/TextEditingHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/TextEditingHandler.java index e9076119..a9939fdd 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/TextEditingHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/TextEditingHandler.java @@ -3,6 +3,7 @@ package io.github.sspanak.tt9.ime; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; +import io.github.sspanak.tt9.ime.modes.InputModeKind; import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.languages.LanguageKind; import io.github.sspanak.tt9.util.Clipboard; @@ -14,7 +15,7 @@ abstract public class TextEditingHandler extends VoiceHandler { @Override protected boolean onStart(InputConnection connection, EditorInfo field) { - isSystemRTL = LanguageKind.isRTL(LanguageCollection.getDefault(this)); + isSystemRTL = LanguageKind.isRTL(LanguageCollection.getDefault()); return super.onStart(connection, field); } @@ -42,7 +43,7 @@ abstract public class TextEditingHandler extends VoiceHandler { private void onCommand(int key) { switch (key) { case 0: - if (!mInputMode.isNumeric()) { + if (!InputModeKind.isNumeric(mInputMode)) { onText(" ", false); } break; diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/TraditionalT9.java b/app/src/main/java/io/github/sspanak/tt9/ime/TraditionalT9.java index 8d7b7a33..1adeff56 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/TraditionalT9.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/TraditionalT9.java @@ -12,7 +12,7 @@ import androidx.annotation.NonNull; import io.github.sspanak.tt9.db.DataStore; import io.github.sspanak.tt9.db.words.DictionaryLoader; import io.github.sspanak.tt9.hacks.InputType; -import io.github.sspanak.tt9.ime.modes.ModePredictive; +import io.github.sspanak.tt9.ime.modes.InputModeKind; import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.ui.UI; @@ -119,6 +119,7 @@ public class TraditionalT9 extends MainViewHandler { isDead = false; settings.setDemoMode(false); Logger.setLevel(settings.getLogLevel()); + LanguageCollection.init(this); DataStore.init(this); super.onInit(); } @@ -137,7 +138,7 @@ public class TraditionalT9 extends MainViewHandler { Logger.setLevel(settings.getLogLevel()); - if (mInputMode.isPassthrough()) { + if (InputModeKind.isPassthrough(mInputMode)) { onStop(); } else { backgroundTasks.removeCallbacksAndMessages(null); @@ -147,7 +148,7 @@ public class TraditionalT9 extends MainViewHandler { InputType newInputType = new InputType(connection, field); if (newInputType.isText()) { - DataStore.loadWordPairs(DictionaryLoader.getInstance(this), LanguageCollection.getAll(this, settings.getEnabledLanguageIds())); + DataStore.loadWordPairs(DictionaryLoader.getInstance(this), LanguageCollection.getAll(settings.getEnabledLanguageIds())); } if (newInputType.isNotUs(this)) { @@ -238,7 +239,7 @@ public class TraditionalT9 extends MainViewHandler { @Override protected boolean onNumber(int key, boolean hold, int repeat) { - if (mInputMode instanceof ModePredictive && DictionaryLoader.autoLoad(this, mLanguage)) { + if (InputModeKind.isPredictive(mInputMode) && DictionaryLoader.autoLoad(this, mLanguage)) { return true; } return super.onNumber(key, hold, repeat); diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java index 261c13dc..bb383452 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java @@ -17,9 +17,10 @@ import io.github.sspanak.tt9.ime.helpers.SuggestionOps; import io.github.sspanak.tt9.ime.helpers.TextField; import io.github.sspanak.tt9.ime.helpers.TextSelection; import io.github.sspanak.tt9.ime.modes.InputMode; -import io.github.sspanak.tt9.ime.modes.ModePredictive; +import io.github.sspanak.tt9.ime.modes.InputModeKind; import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.LanguageCollection; +import io.github.sspanak.tt9.languages.LanguageKind; import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.ui.UI; import io.github.sspanak.tt9.util.Text; @@ -48,7 +49,7 @@ public abstract class TypingHandler extends KeyPadHandler { protected boolean shouldBeOff() { - return getCurrentInputConnection() == null || mInputMode.isPassthrough(); + return getCurrentInputConnection() == null || InputModeKind.isPassthrough(mInputMode); } @Override @@ -93,8 +94,8 @@ public abstract class TypingHandler extends KeyPadHandler { protected void validateLanguages() { - mEnabledLanguages = InputModeValidator.validateEnabledLanguages(getApplicationContext(), mEnabledLanguages); - mLanguage = InputModeValidator.validateLanguage(getApplicationContext(), mLanguage, mEnabledLanguages); + mEnabledLanguages = InputModeValidator.validateEnabledLanguages(mEnabledLanguages); + mLanguage = InputModeValidator.validateLanguage(mLanguage, mEnabledLanguages); settings.saveInputLanguage(mLanguage.getId()); settings.saveEnabledLanguageIds(mEnabledLanguages); } @@ -111,7 +112,7 @@ public abstract class TypingHandler extends KeyPadHandler { public boolean onBackspace(int repeat) { // Dialer fields seem to handle backspace on their own and we must ignore it, // otherwise, keyDown race condition occur for all keys. - if (mInputMode.isPassthrough()) { + if (InputModeKind.isPassthrough(mInputMode)) { return false; } @@ -172,11 +173,17 @@ public abstract class TypingHandler extends KeyPadHandler { protected boolean onNumber(int key, boolean hold, int repeat) { suggestionOps.cancelDelayedAccept(); + + // In Korean, the next char may "steal" components from the previous one, in which case, + // we must replace the previous char with a one containing less strokes. + if (mInputMode.shouldReplaceLastLetter(key, hold)) { + mInputMode.replaceLastLetter(); + } // Automatically accept the previous word, when the next one is a space or punctuation, // instead of requiring "OK" before that. // First pass, analyze the incoming key press and decide whether it could be the start of // a new word. - if (mInputMode.shouldAcceptPreviousSuggestion(key, hold)) { + else if (mInputMode.shouldAcceptPreviousSuggestion(key, hold)) { String lastWord = suggestionOps.acceptIncomplete(); mInputMode.onAcceptSuggestion(lastWord); autoCorrectSpace(lastWord, false, key); @@ -230,15 +237,15 @@ public abstract class TypingHandler extends KeyPadHandler { private void autoCorrectSpace(String currentWord, boolean isWordAcceptedManually, int nextKey) { - if (!inputType.isRustDesk() && mInputMode.shouldDeletePrecedingSpace(inputType, textField)) { + if (!inputType.isRustDesk() && mInputMode.shouldDeletePrecedingSpace()) { textField.deletePrecedingSpace(currentWord); } - if (mInputMode.shouldAddPrecedingSpace(inputType, textField)) { + if (mInputMode.shouldAddPrecedingSpace()) { textField.addPrecedingSpace(currentWord); } - if (mInputMode.shouldAddTrailingSpace(inputType, textField, isWordAcceptedManually, nextKey)) { + if (mInputMode.shouldAddTrailingSpace(isWordAcceptedManually, nextKey)) { textField.setText(" "); } } @@ -253,10 +260,10 @@ public abstract class TypingHandler extends KeyPadHandler { mEnabledLanguages = settings.getEnabledLanguageIds(); int oldLang = mLanguage != null ? mLanguage.getId() : -1; - mLanguage = LanguageCollection.getLanguage(getApplicationContext(), settings.getInputLanguage()); + mLanguage = LanguageCollection.getLanguage(settings.getInputLanguage()); validateLanguages(); - Language appLanguage = textField.getLanguage(getApplicationContext(), mEnabledLanguages); + Language appLanguage = textField.getLanguage(mEnabledLanguages); if (appLanguage != null) { mLanguage = appLanguage; } @@ -270,7 +277,6 @@ public abstract class TypingHandler extends KeyPadHandler { * Restore the last used text case or auto-select a new one based on the input field properties. */ protected void determineTextCase() { - mInputMode.setTextFieldCase(inputType.determineTextCase()); InputModeValidator.validateTextCase(mInputMode, settings.getTextCase()); } @@ -287,7 +293,11 @@ public abstract class TypingHandler extends KeyPadHandler { return InputMode.MODE_PASSTHROUGH; } - allowedInputModes = inputType.determineInputModes(this); + allowedInputModes = new ArrayList<>(inputType.determineInputModes(getApplicationContext())); + if (LanguageKind.isKorean(mLanguage) && allowedInputModes.contains(InputMode.MODE_ABC)) { + allowedInputModes.remove(InputMode.MODE_ABC); + } + return InputModeValidator.validateMode(settings.getInputMode(), allowedInputModes); } @@ -351,7 +361,7 @@ public abstract class TypingHandler extends KeyPadHandler { } protected void getSuggestions() { - if (mInputMode instanceof ModePredictive && DictionaryLoader.getInstance(this).isRunning()) { + if (InputModeKind.isPredictive(mInputMode) && DictionaryLoader.getInstance(this).isRunning()) { mInputMode.reset(); UI.toastShortSingle(this, R.string.dictionary_loading_please_wait); } else { @@ -378,7 +388,7 @@ public abstract class TypingHandler extends KeyPadHandler { // but there are no words for the new language, we'll get only generated suggestions, consisting // of the last word of the previous language + endings from the new language. These words are invalid, // so we discard them. - if (mInputMode instanceof ModePredictive && !mLanguage.isValidWord(suggestionOps.getCurrent()) && !Text.isGraphic(suggestionOps.getCurrent())) { + if (InputModeKind.isPredictive(mInputMode) && !mLanguage.isValidWord(suggestionOps.getCurrent()) && !Text.isGraphic(suggestionOps.getCurrent())) { mInputMode.reset(); suggestionOps.set(null); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/UiHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/UiHandler.java index f6328b47..92a6168c 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/UiHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/UiHandler.java @@ -6,6 +6,7 @@ import android.view.inputmethod.InputMethodManager; import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.hacks.DeviceInfo; import io.github.sspanak.tt9.ime.modes.InputMode; +import io.github.sspanak.tt9.ime.modes.InputModeKind; import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.ui.main.ResizableMainView; import io.github.sspanak.tt9.ui.tray.StatusBar; @@ -56,7 +57,7 @@ abstract class UiHandler extends AbstractHandler { protected void setStatusIcon(InputMode mode) { - if (!mode.isPassthrough() && settings.isStatusIconEnabled()) { + if (!InputModeKind.isPassthrough(mode) && settings.isStatusIconEnabled()) { showStatusIcon(R.drawable.ic_status); } else { hideStatusIcon(); diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/InputField.java b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/InputField.java index 0848d6e2..dcee51f1 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/InputField.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/InputField.java @@ -1,6 +1,5 @@ package io.github.sspanak.tt9.ime.helpers; -import android.content.Context; import android.os.Build; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; @@ -85,13 +84,13 @@ public class InputField { * it's a text field where the language doesn't matter, the function returns null. */ @Nullable - public Language getLanguage(Context context, ArrayList allowedLanguageIds) { + public Language getLanguage(ArrayList allowedLanguageIds) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { return null; } for (int i = 0; field.hintLocales != null && i < field.hintLocales.size(); i++) { - Language lang = LanguageCollection.getByLanguageCode(context, field.hintLocales.get(i).getLanguage()); + Language lang = LanguageCollection.getByLanguageCode(field.hintLocales.get(i).getLanguage()); if (lang != null && allowedLanguageIds.contains(lang.getId())) { return lang; } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/InputModeValidator.java b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/InputModeValidator.java index 923348a2..7f80013a 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/InputModeValidator.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/InputModeValidator.java @@ -1,38 +1,36 @@ package io.github.sspanak.tt9.ime.helpers; -import android.content.Context; - import java.util.ArrayList; -import io.github.sspanak.tt9.util.Logger; import io.github.sspanak.tt9.ime.modes.InputMode; import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.LanguageCollection; +import io.github.sspanak.tt9.util.Logger; public class InputModeValidator { - public static ArrayList validateEnabledLanguages(Context context, ArrayList enabledLanguageIds) { - ArrayList validLanguages = LanguageCollection.getAll(context, enabledLanguageIds); + public static ArrayList validateEnabledLanguages(ArrayList enabledLanguageIds) { + ArrayList validLanguages = LanguageCollection.getAll(enabledLanguageIds); ArrayList validLanguageIds = new ArrayList<>(); for (Language lang : validLanguages) { validLanguageIds.add(lang.getId()); } if (validLanguageIds.isEmpty()) { - validLanguageIds.add(LanguageCollection.getDefault(context).getId()); + validLanguageIds.add(LanguageCollection.getDefault().getId()); Logger.e("validateEnabledLanguages", "The language list seems to be corrupted. Resetting to first language only."); } return validLanguageIds; } - public static Language validateLanguage(Context context, Language language, ArrayList validLanguageIds) { + public static Language validateLanguage(Language language, ArrayList validLanguageIds) { if (language != null && validLanguageIds.contains(language.getId())) { return language; } String error = language != null ? "Language: " + language.getId() + " is not enabled." : "Invalid language."; - Language validLanguage = LanguageCollection.getLanguage(context, validLanguageIds.get(0)); - validLanguage = validLanguage != null ? validLanguage : LanguageCollection.getDefault(context); + Language validLanguage = LanguageCollection.getLanguage(validLanguageIds.get(0)); + validLanguage = validLanguage != null ? validLanguage : LanguageCollection.getDefault(); Logger.d("validateLanguage", error + " Enforcing language: " + validLanguage.getId()); diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/StandardInputType.java b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/StandardInputType.java index 223df098..096f9d57 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/StandardInputType.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/StandardInputType.java @@ -5,7 +5,6 @@ import android.text.InputType; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; -import java.util.ArrayList; import java.util.HashSet; import java.util.Set; @@ -130,14 +129,14 @@ abstract public class StandardInputType { * determineInputModes * Determine the typing mode based on the input field being edited. Returns an ArrayList of the allowed modes. * - * @return ArrayList + * @return Set */ - public ArrayList determineInputModes(Context context) { + public Set determineInputModes(Context context) { Set allowedModes = new HashSet<>(); if (field == null) { allowedModes.add(InputMode.MODE_PASSTHROUGH); - return new ArrayList<>(allowedModes); + return allowedModes; } // Calculators (only 0-9 and math) and Dialer (0-9, "#" and "*") fields @@ -145,7 +144,7 @@ abstract public class StandardInputType { // Note: A Dialer field is not a Phone number field. if (isSpecialNumeric(context)) { allowedModes.add(InputMode.MODE_PASSTHROUGH); - return new ArrayList<>(allowedModes); + return allowedModes; } switch (field.inputType & InputType.TYPE_MASK_CLASS) { @@ -155,7 +154,7 @@ abstract public class StandardInputType { // Numbers, dates and phone numbers default to the numeric keyboard, // with no extra features. allowedModes.add(InputMode.MODE_123); - return new ArrayList<>(allowedModes); + return allowedModes; case InputType.TYPE_CLASS_TEXT: // This is general text editing. We will default to the @@ -179,7 +178,7 @@ abstract public class StandardInputType { allowedModes.add(InputMode.MODE_123); allowedModes.add(InputMode.MODE_ABC); - return new ArrayList<>(allowedModes); + return allowedModes; } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/InputMode.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/InputMode.java index 89788152..096fa79a 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/InputMode.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/InputMode.java @@ -8,6 +8,7 @@ import java.util.ArrayList; 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.languages.LanguageKind; import io.github.sspanak.tt9.languages.NaturalLanguage; import io.github.sspanak.tt9.languages.NullLanguage; import io.github.sspanak.tt9.preferences.settings.SettingsStore; @@ -28,11 +29,11 @@ abstract public class InputMode { public static final int CASE_DICTIONARY = 3; // do not force it, but use the dictionary word as-is protected final ArrayList allowedTextCases = new ArrayList<>(); protected int textCase = CASE_LOWER; - protected int textFieldTextCase = CASE_UNDEFINED; // data protected int autoAcceptTimeout = -1; @NonNull protected String digitSequence = ""; + protected final boolean isEmailMode; @NonNull protected Language language = new NullLanguage(); protected final SettingsStore settings; @NonNull protected final ArrayList suggestions = new ArrayList<>(); @@ -40,7 +41,8 @@ abstract public class InputMode { protected int specialCharSelectedGroup = 0; - protected InputMode(SettingsStore settings) { + protected InputMode(SettingsStore settings, InputType inputType) { + isEmailMode = inputType != null && inputType.isEmail(); this.settings = settings; } @@ -48,15 +50,15 @@ abstract public class InputMode { public static InputMode getInstance(SettingsStore settings, @Nullable Language language, InputType inputType, TextField textField, int mode) { switch (mode) { case MODE_PREDICTIVE: - return new ModePredictive(settings, inputType, textField, language); + return (LanguageKind.isKorean(language) ? new ModeCheonjiin(settings, inputType, textField) : new ModeWords(settings, language, inputType, textField)); case MODE_ABC: - return new ModeABC(settings, inputType, language); + return new ModeABC(settings, language, inputType); case MODE_PASSTHROUGH: - return new ModePassthrough(settings); + return new ModePassthrough(settings, inputType); default: Logger.w("InputMode", "Defaulting to mode: " + Mode123.class.getName() + " for unknown InputMode: " + mode); case MODE_123: - return new Mode123(settings, inputType, language); + return new Mode123(settings, language, inputType); } } @@ -92,11 +94,6 @@ abstract public class InputMode { return this; } - // Numeric mode identifiers. "instanceof" cannot be used in all cases, because they inherit each other. - public boolean is123() { return false; } - public boolean isPassthrough() { return false; } - public boolean isNumeric() { return false; } - // Utility abstract public int getId(); public boolean containsGeneratedSuggestions() { return false; } @@ -105,19 +102,36 @@ abstract public class InputMode { public int getAutoAcceptTimeout() { return autoAcceptTimeout; } - public void changeLanguage(@Nullable Language newLanguage) { + + /** + * Switches to a new language if the input mode supports it. If the InputMode return "false", + * it does not support that language, so you must obtain a compatible alternative using the + * getInstance() method and the same ID. + * The default implementation is to switch to the new language (including NullLanguage) and + * return "true". + */ + public boolean changeLanguage(@Nullable Language newLanguage) { + setLanguage(newLanguage); + return true; + } + + protected void setLanguage(@Nullable Language newLanguage) { language = newLanguage != null ? newLanguage : new NullLanguage(); } + // Interaction with the IME. Return "true" if it should perform the respective action. public boolean shouldAcceptPreviousSuggestion(String unacceptedText) { return false; } public boolean shouldAcceptPreviousSuggestion(int nextKey, boolean hold) { return false; } - public boolean shouldAddTrailingSpace(InputType inputType, TextField textField, boolean isWordAcceptedManually, int nextKey) { return false; } - public boolean shouldAddPrecedingSpace(InputType inputType, TextField textField) { return false; } - public boolean shouldDeletePrecedingSpace(InputType inputType, TextField textField) { return false; } + public boolean shouldAddTrailingSpace(boolean isWordAcceptedManually, int nextKey) { return false; } + public boolean shouldAddPrecedingSpace() { return false; } + public boolean shouldDeletePrecedingSpace() { return false; } public boolean shouldIgnoreText(String text) { return text == null || text.isEmpty(); } + public boolean shouldReplaceLastLetter(int nextKey, boolean hold) { return false; } public boolean shouldSelectNextSuggestion() { return false; } + public boolean recompose(String word) { return false; } + public void replaceLastLetter() {} public void reset() { autoAcceptTimeout = -1; @@ -137,10 +151,6 @@ abstract public class InputMode { return true; } - public void setTextFieldCase(int newTextCase) { - textFieldTextCase = allowedTextCases.contains(newTextCase) ? newTextCase : CASE_UNDEFINED; - } - public void defaultTextCase() { textCase = allowedTextCases.get(0); } @@ -166,6 +176,11 @@ abstract public class InputMode { protected String adjustSuggestionTextCase(String word, int newTextCase) { return word; } + protected boolean shouldSelectNextSpecialCharacters() { + return !digitSequence.isEmpty(); + } + + /** * This is used in nextTextCase() for switching to the next set of characters. Obviously, * special chars do not have a text case, but we use this trick to alternate the char groups. @@ -175,16 +190,13 @@ abstract public class InputMode { specialCharSelectedGroup++; return - loadSpecialCharacters() // validates specialCharSelectedGroup + shouldSelectNextSpecialCharacters() // check if the operation makes sense at all + && loadSpecialCharacters() // validates specialCharSelectedGroup and advances, if possible && previousGroup != specialCharSelectedGroup; // verifies validation has passed } protected boolean loadSpecialCharacters() { - if (digitSequence.isEmpty()) { - return false; - } - int key = digitSequence.charAt(0) - '0'; ArrayList chars = settings.getOrderedKeyChars(language, key, specialCharSelectedGroup); diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/InputModeKind.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/InputModeKind.java new file mode 100644 index 00000000..04f94c81 --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/InputModeKind.java @@ -0,0 +1,27 @@ +package io.github.sspanak.tt9.ime.modes; + +public class InputModeKind { + public static boolean isPassthrough(InputMode mode) { + return mode != null && mode.getId() == InputMode.MODE_PASSTHROUGH; + } + + public static boolean is123(InputMode mode) { + return mode != null && mode.getId() == InputMode.MODE_123; + } + + public static boolean isNumeric(InputMode mode) { + return isPassthrough(mode) || is123(mode); + } + + public static boolean isABC(InputMode mode) { + return mode != null && mode.getId() == InputMode.MODE_ABC; + } + + public static boolean isPredictive(InputMode mode) { + return mode != null && mode.getId() == InputMode.MODE_PREDICTIVE; + } + + public static boolean isCheonjiin(InputMode mode) { + return mode != null && mode.getClass().equals(ModeCheonjiin.class); + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/Mode123.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/Mode123.java index d83a5658..7ace7d4e 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/Mode123.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/Mode123.java @@ -10,25 +10,20 @@ import io.github.sspanak.tt9.languages.NaturalLanguage; import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.util.Characters; -public class Mode123 extends ModePassthrough { +class Mode123 extends ModePassthrough { @Override public int getId() { return MODE_123; } @Override @NonNull public String toString() { return "123"; } - @Override public final boolean is123() { return true; } - @Override public boolean isPassthrough() { return false; } @Override public int getSequenceLength() { return digitSequence.length(); } @Override public boolean shouldAcceptPreviousSuggestion(int nextKey, boolean hold) { return true; } private final ArrayList> KEY_CHARACTERS = new ArrayList<>(); - private final boolean isEmailMode; - public Mode123(SettingsStore settings, InputType inputType, Language language) { - super(settings); + protected Mode123(SettingsStore settings, Language language, InputType inputType) { + super(settings, inputType); changeLanguage(language); - isEmailMode = inputType.isEmail(); - if (inputType.isPhoneNumber()) { setSpecificSpecialCharacters(Characters.Phone, false); } else if (inputType.isNumeric()) { @@ -59,8 +54,14 @@ public class Mode123 extends ModePassthrough { } + @Override + protected boolean shouldSelectNextSpecialCharacters() { + return !isEmailMode && digitSequence.equals(NaturalLanguage.SPECIAL_CHAR_KEY); + } + + @Override protected boolean nextSpecialCharacters() { - if (isEmailMode || !digitSequence.equals(NaturalLanguage.SPECIAL_CHAR_KEY) || !super.nextSpecialCharacters()) { + if (!super.nextSpecialCharacters()) { return false; } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeABC.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeABC.java index 7eb3be13..8b5e635d 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeABC.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeABC.java @@ -12,18 +12,18 @@ import io.github.sspanak.tt9.languages.NaturalLanguage; import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.util.Characters; -public class ModeABC extends InputMode { +class ModeABC extends InputMode { private final ArrayList> KEY_CHARACTERS = new ArrayList<>(); private boolean shouldSelectNextLetter = false; @Override public int getId() { return MODE_ABC; } - ModeABC(SettingsStore settings, InputType inputType, Language lang) { - super(settings); + protected ModeABC(SettingsStore settings, Language lang, InputType inputType) { + super(settings, inputType); changeLanguage(lang); - if (inputType.isEmail()) { + if (isEmailMode) { KEY_CHARACTERS.add(applyPunctuationOrder(Characters.Email.get(0), 0)); KEY_CHARACTERS.add(applyPunctuationOrder(Characters.Email.get(1), 1)); } @@ -76,18 +76,27 @@ public class ModeABC extends InputMode { } @Override - protected boolean nextSpecialCharacters() { - if (KEY_CHARACTERS.isEmpty() && digitSequence.equals(NaturalLanguage.SPECIAL_CHAR_KEY) && super.nextSpecialCharacters()) { - suggestions.add(language.getKeyNumber(digitSequence.charAt(0) - '0')); - return true; - } - - return false; + protected boolean shouldSelectNextSpecialCharacters() { + return KEY_CHARACTERS.isEmpty() && digitSequence.equals(NaturalLanguage.SPECIAL_CHAR_KEY); } @Override - public void changeLanguage(@Nullable Language newLanguage) { - super.changeLanguage(newLanguage); + protected boolean nextSpecialCharacters() { + if (!super.nextSpecialCharacters()) { + return false; + } + + suggestions.add(language.getKeyNumber(digitSequence.charAt(0) - '0')); + return true; + } + + @Override + public boolean changeLanguage(@Nullable Language newLanguage) { + if (newLanguage != null && newLanguage.isSyllabary()) { + return false; + } + + setLanguage(newLanguage); allowedTextCases.clear(); allowedTextCases.add(CASE_LOWER); @@ -97,6 +106,8 @@ public class ModeABC extends InputMode { refreshSuggestions(); shouldSelectNextLetter = true; // do not accept any previous suggestions after loading the new ones + + return true; } @Override public void onAcceptSuggestion(@NonNull String w) { reset(); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeCheonjiin.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeCheonjiin.java new file mode 100644 index 00000000..869152ec --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeCheonjiin.java @@ -0,0 +1,407 @@ +package io.github.sspanak.tt9.ime.modes; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.ArrayList; + +import io.github.sspanak.tt9.hacks.InputType; +import io.github.sspanak.tt9.ime.helpers.TextField; +import io.github.sspanak.tt9.ime.modes.helpers.AutoSpace; +import io.github.sspanak.tt9.ime.modes.helpers.Cheonjiin; +import io.github.sspanak.tt9.ime.modes.predictions.Predictions; +import io.github.sspanak.tt9.ime.modes.predictions.SyllablePredictions; +import io.github.sspanak.tt9.languages.EmojiLanguage; +import io.github.sspanak.tt9.languages.Language; +import io.github.sspanak.tt9.languages.LanguageCollection; +import io.github.sspanak.tt9.languages.LanguageKind; +import io.github.sspanak.tt9.languages.NaturalLanguage; +import io.github.sspanak.tt9.preferences.settings.SettingsStore; +import io.github.sspanak.tt9.util.Characters; + +class ModeCheonjiin extends InputMode { + // used when we want do display a different set of characters for a given key, for example + // in email fields + private final ArrayList> KEY_CHARACTERS = new ArrayList<>(); + + // special chars and emojis + private static String SPECIAL_CHAR_SEQUENCE_PREFIX; + protected String CUSTOM_EMOJI_SEQUENCE; + protected String EMOJI_SEQUENCE; + protected String PUNCTUATION_SEQUENCE; + protected String SPECIAL_CHAR_SEQUENCE; + + // predictions + protected boolean disablePredictions = false; + protected Predictions predictions; + @NonNull private String previousJamoSequence = ""; + + // text analysis + protected final AutoSpace autoSpace; + protected final InputType inputType; + protected final TextField textField; + + + protected ModeCheonjiin(SettingsStore settings, InputType inputType, TextField textField) { + super(settings, inputType); + + SPECIAL_CHAR_SEQUENCE_PREFIX = settings.holdForPunctuationInKorean() ? "11" : "1"; + + digitSequence = ""; + allowedTextCases.add(CASE_LOWER); + this.inputType = inputType; + this.textField = textField; + + initPredictions(); + setLanguage(LanguageCollection.getLanguage(LanguageKind.KOREAN)); + setSpecialCharacterConstants(); + + if (isEmailMode) { + // Note: applyPunctuationOrder() requires the language to be set + KEY_CHARACTERS.add(applyPunctuationOrder(Characters.Email.get(0), 0)); + KEY_CHARACTERS.add(applyPunctuationOrder(Characters.Email.get(1), 1)); + } else { + setCustomSpecialCharacters(); + } + + autoSpace = new AutoSpace(settings).setLanguage(language); + } + + + protected void setCustomSpecialCharacters() { + if (settings.holdForPunctuationInKorean()) { + ArrayList specialChars = new ArrayList<>(applyPunctuationOrder(Characters.Special, 0)); + specialChars.add(0, "0"); + KEY_CHARACTERS.add(specialChars); + } + } + + + + protected void setSpecialCharacterConstants() { + CUSTOM_EMOJI_SEQUENCE = SPECIAL_CHAR_SEQUENCE_PREFIX + EmojiLanguage.CUSTOM_EMOJI_SEQUENCE; + EMOJI_SEQUENCE = SPECIAL_CHAR_SEQUENCE_PREFIX + EmojiLanguage.EMOJI_SEQUENCE; + PUNCTUATION_SEQUENCE = SPECIAL_CHAR_SEQUENCE_PREFIX + NaturalLanguage.PUNCTUATION_KEY; + SPECIAL_CHAR_SEQUENCE = "000"; + } + + + protected void initPredictions() { + predictions = new SyllablePredictions(settings); + predictions + .setOnlyExactMatches(true) + .setMinWords(0) + .setWordsChangedHandler(this::onPredictions); + } + + + @Override + public boolean onBackspace() { + if (settings.holdForPunctuationInKorean() && digitSequence.equals(PUNCTUATION_SEQUENCE)) { + digitSequence = ""; + } else if (digitSequence.equals(SPECIAL_CHAR_SEQUENCE) || (!digitSequence.startsWith(PUNCTUATION_SEQUENCE) && Cheonjiin.isSingleJamo(digitSequence))) { + digitSequence = ""; + } else if (!digitSequence.isEmpty()) { + digitSequence = digitSequence.substring(0, digitSequence.length() - 1); + } + + return !digitSequence.isEmpty(); + } + + + @Override + public boolean onNumber(int number, boolean hold, int repeat) { + if (hold) { + reset(); + digitSequence = String.valueOf(number); + disablePredictions = true; + onNumberHold(number); + } else { + basicReset(); + disablePredictions = false; + onNumberPress(number); + } + + return true; + } + + + protected void onNumberHold(int number) { + if (settings.holdForPunctuationInKorean() && number == 0) { + disablePredictions = false; + digitSequence = SPECIAL_CHAR_SEQUENCE; + } else if (settings.holdForPunctuationInKorean() && number == 1) { + disablePredictions = false; + digitSequence = PUNCTUATION_SEQUENCE; + } else { + autoAcceptTimeout = 0; + suggestions.add(language.getKeyNumber(number)); + } + } + + + protected void onNumberPress(int nextNumber) { + int rewindAmount = shouldRewindRepeatingNumbers(nextNumber); + if (rewindAmount > 0) { + digitSequence = digitSequence.substring(0, digitSequence.length() - rewindAmount); + } + + if (digitSequence.startsWith(PUNCTUATION_SEQUENCE)) { + digitSequence = SPECIAL_CHAR_SEQUENCE_PREFIX + EmojiLanguage.validateEmojiSequence(digitSequence.substring(SPECIAL_CHAR_SEQUENCE_PREFIX.length()), nextNumber); + } else { + digitSequence += String.valueOf(nextNumber); + } + } + + + private int shouldRewindRepeatingNumbers(int nextNumber) { + final int nextChar = nextNumber + '0'; + final int repeatingDigits = digitSequence.length() > 1 && digitSequence.charAt(digitSequence.length() - 1) == nextChar ? Cheonjiin.getRepeatingEndingDigits(digitSequence) : 0; + final int keyCharsCount = nextNumber == 0 ? 2 : language.getKeyCharacters(nextNumber).size(); + + if (!settings.holdForPunctuationInKorean() && SPECIAL_CHAR_SEQUENCE.equals(digitSequence + nextNumber)) { + return 0; + } + + if (SPECIAL_CHAR_SEQUENCE.equals(digitSequence)) { + return SPECIAL_CHAR_SEQUENCE.length(); + } + + if (repeatingDigits == 0 || keyCharsCount < 2) { + return 0; + } + + return keyCharsCount < repeatingDigits + 1 ? repeatingDigits : 0; + } + + + @Override + public boolean changeLanguage(@Nullable Language newLanguage) { + return LanguageKind.isKorean(newLanguage); + } + + + @Override + public void reset() { + basicReset(); + digitSequence = ""; + previousJamoSequence = ""; + disablePredictions = false; + } + + + protected void basicReset() { + super.reset(); + } + + + @Override + public void loadSuggestions(String ignored) { + if (disablePredictions || loadSpecialCharacters() || loadEmojis()) { + onSuggestionsUpdated.run(); + return; + } + + String seq = digitSequence; + if (shouldDisplayCustomEmojis()) { + seq = digitSequence.substring(SPECIAL_CHAR_SEQUENCE_PREFIX.length()); + } else if (!previousJamoSequence.isEmpty()) { + seq = previousJamoSequence; + } + + predictions + .setLanguage(shouldDisplayCustomEmojis() ? new EmojiLanguage() : language) + .setDigitSequence(seq) + .load(); + } + + + protected boolean loadEmojis() { + if (shouldDisplayEmojis()) { + suggestions.clear(); + suggestions.addAll(new EmojiLanguage().getKeyCharacters(digitSequence.charAt(0) - '0', getEmojiGroup())); + return true; + } + + return false; + } + + + protected int getEmojiGroup() { + return digitSequence.length() - EMOJI_SEQUENCE.length(); + } + + + protected boolean shouldDisplayEmojis() { + return !isEmailMode && digitSequence.startsWith(EMOJI_SEQUENCE) && !digitSequence.equals(CUSTOM_EMOJI_SEQUENCE); + } + + + protected boolean shouldDisplayCustomEmojis() { + return !isEmailMode && digitSequence.equals(CUSTOM_EMOJI_SEQUENCE); + } + + + @Override + protected boolean loadSpecialCharacters() { + if (!shouldDisplaySpecialCharacters()) { + return false; + } + + int number = digitSequence.isEmpty() ? Integer.MAX_VALUE : digitSequence.charAt(0) - '0'; + if (KEY_CHARACTERS.size() > number) { + suggestions.clear(); + suggestions.addAll(KEY_CHARACTERS.get(number)); + return true; + } else { + return super.loadSpecialCharacters(); + } + } + + + protected boolean shouldDisplaySpecialCharacters() { + return digitSequence.equals(PUNCTUATION_SEQUENCE) || digitSequence.equals(SPECIAL_CHAR_SEQUENCE); + } + + + /** + * onPredictions + * Gets the currently available Predictions and sends them over to the external caller. + */ + protected void onPredictions() { + // in case the user hasn't added any custom emoji, do not allow advancing to the empty character group + if (predictions.getList().isEmpty() && digitSequence.startsWith(EMOJI_SEQUENCE)) { + digitSequence = EMOJI_SEQUENCE; + return; + } + + suggestions.clear(); + suggestions.addAll(predictions.getList()); + + onSuggestionsUpdated.run(); + } + + + private void onReplacementPredictions() { + autoAcceptTimeout = 0; + onPredictions(); + predictions.setWordsChangedHandler(this::onPredictions); + + autoAcceptTimeout = -1; + loadSuggestions(null); + } + + + @Override + public boolean containsGeneratedSuggestions() { + return predictions.containsGeneratedWords(); + } + + + @Override + public void replaceLastLetter() { + previousJamoSequence = Cheonjiin.stripRepeatingEndingDigits(digitSequence); + if (previousJamoSequence.isEmpty() || previousJamoSequence.length() == digitSequence.length()) { + previousJamoSequence = ""; + return; + } + + digitSequence = digitSequence.substring(previousJamoSequence.length()); + + predictions.setWordsChangedHandler(this::onReplacementPredictions); + } + + + @Override + public boolean shouldReplaceLastLetter(int nextKey, boolean hold) { + return !hold && !shouldDisplayEmojis() && Cheonjiin.isThereMediaVowel(digitSequence) && Cheonjiin.isVowelDigit(nextKey); + } + + + /** + * shouldAcceptPreviousSuggestion + * Used for analysis before processing the incoming pressed key. + */ + @Override + public boolean shouldAcceptPreviousSuggestion(int nextKey, boolean hold) { + return + (hold && !digitSequence.isEmpty()) + || (digitSequence.equals(SPECIAL_CHAR_SEQUENCE) && nextKey != 0) + || (digitSequence.startsWith(PUNCTUATION_SEQUENCE) && nextKey != 1); + } + + + /** + * shouldAcceptPreviousSuggestion + * Used for analysis after loading the suggestions. + */ + @Override + public boolean shouldAcceptPreviousSuggestion(String unacceptedText) { + return + !digitSequence.isEmpty() + && !disablePredictions && !shouldDisplayEmojis() && !shouldDisplaySpecialCharacters() && predictions.noDbWords() + && (Cheonjiin.endsWithDashVowel(digitSequence) || Cheonjiin.endsWithTwoConsonants(digitSequence)); + } + + + @Override + public void onAcceptSuggestion(@NonNull String word, boolean preserveWordList) { + if (shouldDisplaySpecialCharacters() || shouldDisplayEmojis()) { + reset(); + return; + } + + String digitSequenceStash = ""; + + boolean mustReload = false; + if (predictions.noDbWords() && digitSequence.length() >= 2) { + digitSequenceStash = digitSequence.substring(digitSequence.length() - 1); + mustReload = true; + } else if (!previousJamoSequence.isEmpty()) { + digitSequenceStash = digitSequence; + } + + reset(); + + digitSequence = digitSequenceStash; + if (mustReload) { + loadSuggestions(null); + } + } + + + protected boolean shouldSelectNextSpecialCharacters() { + return digitSequence.equals(SPECIAL_CHAR_SEQUENCE); + } + + + @Override + public boolean shouldAddTrailingSpace(boolean isWordAcceptedManually, int nextKey) { + return autoSpace.shouldAddTrailingSpace(textField, inputType, isWordAcceptedManually, nextKey); + } + + + @Override + public boolean shouldAddPrecedingSpace() { + return autoSpace.shouldAddBeforePunctuation(inputType, textField); + } + + + @Override + public boolean shouldDeletePrecedingSpace() { + return autoSpace.shouldDeletePrecedingSpace(inputType, textField); + } + + + @Override + public int getId() { + return MODE_PREDICTIVE; + } + + + @NonNull + @Override + public String toString() { + return language.getName(); + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePassthrough.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePassthrough.java index d5c961b6..2a732183 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePassthrough.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePassthrough.java @@ -2,12 +2,13 @@ package io.github.sspanak.tt9.ime.modes; import androidx.annotation.NonNull; +import io.github.sspanak.tt9.hacks.InputType; import io.github.sspanak.tt9.preferences.settings.SettingsStore; // see: InputType.isSpecialNumeric() -public class ModePassthrough extends InputMode { - ModePassthrough(SettingsStore settings) { - super(settings); +class ModePassthrough extends InputMode { + protected ModePassthrough(SettingsStore settings, InputType inputType) { + super(settings, inputType); reset(); allowedTextCases.add(CASE_LOWER); } @@ -16,9 +17,6 @@ public class ModePassthrough extends InputMode { @Override public int getSequenceLength() { return 0; } @Override @NonNull public String toString() { return "--"; } - @Override public boolean isNumeric() { return true; } - @Override public boolean isPassthrough() { return true; } - @Override public boolean onNumber(int number, boolean hold, int repeat) { return false; } @Override public boolean shouldIgnoreText(String text) { return true; } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePredictive.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeWords.java similarity index 66% rename from app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePredictive.java rename to app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeWords.java index 761fb3e0..4ec9645e 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePredictive.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeWords.java @@ -7,58 +7,58 @@ import java.util.ArrayList; import io.github.sspanak.tt9.hacks.InputType; import io.github.sspanak.tt9.ime.helpers.TextField; -import io.github.sspanak.tt9.ime.modes.helpers.AutoSpace; import io.github.sspanak.tt9.ime.modes.helpers.AutoTextCase; -import io.github.sspanak.tt9.ime.modes.helpers.Predictions; +import io.github.sspanak.tt9.ime.modes.predictions.WordPredictions; import io.github.sspanak.tt9.languages.EmojiLanguage; import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.LanguageKind; import io.github.sspanak.tt9.languages.NaturalLanguage; import io.github.sspanak.tt9.languages.exceptions.InvalidLanguageCharactersException; import io.github.sspanak.tt9.preferences.settings.SettingsStore; -import io.github.sspanak.tt9.util.Characters; import io.github.sspanak.tt9.util.Logger; import io.github.sspanak.tt9.util.Text; -import io.github.sspanak.tt9.util.TextTools; -public class ModePredictive extends InputMode { +class ModeWords extends ModeCheonjiin { private final String LOG_TAG = getClass().getSimpleName(); - private final ArrayList> KEY_CHARACTERS = new ArrayList<>(); - - public int getId() { return MODE_PREDICTIVE; } - private String lastAcceptedWord = ""; // stem filter private boolean isStemFuzzy = false; private String stem = ""; - // async suggestion handling - private boolean disablePredictions = false; - // text analysis tools - private final AutoSpace autoSpace; private final AutoTextCase autoTextCase; - private final Predictions predictions; private boolean isCursorDirectionForward = false; + private int textFieldTextCase; - ModePredictive(SettingsStore settings, InputType inputType, TextField textField, Language lang) { - super(settings); + protected ModeWords(SettingsStore settings, Language lang, InputType inputType, TextField textField) { + super(settings, inputType, textField); - autoSpace = new AutoSpace(settings).setLanguage(lang); autoTextCase = new AutoTextCase(settings); - digitSequence = ""; - predictions = new Predictions(settings, textField); changeLanguage(lang); defaultTextCase(); + determineTextFieldTextCase(); + } - if (inputType.isEmail()) { - KEY_CHARACTERS.add(applyPunctuationOrder(Characters.Email.get(0), 0)); - KEY_CHARACTERS.add(applyPunctuationOrder(Characters.Email.get(1), 1)); - } + + @Override protected void setCustomSpecialCharacters() {} // we use the default ones + + + protected void setSpecialCharacterConstants() { + PUNCTUATION_SEQUENCE = NaturalLanguage.PUNCTUATION_KEY; + EMOJI_SEQUENCE = EmojiLanguage.EMOJI_SEQUENCE; + CUSTOM_EMOJI_SEQUENCE = EmojiLanguage.CUSTOM_EMOJI_SEQUENCE; + SPECIAL_CHAR_SEQUENCE = NaturalLanguage.SPECIAL_CHAR_KEY; + } + + + @Override + protected void initPredictions() { + predictions = new WordPredictions(settings, textField); + predictions.setWordsChangedHandler(this::onPredictions); } @@ -85,31 +85,34 @@ public class ModePredictive extends InputMode { @Override public boolean onNumber(int number, boolean hold, int repeat) { isCursorDirectionForward = true; - - if (hold) { - // hold to type any digit - reset(); - autoAcceptTimeout = 0; - disablePredictions = true; - digitSequence = String.valueOf(number); - suggestions.add(language.getKeyNumber(number)); - } else { - super.reset(); - digitSequence = EmojiLanguage.validateEmojiSequence(digitSequence, number); - disablePredictions = false; - - if (digitSequence.equals(NaturalLanguage.PREFERRED_CHAR_SEQUENCE)) { - autoAcceptTimeout = 0; - } - } - - return true; + return super.onNumber(number, hold, repeat); } @Override - public void changeLanguage(@Nullable Language newLanguage) { - super.changeLanguage(newLanguage); + protected void onNumberHold(int number) { + autoAcceptTimeout = 0; + suggestions.add(language.getKeyNumber(number)); + } + + + @Override + protected void onNumberPress(int number) { + digitSequence = EmojiLanguage.validateEmojiSequence(digitSequence, number); + + if (digitSequence.equals(NaturalLanguage.PREFERRED_CHAR_SEQUENCE)) { + autoAcceptTimeout = 0; + } + } + + + @Override + public boolean changeLanguage(@Nullable Language newLanguage) { + if (newLanguage != null && newLanguage.isSyllabary()) { + return false; + } + + super.setLanguage(newLanguage); autoSpace.setLanguage(language); @@ -119,12 +122,14 @@ public class ModePredictive extends InputMode { allowedTextCases.add(CASE_CAPITALIZE); allowedTextCases.add(CASE_UPPER); } + + return true; } @Override public boolean recompose(String word) { - if (!language.hasSpaceBetweenWords()) { + if (!language.hasSpaceBetweenWords() || language.isSyllabary()) { return false; } @@ -149,7 +154,7 @@ public class ModePredictive extends InputMode { @Override public void reset() { - super.reset(); + basicReset(); digitSequence = ""; disablePredictions = false; stem = ""; @@ -252,60 +257,33 @@ public class ModePredictive extends InputMode { } - @Override - public boolean containsGeneratedSuggestions() { - return predictions.containsGeneratedWords(); - } - /** * loadSuggestions * Loads the possible list of suggestions for the current digitSequence. "currentWord" is used * for generating suggestions when there are no results. - * See: Predictions.generatePossibleCompletions() + * See: WordPredictions.generatePossibleCompletions() */ @Override public void loadSuggestions(String currentWord) { - if (disablePredictions) { - super.loadSuggestions(currentWord); + if (disablePredictions || loadPreferredChar() || loadSpecialCharacters() || loadEmojis()) { + onSuggestionsUpdated.run(); return; } - if (loadStaticSuggestions()) { - return; - } - - Language searchLanguage = digitSequence.equals(EmojiLanguage.CUSTOM_EMOJI_SEQUENCE) ? new EmojiLanguage() : language; - - predictions - .setDigitSequence(digitSequence) + ((WordPredictions) predictions) + .setInputWord(currentWord.isEmpty() ? stem : currentWord) .setIsStemFuzzy(isStemFuzzy) .setStem(stem) - .setLanguage(searchLanguage) - .setInputWord(currentWord.isEmpty() ? stem : currentWord) - .setWordsChangedHandler(this::onPredictions) + .setDigitSequence(digitSequence) + .setLanguage(shouldDisplayCustomEmojis() ? new EmojiLanguage() : language) .load(); } - /** - * loadStatic - * Loads words that are not in the database and are supposed to be in the same order, such as - * emoji or the preferred character for double "0". Returns "false", when there are no static - * options for the current digitSequence. - */ - private boolean loadStaticSuggestions() { - if (digitSequence.equals(NaturalLanguage.PUNCTUATION_KEY) || digitSequence.equals(NaturalLanguage.SPECIAL_CHAR_KEY)) { - loadSpecialCharacters(); - onSuggestionsUpdated.run(); - return true; - } else if (!digitSequence.equals(EmojiLanguage.CUSTOM_EMOJI_SEQUENCE) && digitSequence.startsWith(EmojiLanguage.EMOJI_SEQUENCE)) { - suggestions.clear(); - suggestions.addAll(new EmojiLanguage().getKeyCharacters(digitSequence.charAt(0) - '0', digitSequence.length() - 2)); - onSuggestionsUpdated.run(); - return true; - } else if (digitSequence.startsWith(NaturalLanguage.PREFERRED_CHAR_SEQUENCE)) { + + private boolean loadPreferredChar() { + if (digitSequence.startsWith(NaturalLanguage.PREFERRED_CHAR_SEQUENCE)) { suggestions.clear(); suggestions.add(settings.getDoubleZeroChar()); - onSuggestionsUpdated.run(); return true; } @@ -313,37 +291,6 @@ public class ModePredictive extends InputMode { } - @Override - protected boolean loadSpecialCharacters() { - int number = digitSequence.charAt(0) - '0'; - if (KEY_CHARACTERS.size() > number) { - suggestions.clear(); - suggestions.addAll(KEY_CHARACTERS.get(number)); - return true; - } else { - return super.loadSpecialCharacters(); - } - } - - - /** - * onPredictions - * Gets the currently available Predictions and sends them over to the external caller. - */ - private void onPredictions() { - // in case the user hasn't added any custom emoji, do not allow advancing to the empty character group - if (predictions.getList().isEmpty() && digitSequence.startsWith(EmojiLanguage.EMOJI_SEQUENCE)) { - digitSequence = EmojiLanguage.EMOJI_SEQUENCE; - return; - } - - suggestions.clear(); - suggestions.addAll(predictions.getList()); - - onSuggestionsUpdated.run(); - } - - /** * onAcceptSuggestion * Bring this word up in the suggestions list next time and if necessary preserves the suggestion list @@ -365,26 +312,21 @@ public class ModePredictive extends InputMode { return; } - if (Characters.isStaticEmoji(currentWord)) { + // emojis and special chars are not in the database, so there is no point in wasting resources + // running queries on them + if (!new Text(currentWord).isAlphabetic()) { return; } - - // increment the frequency of the given word try { - Language workingLanguage = TextTools.isGraphic(currentWord) ? new EmojiLanguage() : language; - String sequence = workingLanguage.getDigitSequenceForWord(currentWord); - - // punctuation and special chars are not in the database, so there is no point in - // running queries that would update nothing - if (!sequence.equals(NaturalLanguage.PUNCTUATION_KEY) && !sequence.startsWith(NaturalLanguage.SPECIAL_CHAR_KEY)) { - predictions.onAccept(currentWord, sequence); - } + // increment the frequency of the given word + predictions.onAccept(currentWord, language.getDigitSequenceForWord(currentWord)); } catch (Exception e) { Logger.e(LOG_TAG, "Failed incrementing priority of word: '" + currentWord + "'. " + e.getMessage()); } } + @Override protected String adjustSuggestionTextCase(String word, int newTextCase) { return autoTextCase.adjustSuggestionTextCase(new Text(language, word), newTextCase); @@ -395,17 +337,17 @@ public class ModePredictive extends InputMode { textCase = autoTextCase.determineNextWordTextCase(textCase, textFieldTextCase, textBeforeCursor, digitSequence); } + private void determineTextFieldTextCase() { + int fieldCase = inputType.determineTextCase(); + textFieldTextCase = allowedTextCases.contains(fieldCase) ? fieldCase : CASE_UNDEFINED; + } + @Override public int getTextCase() { // Filter out the internally used text cases. They have no meaning outside this class. return (textCase == CASE_UPPER || textCase == CASE_LOWER) ? textCase : CASE_CAPITALIZE; } - @Override - protected boolean nextSpecialCharacters() { - return digitSequence.equals(NaturalLanguage.SPECIAL_CHAR_KEY) && super.nextSpecialCharacters(); - } - @Override public boolean nextTextCase() { int before = textCase; @@ -424,6 +366,11 @@ public class ModePredictive extends InputMode { } + @Override + public boolean shouldReplaceLastLetter(int n, boolean h) { + return false; + } + /** * shouldAcceptPreviousSuggestion @@ -436,7 +383,7 @@ public class ModePredictive extends InputMode { return true; } - final char SPECIAL_CHAR_KEY_CODE = NaturalLanguage.SPECIAL_CHAR_KEY.charAt(0); + final char SPECIAL_CHAR_KEY_CODE = SPECIAL_CHAR_SEQUENCE.charAt(0); final int SPECIAL_CHAR_KEY = SPECIAL_CHAR_KEY_CODE - '0'; // Prevent typing the preferred character when the user has scrolled the special char suggestions. @@ -454,9 +401,9 @@ public class ModePredictive extends InputMode { } - /** + /** * shouldAcceptPreviousSuggestion - * Variant for post suggestion load analysis. + * Used for analysis after loading the suggestions. */ @Override public boolean shouldAcceptPreviousSuggestion(String unacceptedText) { @@ -473,8 +420,8 @@ public class ModePredictive extends InputMode { return !digitSequence.isEmpty() && predictions.noDbWords() - && digitSequence.contains(NaturalLanguage.PUNCTUATION_KEY) - && !digitSequence.startsWith(EmojiLanguage.EMOJI_SEQUENCE) + && digitSequence.contains(PUNCTUATION_SEQUENCE) + && !digitSequence.startsWith(EMOJI_SEQUENCE) && Text.containsOtherThan1(digitSequence); } @@ -498,24 +445,6 @@ public class ModePredictive extends InputMode { } - @Override - public boolean shouldAddTrailingSpace(InputType inputType, TextField textField, boolean isWordAcceptedManually, int nextKey) { - return autoSpace.shouldAddTrailingSpace(textField, inputType, isWordAcceptedManually, nextKey); - } - - - @Override - public boolean shouldAddPrecedingSpace(InputType inputType, TextField textField) { - return autoSpace.shouldAddBeforePunctuation(inputType, textField); - } - - - @Override - public boolean shouldDeletePrecedingSpace(InputType inputType, TextField textField) { - return autoSpace.shouldDeletePrecedingSpace(inputType, textField); - } - - @NonNull @Override public String toString() { diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/AutoSpace.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/AutoSpace.java index 962715e1..23d4ffd6 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/AutoSpace.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/AutoSpace.java @@ -21,11 +21,13 @@ public class AutoSpace { private final SettingsStore settings; private boolean isLanguageFrench; + private boolean isLanguageWithAlphabet; private boolean isLanguageWithSpaceBetweenWords; public AutoSpace(SettingsStore settingsStore) { settings = settingsStore; + isLanguageWithAlphabet = false; isLanguageFrench = false; isLanguageWithSpaceBetweenWords = true; } @@ -33,6 +35,7 @@ public class AutoSpace { public AutoSpace setLanguage(Language language) { isLanguageFrench = LanguageKind.isFrench(language); + isLanguageWithAlphabet = language != null && !language.isSyllabary(); isLanguageWithSpaceBetweenWords = language != null && language.hasSpaceBetweenWords(); return this; } @@ -120,6 +123,7 @@ public class AutoSpace { private boolean shouldAddAfterWord(boolean isWordAcceptedManually, String previousChars, Text nextChars, int nextKey) { return isWordAcceptedManually // Do not add space when auto-accepting words, because it feels very confusing when typing. + && isLanguageWithAlphabet && nextKey != 1 && nextChars.isEmpty() && Text.previousIsLetter(previousChars); diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/Cheonjiin.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/Cheonjiin.java new file mode 100644 index 00000000..546c9606 --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/Cheonjiin.java @@ -0,0 +1,75 @@ +package io.github.sspanak.tt9.ime.modes.helpers; + +import androidx.annotation.NonNull; + +import java.util.regex.Pattern; + +public class Cheonjiin { + private static final Pattern MEDIAL_VOWEL = Pattern.compile("^[4-9|0]+[1-3]+[4-9|0]+$"); + + public static boolean isThereMediaVowel(@NonNull String digitSequence) { + return !digitSequence.isEmpty() && MEDIAL_VOWEL.matcher(digitSequence).find(); + } + + private static boolean isVowelDigit(char digit) { + return digit == '1' || digit == '2' || digit == '3'; + } + + public static boolean isSingleJamo(@NonNull String digitSequence) { + int digits = digitSequence.length(); + + if (digits == 0 || digits > 3) { + return false; + } + + char firstDigit = digitSequence.charAt(0); + for (int i = 1; i < digits; i++) { + if (digitSequence.charAt(i) != firstDigit) { + return false; + } + } + + return true; + } + + public static boolean isVowelDigit(int digit) { + return digit == 1 || digit == 2 || digit == 3; + } + + public static boolean endsWithTwoConsonants(@NonNull String digitSequence) { + if (digitSequence.length() < 2) { + return false; + } + + char consonant1 = digitSequence.charAt(digitSequence.length() - 1); + for (int i = digitSequence.length() - 2; i >= 0; i--) { + if (!isVowelDigit(digitSequence.charAt(i))) { + return consonant1 != digitSequence.charAt(i); + } + } + + return false; + } + + public static boolean endsWithDashVowel(@NonNull String digitSequence) { + int lastDigit = digitSequence.isEmpty() ? -1 : digitSequence.charAt(digitSequence.length() - 1) - '0'; + return lastDigit == 1 || lastDigit == 3; + } + + public static int getRepeatingEndingDigits(@NonNull String digitSequence) { + int count = 0; + for (int i = digitSequence.length() - 1; i >= 0; i--) { + if (digitSequence.charAt(i) == digitSequence.charAt(digitSequence.length() - 1)) { + count++; + } else { + break; + } + } + return count; + } + + public static String stripRepeatingEndingDigits(@NonNull String digitSequence) { + int end = digitSequence.length() - getRepeatingEndingDigits(digitSequence); + return digitSequence.length() > 1 ? digitSequence.substring(0, end) : digitSequence; + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/Predictions.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/Predictions.java new file mode 100644 index 00000000..415a257d --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/Predictions.java @@ -0,0 +1,124 @@ +package io.github.sspanak.tt9.ime.modes.predictions; + +import androidx.annotation.NonNull; + +import java.util.ArrayList; + +import io.github.sspanak.tt9.db.DataStore; +import io.github.sspanak.tt9.db.words.WordStore; +import io.github.sspanak.tt9.languages.Language; +import io.github.sspanak.tt9.languages.NullLanguage; +import io.github.sspanak.tt9.preferences.settings.SettingsStore; + +abstract public class Predictions { + protected final SettingsStore settings; + + // settings + @NonNull protected String digitSequence = ""; + @NonNull protected Language language = new NullLanguage(); + protected int minWords = SettingsStore.SUGGESTIONS_MIN; + protected int maxWords = SettingsStore.SUGGESTIONS_MAX; + protected boolean onlyExactMatches = false; + @NonNull protected String stem = ""; + + // async operations + protected Runnable onWordsChanged = () -> {}; + + // data + protected boolean areThereDbWords = false; + protected boolean containsGeneratedWords = false; + @NonNull protected ArrayList words = new ArrayList<>(); + + + public Predictions(SettingsStore settings) { + this.settings = settings; + } + + + public Predictions setDigitSequence(String digitSequence) { + this.digitSequence = digitSequence; + return this; + } + + + public Predictions setLanguage(Language language) { + this.language = language; + return this; + } + + + public Predictions setMinWords(int minWords) { + this.minWords = minWords; + return this; + } + + + public Predictions setOnlyExactMatches(boolean onlyExactMatches) { + this.onlyExactMatches = onlyExactMatches; + return this; + } + + + public void setWordsChangedHandler(Runnable handler) { + onWordsChanged = handler; + } + + + public boolean containsGeneratedWords() { + return containsGeneratedWords; + } + + + public ArrayList getList() { + return words; + } + + + public boolean noDbWords() { + return !areThereDbWords; + } + + + /** + * suggestMissingWords + * Takes a list of words and appends them to the words list, if they are missing. + */ + protected void suggestMissingWords(ArrayList newWords) { + for (String newWord : newWords) { + if (!words.contains(newWord) && !words.contains(newWord.toLowerCase(language.getLocale()))) { + words.add(newWord); + } + } + } + + + + /** + * load + * Queries the dictionary database for a list of words matching the current language and sequence. + */ + public void load() { + containsGeneratedWords = false; + + if (digitSequence.isEmpty()) { + words.clear(); + onWordsChanged.run(); + return; + } + + DataStore.getWords( + (dbWords) -> onDbWords(dbWords, isRetryAllowed()), + language, + digitSequence, + onlyExactMatches ? WordStore.FILTER_EXACT_MATCHES_ONLY : stem, + minWords, + maxWords + ); + } + + + abstract public void onAccept(String word, String sequence); + abstract protected boolean isRetryAllowed(); + abstract protected void onDbWords(ArrayList dbWords, boolean retryAllowed); + abstract protected ArrayList generateWordVariations(String baseWord); +} diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/SyllablePredictions.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/SyllablePredictions.java new file mode 100644 index 00000000..57ea0f82 --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/SyllablePredictions.java @@ -0,0 +1,109 @@ +package io.github.sspanak.tt9.ime.modes.predictions; + +import java.util.ArrayList; + +import io.github.sspanak.tt9.ime.modes.helpers.Cheonjiin; +import io.github.sspanak.tt9.preferences.settings.SettingsStore; + +public class SyllablePredictions extends Predictions { + int defaultMinWords; + int loadAttempts; + String lastWord = ""; + String lastStableWord = ""; + int lastStableSequenceLength; + + + public SyllablePredictions(SettingsStore settings) { + super(settings); + } + + + @Override + public Predictions setMinWords(int minWords) { + defaultMinWords = minWords; + return super.setMinWords(minWords); + } + + + @Override + protected boolean isRetryAllowed() { + return loadAttempts == 0; + } + + + @Override + public void load() { + loadAttempts = 0; + minWords = defaultMinWords; + super.load(); + } + + + private void loadSimilar() { + loadAttempts++; + minWords = defaultMinWords + 1; + super.load(); + } + + + @Override + protected void onDbWords(ArrayList dbWords, boolean retryAllowed) { + areThereDbWords = !dbWords.isEmpty(); + + if (loadAttempts == 0) { + words.clear(); + } else { + onWordsChanged.run(); + return; + } + + if (digitSequence.length() < lastStableSequenceLength) { + lastStableWord = ""; + lastStableSequenceLength = 0; + } + + if (areThereDbWords) { + lastWord = dbWords.get(0); + words.addAll(dbWords); + } else { + if (lastStableWord.isEmpty() && !lastWord.isEmpty()) { + lastStableWord = lastWord; + lastStableSequenceLength = digitSequence.length(); + } + lastWord = ""; + words.addAll(generateWordVariations(lastStableWord)); + } + + if (retryAllowed && !areThereDbWords) { + loadSimilar(); + return; + } + + onWordsChanged.run(); + } + + + @Override + protected ArrayList generateWordVariations(String baseWord) { + baseWord = baseWord == null ? "" : baseWord; + ArrayList variants = new ArrayList<>(); + + try { + int charIndex = Cheonjiin.getRepeatingEndingDigits(digitSequence) - 1; + int key = digitSequence.charAt(digitSequence.length() - 1) - '0'; + String variant = baseWord + language.getKeyCharacters(key).get(charIndex); + variants.add(variant); + } catch (Exception ignored) { + variants.add(baseWord); + } + + return variants; + } + + + @Override + public void onAccept(String word, String sequence) { + lastWord = lastStableWord = ""; + lastStableSequenceLength = 0; + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/Predictions.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/WordPredictions.java similarity index 80% rename from app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/Predictions.java rename to app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/WordPredictions.java index edb13bfd..9439f287 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/Predictions.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/WordPredictions.java @@ -1,109 +1,46 @@ -package io.github.sspanak.tt9.ime.modes.helpers; +package io.github.sspanak.tt9.ime.modes.predictions; import java.util.ArrayList; import io.github.sspanak.tt9.db.DataStore; import io.github.sspanak.tt9.ime.helpers.TextField; import io.github.sspanak.tt9.languages.EmojiLanguage; -import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.util.Characters; -public class Predictions { - private final SettingsStore settings; +public class WordPredictions extends Predictions { private final TextField textField; - private String digitSequence; private String inputWord; private boolean isStemFuzzy; - private Language language; - private String stem; private String lastEnforcedTopWord = ""; - // async operations - private Runnable onWordsChanged = () -> {}; - // data - private boolean areThereDbWords = false; - private boolean containsGeneratedWords = false; - private ArrayList words = new ArrayList<>(); - - public Predictions(SettingsStore settings, TextField textField) { - this.settings = settings; + public WordPredictions(SettingsStore settings, TextField textField) { + super(settings); + stem = ""; this.textField = textField; } - public Predictions setLanguage(Language language) { - this.language = language; - return this; - } - - public Predictions setDigitSequence(String digitSequence) { - this.digitSequence = digitSequence; - return this; - } - - public Predictions setIsStemFuzzy(boolean yes) { + public WordPredictions setIsStemFuzzy(boolean yes) { this.isStemFuzzy = yes; return this; } - public Predictions setStem(String stem) { + + public WordPredictions setStem(String stem) { this.stem = stem; return this; } - public Predictions setInputWord(String inputWord) { + + public WordPredictions setInputWord(String inputWord) { this.inputWord = inputWord.toLowerCase(language.getLocale()); return this; } - public Predictions setWordsChangedHandler(Runnable handler) { - onWordsChanged = handler; - return this; - } - - public boolean containsGeneratedWords() { - return containsGeneratedWords; - } - - public ArrayList getList() { - return words; - } - - public boolean noDbWords() { - return !areThereDbWords; - } - - - /** - * load - * Queries the dictionary database for a list of words matching the current language and - * sequence or loads the static ones. - */ - public void load() { - containsGeneratedWords = false; - - if (digitSequence == null || digitSequence.isEmpty()) { - words.clear(); - onWordsChanged.run(); - return; - } - - boolean retryAllowed = !digitSequence.equals(EmojiLanguage.CUSTOM_EMOJI_SEQUENCE); - - DataStore.getWords( - (dbWords) -> onDbWords(dbWords, retryAllowed), - language, - digitSequence, - stem, - SettingsStore.SUGGESTIONS_MIN, - SettingsStore.SUGGESTIONS_MAX - ); - - } private void loadWithoutLeadingPunctuation() { DataStore.getWords( @@ -123,13 +60,18 @@ public class Predictions { } + @Override + protected boolean isRetryAllowed() { + return !EmojiLanguage.CUSTOM_EMOJI_SEQUENCE.equals(digitSequence); + } + /** * dbWordsHandler * Callback for when the database has finished loading words. If there were no matches in the database, * they will be generated based on the "inputWord". After the word list is compiled, it notifies the * external handler it is now possible to use it with "getList()". */ - private void onDbWords(ArrayList dbWords, boolean isRetryAllowed) { + protected void onDbWords(ArrayList dbWords, boolean isRetryAllowed) { // only the first round matters, the second one is just for getting the letters for a given key areThereDbWords = !dbWords.isEmpty() && isRetryAllowed; @@ -167,29 +109,15 @@ public class Predictions { } - /** - * suggestMissingWords - * Takes a list of words and appends them to the words list, if they are missing. - */ - private void suggestMissingWords(ArrayList newWords) { - for (String newWord : newWords) { - if (!words.contains(newWord) && !words.contains(newWord.toLowerCase(language.getLocale()))) { - words.add(newWord); - } - } - } - - /** * generateWordVariations * When there are no matching suggestions after the last key press, generate a list of possible * ones, so that the user can complete a missing word that is completely different from the ones * in the dictionary. - * * For example, if the word is "missin_" and the last pressed key is "4", the results would be: * | missing | missinh | missini | */ - private ArrayList generateWordVariations(String baseWord) { + protected ArrayList generateWordVariations(String baseWord) { ArrayList generatedWords = new ArrayList<>(); // This function is called from async context, so by the time it is executed, the digit sequence @@ -265,9 +193,9 @@ public class Predictions { * Similar to generatePossibleCompletions(), but uses the current filter as a base word. This is * used to complement the database results with all possible variations for the next key, when * the stem filter is on. - * + *

* It will not generate anything if more than one key was pressed after filtering though. - * + *

* For example, if the filter is "extr", the current word is "extr_" and the user has pressed "1", * the database would have returned only "extra", but this function would also * generate: "extrb" and "extrc". This is useful for typing an unknown word, that is similar to @@ -312,7 +240,7 @@ public class Predictions { !settings.getPredictWordPairs() // If the accepted word is longer than the sequence, it is some different word, not a textonym // of the fist suggestion. We don't need to store it. - || word == null || digitSequence == null + || word == null || word.length() != digitSequence.length() // If the word is the first suggestion, we have already guessed it right, and it makes no // sense to store it as a popular pair. diff --git a/app/src/main/java/io/github/sspanak/tt9/languages/Language.java b/app/src/main/java/io/github/sspanak/tt9/languages/Language.java index bef36b4f..700a503e 100644 --- a/app/src/main/java/io/github/sspanak/tt9/languages/Language.java +++ b/app/src/main/java/io/github/sspanak/tt9/languages/Language.java @@ -16,6 +16,7 @@ abstract public class Language { protected String name; protected boolean hasSpaceBetweenWords = true; protected boolean hasUpperCase = true; + protected boolean isSyllabary = false; public int getId() { @@ -65,6 +66,10 @@ abstract public class Language { return hasUpperCase; } + final public boolean isSyllabary() { + return isSyllabary; + } + @NonNull @Override final public String toString() { diff --git a/app/src/main/java/io/github/sspanak/tt9/languages/LanguageCollection.java b/app/src/main/java/io/github/sspanak/tt9/languages/LanguageCollection.java index 67c3f8df..ddcfdbaf 100644 --- a/app/src/main/java/io/github/sspanak/tt9/languages/LanguageCollection.java +++ b/app/src/main/java/io/github/sspanak/tt9/languages/LanguageCollection.java @@ -30,19 +30,17 @@ public class LanguageCollection { } - public static LanguageCollection getInstance(Context context) { + public static void init(Context context) { if (self == null) { self = new LanguageCollection(context); } - - return self; } @Nullable - public static NaturalLanguage getLanguage(Context context, String langId) { + public static NaturalLanguage getLanguage(String langId) { try { - return getLanguage(context, Integer.parseInt(langId)); + return getLanguage(Integer.parseInt(langId)); } catch (NumberFormatException e) { return null; } @@ -50,23 +48,23 @@ public class LanguageCollection { @Nullable - public static NaturalLanguage getLanguage(Context context, int langId) { - if (getInstance(context).languages.containsKey(langId)) { - return getInstance(context).languages.get(langId); + public static NaturalLanguage getLanguage(int langId) { + if (self.languages.containsKey(langId)) { + return self.languages.get(langId); } return null; } - @NonNull public static Language getDefault(Context context) { - Language language = getByLocale(context, SystemSettings.getLocale()); - language = language == null ? getByLocale(context, "en") : language; + @NonNull public static Language getDefault() { + Language language = getByLocale(SystemSettings.getLocale()); + language = language == null ? getByLocale("en") : language; return language == null ? new NullLanguage() : language; } @Nullable - public static NaturalLanguage getByLanguageCode(Context context, String languageCode) { - for (NaturalLanguage lang : getInstance(context).languages.values()) { + public static NaturalLanguage getByLanguageCode(String languageCode) { + for (NaturalLanguage lang : self.languages.values()) { if (lang.getLocale().getLanguage().equals(new Locale(languageCode).getLanguage())) { return lang; } @@ -76,8 +74,8 @@ public class LanguageCollection { } @Nullable - public static NaturalLanguage getByLocale(Context context, String locale) { - for (NaturalLanguage lang : getInstance(context).languages.values()) { + public static NaturalLanguage getByLocale(String locale) { + for (NaturalLanguage lang : self.languages.values()) { if (lang.getLocale().toString().equals(locale)) { return lang; } @@ -86,14 +84,14 @@ public class LanguageCollection { return null; } - public static ArrayList getAll(Context context, ArrayList languageIds, boolean sort) { + public static ArrayList getAll(ArrayList languageIds, boolean sort) { if (languageIds == null) { return new ArrayList<>(); } ArrayList langList = new ArrayList<>(); for (int languageId : languageIds) { - NaturalLanguage lang = getLanguage(context, languageId); + NaturalLanguage lang = getLanguage(languageId); if (lang != null) { langList.add(lang); } @@ -106,12 +104,12 @@ public class LanguageCollection { return new ArrayList<>(langList); } - public static ArrayList getAll(Context context, ArrayList languageIds) { - return getAll(context, languageIds, false); + public static ArrayList getAll(ArrayList languageIds) { + return getAll(languageIds, false); } - public static ArrayList getAll(Context context, boolean sort) { - ArrayList langList = new ArrayList<>(getInstance(context).languages.values()); + public static ArrayList getAll(boolean sort) { + ArrayList langList = new ArrayList<>(self.languages.values()); if (sort) { Collections.sort(langList); @@ -120,8 +118,8 @@ public class LanguageCollection { return new ArrayList<>(langList); } - public static ArrayList getAll(Context context) { - return getAll(context,false); + public static ArrayList getAll() { + return getAll(false); } public static String toString(ArrayList list) { diff --git a/app/src/main/java/io/github/sspanak/tt9/languages/LanguageDefinition.java b/app/src/main/java/io/github/sspanak/tt9/languages/LanguageDefinition.java index d6228d41..f5ac5eb6 100644 --- a/app/src/main/java/io/github/sspanak/tt9/languages/LanguageDefinition.java +++ b/app/src/main/java/io/github/sspanak/tt9/languages/LanguageDefinition.java @@ -15,6 +15,8 @@ import io.github.sspanak.tt9.util.AssetFile; import io.github.sspanak.tt9.util.Logger; public class LanguageDefinition extends AssetFile { + private static final String LOG_TAG = LanguageDefinition.class.getSimpleName(); + private static final String languagesDir = "languages"; private static final String definitionsDir = languagesDir + "/definitions"; @@ -22,6 +24,7 @@ public class LanguageDefinition extends AssetFile { public String dictionaryFile = ""; public boolean hasSpaceBetweenWords = true; public boolean hasUpperCase = true; + public boolean isSyllabary = false; public ArrayList> layout = new ArrayList<>(); public String locale = ""; public String name = ""; @@ -40,9 +43,9 @@ public class LanguageDefinition extends AssetFile { ArrayList files = new ArrayList<>(); try { files.addAll(Arrays.asList(assets.list(definitionsDir))); - Logger.d("LanguageDefinition", "Found: " + files.size() + " languages."); + Logger.d(LOG_TAG, "Found: " + files.size() + " languages."); } catch (IOException | NullPointerException e) { - Logger.e("tt9.LanguageDefinition", "Failed reading language definitions from: '" + definitionsDir + "'. " + e.getMessage()); + Logger.e(LOG_TAG, "Failed reading language definitions from: '" + definitionsDir + "'. " + e.getMessage()); } return files; @@ -95,6 +98,7 @@ public class LanguageDefinition extends AssetFile { hasSpaceBetweenWords = getPropertyFromYaml(yaml, "hasSpaceBetweenWords", hasSpaceBetweenWords); hasUpperCase = getPropertyFromYaml(yaml, "hasUpperCase", hasUpperCase); + isSyllabary = hasYamlProperty(yaml, "sounds"); layout = getLayoutFromYaml(yaml); locale = getPropertyFromYaml(yaml, "locale", locale); name = getPropertyFromYaml(yaml, "name", name); @@ -126,6 +130,19 @@ public class LanguageDefinition extends AssetFile { } + private boolean hasYamlProperty(ArrayList yaml, String property) { + final String yamlProperty = property + ":"; + + for (String line : yaml) { + if (line.startsWith(yamlProperty)) { + return true; + } + } + + return false; + } + + /** * The boolean variant of getPropertyFromYaml. It returns true if the property is found and is: * "true", "on", "yes" or "y". diff --git a/app/src/main/java/io/github/sspanak/tt9/languages/LanguageKind.java b/app/src/main/java/io/github/sspanak/tt9/languages/LanguageKind.java index 87efd80b..dc6b6eb6 100644 --- a/app/src/main/java/io/github/sspanak/tt9/languages/LanguageKind.java +++ b/app/src/main/java/io/github/sspanak/tt9/languages/LanguageKind.java @@ -1,13 +1,19 @@ package io.github.sspanak.tt9.languages; +import java.util.Locale; + public class LanguageKind { + public static final int KOREAN = 601579; + public static boolean isArabic(Language language) { return language != null && language.getId() == 502337; } public static boolean isBulgarian(Language language) { return language != null && language.getId() == 231650; } public static boolean isCyrillic(Language language) { return language != null && language.getKeyCharacters(2).contains("а"); } + public static boolean isEnglish(Language language) { return language != null && language.getLocale().equals(Locale.ENGLISH); } public static boolean isFrench(Language language) { return language != null && language.getId() == 596550; } public static boolean isGreek(Language language) { return language != null && language.getId() == 597381; } public static boolean isHebrew(Language language) { return language != null && (language.getId() == 305450 || language.getId() == 403177); } public static boolean isHinglish(Language language) { return language != null && language.getId() == 468421; } + public static boolean isKorean(Language language) { return language != null && language.getId() == KOREAN; } public static boolean isLatinBased(Language language) { return language != null && language.getKeyCharacters(2).contains("a"); } public static boolean isRTL(Language language) { return isArabic(language) || isHebrew(language); } public static boolean isUkrainian(Language language) { return language != null && language.getId() == 54645; } diff --git a/app/src/main/java/io/github/sspanak/tt9/languages/NaturalLanguage.java b/app/src/main/java/io/github/sspanak/tt9/languages/NaturalLanguage.java index bfb65ee9..ef98d2e7 100644 --- a/app/src/main/java/io/github/sspanak/tt9/languages/NaturalLanguage.java +++ b/app/src/main/java/io/github/sspanak/tt9/languages/NaturalLanguage.java @@ -9,6 +9,7 @@ import java.util.Locale; import io.github.sspanak.tt9.languages.exceptions.InvalidLanguageCharactersException; import io.github.sspanak.tt9.util.Characters; import io.github.sspanak.tt9.util.Text; +import io.github.sspanak.tt9.util.TextTools; public class NaturalLanguage extends Language implements Comparable { @@ -31,6 +32,7 @@ public class NaturalLanguage extends Language implements Comparable keyChars = new ArrayList<>(); for (String defChar : definitionChars) { @@ -100,6 +103,9 @@ public class NaturalLanguage extends Language implements Comparable UI.toastLongFromAsync(activity, "Word pairs deleted. You must reopen the screen manually.") ); return true; diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/deleteWords/PreferenceDeletableWord.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/deleteWords/PreferenceDeletableWord.java index 0490dab5..f2f9ea14 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/deleteWords/PreferenceDeletableWord.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/deleteWords/PreferenceDeletableWord.java @@ -64,7 +64,7 @@ public class PreferenceDeletableWord extends ScreenPreference { SettingsStore settings = new SettingsStore(getContext()); DataStore.deleteCustomWord( this::onWordDeleted, - LanguageCollection.getLanguage(getContext(), settings.getInputLanguage()), + LanguageCollection.getLanguage(settings.getInputLanguage()), word ); } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/HotkeysScreen.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/HotkeysScreen.java index cf38dd82..5a921e37 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/HotkeysScreen.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/HotkeysScreen.java @@ -32,6 +32,7 @@ public class HotkeysScreen extends BaseScreenFragment { findPreference(SectionKeymap.ITEM_NEXT_LANGUAGE), findPreference(SectionKeymap.ITEM_SELECT_KEYBOARD), findPreference(SectionKeymap.ITEM_SHIFT), + findPreference(SectionKeymap.ITEM_SPACE_KOREAN), findPreference(SectionKeymap.ITEM_SHOW_SETTINGS), findPreference(SectionKeymap.ITEM_VOICE_INPUT), }; diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/SectionKeymap.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/SectionKeymap.java index e5b78141..e10f57e6 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/SectionKeymap.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/SectionKeymap.java @@ -25,6 +25,7 @@ public class SectionKeymap { public static final String ITEM_NEXT_LANGUAGE = "key_next_language"; public static final String ITEM_SELECT_KEYBOARD = "key_select_keyboard"; public static final String ITEM_SHIFT = "key_shift"; + public static final String ITEM_SPACE_KOREAN = "key_space_korean"; public static final String ITEM_SHOW_SETTINGS = "key_show_settings"; public static final String ITEM_VOICE_INPUT = "key_voice_input"; @@ -155,7 +156,22 @@ public class SectionKeymap { } for (DropDownPreference item : items) { - if (item != null && !dropDown.getKey().equals(item.getKey()) && key.equals(item.getValue())) { + if (item == null) { + continue; + } + + // "Shift" and "Korean Space" can be the same key. It is properly handled in HotkeyHandler. + if ( + ( + (dropDown.getKey().equals(ITEM_SHIFT) && item.getKey().equals(ITEM_SPACE_KOREAN)) + || (dropDown.getKey().equals(ITEM_SPACE_KOREAN) && item.getKey().equals(ITEM_SHIFT)) + ) + && key.equals(item.getValue()) + ) { + continue; + } + + if (!dropDown.getKey().equals(item.getKey()) && key.equals(item.getValue())) { Logger.i("SectionKeymap.validateKey", "Key: '" + key + "' is already in use for function: " + item.getKey()); return false; } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languageSelection/LanguageSelectionScreen.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languageSelection/LanguageSelectionScreen.java index c795922a..353989f3 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languageSelection/LanguageSelectionScreen.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languageSelection/LanguageSelectionScreen.java @@ -42,7 +42,7 @@ public class LanguageSelectionScreen extends BaseScreenFragment { return; } - ArrayList allLanguages = LanguageCollection.getAll(activity, true); + ArrayList allLanguages = LanguageCollection.getAll(true); if (allLanguages.isEmpty()) { return; } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languageSelection/PreferenceSwitchLanguage.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languageSelection/PreferenceSwitchLanguage.java index cccdca3f..0246aa55 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languageSelection/PreferenceSwitchLanguage.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languageSelection/PreferenceSwitchLanguage.java @@ -51,7 +51,7 @@ public class PreferenceSwitchLanguage extends SwitchPreferenceCompat { ); // word count - WordFile wordFile = new WordFile(activity, language.getDictionaryFile(), activity.getAssets()); + WordFile wordFile = new WordFile(activity, language, activity.getAssets()); summary .append(", ") .append( diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemExportDictionary.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemExportDictionary.java index 1f3f1320..3a608e50 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemExportDictionary.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemExportDictionary.java @@ -31,7 +31,7 @@ class ItemExportDictionary extends ItemExportAbstract { protected boolean onStartProcessing() { return DictionaryExporter.getInstance() - .setLanguages(LanguageCollection.getAll(activity, activity.getSettings().getEnabledLanguageIds())) + .setLanguages(LanguageCollection.getAll(activity.getSettings().getEnabledLanguageIds())) .run(activity); } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemLoadDictionary.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemLoadDictionary.java index 6e6e8a07..8dd4adbf 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemLoadDictionary.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemLoadDictionary.java @@ -50,7 +50,7 @@ class ItemLoadDictionary extends ItemClickable { private void onLoadingStatusChange(Bundle status) { - progressBar.show(activity, status); + progressBar.show(status); item.setSummary(progressBar.getTitle() + " " + progressBar.getMessage()); if (progressBar.isCancelled()) { @@ -67,7 +67,7 @@ class ItemLoadDictionary extends ItemClickable { @Override protected boolean onClick(Preference p) { - ArrayList languages = LanguageCollection.getAll(activity, activity.getSettings().getEnabledLanguageIds()); + ArrayList languages = LanguageCollection.getAll(activity.getSettings().getEnabledLanguageIds()); setLoadingStatus(); if (!loader.load(activity, languages)) { diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemSelectLanguage.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemSelectLanguage.java index 9b9df945..b31d30e2 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemSelectLanguage.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemSelectLanguage.java @@ -28,7 +28,7 @@ class ItemSelectLanguage { } item.setSummary( - LanguageCollection.toString(LanguageCollection.getAll(activity, activity.getSettings().getEnabledLanguageIds(), true)) + LanguageCollection.toString(LanguageCollection.getAll(activity.getSettings().getEnabledLanguageIds(), true)) ); } } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemTruncateAll.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemTruncateAll.java index 69e1d7a1..656302e3 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemTruncateAll.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemTruncateAll.java @@ -29,7 +29,7 @@ class ItemTruncateAll extends ItemClickable { @Override protected boolean onClick(Preference p) { onStartDeleting(); - DataStore.deleteLanguages(this::onFinishDeleting, LanguageCollection.getAll(activity, false)); + DataStore.deleteLanguages(this::onFinishDeleting, LanguageCollection.getAll(false)); return true; } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemTruncateUnselected.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemTruncateUnselected.java index aca5a26b..f1639bdb 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemTruncateUnselected.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/languages/ItemTruncateUnselected.java @@ -25,7 +25,7 @@ class ItemTruncateUnselected extends ItemTruncateAll { protected boolean onClick(Preference p) { ArrayList unselectedLanguages = new ArrayList<>(); Set selectedLanguageIds = new HashSet<>(activity.getSettings().getEnabledLanguageIds()); - for (Language lang : LanguageCollection.getAll(activity, false)) { + for (Language lang : LanguageCollection.getAll(false)) { if (!selectedLanguageIds.contains(lang.getId())) { unselectedLanguages.add(lang); } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/punctuation/ItemPunctuationOrderLanguage.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/punctuation/ItemPunctuationOrderLanguage.java index 622360b3..49b1f3ce 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/punctuation/ItemPunctuationOrderLanguage.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/punctuation/ItemPunctuationOrderLanguage.java @@ -36,7 +36,7 @@ class ItemPunctuationOrderLanguage extends ItemDropDown { } LinkedHashMap values = new LinkedHashMap<>(); - ArrayList languages = LanguageCollection.getAll(item.getContext(), settings.getEnabledLanguageIds(), true); + ArrayList languages = LanguageCollection.getAll(settings.getEnabledLanguageIds(), true); if (languages.isEmpty()) { return; } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/punctuation/PunctuationScreen.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/punctuation/PunctuationScreen.java index 223d8e07..b599b71e 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/punctuation/PunctuationScreen.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/punctuation/PunctuationScreen.java @@ -80,7 +80,7 @@ public class PunctuationScreen extends BaseScreenFragment { restoreDefaults = new ItemRestoreDefaultPunctuation(activity.getSettings(), item, this::onLanguageChanged); restoreDefaults - .setLanguage(LanguageCollection.getLanguage(activity, languageList.getValue())) + .setLanguage(LanguageCollection.getLanguage(languageList.getValue())) .enableClickHandler(); } @@ -97,7 +97,7 @@ public class PunctuationScreen extends BaseScreenFragment { private void onLanguageChanged(@Nullable String newLanguageId) { - Language language = LanguageCollection.getLanguage(activity, newLanguageId); + Language language = LanguageCollection.getLanguage(newLanguageId); restoreDefaults.setLanguage(language); diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsHacks.java b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsHacks.java index 1413ed1d..36a93d8c 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsHacks.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsHacks.java @@ -37,6 +37,10 @@ class SettingsHacks extends BaseSettings { /************* hack settings *************/ + public boolean holdForPunctuationInKorean() { + return prefs.getBoolean("pref_hold_for_punctuation_in_korean", true); + } + public int getSuggestionScrollingDelay() { boolean defaultOn = DeviceInfo.noTouchScreen(context) && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q; return prefs.getBoolean("pref_alternative_suggestion_scrolling", defaultOn) ? 200 : 0; diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsHotkeys.java b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsHotkeys.java index 94bcdb69..d30d01e1 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsHotkeys.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsHotkeys.java @@ -66,6 +66,9 @@ class SettingsHotkeys extends SettingsHacks { public int getKeyShift() { return getFunctionKey(SectionKeymap.ITEM_SHIFT); } + public int getKeySpaceKorean() { + return getFunctionKey(SectionKeymap.ITEM_SPACE_KOREAN); + } public int getKeyShowSettings() { return getFunctionKey(SectionKeymap.ITEM_SHOW_SETTINGS); } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsInput.java b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsInput.java index 34e0ba24..7cb841df 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsInput.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsInput.java @@ -40,7 +40,7 @@ class SettingsInput extends SettingsHotkeys { public Set getEnabledLanguagesIdsAsStrings() { Set defaultLanguages = new HashSet<>(Collections.singletonList( - String.valueOf(LanguageCollection.getDefault(context).getId()) + String.valueOf(LanguageCollection.getDefault().getId()) )); return new HashSet<>(prefs.getStringSet("pref_languages", defaultLanguages)); @@ -51,7 +51,7 @@ class SettingsInput extends SettingsHotkeys { Set validLanguageIds = new HashSet<>(); for (String langId : languageIds) { - if (!Validators.validateInputLanguage(context, Integer.parseInt(langId), "saveEnabledLanguageIds")){ + if (!Validators.validateInputLanguage(Integer.parseInt(langId), "saveEnabledLanguageIds")){ continue; } @@ -69,12 +69,12 @@ class SettingsInput extends SettingsHotkeys { public int getInputLanguage() { - return prefs.getInt("pref_input_language", LanguageCollection.getDefault(context).getId()); + return prefs.getInt("pref_input_language", LanguageCollection.getDefault().getId()); } public void saveInputLanguage(int language) { - if (Validators.validateInputLanguage(context, language, "saveInputLanguage")){ + if (Validators.validateInputLanguage(language, "saveInputLanguage")){ prefsEditor.putInt("pref_input_language", language); prefsEditor.apply(); } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsPunctuation.java b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsPunctuation.java index 13752a20..0a5490a0 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsPunctuation.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsPunctuation.java @@ -88,6 +88,10 @@ class SettingsPunctuation extends SettingsInput { orderedChars = language.getKeyCharacters(number); } + if (number < 2) { + orderedChars = removeLettersFromList(orderedChars); + } + return orderedChars; } @@ -114,4 +118,16 @@ class SettingsPunctuation extends SettingsInput { return charsList; } + + + private ArrayList removeLettersFromList(ArrayList list) { + ArrayList cleanList = new ArrayList<>(); + for (String s : list) { + if (!Character.isAlphabetic(s.codePointAt(0))) { + cleanList.add(s); + } + } + + return cleanList; + } } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsUI.java b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsUI.java index bd7b894b..1784a1bd 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsUI.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsUI.java @@ -28,7 +28,7 @@ public class SettingsUI extends SettingsTyping { if (DeviceInfo.noKeyboard(context)) { DEFAULT_LAYOUT = LAYOUT_NUMPAD; - } else if (DeviceInfo.noBackspaceKey(context) && !DeviceInfo.noTouchScreen(context)) { + } else if (DeviceInfo.noBackspaceKey() && !DeviceInfo.noTouchScreen(context)) { DEFAULT_LAYOUT = LAYOUT_SMALL; } else { DEFAULT_LAYOUT = LAYOUT_TRAY; diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/Validators.java b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/Validators.java index 92f8668b..58a47bc1 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/Validators.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/Validators.java @@ -1,7 +1,5 @@ package io.github.sspanak.tt9.preferences.settings; -import android.content.Context; - import java.util.ArrayList; import java.util.Arrays; @@ -25,16 +23,16 @@ class Validators { InputMode.CASE_CAPITALIZE )); - static boolean doesLanguageExist(Context context, int langId) { - return LanguageCollection.getLanguage(context, langId) != null; + static boolean doesLanguageExist(int langId) { + return LanguageCollection.getLanguage(langId) != null; } static boolean validateInputMode(int mode, String logTag, String logMsg) { return Validators.isIntInList(mode, validInputModes, logTag, logMsg); } - static boolean validateInputLanguage(Context context, int langId, String logTag) { - if (!doesLanguageExist(context, langId)) { + static boolean validateInputLanguage(int langId, String logTag) { + if (!doesLanguageExist(langId)) { Logger.w(logTag, "Not saving invalid language with ID: " + langId); return false; } diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/dialogs/AddWordDialog.java b/app/src/main/java/io/github/sspanak/tt9/ui/dialogs/AddWordDialog.java index 9e963087..da8a1fca 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/dialogs/AddWordDialog.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/dialogs/AddWordDialog.java @@ -35,7 +35,7 @@ public class AddWordDialog extends PopupDialog { word = intent.getStringExtra(PARAMETER_WORD); int languageId = intent.getIntExtra(PARAMETER_LANGUAGE, -1); - language = LanguageCollection.getLanguage(context, languageId); + language = LanguageCollection.getLanguage(languageId); if (language == null) { message = context.getString(R.string.add_word_invalid_language_x, languageId); diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/dialogs/AutoUpdateMonologue.java b/app/src/main/java/io/github/sspanak/tt9/ui/dialogs/AutoUpdateMonologue.java index 8cba08e8..7c743cd6 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/dialogs/AutoUpdateMonologue.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/dialogs/AutoUpdateMonologue.java @@ -20,13 +20,13 @@ public class AutoUpdateMonologue extends PopupDialog { AutoUpdateMonologue(@NonNull Context context, @NonNull Intent intent, ConsumerCompat activityFinisher) { super(context, activityFinisher); - parseIntent(context, intent); + parseIntent(intent); } - private void parseIntent(@NonNull Context context, @NonNull Intent intent) { + private void parseIntent(@NonNull Intent intent) { int languageId = intent.getIntExtra(PARAMETER_LANGUAGE, -1); - language = LanguageCollection.getLanguage(context, languageId); + language = LanguageCollection.getLanguage(languageId); if (language == null) { Logger.e(getClass().getSimpleName(), "Auto-updating is not possible. Intent parameter '" + PARAMETER_LANGUAGE + "' is invalid: " + languageId); diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutNumpad.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutNumpad.java index 0be6280c..a220f4a9 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutNumpad.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutNumpad.java @@ -15,9 +15,12 @@ import java.util.Arrays; import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.hacks.DeviceInfo; import io.github.sspanak.tt9.ime.TraditionalT9; +import io.github.sspanak.tt9.languages.LanguageKind; import io.github.sspanak.tt9.ui.main.keys.SoftKey; import io.github.sspanak.tt9.ui.main.keys.SoftKeyFn; import io.github.sspanak.tt9.ui.main.keys.SoftKeyNumber; +import io.github.sspanak.tt9.ui.main.keys.SoftKeyNumber0; +import io.github.sspanak.tt9.ui.main.keys.SoftKeyNumber1; import io.github.sspanak.tt9.ui.main.keys.SoftKeyPunctuation; import io.github.sspanak.tt9.ui.main.keys.SoftKeySettings; @@ -89,13 +92,14 @@ class MainLayoutNumpad extends BaseMainLayout { @Override void showTextEditingPalette() { isTextEditingShown = true; + boolean notKorean = tt9 != null && !LanguageKind.isKorean(tt9.getLanguage()); for (SoftKey key : getKeys()) { int keyId = key.getId(); if (keyId == R.id.soft_key_0) { - key.setEnabled(tt9 != null && !tt9.isInputModeNumeric()); - } else if (key.getClass().equals(SoftKeyNumber.class)) { + key.setEnabled(tt9 != null && !tt9.isInputModeNumeric() && notKorean); + } else if (key.getClass().equals(SoftKeyNumber.class) || key instanceof SoftKeyNumber0 || key instanceof SoftKeyNumber1) { key.setVisibility(View.GONE); } @@ -115,7 +119,7 @@ class MainLayoutNumpad extends BaseMainLayout { keyId == R.id.soft_key_add_word || keyId == R.id.soft_key_lf3 || keyId == R.id.soft_key_lf4 - || keyId == R.id.soft_key_filter_suggestions + || (keyId == R.id.soft_key_filter_suggestions && notKorean) ) { key.setEnabled(false); } @@ -127,7 +131,7 @@ class MainLayoutNumpad extends BaseMainLayout { isTextEditingShown = false; for (SoftKey key : getKeys()) { - if (key.getClass().equals(SoftKeyNumber.class) || key.getClass().equals(SoftKeyPunctuation.class)) { + if (key.getClass().equals(SoftKeyNumber.class) || key.getClass().equals(SoftKeyPunctuation.class) || key instanceof SoftKeyNumber0 || key instanceof SoftKeyNumber1) { key.setVisibility(View.VISIBLE); key.setEnabled(true); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyAddWord.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyAddWord.java index 50174bc7..e5e3ac1c 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyAddWord.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyAddWord.java @@ -27,7 +27,7 @@ public class SoftKeyAddWord extends SoftKey { public void render() { super.render(); if (tt9 != null) { - setEnabled(!tt9.isVoiceInputActive()); + setEnabled(!tt9.isVoiceInputActive() && tt9.notLanguageSyllabary()); } } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyFilter.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyFilter.java index ce961600..59ed942d 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyFilter.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyFilter.java @@ -3,6 +3,7 @@ package io.github.sspanak.tt9.ui.main.keys; import android.content.Context; import android.util.AttributeSet; +import io.github.sspanak.tt9.languages.LanguageKind; import io.github.sspanak.tt9.ui.Vibration; public class SoftKeyFilter extends SoftKey { @@ -10,11 +11,30 @@ public class SoftKeyFilter extends SoftKey { public SoftKeyFilter(Context context, AttributeSet attrs) { super(context, attrs); } public SoftKeyFilter(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } - @Override protected float getTitleRelativeSize() { return super.getTitleRelativeSize() / 0.85f; } - @Override protected float getSubTitleRelativeSize() { return super.getSubTitleRelativeSize() / 0.85f; } + @Override + protected float getTitleRelativeSize() { + return isKorean() ? 1.1f : super.getTitleRelativeSize() / 0.85f; + } + + + @Override + protected float getSubTitleRelativeSize() { + return super.getSubTitleRelativeSize() / 0.85f; + } + + + private boolean isKorean() { + return tt9 != null && LanguageKind.isKorean(tt9.getLanguage()); + } + @Override protected void handleHold() { + if (isKorean()) { + handleRelease(); + return; + } + preventRepeat(); if (validateTT9Handler() && tt9.onKeyFilterClear(false)) { vibrate(Vibration.getHoldVibration()); @@ -22,21 +42,30 @@ public class SoftKeyFilter extends SoftKey { } } + @Override protected boolean handleRelease() { - return - validateTT9Handler() - && tt9.onKeyFilterSuggestions(false, getLastPressedKey() == getId()); + if (!validateTT9Handler()) { + return false; + } + + if (isKorean()) { + return tt9.onKeySpaceKorean(false); + } else { + return tt9.onKeyFilterSuggestions(false, getLastPressedKey() == getId()); + } } + @Override protected String getTitle() { - return "CLR"; + return isKorean() ? "␣" : "CLR"; } + @Override protected String getSubTitle() { - return "FLTR"; + return isKorean() ? null : "FLTR"; } @@ -44,7 +73,12 @@ public class SoftKeyFilter extends SoftKey { public void render() { super.render(); if (tt9 != null) { - setEnabled(!tt9.isInputModeNumeric() && !tt9.isInputModeABC() && !tt9.isVoiceInputActive()); + setEnabled( + !tt9.isInputModeNumeric() + && !tt9.isInputModeABC() + && !tt9.isVoiceInputActive() + && (LanguageKind.isKorean(tt9.getLanguage()) || tt9.notLanguageSyllabary()) + ); } } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyLF4.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyLF4.java index fdf75098..53273b60 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyLF4.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyLF4.java @@ -55,19 +55,7 @@ public class SoftKeyLF4 extends SwipeableKey { } protected String getPressIcon() { - if (tt9 == null || tt9.getLanguage() == null) { - return getContext().getString(R.string.virtual_key_input_mode); - } - - if (tt9.isInputModeNumeric()) { - return "123"; - } - - if (tt9.isInputModeABC()) { - return tt9.getLanguage().getAbcString().toUpperCase(tt9.getLanguage().getLocale()); - } - - return "T9"; + return tt9 != null ? tt9.getInputModeName() : getContext().getString(R.string.virtual_key_input_mode); } @Override diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyNumber.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyNumber.java index f6d5838f..fc161baa 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyNumber.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyNumber.java @@ -44,8 +44,6 @@ public class SoftKeyNumber extends SoftKey { put(9, 3); }}; - private static final String PUNCTUATION_LABEL = ",:-)"; - public SoftKeyNumber(Context context) { super(context); @@ -71,16 +69,6 @@ public class SoftKeyNumber extends SoftKey { } - @Override - protected float getSubTitleRelativeSize() { - if (tt9 != null && !tt9.isInputModeNumeric() && getNumber(getId()) == 0) { - return 1.1f; - } - - return super.getSubTitleRelativeSize(); - } - - @Override protected void handleHold() { preventRepeat(); @@ -140,30 +128,7 @@ public class SoftKeyNumber extends SoftKey { @Override protected String getSubTitle() { - if (tt9 == null) { - return null; - } - - int number = getNumber(getId()); - - return switch (number) { - case 0 -> getSpecialCharList(tt9); - case 1 -> tt9.isNumericModeStrict() ? null : PUNCTUATION_LABEL; - default -> getKeyCharList(tt9, number); - }; - } - - - private String getSpecialCharList(@NonNull TraditionalT9 tt9) { - if (tt9.isNumericModeSigned()) { - return "+/-"; - } else if (tt9.isNumericModeStrict()) { - return null; - } else if (tt9.isInputModeNumeric()) { - return "+"; - } else { - return "␣"; - } + return tt9 == null ? null : getKeyCharList(tt9, getNumber(getId())); } @@ -234,14 +199,16 @@ public class SoftKeyNumber extends SoftKey { /** * As suggested by the community, there is no need to display the accented letters. - * People are used to seeing just "ABC", "DEF", etc. + * People are used to seeing just "ABC", "DEF", etc. In the case of Korean, the keypad looks too + * cluttered, so we skip the double consonants, like on phones with a physical keypad. */ private boolean shouldSkipAccents(char currentLetter, boolean isGreek, boolean isLatinBased) { return currentLetter == 'ѝ' || currentLetter == 'ґ' - || currentLetter == 'ς' + || (currentLetter == 'ㄲ' || currentLetter == 'ㄸ' || currentLetter == 'ㅃ' || currentLetter == 'ㅆ' || currentLetter == 'ㅉ') || (isLatinBased && currentLetter > 'z') + || currentLetter == 'ς' || (isGreek && (currentLetter < 'α' || currentLetter > 'ω')); } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyNumber0.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyNumber0.java new file mode 100644 index 00000000..a4eee7a0 --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyNumber0.java @@ -0,0 +1,74 @@ +package io.github.sspanak.tt9.ui.main.keys; + +import android.content.Context; +import android.util.AttributeSet; + +import io.github.sspanak.tt9.languages.LanguageKind; + +public class SoftKeyNumber0 extends SoftKeyNumber { + private static final String CHARS_NUMERIC_MODE = "+%$"; + + public SoftKeyNumber0(Context context) { super(context); } + public SoftKeyNumber0(Context context, AttributeSet attrs) { super(context, attrs); } + public SoftKeyNumber0(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } + + @Override + protected String getTitle() { + if (tt9 == null) { + return super.getTitle(); + } + + if (tt9.isNumericModeStrict()) { + return "0"; + } if (tt9.isNumericModeSigned()) { + return "+/-"; + } else if (tt9.isInputModePhone()) { + return "+"; + } else if (tt9.isInputModeNumeric() || LanguageKind.isKorean(tt9.getLanguage())) { + return CHARS_NUMERIC_MODE; + } + + return super.getTitle(); + } + + @Override + protected String getSubTitle() { + if (tt9 == null) { + return null; + } + + if (tt9.isNumericModeStrict()) { + return null; + } else if (tt9.isInputModeNumeric()) { + return "0"; + } else if (LanguageKind.isKorean(tt9.getLanguage())) { + return getKoreanCharList(); + } else { + return "␣"; + } + } + + private String getKoreanCharList() { + if (tt9 == null || tt9.getLanguage() == null) { + return null; + } + + StringBuilder list = new StringBuilder(); + for (String character : tt9.getLanguage().getKeyCharacters(0)) { + if (Character.isAlphabetic(character.charAt(0))) { + list.append(character); + } + } + + return list.toString(); + } + + @Override + protected float getSubTitleRelativeSize() { + if (tt9 != null && !tt9.isInputModeNumeric() && !LanguageKind.isKorean(tt9.getLanguage())) { + return 1.1f; + } + + return super.getSubTitleRelativeSize(); + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyNumber1.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyNumber1.java new file mode 100644 index 00000000..00a52e7a --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyNumber1.java @@ -0,0 +1,48 @@ +package io.github.sspanak.tt9.ui.main.keys; + +import android.content.Context; +import android.util.AttributeSet; + +import io.github.sspanak.tt9.languages.LanguageKind; + +public class SoftKeyNumber1 extends SoftKeyNumber { + private static final String DEFAULT_LARGE_LABEL = ",:-)"; + private static final String KOREAN_SMALL_LABEL = "1 :-)"; + private static final String KOREAN_LARGE_LABEL = "ㅣ"; + + public SoftKeyNumber1(Context context) { super(context); } + public SoftKeyNumber1(Context context, AttributeSet attrs) { super(context, attrs); } + public SoftKeyNumber1(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } + + @Override + protected String getTitle() { + if (tt9 == null) { + return super.getTitle(); + } + + if (tt9.isInputModeNumeric() && !tt9.isNumericModeStrict()) { + return DEFAULT_LARGE_LABEL; + } else if (LanguageKind.isKorean(tt9.getLanguage())) { + return KOREAN_SMALL_LABEL; + } else { + return "1"; + } + } + + @Override + protected String getSubTitle() { + if (tt9 == null || tt9.isNumericModeStrict()) { + return null; + } + + if (tt9.isNumericModeStrict()) { + return null; + } else if (tt9.isInputModeNumeric()) { + return "1"; + } else if (LanguageKind.isKorean(tt9.getLanguage())) { + return KOREAN_LARGE_LABEL; + } else { + return DEFAULT_LARGE_LABEL; + } + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyRF3.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyRF3.java index 384d2adb..6e2141a3 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyRF3.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyRF3.java @@ -57,13 +57,7 @@ public class SoftKeyRF3 extends SoftKey { @Override protected String getTitle() { if (isTextEdtingActive()) { - if (tt9 == null) { - return "ABC"; - } else if (tt9.isInputModeNumeric()) { - return "123"; - } else if (tt9.getLanguage() != null) { - return tt9.getLanguage().getAbcString().toUpperCase(tt9.getLanguage().getLocale()); - } + return tt9 == null ? "ABC" : tt9.getABCString(); } return isTextEditingMissing() && !isVoiceInputMissing() ? "🎤" : getContext().getString(R.string.virtual_key_text_editing).toUpperCase(); diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/notifications/DictionaryLoadingBar.java b/app/src/main/java/io/github/sspanak/tt9/ui/notifications/DictionaryLoadingBar.java index aae354f3..103e9480 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/notifications/DictionaryLoadingBar.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/notifications/DictionaryLoadingBar.java @@ -64,7 +64,7 @@ public class DictionaryLoadingBar extends DictionaryProgressNotification { } - public void show(Context context, Bundle data) { + public void show(Bundle data) { String error = data.getString("error", null); int fileCount = data.getInt("fileCount", -1); int progress = data.getInt("progress", -1); @@ -72,7 +72,6 @@ public class DictionaryLoadingBar extends DictionaryProgressNotification { if (error != null) { hasFailed = true; showError( - context, error, data.getInt("languageId", -1), data.getLong("fileLine", -1) @@ -84,7 +83,6 @@ public class DictionaryLoadingBar extends DictionaryProgressNotification { } showProgress( - context, data.getLong("time", 0), data.getInt("currentFile", 0), data.getInt("progress", 0), @@ -94,8 +92,8 @@ public class DictionaryLoadingBar extends DictionaryProgressNotification { } - private String generateTitle(Context context, int languageId) { - Language lang = LanguageCollection.getLanguage(context, languageId); + private String generateTitle(int languageId) { + Language lang = LanguageCollection.getLanguage(languageId); if (lang != null) { return resources.getString(R.string.dictionary_loading, lang.getName()); @@ -105,7 +103,7 @@ public class DictionaryLoadingBar extends DictionaryProgressNotification { } - private void showProgress(Context context, long time, int currentFile, int currentFileProgress, int languageId) { + private void showProgress(long time, int currentFile, int currentFileProgress, int languageId) { if (currentFileProgress <= 0) { hide(); isStopped = true; @@ -119,12 +117,12 @@ public class DictionaryLoadingBar extends DictionaryProgressNotification { if (progress >= maxProgress) { progress = maxProgress = 0; - title = generateTitle(context, -1); + title = generateTitle(-1); String timeFormat = time > 60000 ? " (%1.0fs)" : " (%1.1fs)"; message = resources.getString(R.string.completed) + String.format(Locale.ENGLISH, timeFormat, time / 1000.0); } else { - title = generateTitle(context, languageId); + title = generateTitle(languageId); message = currentFileProgress + "%"; } @@ -132,8 +130,8 @@ public class DictionaryLoadingBar extends DictionaryProgressNotification { } - private void showError(Context context, String errorType, int langId, long line) { - Language lang = LanguageCollection.getLanguage(context, langId); + private void showError(String errorType, int langId, long line) { + Language lang = LanguageCollection.getLanguage(langId); if (lang == null || errorType.equals(InvalidLanguageException.class.getSimpleName())) { message = resources.getString(R.string.add_word_invalid_language); @@ -147,7 +145,7 @@ public class DictionaryLoadingBar extends DictionaryProgressNotification { message = resources.getString(R.string.dictionary_load_error, lang.getName(), errorType); } - title = generateTitle(context, -1); + title = generateTitle(-1); progress = maxProgress = 0; renderError(); diff --git a/app/src/main/java/io/github/sspanak/tt9/util/Characters.java b/app/src/main/java/io/github/sspanak/tt9/util/Characters.java index 61b5738c..1070d007 100644 --- a/app/src/main/java/io/github/sspanak/tt9/util/Characters.java +++ b/app/src/main/java/io/github/sspanak/tt9/util/Characters.java @@ -47,6 +47,10 @@ public class Characters { ",", ".", "-", "«", "»", "(", ")", "&", "~", "`", "'", "\"", "·", ":", "!", GR_QUESTION_MARK )); + final public static ArrayList PunctuationKorean = new ArrayList<>(Arrays.asList( + ",", ".", "~", "1", "(", ")", "&", "-", "`", ";", ":", "'", "\"", "!", "?" + )); + final public static ArrayList Currency = new ArrayList<>(Arrays.asList( "$", "€", "₹", "₿", "₩", "¢", "¤", "₺", "₱", "¥", "₽", "£" )); @@ -107,15 +111,6 @@ public class Characters { )) )); - public static boolean isStaticEmoji(String emoji) { - for (ArrayList group : Emoji) { - if (group.contains(emoji)) { - return true; - } - } - return false; - } - public static boolean isGraphic(char ch) { return !(ch < 256 || Character.isLetterOrDigit(ch) || Character.isAlphabetic(ch)); } diff --git a/app/src/main/java/io/github/sspanak/tt9/util/TextTools.java b/app/src/main/java/io/github/sspanak/tt9/util/TextTools.java index 297e8a6b..d0a17eb2 100644 --- a/app/src/main/java/io/github/sspanak/tt9/util/TextTools.java +++ b/app/src/main/java/io/github/sspanak/tt9/util/TextTools.java @@ -10,6 +10,7 @@ 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 isHangul = Pattern.compile("[\u1100-\u11FF\u302E-\u302F\u3131-\u318F\u3200-\u321F\u3260-\u327E\uA960-\uA97F\uAC00-\uD7FB\uFFA0-\uFFDF]+"); private static final Pattern nextToWord = Pattern.compile("\\b$"); private static final Pattern previousIsLetter = Pattern.compile("\\p{L}$"); private static final Pattern startOfSentence = Pattern.compile("(? - - Дарете Ако харесвате %1$s, черпете една бира на: %2$s. Редактиране на текст + Добавянето на думи не е възможно на този език. Триене на текст Речникови известия Получавайте известия за обновления на речника и за прогреса при зареждане. @@ -81,6 +82,7 @@ Списък с команди Изчистване на филтър Филтриране на думи + Филтрирането не е възможно на този език. Предишна дума Следваща дума Следващ eзик diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 252c76fe..9ec5b925 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -66,6 +66,7 @@ Tastenkürzel Tastenfeld Leerzeichen + Das Hinzufügen von Wörtern ist in dieser Sprache nicht möglich. Rücktaste Status Standardtastatur auswählen @@ -174,4 +175,5 @@ Reihenfolge speichern Verbotenes Zeichen:%1$s Verbotene Zeichen:%1$s + Das Filtern ist in dieser Sprache nicht möglich. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 35960d66..9ca548f9 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -35,6 +35,7 @@ Tema oscuro Espacio Borrando… + No es posible agregar palabras en este idioma. Retroceso Estado Selecciona teclado predeterminado @@ -81,6 +82,7 @@ Lista de comandos Limpiar el filtro Filtrar sugerencias + No es posible filtrar en este idioma. Sugerencia previa Sugerencia siguiente Idioma siguiente diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 1e1505d6..77f1d79d 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -67,12 +67,14 @@ Commencer automatiquement les phrases avec une majuscule. Clavier Espace + L\'ajout de mots n\'est pas possible dans cette langue. Retour arrière Notifications du dictionnaire Recevoir des notifications sur les mises à jour du dictionnaire et sur la progression du chargement. Liste des commandes Supprimer le filtre Filtrer les mots + Le filtrage n\'est pas possible dans cette langue. Mot précédent Mot suivant Langue suivante diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 41c1fbf7..e5ce27e8 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -67,6 +67,7 @@ Caricamento annullato. Tastiera Spazio + Aggiungere parole non è possibile in questa lingua. Backspace Stato Seleziona la tastiera predefinita @@ -174,5 +175,6 @@ Salvare l\'ordine Carattere vietato:%1$s Caratteri vietati:%1$s + Il filtraggio non è possibile in questa lingua. diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index f0304c90..a6c4c894 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -77,6 +77,7 @@ המילון נמחק בהצלחה. המחיקה מתבצעת… + אין אפשרות להוסיף מילים בשפה זו. לחצן מחיקה לחצן למעבר לשפה הבאה לחצן מצב קלט @@ -187,4 +188,5 @@ שמור את הסדר תו אסור:%1$s תווים אסורים:%1$s + לא ניתן לסנן בשפה זו. diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index cb00c544..f9931d23 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -77,12 +77,14 @@ Žodynas sėkmingai ištrintas. Ištrinama… + Žodžių pridėjimas šia kalba nėra galimas. Trinti Žodyno pranešimai Gaukite pranešimus apie žodynų atnaujinimus ir įkėlimo progresą. Rodyti komandų sąrašą Panaikinti filtrą Filtruoti pasiūlymus + Filtravimas šia kalba nėra galimas. Ankstesnis pasiūlytas žodis Sekantis pasiūlytas žodis Rašymo kalba diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 0bce4786..8c1cf8e4 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -68,6 +68,7 @@ Sneltoetsen Toetsenbord Spatie + Het toevoegen van woorden is niet mogelijk in deze taal. Backspace Status Standaardtoetsenbord selecteren @@ -173,4 +174,5 @@ Volgorde opslaan Verboden teken:%1$s Verboden tekens:%1$s + Het filteren is niet mogelijk in deze taal. diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index d37e0b80..ded88cbe 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -72,6 +72,7 @@ Limpar Dicionário Dicionário apagado com sucesso. + Não é possível adicionar palavras neste idioma. Backspace Próximo Idioma Modo de Entrada @@ -187,4 +188,5 @@ Salvar ordem Caractere proibido:%1$s Caracteres proibidos:%1$s + Não é possível filtrar neste idioma. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 32cd8f97..b6de5dfb 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -67,12 +67,14 @@ Автоматически начинать предложение с заглавной буквы. Символ при двойном нажатии клавиши 0 Не удалось загрузить словарь. Проблема в слове в строке %1$d для языка «%2$s». + Добавление слов невозможно на этом языке. Стереть Уведомления словаря Получать уведомления о обновлениях словаря и о процессе загрузки. Список команд Удалить фильтр Фильтровать слова + Фильтрация невозможна на этом языке. Предыдущее слово Следующее слово Следующий язык diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 9db9ac97..6fdfbed0 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -65,6 +65,7 @@ Klavye Kısayolları Tuş Takımı Boşluk + Bu dilde kelime eklemek mümkün değil. Geri Tuşu Durum Varsayılan Klavyeyi Seçin @@ -136,6 +137,7 @@ Komut listesini göster Filtre Temizle Tahminleri Filtrele + Bu dilde filtreleme mümkün değil. Önceki Tahmin Sonraki Tahmin Sonraki Dil diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 069cfff9..822a360d 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -110,12 +110,14 @@ Підтримати Якщо вам подобається %1$s, ви можете пригостити мене пивом за адресою: %2$s. + Додавання слів неможливе цією мовою. Стерти Сповіщення словника Отримувати повідомлення про оновлення словника та процес завантаження. Список команд Очистити фільтр Фільтрувати пропозиції + Фільтрація неможлива цією мовою. Попередня пропозиція Наступна пропозиція Наступна мова diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 84a351d0..f3cbcbef 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -144,11 +144,13 @@ www.buymeacoffee.com Add Word + Adding words is not possible in this language. Backspace Show Command List Clear Filter Edit Text Filter Suggestions + Filtering is not possible in this language. Previous Suggestion Next Suggestion Next Language @@ -221,6 +223,7 @@ Mode Cfg Shift + Space (Korean) Copy Speak diff --git a/app/src/main/res/xml/prefs_screen_debug.xml b/app/src/main/res/xml/prefs_screen_debug.xml index 26a1b109..a8eeebbb 100644 --- a/app/src/main/res/xml/prefs_screen_debug.xml +++ b/app/src/main/res/xml/prefs_screen_debug.xml @@ -19,6 +19,13 @@ + + diff --git a/app/src/main/res/xml/prefs_screen_hotkeys.xml b/app/src/main/res/xml/prefs_screen_hotkeys.xml index 8fadf0c2..9097ec77 100644 --- a/app/src/main/res/xml/prefs_screen_hotkeys.xml +++ b/app/src/main/res/xml/prefs_screen_hotkeys.xml @@ -50,6 +50,10 @@ app:key="key_shift" app:title="@string/virtual_key_shift" /> + + diff --git a/app/validate-languages.gradle b/app/validate-languages.gradle index 39561f3a..8d6abecd 100644 --- a/app/validate-languages.gradle +++ b/app/validate-languages.gradle @@ -60,6 +60,7 @@ ext.parseLanguageDefintion = { File languageFile, String dictionariesDir -> alphabet = languageFile.name.contains("Catalan") ? '·' : alphabet alphabet = languageFile.name.contains("Hebrew") || languageFile.name.contains("Yiddish") ? '"' : alphabet + alphabet = languageFile.name.contains("Korean") ? ':' : alphabet for (String line : languageFile.readLines()) { if ( @@ -292,7 +293,7 @@ static def validateWord(String word, String validCharacters, boolean isAlphabeti errors += "${errorMsgPrefix}. Found numbers on line ${lineNumber}. Remove all numbers.\n" } - if (word.matches("^\\P{L}+\$")) { + if (word.matches("^\\P{L}+\$") && !validCharacters.contains(word)) { errorCount++ errors += "${errorMsgPrefix}. Found a garbage word: '${word}' on line ${lineNumber}.\n" } diff --git a/docs/dictionaries/koWordlistReadme.txt b/docs/dictionaries/koWordlistReadme.txt new file mode 100644 index 00000000..4ae340cf --- /dev/null +++ b/docs/dictionaries/koWordlistReadme.txt @@ -0,0 +1,6 @@ +Hangul characters table 1 from Wikipedia +URL: https://en.wikipedia.org/wiki/Hangul_consonant_and_vowel_tables +License: Creative Commons Attribution-ShareAlike 4.0 License + +Hangul characters table 2 by Tammy Korean +URL: https://learning-korean.com/pdf/ diff --git a/docs/help/help.de.md b/docs/help/help.de.md index 2e89c8d8..fe4c0331 100644 --- a/docs/help/help.de.md +++ b/docs/help/help.de.md @@ -79,6 +79,10 @@ _**Hinweis 2:** Um Nachrichten mit OK in Nachrichtenanwendungen zu senden, müss - **Doppeldruck:** tippt das Zeichen, das in den Einstellungen für den Prädiktiven Modus zugewiesen wurde (Standard: „.“) - **Halten:** tippt „0“. - **Drücken „0“ und Drücken der Umschalt-Taste (Standard: Drücken „0“, „✱“):** tippt Währungszeichen +- **Im Cheonjiin-Modus (Koreanisch):** + - **Drücken:** Gibt "ㅇ" und "ㅁ" ein. + - **Halten:** Gibt Leerzeichen, neue Zeilen, "0" oder Sonder-/Mathematikzeichen ein. + - **Halten von "0" und Drücken von Shift (Standard: Halten von "0", Drücken von "✱"):** Gibt Währungszeichen ein. #### 1-Taste: - **Im 123 Modus:** @@ -92,7 +96,11 @@ _**Hinweis 2:** Um Nachrichten mit OK in Nachrichtenanwendungen zu senden, müss - **Mehrfaches Drücken:** tippt Emoji - **Drücken von 1-1-3:** tippt benutzerdefinierte Emojis (diese müssen zuvor über die [Wort hinzufügen-Taste](#wort-hinzufügen-taste) hinzugefügt worden sein) - **Halten:** tippt „1“. - +- **Im Cheonjiin-Modus (Koreanisch):** + - **Drücken:** tippt den Vokal "ㅣ". + - **Halten:** tippt Satzzeichen. + - **Halten, dann drücken:** tippt Emoji. + - **Halten 1, 1 drücken, 3 drücken:** tippt benutzerdefinierte Emojis (diese müssen zuvor über die [Wort hinzufügen-Taste](#wort-hinzufügen-taste) hinzugefügt worden sein) #### 2- bis 9-Taste: - **Im 123 Modus:** tippt die entsprechende Zahl diff --git a/docs/help/help.en.md b/docs/help/help.en.md index c499bde7..29c3d3b9 100644 --- a/docs/help/help.en.md +++ b/docs/help/help.en.md @@ -69,33 +69,41 @@ _**Note 2:** To send messages with OK in messaging applications, you must enable - **In 123 mode:** - **Press:** type "0". - **Hold:** type special/math characters. - - **Hold "0", then press Shift (Default: hold "0", press "✱"):** type currency characters + - **Hold "0", then press Shift (Default: hold "0", press "✱"):** type currency characters. - **In ABC mode:** - **Press:** type space, newline, or special/math characters. - **Hold:** type "0". - - **Press "0", then press Shift (Default: press "0", "✱"):** type currency characters + - **Press "0", then press Shift (Default: press "0", "✱"):** type currency characters. - **In Predictive mode:** - **Press:** type space, newline, or special/math characters. - **Double press:** type the character assigned in Predictive mode settings. (Default: ".") - **Hold:** type "0". - - **Press "0", then press Shift (Default: press "0", "✱"):** type currency characters + - **Press "0", then press Shift (Default: press "0", "✱"):** type currency characters. +- **In Cheonjiin mode (Korean):** + - **Press:** type "ㅇ" and "ㅁ". + - **Hold:** type space, newline, 0, or special/math characters. + - **Hold "0", then press Shift (Default: hold "0", press "✱"):** type currency characters. #### 1-key: - **In 123 mode:** - **Press:** type "1". - - **Hold:** type sentence characters + - **Hold:** type sentence characters. - **In ABC mode:** - - **Press:** type sentence characters + - **Press:** type sentence characters. - **Hold:** type "1". - **In Predictive mode:** - - **Press:** type sentence characters - - **Press multiple times:** type emoji - - **Press 1-1-3:** type custom added emoji (you must have added some using [the Add Word key](#add-word-key)) + - **Press:** type punctuation and sentence characters. + - **Press multiple times:** type emoji. + - **Press 1-1-3:** type custom added emoji (you must have added some using [the Add Word key](#add-word-key)). - **Hold:** type "1". - +- **In Cheonjiin mode (Korean):** + - **Press:** type the "ㅣ" vowel. + - **Hold:** type punctuation and sentence characters. + - **Hold, then press:** type emoji. + - **Hold 1, press 1, press 3:** type custom added emoji (you must have added some using [the Add Word key](#add-word-key)). #### 2- to 9-key: -- **In 123 mode:** type the respective number +- **In 123 mode:** type the respective number. - **In ABC and Predictive mode:** type a letter or hold to type the respective number. ### Function Keys diff --git a/docs/help/help.es.md b/docs/help/help.es.md index 2d3d1aa5..a87b1693 100644 --- a/docs/help/help.es.md +++ b/docs/help/help.es.md @@ -71,31 +71,40 @@ _**Nota 2:** Para enviar mensajes con OK en aplicaciones de mensajería, debes h - **Mantenga presionado:** escribir caracteres especiales/matemáticos. - **Mantenga presionado "0", luego presione Shift (Por Defecto: mantenga presionado "0", presione "✱")**: escribir caracteres de moneda. - **En modo ABC:** - - **Presione:** escribir espacio, nueva línea o caracteres especiales/matemáticos. + - **Presione:** escribir un espacio, nueva línea o caracteres especiales/matemáticos. - **Mantenga presionado:** escribir "0". - **Presione "0", luego presione Shift (Por Defecto: presione "0", "✱")**: escribir caracteres de moneda. -- **En modo predictivo:** - - **Presione:** escribir espacio, nueva línea o caracteres especiales/matemáticos. - - **Doble presión:** escribir el carácter asignado en la configuración del modo predictivo. (Por Defecto: ".") +- **En modo Predictivo:** + - **Presione:** escribir un espacio, nueva línea o caracteres especiales/matemáticos. + - **Presione dos veces:** escribir el carácter asignado en la configuración de modo predictivo. (Por Defecto: ".") - **Mantenga presionado:** escribir "0". - **Presione "0", luego presione Shift (Por Defecto: presione "0", "✱")**: escribir caracteres de moneda. +- **En modo Cheonjiin (Coreano):** + - **Presione:** escribir "ㅇ" y "ㅁ". + - **Mantenga presionado:** escribir espacio, nueva línea, "0" o caracteres especiales/matemáticos. + - **Mantenga presionado "0", luego presione Shift (Por Defecto: Mantenga presionado "0", presione "✱"):** escribir caracteres de moneda. #### Tecla 1: - **En modo 123:** - **Presione:** escribir "1". - - **Mantenga presionado:** escribir caracteres de oración. + - **Mantenga presionado:** escribir caracteres de puntuación. - **En modo ABC:** - - **Presione:** escribir caracteres de oración. + - **Presione:** escribir caracteres de puntuación. - **Mantenga presionado:** escribir "1". -- **En modo predictivo:** - - **Presione:** escribir caracteres de oración. - - **Presione varias veces:** escribir emoji. +- **En modo Predictivo:** + - **Presione:** escribir caracteres de puntuación. + - **Presione varias veces:** escribir emojis. - **Presione 1-1-3:** escribir emoji personalizados agregados (debe haber agregado algunos usando la [Tecla de Agregar Palabra](#tecla-de-agregar-palabra)). - **Mantenga presionado:** escribir "1". +- **En modo Cheonjiin (Coreano):** + - **Presione:** escribir la vocal "ㅣ". + - **Mantenga presionado:** escribir caracteres de puntuación. + - **Mantene, luego presione:** escribir emojis. + - **Mantenga presionado 1, presione 1, presione 3:** escribir emoji personalizados agregados (debe haber agregado algunos usando la [Tecla de Agregar Palabra](#tecla-de-agregar-palabra)). #### Teclas del 2 al 9: -- **En modo 123:** escribir el número respectivo. -- **En modo ABC y Predictivo:** escribir una letra o mantener presionado para escribir el número respectivo. +- **En modo 123:** escribir el número correspondiente. +- **En modo ABC y Predictivo:** escribir una letra o mantener presionado para escribir el número correspondiente. ### Teclas de Función diff --git a/docs/help/help.fr.md b/docs/help/help.fr.md index db4457a5..bf4cde81 100644 --- a/docs/help/help.fr.md +++ b/docs/help/help.fr.md @@ -75,23 +75,32 @@ _**Remarque 2** : Pour envoyer des messages avec OK dans les applications de mes - **Appui long** : tape "0". - **Appui sur "0", puis appui sur Maj (par défaut : appui sur "0", "✱") :** tape des symboles monétaires. - **En mode Prédictif :** - - **Appui** : tape espace, nouvelle ligne, ou caractères spéciaux/mathématiques. + - **Appui** : tape un espace, une nouvelle ligne, ou caractères spéciaux/mathématiques. - **Double appui** : tape le caractère attribué dans les paramètres du mode Prédictif (par défaut : "."). - **Appui long** : tape "0". - **Appui sur "0", puis appui sur Maj (par défaut : appui sur "0", "✱") :** tape des symboles monétaires. +- **En mode Cheonjiin (Coréen) :** + - **Appui :** tape "ㅇ" et "ㅁ". + - **Appui long :** tape un espace, une nouvelle ligne, "0" ou des caractères spéciaux/mathématiques. + - **Appui long sur "0", puis appui sur Maj (Par défaut : appui long sur "0", appuyer sur "✱") :** tape des symboles monétaires. #### Touche 1 : - **En mode 123 :** - **Appui** : tape "1". - - **Appui long** : tape des caractères de phrase. + - **Appui long** : tape des caractères de ponctuation. - **En mode ABC :** - - **Appui** : tape des caractères de phrase. + - **Appui** : tape des caractères de ponctuation. - **Appui long** : tape "1". - **En mode Prédictif :** - - **Appui** : tape des caractères de phrase. + - **Appui** : tape des caractères de ponctuation. - **Appui multiple** : tape des émojis. - **Appui 1-1-3** : tape des émojis ajoutés (vous devez en ajouter en utilisant [la touche Ajouter un Mot](#touche-ajouter-un-mot)). - **Appui long** : tape "1". +- **En mode Cheonjiin (Coréen) :** + - **Appui :** tape la voyelle "ㅣ". + - **Appui long :** tape des caractères de ponctuation. + - **Appui long, puis appui court :** tape des emojis. + - **Appui long sur 1, appui sur 1, puis sur 3 :** tape des emojis ajoutés (vous devez en ajouter en utilisant [la touche Ajouter un Mot](#touche-ajouter-un-mot)). #### Touche 2 à 9 : - **En mode 123** : tape le chiffre correspondant. diff --git a/docs/help/help.it.md b/docs/help/help.it.md index 6752d52e..bbfb30c4 100644 --- a/docs/help/help.it.md +++ b/docs/help/help.it.md @@ -75,10 +75,14 @@ _**Nota 2:** Per inviare messaggi con OK nelle applicazioni di messaggistica, è - **Tenere premuto:** digita "0". - **Premere "0", quindi premere Maiusc (Default: premere "0", "✱"):** digita caratteri di valuta - **In modalità Predittiva:** - - **Premere:** digita spazio, nuova riga o caratteri speciali/matematici. + - **Premere:** digita uno spazio, una nuova linea, "0" o caratteri speciali/matematici. - **Premere due volte:** digita il carattere assegnato nelle impostazioni della modalità predittiva. (Default: ".") - **Tenere premuto:** digita "0". - **Premere "0", quindi premere Maiusc (Default: premere "0", "✱"):** digita caratteri di valuta +- **In modalità Cheonjiin (Coreano):** + - **Premere:** digita "ㅇ" e "ㅁ". + - **Tenere premuto:** digita uno spazio, una nuova linea, "0" o caratteri speciali/matematici. + - **Tenere premuto "0", poi premere Maiusc (Predefinito: tenere premuto "0", premere "✱"):** Digita caratteri monetari. #### Tasto 1: - **In modalità 123:** @@ -92,6 +96,11 @@ _**Nota 2:** Per inviare messaggi con OK nelle applicazioni di messaggistica, è - **Premere più volte:** digita emoji - **Premere 1-1-3:** digita emoji aggiunti personalizzati (è necessario averne aggiunti alcuni utilizzando [il Tasto Aggiungi Parola](#tasto-aggiungi-parola)) - **Tenere premuto:** digita "1". +- **In modalità Cheonjiin (Coreano):** + - **Premere:** digita la vocale "ㅣ". + - **Tenere premuto:** digita caratteri di punteggiatura. + - **Tenere premuto, quindi premere:** digita emoji. + - **Tenere premuto 1, premere 1, poi 3:** digita emoji aggiunti personalizzati (è necessario averne aggiunti alcuni utilizzando [il Tasto Aggiungi Parola](#tasto-aggiungi-parola)) #### Tasti da 2 a 9: - **In modalità 123:** digita il rispettivo numero diff --git a/docs/help/help.pt.md b/docs/help/help.pt.md index d45795fe..641244fd 100644 --- a/docs/help/help.pt.md +++ b/docs/help/help.pt.md @@ -79,19 +79,29 @@ _**Nota 2**: Para enviar mensagens com OK em aplicativos de mensagens, você dev - **Toque duas vezes**: insere o caractere atribuído nas configurações do modo Preditivo (padrão: "."). - **Pressione e segure**: insere "0". - **Pressione "0", depois pressione Shift (padrão: pressione "0", "✱")**: insere símbolos monetários. +- **No modo Cheonjiin (Coreano):** + - **Pressione:** insere "ㅇ" e "ㅁ". + - **Segure:** insere espaço, nova linha, "0" ou caracteres especiais/matemáticos. + - **Segure "0", depois pressione Shift (Padrão: segure "0", pressione "✱")**: insere símbolos monetários. + #### Tecla 1: - **No modo 123:** - **Toque**: insere "1". - - **Pressione e segure**: insere caracteres de frase. + - **Pressione e segure**: insere caracteres de pontuação. - **No modo ABC:** - - **Toque**: insere caracteres de frase. + - **Toque**: insere caracteres de pontuação. - **Pressione e segure**: insere "1". - **No modo Preditivo:** - - **Toque**: insere caracteres de frase. + - **Toque**: insere caracteres de pontuação. - **Toque múltiplo**: insere emojis. - **Toque 1-1-3**: insere emojis adicionados (é necessário adicionar usando a [tecla Adicionar Palavra](#tecla-adicionar-palavra)). - **Pressione e segure**: insere "1". +- **No modo Cheonjiin (Coreano):** + - **Pressione:** insere a vogal "ㅣ". + - **Pressione e segure:** insere caracteres de pontuação. + - **Segure, depois pressione:** insere emojis. + - **Segure 1, pressione 1, depois 3:** insere emojis adicionados (é necessário adicionar usando a [tecla Adicionar Palavra](#tecla-adicionar-palavra)). #### Teclas de 2 a 9: - **No modo 123**: insere o número correspondente. diff --git a/docs/help/help.ru.md b/docs/help/help.ru.md index 663f3250..e714829e 100644 --- a/docs/help/help.ru.md +++ b/docs/help/help.ru.md @@ -79,19 +79,28 @@ _**Примечание 2:** Чтобы отправлять сообщения - **Двойное нажатие:** ввести символ, назначенный в настройках режима Предсказания (по умолчанию: "."). - **Удержание:** ввести "0". - **Нажатие "0", затем нажатие Shift (По умолчанию: нажатие "0", "✱")**: ввести символы валют +- **В режиме Чонджиин (Корейский):** + - **Нажатие:** ввести "ㅇ" и "ㅁ". + - **Удержание:** ввести пробел, новую строку, "0" или специальные/математические символы. + - **Удержание "0", затем нажатие Shift (По умолчанию: удержание "0", нажатие "✱")**: ввести валютные символы. #### Клавиша 1: - **В режиме 123:** - **Нажатие:** ввести "1". - - **Удержание:** ввести символы предложения. + - **Удержание:** ввести знаки препинания. - **В режиме ABC:** - - **Нажатие:** ввести символы предложения. + - **Нажатие:** ввести знаки препинания. - **Удержание:** ввести "1". - **В режиме Предсказания:** - - **Нажатие:** ввести символы предложения. + - **Нажатие:** ввести знаки препинания. - **Многократное нажатие:** ввести эмодзи. - **Нажатие 1-1-3:** ввести пользовательские добавленные эмодзи (необходимо добавить их с помощью [Клавиши добавления слова](#клавиша-добавления-слова)). - **Удержание:** ввести "1". +- **В режиме Чонджиин (Корейский):** + - **Нажатие:** ввести гласную "ㅣ". + - **Удержание:** ввести знаки препинания. + - **Удержание, затем нажатие:** ввести эмодзи. + - **Удержание 1, нажатие 1, затем 3:** ввести пользовательские добавленные эмодзи (необходимо добавить их с помощью [Клавиши добавления слова](#клавиша-добавления-слова)). #### Клавиши 2-9: - **В режиме 123:** введите соответствующую цифру diff --git a/docs/help/help.tr.md b/docs/help/help.tr.md index 045abb63..1bbdbcd8 100644 --- a/docs/help/help.tr.md +++ b/docs/help/help.tr.md @@ -79,19 +79,28 @@ _**Not 2:** Mesajlaşma uygulamalarında OK ile mesaj göndermek için, uygulama - **Çift bas:** Tahmin modunda ayarlanan karakteri yaz. (Varsayılan: ".") - **Basılı tut:** "0" yaz. - **"0" bas, ardından Shift'e bas (Varsayılan: "0" bas, "✱"e bas):** para birimi karakterleri yaz. +- **Cheonjiin modunda (Korece):** + - **Bas:** "ㅇ" ve "ㅁ" yaz. + - **Basılı tut:** boşluk, yeni satır, "0" veya özel/matematiksel karakterler yaz. + - **"0" tuşuna basılı tut, ardından Shift'e bas (Varsayılan: "0" tuşunu basılı tutun, "✱"ye basın):** Para birimi karakterleri yazar. #### 1-tușu: - **123 modunda:** - **Bas:** "1" yaz. - - **Basılı tut:** cümle işaretleri yaz. + - **Basılı tut:** noktalama işaretleri yaz. - **ABC modunda:** - - **Bas:** cümle işaretleri yaz. + - **Bas:** noktalama işaretleri yaz. - **Basılı tut:** "1" yaz. - **Tahmin modunda:** - - **Bas:** cümle işaretleri yaz. + - **Bas:** noktalama işaretleri yaz. - **Çoklu basış:** emoji yaz. - **1-1-3 bas:** özel eklenen emojiyi yaz (bazı emojiler [Kelime Ekle tuşu](#kelime-ekle-tușu) kullanarak eklenmelidir). - **Basılı tut:** "1" yaz. +- **Cheonjiin modunda (Korece):** + - **Bas:** "ㅣ" ünlüsünü yaz. + - **Basılı tut:** noktalama işaretleri yaz. + - **Basılı tut, ardından bas:** emoji yaz. + - **1 basılı tut, ardından 1'e ve 3'e bas:** özel eklenen emojiyi yaz (bazı emojiler [Kelime Ekle tuşu](#kelime-ekle-tușu) kullanarak eklenmelidir). #### 2-9 tușu: - **123 modunda:** ilgili sayıyı yaz. diff --git a/docs/help/help.uk.md b/docs/help/help.uk.md index b6dece5d..35db528a 100644 --- a/docs/help/help.uk.md +++ b/docs/help/help.uk.md @@ -79,19 +79,28 @@ _**Примітка 2:** Для відправлення повідомлень - **Подвійне натискання:** вводить символ, призначений у налаштуваннях режиму Прогнозування (за замовчуванням "."). - **Утримування:** вводить "0". - **Натискання "0", потім натискання Shift (за замовчуванням: натискання "0", "✱")**: вводить символи валюти. +- **У режимі Чонджиін (Корейська):** + - **Натискання:** вводить "ㅇ" та "ㅁ". + - **Утримування:** вводить пробіл, новий рядок, "0" або спеціальні/математичні символи. + - **Утримування "0", потім натискання Shift (за замовчуванням: утримування "0", натискання "✱"):** вводить символи валюти. #### Клавіша 1: - **У режимі 123:** - **Натискання:** вводить "1". - - **Утримування:** вводить символи речення. + - **Утримування:** вводить знаки пунктуації. - **У режимі ABC:** - - **Натискання:** вводить символи речення. + - **Натискання:** вводить знаки пунктуації. - **Утримування:** вводить "1". - **У режимі Прогнозування:** - - **Натискання:** вводить символи речення. + - **Натискання:** вводить знаки пунктуації. - **Кілька натискань:** вводить емодзі. - **Натискання 1-1-3:** вводить додані емодзі (їх потрібно додати через [клавішу Додати слово](#клавіша-додати-слово)). - **Утримування:** вводить "1". +- **У режимі Чонджиін (Корейська):** + - **Натискання:** вводить голосну "ㅣ". + - **Утримування:** вводить знаки пунктуації. + - **Утримування, потім натискань:** вводить емодзі. + - **Утримування 1, натискань 1, потім 3:** вводить додані емодзі (їх потрібно додати через [клавішу Додати слово](#клавіша-додати-слово)). #### Клавіші 2 до 9: - **У режимі 123:** вводить відповідне число. diff --git a/downloads/de-utf8.zip b/downloads/de-utf8.zip index 213d27c6..4c6f1646 100644 Binary files a/downloads/de-utf8.zip and b/downloads/de-utf8.zip differ diff --git a/downloads/ko-utf8.zip b/downloads/ko-utf8.zip new file mode 100644 index 00000000..148f6b81 Binary files /dev/null and b/downloads/ko-utf8.zip differ