字符串本地化

NSLocalizedString

NSLocalizedString 這個(gè)宏是字符串本地化的核心工具。它還有三個(gè)鮮為人知的變體:NSLocalizedStringFromTable、NSLocalizedStringFromTableInBundle 和 NSLocalizedStringWithDefaultValue。這些宏最終都調(diào)用 NSBundle 的 localizedStringForKey:value:table: 方法來(lái)完成任務(wù)

使用這些宏有兩個(gè)好處:一方面相比直接調(diào)用 localizedStringForKey:value:table: 方法,使用宏讓代碼簡(jiǎn)單易懂磁携;另一方面,類(lèi)似 genstrings 這樣的工具能夠監(jiān)測(cè)到這些宏,從而生成供你翻譯使用的字符串文件括授。這些工具會(huì)解析 .c 和 .m 后綴的文件,然后為其中每一個(gè)需要進(jìn)行本地化的字符串都生成對(duì)應(yīng)條目,并寫(xiě)入到生成的 .strings 文件中荚虚。

如果想讓 genstrings 檢測(cè)自己項(xiàng)目中所有的 .m 后綴文件薛夜,可以執(zhí)行如下命令:

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

-o 選項(xiàng)指定了生成字符串文件的存放目錄,默認(rèn)情況下文件名是 Localizable.strings版述。需要注意的是梯澜,genstrings 默認(rèn)會(huì)覆蓋已存在的同名字符串文件。-a 選項(xiàng)可以讓 genstrings 將生成的條目追加到已存在同名文件的末尾渴析,而不會(huì)覆蓋原文件晚伙。

不過(guò)一般情況下你也許想將生成文件放到另一個(gè)目錄中,然后使用你喜歡的合并工具將它們與已有文件合并以保留已翻譯好的條目俭茧。

字符串文件的格式非常簡(jiǎn)單咆疗,都是鍵值對(duì)的形式:

/* Insert new contact button */

"contact-editor.insert-new-contact-button" = "Insert contact";

/* Delete contact button */

"contact-editor.delete-contact-button" = "Delete contact";

字符串文件現(xiàn)在可以保存成 UTF-8 格式了,因?yàn)?Xcode 在構(gòu)建過(guò)程中能夠?qū)⑺鼈冝D(zhuǎn)換成所需的 UTF-16 格式母债。

應(yīng)用中哪些字符串需要本地化民傻?

一般而言,所有你想以某種形式展現(xiàn)在用戶(hù)眼前的字符串都需要本地化场斑,包括標(biāo)簽和按鈕上的文本漓踢,或者在運(yùn)行時(shí)通過(guò)格式化字符串和數(shù)據(jù)動(dòng)態(tài)生成的字符串。

在本地化字符串時(shí)漏隐,根據(jù)語(yǔ)法規(guī)則為每一種類(lèi)型的語(yǔ)句定義一個(gè)可本地化的字符串是非常重要的喧半。假設(shè)你在應(yīng)用中需要顯示「Paul invited you」和「You invited Paul」,那么只本地化格式化字符串「%@ invited %@」看起來(lái)是個(gè)不錯(cuò)的選擇青责,這樣在合適的時(shí)候把「you」本地化之后插入進(jìn)去就可以完成任務(wù)挺据。

在英語(yǔ)中這種做法沒(méi)什么問(wèn)題,但是請(qǐng)謹(jǐn)記脖隶,當(dāng)把這種小伎倆應(yīng)用到其他語(yǔ)言中時(shí)基本都會(huì)以失敗而告終扁耐。以德語(yǔ)為例,「Paul invited you」譯為「Paul hat dich eingeladen」产阱,而「You invited Paul」則譯為「Du hast Paul eingeladen」婉称。

正確的做法是定義兩個(gè)可本地化字符串「%@ invited you」和「You invited %@」,只有這樣翻譯器才能正確處理其他語(yǔ)言的特殊語(yǔ)法規(guī)則。

