Merge branch 'transliteration' into develop

This commit is contained in:
Sakamoto Shiina
2025-09-18 10:21:46 +09:00
19 changed files with 775 additions and 85 deletions

View File

@@ -5,7 +5,7 @@ a = Analysis(
['src-python\\mainloop.py'], ['src-python\\mainloop.py'],
pathex=[], pathex=[],
binaries=[], binaries=[],
datas=[('./fonts', 'fonts/'), ('.venv/Lib/site-packages/zeroconf', 'zeroconf/'), ('.venv/Lib/site-packages/openvr', 'openvr/'), ('.venv/Lib/site-packages/pykakasi', 'pykakasi/'), ('.venv/Lib/site-packages/faster_whisper', 'faster_whisper/'), ('.venv/Lib/site-packages/hf_xet', 'hf_xet/')], datas=[('./fonts', 'fonts/'), ('.venv/Lib/site-packages/zeroconf', 'zeroconf/'), ('.venv/Lib/site-packages/openvr', 'openvr/'), ('.venv/Lib/site-packages/faster_whisper', 'faster_whisper/'), ('.venv/Lib/site-packages/hf_xet', 'hf_xet/')],
hiddenimports=[], hiddenimports=[],
hookspath=[], hookspath=[],
hooksconfig={}, hooksconfig={},

View File

@@ -5,7 +5,7 @@ a = Analysis(
['src-python\\mainloop.py'], ['src-python\\mainloop.py'],
pathex=[], pathex=[],
binaries=[], binaries=[],
datas=[('./fonts', 'fonts/'), ('.venv_cuda/Lib/site-packages/zeroconf', 'zeroconf/'), ('.venv_cuda/Lib/site-packages/openvr', 'openvr/'), ('.venv_cuda/Lib/site-packages/pykakasi', 'pykakasi/'), ('.venv_cuda/Lib/site-packages/faster_whisper', 'faster_whisper/'), ('.venv/Lib/site-packages/hf_xet', 'hf_xet/')], datas=[('./fonts', 'fonts/'), ('.venv_cuda/Lib/site-packages/zeroconf', 'zeroconf/'), ('.venv_cuda/Lib/site-packages/openvr', 'openvr/'), ('.venv_cuda/Lib/site-packages/faster_whisper', 'faster_whisper/'), ('.venv/Lib/site-packages/hf_xet', 'hf_xet/')],
hiddenimports=[], hiddenimports=[],
hookspath=[], hookspath=[],
hooksconfig={}, hooksconfig={},

View File

@@ -250,6 +250,13 @@ config_page:
received_message_format: received_message_format:
label: "Message Format (Speaker2Chatbox)" label: "Message Format (Speaker2Chatbox)"
desc: "Currently, it is used in Speaker2Chatbox." desc: "Currently, it is used in Speaker2Chatbox."
convert_message_to_romaji:
label: Show Romaji
desc: Supported only when Japanese is selected as the translation language. When enabled along with '{{convert_message_to_hiragana}}', romaji will be shown on mouse hover.
convert_message_to_hiragana:
label: Show Hiragana
desc: Supported only when Japanese is selected as the translation language.
hotkeys: hotkeys:
toggle_vrct_visibility: toggle_vrct_visibility:

View File

@@ -250,6 +250,12 @@ config_page:
received_message_format: received_message_format:
label: メッセージフォーマットSpeaker2Chatbox label: メッセージフォーマットSpeaker2Chatbox
desc: 今のところ、Speaker2Chatboxで送信した時の表示に使われます。 desc: 今のところ、Speaker2Chatboxで送信した時の表示に使われます。
convert_message_to_romaji:
label: ローマ字を表示
desc: 翻訳言語として日本語を選択した時のみサポート。「{{convert_message_to_hiragana}}」と同時に有効にした場合は、マウスホバーで表示されます。
convert_message_to_hiragana:
label: ひらがなを表示
desc: 翻訳言語として日本語を選択した時のみサポート。
hotkeys: hotkeys:
toggle_vrct_visibility: toggle_vrct_visibility:

View File

@@ -13,12 +13,14 @@ sentencepiece==0.2.0
openvr==1.26.701 openvr==1.26.701
pydub==0.25.1 pydub==0.25.1
psutil==5.9.8 psutil==5.9.8
pykakasi==2.3.0
pycaw==20240210 pycaw==20240210
websockets==15.0.1 websockets==15.0.1
huggingface_hub==0.32.2 huggingface_hub==0.32.2
hf-xet==1.1.2 hf-xet==1.1.2
setuptools==80.8.0 setuptools==80.8.0
SudachiPy==0.6.10
SudachiDict-core==20250825
SudachiDict-full==20250825
translators @ git+https://github.com/misyaguziya/translators@5.9.2.1 translators @ git+https://github.com/misyaguziya/translators@5.9.2.1
SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4.1 SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4.1
tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.3 tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.3

View File

@@ -14,12 +14,14 @@ sentencepiece==0.2.0
openvr==1.26.701 openvr==1.26.701
pydub==0.25.1 pydub==0.25.1
psutil==5.9.8 psutil==5.9.8
pykakasi==2.3.0
pycaw==20240210 pycaw==20240210
websockets==15.0.1 websockets==15.0.1
huggingface_hub==0.32.2 huggingface_hub==0.32.2
hf-xet==1.1.2 hf-xet==1.1.2
setuptools==80.8.0 setuptools==80.8.0
SudachiPy==0.6.10
SudachiDict-core==20250825
SudachiDict-full==20250825
translators @ git+https://github.com/misyaguziya/translators@5.9.2.1 translators @ git+https://github.com/misyaguziya/translators@5.9.2.1
SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4.1 SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4.1
tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.3 tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.3

View File

@@ -107,6 +107,10 @@ class Config:
def SELECTABLE_TAB_NO_LIST(self): def SELECTABLE_TAB_NO_LIST(self):
return self._SELECTABLE_TAB_NO_LIST return self._SELECTABLE_TAB_NO_LIST
@property
def SELECTED_TAB_TARGET_LANGUAGES_NO_LIST(self):
return self._SELECTED_TAB_TARGET_LANGUAGES_NO_LIST
@property @property
def SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_LIST(self): def SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_LIST(self):
return self._SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_LIST return self._SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_LIST
@@ -1115,23 +1119,16 @@ class Config:
}, },
} }
self._SELECTED_TARGET_LANGUAGES = {} self._SELECTED_TARGET_LANGUAGES = {}
self._SELECTED_TAB_TARGET_LANGUAGES_NO_LIST = ["1", "2", "3"]
for tab_no in self.SELECTABLE_TAB_NO_LIST: for tab_no in self.SELECTABLE_TAB_NO_LIST:
self._SELECTED_TARGET_LANGUAGES[tab_no] = { for tab_target_lang_no in self.SELECTED_TAB_TARGET_LANGUAGES_NO_LIST:
"1": { if tab_no not in self._SELECTED_TARGET_LANGUAGES:
self.SELECTED_TARGET_LANGUAGES[tab_no] = {}
if tab_target_lang_no not in self._SELECTED_TARGET_LANGUAGES[tab_no]:
self.SELECTED_TARGET_LANGUAGES[tab_no][tab_target_lang_no] = {
"language": "English", "language": "English",
"country": "United States", "country": "United States",
"enable": True, "enable": True,
},
"2": {
"language": "English",
"country": "United States",
"enable": False,
},
"3": {
"language": "English",
"country": "United States",
"enable": False,
},
} }
self._SELECTED_TRANSCRIPTION_ENGINE = "Google" self._SELECTED_TRANSCRIPTION_ENGINE = "Google"
self._CONVERT_MESSAGE_TO_ROMAJI = False self._CONVERT_MESSAGE_TO_ROMAJI = False

