4141TRANSLATION_DELAY = 0.6
4242# 同步最大迭代次数
4343MAX_SYNC_ITERATIONS = 10
44- # 占位符名称相似度修复阈值 (0.0 到 1.0,越高越严格,这里指 Levenshtein 距离与长度的比值,1.0 - dist/len)
45- # 或者使用绝对距离阈值,如下面的实现。
46- PLACEHOLDER_SIMILARITY_THRESHOLD_FACTOR = 0.7 # 用于计算最大可接受距离
47- PLACEHOLDER_ABSOLUTE_DISTANCE_THRESHOLD = 3 # 或者一个固定的最大编辑距离
44+ # 占位符名称相似度修复阈值
45+ PLACEHOLDER_SIMILARITY_THRESHOLD_FACTOR = 0.7
46+ PLACEHOLDER_ABSOLUTE_DISTANCE_THRESHOLD = 3
47+
48+ # --- 新增配置: Info 文件处理 ---
49+ INFO_FILENAMES = {"info.json" , "info.dynamic.json" }
50+ # 仅翻译这些字段,其他字段(如 version, author, home_page)直接复制
51+ TRANSLATABLE_INFO_FIELDS = {"name" , "description" , "description_markdown" , "summary" , "tags" }
4852# ----------- 配置结束 -----------
4953
5054
@@ -413,6 +417,145 @@ def sync_unique_key(dict_has_key, dict_missing_key, lang_has_key, lang_missing_k
413417 return handle_missing_key_translation (dict_missing_key , dict_has_key , key , lang_missing_key , lang_has_key , path )
414418
415419
420+ # --- Info File Sync Logic ---
421+
422+ def sync_info_content (info_data , available_lang_codes ):
423+ """
424+ 同步 info.json 类型的内容。
425+ 结构通常为: { "en-UK": {...}, "zh-CN": {...} }
426+ """
427+ changed = False
428+
429+ # 1. 确定参考源 (Source of Truth)
430+ source_lang = None
431+ for ref in REFERENCE_LANG_CODES :
432+ if ref in info_data :
433+ source_lang = ref
434+ break
435+ if not source_lang :
436+ # 如果找不到参考语言,尝试使用存在的第一个语言
437+ available_in_file = [k for k in info_data .keys () if k in available_lang_codes ]
438+ if available_in_file :
439+ source_lang = available_in_file [0 ]
440+
441+ if not source_lang :
442+ print (f" - 跳过: 无法在文件中找到任何有效的参考语言块。" )
443+ return False
444+
445+ source_obj = info_data [source_lang ]
446+ if not isinstance (source_obj , (dict , OrderedDict )):
447+ return False
448+
449+ # 2. 遍历所有应该支持的语言
450+ for target_lang in available_lang_codes :
451+ if target_lang == source_lang :
452+ continue
453+
454+ # 如果目标语言块不存在,创建它
455+ if target_lang not in info_data :
456+ print (f" + 为 '{ target_lang } ' 创建新块 (基于 '{ source_lang } ')" )
457+ info_data [target_lang ] = OrderedDict ()
458+ changed = True
459+
460+ target_obj = info_data [target_lang ]
461+
462+ # 3. 同步字段
463+ for key , val in source_obj .items ():
464+ if key not in target_obj :
465+ # 缺失键:决定是翻译还是复制
466+ if key in TRANSLATABLE_INFO_FIELDS :
467+ # 翻译
468+ print (f" + 翻译缺失字段 '{ key } ': { source_lang } -> { target_lang } " )
469+ translated = translate_value (copy .deepcopy (val ), source_lang , target_lang )
470+ if translated is not None :
471+ target_obj [key ] = translated
472+ changed = True
473+ else :
474+ # 直接复制 (例如 version, author, provider)
475+ # print(f" + 复制字段 '{key}'")
476+ target_obj [key ] = copy .deepcopy (val )
477+ changed = True
478+
479+ # 如果键存在,暂时不做覆盖更新,假设已有的是手动修正过的
480+ # 除非值为空字符串且源不为空?这里暂保持保守策略。
481+
482+ # 4. (可选) 排序:让 info.json 的语言顺序好看一点
483+ # 这里简单对顶层 key (语言) 排序,把非语言的 key 留着
484+ sorted_keys = sorted (info_data .keys (), key = lambda k : (0 if k in REFERENCE_LANG_CODES else 1 , k ))
485+ if list (info_data .keys ()) != sorted_keys :
486+ new_ordered = OrderedDict ((k , info_data [k ]) for k in sorted_keys )
487+ info_data .clear ()
488+ info_data .update (new_ordered )
489+ changed = True
490+
491+ return changed
492+
493+ def process_info_files (fount_dir , gitignore_spec , available_lang_codes ):
494+ """
495+ 扫描并同步所有的 info.json / info.dynamic.json 文件
496+ 不仅同步内容,还会强制统一缩进格式(缩进使用 Tab,末尾有空行)。
497+ """
498+ print (f"\n --- 开始扫描并同步 Info JSON 文件 ---" )
499+ print (f"目标文件名: { INFO_FILENAMES } " )
500+
501+ count_processed = 0
502+ count_changed = 0
503+
504+ for root , dirs , files in os .walk (fount_dir ):
505+ if gitignore_spec :
506+ dirs [:] = [d for d in dirs if not gitignore_spec .match_file (os .path .relpath (os .path .join (root , d ), fount_dir ))]
507+
508+ for filename in files :
509+ if filename not in INFO_FILENAMES :
510+ continue
511+
512+ filepath = os .path .join (root , filename )
513+ if gitignore_spec and gitignore_spec .match_file (os .path .relpath (filepath , fount_dir )):
514+ continue
515+
516+ try :
517+ # 1. 读取原始文件内容(字符串)以便后续比较格式
518+ with open (filepath , "r" , encoding = "utf-8" ) as f :
519+ original_content_str = f .read ()
520+
521+ # 2. 解析数据
522+ # 使用 loads 而不是 load,因为我们已经读取了字符串
523+ try :
524+ data = json5 .loads (original_content_str , object_pairs_hook = OrderedDict )
525+ except Exception as e :
526+ print (f" ! 解析 JSON5 失败 { os .path .relpath (filepath , fount_dir )} : { e } " )
527+ continue
528+
529+ print (f" 正在处理: { os .path .relpath (filepath , fount_dir )} " )
530+
531+ # 3. 执行内容同步逻辑 (sync_info_content 会修改 data 对象)
532+ content_has_logical_changes = sync_info_content (data , available_lang_codes )
533+
534+ # 4. 生成标准格式的新字符串
535+ # 强制使用 Tab 缩进,且末尾附加一个换行符
536+ new_content_str = json .dumps (data , ensure_ascii = False , indent = "\t " , default = str ) + "\n "
537+
538+ # 5. 判定是否需要写入
539+ # 条件:(内容逻辑有变化) 或者 (原始文本与标准格式文本不一致)
540+ if content_has_logical_changes or original_content_str != new_content_str :
541+ action = "更新内容" if content_has_logical_changes else "格式化"
542+
543+ with open (filepath , "w" , encoding = "utf-8" , newline = "\n " ) as f :
544+ f .write (new_content_str )
545+
546+ print (f" -> 已保存 ({ action } )。" )
547+ count_changed += 1
548+ else :
549+ print (" -> 无需变更。" )
550+ pass
551+
552+ count_processed += 1
553+
554+ except Exception as e :
555+ print (f" ! 错误处理文件 { filepath } : { e } " )
556+
557+ print (f"--- Info 文件处理完毕: 扫描 { count_processed } 个, 更新 { count_changed } 个 ---" )
558+
416559def normalize_and_sync_dicts (
417560 dict_a ,
418561 dict_b ,
@@ -486,7 +629,7 @@ def check_used_keys_in_fount(fount_dir, reference_loc_data, reference_lang_code,
486629 print (f"警告: fount 目录 '{ fount_dir } ' 不存在,跳过i18n键使用情况检查。" )
487630 return
488631
489- print (f"\n --- 开始在 FOUNT 目录 '{ fount_dir } ' 中检查 MJS/HTML 文件中的 i18n 键 ---" )
632+ print (f"\n --- 开始在 fount 目录 '{ fount_dir } ' 中检查 MJS/HTML 文件中的 i18n 键 ---" )
490633 regex_patterns = [re .compile (r"data-i18n=(?:\"([a-zA-Z0-9_.-]+)\"|'([a-zA-Z0-9_.-]+)')" ), re .compile (r"(?:geti|I)18n\((?:\"([a-zA-Z0-9_.-]+)\"|'([a-zA-Z0-9_.-]+)')\)" )]
491634 found_keys_in_fount_code = set ()
492635
@@ -514,10 +657,10 @@ def check_used_keys_in_fount(fount_dir, reference_loc_data, reference_lang_code,
514657 print (f" 警告: 读取或解析文件 { filepath } 失败: { e } " )
515658
516659 if not found_keys_in_fount_code :
517- print (" 在 FOUNT 目录的 MJS/HTML 文件中没有检测到任何 i18n 键的使用。" )
660+ print (" 在 fount 目录的 MJS/HTML 文件中没有检测到任何 i18n 键的使用。" )
518661 return
519662
520- print (f" 在 FOUNT 代码中检测到 { len (found_keys_in_fount_code )} 个唯一 i18n 键。正在与参考语言 '{ reference_lang_code } ' 数据比较..." )
663+ print (f" 在 fount 代码中检测到 { len (found_keys_in_fount_code )} 个唯一 i18n 键。正在与参考语言 '{ reference_lang_code } ' 数据比较..." )
521664 missing_keys_count = 0
522665 for key in sorted (list (found_keys_in_fount_code )):
523666 _ , found = get_value_at_path (reference_loc_data , key )
@@ -526,10 +669,10 @@ def check_used_keys_in_fount(fount_dir, reference_loc_data, reference_lang_code,
526669 missing_keys_count += 1
527670
528671 if missing_keys_count == 0 :
529- print (f" 所有在 FOUNT 代码中使用的 { len (found_keys_in_fount_code )} 个 i18n 键均存在于参考语言 '{ reference_lang_code } ' 数据中。" )
672+ print (f" 所有在 fount 代码中使用的 { len (found_keys_in_fount_code )} 个 i18n 键均存在于参考语言 '{ reference_lang_code } ' 数据中。" )
530673 else :
531- print (f" 总计: { missing_keys_count } 个在 FOUNT 代码中使用的键在参考语言数据中缺失。" )
532- print ("--- FOUNT 目录i18n键检查完毕 ---" )
674+ print (f" 总计: { missing_keys_count } 个在 fount 代码中使用的键在参考语言数据中缺失。" )
675+ print ("--- fount 目录i18n键检查完毕 ---" )
533676
534677
535678def extract_placeholders (text_string : str ) -> list [str ]:
@@ -1034,14 +1177,15 @@ def main():
10341177 gitignore_spec = load_gitignore_spec (FOUNT_DIR )
10351178
10361179 all_data , languages , lang_to_path = load_locale_files ()
1180+ available_lang_codes = list (lang_to_path .keys ()) # 收集所有可用语言代码
10371181
10381182 ref_path , ref_lang = find_reference_language (lang_to_path , all_data , languages )
10391183
10401184 first_ref_lang = next ((lang for lang in REFERENCE_LANG_CODES if lang in lang_to_path ), ref_lang )
10411185 if first_ref_lang :
10421186 check_used_keys_in_fount (FOUNT_DIR , all_data [lang_to_path [first_ref_lang ]], first_ref_lang , gitignore_spec )
10431187 else :
1044- print ("\n 警告: 未找到参考语言数据,跳过 FOUNT 目录i18n键检查。" )
1188+ print ("\n 警告: 未找到参考语言数据,跳过 fount 目录i18n键检查。" )
10451189
10461190 run_synchronization_loop (all_data , languages , ref_path )
10471191
@@ -1050,6 +1194,8 @@ def main():
10501194
10511195 save_locale_files (all_data , ref_path )
10521196
1197+ process_info_files (FOUNT_DIR , gitignore_spec , available_lang_codes )
1198+
10531199 ts_decl_path = os .path .join (LOCALE_DIR , "../decl/locale_data.ts" )
10541200 generate_locale_data_ts (all_data [ref_path ], ts_decl_path )
10551201
0 commit comments