字符串本地化

NSLocalizedString

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

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

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

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

-o 選項指定了生成字符串文件的存放目錄,默認情況下文件名是 Localizable.strings。需要注意的是,genstrings 默認會覆蓋已存在的同名字符串文件。-a 選項可以讓 genstrings 將生成的條目追加到已存在同名文件的末尾,而不會覆蓋原文件。

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

字符串文件的格式非常簡單,都是鍵值對的形式:

/* 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 格式了,因為 Xcode 在構(gòu)建過程中能夠?qū)⑺鼈冝D(zhuǎn)換成所需的 UTF-16 格式。

應用中哪些字符串需要本地化?

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

在本地化字符串時,根據(jù)語法規(guī)則為每一種類型的語句定義一個可本地化的字符串是非常重要的。假設你在應用中需要顯示「Paul invited you」和「You invited Paul」,那么只本地化格式化字符串「%@ invited %@」看起來是個不錯的選擇,這樣在合適的時候把「you」本地化之后插入進去就可以完成任務。

在英語中這種做法沒什么問題,但是請謹記,當把這種小伎倆應用到其他語言中時基本都會以失敗而告終。以德語為例,「Paul invited you」譯為「Paul hat dich eingeladen」,而「You invited Paul」則譯為「Du hast Paul eingeladen」。

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

永遠不要將句子分解為幾個部分,而要將它們作為一個完整的可本地化字符串。如果一個句子與另一個句子的語法規(guī)則并不完全一致,那么即使它們在你的母語中看起來極為相像,也要創(chuàng)建兩個可本地化字符串。

字符串鍵值最佳實踐

使用 NSLocalizedString 宏的時候,第一個參數(shù)就是為每個特殊字符串指定的鍵值(key)。程序員經(jīng)常使用母語中的單詞作為鍵值,這樣乍一看是個便利的方案,但是實際上相當糟糕,會引發(fā)非常嚴重的錯誤。

在一個字符串文件中,鍵值需要具有唯一性,因此任何母語中字面上具有唯一性的單詞在翻譯為其他語言的時候也必須具有唯一性。這一點是無法滿足的,因為一個單詞翻譯為其他語言時經(jīng)常會有多種意思,需要對應到多種文字表示。

以英文單詞「run」為例,作為名詞表示「跑步」,作為動詞表示「奔跑」,在翻譯的時候要加以區(qū)別。而且根據(jù)上下文的不同,每種具體的譯法在文字上可能還會有細微變化。

一個健身應用在不同的地方用到這個單詞的不同意思是很正常的,但是如果你使用下面的方法來進行本地化

NSLocalizedString(@"Run", nil)

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

好的鍵值應該滿足兩個條件:首先鍵值必須在每個具體的上下文中保持唯一性,其次如果我們沒有翻譯特定的那個上下文,那么它們不會被其他情況覆蓋到而被翻譯。

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

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

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

這樣的鍵值可以區(qū)分應用中不同地方出現(xiàn)的單詞,同時提供具體的上下文,比如是標題中的或者按鈕中的。上面的例子里我們?yōu)榱撕啽愫雎粤说诙€參數(shù),實際使用中如果鍵值本身沒有提供清晰的上下文說明,你可以將進一步的說明作為第二個參數(shù)傳入。同時請確保鍵值中只含有 ASCII 字符。

分割字符串文件

正如我們一開始提到的,NSLocalizedString 有一些變體能夠提供更多字符串本地化的操作方式。NSLocalizedStringFromTable 接收 key、table 和 comment 這三個參數(shù),其中 table 參數(shù)表示該字符串對應的一個表格,genstrings 會為表中的每一個條目生成一個以條目名稱(假設為 table-item)命名的獨立字符串文件 table-item.strings。

這樣你就可以把字符串文件分割成幾個小一些的文件。在一個龐大的項目或者團隊中工作時,這一點顯得尤為重要。同時這也讓合并原有的和重新生成的字符串文件變得容易一些。

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

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

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

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

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

}

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

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

-s 這個選項指定了本地化函數(shù)的共同前綴名稱,如果你還定義了 LocalizedActivityTrackerStringFromTable,LocalizedActivityTrackerStringFromTableInBundle, LocalizedActivityTrackerStringWithDefaultValue 等函數(shù),以上命令也會調(diào)用它們。

運用格式化字符串

我們經(jīng)常需要對一些在運行時才能最終確定下來的字符串進行本地化,格式化字符串可以完成這項工作。Foundation 在這方面提供了一些非常強大的特性。(可以參考Daniel 的文章獲得更多關于格式化字符串的細節(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];

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

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

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

如果你需要對簡單的整數(shù)或者浮點數(shù)進行本地化,你可以使用 localizedStringWithFormat: 這個變體。數(shù)字本地化的更高級用法涉及 NSNumberFormatter,會在本文后面講到

單復數(shù)與陰陽性

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

讓我們再看一下之前的例子:@”%lu out of %lu runs completed.” 這個翻譯在「跑多次」的時候才是對的(譯者注:即第二個 %lu 代表的數(shù)字大于 1),所以我們不得不定義兩個不同的字符串來處理單次和多次的情況:

@"%lu out of one run completed"

@"%lu out of %lu runs completed"

這種做法在英語中是對的,但是在其他很多語言中會出錯。比如希伯來語中名詞有三種形式:第一種是單數(shù)和十的倍數(shù),第二種是 2,第三種是其他的復數(shù)。克羅地亞語中,個位數(shù)為 1 的數(shù)字有單獨的表示方法:「31 od 32 staze zavr?ene」,與之相對的是「5 od 8 staza zavr?ene」(注意其中「staze」和「staza」的差別)。很多語言針對非整型數(shù)也有不同的表達方式。

想全面了解這個問題可以參見基于 Unicode 的語言復數(shù)規(guī)則。其中涵蓋的變化之博大精深令人嘆為觀止。

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

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

然后我們在 .strings 后綴文件所處目錄中創(chuàng)建一個同名的 .stringsdict 后綴的文件,如果前者名為 Localizable.strings,則后者為 Localizable.stringsdict。保留 .strings 后綴的字符串文件是必須的,即使它里面什么內(nèi)容也沒有。這個 .stringsdict 后綴的字符串字典文件是一個屬性列表(plist)文件,比字符串文件復雜得多,換來的是正確處理所有語言的名詞復數(shù)問題,而不需要將處理邏輯寫在代碼中。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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