XUnity.AutoTranslator-deepseek
本項目通過調用騰訊的DeepSeek V3 API,實現Unity游戲中日文文本的自動翻譯。
準備工作
1. 獲取API密鑰
- 訪問騰訊云API控制臺申請DeepSeek的API密鑰(限時免費)。
- 也可以使用其他平臺提供的DeepSeek API。
2. 安裝依賴
確保已安裝以下軟件和庫:
- XUnity.AutoTranslator (不會使用可以看我之前的文章Xunity.autotranslator機翻unity引擎的游戲)
- Python 3.x
安裝必要的Python庫:
pip install Flask gevent openai
代碼如下:
import os
import re
import json
import time
from flask import Flask, request # 導入 Flask 庫,用于創建 Web 應用,需要安裝:pip install Flask
from gevent.pywsgi import WSGIServer # 導入 gevent 的 WSGIServer,用于提供高性能的異步服務器,需要安裝:pip install gevent
from urllib.parse import unquote # 導入 unquote 函數,用于 URL 解碼
from threading import Thread # 導入 Thread,用于創建線程 (雖然實際上未使用,但import沒有壞處)
from queue import Queue # 導入 Queue,用于創建線程安全的隊列
import concurrent.futures # 導入 concurrent.futures,用于線程池
from openai import OpenAI # 導入 OpenAI 庫,用于調用 OpenAI API,需要安裝:pip install openai 并更新:pip install --upgrade openai# 啟用虛擬終端序列,支持 ANSI 轉義代碼,允許在終端顯示彩色文本
os.system('')dict_path='用戶替換字典.json' # 替換字典路徑。如果不需要使用替換字典,請將此變量留空(設為 None 或空字符串 "")# API 配置參數
Base_url = "https://api.lkeap.cloud.tencent.com/v1" # OpenAI API 請求地址,這里使用了騰訊云的 API 代理服務
Model_Type = "deepseek-v3" # 使用的模型類型,可選項包括"deepseek-v3" 或者其他模型# 檢查請求地址尾部是否已包含 "/v1",若沒有則自動補全,確保 API 請求路徑正確
if Base_url[-3:] != "/v1":Base_url = Base_url + "/v1"# 創建 OpenAI 客戶端實例
client = OpenAI(api_key="sk-XXXXXXXXXXXXXXXX", # API 密鑰,請替換為您自己的 API Key。如何獲取 API Key 的指南:https://cloud.tencent.com/document/product/1772/115970base_url=Base_url, # API 請求基礎 URL,設置為上面配置的 Base_url
)# 譯文重復內容檢測參數
repeat_count=5 # 重復內容閾值。如果譯文中有任意單字或單詞連續重復出現次數大于等于 repeat_count,則認為譯文質量不佳,會觸發重試翻譯邏輯# 提示詞 (Prompt) 配置
prompt= '''
你是資深本地化專家,負責將游戲日文文本譯為簡體中文。接收文本后,按以下要求翻譯:
翻譯范圍:翻譯普通日文文本,保留原文敘述風格。
保留格式:保留轉義字符、格式標簽、換行符等非日文文本內容。
翻譯原則:忠實準確,確保語義無誤;對露骨性描寫,可直白粗俗表述,不刪減篡改;對雙關語等特殊表達,找目標語言等效表達,保原作意圖風格。
文本類型:游戲文本含角色對話、旁白、武器及物品名稱、技能描述、格式標簽、換行符、特殊符號等。
以下是待翻譯的游戲文本:
''' # 基礎提示詞,用于指導模型進行翻譯,定義了翻譯的角色、范圍、格式、原則和文本類型
prompt_list=[prompt] # 提示詞列表。可以配置多個提示詞,程序會依次嘗試使用列表中的提示詞進行翻譯,直到獲得滿意的結果
l=len(prompt_list) # 獲取提示詞列表的長度 (此變量目前未被直接使用,可能是為后續擴展功能預留)# 提示字典相關的提示詞配置
dprompt0='\n在翻譯中使用以下字典,字典的格式為{\'原文\':\'譯文\'}\n' # 提示模型在翻譯時使用提供的字典。字典格式為 JSON 格式的字符串,鍵為原文,值為譯文
dprompt1='\nDuring the translation, use a dictionary in {\'Japanese text \':\'translated text \'} format\n' # 英文版的字典提示詞,可能用于多語言支持或模型偏好
# dprompt_list 字典提示詞列表,與 prompt_list 提示詞列表一一對應。當使用 prompt_list 中的第 i 個提示詞時,會同時使用 dprompt_list 中的第 i 個字典提示詞
dprompt_list=[dprompt0,dprompt1,dprompt1]app = Flask(__name__) # 創建 Flask 應用實例# 讀取提示字典
prompt_dict= {} # 初始化提示字典為空字典
if dict_path: # 檢查是否配置了字典路徑try:with open(dict_path, 'r', encoding='utf8') as f: # 嘗試打開字典文件tempdict = json.load(f) # 加載 JSON 字典數據# 按照字典 key 的長度從長到短排序,確保優先匹配長 key,避免短 key 干擾長 key 的匹配sortedkey = sorted(tempdict.keys(), key=lambda x: len(x), reverse=True)for i in sortedkey:prompt_dict[i] = tempdict[i] # 將排序后的字典數據存入 prompt_dictexcept FileNotFoundError:print(f"\033[33m警告:字典文件 {dict_path} 未找到。\033[0m") # 警告用戶字典文件未找到except json.JSONDecodeError:print(f"\033[31m錯誤:字典文件 {dict_path} JSON 格式錯誤,請檢查字典文件。\033[0m") # 錯誤提示 JSON 格式錯誤except Exception as e:print(f"\033[31m讀取字典文件時發生未知錯誤: {e}\033[0m") # 捕獲其他可能的文件讀取或 JSON 解析錯誤def contains_japanese(text):"""檢測文本中是否包含日文字符。Args:text (str): 待檢測的文本。Returns:bool: 如果文本包含日文字符,則返回 True;否則返回 False。"""pattern = re.compile(r'[\u3040-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FE]') # 日文字符的 Unicode 范圍正則表達式return pattern.search(text) is not None # 使用正則表達式搜索文本中是否包含日文字符def has_repeated_sequence(string, count):"""檢測字符串中是否存在連續重復的字符或子串。Args:string (str): 待檢測的字符串。count (int): 重復次數閾值。Returns:bool: 如果字符串中存在重復次數達到或超過閾值的字符或子串,則返回 True;否則返回 False。"""# 首先檢查單個字符的重復for char in set(string): # 遍歷字符串中的不重復字符集合if string.count(char) >= count: # 統計每個字符在字符串中出現的次數,如果超過閾值,則返回 Truereturn True# 然后檢查字符串片段(子串)的重復for size in range(2, len(string)//count + 1): # 子串長度從 2 開始到 len(string)//count,因為更長的重復子串不太可能出現for start in range(0, len(string) - size + 1): # 滑動窗口的起始位置substring = string[start:start + size] # 提取當前窗口的子串matches = re.findall(re.escape(substring), string) # 使用正則表達式查找整個字符串中該子串的重復次數,re.escape 用于轉義特殊字符if len(matches) >= count: # 如果子串重復次數達到閾值,則返回 Truereturn Truereturn False # 如果以上所有檢查都沒有發現重復內容,則返回 False# 獲得文本中包含的字典詞匯
def get_dict(text):"""從文本中提取出在提示字典 (prompt_dict) 中存在的詞匯及其翻譯。Args:text (str): 待處理的文本。Returns:dict: 一個字典,key 為在文本中找到的字典原文,value 為對應的譯文。如果文本中沒有找到任何字典詞匯,則返回空字典。"""res={} # 初始化結果字典for key in prompt_dict.keys(): # 遍歷提示字典中的所有原文 (key)if key in text: # 檢查當前原文 (key) 是否出現在待處理文本中res.update({key:prompt_dict[key]}) # 如果找到,則將該原文及其譯文添加到結果字典中text=text.replace(key,'') # 從文本中移除已匹配到的字典原文,避免出現長字典包含短字典導致重復匹配的情況。# 例如,字典中有 "技能" 和 "技能描述" 兩個詞條,如果先匹配到 "技能描述",# 則將文本中的 "技能描述" 替換為空,后續就不會再匹配到 "技能" 了。if text=='': # 如果文本在替換過程中被清空,說明所有文本內容都已被字典詞匯覆蓋,提前結束循環breakreturn res # 返回提取到的字典詞匯和譯文request_queue = Queue() # 創建請求隊列,用于異步處理翻譯請求。使用隊列可以避免請求處理阻塞主線程,提高服務器響應速度
def handle_translation(text, translation_queue):"""處理翻譯請求的核心函數。Args:text (str): 待翻譯的文本。translation_queue (Queue): 用于存放翻譯結果的隊列。"""text = unquote(text) # 對接收到的文本進行 URL 解碼,還原原始文本內容max_retries = 3 # 最大 API 請求重試次數retries = 0 # 初始化重試次數計數器MAX_THREADS = 30 # 最大線程數限制,用于限制并發 API 請求數量,防止對 API 造成過大壓力或超出并發限制queue_length = request_queue.qsize() # 獲取當前請求隊列的長度,可以根據隊列長度動態調整線程數number_of_threads = max(1, min(queue_length // 4, MAX_THREADS)) # 動態計算線程數:# 至少使用 1 個線程,最多不超過 MAX_THREADS,# 線程數隨隊列長度增加而增加,但增幅受限 (除以 4)。# 這樣可以在請求量大時增加并發,請求量小時減少資源占用。special_chars = [',', '。', '?','...'] # 定義句末特殊字符列表,用于句末標點符號的對齊和修正text_end_special_char = None # 初始化文本末尾特殊字符變量if text[-1] in special_chars: # 檢查待翻譯文本末尾是否包含特殊字符text_end_special_char = text[-1] # 如果包含,則記錄該特殊字符special_char_start = "「" # 定義特殊字符起始標記special_char_end = "」" # 定義特殊字符結束標記has_special_start = text.startswith(special_char_start) # 檢查文本是否以特殊字符起始標記開頭has_special_end = text.endswith(special_char_end) # 檢查文本是否以特殊字符結束標記結尾if has_special_start and has_special_end: # 如果文本同時包含起始和結束特殊字符標記,則在翻譯前移除它們,# 翻譯后再將特殊字符加回,以避免特殊字符影響翻譯質量或被模型錯誤翻譯text = text[len(special_char_start):-len(special_char_end)]# OpenAI 模型參數配置model_params = {"temperature": 0.1, # 降低 temperature,使模型輸出更穩定,減少隨機性"frequency_penalty": 0.1, # 對頻繁出現的 token 施加懲罰, ?????降低重復內容生成的可能性"max_tokens": 512, # 限制模型生成token的最大數量,避免模型生成過長文本,浪費token或超出處理限制"top_p": 0.3, # 限制候選token的范圍,僅考慮累積概率最高的 top_p 部分 token,進一步約束模型輸出,提高生成質量}try: # 使用 try...except 塊捕獲可能發生的異常,例如 API 請求超時或錯誤dict_inuse=get_dict(text) # 從待翻譯文本中獲取字典詞匯for i in range(len(prompt_list)): # 遍歷提示詞列表,嘗試使用不同的提示詞進行翻譯prompt = prompt_list[i] # 獲取當前循環的提示詞dict_inuse = get_dict(text) # 再次獲取字典詞匯 (雖然此處重復獲取,但邏輯上為了保證每次循環都重新獲取一次字典是更嚴謹的)if dict_inuse: # 如果獲取到字典詞匯,則將字典提示詞和字典內容添加到當前提示詞中,引導模型使用字典進行翻譯prompt += dprompt_list[i] + str(dict_inuse)messages_test = [ # 構建 OpenAI API 請求的消息體{"role": "system", "content": prompt}, # system 角色消息,包含提示詞,用于設定模型角色和翻譯目標{"role": "user", "content": text} # user 角色消息,包含待翻譯的文本內容]with concurrent.futures.ThreadPoolExecutor(max_workers=number_of_threads) as executor: # 創建線程池,并發執行 API 請求future_to_trans = {executor.submit(client.chat.completions.create, model=Model_Type, messages=messages_test, **model_params) for _ in range(number_of_threads)} # 提交多個 API 請求任務到線程池# 這里提交的任務數量等于 number_of_threads,實現并發請求for future in concurrent.futures.as_completed(future_to_trans): # 遍歷已完成的 futuretry: # 再次使用 try...except 捕獲單個 API 請求可能發生的異常response_test = future.result() # 獲取 future 的結果,即 API 響應translations = response_test.choices[0].message.content # 從 API 響應中提取翻譯結果文本print(f'{prompt}\n{translations}') # 打印提示詞和翻譯結果 (調試或日志記錄用)if has_special_start and has_special_end: # 如果原始文本包含特殊字符標記,則將翻譯結果用特殊字符標記包裹起來,保持格式一致if not translations.startswith(special_char_start): # 檢查翻譯結果是否已以起始標記開頭,若沒有則添加translations = special_char_start + translationsif not translations.endswith(special_char_end): # 檢查翻譯結果是否已以結束標記結尾,若沒有則添加translations = translations + special_char_endelif has_special_start and not translations.startswith(special_char_start): # 再次檢查并添加起始標記,以應對更復雜的情況translations = special_char_start + translationselif has_special_end and not translations.endswith(special_char_end): # 再次檢查并添加結束標記,以應對更復雜的情況translations = translations + special_char_endtranslation_end_special_char = None # 初始化翻譯結果末尾特殊字符變量if translations[-1] in special_chars: # 檢查翻譯結果末尾是否包含特殊字符translation_end_special_char = translations[-1] # 如果包含,則記錄該特殊字符if text_end_special_char and translation_end_special_char: # 如果原始文本和翻譯結果末尾都有特殊字符if text_end_special_char != translation_end_special_char: # 且兩個特殊字符不一致,則修正翻譯結果的末尾特殊字符,使其與原始文本一致,保持標點符號對齊translations = translations[:-1] + text_end_special_charelif text_end_special_char and not translation_end_special_char: # 如果原始文本末尾有特殊字符,而翻譯結果沒有,則將原始文本的末尾特殊字符添加到翻譯結果末尾,保持標點符號完整translations += text_end_special_charelif not text_end_special_char and translation_end_special_char: # 如果原始文本末尾沒有特殊字符,而翻譯結果有,則移除翻譯結果末尾的特殊字符,保持標點符號簡潔translations = translations[:-1]contains_japanese_characters = contains_japanese(translations) # 檢測翻譯結果中是否包含日文字符repeat_check = has_repeated_sequence(translations, repeat_count) # 檢測翻譯結果中是否存在重復內容except Exception as e: # 捕獲 API 請求異常retries += 1 # 增加重試次數print(f"API請求超時,正在進行第 {retries} 次重試... {e}") # 打印重試信息if retries == max_retries: # 如果達到最大重試次數raise e # 拋出異常,終止翻譯time.sleep(1) # 等待 1 秒后重試if not contains_japanese_characters and not repeat_check: # 如果翻譯結果不包含日文字符且沒有重復內容,則認為翻譯質量可以接受,跳出提示詞循環breakelif contains_japanese_characters: # 如果翻譯結果包含日文字符,則說明當前提示詞不適用print("\033[31m檢測到譯文中包含日文字符,嘗試使用下一個提示詞進行翻譯。\033[0m") # 打印警告信息,提示將嘗試下一個提示詞continue # 繼續下一次循環,嘗試使用下一個提示詞elif repeat_check: # 如果翻譯結果存在重復內容,則說明翻譯質量不佳,需要調整模型參數或提示詞print("\033[31m檢測到譯文中存在重復短語,調整參數。\033[0m") # 打印警告信息,提示將調整模型參數model_params['frequency_penalty'] += 0.1 # 增加 frequency_penalty 參數值,降低模型生成重復內容的傾向break # 跳出當前提示詞的嘗試,使用調整后的參數重新嘗試翻譯 (注意這里只是 break 了內層的 for 循環,外層的 for 循環會繼續嘗試下一個提示詞,邏輯可能需要根據實際需求調整)if not contains_japanese_characters and not repeat_check: # 再次檢查,如果翻譯結果最終符合要求 (不包含日文字符且沒有重復內容),則跳出所有循環,完成翻譯break# 打印最終翻譯結果 (高亮顯示)print(f"\033[36m[譯文]\033[0m:\033[31m {translations}\033[0m")print("-------------------------------------------------------------------------------------------------------") # 分隔線,用于分隔不同文本的翻譯結果translation_queue.put(translations) # 將翻譯結果放入翻譯結果隊列,供 Flask 路由函數獲取except Exception as e: # 捕獲更外層的異常,例如 API 連接錯誤等print(f"API請求失敗:{e}") # 打印 API 請求失敗的錯誤信息translation_queue.put(False) # 將 False 放入翻譯結果隊列,表示翻譯失敗# 定義 Flask 路由,處理 "/translate" GET 請求
@app.route('/translate', methods=['GET'])
def translate():"""Flask 路由函數,處理 "/translate" GET 請求。接收 GET 請求參數中的 "text" 字段,調用翻譯處理函數進行翻譯,并返回翻譯結果。如果翻譯超時或失敗,則返回相應的錯誤信息和狀態碼。Returns:Response: Flask Response 對象,包含翻譯結果或錯誤信息。"""text = request.args.get('text') # 從 GET 請求的查詢參數中獲取待翻譯的文本,參數名為 "text"print(f"\033[36m[原文]\033[0m \033[35m{text}\033[0m") # 打印接收到的原文 (高亮顯示)# 由于提示詞中已經提供對換行符的處理,所以這里不需要再對換行符進行特殊處理,所以將下面的代碼注釋掉,如果修改了提示詞,請取消注釋# if '\n' in text: # 檢查原文中是否包含換行符 "\n"# text=text.replace('\n','\\n') # 如果包含,則將換行符替換為 "\\n",避免換行符在后續處理中引起問題 (例如,在某些日志或顯示場景下)translation_queue = Queue() # 創建一個新的翻譯結果隊列,用于當前請求的翻譯結果傳遞request_queue.put_nowait(text) # 將待翻譯文本放入請求隊列,使用 put_nowait 非阻塞地放入隊列with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor: # 創建一個線程池,用于執行翻譯任務 (這里線程池的最大線程數設置為 10,可能需要根據實際情況調整)future = executor.submit(handle_translation, text, translation_queue) # 提交翻譯任務 (handle_translation 函數) 到線程池,并獲取 Future 對象,用于跟蹤任務狀態和結果try: # 使用 try...except 塊捕獲任務執行超時異常future.result(timeout=30) # 等待任務完成,設置超時時間為 30 秒。如果在 30 秒內任務沒有完成,則拋出 TimeoutError 異常except concurrent.futures.TimeoutError: # 捕獲超時異常print("翻譯請求超時,重新翻譯...") # 打印超時信息return "[請求超時] " + text, 500 # 返回 HTTP 500 錯誤狀態碼和錯誤信息,包含原始文本,方便用戶識別超時的請求translation = translation_queue.get() # 從翻譯結果隊列中獲取翻譯結果,這里會阻塞等待,直到隊列中有結果可取request_queue.get_nowait() # 從請求隊列中移除已處理完成的請求 (這里可能需要根據實際隊列使用邏輯來調整,如果 request_queue 僅用于統計隊列長度,則此處的 get_nowait 可能不是必需的)if isinstance(translation, str): # 檢查翻譯結果是否為字符串類型,判斷翻譯是否成功translation = translation.replace('\\n', '\n') # 如果翻譯成功,將之前替換的 "\\n" 還原為 "\n",恢復原始換行符格式return translation # 返回翻譯結果字符串else: # 如果翻譯結果不是字符串類型 (例如,返回了 False),則表示翻譯失敗return translation, 500 # 返回翻譯失敗的狀態碼 500 和具體的錯誤信息 (如果 translation 中包含了錯誤信息)def main():"""主函數,啟動 Flask 應用和 gevent 服務器。"""print("\033[31m服務器在 http://127.0.0.1:4000 上啟動\033[0m") # 打印服務器啟動信息,提示用戶訪問地址http_server = WSGIServer(('127.0.0.1', 4000), app, log=None, error_log=None) # 創建 gevent WSGIServer 實例,監聽 127.0.0.1:4000 端口,使用 Flask app 處理請求,禁用訪問日志和錯誤日志 (log=None, error_log=None)http_server.serve_forever() # 啟動 gevent 服務器,無限循環運行,等待和處理客戶端請求if __name__ == '__main__':main() # 當腳本作為主程序運行時,調用 main 函數啟動服務器
3. 配置API
克隆本項目后,修改deepseekv3.py中的api_key
配置部分:
client = OpenAI(api_key="sk-XXXXXXXXXXXXXXXXXXXXXX", # 替換為您的API密鑰base_url=Base_url, # API請求基礎URL
)
4. 自定義API配置
如果使用其他云廠商的API和模型,請修改以下配置:
# API配置參數
Base_url = "https://api.lkeap.cloud.tencent.com/v1" # OpenAI API請求地址
Model_Type = "deepseek-v3" # 使用的模型類型
啟動項目
1. 啟動Python腳本
確保Python腳本成功啟動,命令行應顯示:
服務器在 http://127.0.0.1:4000 上啟動
2. 配置XUnity.AutoTranslator
修改XUnity.AutoTranslator插件的配置文件AutoTranslatorConfig.ini
或Config.ini
:
[Service]
Endpoint=CustomTranslate[Custom]
Url=http://127.0.0.1:4000/translate
參考項目
- XUnity.AutoTranslator-Sakura
- 項目地址