import os
import csv
import re
from xml.etree import ElementTree as ET
LANGUAGE_ORDER = ['values-zh-rCN','values-zh-rTW','values', 'values-de','values-es','values-ru','values-fr','values-ar','values-it','values-nl','values-pt','values-tr','values-vi','values-pl','values-ko','values-ja','values-iw','values-el','values-th','values-hr','values-cs'
]def parse_lang_tag(dir_name):"""解析目录名,返回语言标签(不带区域)"""if dir_name == 'values':return 'base'parts = dir_name.split('-', 1)[1] if dir_name.startswith('values-') else dir_namelang_parts = parts.split('-')lang_tag = lang_parts[0]if len(lang_parts) > 1 and lang_parts[1].startswith('r'):return lang_tagreturn partsdef extract_strings_from_xml(xml_path):"""从strings.xml文件中提取字符串资源"""try:tree = ET.parse(xml_path)root = tree.getroot()strings = {}for child in root:if child.tag == 'string' and 'name' in child.attrib:name = child.attrib['name']text = child.text or ""if len(child) > 0: full_text = [child.text or ""]for elem in child:full_text.append(ET.tostring(elem, encoding='unicode'))if elem.tail:full_text.append(elem.tail)text = ''.join(full_text)strings[name] = textelif child.tag == 'plurals' and 'name' in child.attrib:name = child.attrib['name']plurals = {}for item in child:if item.tag == 'item' and 'quantity' in item.attrib:quantity = item.attrib['quantity']text = item.text or ""plurals[quantity] = textif plurals:strings[name] = f"[PLURALS] {str(plurals)}"return stringsexcept ET.ParseError:print(f"解析错误: {xml_path}")return {}except FileNotFoundError:print(f"文件未找到: {xml_path}")return {}except Exception as e:print(f"处理文件 {xml_path} 时出错: {str(e)}")return {}def find_all_translations(project_root):"""在项目中查找所有翻译资源"""translations = {}lang_tag_map = {} possible_res_dirs = [os.path.join(project_root, 'app', 'src', 'main', 'res'),os.path.join(project_root, 'app', 'src', 'debug', 'res'),os.path.join(project_root, 'app', 'src', 'release', 'res'),os.path.join(project_root, 'res')]res_dir = Nonefor dir_path in possible_res_dirs:if os.path.exists(dir_path):res_dir = dir_pathbreakif not res_dir:raise FileNotFoundError("找不到res目录,请确认项目结构")print(f"使用资源目录: {res_dir}")found_dirs = []for dir_name in os.listdir(res_dir):if dir_name.startswith('values'):strings_path = os.path.join(res_dir, dir_name, 'strings.xml')if os.path.exists(strings_path):found_dirs.append(dir_name)strings = extract_strings_from_xml(strings_path)if strings: translations[dir_name] = stringslang_tag = parse_lang_tag(dir_name)if lang_tag not in lang_tag_map:lang_tag_map[lang_tag] = []lang_tag_map[lang_tag].append(dir_name)print(f"找到 {len(found_dirs)} 个语言目录")return translations, lang_tag_mapdef find_alternative_dir(translations, lang_tag_map, target_dir):"""寻找替代目录:如果目标目录不存在,查找同语言的区域变体"""if target_dir in translations:return target_dirtarget_lang = parse_lang_tag(target_dir)candidate_dirs = lang_tag_map.get(target_lang, [])if candidate_dirs:candidate_dirs.sort(key=lambda x: len(x))return candidate_dirs[0]return Nonedef generate_translation_table(translations, lang_tag_map):"""生成翻译表格,按照固定顺序排列语言列,支持区域变体替代"""all_keys = set()for strings in translations.values():all_keys.update(strings.keys())sorted_keys = sorted(all_keys)table = []header = ['String Name'] + LANGUAGE_ORDERtable.append(header)for key in sorted_keys:row = [key]for target_dir in LANGUAGE_ORDER:actual_dir = find_alternative_dir(translations, lang_tag_map, target_dir)if actual_dir and actual_dir in translations:trans_dict = translations[actual_dir]text = trans_dict.get(key, '')if isinstance(text, str):text = text.replace('\n', '\\n').replace('\r', '\\r').replace('"', '""')row.append(text)else:row.append('')table.append(row)return tabledef save_to_csv(table, output_file='translations.csv'):"""保存表格到CSV文件"""with open(output_file, 'w', newline='', encoding='utf-8') as f:writer = csv.writer(f)writer.writerows(table)print(f"已生成翻译表格: {os.path.abspath(output_file)}")return output_filedef print_summary(translations, lang_tag_map):"""打印统计摘要"""total_keys = set()for strings in translations.values():total_keys.update(strings.keys())print("\n统计信息:")print(f"- 找到的语言数量: {len(translations)}")print(f"- 提取的字符串数量: {len(total_keys)}")print("\n语言目录状态:")for target_dir in LANGUAGE_ORDER:actual_dir = find_alternative_dir(translations, lang_tag_map, target_dir)if actual_dir:count = len(translations[actual_dir])status = "✓"if actual_dir != target_dir:status = f"✓ (使用 {actual_dir} 替代)"print(f" {status} {target_dir.ljust(15)} - {count} 个字符串")else:print(f" ✗ {target_dir.ljust(15)} - 未找到")all_found_dirs = set(translations.keys())specified_dirs = set(LANGUAGE_ORDER)extra_dirs = all_found_dirs - specified_dirsused_alternatives = set()for target_dir in LANGUAGE_ORDER:actual_dir = find_alternative_dir(translations, lang_tag_map, target_dir)if actual_dir and actual_dir != target_dir:used_alternatives.add(actual_dir)extra_dirs -= used_alternativesif extra_dirs:print("\n额外找到的语言目录(未包含在表格中):")for extra in sorted(extra_dirs):count = len(translations[extra])print(f" - {extra.ljust(15)} - {count} 个字符串")def find_project_root():"""尝试自动定位项目根目录"""cwd = os.getcwd()if os.path.exists(os.path.join(cwd, 'res')) or \os.path.exists(os.path.join(cwd, 'app', 'src', 'main', 'res')):return cwdfor i in range(3):parent = os.path.dirname(cwd)if os.path.exists(os.path.join(parent, 'res')) or \os.path.exists(os.path.join(parent, 'app', 'src', 'main', 'res')):return parentcwd = parentreturn os.getcwd() if __name__ == '__main__':print("Android 多语言资源提取工具 - 支持区域变体替代")print("=" * 60)PROJECT_ROOT = find_project_root()print(f"项目根目录: {PROJECT_ROOT}")try:translations, lang_tag_map = find_all_translations(PROJECT_ROOT)if not translations:print("未找到任何翻译文件,请检查项目结构")exit()table = generate_translation_table(translations, lang_tag_map)output_file = save_to_csv(table)print_summary(translations, lang_tag_map)print(f"\n提示: 生成的CSV文件 '{output_file}' 可用Excel或文本编辑器打开")print(" 表格列顺序已按照要求固定排序:")print(" values-zh-rCN, values-zh-rTW, values, values-de, values-es, ... values-cs")print(" 缺失的语言目录保留空列")print("\n区域变体替代规则:")print(" - 如果指定的语言目录不存在(如 values-iw)")print(" - 但存在其区域变体(如 values-iw-rIL)")print(" - 则使用区域变体的内容作为替代")except Exception as e:print(f"\n错误: {str(e)}")print("请确保在Android项目的根目录运行此脚本")print("或者手动指定项目根目录路径")