永遠(yuǎn)不要將句子分解為幾個(gè)部分,而要將它們作為一個(gè)完整的可本地化字符串鳖枕。如果一個(gè)句子與另一個(gè)句子的語(yǔ)法規(guī)則并不完全一致,那么即使它們?cè)谀愕哪刚Z(yǔ)中看起來(lái)極為相像俗壹,也要?jiǎng)?chuàng)建兩個(gè)可本地化字符串。

字符串鍵值最佳實(shí)踐

使用 NSLocalizedString 宏的時(shí)候藻烤,第一個(gè)參數(shù)就是為每個(gè)特殊字符串指定的鍵值(key)绷雏。程序員經(jīng)常使用母語(yǔ)中的單詞作為鍵值头滔,這樣乍一看是個(gè)便利的方案,但是實(shí)際上相當(dāng)糟糕涎显,會(huì)引發(fā)非常嚴(yán)重的錯(cuò)誤拙毫。

在一個(gè)字符串文件中,鍵值需要具有唯一性棺禾,因此任何母語(yǔ)中字面上具有唯一性的單詞在翻譯為其他語(yǔ)言的時(shí)候也必須具有唯一性缀蹄。這一點(diǎn)是無(wú)法滿(mǎn)足的,因?yàn)橐粋€(gè)單詞翻譯為其他語(yǔ)言時(shí)經(jīng)常會(huì)有多種意思膘婶,需要對(duì)應(yīng)到多種文字表示缺前。

以英文單詞「run」為例,作為名詞表示「跑步」悬襟,作為動(dòng)詞表示「奔跑」衅码,在翻譯的時(shí)候要加以區(qū)別。而且根據(jù)上下文的不同脊岳,每種具體的譯法在文字上可能還會(huì)有細(xì)微變化逝段。

一個(gè)健身應(yīng)用在不同的地方用到這個(gè)單詞的不同意思是很正常的,但是如果你使用下面的方法來(lái)進(jìn)行本地化

NSLocalizedString(@"Run", nil)

無(wú)論第二個(gè)參數(shù)指定了注釋內(nèi)容還是留空割捅,你在字符串文件中都只有一個(gè)「run」的條目奶躯。而在德語(yǔ)中,「run」作名詞時(shí)應(yīng)該譯為「Lauf」亿驾,作動(dòng)詞時(shí)則應(yīng)該譯為「laufen」嘹黔,或者在特定情況下譯為完全不同的形式比如「loslaufen」和「Los geht’s」。

好的鍵值應(yīng)該滿(mǎn)足兩個(gè)條件:首先鍵值必須在每個(gè)具體的上下文中保持唯一性莫瞬,其次如果我們沒(méi)有翻譯特定的那個(gè)上下文儡蔓,那么它們不會(huì)被其他情況覆蓋到而被翻譯。

本文推薦使用如下的命名空間方法:

NSLocalizedString(@"activity-profile.title.the-run", nil)

NSLocalizedString(@"home.button.start-run", nil)

這樣的鍵值可以區(qū)分應(yīng)用中不同地方出現(xiàn)的單詞疼邀,同時(shí)提供具體的上下文喂江,比如是標(biāo)題中的或者按鈕中的。上面的例子里我們?yōu)榱撕?jiǎn)便忽略了第二個(gè)參數(shù)旁振,實(shí)際使用中如果鍵值本身沒(méi)有提供清晰的上下文說(shuō)明获询,你可以將進(jìn)一步的說(shuō)明作為第二個(gè)參數(shù)傳入。同時(shí)請(qǐng)確保鍵值中只含有 ASCII 字符规求。

分割字符串文件

正如我們一開(kāi)始提到的筐付,NSLocalizedString 有一些變體能夠提供更多字符串本地化的操作方式卵惦。NSLocalizedStringFromTable 接收 key阻肿、table 和 comment 這三個(gè)參數(shù),其中 table 參數(shù)表示該字符串對(duì)應(yīng)的一個(gè)表格沮尿,genstrings 會(huì)為表中的每一個(gè)條目生成一個(gè)以條目名稱(chēng)(假設(shè)為 table-item)命名的獨(dú)立字符串文件 table-item.strings丛塌。

