diff --git a/lib/services/romaji_transliteration.dart b/lib/services/romaji_transliteration.dart new file mode 100644 index 0000000..814453a --- /dev/null +++ b/lib/services/romaji_transliteration.dart @@ -0,0 +1,622 @@ +// Source: https://github.com/Kimtaro/ve/blob/master/lib/providers/japanese_transliterators.rb + +const hiragana_syllabic_n = 'ん'; +const hiragana_small_tsu = 'っ'; + +const Map hiragana_to_latin = { + 'あ': 'a', + 'い': 'i', + 'う': 'u', + 'え': 'e', + 'お': 'o', + 'か': 'ka', + 'き': 'ki', + 'く': 'ku', + 'け': 'ke', + 'こ': 'ko', + 'が': 'ga', + 'ぎ': 'gi', + 'ぐ': 'gu', + 'げ': 'ge', + 'ご': 'go', + 'さ': 'sa', + 'し': 'shi', + 'す': 'su', + 'せ': 'se', + 'そ': 'so', + 'ざ': 'za', + 'じ': 'ji', + 'ず': 'zu', + 'ぜ': 'ze', + 'ぞ': 'zo', + 'た': 'ta', + 'ち': 'chi', + 'つ': 'tsu', + 'て': 'te', + 'と': 'to', + 'だ': 'da', + 'ぢ': 'ji', + 'づ': 'zu', + 'で': 'de', + 'ど': 'do', + 'な': 'na', + 'に': 'ni', + 'ぬ': 'nu', + 'ね': 'ne', + 'の': 'no', + 'は': 'ha', + 'ひ': 'hi', + 'ふ': 'fu', + 'へ': 'he', + 'ほ': 'ho', + 'ば': 'ba', + 'び': 'bi', + 'ぶ': 'bu', + 'べ': 'be', + 'ぼ': 'bo', + 'ぱ': 'pa', + 'ぴ': 'pi', + 'ぷ': 'pu', + 'ぺ': 'pe', + 'ぽ': 'po', + 'ま': 'ma', + 'み': 'mi', + 'む': 'mu', + 'め': 'me', + 'も': 'mo', + 'や': 'ya', + 'ゆ': 'yu', + 'よ': 'yo', + 'ら': 'ra', + 'り': 'ri', + 'る': 'ru', + 'れ': 're', + 'ろ': 'ro', + 'わ': 'wa', + 'うぃ': 'whi', + 'うぇ': 'whe', + 'を': 'wo', + 'ゑ': 'we', + 'ゐ': 'wi', + 'ー': '-', + 'ん': 'n', + 'きゃ': 'kya', + 'きゅ': 'kyu', + 'きょ': 'kyo', + 'きぇ': 'kye', + 'きぃ': 'kyi', + 'ぎゃ': 'gya', + 'ぎゅ': 'gyu', + 'ぎょ': 'gyo', + 'ぎぇ': 'gye', + 'ぎぃ': 'gyi', + 'くぁ': 'kwa', + 'くぃ': 'kwi', + 'くぅ': 'kwu', + 'くぇ': 'kwe', + 'くぉ': 'kwo', + 'ぐぁ': 'qwa', + 'ぐぃ': 'gwi', + 'ぐぅ': 'gwu', + 'ぐぇ': 'gwe', + 'ぐぉ': 'gwo', + 'しゃ': 'sha', + 'しぃ': 'syi', + 'しゅ': 'shu', + 'しぇ': 'she', + 'しょ': 'sho', + 'じゃ': 'ja', + 'じゅ': 'ju', + 'じぇ': 'jye', + 'じょ': 'jo', + 'じぃ': 'jyi', + 'すぁ': 'swa', + 'すぃ': 'swi', + 'すぅ': 'swu', + 'すぇ': 'swe', + 'すぉ': 'swo', + 'ちゃ': 'cha', + 'ちゅ': 'chu', + 'ちぇ': 'tye', + 'ちょ': 'cho', + 'ちぃ': 'tyi', + 'ぢゃ': 'ja', + 'ぢぃ': 'dyi', + 'ぢゅ': 'ju', + 'ぢぇ': 'dye', + 'ぢょ': 'jo', + 'つぁ': 'tsa', + 'つぃ': 'tsi', + 'つぇ': 'tse', + 'つぉ': 'tso', + 'てゃ': 'tha', + 'てぃ': 'thi', + 'てゅ': 'thu', + 'てぇ': 'the', + 'てょ': 'tho', + 'とぁ': 'twa', + 'とぃ': 'twi', + 'とぅ': 'twu', + 'とぇ': 'twe', + 'とぉ': 'two', + 'でゃ': 'dha', + 'でぃ': 'dhi', + 'でゅ': 'dhu', + 'でぇ': 'dhe', + 'でょ': 'dho', + 'どぁ': 'dwa', + 'どぃ': 'dwi', + 'どぅ': 'dwu', + 'どぇ': 'dwe', + 'どぉ': 'dwo', + 'にゃ': 'nya', + 'にゅ': 'nyu', + 'にょ': 'nyo', + 'にぇ': 'nye', + 'にぃ': 'nyi', + 'ひゃ': 'hya', + 'ひぃ': 'hyi', + 'ひゅ': 'hyu', + 'ひぇ': 'hye', + 'ひょ': 'hyo', + 'びゃ': 'bya', + 'びぃ': 'byi', + 'びゅ': 'byu', + 'びぇ': 'bye', + 'びょ': 'byo', + 'ぴゃ': 'pya', + 'ぴぃ': 'pyi', + 'ぴゅ': 'pyu', + 'ぴぇ': 'pye', + 'ぴょ': 'pyo', + 'ふぁ': 'fwa', + 'ふぃ': 'fyi', + 'ふぇ': 'fye', + 'ふぉ': 'fwo', + 'ふぅ': 'fwu', + 'ふゃ': 'fya', + 'ふゅ': 'fyu', + 'ふょ': 'fyo', + 'みゃ': 'mya', + 'みぃ': 'myi', + 'みゅ': 'myu', + 'みぇ': 'mye', + 'みょ': 'myo', + 'りゃ': 'rya', + 'りぃ': 'ryi', + 'りゅ': 'ryu', + 'りぇ': 'rye', + 'りょ': 'ryo', + 'ゔぁ': 'va', + 'ゔぃ': 'vyi', + 'ゔ': 'vu', + 'ゔぇ': 'vye', + 'ゔぉ': 'vo', + 'ゔゃ': 'vya', + 'ゔゅ': 'vyu', + 'ゔょ': 'vyo', + 'うぁ': 'wha', + 'いぇ': 'ye', + 'うぉ': 'who', + 'ぁ': 'xa', + 'ぃ': 'xi', + 'ぅ': 'xu', + 'ぇ': 'xe', + 'ぉ': 'xo', + 'ゕ': 'xka', + 'ゖ': 'xke', + 'ゎ': 'xwa' +}; + +const Map latin_to_hiragana = { + 'a': 'あ', + 'i': 'い', + 'u': 'う', + 'e': 'え', + 'o': 'お', + 'ka': 'か', + 'ki': 'き', + 'ku': 'く', + 'ke': 'け', + 'ko': 'こ', + 'ga': 'が', + 'gi': 'ぎ', + 'gu': 'ぐ', + 'ge': 'げ', + 'go': 'ご', + 'sa': 'さ', + 'si': 'し', + 'shi': 'し', + 'su': 'す', + 'se': 'せ', + 'so': 'そ', + 'za': 'ざ', + 'zi': 'じ', + 'ji': 'じ', + 'zu': 'ず', + 'ze': 'ぜ', + 'zo': 'ぞ', + 'ta': 'た', + 'ti': 'ち', + 'chi': 'ち', + 'tu': 'つ', + 'tsu': 'つ', + 'te': 'て', + 'to': 'と', + 'da': 'だ', + 'di': 'ぢ', + 'du': 'づ', + 'dzu': 'づ', + 'de': 'で', + 'do': 'ど', + 'na': 'な', + 'ni': 'に', + 'nu': 'ぬ', + 'ne': 'ね', + 'no': 'の', + 'ha': 'は', + 'hi': 'ひ', + 'hu': 'ふ', + 'fu': 'ふ', + 'he': 'へ', + 'ho': 'ほ', + 'ba': 'ば', + 'bi': 'び', + 'bu': 'ぶ', + 'be': 'べ', + 'bo': 'ぼ', + 'pa': 'ぱ', + 'pi': 'ぴ', + 'pu': 'ぷ', + 'pe': 'ぺ', + 'po': 'ぽ', + 'ma': 'ま', + 'mi': 'み', + 'mu': 'む', + 'me': 'め', + 'mo': 'も', + 'ya': 'や', + 'yu': 'ゆ', + 'yo': 'よ', + 'ra': 'ら', + 'ri': 'り', + 'ru': 'る', + 're': 'れ', + 'ro': 'ろ', + 'la': 'ら', + 'li': 'り', + 'lu': 'る', + 'le': 'れ', + 'lo': 'ろ', + 'wa': 'わ', + 'wi': 'うぃ', + 'we': 'うぇ', + 'wo': 'を', + 'wye': 'ゑ', + 'wyi': 'ゐ', + '-': 'ー', + 'n': 'ん', + 'nn': 'ん', + "n'": 'ん', + 'kya': 'きゃ', + 'kyu': 'きゅ', + 'kyo': 'きょ', + 'kye': 'きぇ', + 'kyi': 'きぃ', + 'gya': 'ぎゃ', + 'gyu': 'ぎゅ', + 'gyo': 'ぎょ', + 'gye': 'ぎぇ', + 'gyi': 'ぎぃ', + 'kwa': 'くぁ', + 'kwi': 'くぃ', + 'kwu': 'くぅ', + 'kwe': 'くぇ', + 'kwo': 'くぉ', + 'gwa': 'ぐぁ', + 'gwi': 'ぐぃ', + 'gwu': 'ぐぅ', + 'gwe': 'ぐぇ', + 'gwo': 'ぐぉ', + 'qwa': 'ぐぁ', + 'qwi': 'ぐぃ', + 'qwu': 'ぐぅ', + 'qwe': 'ぐぇ', + 'qwo': 'ぐぉ', + 'sya': 'しゃ', + 'syi': 'しぃ', + 'syu': 'しゅ', + 'sye': 'しぇ', + 'syo': 'しょ', + 'sha': 'しゃ', + 'shu': 'しゅ', + 'she': 'しぇ', + 'sho': 'しょ', + 'ja': 'じゃ', + 'ju': 'じゅ', + 'je': 'じぇ', + 'jo': 'じょ', + 'jya': 'じゃ', + 'jyi': 'じぃ', + 'jyu': 'じゅ', + 'jye': 'じぇ', + 'jyo': 'じょ', + 'zya': 'じゃ', + 'zyu': 'じゅ', + 'zyo': 'じょ', + 'zye': 'じぇ', + 'zyi': 'じぃ', + 'swa': 'すぁ', + 'swi': 'すぃ', + 'swu': 'すぅ', + 'swe': 'すぇ', + 'swo': 'すぉ', + 'cha': 'ちゃ', + 'chu': 'ちゅ', + 'che': 'ちぇ', + 'cho': 'ちょ', + 'cya': 'ちゃ', + 'cyi': 'ちぃ', + 'cyu': 'ちゅ', + 'cye': 'ちぇ', + 'cyo': 'ちょ', + 'tya': 'ちゃ', + 'tyi': 'ちぃ', + 'tyu': 'ちゅ', + 'tye': 'ちぇ', + 'tyo': 'ちょ', + 'dya': 'ぢゃ', + 'dyi': 'ぢぃ', + 'dyu': 'ぢゅ', + 'dye': 'ぢぇ', + 'dyo': 'ぢょ', + 'tsa': 'つぁ', + 'tsi': 'つぃ', + 'tse': 'つぇ', + 'tso': 'つぉ', + 'tha': 'てゃ', + 'thi': 'てぃ', + 'thu': 'てゅ', + 'the': 'てぇ', + 'tho': 'てょ', + 'twa': 'とぁ', + 'twi': 'とぃ', + 'twu': 'とぅ', + 'twe': 'とぇ', + 'two': 'とぉ', + 'dha': 'でゃ', + 'dhi': 'でぃ', + 'dhu': 'でゅ', + 'dhe': 'でぇ', + 'dho': 'でょ', + 'dwa': 'どぁ', + 'dwi': 'どぃ', + 'dwu': 'どぅ', + 'dwe': 'どぇ', + 'dwo': 'どぉ', + 'nya': 'にゃ', + 'nyu': 'にゅ', + 'nyo': 'にょ', + 'nye': 'にぇ', + 'nyi': 'にぃ', + 'hya': 'ひゃ', + 'hyi': 'ひぃ', + 'hyu': 'ひゅ', + 'hye': 'ひぇ', + 'hyo': 'ひょ', + 'bya': 'びゃ', + 'byi': 'びぃ', + 'byu': 'びゅ', + 'bye': 'びぇ', + 'byo': 'びょ', + 'pya': 'ぴゃ', + 'pyi': 'ぴぃ', + 'pyu': 'ぴゅ', + 'pye': 'ぴぇ', + 'pyo': 'ぴょ', + 'fa': 'ふぁ', + 'fi': 'ふぃ', + 'fe': 'ふぇ', + 'fo': 'ふぉ', + 'fwa': 'ふぁ', + 'fwi': 'ふぃ', + 'fwu': 'ふぅ', + 'fwe': 'ふぇ', + 'fwo': 'ふぉ', + 'fya': 'ふゃ', + 'fyi': 'ふぃ', + 'fyu': 'ふゅ', + 'fye': 'ふぇ', + 'fyo': 'ふょ', + 'mya': 'みゃ', + 'myi': 'みぃ', + 'myu': 'みゅ', + 'mye': 'みぇ', + 'myo': 'みょ', + 'rya': 'りゃ', + 'ryi': 'りぃ', + 'ryu': 'りゅ', + 'rye': 'りぇ', + 'ryo': 'りょ', + 'lya': 'りゃ', + 'lyu': 'りゅ', + 'lyo': 'りょ', + 'lye': 'りぇ', + 'lyi': 'りぃ', + 'va': 'ゔぁ', + 'vi': 'ゔぃ', + 'vu': 'ゔ', + 've': 'ゔぇ', + 'vo': 'ゔぉ', + 'vya': 'ゔゃ', + 'vyi': 'ゔぃ', + 'vyu': 'ゔゅ', + 'vye': 'ゔぇ', + 'vyo': 'ゔょ', + 'wha': 'うぁ', + 'whi': 'うぃ', + 'ye': 'いぇ', + 'whe': 'うぇ', + 'who': 'うぉ', + 'xa': 'ぁ', + 'xi': 'ぃ', + 'xu': 'ぅ', + 'xe': 'ぇ', + 'xo': 'ぉ', + 'xya': 'ゃ', + 'xyu': 'ゅ', + 'xyo': 'ょ', + 'xtu': 'っ', + 'xtsu': 'っ', + 'xka': 'ゕ', + 'xke': 'ゖ', + 'xwa': 'ゎ', + '@@': ' ', + '#[': '「', + '#]': '」', + '#,': '、', + '#.': '。', + '#/': '・', +}; + +bool _smallTsu(String for_conversion) => for_conversion == hiragana_small_tsu; +bool _nFollowedByYuYeYo(String for_conversion, String kana) => + for_conversion == hiragana_syllabic_n && + kana.length > 1 && + 'やゆよ'.contains(kana.substring(1, 2)); + +String transliterateHiraganaToLatin(String hiragana) { + String kana = hiragana; + String romaji = ''; + bool geminate = false; + + while (kana.isNotEmpty) { + final lengths = [if (kana.length > 1) 2, 1]; + for (final length in lengths) { + final String for_conversion = kana.substring(0, length); + String? mora; + + if (_smallTsu(for_conversion)) { + geminate = true; + kana = kana.replaceRange(0, length, ''); + break; + } else if (_nFollowedByYuYeYo(for_conversion, kana)) { + mora = "n'"; + } + mora ??= hiragana_to_latin[for_conversion]; + + if (mora != null) { + if (geminate) { + geminate = false; + romaji += mora.substring(0, 1); + } + romaji += mora; + kana = kana.replaceRange(0, length, ''); + break; + } else if (length == 1) { + romaji += for_conversion; + kana = kana.replaceRange(0, length, ''); + } + } + } + return romaji; +} + +bool _doubleNFollowedByAIUEO(String for_conversion) => + RegExp(r'^nn[aiueo]$').hasMatch(for_conversion); +bool _hasTableMatch(String for_conversion) => + latin_to_hiragana[for_conversion] != null; +bool _hasDoubleConsonant(String for_conversion, int length) => + for_conversion == 'tch' || + (length == 2 && + RegExp(r'^([kgsztdnbpmyrlwchf])\1$').hasMatch(for_conversion)); + +String transliterateLatinToHiragana(String latin) { + String romaji = + latin.toLowerCase().replaceAll('mb', 'nb').replaceAll('mp', 'np'); + String kana = ''; + + while (romaji.isNotEmpty) { + final lengths = [ + if (romaji.length > 2) 3, + if (romaji.length > 1) 2, + 1, + ]; + + for (final length in lengths) { + String? mora; + int for_removal = length; + final String for_conversion = romaji.substring(0, length); + + if (_doubleNFollowedByAIUEO(for_conversion)) { + mora = hiragana_syllabic_n; + for_removal = 1; + } else if (_hasTableMatch(for_conversion)) { + mora = latin_to_hiragana[for_conversion]; + } else if (_hasDoubleConsonant(for_conversion, length)) { + mora = hiragana_small_tsu; + for_removal = 1; + } + + if (mora != null) { + kana += mora; + romaji = romaji.replaceRange(0, for_removal, ''); + break; + } else if (length == 1) { + kana += for_conversion; + romaji = romaji.replaceRange(0, 1, ''); + } + } + } + + return kana; +} + +String _transposeCodepointsInRange( + String text, + int distance, + int rangeStart, + int rangeEnd, +) => + String.fromCharCodes( + text.codeUnits + .map((c) => c + ((rangeStart <= c && c <= rangeEnd) ? distance : 0)), + ); + +String transliterateKanaToLatin(String kana) => + transliterateHiraganaToLatin(transliterateKatakanaToHiragana(kana)); + +String transliterateLatinToKatakana(String latin) => + transliterateHiraganaToKatakana(transliterateLatinToHiragana(latin)); + +String transliterateKatakanaToHiragana(String katakana) => + _transposeCodepointsInRange(katakana, -96, 12449, 12534); + +String transliterateHiraganaToKatakana(String hiragana) => + _transposeCodepointsInRange(hiragana, 96, 12353, 12438); + +String transliterateFullwidthRomajiToHalfwidth(String halfwidth) => + _transposeCodepointsInRange( + _transposeCodepointsInRange( + halfwidth, + -65248, + 65281, + 65374, + ), + -12256, + 12288, + 12288, + ); + +String transliterateHalfwidthRomajiToFullwidth(String halfwidth) => + _transposeCodepointsInRange( + _transposeCodepointsInRange( + halfwidth, + 65248, + 33, + 126, + ), + 12256, + 32, + 32, + );