feat: Add Text Translator (UTK) node with multiple API support
- Added comprehensive text translation node supporting multiple free and paid APIs - Implemented 5 translation providers: Google Translate (Free), LibreTranslate (Free), MyMemory (Free), DeepL (Paid), Azure Translator (Paid) - Added auto mode with intelligent fallback between providers - Implemented API key validation and error handling - Support for 60+ languages with auto-detection - Added proper error messages and status reporting - Version bump to 1.3.9
This commit is contained in:
+12
@@ -677,6 +677,15 @@ except ImportError:
|
||||
LAZY_SWITCH_MAPPINGS = {}
|
||||
LAZY_SWITCH_DISPLAY = {}
|
||||
|
||||
try:
|
||||
from .nodes.tools.text_translator import \
|
||||
NODE_CLASS_MAPPINGS as TEXT_TRANSLATOR_MAPPINGS
|
||||
from .nodes.tools.text_translator import \
|
||||
NODE_DISPLAY_NAME_MAPPINGS as TEXT_TRANSLATOR_DISPLAY
|
||||
except ImportError:
|
||||
TEXT_TRANSLATOR_MAPPINGS = {}
|
||||
TEXT_TRANSLATOR_DISPLAY = {}
|
||||
|
||||
|
||||
# 合并所有节点映射
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
@@ -715,6 +724,7 @@ NODE_CLASS_MAPPINGS.update(KONTEXT_PRESETS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(PROMPT_HELPER_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(COLOR_TO_MASK_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(LAZY_SWITCH_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(TEXT_TRANSLATOR_MAPPINGS)
|
||||
|
||||
# 合并显示名称映射
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
@@ -753,6 +763,7 @@ NODE_DISPLAY_NAME_MAPPINGS.update(KONTEXT_PRESETS_DISPLAY_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(PROMPT_HELPER_DISPLAY_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(COLOR_TO_MASK_DISPLAY)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(LAZY_SWITCH_DISPLAY)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(TEXT_TRANSLATOR_DISPLAY)
|
||||
|
||||
NODE_CATEGORIES = {
|
||||
"UniversalToolkit": [
|
||||
@@ -792,6 +803,7 @@ NODE_CATEGORIES = {
|
||||
"ColorToMask_UTK",
|
||||
"LazySwitchKJ_UTK",
|
||||
"APIImageGenerator_UTK",
|
||||
"TextTranslator_UTK",
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
"""
|
||||
Text Translator Node
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A comprehensive text translation node supporting multiple free and paid translation APIs.
|
||||
|
||||
:copyright: (c) 2024 by May
|
||||
:license: MIT, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import json
|
||||
import requests
|
||||
import time
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
import hashlib
|
||||
import random
|
||||
|
||||
|
||||
class TranslationProvider:
|
||||
"""Base class for translation providers"""
|
||||
|
||||
def __init__(self, name: str, is_free: bool, requires_key: bool = False):
|
||||
self.name = name
|
||||
self.is_free = is_free
|
||||
self.requires_key = requires_key
|
||||
self.priority = 0 # Lower number = higher priority
|
||||
|
||||
def translate(self, text: str, target_lang: str, source_lang: str = "auto", api_key: str = None) -> Tuple[bool, str]:
|
||||
"""Translate text. Returns (success, result)"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class GoogleTranslateProvider(TranslationProvider):
|
||||
"""Google Translate (Free) - Using web interface"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Google Translate (Free)", True, False)
|
||||
self.priority = 1
|
||||
self.base_url = "https://translate.googleapis.com/translate_a/single"
|
||||
|
||||
def translate(self, text: str, target_lang: str, source_lang: str = "auto", api_key: str = None) -> Tuple[bool, str]:
|
||||
try:
|
||||
params = {
|
||||
'client': 'gtx',
|
||||
'sl': source_lang,
|
||||
'tl': target_lang,
|
||||
'dt': 't',
|
||||
'q': text
|
||||
}
|
||||
|
||||
response = requests.get(self.base_url, params=params, timeout=10)
|
||||
response.raise_for_status()
|
||||
|
||||
result = response.json()
|
||||
if result and len(result) > 0 and result[0]:
|
||||
translated_text = ''.join([item[0] for item in result[0] if item[0]])
|
||||
return True, translated_text
|
||||
return False, "Translation failed"
|
||||
|
||||
except Exception as e:
|
||||
return False, f"Google Translate error: {str(e)}"
|
||||
|
||||
|
||||
class LibreTranslateProvider(TranslationProvider):
|
||||
"""LibreTranslate (Free) - Open source translation"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("LibreTranslate (Free)", True, False)
|
||||
self.priority = 2
|
||||
self.base_url = "https://libretranslate.de/translate"
|
||||
|
||||
def translate(self, text: str, target_lang: str, source_lang: str = "auto", api_key: str = None) -> Tuple[bool, str]:
|
||||
try:
|
||||
data = {
|
||||
'q': text,
|
||||
'source': source_lang if source_lang != "auto" else "en",
|
||||
'target': target_lang,
|
||||
'format': 'text'
|
||||
}
|
||||
|
||||
response = requests.post(self.base_url, data=data, timeout=10)
|
||||
response.raise_for_status()
|
||||
|
||||
result = response.json()
|
||||
if 'translatedText' in result:
|
||||
return True, result['translatedText']
|
||||
return False, "Translation failed"
|
||||
|
||||
except Exception as e:
|
||||
return False, f"LibreTranslate error: {str(e)}"
|
||||
|
||||
|
||||
class MyMemoryProvider(TranslationProvider):
|
||||
"""MyMemory (Free) - Community translation"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("MyMemory (Free)", True, False)
|
||||
self.priority = 3
|
||||
self.base_url = "https://api.mymemory.translated.net/get"
|
||||
|
||||
def translate(self, text: str, target_lang: str, source_lang: str = "auto", api_key: str = None) -> Tuple[bool, str]:
|
||||
try:
|
||||
params = {
|
||||
'q': text,
|
||||
'langpair': f"{source_lang}|{target_lang}" if source_lang != "auto" else f"auto|{target_lang}"
|
||||
}
|
||||
|
||||
response = requests.get(self.base_url, params=params, timeout=10)
|
||||
response.raise_for_status()
|
||||
|
||||
result = response.json()
|
||||
if result.get('responseStatus') == 200 and 'responseData' in result:
|
||||
return True, result['responseData']['translatedText']
|
||||
return False, "Translation failed"
|
||||
|
||||
except Exception as e:
|
||||
return False, f"MyMemory error: {str(e)}"
|
||||
|
||||
|
||||
class DeepLProvider(TranslationProvider):
|
||||
"""DeepL (Paid) - High quality translation"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("DeepL (Paid)", False, True)
|
||||
self.priority = 4
|
||||
self.base_url = "https://api-free.deepl.com/v2/translate"
|
||||
|
||||
def translate(self, text: str, target_lang: str, source_lang: str = "auto", api_key: str = None) -> Tuple[bool, str]:
|
||||
if not api_key:
|
||||
return False, "DeepL requires API key"
|
||||
|
||||
try:
|
||||
data = {
|
||||
'auth_key': api_key,
|
||||
'text': text,
|
||||
'target_lang': target_lang.upper()
|
||||
}
|
||||
if source_lang != "auto":
|
||||
data['source_lang'] = source_lang.upper()
|
||||
|
||||
response = requests.post(self.base_url, data=data, timeout=10)
|
||||
response.raise_for_status()
|
||||
|
||||
result = response.json()
|
||||
if 'translations' in result and len(result['translations']) > 0:
|
||||
return True, result['translations'][0]['text']
|
||||
return False, "Translation failed"
|
||||
|
||||
except Exception as e:
|
||||
return False, f"DeepL error: {str(e)}"
|
||||
|
||||
|
||||
class AzureTranslatorProvider(TranslationProvider):
|
||||
"""Azure Translator (Paid) - Microsoft translation service"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Azure Translator (Paid)", False, True)
|
||||
self.priority = 5
|
||||
self.base_url = "https://api.cognitive.microsofttranslator.com/translate"
|
||||
|
||||
def translate(self, text: str, target_lang: str, source_lang: str = "auto", api_key: str = None) -> Tuple[bool, str]:
|
||||
if not api_key:
|
||||
return False, "Azure Translator requires API key"
|
||||
|
||||
try:
|
||||
headers = {
|
||||
'Ocp-Apim-Subscription-Key': api_key,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
params = {
|
||||
'api-version': '3.0',
|
||||
'to': target_lang
|
||||
}
|
||||
if source_lang != "auto":
|
||||
params['from'] = source_lang
|
||||
|
||||
body = [{'text': text}]
|
||||
|
||||
response = requests.post(self.base_url, params=params, headers=headers, json=body, timeout=10)
|
||||
response.raise_for_status()
|
||||
|
||||
result = response.json()
|
||||
if result and len(result) > 0 and 'translations' in result[0]:
|
||||
return True, result[0]['translations'][0]['text']
|
||||
return False, "Translation failed"
|
||||
|
||||
except Exception as e:
|
||||
return False, f"Azure Translator error: {str(e)}"
|
||||
|
||||
|
||||
class TextTranslator_UTK:
|
||||
CATEGORY = "UniversalToolkit/Tools"
|
||||
|
||||
def __init__(self):
|
||||
self.providers = {
|
||||
"Google Translate (Free)": GoogleTranslateProvider(),
|
||||
"LibreTranslate (Free)": LibreTranslateProvider(),
|
||||
"MyMemory (Free)": MyMemoryProvider(),
|
||||
"DeepL (Paid)": DeepLProvider(),
|
||||
"Azure Translator (Paid)": AzureTranslatorProvider(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
# Language codes for common languages
|
||||
languages = [
|
||||
"auto", "en", "zh", "zh-cn", "zh-tw", "ja", "ko", "fr", "de", "es", "it",
|
||||
"pt", "ru", "ar", "hi", "th", "vi", "tr", "pl", "nl", "sv", "da", "no",
|
||||
"fi", "cs", "hu", "ro", "bg", "hr", "sk", "sl", "et", "lv", "lt", "el",
|
||||
"he", "fa", "ur", "bn", "ta", "te", "ml", "kn", "gu", "pa", "or", "as",
|
||||
"ne", "si", "my", "km", "lo", "ka", "am", "sw", "zu", "af", "sq", "eu",
|
||||
"be", "bs", "ca", "cy", "eo", "gl", "is", "mk", "mt", "sr", "uk", "uz"
|
||||
]
|
||||
|
||||
# Provider list with free/paid indicators
|
||||
provider_list = [
|
||||
"auto",
|
||||
"Google Translate (Free)",
|
||||
"LibreTranslate (Free)",
|
||||
"MyMemory (Free)",
|
||||
"DeepL (Paid)",
|
||||
"Azure Translator (Paid)"
|
||||
]
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {
|
||||
"multiline": True,
|
||||
"default": "Hello, world!",
|
||||
"placeholder": "Enter text to translate..."
|
||||
}),
|
||||
"target_language": (languages, {"default": "zh"}),
|
||||
"source_language": (languages, {"default": "auto"}),
|
||||
"provider": (provider_list, {"default": "auto"}),
|
||||
},
|
||||
"optional": {
|
||||
"api_key": ("STRING", {
|
||||
"default": "",
|
||||
"placeholder": "API key (required for paid services)"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING", "STRING")
|
||||
RETURN_NAMES = ("translated_text", "provider_used", "status_message")
|
||||
FUNCTION = "translate_text"
|
||||
|
||||
def translate_text(self, text: str, target_language: str, source_language: str, provider: str, api_key: str = ""):
|
||||
"""Main translation function"""
|
||||
|
||||
if not text.strip():
|
||||
return ("", "", "Error: No text provided")
|
||||
|
||||
# Clean API key
|
||||
api_key = api_key.strip() if api_key else ""
|
||||
|
||||
if provider == "auto":
|
||||
# Try providers in priority order
|
||||
sorted_providers = sorted(self.providers.values(), key=lambda x: x.priority)
|
||||
|
||||
for provider_obj in sorted_providers:
|
||||
# Check if API key is required but not provided
|
||||
if provider_obj.requires_key and not api_key:
|
||||
continue
|
||||
|
||||
success, result = provider_obj.translate(text, target_language, source_language, api_key)
|
||||
|
||||
if success:
|
||||
return (result, provider_obj.name, f"Successfully translated using {provider_obj.name}")
|
||||
else:
|
||||
# Log the error but continue to next provider
|
||||
print(f"Translation failed with {provider_obj.name}: {result}")
|
||||
continue
|
||||
|
||||
return ("", "", "Error: All translation providers failed")
|
||||
|
||||
else:
|
||||
# Use specific provider
|
||||
if provider not in self.providers:
|
||||
return ("", "", f"Error: Unknown provider '{provider}'")
|
||||
|
||||
provider_obj = self.providers[provider]
|
||||
|
||||
# Check API key requirement
|
||||
if provider_obj.requires_key and not api_key:
|
||||
return ("", "", f"Error: {provider} requires an API key")
|
||||
|
||||
success, result = provider_obj.translate(text, target_language, source_language, api_key)
|
||||
|
||||
if success:
|
||||
return (result, provider_obj.name, f"Successfully translated using {provider_obj.name}")
|
||||
else:
|
||||
return ("", "", f"Error: {result}")
|
||||
|
||||
|
||||
# Node mappings
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"TextTranslator_UTK": TextTranslator_UTK,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"TextTranslator_UTK": "Text Translator (UTK)",
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "universaltoolkit"
|
||||
description = "A comprehensive toolkit based on ComfyUI, providing image, mask, audio, and tools nodes, fully modular and v3 compatible."
|
||||
version = "1.3.8"
|
||||
version = "1.3.9"
|
||||
license = {file = "LICENSE"}
|
||||
dependencies = [
|
||||
"torch>=1.9.0",
|
||||
|
||||
Reference in New Issue
Block a user