前言場景
已存在的項目(中文)突然要支持多語言切換或者國際化了乘盖。前段時間突然遇到這么一個比較急的需求玉雾。在經(jīng)歷了半腳本半人工體力活的加班修改之后(眼睛都快花了),匆匆完成厦幅,內心對結果并不滿意蜘澜,其中還是有著一些重復的施流,不必要的翻譯,最后再去查漏補缺去重鄙信。后面事情告一段落瞪醋。靜下心來,就再研究了下方案的優(yōu)化装诡,把純體力活盡量干掉银受。
整理主要步驟
- 獲取漢字及相關頁面文件
- strings文案生成及中文中轉映射key
- 項目代碼中文添加本地化方法
- xib/storyBoard的檢查處理
- 檢查驗證,查看實際效果
簡單模擬項目demo準備
- 添加設置切換多語言以便查看效果鸦采。
- 預先增加兩個已國際化的漢字
- 不同頁面同文案
- 添加中文注釋宾巍、斷言、log輸出赖淤、特殊字符換行等場景
- xib內包含中文文案
運行效果如下圖
具體流程
1. 獲取漢字及相關頁面文件
漢字的獲取整理是第一步蜀漆,這一塊我們可以用腳本去處理谅河,但是過程中需要注意的有一些不必要處理的要過濾掉咱旱。
- 注釋、log輸出绷耍、斷言等
- 不需要處理的文件包括特定頁面吐限、已處理過的頁面等。
- 已經(jīng)添加國際化的中文褂始。
- 去重诸典,僅針對翻譯,后續(xù)替換時需要替換所有崎苗。
這里用的是python腳本狐粱,主要作用是獲取當前文件夾下特定文件下的中文信息。后面完整項目里有胆数,部分代碼如下肌蜻。
整行的過濾部分如下。str
為逐行讀取的內容必尼。這里過濾條件可視項目情況調整蒋搜。
# log assert類型 忽略
if str.startswith("http://") or str.startswith("DYYLog") or str.startswith("NSLog") or str.startswith("print") or str.startswith("NSAssert") or str.startswith("assert"):
continue
if str.startswith("/*"):
isComment = True
if str.endswith("*/"):
isComment = False
if isComment:
continue
具體漢字匹配后篡撵,已本地化的也需要過濾掉《雇欤可視項目情況調整育谬。
# 匹配包含中文
matchObjs = re.findall(u'"[^"]*[\u4E00-\u9FA5]+[^"\n]*?"', str, re.M|re.S)
if matchObjs and len(matchObjs) > 0:
for cnStr in matchObjs:
# 已本地化則忽略
locali1 = "JJLocalized(" + cnStr
locali2 = "JJLocalized(@" + cnStr
locali3 = cnStr + ".localizedString"
if locali1 in str or locali2 in str or locali3 in str:
continue
直接運行,可以看到xib帮哈、swift膛檀、m文件里的都有過濾出來。
同時同文件夾下生成了三個文件但汞。
- 第一個用于接下來的第二步
- 第二個文件記錄需要處理的xib及storyboard
- 第三個內容是漢字對應的文件完整路徑宿刮,后續(xù)會用到
第一步完成
2. strings文案生成及中文中轉映射key
將py_cnStr.txt
加入項目中。其內容如下私蕾,--*--
僅作為分隔符僵缺。接下來基于以下內容生成strings
內容。
ViewController1.swift--*--"晚上好"
ViewController.m--*--"晚上好"
ViewController1.swift--*--"早安"
SettingVC.swift--*--"切換英文"
SettingVC.swift--*--"切換中文"
ViewController.m--*--"早上好"
ViewController1.swift--*--"“特殊字符”%@%d個"
ViewController.m--*--"中午好"
AppDelegate.m--*--"測試2-(Swift)"
ViewController1.swift--*--"有"
ViewController1.swift--*--"測試\n換行"
AppDelegate.m--*--"測試1-(OC)"
ViewController1.xib--*--"xib標題"
ViewController1.swift--*--"晚安"
本著strings
內key
盡量不使用中文的原則踩叭,我們需要中文key
做一次格式化處理(后續(xù)新增文案應規(guī)范命名)磕潮。如下:
/// 檢測并生成本地化文案
- (void)generationLocalizationStr {
NSString *path = [[NSBundle mainBundle] pathForResource:@"py_cnStr" ofType:@"txt"];
NSString *str = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
NSArray *arr = [str componentsSeparatedByString:@"\n"];
int num2 = 0;
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true) objectAtIndex:0];
NSString *enStringsPath = [docPath stringByAppendingPathComponent:@"waitingTranslation_en.strings"];
NSString *cnStringsPath = [docPath stringByAppendingPathComponent:@"waitingTranslation_cn.strings"];
// 英文 strings
NSMutableArray *enStrArray = [@[] mutableCopy];
// 中文 strings
NSMutableArray *cnStrArray = [@[] mutableCopy];
NSMutableArray *transStrArray = [@[] mutableCopy];
// 包含中文字符串正則
NSString *regex = @"[^\"]*[\u4E00-\u9FA5]+[^\"\n]*?";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
NSMutableDictionary *numDic = [NSMutableDictionary new];
NSMutableDictionary * keyValue = [@{} mutableCopy];
NSMutableSet *set = [NSMutableSet new];
for (NSString *lineStr in arr) {
NSArray *array = [lineStr componentsSeparatedByString:@"--*--"];
NSString *originStr = array.lastObject;
if (originStr.length < 2) {
continue;
}
NSString *txt = [originStr substringWithRange:NSMakeRange(1, originStr.length-2)];
// 用于檢測是否已經(jīng)有對應翻譯了
NSString *localizedTxt = [txt stringByReplacingOccurrencesOfString:@"\\n" withString:@"\n"];
localizedTxt = [localizedTxt stringByReplacingOccurrencesOfString:@"\\\\" withString:@"\\"];
localizedTxt = [localizedTxt stringByReplacingOccurrencesOfString:@"\\\"" withString:@"\""];
if ([set containsObject:localizedTxt]) {
continue;
}
NSString *tran = localizedTxt.localizedString;
BOOL hasCN = [predicate evaluateWithObject:tran];
NSString *fileName = [array.firstObject stringByDeletingPathExtension];
NSString *hashCode = [NSString stringWithFormat:@"%lu",fileName.hash];
hashCode = [hashCode substringToIndex:6];
NSNumber *num = numDic[fileName];
if (!num) {
num = @0;
}
int num1 = [num intValue];
NSString *classPre = fileName;
classPre = [classPre stringByReplacingOccurrencesOfString:@"ViewController" withString:@"VC"];
classPre = [classPre stringByReplacingOccurrencesOfString:@"Controller" withString:@"C"];
classPre = [classPre stringByReplacingOccurrencesOfString:@"View" withString:@"V"];
NSString *key = [NSString stringWithFormat:@"%@_%@_%d",classPre,hashCode,num1];
if (hasCN) {
NSLog(@"-->未多語言化%d:%@",num1,originStr);
[enStrArray addObject:[NSString stringWithFormat:@"\"%@\" = \"\";//%@\n",key,txt]];
[cnStrArray addObject:[NSString stringWithFormat:@"\"%@\" = \"%@\";\n",key,txt]];
num1++;
numDic[fileName] = @(num1);
keyValue[key] = txt;
[set addObject:txt];
} else {
NSLog(@"-->已多語言化%d:%@-%@",num2,originStr,tran);
num2++;
}
}
[enStrArray sortUsingSelector:@selector(compare:)];
[cnStrArray sortUsingSelector:@selector(compare:)];
[transStrArray sortUsingSelector:@selector(compare:)];
NSString *enStr = [enStrArray componentsJoinedByString:@""];
NSString *cnStr = [cnStrArray componentsJoinedByString:@""];
[self writeStr:enStr toPath:enStringsPath];
[self writeStr:cnStr toPath:cnStringsPath];
NSString *keyValuePath = [docPath stringByAppendingPathComponent:@"keyValue.txt"];
NSArray *sortArray = [keyValue.allKeys sortedArrayUsingSelector:@selector(compare:)];
NSMutableString *keyValueStr = [@"" mutableCopy];
for (NSString *key in sortArray) {
[keyValueStr appendFormat:@"\"%@\": \"%@\",\n", keyValue[key],key];
// OC
// [keyValueStr appendFormat:@"@\"%@\": @\"%@\",\n", keyValue[key],key];
}
[self writeStr:keyValueStr toPath:keyValuePath];
}
這里key
的規(guī)則為文件名(縮減)+文件名hashCode
(前6位)+數(shù)字組成。同時對其中漢字去重并過濾掉已國際化的場景容贝,生成strings的過程中自脯,先進行了一次排序,內容會更整齊有序斤富。放在AppDelegate
中調用膏潮,運行項目,控制臺輸出未/已國際化的信息同時在沙盒生成三個文件满力,將其中中文strings文件內容拷入對應Localizable.strings
焕参。英文strings如圖,可以丟給專業(yè)人士填充翻譯后再拷入對應英文strings油额,檢查下特殊字符"
添加轉義叠纷。keyValue.txt
內容拷入LanguageManager.swift
中作為中轉字典。如圖潦嘶,第二步完成涩嚣。
3. 項目代碼中文添加本地化方法
對應關系都好了,就剩下代碼處的調用了掂僵。這里也是腳本添加一步到位航厚,唯一需要注意的是整文件查找替換時已添加國際化的中文辦法區(qū)分,所以先替換锰蓬,再替換還原多替換的場景幔睬。代碼不多,如下:
#-*- coding:utf-8-*-
#處理中文字符的情況
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
import os
import codecs
project_path = os.path.split(os.path.realpath(__file__))[0]
def logYellow(str):
print("\033[36m%s\033[0m"%(str))
def updateFile(file,old_str):
logYellow(file)
with open(file,"r") as f:
file_data = f.read()
f.close
new_str = old_str + ".localizedString"
# 替換
new_file_data = file_data.replace(old_str,new_str)
# 已處理場景需還原
new_file_data = new_file_data.replace(new_str + ".localizedString",new_str)
new_file_data = new_file_data.replace("JJLocalized(%s)"%(new_str),"JJLocalized(%s)"%(old_str))
new_file_data = new_file_data.replace("JJLocalized(@%s)"%(new_str),"JJLocalized(@%s)"%(old_str))
with open(file,"w") as f:
f.write(new_file_data)
f.close
logYellow("已更新" + old_str)
separatorStr = "--*--"
with open(os.path.join(project_path, "py_cn_wholePath.txt"), 'r+') as f:
lineList = f.readlines()
f.close()
for str in lineList:
str = str.decode()
str = str.strip()
path_info = str.split(separatorStr)
cnStr = path_info[1]
updateFile(path_info[0],cnStr)
執(zhí)行腳本互妓,第三步完成溪窒。
4. xib/storyBoard的檢查處理
如果是純代碼的項目坤塞,那這一步可以跳過了。打開前面生成的文件py_xibCnStr.xlsx
澈蚌,這里只能去挨個檢查xib了摹芙,如果xib中控件標題都已經(jīng)是在代碼中設置過的,可以把其中文文案刪掉或者替換成其它值宛瞄。在需要保持中文以便更好理解布局的情況下浮禾,也可以把該xib文件名加入到第一步腳本中的ignoreFileNames
中,沒有連線控件的手動拉線設置文案(又是體力活份汗,這里只能祈禱這種場景比較少了)盈电。
5. 檢查驗證,查看實際效果
前面四步完成后杯活,可以重新運行下第一次的腳本看看未處理的中文是不是清空了匆帚。自測下就可以交給測試了,大功告成旁钧。
總結
總結下來吸重,這方案執(zhí)行下來簡單步驟就是,執(zhí)行腳本->拷貝+翻譯->執(zhí)行腳本->檢查xib/storyBoard->完成歪今。在沒有或較少xib/storyBoard文案的情況下嚎幸,開發(fā)這邊的工作小半天基本就足夠了。