View File

@@ -246,7 +246,8 @@ class Controller:
elif isinstance(message, str) and len(message) > 0: elif isinstance(message, str) and len(message) > 0:
translation = [] translation = []
transliteration = [] transliteration_message = []
transliteration_translation = []
if model.checkKeywords(message): if model.checkKeywords(message):
self.run( self.run(
200, 200,
@@ -298,9 +299,30 @@ class Controller:
# その他のエラーは通常通り処理 # その他のエラーは通常通り処理
raise raise
if config.CONVERT_MESSAGE_TO_ROMAJI is True or config.CONVERT_MESSAGE_TO_HIRAGANA is True: if config.CONVERT_MESSAGE_TO_HIRAGANA is True or config.CONVERT_MESSAGE_TO_ROMAJI is True:
if config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"] == "Japanese": if config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"] == "Japanese":
transliteration = model.convertMessageToTransliteration(translation[0]) transliteration_message = model.convertMessageToTransliteration(
message,
hiragana=config.CONVERT_MESSAGE_TO_HIRAGANA,
romaji=config.CONVERT_MESSAGE_TO_ROMAJI
)
for i, no in enumerate(config.SELECTED_TAB_TARGET_LANGUAGES_NO_LIST):
if (config.ENABLE_TRANSLATION is True and
config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO][no]["language"] == "Japanese" and
config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO][no]["enable"] is True
):
transliteration_translation.append(
model.convertMessageToTransliteration(
translation[i],
hiragana=config.CONVERT_MESSAGE_TO_HIRAGANA,
romaji=config.CONVERT_MESSAGE_TO_ROMAJI
)
)
else:
transliteration_translation.append([])
else:
transliteration_translation = [[] for _ in config.SELECTED_TAB_TARGET_LANGUAGES_NO_LIST]
if config.ENABLE_TRANSCRIPTION_SEND is True: if config.ENABLE_TRANSCRIPTION_SEND is True:
if config.SEND_MESSAGE_TO_VRC is True: if config.SEND_MESSAGE_TO_VRC is True:
@@ -317,9 +339,16 @@ class Controller:
200, 200,
self.run_mapping["transcription_mic"], self.run_mapping["transcription_mic"],
{ {
"original": {
"message": message, "message": message,
"translation":translation, "transliteration": transliteration_message
},
"translations": [
{
"message": translation_message,
"transliteration": transliteration "transliteration": transliteration
} for translation_message, transliteration in zip(translation, transliteration_translation)
]
}) })
if config.OVERLAY_LARGE_LOG is True and model.overlay.initialized is True: if config.OVERLAY_LARGE_LOG is True and model.overlay.initialized is True:
@@ -351,7 +380,7 @@ class Controller:
"dst_languages":config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO], "dst_languages":config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO],
"message":message, "message":message,
"translation":translation, "translation":translation,
"transliteration":transliteration "transliteration":transliteration_translation
} }
) )
@@ -373,7 +402,8 @@ class Controller:
) )
elif isinstance(message, str) and len(message) > 0: elif isinstance(message, str) and len(message) > 0:
translation = [] translation = []
transliteration = [] transliteration_message = []
transliteration_translation = []
if model.checkKeywords(message): if model.checkKeywords(message):
self.run( self.run(
200, 200,
@@ -425,9 +455,28 @@ class Controller:
# その他のエラーは通常通り処理 # その他のエラーは通常通り処理
raise raise
if config.CONVERT_MESSAGE_TO_ROMAJI is True or config.CONVERT_MESSAGE_TO_HIRAGANA is True: if config.CONVERT_MESSAGE_TO_HIRAGANA is True or config.CONVERT_MESSAGE_TO_ROMAJI is True:
if config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"] == "Japanese": if language == "Japanese":
transliteration = model.convertMessageToTransliteration(message) transliteration_message = model.convertMessageToTransliteration(
message,
hiragana=config.CONVERT_MESSAGE_TO_HIRAGANA,
romaji=config.CONVERT_MESSAGE_TO_ROMAJI
)
if (config.ENABLE_TRANSLATION is True and
config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"] == "Japanese"
):
transliteration_translation.append(
model.convertMessageToTransliteration(
translation[0],
hiragana=config.CONVERT_MESSAGE_TO_HIRAGANA,
romaji=config.CONVERT_MESSAGE_TO_ROMAJI
)
)
else:
transliteration_translation.append([])
else:
transliteration_translation = [[]]
if config.ENABLE_TRANSCRIPTION_RECEIVE is True: if config.ENABLE_TRANSCRIPTION_RECEIVE is True:
if config.OVERLAY_SMALL_LOG is True and model.overlay.initialized is True: if config.OVERLAY_SMALL_LOG is True and model.overlay.initialized is True:
@@ -484,9 +533,16 @@ class Controller:
200, 200,
self.run_mapping["transcription_speaker"], self.run_mapping["transcription_speaker"],
{ {
"original": {
"message": message, "message": message,
"translation":translation, "transliteration": transliteration_message
"transliteration":transliteration, },
"translations": [
{
"message": translation_message,
"transliteration": transliteration
} for translation_message, transliteration in zip(translation, transliteration_translation)
]
}) })
if model.checkWebSocketServerAlive() is True: if model.checkWebSocketServerAlive() is True:
@@ -497,7 +553,7 @@ class Controller:
"dst_languages":config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO], "dst_languages":config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO],
"message":message, "message":message,
"translation":translation, "translation":translation,
"transliteration":transliteration "transliteration":transliteration_translation
} }
) )
@@ -510,7 +566,8 @@ class Controller:
message = data["message"] message = data["message"]
if len(message) > 0: if len(message) > 0:
translation = [] translation = []
transliteration = [] transliteration_message = []
transliteration_translation = []
if config.ENABLE_TRANSLATION is False: if config.ENABLE_TRANSLATION is False:
pass pass
else: else:
@@ -562,18 +619,45 @@ class Controller:
"result": "result":
{ {
"id":id, "id":id,
"original": {
"message":message, "message":message,
"translation":[], "transliteration":[]
"transliteration":[], },
"translations": [
{
"message": "",
"transliteration": []
} for _ in config.SELECTED_TAB_TARGET_LANGUAGES_NO_LIST
]
}, },
} }
else: else:
# その他のエラーは通常通り処理 # その他のエラーは通常通り処理
raise raise
if config.CONVERT_MESSAGE_TO_ROMAJI is True or config.CONVERT_MESSAGE_TO_HIRAGANA is True: if config.CONVERT_MESSAGE_TO_HIRAGANA is True or config.CONVERT_MESSAGE_TO_ROMAJI is True:
if config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"] == "Japanese": if config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["1"]["language"] == "Japanese":
transliteration = model.convertMessageToTransliteration(translation[0]) transliteration_message = model.convertMessageToTransliteration(
message,
hiragana=config.CONVERT_MESSAGE_TO_HIRAGANA,
romaji=config.CONVERT_MESSAGE_TO_ROMAJI
)
for i, no in enumerate(config.SELECTED_TAB_TARGET_LANGUAGES_NO_LIST):
if (config.ENABLE_TRANSLATION is True and
config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO][no]["language"] == "Japanese" and
config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO][no]["enable"] is True
):
transliteration_translation.append(
model.convertMessageToTransliteration(
translation[i],
hiragana=config.CONVERT_MESSAGE_TO_HIRAGANA,
romaji=config.CONVERT_MESSAGE_TO_ROMAJI
)
)
else:
transliteration_translation.append([])
else:
transliteration_translation = [[] for _ in config.SELECTED_TAB_TARGET_LANGUAGES_NO_LIST]
# send OSC message # send OSC message
if config.SEND_MESSAGE_TO_VRC is True: if config.SEND_MESSAGE_TO_VRC is True:
@@ -615,7 +699,7 @@ class Controller:
"dst_languages":config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO], "dst_languages":config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO],
"message":message, "message":message,
"translation":translation, "translation":translation,
"transliteration":transliteration "transliteration":transliteration_translation
} }
) )
@@ -623,14 +707,21 @@ class Controller:
translation_text = f" ({'/'.join(translation)})" if translation else "" translation_text = f" ({'/'.join(translation)})" if translation else ""
model.logger.info(f"[CHAT] {message}{translation_text}") model.logger.info(f"[CHAT] {message}{translation_text}")
return {"status":200, return {
"status":200,
"result":{ "result":{
"id":id, "id":id,
"original": {
"message":message, "message":message,
"translation":translation, "transliteration":transliteration_message
"transliteration":transliteration,
}, },
} "translations": [
{
"message": translation_message,
"transliteration": transliteration
} for translation_message, transliteration in zip(translation, transliteration_translation)
]
}}
@staticmethod @staticmethod
def getVersion(*args, **kwargs) -> dict: def getVersion(*args, **kwargs) -> dict:

