XLIFF(XML Localisation Interchange File Format,即XML本地化交换文件格式)是一种基于XML的交换格式,旨在标准化本地化过程中在工具之间传递可本地化数据的方式,是CAT工具中常用的一种文件格式。XLIFF由结构化信息标准促进组织(OASIS)于2002年标准化,目前规范为2014年8月5日发布的v2.0
                                   ——Wiki
update:升级到Xcode13以后,Export Localizationns 会报错,需要在Build Setting中设置Use Compiler to Extract Swift Strings为NO。多个targert的话每个target都设置即可。
Apple对国际化文案的管理也是基于XLIFF的,这几年一直负责海外项目。国际化文案翻译和录入是必不可免的。XLIFF是业内的通用做法。
如果你对XLIFF不了解,可以参考WWDC Session 401:
《Localizing with Xcode 9》
Apple在2018年升级了国际化文案管理方式,从XLIFF升级到了Localization Catalog。不过本质上文案管理还是XLIFF。

具体参考WWDC Session404:
《New Localization Workflows in Xcode 10》
Localization Catalog 解决了XLIFF的单一性,可以让翻译人员根据上下文语境更准确的翻译。


一般来说,正确的方法是从Xcode中生成Localization Catalog,直接把Localization Catalog给到翻译人员,翻译人员根据storyboard或者图片结合上下文,对文案进行翻译,并且录入到XLIFF中。然后我们只需要导入到Xcode中就可以了。
不过现实中一般都是用Excel进行文案管理,特别是云端Excel,可以方便管理也方便安卓使用。之前推动过XLIFF,用了几次每次都要给对方进行培训。成本太高。
在写脚本之前,我们用的是XLiffTool这个工具,可以在Mac的App Store直接下载。比较方人工录入。

但是人工录入耗时耗力还容易出错,特别是我们有十几种国际化语言,每次要录入几千条文案。
所以考虑用Python撸一个脚本。Python对Excel和XML的读写都很友好。也顺带学习下Python3.8的语法。
对Excel的读写可以使用xlrd,指定Excel的路径和要读取的sheet。

update:更新了下python文件,支持批量操作
import keyword import xlrd import xml.etree.ElementTree as ET from xml.dom import minidom import os
 
  ns = dict(xliffNameSpace='urn:oasis:names:tc:xliff:document:1.2')
 
  excelPath = os.path.join(os.path.expanduser("~"), 'Desktop/localizable.xlsx')
  xliffRootPath = os.path.join(os.path.expanduser("~"), 'Desktop/exportLoaclization')
  languages = {"Spanish" : "es", "Portuguese" : "pt", "Indonesian" : "id", "Arabic" : "ar", "Japanese" : "ja", "Vietnam" : "vi-VN"}
 
  targetName = "" sourceName = "English"
 
  sheetName = "ios"
  def GetDesktopPath():     return os.path.join(os.path.expanduser("~"), 'Desktop')
  def readExcel():         if not os.path.exists(excelPath):         print('excel路径不存在')         return
                
      data = xlrd.open_workbook(excelPath)          sheet1 = data.sheet_by_index(0)     if sheetName in data.sheet_names():         sheet1 = data.sheet_by_name(sheetName)     else:         print("sheetName不存在 默认读取第一个")
 
           sourceIndex = -1          row_0 = sheet1.row_values(0)     for k in range(len(row_0)):         if row_0[k] == sourceName:             sourceIndex = k             print(sourceName + " 所在索引 " + str(k))
      if sourceIndex == -1:         print('未找到English Index')         return
 
           for subLan in languages:         print(subLan)         targetName = subLan         targetIndex = -1         for k in range(len(row_0)):             if row_0[k] == targetName:                 targetIndex = k                 print(targetName + " 所在索引 " + str(k))                                  dict = {}                 for i in range(sheet1.nrows):                     row_value = sheet1.cell_value(i, targetIndex)                     row_key = sheet1.cell_value(i, sourceIndex)                     if row_key == 'English' or row_key == "":                         continue                     dict[row_key] = row_value                 print('excel数据读取完毕,共 ', len(dict), ' 条数据')                 print(dict)                                  writeXliff(dict,targetName)
   | 
 
