iOS-項目快速支持多語言國際化

前言場景

已存在的項目(中文)突然要支持多語言切換或者國際化了乘盖。前段時間突然遇到這么一個比較急的需求玉雾。在經(jīng)歷了半腳本半人工體力活的加班修改之后(眼睛都快花了),匆匆完成厦幅,內心對結果并不滿意蜘澜,其中還是有著一些重復的施流,不必要的翻譯,最后再去查漏補缺去重鄙信。后面事情告一段落瞪醋。靜下心來,就再研究了下方案的優(yōu)化装诡,把純體力活盡量干掉银受。

整理主要步驟

  1. 獲取漢字及相關頁面文件
  2. strings文案生成及中文中轉映射key
  3. 項目代碼中文添加本地化方法
  4. xib/storyBoard的檢查處理
  5. 檢查驗證,查看實際效果

簡單模擬項目demo準備

  1. 添加設置切換多語言以便查看效果鸦采。
  2. 預先增加兩個已國際化的漢字
  3. 不同頁面同文案
  4. 添加中文注釋宾巍、斷言、log輸出赖淤、特殊字符換行等場景
  5. 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--*--"晚安"

本著stringskey盡量不使用中文的原則踩叭,我們需要中文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ā)這邊的工作小半天基本就足夠了。

鏈接

完整Demo項目鏈接

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末寄猩,一起剝皮案震驚了整個濱河市嫉晶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌田篇,老刑警劉巖替废,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異斯辰,居然都是意外死亡舶担,警方通過查閱死者的電腦和手機坡疼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門彬呻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人柄瑰,你說我怎么就攤上這事闸氮。” “怎么了教沾?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵蒲跨,是天一觀的道長。 經(jīng)常有香客問我授翻,道長或悲,這世上最難降的妖魔是什么孙咪? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮巡语,結果婚禮上翎蹈,老公的妹妹穿的比我還像新娘。我一直安慰自己男公,他們只是感情好荤堪,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著枢赔,像睡著了一般澄阳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上踏拜,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天碎赢,我揣著相機與錄音,去河邊找鬼速梗。 笑死揩抡,一個胖子當著我的面吹牛,可吹牛的內容都是我干的镀琉。 我是一名探鬼主播峦嗤,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼屋摔!你這毒婦竟也來了烁设?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤钓试,失蹤者是張志新(化名)和其女友劉穎装黑,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弓熏,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡恋谭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了挽鞠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疚颊。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖信认,靈堂內的尸體忽然破棺而出材义,到底是詐尸還是另有隱情,我是刑警寧澤嫁赏,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布其掂,位于F島的核電站,受9級特大地震影響潦蝇,放射性物質發(fā)生泄漏款熬。R本人自食惡果不足惜深寥,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贤牛。 院中可真熱鬧翩迈,春花似錦、人聲如沸盔夜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽喂链。三九已至返十,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間椭微,已是汗流浹背洞坑。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蝇率,地道東北人迟杂。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像本慕,于是被迫代替她去往敵國和親排拷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內容

  • 最近公司的項目要求適配國際化锅尘,在過程中遇到挺多有意思的知識點监氢。 多語言大家都知道,字母文字有顯著的形狀差別藤违。 但國...
    PengElement閱讀 6,535評論 0 6
  • 目錄 概覽 各種資源的國際化 1.文本2.圖片3.nib4.其他資源 特定模塊/功能的國際化 1.APP圖標2.應...
    十拿九穩(wěn)啦閱讀 3,565評論 0 6
  • 開發(fā)一款國際化的iOS App浪腐,則必須考慮支持多國家語言,如何實現(xiàn)呢顿乒? 第一议街、國際化——多國家語言;第二璧榄、本土化—...
    John_LS閱讀 3,579評論 0 7
  • 前言 iOS的國際化特漩,即多語言的實現(xiàn),主要有兩種: 跟隨系統(tǒng)語言的自動切換顯示的語言 手動設置語言犹菱,由用戶選擇拾稳,可...
    Fxxxxxxx閱讀 12,248評論 1 16
  • 根據(jù)當前設備語言自動切換顯示吮炕。 幾個涉及到多語言本地化設置的: 1.應用名稱 2.文字 3.圖片腊脱、素材 4.Sto...
    齊玉婷閱讀 3,480評論 2 3