View File

@@ -14,7 +14,6 @@ from typing import Callable
from packaging.version import parse from packaging.version import parse
from flashtext import KeywordProcessor from flashtext import KeywordProcessor
from pykakasi import kakasi
from device_manager import device_manager from device_manager import device_manager
from config import config from config import config
@@ -28,6 +27,7 @@ from models.translation.translation_languages import translation_lang
from models.transcription.transcription_languages import transcription_lang from models.transcription.transcription_languages import transcription_lang
from models.translation.translation_utils import checkCTranslate2Weight, downloadCTranslate2Weight, downloadCTranslate2Tokenizer from models.translation.translation_utils import checkCTranslate2Weight, downloadCTranslate2Weight, downloadCTranslate2Tokenizer
from models.transcription.transcription_whisper import checkWhisperWeight, downloadWhisperWeight from models.transcription.transcription_whisper import checkWhisperWeight, downloadWhisperWeight
from models.transliteration.transliteration_transliterator import Transliterator
from models.overlay.overlay import Overlay from models.overlay.overlay import Overlay
from models.overlay.overlay_image import OverlayImage from models.overlay.overlay_image import OverlayImage
from models.watchdog.watchdog import Watchdog from models.watchdog.watchdog import Watchdog
@@ -99,7 +99,7 @@ class Model:
self.overlay_image = OverlayImage(config.PATH_LOCAL) self.overlay_image = OverlayImage(config.PATH_LOCAL)
self.mic_audio_queue = None self.mic_audio_queue = None
self.mic_mute_status = None self.mic_mute_status = None
self.kks = kakasi() self.transliterator = Transliterator()
self.watchdog = Watchdog(config.WATCHDOG_TIMEOUT, config.WATCHDOG_INTERVAL) self.watchdog = Watchdog(config.WATCHDOG_TIMEOUT, config.WATCHDOG_INTERVAL)
self.osc_handler = OSCHandler(config.OSC_IP_ADDRESS, config.OSC_PORT) self.osc_handler = OSCHandler(config.OSC_IP_ADDRESS, config.OSC_PORT)
self.websocket_server = None self.websocket_server = None
@@ -275,13 +275,21 @@ class Model:
self.previous_receive_message = message self.previous_receive_message = message
return repeat_flag return repeat_flag
def convertMessageToTransliteration(self, message: str) -> str: def convertMessageToTransliteration(self, message: str, hiragana: bool=True, romaji: bool=True) -> str:
data_list = self.kks.convert(message) if hiragana is False and romaji is False:
keys_to_keep = {"orig", "hira", "hepburn"} return message
filtered_list = []
for item in data_list: keys_to_keep = {"orig"}
filtered_item = {key: value for key, value in item.items() if key in keys_to_keep} if hiragana:
filtered_list.append(filtered_item) keys_to_keep.add("hira")
if romaji:
keys_to_keep.add("hepburn")
data_list = self.transliterator.analyze(message, use_macron=False)
filtered_list = [
{key: value for key, value in item.items() if key in keys_to_keep}
for item in data_list
]
return filtered_list return filtered_list
def setOscIpAddress(self, ip_address): def setOscIpAddress(self, ip_address):

