Translation Service
- Added translation service with 3 services. - MyMemory (Free no api key needed but 1000 words a day and Skow) - Deepl (Free with API key, 500000 words a month and fast) - Libre Translate (Paid unless you host your own server, open source) - Added caching for Quick Access and the translate service to speed up the UI. Can be fast depending on the service you use/ PC specs and etc).
This commit is contained in:
@@ -0,0 +1,372 @@
|
||||
# GPL License
|
||||
|
||||
from typing import Dict, List, Optional, Set, Tuple
|
||||
from .dictionaries import bone_names, reverse_bone_lookup, simplify_bonename
|
||||
from .logging_setup import logger
|
||||
|
||||
# Enhanced dictionaries for comprehensive translation support
|
||||
|
||||
# Shapekey/Morph name translations (Japanese to English)
|
||||
shapekey_names: Dict[str, List[str]] = {
|
||||
# Basic facial expressions
|
||||
"neutral": ["ニュートラル", "中立", "通常", "普通", "デフォルト", "basis"],
|
||||
"smile": ["笑顔", "スマイル", "えがお", "笑い", "にこり", "ほほえみ", "smile", "happy"],
|
||||
"angry": ["怒り", "怒る", "アングリー", "いかり", "おこり", "むかつき", "angry", "mad"],
|
||||
"sad": ["悲しい", "かなしい", "悲哀", "サッド", "sad", "sorrow"],
|
||||
"surprised": ["驚き", "びっくり", "おどろき", "サプライズ", "surprised", "shock"],
|
||||
"disgusted": ["嫌悪", "いやがり", "きもち悪い", "disgusted"],
|
||||
"fearful": ["恐怖", "怖い", "こわい", "恐れ", "fearful", "scared"],
|
||||
"blink": ["瞬き", "まばたき", "ブリンク", "目閉じ", "blink", "eyeclose"],
|
||||
"wink_left": ["ウィンク左", "左目ウィンク", "ひだりめうぃんく", "winkleft", "wink_l"],
|
||||
"wink_right": ["ウィンク右", "右目ウィンク", "みぎめうぃんく", "winkright", "wink_r"],
|
||||
"eye_close": ["目閉じ", "目を閉じる", "めとじ", "eyeclose", "closedeyes"],
|
||||
"eye_wide": ["目見開き", "目を見開く", "びっくり目", "eyewide", "wideeyes"],
|
||||
"eye_narrow": ["細目", "目細め", "ほそめ", "eyenarrow", "narroweyes"],
|
||||
"mouth_open": ["口開け", "口を開ける", "くちあけ", "mouthopen", "openmouth"],
|
||||
"mouth_smile": ["口角上げ", "口笑顔", "くちえがお", "mouthsmile"],
|
||||
"mouth_frown": ["口角下げ", "への字口", "くちしかめ", "mouthfrown"],
|
||||
"mouth_pout": ["すぼめ口", "とがらせ口", "mouthpout"],
|
||||
"eyebrow_up": ["眉上げ", "眉毛上げ", "まゆあげ", "eyebrowup", "raiseeyebrow"],
|
||||
"eyebrow_down": ["眉下げ", "眉寄せ", "まゆさげ", "eyebrowdown", "lowereyebrow"],
|
||||
"eyebrow_angry": ["怒り眉", "眉怒り", "まゆいかり", "angrybrow"],
|
||||
"cheek_puff": ["頬膨らまし", "ほほふくらまし", "cheekpuff"],
|
||||
"cheek_suck": ["頬すぼめ", "ほほすぼめ", "cheeksuck"],
|
||||
"joy": ["喜び", "よろこび", "ジョイ", "joy", "happiness"],
|
||||
"contempt": ["軽蔑", "けいべつ", "contempt"],
|
||||
"confusion": ["困惑", "こんわく", "confusion", "confused"],
|
||||
"concentration": ["集中", "しゅうちゅう", "concentration", "focused"],
|
||||
|
||||
# VRC Visemes
|
||||
"viseme_sil": ["無音", "むおん", "サイレンス", "silence", "sil"],
|
||||
"viseme_aa": ["あ", "aa", "mouth_a"],
|
||||
"viseme_ih": ["い", "ih", "mouth_i"],
|
||||
"viseme_ou": ["う", "ou", "mouth_u"],
|
||||
"viseme_e": ["え", "e", "mouth_e"],
|
||||
"viseme_oh": ["お", "oh", "mouth_o"],
|
||||
"viseme_ch": ["ち", "ch"],
|
||||
"viseme_dd": ["だ", "dd"],
|
||||
"viseme_ff": ["ふ", "ff"],
|
||||
"viseme_kk": ["か", "kk"],
|
||||
"viseme_nn": ["ん", "nn"],
|
||||
"viseme_pp": ["ぱ", "pp"],
|
||||
"viseme_rr": ["ら", "rr"],
|
||||
"viseme_ss": ["さ", "ss"],
|
||||
"viseme_th": ["た", "th"],
|
||||
|
||||
"basis": ["基本", "きほん", "ベース", "base", "basis", "default"],
|
||||
"reset": ["リセット", "初期化", "しょきか", "reset", "clear"],
|
||||
}
|
||||
|
||||
# Material name translations (Japanese to English)
|
||||
material_names: Dict[str, List[str]] = {
|
||||
# Basic materials
|
||||
"skin": ["肌", "はだ", "皮膚", "ひふ", "スキン", "skin", "flesh"],
|
||||
"hair": ["髪", "かみ", "毛髪", "もうはつ", "ヘア", "hair"],
|
||||
"eyes": ["目", "め", "眼", "がん", "アイ", "eye", "iris"],
|
||||
"eyebrow": ["眉", "まゆ", "眉毛", "まゆげ", "eyebrow", "brow"],
|
||||
"eyelash": ["まつ毛", "まつげ", "睫毛", "eyelash", "lash"],
|
||||
"teeth": ["歯", "は", "歯列", "しれつ", "tooth", "teeth"],
|
||||
"tongue": ["舌", "した", "tongue"],
|
||||
"nails": ["爪", "つめ", "nail", "nails"],
|
||||
"shirt": ["シャツ", "上着", "うわぎ", "shirt", "top"],
|
||||
"pants": ["パンツ", "ズボン", "下着", "したぎ", "pants", "trousers"],
|
||||
"skirt": ["スカート", "skirt"],
|
||||
"dress": ["ドレス", "ワンピース", "dress"],
|
||||
"shoes": ["靴", "くつ", "シューズ", "shoe", "shoes"],
|
||||
"socks": ["靴下", "くつした", "ソックス", "sock", "socks"],
|
||||
"gloves": ["手袋", "てぶくろ", "グローブ", "glove", "gloves"],
|
||||
"hat": ["帽子", "ぼうし", "ハット", "hat", "cap"],
|
||||
"jacket": ["ジャケット", "上着", "うわぎ", "jacket", "coat"],
|
||||
"underwear": ["下着", "したぎ", "パンティー", "underwear", "panties"],
|
||||
"bra": ["ブラ", "ブラジャー", "胸当て", "bra", "brassiere"],
|
||||
"glasses": ["眼鏡", "めがね", "メガネ", "glasses", "spectacles"],
|
||||
"earring": ["イヤリング", "耳飾り", "みみかざり", "earring"],
|
||||
"necklace": ["ネックレス", "首飾り", "くびかざり", "necklace"],
|
||||
"bracelet": ["ブレスレット", "腕輪", "うでわ", "bracelet"],
|
||||
"ring": ["指輪", "ゆびわ", "リング", "ring"],
|
||||
"watch": ["時計", "とけい", "ウォッチ", "watch"],
|
||||
"bag": ["鞄", "かばん", "バッグ", "bag", "purse"],
|
||||
"belt": ["ベルト", "帯", "おび", "belt"],
|
||||
"transparent": ["透明", "とうめい", "クリア", "transparent", "clear"],
|
||||
"metal": ["金属", "きんぞく", "メタル", "metal"],
|
||||
"fabric": ["布", "ぬの", "生地", "きじ", "fabric", "cloth"],
|
||||
"leather": ["革", "かわ", "皮", "ひ", "レザー", "leather"],
|
||||
"plastic": ["プラスチック", "プラ", "plastic"],
|
||||
"glass": ["ガラス", "硝子", "glass"],
|
||||
"rubber": ["ゴム", "ラバー", "rubber"],
|
||||
"wood": ["木", "き", "木材", "もくざい", "wood", "wooden"],
|
||||
"diffuse": ["ディフューズ", "基本色", "きほんしょく", "diffuse", "albedo"],
|
||||
"normal": ["ノーマル", "法線", "ほうせん", "normal", "bump"],
|
||||
"specular": ["スペキュラー", "反射", "はんしゃ", "specular", "reflection"],
|
||||
"emission": ["発光", "はっこう", "エミッション", "emission", "glow"],
|
||||
"roughness": ["粗さ", "あらさ", "ラフネス", "roughness"],
|
||||
"metallic": ["メタリック", "金属性", "きんぞくせい", "metallic"],
|
||||
"subsurface": ["表面下散乱", "サブサーフェス", "subsurface", "sss"],
|
||||
|
||||
# Common naming patterns
|
||||
"main": ["メイン", "主要", "しゅよう", "main", "primary"],
|
||||
"sub": ["サブ", "副", "ふく", "sub", "secondary"],
|
||||
"detail": ["詳細", "しょうさい", "ディテール", "detail"],
|
||||
"shadow": ["影", "かげ", "シャドウ", "shadow"],
|
||||
"highlight": ["ハイライト", "強調", "きょうちょう", "highlight"],
|
||||
}
|
||||
|
||||
# Object name translations (Japanese to English)
|
||||
object_names: Dict[str, List[str]] = {
|
||||
|
||||
"body": ["体", "からだ", "身体", "しんたい", "ボディ", "body", "torso"],
|
||||
"head": ["頭", "あたま", "ヘッド", "head"],
|
||||
"face": ["顔", "かお", "フェイス", "face"],
|
||||
"neck": ["首", "くび", "ネック", "neck"],
|
||||
"chest": ["胸", "むね", "チェスト", "chest", "breast"],
|
||||
"back": ["背中", "せなか", "バック", "back"],
|
||||
"waist": ["腰", "こし", "ウエスト", "waist"],
|
||||
"hip": ["腰", "こし", "ヒップ", "hip"],
|
||||
"arm": ["腕", "うで", "アーム", "arm"],
|
||||
"hand": ["手", "て", "ハンド", "hand"],
|
||||
"finger": ["指", "ゆび", "フィンガー", "finger"],
|
||||
"leg": ["足", "あし", "脚", "レッグ", "leg"],
|
||||
"foot": ["足", "あし", "フット", "foot"],
|
||||
"toe": ["つま先", "つまさき", "トゥ", "toe"],
|
||||
"clothing": ["服", "ふく", "衣服", "いふく", "クロージング", "clothing", "clothes"],
|
||||
"outfit": ["服装", "ふくそう", "アウトフィット", "outfit"],
|
||||
"accessory": ["アクセサリー", "装身具", "そうしんぐ", "accessory"],
|
||||
"decoration": ["装飾", "そうしょく", "デコレーション", "decoration"],
|
||||
"hair_front": ["前髪", "まえがみ", "フロント髪", "hairfront"],
|
||||
"hair_back": ["後ろ髪", "うしろがみ", "バック髪", "hairback"],
|
||||
"hair_side": ["横髪", "よこがみ", "サイド髪", "hairside"],
|
||||
"ponytail": ["ポニーテール", "一つ結び", "ひとつむすび", "ponytail"],
|
||||
"twintail": ["ツインテール", "二つ結び", "ふたつむすび", "twintail"],
|
||||
"ahoge": ["あほ毛", "アホ毛", "はね毛", "ahoge", "antenna"],
|
||||
"eyeball": ["眼球", "がんきゅう", "目玉", "めだま", "eyeball"],
|
||||
"pupil": ["瞳", "ひとみ", "瞳孔", "どうこう", "pupil"],
|
||||
"iris": ["虹彩", "こうさい", "アイリス", "iris"],
|
||||
"eyelid": ["まぶた", "眼瞼", "がんけん", "eyelid"],
|
||||
"nose": ["鼻", "はな", "ノーズ", "nose"],
|
||||
"mouth": ["口", "くち", "マウス", "mouth"],
|
||||
"lip": ["唇", "くちびる", "リップ", "lip"],
|
||||
"ear": ["耳", "みみ", "イヤー", "ear"],
|
||||
|
||||
# Common object suffixes
|
||||
"left": ["左", "ひだり", "レフト", "left", "l"],
|
||||
"right": ["右", "みぎ", "ライト", "right", "r"],
|
||||
"upper": ["上", "うえ", "アッパー", "upper", "top"],
|
||||
"lower": ["下", "した", "ロワー", "lower", "bottom"],
|
||||
"inner": ["内", "うち", "インナー", "inner", "inside"],
|
||||
"outer": ["外", "そと", "アウター", "outer", "outside"],
|
||||
"front": ["前", "まえ", "フロント", "front"],
|
||||
"back": ["後ろ", "うしろ", "バック", "back", "rear"],
|
||||
}
|
||||
|
||||
# Physics object names (for MMD rigid bodies and joints)
|
||||
physics_names: Dict[str, List[str]] = {
|
||||
# Rigid body types
|
||||
"rigidbody": ["剛体", "ごうたい", "リジッドボディ", "rigidbody", "rigid"],
|
||||
"joint": ["ジョイント", "関節", "かんせつ", "joint", "constraint"],
|
||||
"collision": ["当たり判定", "あたりはんてい", "コリジョン", "collision"],
|
||||
"hair_physics": ["髪物理", "かみぶつり", "ヘアフィジックス", "hairphys"],
|
||||
"hair_root": ["髪根元", "かみねもと", "ヘアルート", "hairroot"],
|
||||
"hair_tip": ["髪先", "かみさき", "ヘアティップ", "hairtip"],
|
||||
"cloth_physics": ["布物理", "ぬのぶつり", "クロスフィジックス", "clothphys"],
|
||||
"skirt_physics": ["スカート物理", "スカートフィジックス", "skirtphys"],
|
||||
"breast_physics": ["胸物理", "むねぶつり", "ブレストフィジックス", "breastphys"],
|
||||
"breast_root": ["胸根元", "むねねもと", "ブレストルート", "breastroot"],
|
||||
"breast_tip": ["胸先", "むねさき", "ブレストティップ", "breasttip"],
|
||||
}
|
||||
|
||||
# Create reverse lookup dictionaries
|
||||
reverse_shapekey_lookup: Dict[str, str] = {}
|
||||
reverse_material_lookup: Dict[str, str] = {}
|
||||
reverse_object_lookup: Dict[str, str] = {}
|
||||
reverse_physics_lookup: Dict[str, str] = {}
|
||||
|
||||
def _build_reverse_lookups():
|
||||
"""Build reverse lookup dictionaries for fast translation"""
|
||||
global reverse_shapekey_lookup, reverse_material_lookup, reverse_object_lookup, reverse_physics_lookup
|
||||
|
||||
for standard_name, variations in shapekey_names.items():
|
||||
for variation in variations:
|
||||
simplified = simplify_bonename(variation)
|
||||
reverse_shapekey_lookup[simplified] = standard_name
|
||||
|
||||
for standard_name, variations in material_names.items():
|
||||
for variation in variations:
|
||||
simplified = simplify_bonename(variation)
|
||||
reverse_material_lookup[simplified] = standard_name
|
||||
|
||||
for standard_name, variations in object_names.items():
|
||||
for variation in variations:
|
||||
simplified = simplify_bonename(variation)
|
||||
reverse_object_lookup[simplified] = standard_name
|
||||
|
||||
for standard_name, variations in physics_names.items():
|
||||
for variation in variations:
|
||||
simplified = simplify_bonename(variation)
|
||||
reverse_physics_lookup[simplified] = standard_name
|
||||
|
||||
_build_reverse_lookups()
|
||||
|
||||
|
||||
class EnhancedDictionaryTranslator:
|
||||
"""Enhanced dictionary translator with support for bones, shapekeys, materials, and objects"""
|
||||
|
||||
def __init__(self):
|
||||
self.translation_stats = {
|
||||
'bones': 0,
|
||||
'shapekeys': 0,
|
||||
'materials': 0,
|
||||
'objects': 0,
|
||||
'physics': 0,
|
||||
'total': 0
|
||||
}
|
||||
|
||||
def translate_bone_name(self, name: str) -> Optional[str]:
|
||||
"""Translate bone name using existing bone dictionary"""
|
||||
simplified = simplify_bonename(name)
|
||||
if simplified in reverse_bone_lookup:
|
||||
self.translation_stats['bones'] += 1
|
||||
self.translation_stats['total'] += 1
|
||||
return reverse_bone_lookup[simplified]
|
||||
return None
|
||||
|
||||
def translate_shapekey_name(self, name: str) -> Optional[str]:
|
||||
"""Translate shapekey/morph name using shapekey dictionary"""
|
||||
simplified = simplify_bonename(name)
|
||||
if simplified in reverse_shapekey_lookup:
|
||||
self.translation_stats['shapekeys'] += 1
|
||||
self.translation_stats['total'] += 1
|
||||
return reverse_shapekey_lookup[simplified]
|
||||
return None
|
||||
|
||||
def translate_material_name(self, name: str) -> Optional[str]:
|
||||
"""Translate material name using material dictionary"""
|
||||
simplified = simplify_bonename(name)
|
||||
if simplified in reverse_material_lookup:
|
||||
self.translation_stats['materials'] += 1
|
||||
self.translation_stats['total'] += 1
|
||||
return reverse_material_lookup[simplified]
|
||||
return None
|
||||
|
||||
def translate_object_name(self, name: str) -> Optional[str]:
|
||||
"""Translate object name using object dictionary"""
|
||||
simplified = simplify_bonename(name)
|
||||
if simplified in reverse_object_lookup:
|
||||
self.translation_stats['objects'] += 1
|
||||
self.translation_stats['total'] += 1
|
||||
return reverse_object_lookup[simplified]
|
||||
return None
|
||||
|
||||
def translate_physics_name(self, name: str) -> Optional[str]:
|
||||
"""Translate physics object name using physics dictionary"""
|
||||
simplified = simplify_bonename(name)
|
||||
if simplified in reverse_physics_lookup:
|
||||
self.translation_stats['physics'] += 1
|
||||
self.translation_stats['total'] += 1
|
||||
return reverse_physics_lookup[simplified]
|
||||
return None
|
||||
|
||||
def translate_name(self, name: str, category: str = "auto") -> Tuple[Optional[str], str]:
|
||||
"""
|
||||
Translate name with automatic category detection or specified category
|
||||
Returns (translated_name, detected_category)
|
||||
"""
|
||||
if not name or not name.strip():
|
||||
return None, "none"
|
||||
|
||||
if category == "bones":
|
||||
result = self.translate_bone_name(name)
|
||||
return (result, "bones") if result else (None, "unknown")
|
||||
elif category == "shapekeys":
|
||||
result = self.translate_shapekey_name(name)
|
||||
return (result, "shapekeys") if result else (None, "unknown")
|
||||
elif category == "materials":
|
||||
result = self.translate_material_name(name)
|
||||
return (result, "materials") if result else (None, "unknown")
|
||||
elif category == "objects":
|
||||
result = self.translate_object_name(name)
|
||||
return (result, "objects") if result else (None, "unknown")
|
||||
elif category == "physics":
|
||||
result = self.translate_physics_name(name)
|
||||
return (result, "physics") if result else (None, "unknown")
|
||||
elif category == "auto":
|
||||
# Try all categories in order of likelihood
|
||||
for cat_name, translate_func in [
|
||||
("bones", self.translate_bone_name),
|
||||
("shapekeys", self.translate_shapekey_name),
|
||||
("materials", self.translate_material_name),
|
||||
("objects", self.translate_object_name),
|
||||
("physics", self.translate_physics_name)
|
||||
]:
|
||||
result = translate_func(name)
|
||||
if result:
|
||||
return result, cat_name
|
||||
return None, "unknown"
|
||||
else:
|
||||
return None, "invalid_category"
|
||||
|
||||
def get_statistics(self) -> Dict[str, int]:
|
||||
"""Get translation statistics"""
|
||||
return self.translation_stats.copy()
|
||||
|
||||
def reset_statistics(self) -> None:
|
||||
"""Reset translation statistics"""
|
||||
for key in self.translation_stats:
|
||||
self.translation_stats[key] = 0
|
||||
|
||||
|
||||
# Global enhanced dictionary translator instance
|
||||
_enhanced_translator: Optional[EnhancedDictionaryTranslator] = None
|
||||
|
||||
|
||||
def get_enhanced_translator() -> EnhancedDictionaryTranslator:
|
||||
"""Get the global enhanced dictionary translator"""
|
||||
global _enhanced_translator
|
||||
if _enhanced_translator is None:
|
||||
_enhanced_translator = EnhancedDictionaryTranslator()
|
||||
return _enhanced_translator
|
||||
|
||||
|
||||
def get_all_dictionary_names() -> Dict[str, Dict[str, List[str]]]:
|
||||
"""Get all dictionary names for reference"""
|
||||
return {
|
||||
"bones": bone_names,
|
||||
"shapekeys": shapekey_names,
|
||||
"materials": material_names,
|
||||
"objects": object_names,
|
||||
"physics": physics_names
|
||||
}
|
||||
|
||||
|
||||
def add_custom_translation(category: str, standard_name: str, variations: List[str]) -> bool:
|
||||
"""Add custom translation to the dictionaries"""
|
||||
try:
|
||||
if category == "bones":
|
||||
if standard_name not in bone_names:
|
||||
bone_names[standard_name] = []
|
||||
bone_names[standard_name].extend(variations)
|
||||
elif category == "shapekeys":
|
||||
if standard_name not in shapekey_names:
|
||||
shapekey_names[standard_name] = []
|
||||
shapekey_names[standard_name].extend(variations)
|
||||
elif category == "materials":
|
||||
if standard_name not in material_names:
|
||||
material_names[standard_name] = []
|
||||
material_names[standard_name].extend(variations)
|
||||
elif category == "objects":
|
||||
if standard_name not in object_names:
|
||||
object_names[standard_name] = []
|
||||
object_names[standard_name].extend(variations)
|
||||
elif category == "physics":
|
||||
if standard_name not in physics_names:
|
||||
physics_names[standard_name] = []
|
||||
physics_names[standard_name].extend(variations)
|
||||
else:
|
||||
return False
|
||||
|
||||
_build_reverse_lookups()
|
||||
logger.info(f"Added custom translation for {category}: {standard_name}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to add custom translation: {e}")
|
||||
return False
|
||||
Reference in New Issue
Block a user