#include "romaji_converter.h" #include #include #include #include #include namespace { const std::unordered_map &RomajiTable() { static const std::unordered_map table = { {"a", "あ"}, {"i", "い"}, {"u", "う"}, {"e", "え"}, {"o", "お"}, {"ka", "か"}, {"ki", "き"}, {"ku", "く"}, {"ke", "け"}, {"ko", "こ"}, {"ga", "が"}, {"gi", "ぎ"}, {"gu", "ぐ"}, {"ge", "げ"}, {"go", "ご"}, {"sa", "さ"}, {"shi", "し"}, {"si", "し"}, {"su", "す"}, {"se", "せ"}, {"so", "そ"}, {"za", "ざ"}, {"ji", "じ"}, {"zi", "じ"}, {"zu", "ず"}, {"ze", "ぜ"}, {"zo", "ぞ"}, {"ta", "た"}, {"chi", "ち"}, {"ti", "ち"}, {"tsu", "つ"}, {"tu", "つ"}, {"te", "て"}, {"to", "と"}, {"da", "だ"}, {"di", "ぢ"}, {"du", "づ"}, {"de", "で"}, {"do", "ど"}, {"na", "な"}, {"ni", "に"}, {"nu", "ぬ"}, {"ne", "ね"}, {"no", "の"}, {"ha", "は"}, {"hi", "ひ"}, {"fu", "ふ"}, {"hu", "ふ"}, {"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", "わ"}, {"wo", "を"}, {"nn", "ん"}, {"kya", "きゃ"}, {"kyu", "きゅ"}, {"kyo", "きょ"}, {"gya", "ぎゃ"}, {"gyu", "ぎゅ"}, {"gyo", "ぎょ"}, {"sha", "しゃ"}, {"shu", "しゅ"}, {"sho", "しょ"}, {"sya", "しゃ"}, {"syu", "しゅ"}, {"syo", "しょ"}, {"ja", "じゃ"}, {"ju", "じゅ"}, {"jo", "じょ"}, {"jya", "じゃ"}, {"jyu", "じゅ"}, {"jyo", "じょ"}, {"cha", "ちゃ"}, {"chu", "ちゅ"}, {"cho", "ちょ"}, {"cya", "ちゃ"}, {"cyu", "ちゅ"}, {"cyo", "ちょ"}, {"tya", "ちゃ"}, {"tyu", "ちゅ"}, {"tyo", "ちょ"}, {"nya", "にゃ"}, {"nyu", "にゅ"}, {"nyo", "にょ"}, {"hya", "ひゃ"}, {"hyu", "ひゅ"}, {"hyo", "ひょ"}, {"bya", "びゃ"}, {"byu", "びゅ"}, {"byo", "びょ"}, {"pya", "ぴゃ"}, {"pyu", "ぴゅ"}, {"pyo", "ぴょ"}, {"mya", "みゃ"}, {"myu", "みゅ"}, {"myo", "みょ"}, {"rya", "りゃ"}, {"ryu", "りゅ"}, {"ryo", "りょ"}, {"fa", "ふぁ"}, {"fi", "ふぃ"}, {"fe", "ふぇ"}, {"fo", "ふぉ"}, {"va", "ゔぁ"}, {"vi", "ゔぃ"}, {"vu", "ゔ"}, {"ve", "ゔぇ"}, {"vo", "ゔぉ"}, {"wi", "うぃ"}, {"we", "うぇ"}, {"she", "しぇ"}, {"je", "じぇ"}, {"che", "ちぇ"}, {"tsa", "つぁ"}, {"tsi", "つぃ"}, {"tse", "つぇ"}, {"tso", "つぉ"}, {"thi", "てぃ"}, {"dhi", "でぃ"}, {"twu", "とぅ"}, {"dwu", "どぅ"}, {"kwa", "くぁ"}, {"gwa", "ぐぁ"}, {"xa", "ぁ"}, {"xi", "ぃ"}, {"xu", "ぅ"}, {"xe", "ぇ"}, {"xo", "ぉ"}, {"la", "ぁ"}, {"li", "ぃ"}, {"lu", "ぅ"}, {"le", "ぇ"}, {"lo", "ぉ"}, {"xya", "ゃ"}, {"xyu", "ゅ"}, {"xyo", "ょ"}, {"lya", "ゃ"}, {"lyu", "ゅ"}, {"lyo", "ょ"}, {"xtsu", "っ"}, {"ltsu", "っ"}, {"xwa", "ゎ"}, {"-", "ー"}, }; return table; } bool IsConsonant(char ch) { return ch >= 'a' && ch <= 'z' && ch != 'a' && ch != 'i' && ch != 'u' && ch != 'e' && ch != 'o'; } } // namespace namespace japanese { RomajiConversion ConvertRomaji(std::string_view input) { std::string normalized(input); std::transform(normalized.begin(), normalized.end(), normalized.begin(), [](unsigned char ch) { return static_cast(std::tolower(ch)); }); RomajiConversion result; const auto &table = RomajiTable(); size_t index = 0; const auto append = [&](const std::string &kana, size_t input_length) { result.hiragana += kana; index += input_length; result.segments.push_back({index, result.hiragana.size()}); }; while (index < normalized.size()) { if (normalized[index] == 'n') { if (index + 1 == normalized.size()) { append("ん", 1); continue; } const char next = normalized[index + 1]; // Both spellings complete one nasal; the next key starts a new syllable. if (next == '\'' || next == 'n') { append("ん", 2); continue; } // 长音符 '-' 不是元音,和辅音一样让前面的 n 收成 ん。 if (next == '-' || (IsConsonant(next) && next != 'y')) { append("ん", 1); continue; } } if (index + 1 < normalized.size() && normalized[index] == normalized[index + 1] && IsConsonant(normalized[index]) && normalized[index] != 'n') { append("っ", 1); continue; } bool matched = false; const size_t remaining = normalized.size() - index; for (size_t length = (std::min)(size_t{4}, remaining); length > 0; --length) { const auto found = table.find(normalized.substr(index, length)); if (found == table.end()) { continue; } append(found->second, length); matched = true; break; } if (!matched) { result.pending = normalized.substr(index); break; } } result.complete = !result.hiragana.empty() && result.pending.empty(); return result; } std::optional RomajiConversion::InputLengthForHiragana(size_t length) const { for (const auto &segment : segments) if (segment.hiragana_end == length) return segment.input_end; return std::nullopt; } std::string HiraganaToKatakana(std::string_view hiragana) { std::u32string codepoints = utf8::utf8to32(hiragana); for (char32_t &codepoint : codepoints) { if (codepoint >= U'ぁ' && codepoint <= U'ゖ') { codepoint += 0x60; } } return utf8::utf32to8(codepoints); } bool IsSingleKanaConversion(const RomajiConversion &conversion) { if (!conversion.complete || conversion.hiragana.empty()) return false; const auto codepoints = utf8::utf8to32(conversion.hiragana); return codepoints.size() == 1 && codepoints.front() >= U'ぁ' && codepoints.front() <= U'ゖ'; } const std::vector> &KanaToRomajiTable() { static const std::vector> table = [] { std::vector> entries; entries.reserve(RomajiTable().size()); for (const auto &entry : RomajiTable()) entries.emplace_back(entry.second, entry.first); std::sort(entries.begin(), entries.end(), [](const auto &a, const auto &b) { if (a.first.size() != b.first.size()) return a.first.size() > b.first.size(); return a.second.size() > b.second.size(); }); std::vector> unique; for (const auto &entry : entries) { if (std::none_of(unique.begin(), unique.end(), [&](const auto &seen) { return seen.first == entry.first; })) unique.push_back(entry); } return unique; }(); return table; } std::string HiraganaToRomaji(std::string_view kana) { std::u32string codepoints = utf8::utf8to32(kana); for (char32_t &codepoint : codepoints) { if (codepoint >= U'ァ' && codepoint <= U'ヶ') codepoint -= 0x60; } const std::string hiragana = utf8::utf32to8(codepoints); std::string romaji; const auto &table = KanaToRomajiTable(); size_t index = 0; while (index < hiragana.size()) { if (hiragana.compare(index, 3, "っ") == 0 || hiragana.compare(index, 3, "ッ") == 0) { index += 3; std::string next; for (const auto &entry : table) { if (index + entry.first.size() <= hiragana.size() && hiragana.compare(index, entry.first.size(), entry.first) == 0) { next = entry.second; break; } } if (!next.empty() && IsConsonant(next.front())) romaji.push_back(next.front()); else romaji += "xtsu"; continue; } if (hiragana.compare(index, 3, "ん") == 0) { romaji.push_back('n'); index += 3; continue; } bool matched = false; for (const auto &entry : table) { if (index + entry.first.size() <= hiragana.size() && hiragana.compare(index, entry.first.size(), entry.first) == 0) { romaji += entry.second; index += entry.first.size(); matched = true; break; } } if (!matched) { const unsigned char lead = static_cast(hiragana[index]); index += lead < 0x80 ? 1 : (lead >> 5) == 0x6 ? 2 : (lead >> 4) == 0xE ? 3 : 4; } } return romaji; } std::vector KanaForRomajiPrefix(std::string_view pending) { if (pending.empty()) return {}; std::string prefix(pending); std::transform(prefix.begin(), prefix.end(), prefix.begin(), [](unsigned char ch) { return static_cast(std::tolower(ch)); }); std::vector kana; for (const auto &entry : RomajiTable()) { if (entry.first.size() >= prefix.size() && entry.first.compare(0, prefix.size(), prefix) == 0) { kana.push_back(entry.second); } } std::sort(kana.begin(), kana.end()); kana.erase(std::unique(kana.begin(), kana.end()), kana.end()); return kana; } } // namespace japanese