View File

@@ -0,0 +1,215 @@
# katakana_to_hepburn.py
# カタカナ -> ヘボン式ローマ字(パッケージ不要)
def katakana_to_hepburn(kata: str, use_macron: bool = True) -> str:
"""
カタカナ文字列をヘボン式ローマ字に変換する。
use_macron=True のとき ā ī ū ē ō で長音を表現(マクロン)。
use_macron=False のときは単純に連続母音を残す(例: ou, oo
"""
# 基本音の対応(主要なカタカナ)
base = {
'':'a','':'i','':'u','':'e','':'o',
'':'ka','':'ki','':'ku','':'ke','':'ko',
'':'sa','':'shi','':'su','':'se','':'so',
'':'ta','':'chi','':'tsu','':'te','':'to',
'':'na','':'ni','':'nu','':'ne','':'no',
'':'ha','':'hi','':'fu','':'he','':'ho',
'':'ma','':'mi','':'mu','':'me','':'mo',
'':'ya','':'yu','':'yo',
'':'ra','':'ri','':'ru','':'re','':'ro',
'':'wa','':'wo','':'n',
'':'ga','':'gi','':'gu','':'ge','':'go',
'':'za','':'ji','':'zu','':'ze','':'zo',
'':'da','':'ji','':'zu','':'de','':'do',
'':'ba','':'bi','':'bu','':'be','':'bo',
'':'pa','':'pi','':'pu','':'pe','':'po',
# 小書き(単独で使われることは少ないがマップしておく)
'':'a','':'i','':'u','':'e','':'o',
'':'ya','':'yu','':'yo','':'xtsu','':'-',
'':'vu','シェ':'she' # 特殊は下で組合せで処理
}
# 拡張:子音 + 小ャユョ の組合せ(主要なもの)
digraphs = {
('',''):'kya', ('',''):'kyu', ('',''):'kyo',
('',''):'gya', ('',''):'gyu', ('',''):'gyo',
('',''):'sha', ('',''):'shu', ('',''):'sho',
('',''):'ja', ('',''):'ju', ('',''):'jo',
('',''):'cha', ('',''):'chu', ('',''):'cho',
('',''):'nya', ('',''):'nyu', ('',''):'nyo',
('',''):'hya', ('',''):'hyu', ('',''):'hyo',
('',''):'bya', ('',''):'byu', ('',''):'byo',
('',''):'pya', ('',''):'pyu', ('',''):'pyo',
('',''):'mya', ('',''):'myu', ('',''):'myo',
('',''):'rya', ('',''):'ryu', ('',''):'ryo',
# 外来音対応(ファ/フィ/チェ 等)
('',''):'fya', ('',''):'fyu', ('',''):'fyo',
('',''):'tu', ('',''):'du',
# F-sounds (ファ フィ フェ フォ)
('',''):'fa', ('',''):'fi', ('',''):'fe', ('',''):'fo',
# シェ チェ ティ etc.
('',''):'she', ('',''):'che',
('',''):'ti', ('',''):'tu', ('',''):'du',
('',''):'wa', ('',''):'wi', ('',''):'we', ('',''):'wo',
# その他外来語によくある組合せ
('',''):'si', ('',''):'zi', ('',''):'tsa', ('',''):'tsi', ('',''):'tse', ('',''):'tso',
('',''):'kye', ('',''):'gye',
('',''):'va', ('',''):'vi', ('',''):'ve', ('',''):'vo', ('',''):'vyu'
}
# 小文字一覧(ゃゅょぁぃぅぇぉ など)
small_kana = set(['','','','','','','','','','','','','','',''])
# マクロン変換マップ(連続母音 -> マクロン)
macron_map = {
'aa':'ā','ii':'ī','uu':'ū','ee':'ē','oo':'ō',
# ou -> ō という扱いを多くのヘボン式はする(特に日本語由来の長音)
'ou':'ō'
}
# Helper: 次のローマ字の先頭子音を取り出す(促音処理用)
def initial_consonant(rom: str) -> str:
# romはローマ字例 'shi','chi','ta'
# 子音は最初の母音直前までと考える(母音: a,i,u,e,o
for i,ch in enumerate(rom):
if ch in 'aeiou':
return rom[:i]
return rom # 母音がないなら全部
# 変換メイン
res = []
i = 0
kata = kata.strip()
length = len(kata)
while i < length:
ch = kata[i]
# 促音(ッ):次の音の初めの子音を重ねる
if ch == '':
# lookahead
if i+1 < length:
# 先の1文字 or 合字を取り得る(小書きが続く可能性)
# まず合字優先で調べる
next_pair = None
if i+2 < length and (kata[i+1], kata[i+2]) in digraphs:
next_pair = digraphs[(kata[i+1], kata[i+2])]
elif kata[i+1] in base:
next_pair = base.get(kata[i+1])
if next_pair:
cons = initial_consonant(next_pair)
if cons == '':
# もし母音始まりなら促音は無視(稀)
pass
else:
# Hepburnでは "ch" の場合 "cch"matcha等の扱いになるように
# cons の先頭1文字を倍にするより、cons全体の先頭文字を重ねるのが一般的例: 'shi' -> 'ssh' ? いい例は少ない)
# 実務上は先頭子音の最初の文字を重複する:
res.append(cons[0])
# advance only the 促音 itself here; next loop handles next kana
i += 1
continue
# 長音符(ー):前の母音を伸ばす(マクロン処理は後でまとめて)
if ch == '':
# append marker '-' to indicate prolong; we'll post-process
res.append('-')
i += 1
continue
# 合字(子 + 小ャュョ等)
if i+1 < length and (ch, kata[i+1]) in digraphs:
res.append(digraphs[(ch, kata[i+1])])
i += 2
continue
# 小書きが前に独立して出てきた場合(通常は合字で処理されるが念のため)
if ch in small_kana and ch != '':
# 小書きを単独で英字に変換(例: 'ァ' -> 'a'
res.append(base.get(ch, ''))
i += 1
continue
# 普通のカタカナ
if ch in base:
res.append(base[ch])
i += 1
continue
# 英数字や記号・ひらがななどはそのまま(変換対象外)
res.append(ch)
i += 1
# ここまでで res はローマ字パーツのリスト(長音は '-' でマーク)
raw = ''.join(res)
# 撥音(ン)処理: n の前が b/p/m の場合 m にする
# ただし既に 'n' のまま次が母音や y の時は通常 n' を入れるべきだが簡易処理として n のまま保持。
# 我々は 'n' の後に b/p/m が来たら 'm' に置換
import re
raw = re.sub(r'n(?=[bmp])', 'm', raw)
# 長音処理('-' マークを見て前の母音を伸ばす)
# raw 中の '-' を削って該当の母音を伸ばす
while '-' in raw:
idx = raw.find('-')
if idx == 0:
# 先頭に長音符が来るのはおかしいので削除
raw = raw[:idx] + raw[idx+1:]
continue
# 前の文字が母音ならそれを重ねる
prev = raw[idx-1]
if prev in 'aiueo':
# 直前に既に vowel がある場合、後でマクロン処理に任せて母音を2つにする
raw = raw[:idx] + prev + raw[idx+1:]
else:
# 直前が子音なら何もして取り除く
raw = raw[:idx] + raw[idx+1:]
# 小さな例外対応: 'ti' 等の表記は 'chi' と扱いたいが上述マップでカバー済み
# macron の適用(長音の正規化)
if use_macron:
# まず 'ou' を ō に(ただし語による例外はあるが、一般的ヘボンに合わせる)
# その前に 'oo' を 'ō' に(稀)
for pair, mac in macron_map.items():
raw = raw.replace(pair, mac)
# else: leave as is (ou/oo/aa...)
# 仕上げ:小文字統一(ヘボンは小文字)
raw = raw.lower()
# 最後に、n の後に母音または y が来る場合は「んあ->n'a」的扱いが必要だが
# シンプル実装では n の後に母音や y が来るときは n' を入れる(明瞭化)
# ただし多くの実例では省略されることも多いのでコメントアウトしておく
# raw = re.sub(r"n(?=[aiueoy])", "n'", raw)
return raw
# --- テスト例 ---
if __name__ == "__main__":
tests = [
"カタカナ",
"コンピューター",
"キャッチ",
"マッチャ",
"シェア",
"ジェット",
"ヴァイオリン",
"ホテル",
"スーパー",
"ギュウニュウ",
"パーティー",
"トウキョウ", # 東京(トウキョウ -> tōkyō
"オーケー",
"ファイル",
"ニューヨーク",
"ラーメン",
"パン",
"チョコレート",
]
for s in tests:
print(s, "->", katakana_to_hepburn(s, use_macron=True))

