Fix: added hangul jamo decomposition to handle ios broken IME

This commit is contained in:
Richard Wong 2024-08-29 15:25:39 +09:00
parent faf83b4827
commit 4513592483
Signed by: richard
GPG Key ID: 72948FBB6D359A6D
2 changed files with 119 additions and 38 deletions

View File

@ -1,6 +1,7 @@
import { useState } from "react"; import { useState } from "react";
import "./VerseValidator.css"; import "./VerseValidator.css";
import { StringDiff } from "react-string-diff"; import { StringDiff } from "react-string-diff";
import { containsKorean, jamoSubstringMatch } from './jamoUtils';
const STATE = { const STATE = {
INCORRECT: 0, INCORRECT: 0,
@ -23,11 +24,36 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
const[diffBool, setDiffBool] = useState(false) const[diffBool, setDiffBool] = useState(false)
const [isComposing, setIsComposing] = useState(false); const [isComposing, setIsComposing] = useState(false);
// Handle the start of composition // Handle the start of composition
const handleCompositionStart = () => { const handleCompositionStart = () => {
setIsComposing(true); setIsComposing(true);
}; };
function resultChecker(string1, string2) {
var result = STATE.INCORRECT; // init
// contains korean
if (containsKorean(string1)) {
if (string1 === string2) {
result = STATE.CORRECT;
} else if (jamoSubstringMatch(string2, string1) & string1 !== "") {
result = STATE.PARTIAL;
} else {
result = STATE.INCORRECT;
}
} else { // does not contain korean
if (string1 === string2) {
result = STATE.CORRECT;
} else if (string2.startsWith(string1) & string1 !== "") {
result = STATE.PARTIAL;
} else {
result = STATE.INCORRECT;
}
}
return result;
}
// function to check correctness of reference input // function to check correctness of reference input
const referenceChange = (e) => { const referenceChange = (e) => {
@ -40,14 +66,8 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
.replace(/\s+/g, "") .replace(/\s+/g, "")
.toLowerCase() .toLowerCase()
.normalize("NFC"); .normalize("NFC");
var result = STATE.INCORRECT; // init
if (string1 === string2) { const result = resultChecker(string1, string2);
result = STATE.CORRECT;
} else if (string2.startsWith(string1) & string1 !== "") {
result = STATE.PARTIAL
} else {
result = STATE.INCORRECT
}
setReference(value); setReference(value);
setReferenceBool(result); setReferenceBool(result);
@ -79,14 +99,9 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
.toLowerCase() .toLowerCase()
.normalize("NFC"); .normalize("NFC");
var result = STATE.INCORRECT; // init
if (string1 === string2) { const result = resultChecker(string1, string2);
result = STATE.CORRECT;
} else if (string2.startsWith(string1) & string1 !== "") {
result = STATE.PARTIAL
} else {
result = STATE.INCORRECT
}
setTitle(value); setTitle(value);
setTitleBool(result); setTitleBool(result);
@ -118,14 +133,7 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
.toLowerCase() .toLowerCase()
.normalize("NFC"); .normalize("NFC");
var result = STATE.INCORRECT; // init const result = resultChecker(string1, string2);
if (string1 === string2) {
result = STATE.CORRECT;
} else if (string2.startsWith(string1) & string1 !== "") {
result = STATE.PARTIAL
} else {
result = STATE.INCORRECT
}
setChapterTitle(value); setChapterTitle(value);
setChapterTitleBool(result); setChapterTitleBool(result);
@ -153,14 +161,7 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
.toLowerCase() .toLowerCase()
.normalize("NFC"); .normalize("NFC");
var result = STATE.INCORRECT; // init const result = resultChecker(string1, string2);
if (string1 === string2) {
result = STATE.CORRECT;
} else if (string2.startsWith(string1) & string1 !== "") {
result = STATE.PARTIAL
} else {
result = STATE.INCORRECT
}
setVerse(value); setVerse(value);
setVerseBool(result); setVerseBool(result);
@ -215,15 +216,11 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse
id="referenceBox" id="referenceBox"
name="referenceBox" name="referenceBox"
onChange={(event) => { onChange={(event) => {
if (!isComposing) { if (!isComposing) {
referenceChange(event); referenceChange(event);
} }
}} }}
onCompositionStart={handleCompositionStart}
onCompositionEnd={(event) => {
setIsComposing(false);
referenceChange(event);
}}
/> />
</div> </div>
) : ( ) : (

84
src/jamoUtils.js Normal file
View File

@ -0,0 +1,84 @@
const BASE_CODE = 44032;
const CHO = 588;
const JUNG = 28;
const cho = ["ㄱ", "ㄲ", "ㄴ", "ㄷ", "ㄸ", "ㄹ", "ㅁ", "ㅂ", "ㅃ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅉ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ"];
const jung = ["ㅏ", "ㅐ", "ㅑ", "ㅒ", "ㅓ", "ㅔ", "ㅕ", "ㅖ", "ㅗ", "ㅘ", "ㅙ", "ㅚ", "ㅛ", "ㅜ", "ㅝ", "ㅞ", "ㅟ", "ㅠ", "ㅡ", "ㅢ", "ㅣ"];
const jong = ["", "ㄱ", "ㄲ", "ㄳ", "ㄴ", "ㄵ", "ㄶ", "ㄷ", "ㄹ", "ㄺ", "ㄻ", "ㄼ", "ㄽ", "ㄾ", "ㄿ", "ㅀ", "ㅁ", "ㅂ", "ㅄ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ"];
// Decompose the medial vowel into individual components, if necessary
const jungSplit = {
"ㅘ": ["ㅗ", "ㅏ"],
"ㅙ": ["ㅗ", "ㅐ"],
"ㅚ": ["ㅗ", "ㅣ"],
"ㅝ": ["ㅜ", "ㅓ"],
"ㅞ": ["ㅜ", "ㅔ"],
"ㅟ": ["ㅜ", "ㅣ"],
"ㅢ": ["ㅡ", "ㅣ"],
};
// decompose ending consonents
const jongSplit = {
"ㄲ": ["ㄱ", "ㄱ"],
"ㄳ": ["ㄱ", "ㅅ"],
"ㄵ": ["ㄴ", "ㅈ"],
"ㄶ": ["ㄴ", "ㅎ"],
"ㄺ": ["ㄹ", "ㄱ"],
"ㄻ": ["ㄹ", "ㅁ"],
"ㄼ": ["ㄹ", "ㅂ"],
"ㄽ": ["ㄹ", "ㅅ"],
"ㄾ": ["ㄹ", "ㅌ"],
"ㄿ": ["ㄹ", "ㅍ"],
"ㅀ": ["ㄹ", "ㅎ"],
"ㅄ": ["ㅂ", "ㅅ"],
"ㅆ": ["ㅅ", "ㅅ"],
}
export function decomposeHangul(character) {
const code = character.charCodeAt(0) - BASE_CODE;
if (code < 0 || code > 11171) {
// Return character as is if it's not a Hangul syllable
return character;
}
const choIndex = Math.floor(code / CHO);
const jungIndex = Math.floor((code - (choIndex * CHO)) / JUNG);
const jongIndex = code % JUNG;
// Decompose cho, jung, and jong
const choJamo = cho[choIndex];
const jungJamo = jung[jungIndex];
const jongJamo = jong[jongIndex];
// Handle double jamo in jung
const jungComponents = jungSplit[jungJamo] || [jungJamo];
// handle double jamo in jong
const jongComponents = jongSplit[jongJamo] || [jongJamo];
return [choJamo, ...jungComponents, ...jongComponents].join('');
}
export function decomposeStringToJamo(inputString) {
return inputString.split('').map(decomposeHangul).join('');
}
export function jamoSubstringMatch(mainString, substring) {
const decomposedMain = decomposeStringToJamo(mainString)
.replace(/\s+/g, "");
const decomposedSub = decomposeStringToJamo(substring)
.replace(/\s+/g, "");
return decomposedMain.startsWith(decomposedSub);
}
// Unicode Ranges for Korean Characters:
// Hangul Syllables: \uAC00-\uD7A3
// Hangul Jamo: \u1100-\u11FF (for initial consonants, vowels, and final consonants)
// Hangul Compatibility Jamo: \u3130-\u318F
export function containsKorean(text) {
const koreanRegex = /[\u1100-\u11FF\u3130-\u318F\uAC00-\uD7A3]/;
return koreanRegex.test(text);
}