Files
4thCafeandClaude Fable 5 7b31cb53be 翻訳機能を追加(チップ下に表示・言語自動検出)
- 各タグチップの2段目に「訳」ボタン+翻訳表示(チップを常時2行化)
- 言語自動検出: 日本語タグ→英訳を表示、英語タグ→何もしない
- バックエンドは Google(deep-translator, APIキー不要)優先 + argostranslate
  (オフライン)フォールバック。エスケープ括弧を除去して翻訳精度向上
- server.py: /prompt_tag_editor/translate ルート、requirements追記

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 03:14:40 +09:00

116 lines
3.8 KiB
Python

"""翻訳バックエンド。argostranslate(オフライン/API不要)で各タグを翻訳する軽量ルート。
フロントの各チップの「訳」ボタンから
POST /prompt_tag_editor/translate {tags:[...], from:"en", to:"ja"}
で呼ばれ、{translations:[...]} を返す。言語パッケージ未導入時は初回のみ自動DL。
"""
import asyncio
import threading
from aiohttp import web
try:
from server import PromptServer
except Exception: # ComfyUI外(単体テスト等)
PromptServer = None
try:
import argostranslate.package as _pkg
import argostranslate.translate as _tr
_ARGOS_OK = True
except Exception:
_ARGOS_OK = False
_install_lock = threading.Lock()
_ready_pairs = set()
def _clean(text):
"""翻訳前の正規化。プロンプトのエスケープ括弧を素の括弧へ戻す。"""
return (
(text or "")
.replace("\\(", "(").replace("\\)", ")")
.replace("\\[", "[").replace("\\]", "]")
.strip()
)
def _google_translate(tags, src, to):
"""deep-translator(Google, APIキー不要・要ネット)。高品質だが失敗し得る。"""
from deep_translator import GoogleTranslator
g = GoogleTranslator(source=src, target=to)
out = []
for t in tags:
c = _clean(t)
out.append(g.translate(c) if c else "")
return out
def _ensure_pair(src, to):
"""(src,to) の argos 言語パッケージを用意(未導入なら取得・インストール)。"""
key = (src, to)
if key in _ready_pairs:
return
with _install_lock:
if key in _ready_pairs:
return
installed = {(p.from_code, p.to_code) for p in _pkg.get_installed_packages()}
if key not in installed:
_pkg.update_package_index()
avail = _pkg.get_available_packages()
cand = [p for p in avail if p.from_code == src and p.to_code == to]
if not cand:
raise RuntimeError(f"argos package not found: {src}->{to}")
_pkg.install_from_path(cand[0].download())
_ready_pairs.add(key)
def _argos_translate(tags, src, to):
"""argostranslate(オフライン/API不要)。品質は控えめだがネット不要。"""
_ensure_pair(src, to)
out = []
for t in tags:
c = _clean(t)
out.append(_tr.translate(c, src, to) if c else "")
return out
def _translate_sync(tags, src, to):
# まず Google(高品質・要ネット)、失敗時に argos(オフライン)へフォールバック
try:
return _google_translate(tags, src, to)
except Exception as e:
print(f"[PromptTagEditor] google translate failed, fallback to argos: {e}")
if not _ARGOS_OK:
raise RuntimeError("google failed and argostranslate not available")
return _argos_translate(tags, src, to)
if PromptServer is not None:
@PromptServer.instance.routes.post("/prompt_tag_editor/translate")
async def _route_translate(request):
try:
data = await request.json()
except Exception:
return web.json_response({"error": "invalid json"}, status=400)
tags = data.get("tags") or []
src = data.get("from", "en")
to = data.get("to", "ja")
if not isinstance(tags, list):
return web.json_response({"error": "tags must be a list"}, status=400)
loop = asyncio.get_event_loop()
try:
# ブロッキング処理(初回DL/推論)はexecutorへ逃がしイベントループを止めない
translations = await loop.run_in_executor(
None, _translate_sync, tags, src, to
)
return web.json_response({"translations": translations})
except Exception as e:
return web.json_response({"error": str(e)}, status=500)