View File

@@ -0,0 +1,187 @@
from sudachipy import tokenizer
from sudachipy import dictionary
try:
from .transliteration_kana_to_hepburn import katakana_to_hepburn
except ImportError:
from transliteration_kana_to_hepburn import katakana_to_hepburn
class Transliterator:
def __init__(self):
self.tokenizer_obj = dictionary.Dictionary().create()
self.mode = tokenizer.Tokenizer.SplitMode.A
@staticmethod
def is_kanji(ch: str) -> bool:
return '\u4e00' <= ch <= '\u9fff'
@staticmethod
def kata_to_hira(text: str) -> str:
return "".join(
chr(ord(c) - 0x60) if '' <= c <= '' else c
for c in text
)
@staticmethod
def split_kanji_okurigana(surface: str, reading_kana: str):
"""
1語の表層形(surface)と読み(reading_kana)を
[ {"orig":..., "kana":..., "hira":..., "hepburn":...}, ... ] に分割
"""
result = []
# 表層を「漢字ブロック」と「非漢字ブロック」に分割
buf = ""
prev_is_kanji = None
blocks = []
for ch in surface:
now_is_kanji = Transliterator.is_kanji(ch)
if prev_is_kanji is None or now_is_kanji == prev_is_kanji:
buf += ch
else:
blocks.append((prev_is_kanji, buf))
buf = ch
prev_is_kanji = now_is_kanji
if buf:
blocks.append((prev_is_kanji, buf))
# 読みを分配
kana_left = reading_kana
for i, (is_kan, part) in enumerate(blocks):
if is_kan:
# 漢字ブロックの処理
if len(blocks) == 1:
# 単一ブロック(全て漢字)の場合
kana_for_kan = kana_left
elif i == len(blocks) - 1:
# 最後のブロック(漢字)の場合
kana_for_kan = kana_left
else:
# 中間の漢字ブロックの場合
# 後続の非漢字ブロックの文字数を計算
remaining_non_kanji = sum(len(p) for is_k, p in blocks[i+1:] if not is_k)
if remaining_non_kanji > 0 and len(kana_left) > remaining_non_kanji:
kana_for_kan = kana_left[:-remaining_non_kanji]
else:
# 漢字1文字あたり最低1文字の読みを割り当て
min_kana = len(part)
kana_for_kan = kana_left[:max(min_kana, len(kana_left) - remaining_non_kanji)]
# 空の読みを避ける
if not kana_for_kan and kana_left:
kana_for_kan = kana_left[:1]
result.append(
{
"orig": part,
"kana": kana_for_kan,
"hira": Transliterator.kata_to_hira(kana_for_kan),
"hepburn": katakana_to_hepburn(kana_for_kan, use_macron=True)
}
)
kana_left = kana_left[len(kana_for_kan):]
else:
# 非漢字部分(送り仮名など)
kana_for_okuri = kana_left[:len(part)]
result.append(
{
"orig": part,
"kana": kana_for_okuri,
"hira": Transliterator.kata_to_hira(kana_for_okuri),
"hepburn": katakana_to_hepburn(kana_for_okuri, use_macron=True)
}
)
kana_left = kana_left[len(kana_for_okuri):]
return result
def analyze(self, text: str, use_macron: bool = True):
tokens = self.tokenizer_obj.tokenize(text, self.mode)
results = []
for t in tokens:
surface = t.surface()
reading = t.reading_form()
# 単純に1文字ずつ処理
if len(surface) == 1:
# 1文字の場合はそのまま
results.append({
"orig": surface,
"kana": reading,
"hira": self.kata_to_hira(reading),
"hepburn": katakana_to_hepburn(reading, use_macron=use_macron)
})
else:
# 複数文字の場合は文字種別で分割
i = 0
reading_pos = 0
while i < len(surface):
char = surface[i]
if self.is_kanji(char):
# 漢字の場合、連続する漢字をまとめて処理
kanji_block = ""
while i < len(surface) and self.is_kanji(surface[i]):
kanji_block += surface[i]
i += 1
# 漢字ブロックの読みを推定
if i < len(surface):
# 後に文字がある場合、送り仮名を考慮
remaining_chars = len(surface) - i
kanji_reading = reading[reading_pos:-remaining_chars] if remaining_chars > 0 else reading[reading_pos:]
else:
# 最後の漢字ブロックの場合
kanji_reading = reading[reading_pos:]
results.append({
"orig": kanji_block,
"kana": kanji_reading,
"hira": self.kata_to_hira(kanji_reading),
"hepburn": katakana_to_hepburn(kanji_reading, use_macron=use_macron)
})
reading_pos += len(kanji_reading)
else:
# 非漢字の場合
non_kanji_block = ""
while i < len(surface) and not self.is_kanji(surface[i]):
non_kanji_block += surface[i]
i += 1
# 非漢字部分の読み(通常は文字数分)
non_kanji_reading = reading[reading_pos:reading_pos + len(non_kanji_block)]
results.append({
"orig": non_kanji_block,
"kana": non_kanji_reading,
"hira": self.kata_to_hira(non_kanji_reading),
"hepburn": katakana_to_hepburn(non_kanji_reading, use_macron=use_macron)
})
reading_pos += len(non_kanji_reading)
return results
# --- テスト ---
if __name__ == "__main__":
test_cases = [
"美しい花を見る",
"東京に行く",
"漢字とカタカナの混在",
"パーティーに行く",
"コンピューターを使う",
"シェアハウスに住む",
"ヴァイオリンを弾く",
"ギュウニュウを飲む",
"ニューヨークに行く",
"ラーメンを食べる",
"チョコレートが好き",
"SessionIDを取得する",
"取り敢えず検索してみる",
"見知らぬ土地で冒険する",
"彼は優れたエンジニアです",
]
transliterator = Transliterator()
for case in test_cases:
print(transliterator.analyze(case))

