import re import os import glob import fileinput import xml.etree.ElementTree as ET import pygsheets from optparse import OptionParser import logging logging.basicConfig(level=logging.DEBUG) def CDATA(text=None): element = ET.Element('![CDATA[') element.text = text return element ET._original_serialize_xml = ET._serialize_xml #CDATA特殊处理 def _serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs): if 'name' in elem.attrib.keys() and elem.attrib['name'] in andorid_contian_cdata_keys: write("<{} name=\"{}\">{}".format(elem.tag,elem.attrib['name'],elem.text,elem.tag,elem.tail)) else: return ET._original_serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs) ET._serialize_xml = ET._serialize['xml'] = _serialize_xml andorid_root_directory = os.path.abspath(os.path.join(os.getcwd(), os.pardir, os.pardir)) ios_root_directory = os.path.abspath(os.path.join(os.getcwd(), os.pardir, os.pardir)) ios_languages = ['ar', 'en', 'hi', 'ta', 'te', 'th', 'tr', 'zh-Hans'] #andorid_language = ['', '-ar', '-hi', '-ta', '-te', '-th', '-tr', '-zh'] # andorid_language = ['', '-ar','-hi','-bn','-es','-in','-ms','-pt','-th','-tl','-tr','-ur','-vi','-zh','-zh-TW']ru kk ky tk tg uz andorid_language = ['','-zh', '-ar'] #xml对CDATA解析有点问题, CDATA 数据需要在这里添加key手动处理 andorid_contian_cdata_keys = ['room_high_potential_conversion_reward','ludo_exit_game_tip','message_follow', 'call_send_diamond','call_end_chat_get_diamonds','call_end_chat_get_coins','call_end_chat_un_get_diamonds','call_end_chat_un_get_coins', 'couple_love_house_no_couple_desc','game_rules_2','game_rocket_reward_record','message_follow', 'room_rank_list_title','wallet_diamond_balance','wallet_coin_balance','common_cp_level_not_reach','domino_exit_game_tip','ludo_load_game_go'] def find_strings_files(root_dir,lan = "en"): translation_files = [f for f in glob.glob(os.path.join(root_dir, '**', lan + '.lproj', 'Localizable.strings'), recursive=True) if 'MJRefresh' not in f] return translation_files def find_xml_files(root_dir = andorid_root_directory,lan = ""): translation_files = glob.glob(os.path.join(root_dir, '**', 'res/values'+lan, 'strings.xml'), recursive=True) return translation_files def lan_of_path(path): parts = path.split('/') lan = parts[-2].strip('.lproj') return lan def convert_ios_strings(value): placeholder_pattern = r'%\d*\$?[sdf]' if len(re.findall(placeholder_pattern,value)) > 0: value = value.replace('%s', '%@') value = re.sub(r'%(\d+)\$s', r'%\1$@', value) return value def map_andorid_lan(lan): if lan == 'en': return '' elif lan == 'zh-Hans': return '-zh' else: return '-'+lan def map_sheet_lan(lan): if lan == 'zh-Hans': return 'zh' else: return lan def extract_xml_to_dic(lan = ''): count = 0 translations = {} translation_files = find_xml_files(andorid_root_directory,lan) android_placeholder_pattern = r'%\d*\$?[sdf]' for fname in translation_files: print(f"Processing file: {fname}") tree = ET.parse(fname) root = tree.getroot() for child in root: key = child.attrib['name'] value = child.text if len(re.findall(android_placeholder_pattern,value)) > 0: value = value.replace('%s', '%@') value = re.sub(r'%(\d+)\$s', r'%\1$@', value) translations[key] = value else: translations[key] = value return translations def read_into_table(filelist): table = {} with fileinput.input(files=filelist) as f: for line in f: if "=" in line: key, value = line.split("=", 1) table[key.strip()[1:-1]] = value.strip()[1:-2] return table def read_xml_into_table(file): table = {} with fileinput.input(files=[file]) as f: tree = ET.parse(file) root = tree.getroot() for child in root: key = child.attrib['name'] value = child.text table[key] = value return table def read_xml_to_table(lan = ''): count = 0 translations = {} translation_files = find_xml_files(andorid_root_directory,lan) print(f"Processing read_xml_to_table lan: {lan}") for fname in translation_files: tree = ET.parse(fname) root = tree.getroot() for child in root: key = child.attrib['name'] value = child.text # print(f'key ==> {key}') translations[key] = value return translations def mergexml(lan = ""): # 创建一个 ElementTree 用于存储合并后的内容 merged_tree = ET.ElementTree(ET.Element('resources')) # 遍历根目录及其子目录 for root, dirs, files in os.walk(andorid_root_directory): for filename in files: if filename == 'strings.xml' and root.endswith("res/values"+lan): xml_file_path = os.path.join(root, filename) # print(xml_file_path) # 解析当前 XML 文件 tree = ET.parse(xml_file_path) root_elem = tree.getroot() # 将当前 XML 文件的内容添加到合并后的 XML 文件中 for elem in root_elem: merged_tree.getroot().append(elem) # 将合并后的内容写入总的 strings.xml 文件 outfilename = 'merged_strings'+lan +'.xml' outfilepath = os.path.join('MergedXml', outfilename) merged_tree.write(outfilepath, encoding='utf-8', xml_declaration=True) print(f"{lan} 合并完成 \n") def findallxml(): # 遍历根目录及其子目录 for root, dirs, files in os.walk(andorid_root_directory): for filename in files: if filename == 'strings.xml' and "res/values" in root: # 找到 strings.xml 文件 xml_file_path = os.path.join(root, filename) print(f"找到 strings.xml 文件:{xml_file_path}") # print(f'{filename}') def find_all_xml_files(): for lan in andorid_language: find_xml_files(lan=lan) def merge_all_files(): for lan in andorid_language: mergexml(lan=lan) def find_untrans_items(): untransItems = {} files = find_strings_files(root_dir=ios_root_directory) for filename in files: en_tables = read_into_table(filename) ar_tables = read_into_table(filename.replace('en.lproj','ar.lproj')) result = set(en_tables.keys()) - set(ar_tables.keys()) if len(result) > 0 : print(f'{filename} , untranslate keys: {result}') for key in result: print(f'{key} ==> {en_tables[key]}') untransItems[key] = en_tables[key] return untransItems def find_all_untrans_items(): untransItems = {} files = find_strings_files(root_dir=ios_root_directory) for filename in files: en_tables = read_into_table(filename) ar_tables = read_into_table(filename.replace('en.lproj','ar.lproj')) result = set(en_tables.keys()) - set(ar_tables.keys()) if len(result) > 0 : print(f'{filename} , untranslate keys: {result}') for key in result: print(f'{key} ==> {en_tables[key]}') untransItems[key] = en_tables[key] return untransItems def syn_translate_ios(transItems = {}): if len(transItems) > 0: files = find_strings_files(root_dir=ios_root_directory) for filename in files: en_tables = read_into_table(filename) processkeys = set(en_tables.keys()) & set(transItems.keys()) if len(processkeys) > 0 : for lan in ios_languages: lanfile = filename.replace('en.lproj',f'{lan}.lproj') print(f'开始处理文件: {lanfile}') preparekey = list(processkeys) with open(lanfile, 'r', encoding='utf-8') as infile: lines = infile.readlines() with open(lanfile, 'w', encoding='utf-8') as outfile: for line in lines: if '=' in line: key, value = line.strip().split("=", 1) key = key.strip()[1:-1] value = value.strip()[1:-2] if key in transItems.keys(): newvalue = convert_ios_strings(transItems[key][map_sheet_lan(lan)]).strip() if newvalue == '': newvalue = transItems[key]['en'] if newvalue != value: modified_line = f'"{key}" = "{newvalue}";\n' outfile.write(modified_line) if key in preparekey: preparekey.remove(key) print(f'更新 ==>[{lan}:{key}] {value} ==> {newvalue}') else: outfile.write(line) if key in preparekey: preparekey.remove(key) else: outfile.write(line) for key in preparekey: newvalue = convert_ios_strings(transItems[key][map_sheet_lan(lan)]).strip() if newvalue == '': print(f'!!添加 ==>[{lan}:{key}] 翻译为空,默认使用英文翻译') newvalue = transItems[key]['en'] newline = f'"{key}" = "{newvalue}";\n' outfile.write(newline) print(f'添加 ==>[{lan}:{key}] ==> {newvalue}') print(f'同步完成 \n') else: print(f'未找到英文翻译中的差异的 key \n') else: print('没有需要同步的表格数据') def syn_translate_andorid(transItems = {}): if len(transItems) > 0: files = find_xml_files(root_dir=andorid_root_directory) for filename in files: en_tables = read_xml_into_table(filename) processkeys = set(en_tables.keys()) & set(transItems.keys()) if len(processkeys) > 0 : for lan in andorid_language: lan_key = lan.replace('-','') if lan_key == '': lan_key = 'en' if lan=='-zh-TW': lanfile = filename.replace('res/values',f'res/values-zh-rTW') lan_key = 'zh-TW' else : lanfile = filename.replace('res/values',f'res/values{lan}') # 检查目录是否存在 lanfile_dir = os.path.dirname(lanfile) if not os.path.exists(lanfile_dir): os.makedirs(lanfile_dir) # 创建目录 print(f"目录 {lanfile_dir} 已创建") # 检查文件是否存在 if not os.path.exists(lanfile): with open(lanfile, 'w', encoding='utf-8') as f: f.write('\n\n') # 可以初始化文件内容 print(f"文件 {lanfile} 已创建") else: print(f"文件 {lanfile} 已存在") print(lan) print(lan_key) print(lanfile) print(f'开始处理文件: {lanfile}') preparekey = list(processkeys) tree = ET.parse(lanfile) root = tree.getroot() for child in root: key = child.attrib['name'] value = child.text print(key) print(value) if key in transItems.keys(): newvalue = str(transItems[key][lan_key]).strip() print(111) print(newvalue) if newvalue == '': newvalue = transItems[key]['en'] if newvalue != value: child.text = newvalue print(f'更新 ==>[{lan}:{key}] {value} ==> {newvalue}') if key in preparekey: preparekey.remove(key) else: child.text = value for key in preparekey: newvalue = str(transItems[key][lan_key]).strip() if newvalue == '': print(f'!!添加 ==>[{lan}:{key}] 翻译为空,默认使用英文翻译') newvalue = transItems[key]['en'] new_string_element = ET.Element('string') new_string_element.set('name', key) new_string_element.text = newvalue root.append(new_string_element) print(f'添加 ==>[{lan}:{key}] ==> {newvalue}') ET.indent(tree, ' ') tree.write(lanfile, encoding='utf-8',xml_declaration=False) #write("\n" % (declared_encoding,)) with open(lanfile,'r+') as f: file_data = f.read() f.seek(0, 0) f.write('\n' + file_data) print(f'同步完成 \n') else: print('没有需要同步的表格数据') class syntransition: def __init__(self,fileName = 'Ludo Transaltion',export = False, platform = 1,title = ''): self.client = pygsheets.authorize(service_file = "localizable-key.json") self.sheet = self.client.open(fileName) self.platform = platform # 尝试查找工作表,如果找不到,则添加一个新工作表 try: self.worksheet = self.sheet.worksheet_by_title(title) except pygsheets.exceptions.WorksheetNotFound: if export: # 如果工作表不存在,添加一个新工作表 template_wks = self.sheet.worksheet_by_title('template') self.worksheet = self.sheet.add_worksheet(title=title, src_worksheet=template_wks) else: print('工作表不存在,请检查sheet名是否正确') def read_sheet(self): tables = {} for row in self.worksheet.get_all_records(): key = 'i_key' if self.platform == 2 else 'a_key' trans_key = row[key] if trans_key.strip() != '': tables[trans_key] = row return tables def syn_translate(filename, title,platform,): #'消息设置页面' syn = syntransition(fileName=filename,title=title,platform=platform) translations = syn.read_sheet() print(translations) if platform == 1: syn_translate_andorid(translations) else: syn_translate_ios(translations) def addParser(): parser = OptionParser() parser.add_option("-f", "--fileName", default="Ludo Transaltion", help="表文件名称", metavar="fileName") parser.add_option("-t", "--sheetTitle", help="google sheet表格名", metavar="sheetTitle") parser.add_option("-p", "--platform", default=1, help="平台(andorid: 1,ios :2 )", metavar="platform") (options, args) = parser.parse_args() print("options: %s, args: %s" % (options, args)) return options def startConvert(options): filename = options.fileName sheetTitle = options.sheetTitle platform = options.platform print("Start converting") if platform != 1: platform = 2 if sheetTitle is None: print("google sheet表格名不能为空") return syn_translate(filename=filename,title=sheetTitle,platform=1) if __name__ == '__main__': options = addParser() startConvert(options) #syn_translate(filename='Ludo Transaltion',title='ludo排行榜',platform=1) # 使用:表名如有空格,需要用''标识sheet表名,synxml.py -t 'ludo 排行榜' # synxml.py -t ludo排行榜