一. 針對兩種文件的國際化處理
代碼中即.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后執行腳本。
腳本代碼
#!/usr/bin/env python
# encoding: utf-8
"""
untitled.py
Created by linyu on 2015-02-13.
Copyright (c) 2015 __MyCompanyName__. All rights reserved.
"""
import imp
import sys
import os
import glob
import string
import re
import time
imp.reload(sys)
sys.setdefaultencoding('utf-8') #設置默認編碼,只能是utf-8,下面\u4e00-\u9fa5要求的
KSourceFile = 'Base.lproj/*.storyboard'
KTargetFile = '*.lproj/*.strings'
KGenerateStringsFile = 'TempfileOfStoryboardNew.strings'
ColonRegex = ur'["](.*?)["]'
KeyParamRegex = ur'["](.*?)["](\s*)=(\s*)["](.*?)["];'
AnotationRegexPrefix = ur'/(.*?)/'
def getCharaset(string_txt):
filedata = bytearray(string_txt[:4])
if len(filedata) < 4 :
return 0
if (filedata[0] == 0xEF) and (filedata[1] == 0xBB) and (filedata[2] == 0xBF):
print 'utf-8'
return 1
elif (filedata[0] == 0xFF) and (filedata[1] == 0xFE) and (filedata[2] == 0x00) and (filedata[3] == 0x00):
print 'utf-32/UCS-4,little endian'
return 3
elif (filedata[0] == 0x00) and (filedata[1] == 0x00) and (filedata[2] == 0xFE) and (filedata[3] == 0xFF):
print 'utf-32/UCS-4,big endian'
return 3
elif (filedata[0] == 0xFE) and (filedata[1] == 0xFF):
print 'utf-16/UCS-2,little endian'
return 2
elif (filedata[0] == 0xFF) and (filedata[1] == 0xFE):
print 'utf-16/UCS-2,big endian'
return 2
else:
print 'can not recognize!'
return 0
def decoder(string_txt):
var = getCharaset(string_txt)
if var == 1:
return string_txt.decode("utf-8")
elif var == 2:
return string_txt.decode("utf-16")
elif var == 3:
return string_txt.decode("utf-32")
else:
return string_txt
def constructAnotationRegex(str):
return AnotationRegexPrefix + '\n' + str
def getAnotationOfString(string_txt,suffix):
anotationRegex = constructAnotationRegex(suffix)
anotationMatch = re.search(anotationRegex,string_txt)
anotationString = ''
if anotationMatch:
match = re.search(AnotationRegexPrefix,anotationMatch.group(0))
if match:
anotationString = match.group(0)
return anotationString
def compareWithFilePath(newStringPath,originalStringPath):
#read newStringfile
nspf=open(newStringPath,"r")
#newString_txt = str(nspf.read(5000000)).decode("utf-16")
newString_txt = decoder(str(nspf.read(5000000)))
nspf.close()
newString_dic = {}
anotation_dic = {}
for stfmatch in re.finditer(KeyParamRegex , newString_txt):
linestr = stfmatch.group(0)
anotationString = getAnotationOfString(newString_txt,linestr)
linematchs = re.findall(ColonRegex, linestr)
if len(linematchs) == 2:
leftvalue = linematchs[0]
rightvalue = linematchs[1]
newString_dic[leftvalue] = rightvalue
anotation_dic[leftvalue] = anotationString
#read originalStringfile
ospf=open(originalStringPath,"r")
originalString_txt = decoder(str(ospf.read(5000000)))
ospf.close()
originalString_dic = {}
for stfmatch in re.finditer(KeyParamRegex , originalString_txt):
linestr = stfmatch.group(0)
linematchs = re.findall(ColonRegex, linestr)
if len(linematchs) == 2:
leftvalue = linematchs[0]
rightvalue = linematchs[1]
originalString_dic[leftvalue] = rightvalue
#compare and remove the useless param in original string
for key in originalString_dic:
if(key not in newString_dic):
keystr = '"%s"'%key
replacestr = '//'+keystr
match = re.search(replacestr , originalString_txt)
if match is None:
originalString_txt = originalString_txt.replace(keystr,replacestr)
#compare and add new param to original string
executeOnce = 1
for key in newString_dic:
values = (key, newString_dic[key])
if(key not in originalString_dic):
newline = ''
if executeOnce == 1:
timestamp = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))
newline = '\n//##################################################################################\n'
newline +='//# AutoGenStrings '+timestamp+'\n'
newline +='//##################################################################################\n'
executeOnce = 0
newline += '\n'+anotation_dic[key]
newline += '\n"%s" = "%s";\n'%values
originalString_txt += newline
#write into origial file
sbfw=open(originalStringPath,"w")
sbfw.write(originalString_txt)
sbfw.close()
def extractFileName(file_path):
seg = file_path.split('/')
lastindex = len(seg) - 1
return seg[lastindex]
def extractFilePrefix(file_path):
seg = file_path.split('/')
lastindex = len(seg) - 1
prefix = seg[lastindex].split('.')[0]
return prefix
def generateStoryboardStringsfile(storyboard_path,tempstrings_path):
cmdstring = 'ibtool '+storyboard_path+' --generate-strings-file '+tempstrings_path
if os.system(cmdstring) == 0:
return 1
def main():
filePath = sys.argv[1]
sourceFilePath = filePath + '/' + KSourceFile
sourceFile_list = glob.glob(sourceFilePath)
if len(sourceFile_list) == 0:
print 'error dictionary,you should choose the dic upper the Base.lproj'
return
targetFilePath = filePath + '/' + KTargetFile
targetFile_list = glob.glob(targetFilePath)
tempFile_Path = filePath + '/' + KGenerateStringsFile
if len(targetFile_list) == 0:
print 'error framework , no .lproj dic was found'
return
for sourcePath in sourceFile_list:
sourceprefix = extractFilePrefix(sourcePath)
sourcename = extractFileName(sourcePath)
print 'init with %s'%sourcename
if generateStoryboardStringsfile(sourcePath,tempFile_Path) == 1:
print '- - genstrings %s successfully'%sourcename
for targetPath in targetFile_list:
targetprefix = extractFilePrefix(targetPath)
targetname = extractFileName(targetPath)
if cmp(sourceprefix,targetprefix) == 0:
print '- - dealing with %s'%targetPath
compareWithFilePath(tempFile_Path,targetPath)
print 'finish with %s'%sourcename
os.remove(tempFile_Path)
else:
print '- - genstrings %s error'%sourcename
if __name__ == '__main__':
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