View File

@@ -45,6 +45,10 @@ export const Others = () => {
<SendMessageFormatPartsContainer /> <SendMessageFormatPartsContainer />
<ReceivedMessageFormatPartsContainer /> <ReceivedMessageFormatPartsContainer />
</div> </div>
<div>
<ConvertMessageToRomajiContainer />
<ConvertMessageToHiraganaContainer />
</div>
</div> </div>
); );
}; };
@@ -202,3 +206,34 @@ const ReceivedMessageFormatPartsContainer = () => {
/> />
); );
}; };
const ConvertMessageToRomajiContainer = () => {
const { t } = useI18n();
const { currentConvertMessageToRomaji, toggleConvertMessageToRomaji } = useOthers();
return (
<CheckboxContainer
label={t("config_page.others.convert_message_to_romaji.label")}
desc={t(
"config_page.others.convert_message_to_romaji.desc",
{ convert_message_to_hiragana: t("config_page.others.convert_message_to_hiragana.label") }
)}
variable={currentConvertMessageToRomaji}
toggleFunction={toggleConvertMessageToRomaji}
/>
);
};
const ConvertMessageToHiraganaContainer = () => {
const { t } = useI18n();
const { currentConvertMessageToHiragana, toggleConvertMessageToHiragana } = useOthers();
return (
<CheckboxContainer
label={t("config_page.others.convert_message_to_hiragana.label")}
desc={t("config_page.others.convert_message_to_hiragana.desc")}
variable={currentConvertMessageToHiragana}
toggleFunction={toggleConvertMessageToHiragana}
/>
);
};

View File

@@ -17,10 +17,10 @@ export const MessageContainer = ({ messages, status, category, created_at }) =>
const [is_locked, setIsLocked] = useState(false); const [is_locked, setIsLocked] = useState(false);
const resendFunction = () => { const resendFunction = () => {
sendMessage(messages.original); sendMessage(messages.original.message);
}; };
const editFunction = () => { const editFunction = () => {
updateMessageInputValue(messages.original); updateMessageInputValue(messages.original.message);
}; };
const handleMouseEnter = () => { const handleMouseEnter = () => {
@@ -39,7 +39,7 @@ export const MessageContainer = ({ messages, status, category, created_at }) =>
setIsLocked(true); setIsLocked(true);
}; };
const is_translated_exist = messages.translated?.length >= 1; const is_translation_exist = messages.translations?.length > 0;
const is_pending = status === "pending"; const is_pending = status === "pending";
const is_sent_message = category === "sent"; const is_sent_message = category === "sent";
const is_system_message = category === "system"; const is_system_message = category === "system";
@@ -69,11 +69,11 @@ export const MessageContainer = ({ messages, status, category, created_at }) =>
</div> </div>
<div className={clsx(styles.message_box, message_type_class_name)}> <div className={clsx(styles.message_box, message_type_class_name)}>
{is_system_message ? ( {is_system_message ? (
<p className={styles.message_main_system}>{messages.message}</p> <p className={styles.message_main_system}>{messages.original.message}</p>
) : is_translated_exist ? ( ) : is_translation_exist ? (
<WithTranslatedMessages messages={messages} /> <WithTranslatedMessages messages={messages} />
) : ( ) : (
<p className={styles.message_main}>{messages.original}</p> <OriginalMessage messages={messages} />
)} )}
</div> </div>
</div> </div>
@@ -88,13 +88,72 @@ export const MessageContainer = ({ messages, status, category, created_at }) =>
); );
}; };
const WithTranslatedMessages = ({ messages }) => { const MessageWithTransliteration = ({ item }) => {
const translated_data = Array.isArray(messages.translated) ? messages.translated : [messages.translated]; const renderTokenNode = (token, key) => {
const orig = token.orig ?? "";
const hira = token.hira ?? "";
const hepburn = token.hepburn ?? "";
if (hira && hira === orig && hepburn) {
return (
<span key={key} title={hepburn} className={styles.with_hepburn}>
{orig}
</span>
);
}
if (hira && hira !== orig && hepburn) {
return (
<ruby key={key} title={hepburn} className={styles.with_hepburn}>
{orig}
<rt>{hira}</rt>
</ruby>
);
}
if (hepburn && hepburn !== orig) {
return (
<ruby key={key} className={styles.ruby}>
{orig}
<rt>{hepburn}</rt>
</ruby>
);
}
return (
<span key={key} className={styles.original_only}>
{orig}
</span>
);
};
if (!item.transliteration.length) {
return <p className={styles.message_main}>{item.message}</p>;
}
return (
<p className={styles.message_main}>
{item.transliteration.map((token, idx) => renderTokenNode(token, idx))}
</p>
);
};
const OriginalMessage = ({ messages }) => {
return ( return (
<> <>
<p className={styles.message_second}>{messages.original}</p> <MessageWithTransliteration item={messages.original} />
{translated_data.map((message, index) => ( </>
<p key={index} className={styles.message_main}>{message}</p> );
};
const WithTranslatedMessages = ({ messages }) => {
return (
<>
<p className={styles.message_second}>{messages.original.message}</p>
{messages.translations.map((item, idx) => (
<div key={idx}>
<MessageWithTransliteration item={item} />
</div>
))} ))}
</> </>
); );

View File

@@ -113,3 +113,10 @@
color: var(--dark_500_color); color: var(--dark_500_color);
} }
} }
// For ruby
.with_hepburn {
&:hover {
color: var(--dark_500_color);
}
}

