当前位置:   article > 正文

iOS国际化——通过脚本使storyboard翻译自增

${srcroot}/${target_name}/runscript/autogenstrings.py ${srcroot}/${target_na

 一. 针对两种文件的国际化处理

  代码中即.m文件的国际化

  首先在你需要进行国际化处理的字符串外面加一层NSLocalizedString,注意中文也是可以的哦

textfield.text = [NSString stringWithFormat:NSLocalizedString(@"使用帮助", nil)];

  NSLocalizedString是一个定义在NSBundle.h中的宏,其用途是寻找当前系统语言对应的Localizable.strings文件中的某个key的值。
  第一个参数是key的名字,第二个参数是对这个“键值对”的注释,在用genstrings工具生成Localizable.strings文件时会自动加上去。

  当我们把所有的.m文件都修改好了,就可以动用genstrings工具了

  1. 启动终端,进入工程所在目录。
  2. 新建两个目录,推荐放在资源目录下。
    目录名会作用到Localizable.strings文件对应的语言,不能写错了。这里zh-Hans指简体中文,注意不能用zh.lproj表示。

mkdir zh-Hans.lproj
mkdir en.lproj

  3. 生成Localizable.strings文件

genstrings -o zh-Hans.lproj *.m
genstrings -o en.lproj *.m

  -o <文件夹>,指定生成的Localizable.strings文件放置的目录。
  *.m,扫描所有的.m文件。这里支持的文件还包括.h, .java等。

  如果你认为这样就结束了,那就too naive了,实际上,上面genstrings指令只能是该目录下的文件遍历,但不能实现递归遍历,这样在一些大工程里面明显不能满足需求,于是在工程里更普遍的用法是

find ./ -name *.m | xargs genstrings -o en.lproj

  这是传说中shell组合指令find+xargs,find ./ -name *.m 会递归所有.m文件并存在一个数组中,这个数组经由pipe传递给了下一个指令,而xargs会将收到的数组一一分割,并交给genstrings执行。

这样我们就可以得到如下的文件结构

 

  storyboard的国际化处理

  看过文档或者其他介绍的都知道storyboard的国际化非常简单,这里简单介绍一下

  1 PROJECT -> Info -> Localizations 在这里添加你想增加的语种

   

  2 将storyboard的inspector栏目的localization项勾上后(图左),project的file navigator那里会多出几个文件(图右)

      

 2 国际化后续优化

  不足之处

  到了这里其实关于iOS的国际化已经算是完成了,但你可能也发现一个问题了,上面两种方法全都是一次性生成文件,但我们的项目往往会不断地更新。重新照上面流程跑,生成的string文件会将之前翻译好的文件覆盖掉。

  代码的国际化还好,毕竟一般面对新需求我们都会新建新的类和文件,只要把新代码文件脱到一个文件夹下,执行genstrings便可以得到一份对应的本地化文件,然后拿给翻译人员,最后将翻译好的键值对追加在初始的localizable.string文件尾部即可。

  而storyboard的国际化便不能如此了,当然新建一个storyboard也是可以的,但如果是小改动我们一般还是会在原storyboard那里添加控件,这时候,原storyboard新增控件的国际化是我们今天要解决的重点。

  解决方案

  仔细观察storyboard的翻译文件,你会发现这里面也是一个个键值对Key-Value,key是控件ID+状态,Value就是显示文本。

  实际上,storyboard的翻译是由ibtool工具生成的,你可以用terminal进入storyboard所在的目录,输入下面指令,就可以得到一个新的翻译文本

ibtool Main.storyboard --generate-strings-file ./NewTemp.string

  但是这个翻译文本的Key-Value的value是按照storyboard上的文本显示的,不一定是我们想要的,如果要实现更新,我们需要将NewTemp.string和之前的Main.string比较,将NewTemp.string中多出来的Key-Value取出来,追加到Main.string的尾部。

  假设原来我们就有翻译文件A,添加控件后,我们再执行一次国际化指令,生成文件B,我们拿A和B对比,把B中多出来的键值对插入A文件的尾部,将A中有而B中没有的键值对删掉(即控件被删除),这样我们就算是更新了storyboard的翻译文件了。而这一步操作我们可以借助脚本文件来实现,XCode的Run Script也提供了脚本支持,可以让我们在Build后执行脚本。

  脚本代码

  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. """
  4. untitled.py
  5. Created by linyu on 2015-02-13.
  6. Copyright (c) 2015 __MyCompanyName__. All rights reserved.
  7. """
  8. import imp
  9. import sys
  10. import os
  11. import glob
  12. import string
  13. import re
  14. import time
  15. imp.reload(sys)
  16. sys.setdefaultencoding('utf-8') #设置默认编码,只能是utf-8,下面\u4e00-\u9fa5要求的
  17. KSourceFile = 'Base.lproj/*.storyboard'
  18. KTargetFile = '*.lproj/*.strings'
  19. KGenerateStringsFile = 'TempfileOfStoryboardNew.strings'
  20. ColonRegex = ur'["](.*?)["]'
  21. KeyParamRegex = ur'["](.*?)["](\s*)=(\s*)["](.*?)["];'
  22. AnotationRegexPrefix = ur'/(.*?)/'
  23. def getCharaset(string_txt):
  24. filedata = bytearray(string_txt[:4])
  25. if len(filedata) < 4 :
  26. return 0
  27. if (filedata[0] == 0xEF) and (filedata[1] == 0xBB) and (filedata[2] == 0xBF):
  28. print 'utf-8'
  29. return 1
  30. elif (filedata[0] == 0xFF) and (filedata[1] == 0xFE) and (filedata[2] == 0x00) and (filedata[3] == 0x00):
  31. print 'utf-32/UCS-4,little endian'
  32. return 3
  33. elif (filedata[0] == 0x00) and (filedata[1] == 0x00) and (filedata[2] == 0xFE) and (filedata[3] == 0xFF):
  34. print 'utf-32/UCS-4,big endian'
  35. return 3
  36. elif (filedata[0] == 0xFE) and (filedata[1] == 0xFF):
  37. print 'utf-16/UCS-2,little endian'
  38. return 2
  39. elif (filedata[0] == 0xFF) and (filedata[1] == 0xFE):
  40. print 'utf-16/UCS-2,big endian'
  41. return 2
  42. else:
  43. print 'can not recognize!'
  44. return 0
  45. def decoder(string_txt):
  46. var = getCharaset(string_txt)
  47. if var == 1:
  48. return string_txt.decode("utf-8")
  49. elif var == 2:
  50. return string_txt.decode("utf-16")
  51. elif var == 3:
  52. return string_txt.decode("utf-32")
  53. else:
  54. return string_txt
  55. def constructAnotationRegex(str):
  56. return AnotationRegexPrefix + '\n' + str
  57. def getAnotationOfString(string_txt,suffix):
  58. anotationRegex = constructAnotationRegex(suffix)
  59. anotationMatch = re.search(anotationRegex,string_txt)
  60. anotationString = ''
  61. if anotationMatch:
  62. match = re.search(AnotationRegexPrefix,anotationMatch.group(0))
  63. if match:
  64. anotationString = match.group(0)
  65. return anotationString
  66. def compareWithFilePath(newStringPath,originalStringPath):
  67. #read newStringfile
  68. nspf=open(newStringPath,"r")
  69. #newString_txt = str(nspf.read(5000000)).decode("utf-16")
  70. newString_txt = decoder(str(nspf.read(5000000)))
  71. nspf.close()
  72. newString_dic = {}
  73. anotation_dic = {}
  74. for stfmatch in re.finditer(KeyParamRegex , newString_txt):
  75. linestr = stfmatch.group(0)
  76. anotationString = getAnotationOfString(newString_txt,linestr)
  77. linematchs = re.findall(ColonRegex, linestr)
  78. if len(linematchs) == 2:
  79. leftvalue = linematchs[0]
  80. rightvalue = linematchs[1]
  81. newString_dic[leftvalue] = rightvalue
  82. anotation_dic[leftvalue] = anotationString
  83. #read originalStringfile
  84. ospf=open(originalStringPath,"r")
  85. originalString_txt = decoder(str(ospf.read(5000000)))
  86. ospf.close()
  87. originalString_dic = {}
  88. for stfmatch in re.finditer(KeyParamRegex , originalString_txt):
  89. linestr = stfmatch.group(0)
  90. linematchs = re.findall(ColonRegex, linestr)
  91. if len(linematchs) == 2:
  92. leftvalue = linematchs[0]
  93. rightvalue = linematchs[1]
  94. originalString_dic[leftvalue] = rightvalue
  95. #compare and remove the useless param in original string
  96. for key in originalString_dic:
  97. if(key not in newString_dic):
  98. keystr = '"%s"'%key
  99. replacestr = '//'+keystr
  100. match = re.search(replacestr , originalString_txt)
  101. if match is None:
  102. originalString_txt = originalString_txt.replace(keystr,replacestr)
  103. #compare and add new param to original string
  104. executeOnce = 1
  105. for key in newString_dic:
  106. values = (key, newString_dic[key])
  107. if(key not in originalString_dic):
  108. newline = ''
  109. if executeOnce == 1:
  110. timestamp = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))
  111. newline = '\n//##################################################################################\n'
  112. newline +='//# AutoGenStrings '+timestamp+'\n'
  113. newline +='//##################################################################################\n'
  114. executeOnce = 0
  115. newline += '\n'+anotation_dic[key]
  116. newline += '\n"%s" = "%s";\n'%values
  117. originalString_txt += newline
  118. #write into origial file
  119. sbfw=open(originalStringPath,"w")
  120. sbfw.write(originalString_txt)
  121. sbfw.close()
  122. def extractFileName(file_path):
  123. seg = file_path.split('/')
  124. lastindex = len(seg) - 1
  125. return seg[lastindex]
  126. def extractFilePrefix(file_path):
  127. seg = file_path.split('/')
  128. lastindex = len(seg) - 1
  129. prefix = seg[lastindex].split('.')[0]
  130. return prefix
  131. def generateStoryboardStringsfile(storyboard_path,tempstrings_path):
  132. cmdstring = 'ibtool '+storyboard_path+' --generate-strings-file '+tempstrings_path
  133. if os.system(cmdstring) == 0:
  134. return 1
  135. def main():
  136. filePath = sys.argv[1]
  137. sourceFilePath = filePath + '/' + KSourceFile
  138. sourceFile_list = glob.glob(sourceFilePath)
  139. if len(sourceFile_list) == 0:
  140. print 'error dictionary,you should choose the dic upper the Base.lproj'
  141. return
  142. targetFilePath = filePath + '/' + KTargetFile
  143. targetFile_list = glob.glob(targetFilePath)
  144. tempFile_Path = filePath + '/' + KGenerateStringsFile
  145. if len(targetFile_list) == 0:
  146. print 'error framework , no .lproj dic was found'
  147. return
  148. for sourcePath in sourceFile_list:
  149. sourceprefix = extractFilePrefix(sourcePath)
  150. sourcename = extractFileName(sourcePath)
  151. print 'init with %s'%sourcename
  152. if generateStoryboardStringsfile(sourcePath,tempFile_Path) == 1:
  153. print '- - genstrings %s successfully'%sourcename
  154. for targetPath in targetFile_list:
  155. targetprefix = extractFilePrefix(targetPath)
  156. targetname = extractFileName(targetPath)
  157. if cmp(sourceprefix,targetprefix) == 0:
  158. print '- - dealing with %s'%targetPath
  159. compareWithFilePath(tempFile_Path,targetPath)
  160. print 'finish with %s'%sourcename
  161. os.remove(tempFile_Path)
  162. else:
  163. print '- - genstrings %s error'%sourcename
  164. if __name__ == '__main__':
  165. main()

  

  1 新建一个文件夹RunScript,存放python脚本,放在工程文件的目录下,如图:

  

  为了直观可以顺便把该文件夹拖入工程中(不做也可以)

  

  2 Target->Build Phases->New Run Script Phase,在shell里面写入下面指令

  python ${SRCROOT}/${TARGET_NAME}/RunScript/AutoGenStrings.py ${SRCROOT}/${TARGET_NAME}

  

  3 在Storyboard中添加一个textfield控件,再Bulid一下,成功后我们打开翻译文件,发增加了如下文本

  

  4 我们没有必要每次Build都运行一次脚本,所以当你不需要的时候,可以在步骤2选上Run Script Only when installing,这样他就不会跑脚本,具体原因有兴趣的可以自行百度一下。

  以下是相关程序的GitHub地址:

  https://github.com/linyu92/MyGitHub

  

转载于:https://www.cnblogs.com/levilinxi/p/4296712.html

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号