33import re
44import json
55import logging
6+ from core .engines import wolf as wolf_engine
67from core .utils import file_system , text_processing # 需要文件名清理
8+ from core .utils .engine_detection import detect_game_engine
79
810log = logging .getLogger (__name__ )
911
@@ -28,6 +30,29 @@ def _is_multiline_block_marker(marker):
2830 return marker in MULTILINE_BLOCK_MARKERS or marker .startswith ("PluginCommand_" )
2931
3032
33+ def _store_extracted_string (
34+ strings , original , translated , marker , speaker_id ,
35+ wolf_code = None , wolf_export_schema = None ,
36+ ):
37+ metadata = strings .get (original )
38+ if metadata is None :
39+ metadata = {
40+ "text_to_translate" : translated ,
41+ "original_marker" : marker ,
42+ "speaker_id" : speaker_id ,
43+ }
44+ strings [original ] = metadata
45+ if wolf_code :
46+ codes = metadata .setdefault ("wolf_codes" , [])
47+ if wolf_code not in codes :
48+ codes .append (wolf_code )
49+ if wolf_export_schema != wolf_engine .WOLF_EXPORT_SCHEMA :
50+ raise ValueError ("WOLF 导出版本无效,请重新导出翻译文本" )
51+ previous_schema = metadata .setdefault ("wolf_export_schema" , wolf_export_schema )
52+ if previous_schema != wolf_export_schema :
53+ raise ValueError ("同一 WOLF 文本混入了不同版本的导出数据" )
54+
55+
3156def _parse_face_graphic_command_details (command_details_str ):
3257 """
3358 解析 "Select Face Graphic:" 指令的冒号之后的内容。
@@ -73,7 +98,7 @@ def _parse_face_graphic_command_details(command_details_str):
7398 return None
7499
75100
76- def _extract_strings_from_file (file_path ):
101+ def _extract_strings_from_file (file_path , require_wolf_metadata = False ):
77102 """
78103 从单个 StringScripts txt 文件中提取需要翻译的字符串及其元数据。
79104 (此函数逻辑基本保持你提供的已修复版本)
@@ -96,6 +121,10 @@ def _extract_strings_from_file(file_path):
96121 current_speaker_id = DEFAULT_SPEAKER_ID
97122 current_line_number_for_log = 0
98123 current_page_for_log = "Page_Unknown_Init"
124+ pending_wolf_code = None
125+ pending_wolf_export_schema = None
126+ pending_wolf_marker = None
127+ saw_wolf_metadata = False
99128
100129 log .debug (f"开始解析文件: '{ os .path .basename (file_path )} ', 初始 Speaker ID: '{ current_speaker_id } '" )
101130
@@ -109,6 +138,24 @@ def _extract_strings_from_file(file_path):
109138 line_content = lines [i ]
110139 line_content_stripped = line_content .strip ()
111140
141+ if line_content .startswith ("@WOLF " ):
142+ encoded = line_content [6 :].strip ()
143+ try :
144+ metadata = wolf_engine ._decode_metadata (encoded )
145+ if metadata .get ("wolf_export_schema" ) != wolf_engine .WOLF_EXPORT_SCHEMA :
146+ raise ValueError ("WOLF StringScripts 版本已过期,请重新导出翻译文本" )
147+ expected_code = wolf_engine ._translation_code (metadata )
148+ if metadata .get ("wolf_code" ) != expected_code :
149+ raise ValueError ("WOLF StringScripts 结构 Code 无效,请重新导出翻译文本" )
150+ pending_wolf_code = expected_code
151+ pending_wolf_export_schema = metadata ["wolf_export_schema" ]
152+ pending_wolf_marker = metadata .get ("marker" , "Message" )
153+ saw_wolf_metadata = True
154+ except (ValueError , TypeError , json .JSONDecodeError ) as error :
155+ raise ValueError (f"无效的 WOLF 条目元数据: { error } " ) from error
156+ i += 1
157+ continue
158+
112159 # 1. 检查是否是Page分隔符
113160 page_match = RE_PAGE_SEPARATOR .match (line_content_stripped )
114161 if page_match :
@@ -146,6 +193,20 @@ def _extract_strings_from_file(file_path):
146193 marker_match = RE_MARKER_LINE .match (line_content_stripped )
147194 if marker_match :
148195 original_marker = marker_match .group (1 )
196+ if require_wolf_metadata and pending_wolf_code is None :
197+ raise ValueError (
198+ "WOLF StringScripts 缺少当前版本元数据,请重新导出翻译文本"
199+ )
200+ if pending_wolf_code and original_marker != pending_wolf_marker :
201+ raise ValueError (
202+ f"WOLF 文本标记与元数据不一致: "
203+ f"{ original_marker !r} != { pending_wolf_marker !r} "
204+ )
205+ wolf_code = pending_wolf_code
206+ wolf_export_schema = pending_wolf_export_schema
207+ pending_wolf_code = None
208+ pending_wolf_export_schema = None
209+ pending_wolf_marker = None
149210 # 对话、图文、滚动文本与插件指令参数按完整块处理;其他标记按单行处理。
150211 speaker_id_for_this_entry = current_speaker_id if original_marker == 'Message' else SYSTEM_TEXT_SPEAKER_ID # Choice 文本也可能需要发言人ID,但RPG Maker 2000/2003的Choice通常不直接关联脸图,所以默认为SYSTEM
151212 log .debug (f" [L{ current_line_number_for_log } , { current_page_for_log } ] 处理标记 '#{ original_marker } #'. 使用 Speaker ID: '{ speaker_id_for_this_entry } ' (基于 current_speaker_id='{ current_speaker_id } ')." )
@@ -162,11 +223,15 @@ def _extract_strings_from_file(file_path):
162223
163224 if message_key_as_original :
164225 text_to_translate_val = text_processing .convert_half_to_full_katakana (message_key_as_original )
165- strings_with_metadata [message_key_as_original ] = {
166- "text_to_translate" : text_to_translate_val ,
167- "original_marker" : original_marker ,
168- "speaker_id" : speaker_id_for_this_entry if original_marker == 'Message' else SYSTEM_TEXT_SPEAKER_ID
169- }
226+ _store_extracted_string (
227+ strings_with_metadata ,
228+ message_key_as_original ,
229+ text_to_translate_val ,
230+ original_marker ,
231+ speaker_id_for_this_entry if original_marker == 'Message' else SYSTEM_TEXT_SPEAKER_ID ,
232+ wolf_code ,
233+ wolf_export_schema ,
234+ )
170235 log .debug (f" 提取到 '{ original_marker } ' 块. 原文Key: '{ message_key_as_original [:30 ].replace (chr (10 ),'/LF/' ) + ('...' if len (message_key_as_original )> 30 else '' )} '. Speaker: '{ speaker_id_for_this_entry } '" )
171236 if message_key_as_original != text_to_translate_val :
172237 log .debug (f" 半角假名已转换." )
@@ -192,11 +257,15 @@ def _extract_strings_from_file(file_path):
192257 choice_line_key = choice_line .strip ()
193258 if choice_line_key :
194259 text_to_translate_val = text_processing .convert_half_to_full_katakana (choice_line_key )
195- strings_with_metadata [choice_line_key ] = {
196- "text_to_translate" : text_to_translate_val ,
197- "original_marker" : original_marker ,
198- "speaker_id" : speaker_id_for_this_entry
199- }
260+ _store_extracted_string (
261+ strings_with_metadata ,
262+ choice_line_key ,
263+ text_to_translate_val ,
264+ original_marker ,
265+ speaker_id_for_this_entry ,
266+ wolf_code ,
267+ wolf_export_schema ,
268+ )
200269 log .debug (f" 提取到 Choice 标记 '{ original_marker } '. 原文Key: '{ choice_line_key [:30 ].replace (chr (10 ),'/LF/' ) + ('...' if len (choice_line_key )> 30 else '' )} '. Speaker: '{ speaker_id_for_this_entry } ' (内容来自 L{ i + 1 } )" )
201270 if choice_line_key != text_to_translate_val :
202271 log .debug (f" 半角假名已转换." )
@@ -209,11 +278,15 @@ def _extract_strings_from_file(file_path):
209278 if single_line_key :
210279 text_to_translate_val = text_processing .convert_half_to_full_katakana (single_line_key )
211280 # 对于非Message类,speaker_id 固定为 SYSTEM_TEXT_SPEAKER_ID
212- strings_with_metadata [single_line_key ] = {
213- "text_to_translate" : text_to_translate_val ,
214- "original_marker" : original_marker ,
215- "speaker_id" : SYSTEM_TEXT_SPEAKER_ID
216- }
281+ _store_extracted_string (
282+ strings_with_metadata ,
283+ single_line_key ,
284+ text_to_translate_val ,
285+ original_marker ,
286+ SYSTEM_TEXT_SPEAKER_ID ,
287+ wolf_code ,
288+ wolf_export_schema ,
289+ )
217290 log .debug (f" 提取到单行标记 '{ original_marker } '. 原文Key: '{ single_line_key [:30 ].replace (chr (10 ),'/LF/' ) + ('...' if len (single_line_key )> 30 else '' )} '. Speaker: '{ SYSTEM_TEXT_SPEAKER_ID } ' (内容来自 L{ i + 1 } )" )
218291 if single_line_key != text_to_translate_val :
219292 log .debug (f" 半角假名已转换." )
@@ -225,15 +298,19 @@ def _extract_strings_from_file(file_path):
225298 else :
226299 i += 1
227300
301+ if pending_wolf_code is not None :
302+ raise ValueError ("WOLF 条目元数据后缺少文本标记" )
303+ if require_wolf_metadata and not saw_wolf_metadata :
304+ raise ValueError ("WOLF StringScripts 不含当前版本元数据,请重新导出翻译文本" )
228305 log .debug (f"完成文件解析: '{ os .path .basename (file_path )} '. 共提取 { len (strings_with_metadata )} 条数据." )
229306 return strings_with_metadata
230307
231308 except FileNotFoundError :
232309 log .error (f"读取文件失败: { file_path } 未找到。" )
233- return {}
310+ raise
234311 except Exception as e :
235312 log .exception (f"处理文件 '{ os .path .basename (file_path )} ' (行 ~{ current_line_number_for_log } ) 时发生严重错误: { e } " )
236- return {}
313+ raise
237314
238315
239316# --- 主任务函数 ---
@@ -251,6 +328,8 @@ def run_create_json(game_path, works_dir, message_queue):
251328 message_queue .put (("status" , "创建 JSON 失败" ))
252329 message_queue .put (("done" , None ))
253330 return
331+ detected_game = detect_game_engine (game_path )
332+ is_wolf_game = bool (detected_game and detected_game .engine == "wolf" )
254333
255334 game_folder_name = text_processing .sanitize_filename (os .path .basename (game_path ))
256335 if not game_folder_name : game_folder_name = "UntitledGame"
@@ -261,6 +340,9 @@ def run_create_json(game_path, works_dir, message_queue):
261340 raise OSError (f"无法创建或访问游戏特定工作目录: { work_game_dir } " )
262341 if not file_system .ensure_dir_exists (untranslated_dir ):
263342 raise OSError (f"无法创建或访问 untranslated 目录: { untranslated_dir } " )
343+ json_path = os .path .join (untranslated_dir , "translation.json" )
344+ if os .path .exists (json_path ) and not file_system .safe_remove (json_path ):
345+ raise OSError (f"无法废弃旧版未翻译 JSON: { json_path } " )
264346
265347 message_queue .put (("log" , ("normal" , f"将在以下目录创建 JSON: { untranslated_dir } " )))
266348
@@ -282,7 +364,13 @@ def run_create_json(game_path, works_dir, message_queue):
282364 message_queue .put (("log" , ("debug" , f"正在解析文件: { file_key_in_json } " )))
283365
284366 # 调用 _extract_strings_from_file 获取该文件的所有文本和元数据
285- data_from_single_file = _extract_strings_from_file (file_path )
367+ data_from_single_file = _extract_strings_from_file (
368+ file_path ,
369+ require_wolf_metadata = (
370+ is_wolf_game
371+ or bool (relative_parts and relative_parts [0 ].casefold () == "wolf" )
372+ ),
373+ )
286374
287375 if data_from_single_file : # 只有当文件中有数据时才添加
288376 file_organized_data [file_key_in_json ] = data_from_single_file
@@ -297,17 +385,21 @@ def run_create_json(game_path, works_dir, message_queue):
297385 if not file_organized_data : # 如果没有任何文件包含可翻译内容
298386 message_queue .put (("log" , ("warning" , "未从任何文件中提取到文本条目。生成的 JSON 文件将为空对象。" )))
299387
300- json_filename = "translation.json" # 输出文件名保持不变
301- json_path = os .path .join (untranslated_dir , json_filename )
302388 message_queue .put (("log" , ("normal" , f"正在将按文件组织的文本及元数据写入 JSON 文件: { json_path } " )))
303389
390+ temporary_json_path = f"{ json_path } .tmp"
304391 try :
305- with open (json_path , 'w' , encoding = 'utf-8' ) as json_file :
392+ with open (temporary_json_path , 'w' , encoding = 'utf-8' ) as json_file :
306393 json .dump (file_organized_data , json_file , ensure_ascii = False , indent = 4 ) # 写入新的组织结构
394+ json_file .flush ()
395+ os .fsync (json_file .fileno ())
396+ os .replace (temporary_json_path , json_path )
307397 message_queue .put (("success" , f"按文件组织的未翻译 JSON 文件创建成功: { json_path } " ))
308398 message_queue .put (("status" , "创建 JSON 文件完成" ))
309399 message_queue .put (("done" , None ))
310400 except Exception as write_err :
401+ if os .path .exists (temporary_json_path ):
402+ file_system .safe_remove (temporary_json_path )
311403 log .exception (f"写入 JSON 文件失败: { json_path } - { write_err } " )
312404 message_queue .put (("error" , f"写入 JSON 文件失败: { write_err } " ))
313405 message_queue .put (("status" , "创建 JSON 失败" ))
0 commit comments