這樣你就可以把字符串文件分割成幾個(gè)小一些的文件较解。在一個(gè)龐大的項(xiàng)目或者團(tuán)隊(duì)中工作時(shí),這一點(diǎn)顯得尤為重要赴邻。同時(shí)這也讓合并原有的和重新生成的字符串文件變得容易一些印衔。

相比在每個(gè)地方調(diào)用下面的語(yǔ)句:

NSLocalizedStringFromTable(@"home.button.start-run", @"ActivityTracker", @"some comment..")

你可以自定義一個(gè)用于字符串本地化的函數(shù)來(lái)讓工作變得輕松一些

static NSString * LocalizedActivityTrackerString(NSString *key, NSString *comment) {

return [[NSBundle mainBundle] localizedStringForKey:key value:key table:@"ActivityTracker"];

}

為了給所有調(diào)用此函數(shù)的地方生成字符串文件,你可以在執(zhí)行 genstrings 的時(shí)候加上 -s 選項(xiàng):

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

-s 這個(gè)選項(xiàng)指定了本地化函數(shù)的共同前綴名稱(chēng)姥敛,如果你還定義了 LocalizedActivityTrackerStringFromTable奸焙,LocalizedActivityTrackerStringFromTableInBundle, LocalizedActivityTrackerStringWithDefaultValue 等函數(shù)彤敛,以上命令也會(huì)調(diào)用它們与帆。

運(yùn)用格式化字符串

我們經(jīng)常需要對(duì)一些在運(yùn)行時(shí)才能最終確定下來(lái)的字符串進(jìn)行本地化,格式化字符串可以完成這項(xiàng)工作墨榄。Foundation 在這方面提供了一些非常強(qiáng)大的特性玄糟。(可以參考Daniel 的文章獲得更多關(guān)于格式化字符串的細(xì)節(jié)

以字符串「Run 1 out of 3 completed.」為例,我們可以這樣構(gòu)造格式化字符串:

NSString *localizedString = NSLocalizedString(@"activity-profile.label.run %lu out of %lu completed", nil);

self.label.text = [NSString localizedStringWithFormat:localizedString, completedRuns, totalRuns];

在翻譯的時(shí)候經(jīng)常需要對(duì)其中的格式化占位符進(jìn)行順序調(diào)整以符合語(yǔ)法袄秩,幸運(yùn)的是我們可以在字符串文件中輕松地搞定:

"activity-profile.label.run %lu out of %lu completed" = "Von %2$lu L?ufen hast du %$1lu absolver";

上面的德文翻譯得不是非常好阵翎,只是單純用來(lái)說(shuō)明調(diào)換占位符順序的功能而已。

如果你需要對(duì)簡(jiǎn)單的整數(shù)或者浮點(diǎn)數(shù)進(jìn)行本地化之剧,你可以使用 localizedStringWithFormat: 這個(gè)變體郭卫。數(shù)字本地化的更高級(jí)用法涉及 NSNumberFormatter,會(huì)在本文后面講到

單復(fù)數(shù)與陰陽(yáng)性

在 OS X 10.9 和 iOS 7 中背稼,本地化字符串的時(shí)候可以使用比替換格式化字符串中的占位符更酷的特性:蘋(píng)果官方想處理不同語(yǔ)言中對(duì)于名詞復(fù)數(shù)和不同性別采取的不同變化箱沦。

讓我們?cè)倏匆幌轮暗睦樱篅”%lu out of %lu runs completed.” 這個(gè)翻譯在「跑多次」的時(shí)候才是對(duì)的(譯者注:即第二個(gè) %lu 代表的數(shù)字大于 1),所以我們不得不定義兩個(gè)不同的字符串來(lái)處理單次和多次的情況:

@"%lu out of one run completed"

@"%lu out of %lu runs completed"

這種做法在英語(yǔ)中是對(duì)的雇庙,但是在其他很多語(yǔ)言中會(huì)出錯(cuò)谓形。比如希伯來(lái)語(yǔ)中名詞有三種形式:第一種是單數(shù)和十的倍數(shù),第二種是 2疆前,第三種是其他的復(fù)數(shù)寒跳。克羅地亞語(yǔ)中竹椒,個(gè)位數(shù)為 1 的數(shù)字有單獨(dú)的表示方法:「31 od 32 staze zavr?ene」童太,與之相對(duì)的是「5 od 8 staza zavr?ene」(注意其中「staze」和「staza」的差別)。很多語(yǔ)言針對(duì)非整型數(shù)也有不同的表達(dá)方式胸完。