View File

@@ -33,7 +33,7 @@ export const MessageInputBox = () => {
if (currentMessageLogs.data) { if (currentMessageLogs.data) {
const sentMessages = currentMessageLogs.data const sentMessages = currentMessageLogs.data
.filter(log => log.category === "sent") .filter(log => log.category === "sent")
.map(log => log.messages.original); .map(log => log.messages.original.message);
setMessageHistory(sentMessages); setMessageHistory(sentMessages);
} }
}, [currentMessageLogs.data]); }, [currentMessageLogs.data]);

View File

@@ -24,8 +24,8 @@ export const useMessage = () => {
status: "pending", status: "pending",
created_at: generateTimeData(), created_at: generateTimeData(),
messages: { messages: {
original: message, original: { message: message, transliteration: [] },
translated: [], translations: [],
}, },
}); });
}; };
@@ -39,20 +39,26 @@ export const useMessage = () => {
category: "system", category: "system",
status: "system", status: "system",
created_at: date, created_at: date,
messages: {message: message}, messages: {
original: { message: message, transliteration: [] },
translations: [],
},
}); });
}; };
const addSystemMessageLog_FromBackend = (payload) => { const addSystemMessageLog_FromBackend = (payload) => {
addSystemMessageLog(payload.message); addSystemMessageLog(payload.message);
}; };
const updateSentMessageLogById = (payload) => { const updateSentMessageLogById = (payload) => {
updateMessageLogs(updateItemById(payload.id, payload.translation)); updateMessageLogs(updateItemById(payload.id, payload));
}; };
const addSentMessageLog = (payload) => { const addSentMessageLog = (payload) => {
const message_object = generateMessageObject(payload, "sent"); const message_object = generateMessageObject(payload, "sent");
addMessageLogs(message_object); addMessageLogs(message_object);
}; };
const addReceivedMessageLog = (payload) => { const addReceivedMessageLog = (payload) => {
const message_object = generateMessageObject(payload, "received"); const message_object = generateMessageObject(payload, "received");
addMessageLogs(message_object); addMessageLogs(message_object);
@@ -61,6 +67,7 @@ export const useMessage = () => {
const startTyping = () => { const startTyping = () => {
asyncStdoutToPython("/run/typing_message_box"); asyncStdoutToPython("/run/typing_message_box");
}; };
const stopTyping = () => { const stopTyping = () => {
asyncStdoutToPython("/run/stop_typing_message_box"); asyncStdoutToPython("/run/stop_typing_message_box");
}; };
@@ -83,11 +90,10 @@ export const useMessage = () => {
}; };
const generateTimeData = () => { const generateTimeData = () => {
const data = new Date().toLocaleTimeString( return new Date().toLocaleTimeString(
"ja-JP", "ja-JP",
{ hour12: false, hour: "2-digit", minute: "2-digit" }, { hour12: false, hour: "2-digit", minute: "2-digit" }
); );
return data;
}; };
const generateMessageObject = (data, category) => { const generateMessageObject = (data, category) => {
@@ -97,17 +103,17 @@ const generateMessageObject = (data, category) => {
category: category, category: category,
status: "ok", status: "ok",
messages: { messages: {
original: data.message, original: data.original,
translated: data.translation, translations: data.translations ?? [],
}, },
}; };
}; };
const updateItemById = (id, translated_data) => (current_items) => { const updateItemById = (id, updated_data) => (current_items) => {
return current_items.data.map(item => { return current_items.data.map(item => {
if (item.id === id) { if (item.id === id) {
item.status = "ok"; item.status = "ok";
item.messages.translated = translated_data; if (updated_data.translations) item.messages.translations = updated_data.translations;
} }
return item; return item;
}); });

View File

@@ -9,6 +9,8 @@ import {
useStore_MessageFormat_ExampleViewFilter, useStore_MessageFormat_ExampleViewFilter,
useStore_SendMessageFormatParts, useStore_SendMessageFormatParts,
useStore_ReceivedMessageFormatParts, useStore_ReceivedMessageFormatParts,
useStore_ConvertMessageToRomaji,
useStore_ConvertMessageToHiragana,
} from "@store"; } from "@store";
import { useStdoutToPython } from "@useStdoutToPython"; import { useStdoutToPython } from "@useStdoutToPython";
import { useNotificationStatus } from "@logics_common"; import { useNotificationStatus } from "@logics_common";
@@ -39,6 +41,11 @@ export const useOthers = () => {
// Received // Received
const { currentReceivedMessageFormatParts, updateReceivedMessageFormatParts, pendingReceivedMessageFormatParts } = useStore_ReceivedMessageFormatParts(); const { currentReceivedMessageFormatParts, updateReceivedMessageFormatParts, pendingReceivedMessageFormatParts } = useStore_ReceivedMessageFormatParts();
// Convert Message To Romaji
const { currentConvertMessageToRomaji, updateConvertMessageToRomaji, pendingConvertMessageToRomaji } = useStore_ConvertMessageToRomaji();
// Convert Message To Hiragana
const { currentConvertMessageToHiragana, updateConvertMessageToHiragana, pendingConvertMessageToHiragana } = useStore_ConvertMessageToHiragana();
const { showNotification_SaveSuccess } = useNotificationStatus(); const { showNotification_SaveSuccess } = useNotificationStatus();
// Auto Clear Message Input Box // Auto Clear Message Input Box
@@ -233,6 +240,45 @@ export const useOthers = () => {
}); });
}; };
// Convert Message To Romaji
const getConvertMessageToRomaji = () => {
pendingConvertMessageToRomaji();
asyncStdoutToPython("/get/data/convert_message_to_romaji");
};
const toggleConvertMessageToRomaji = () => {
pendingConvertMessageToRomaji();
if (currentConvertMessageToRomaji.data) {
asyncStdoutToPython("/set/disable/convert_message_to_romaji");
} else {
asyncStdoutToPython("/set/enable/convert_message_to_romaji");
}
};
const setSuccessConvertMessageToRomaji = (enabled) => {
updateConvertMessageToRomaji(enabled);
showNotification_SaveSuccess();
};
// Convert Message To Hiragana
const getConvertMessageToHiragana = () => {
pendingConvertMessageToHiragana();
asyncStdoutToPython("/get/data/convert_message_to_hiragana");
};
const toggleConvertMessageToHiragana = () => {
pendingConvertMessageToHiragana();
if (currentConvertMessageToHiragana.data) {
asyncStdoutToPython("/set/disable/convert_message_to_hiragana");
} else {
asyncStdoutToPython("/set/enable/convert_message_to_hiragana");
}
};
const setSuccessConvertMessageToHiragana = (enabled) => {
updateConvertMessageToHiragana(enabled);
showNotification_SaveSuccess();
};
return { return {
// Auto Clear Message Input Box // Auto Clear Message Input Box
@@ -303,5 +349,19 @@ export const useOthers = () => {
getReceivedMessageFormatParts, getReceivedMessageFormatParts,
setReceivedMessageFormatParts, setReceivedMessageFormatParts,
setSuccessReceivedMessageFormatParts, setSuccessReceivedMessageFormatParts,
// Convert Message To Romaji
currentConvertMessageToRomaji,
getConvertMessageToRomaji,
toggleConvertMessageToRomaji,
updateConvertMessageToRomaji,
setSuccessConvertMessageToRomaji,
// Convert Message To Hiragana
currentConvertMessageToHiragana,
getConvertMessageToHiragana,
toggleConvertMessageToHiragana,
updateConvertMessageToHiragana,
setSuccessConvertMessageToHiragana,
}; };
}; };