拿到需要的文案,放到字典里面,然后开始读写XLIFF
查看XLIFF源码可以看到内部结构。注意是有namespace的,需要进行处理

把dict传递给writeXliff函数,把译文填写到target中,没有target要先进行创建。
def writeXliff(dict, targetName):     print('读取'+targetName+'xliff文件')     lan = languages[targetName]     xliffPath = os.path.join(xliffRootPath, lan+'.xcloc/Localized Contents/'+lan+'.xliff')     print(xliffPath)     # 重写nameSpace     ET.register_namespace('', 'urn:oasis:names:tc:xliff:document:1.2')     # 拿到根节点     tree = ET.parse(xliffPath)     root = tree.getroot()     # print(root.tag)     count = 0     for file in root.findall('xliffNameSpace:file', ns):         #print('读取子文件:' + file.attrib['original'])         body = file.find('xliffNameSpace:body', ns)         for unit in body.findall('xliffNameSpace:trans-unit', ns):             source = unit.find('xliffNameSpace:source', ns)             #print(source.text)             target = unit.find('xliffNameSpace:target', ns)             # target不存在就创建 在excel中填写译文 否则写入原文             if target is None:                 # 创建target                 print("target为空")                 node = ET.SubElement(unit, "target")                 if source.text in dict:                     node.text = dict[source.text]                     count += 1                 else:                     node.text = source.text                     print('填补空白译文')                 node.tail = '\n\t'                 print('create success')             elif source.text in dict:                 target.text = dict[source.text]                 count += 1
      print('写入完成,共写入 ' + str(count) + ' 条数据')     saveXML(root, xliffPath)     print('finish')
 
  def subElement(root, tag, text):     ele = ET.SubElement(root, tag)     ele.text = text
  def saveXML(root, filename, indent="\t", newl="", encoding="utf-8"):     rawText = ET.tostring(root)     dom = minidom.parseString(rawText)     with open(filename, 'w') as f:         dom.writexml(f, "", indent, newl, encoding)
   | 
 
saveXML是一个自己写的优化XML格式的方法。
注意存的时候要指定UTF-8编码,要不会有乱码问题。
Xcode导出和导入Xliff文件也可以使用xocdebuild命令做成自动化。
所以可以用shell脚本解决,支持一键导出、录入、导入操作。
#!/bin/sh
 
 
 
 
 
 
  echo "=========开始执行========="
 
  languages=("ar"                          "en"             "es"                          "id"                          "ja"                          "pt"                          "vi-VN"             "zh-Hant"             ) outPath="$HOME/Desktop/exportLoaclization" scheme="UDictionary"
 
 
  exportLocalizations(){     echo "=========开始导出Xliff文件========="
      if test -e $outPath     then         echo "=========outPath existed, clean outPath"         rm -rf $outPath     fi
      exportLanguageStr=""
      for subLanguage in "${languages[@]}"     do         tmpStr="-exportLanguage $subLanguage "         exportLanguageStr=$exportLanguageStr$tmpStr     done         
      
           commandStr="xcodebuild -exportLocalizations -project $scheme.xcodeproj -localizationPath $outPath $exportLanguageStr"
      echo $commandStr
      eval $commandStr     echo "=========Xliff导出完成========="
  }
  runPython(){     echo "=========执行Python脚本读取Excel========="
 
      chmod +x xliff.py     python3 xliff.py }
  importLocalizations(){     for sub in "${languages[@]}"     do         echo $sub
          tmpStr="-localizationPath $outPath/$sub.xcloc "         commandStr="xcodebuild -importLocalizations -project $scheme.xcodeproj $tmpStr"         echo $commandStr         eval $commandStr     done         echo "=========Xliff导入完成=========" }
 
 
  exportLocalizations
  runPython
  importLocalizations
   | 
 
把excel放到桌面,在Terminal中运行shell即可
chmod +x Localization.sh ./Localization.sh
   | 
 
附上脚本Git地址:https://github.com/liweican1992/ExcelToXliff