想全面了解這個(gè)問(wèn)題可以參見(jiàn)基于 Unicode 的語(yǔ)言復(fù)數(shù)規(guī)則书释。其中涵蓋的變化之博大精深令人嘆為觀(guān)止。

為了在 10.9 和 iOS 7 平臺(tái)上正確處理這個(gè)問(wèn)題赊窥,我們需要如下構(gòu)造可本地化字符串:

[NSString localizedStringWithFormat:NSLocalizedString(@"activity-profile.label.%lu out of %lu runs completed"), completedRuns, totalRuns];

然后我們?cè)?.strings 后綴文件所處目錄中創(chuàng)建一個(gè)同名的 .stringsdict 后綴的文件爆惧,如果前者名為 Localizable.strings,則后者為 Localizable.stringsdict锨能。保留 .strings 后綴的字符串文件是必須的扯再,即使它里面什么內(nèi)容也沒(méi)有芍耘。這個(gè) .stringsdict 后綴的字符串字典文件是一個(gè)屬性列表(plist)文件,比字符串文件復(fù)雜得多熄阻,換來(lái)的是正確處理所有語(yǔ)言的名詞復(fù)數(shù)問(wèn)題斋竞,而不需要將處理邏輯寫(xiě)在代碼中。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末秃殉,一起剝皮案震驚了整個(gè)濱河市坝初,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钾军,老刑警劉巖脖卖,帶你破解...
    沈念sama閱讀 212,029評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異巧颈,居然都是意外死亡畦木,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)砸泛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)十籍,“玉大人,你說(shuō)我怎么就攤上這事唇礁」蠢酰” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,570評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵盏筐,是天一觀(guān)的道長(zhǎng)围俘。 經(jīng)常有香客問(wèn)我,道長(zhǎng)琢融,這世上最難降的妖魔是什么界牡? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,535評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮漾抬,結(jié)果婚禮上宿亡,老公的妹妹穿的比我還像新娘。我一直安慰自己纳令,他們只是感情好挽荠,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,650評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著平绩,像睡著了一般圈匆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捏雌,一...
    開(kāi)封第一講書(shū)人閱讀 49,850評(píng)論 1 290
  • 那天跃赚,我揣著相機(jī)與錄音,去河邊找鬼腹忽。 笑死来累,一個(gè)胖子當(dāng)著我的面吹牛砚作,可吹牛的內(nèi)容都是我干的窘奏。 我是一名探鬼主播嘹锁,決...
    沈念sama閱讀 39,006評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼着裹!你這毒婦竟也來(lái)了领猾?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,747評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤骇扇,失蹤者是張志新(化名)和其女友劉穎摔竿,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體少孝,經(jīng)...
    沈念sama閱讀 44,207評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡继低,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,536評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了稍走。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片袁翁。...
    茶點(diǎn)故事閱讀 38,683評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖婿脸,靈堂內(nèi)的尸體忽然破棺而出粱胜,到底是詐尸還是另有隱情,我是刑警寧澤狐树,帶...
    沈念sama閱讀 34,342評(píng)論 4 330
  • 正文 年R本政府宣布焙压,位于F島的核電站,受9級(jí)特大地震影響抑钟,放射性物質(zhì)發(fā)生泄漏涯曲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,964評(píng)論 3 315
  • 文/蒙蒙 一在塔、第九天 我趴在偏房一處隱蔽的房頂上張望掀抹。 院中可真熱鬧,春花似錦心俗、人聲如沸傲武。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,772評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)揪利。三九已至,卻和暖如春狠持,著一層夾襖步出監(jiān)牢的瞬間疟位,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,004評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工喘垂, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留甜刻,地道東北人绍撞。 一個(gè)月前我還...
    沈念sama閱讀 46,401評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像得院,于是被迫代替她去往敵國(guó)和親傻铣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,566評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容