View File

@@ -292,6 +292,14 @@ export const ROUTE_META_LIST = [
{ endpoint: "/get/data/received_message_format_parts", ns: configs, hook_name: "useOthers", method_name: "updateReceivedMessageFormatParts" }, { endpoint: "/get/data/received_message_format_parts", ns: configs, hook_name: "useOthers", method_name: "updateReceivedMessageFormatParts" },
{ endpoint: "/set/data/received_message_format_parts", ns: configs, hook_name: "useOthers", method_name: "setSuccessReceivedMessageFormatParts" }, { endpoint: "/set/data/received_message_format_parts", ns: configs, hook_name: "useOthers", method_name: "setSuccessReceivedMessageFormatParts" },
{ endpoint: "/get/data/convert_message_to_romaji", ns: configs, hook_name: "useOthers", method_name: "updateConvertMessageToRomaji" },
{ endpoint: "/set/enable/convert_message_to_romaji", ns: configs, hook_name: "useOthers", method_name: "setSuccessConvertMessageToRomaji" },
{ endpoint: "/set/disable/convert_message_to_romaji", ns: configs, hook_name: "useOthers", method_name: "setSuccessConvertMessageToRomaji" },
{ endpoint: "/get/data/convert_message_to_hiragana", ns: configs, hook_name: "useOthers", method_name: "updateConvertMessageToHiragana" },
{ endpoint: "/set/enable/convert_message_to_hiragana", ns: configs, hook_name: "useOthers", method_name: "setSuccessConvertMessageToHiragana" },
{ endpoint: "/set/disable/convert_message_to_hiragana", ns: configs, hook_name: "useOthers", method_name: "setSuccessConvertMessageToHiragana" },
// Hotkeys // Hotkeys
{ endpoint: "/get/data/hotkeys", ns: configs, hook_name: "useHotkeys", method_name: "updateHotkeys" }, { endpoint: "/get/data/hotkeys", ns: configs, hook_name: "useHotkeys", method_name: "updateHotkeys" },
{ endpoint: "/set/data/hotkeys", ns: configs, hook_name: "useHotkeys", method_name: "setSuccessHotkeys" }, { endpoint: "/set/data/hotkeys", ns: configs, hook_name: "useHotkeys", method_name: "setSuccessHotkeys" },
@@ -323,8 +331,6 @@ export const ROUTE_META_LIST = [
{ endpoint: "/get/data/mic_no_speech_prob", ns: null, hook_name: null, method_name: null }, // Not implemented on UI yet { endpoint: "/get/data/mic_no_speech_prob", ns: null, hook_name: null, method_name: null }, // Not implemented on UI yet
{ endpoint: "/get/data/speaker_avg_logprob", ns: null, hook_name: null, method_name: null }, // Not implemented on UI yet { endpoint: "/get/data/speaker_avg_logprob", ns: null, hook_name: null, method_name: null }, // Not implemented on UI yet
{ endpoint: "/get/data/speaker_no_speech_prob", ns: null, hook_name: null, method_name: null }, // Not implemented on UI yet { endpoint: "/get/data/speaker_no_speech_prob", ns: null, hook_name: null, method_name: null }, // Not implemented on UI yet
{ endpoint: "/get/data/convert_message_to_romaji", ns: null, hook_name: null, method_name: null }, // Not implemented on UI yet
{ endpoint: "/get/data/convert_message_to_hiragana", ns: null, hook_name: null, method_name: null }, // Not implemented on UI yet
{ endpoint: "/get/data/transcription_engines", ns: null, hook_name: null, method_name: null }, // Not implemented on UI yet. (if ai_models has not been detected, this will be blank array[]. if the ai_models are ok but just network has not connected, it'l be only ["Whisper"]) { endpoint: "/get/data/transcription_engines", ns: null, hook_name: null, method_name: null }, // Not implemented on UI yet. (if ai_models has not been detected, this will be blank array[]. if the ai_models are ok but just network has not connected, it'l be only ["Whisper"])
]; ];

View File

@@ -309,6 +309,8 @@ export const { atomInstance: Atom_ReceivedMessageFormatParts, useHook: useStore_
}, },
translation_first: false, translation_first: false,
}, "ReceivedMessageFormatParts"); }, "ReceivedMessageFormatParts");
export const { atomInstance: Atom_ConvertMessageToRomaji, useHook: useStore_ConvertMessageToRomaji } = createAtomWithHook(false, "ConvertMessageToRomaji");
export const { atomInstance: Atom_ConvertMessageToHiragana, useHook: useStore_ConvertMessageToHiragana } = createAtomWithHook(false, "ConvertMessageToHiragana");
// Hotkeys // Hotkeys