一個應用在進行多語言本地化的時候涉及到大量的工做。由於這一期的主題是字符串,因此本文主要探討字符串的本地化。字符串本地化有兩種方法:修改代碼或修改 nib 文件和 storyboard。本文將專一於經過代碼實現字符串的本地化。php
NSLocalizedString
這個宏是字符串本地化的核心工具。它還有三個不爲人知的變體:NSLocalizedStringFromTable
、NSLocalizedStringFromTableInBundle
和 NSLocalizedStringWithDefaultValue
。這些宏最終都調用 NSBundle
的 localizedStringForKey:value:table:
方法來完成任務。html
使用這些宏有兩個好處:一方面相比直接調用 localizedStringForKey:value:table:
方法,使用宏讓代碼簡單易懂;另外一方面,相似 genstrings 這樣的工具可以監測到這些宏,從而生成供你翻譯使用的字符串文件。這些工具會解析 .c 和 .m 後綴的文件,而後爲其中每個須要進行本地化的字符串都生成對應條目,並寫入到生成的 .strings 文件中。ios
若是想讓 genstrings
檢測本身項目中全部的 .m
後綴文件,能夠執行以下命令:git
find . -name *.m | xargs genstrings -o en.lproj
複製代碼
-o
選項指定了生成字符串文件的存放目錄,默認狀況下文件名是 Localizable.strings
。須要注意的是,genstrings
默認會覆蓋已存在的同名字符串文件。-a
選項可讓 genstrings
將生成的條目追加到已存在同名文件的末尾,而不會覆蓋原文件。程序員
不過通常狀況下你也許想將生成文件放到另外一個目錄中,而後使用你喜歡的合併工具將它們與已有文件合併以保留已翻譯好的條目。github
字符串文件的格式很是簡單,都是鍵值對的形式:面試
/* Insert new contact button */
"contact-editor.insert-new-contact-button" = "Insert contact";
/* Delete contact button */
"contact-editor.delete-contact-button" = "Delete contact";
複製代碼
更復雜的操做好比在須要本地化的字符串中插入格式化佔位符等,咱們將在稍後談到。緩存
另外,字符串文件如今能夠保存成 UTF-8 格式了,由於 Xcode 在構建過程當中可以將它們轉換成所需的 UTF-16 格式。安全
通常而言,全部你想以某種形式展示在用戶眼前的字符串都須要本地化,包括標籤和按鈕上的文本,或者在運行時經過格式化字符串和數據動態生成的字符串。bash
在本地化字符串時,根據語法規則爲每一種類型的語句定義一個可本地化的字符串是很是重要的。假設你在應用中須要顯示「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 %@」,只有這樣翻譯器才能正確處理其餘語言的特殊語法規則。
永遠不要將句子分解爲幾個部分,而要將它們做爲一個完整的可本地化字符串。若是一個句子與另外一個句子的語法規則並不徹底一致,那麼即便它們在你的母語中看起來極爲相像,也要建立兩個可本地化字符串。
使用 NSLocalizedString
宏的時候,第一個參數就是爲每一個特殊字符串指定的鍵值(key)。程序員常用母語中的單詞做爲鍵值,這樣乍一看是個便利的方案,可是實際上至關糟糕,會引起很是嚴重的錯誤。
在一個字符串文件中,鍵值須要具備惟一性,所以任何母語中字面上具備惟一性的單詞在翻譯爲其餘語言的時候也必須具備惟一性。這一點是沒法知足的,由於一個單詞翻譯爲其餘語言時常常會有多種意思,須要對應到多種文字表示。
以英文單詞「run」爲例,做爲名詞表示「跑步」,做爲動詞表示「奔跑」,在翻譯的時候要加以區別。並且根據上下文的不一樣,每種具體的譯法在文字上可能還會有細微變化。
一個健身應用在不一樣的地方用到這個單詞的不一樣意思是很正常的,可是若是你使用下面的方法來進行本地化:
NSLocalizedString(@"Run", nil)
複製代碼
不管第二個參數指定了註釋內容仍是留空,你在字符串文件中都只有一個「run」的條目。而在德語中,「run」做名詞時應該譯爲「Lauf」,做動詞時則應該譯爲「laufen」,或者在特定狀況下譯爲徹底不一樣的形式好比「loslaufen」和「Los geht’s」。
好的鍵值應該知足兩個條件:首先鍵值必須在每一個具體的上下文中保持惟一性,其次若是咱們沒有翻譯特定的那個上下文,那麼它們不會被其餘狀況覆蓋到而被翻譯。
本文推薦使用以下的命名空間方法:
NSLocalizedString(@"activity-profile.title.the-run", nil)
NSLocalizedString(@"home.button.start-run", nil)
複製代碼
這樣的鍵值能夠區分應用中不一樣地方出現的單詞,同時提供具體的上下文,好比是標題中的或者按鈕中的。上面的例子裏咱們爲了簡便忽略了第二個參數,實際使用中若是鍵值自己沒有提供清晰的上下文說明,你能夠將進一步的說明做爲第二個參數傳入。同時請確保鍵值中只含有 ASCII 字符。
正如咱們一開始提到的,NSLocalizedString
有一些變體可以提供更多字符串本地化的操做方式。NSLocalizedStringFromTable
接收 key、table 和 comment 這三個參數,其中 table 參數表示該字符串對應的一個表格,genstrings
會爲表中的每個條目生成一個以條目名稱(假設爲 table-item)命名的獨立字符串文件 table-item.strings
。
這樣你就能夠把字符串文件分割成幾個小一些的文件。在一個龐大的項目或者團隊中工做時,這一點顯得尤其重要。同時這也讓合併原有的和從新生成的字符串文件變得容易一些。
相比在每一個地方調用下面的語句:
NSLocalizedStringFromTable(@"home.button.start-run", @"ActivityTracker", @"some comment..")
複製代碼
你能夠自定義一個用於字符串本地化的函數來讓工做變得輕鬆一些
static NSString * LocalizedActivityTrackerString(NSString *key, NSString *comment) {
return [[NSBundle mainBundle] localizedStringForKey:key value:key table:@"ActivityTracker"];
}
複製代碼
爲了給全部調用此函數的地方生成字符串文件,你能夠在執行 genstrings
的時候加上 -s
選項:
find . -name *.m | xargs genstrings -o en.lproj -s LocalizedActivityTrackerString
複製代碼
-s
這個選項指定了本地化函數的共同前綴名稱,若是你還定義了 LocalizedActivityTrackerStringFromTable
,LocalizedActivityTrackerStringFromTableInBundle
, LocalizedActivityTrackerStringWithDefaultValue
等函數,以上命令也會調用它們。
咱們常常須要對一些在運行時才能最終肯定下來的字符串進行本地化,格式化字符串能夠完成這項工做。Foundation 在這方面提供了一些很是強大的特性。(能夠參考Daniel 的文章得到更多關於格式化字符串的細節)
以字符串「Run 1 out of 3 completed.」爲例,咱們能夠這樣構造格式化字符串:
NSString *localizedString = NSLocalizedString(@"activity-profile.label.run %lu out of %lu completed", nil);
self.label.text = [NSString localizedStringWithFormat:localizedString, completedRuns, totalRuns];
複製代碼
在翻譯的時候常常須要對其中的格式化佔位符進行順序調整以符合語法,幸運的是咱們能夠在字符串文件中輕鬆地搞定:
"activity-profile.label.run %lu out of %lu completed" = "Von %2$lu Läufen hast du %1$lu absolviert";
複製代碼
上面的德文翻譯得不是很是好,只是單純用來講明調換佔位符順序的功能而已。
若是你須要對簡單的整數或者浮點數進行本地化,你可使用 localizedStringWithFormat:
這個變體。數字本地化的更高級用法涉及 NSNumberFormatter
,會在本文後面講到。
在 OS X 10.9 和 iOS 7 中,本地化字符串的時候可使用比替換格式化字符串中的佔位符更酷的特性:蘋果官方想處理不一樣語言中對於名詞複數和不一樣性別採起的不一樣變化。
讓咱們再看一下以前的例子:@」%lu out of %lu runs completed.」 這個翻譯在「跑屢次」的時候纔是對的(譯者注:即第二個 %lu 表明的數字大於 1),因此咱們不得不定義兩個不一樣的字符串來處理單次和屢次的狀況:
@"%lu out of one run completed"
@"%lu out of %lu runs completed"
複製代碼
這種作法在英語中是對的,可是在其餘不少語言中會出錯。好比希伯來語中名詞有三種形式:第一種是單數和十的倍數,第二種是 2,第三種是其餘的複數。克羅地亞語中,個位數爲 1 的數字有單獨的表示方法:「31 od 32 staze završene」,與之相對的是「5 od 8 staza završene」(注意其中「staze」和「staza」的差異)。不少語言針對非整型數也有不一樣的表達方式。
想全面瞭解這個問題能夠參見基於 Unicode 的語言複數規則。其中涵蓋的變化之博大精深使人歎爲觀止。
爲了在 10.9 和 iOS 7 平臺上正確處理這個問題,咱們須要以下構造可本地化字符串:
[NSString localizedStringWithFormat:NSLocalizedString(@"activity-profile.label.%lu out of %lu runs completed"), completedRuns, totalRuns];
複製代碼
而後咱們在 .strings
後綴文件所處目錄中建立一個同名的 .stringsdict
後綴的文件,若是前者名爲 Localizable.strings
,則後者爲 Localizable.stringsdict
。保留 .strings
後綴的字符串文件是必須的,即便它裏面什麼內容也沒有。這個 .stringsdict
後綴的字符串字典文件是一個屬性列表(plist
)文件,比字符串文件複雜得多,換來的是正確處理全部語言的名詞複數問題,而不須要將處理邏輯寫在代碼中。
下面是一個該文件的例子:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>activity-profile.label.%lu out of %lu runs completed</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%lu out of %#@lu_total_runs@ completed</string>
<key>lu_total_runs</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>lu</string>
<key>one</key>
<string>%lu run</string>
<key>other</key>
<string>%lu runs</string>
</dict>
</dict>
</dict>
</plist>
複製代碼
頂層字典的鍵值即爲待翻譯的字符串(即 activity-profile.label.%lu out of %lu runs completed
),在下層字典中又指定了 NSStringLocalizedFormatKey
所需的格式化字符串。爲了將不一樣的佔位符替換爲不一樣的數字,必須擴展格式化字符串的語法。因此咱們能夠定義相似 %#@lu_total_runs@
的格式化字符串,而後定義一個字典來解析它。在上面的字典中,咱們經過將 NSStringFormatSpecTypeKey
設置爲 NSStringPluralRuleType
代表這是一個處理名詞複數的規則,指定了值的類型(在本例中是 lu,即無符號長整數),還定義了針對不一樣複數形式的不一樣輸出(能夠從「zero」、「one」、「few」、「many」和「others」中選擇,上例中僅制定了「one」和「other」)。
這是一個很是強大的特性,不但能夠處理其餘語言中多種複數形式的問題,還能夠爲不一樣的數字定製不一樣的字面表示。
咱們還能夠更進一步定義遞歸的規則。爲了讓上面例子的輸出更友好,咱們須要覆蓋以下幾種自定義的字符串用例:
Completed runs Total Runs Output
------------------------------------------------------------------
0 0+ No runs completed yet
1 1 One run completed
1 2+ One of x runs completed
2+ 2+ x of y runs completed
複製代碼
咱們能夠經過字符串字典後綴文件來處理以上四種狀況,而無需修改代碼邏輯,以下所示:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>scope.%lu out of %lu runs</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%1$#@lu_completed_runs@</string>
<key>lu_completed_runs</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>lu</string>
<key>zero</key>
<string>No runs completed yet</string>
<key>one</key>
<string>One %2$#@lu_total_runs@</string>
<key>other</key>
<string>%lu %2$#@lu_total_runs@</string>
</dict>
<key>lu_total_runs</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>lu</string>
<key>one</key>
<string>run completed</string>
<key>other</key>
<string>of %lu runs completed</string>
</dict>
</dict>
</dict>
</plist>
複製代碼
調用 localizedStringForKey:value:table:
會返回根據字符串字典文件中的鍵值對進行初始化的字符串集合,這些字符串都是包含字符串字典文件中信息的的代理對象(proxy objects)。這些信息在調用 copy
和 mutableCopy
進行字符串拷貝的時候會被保留,可是一旦你修改了該字符串,這些額外信息就會丟失。更多細節請參見 OS X 10.9 的 Foundation 發行說明。
若是你要修改一個用戶可見字符串的大小寫,請必定使用包含本地化功能的 NSString
方法變體:lowercaseStringWithLocale:
和 uppercaseStringWithLocale:
。
調用這些方法的時候你須要傳入區域設置參數 locale ,這樣就能夠將大小寫的改變應用到本地化以後的其餘語言版本中。當你使用 NSLocalizedString
及其變體的那些宏時無須擔憂本地化後的大小寫問題,由於在方法內部已經自動作了處理,並且在用戶選擇的語言不可用時會使用默認語言來代替。
爲了用戶界面的一致性,使用**區域設置(locale)**來本地化界面的其餘部分是一個很好的方法,能夠參見後面的小節「選擇正確的區域設置」。
通常而言你應該始終用 NSURL
來表現文件路徑,由於這會讓文件名的本地化變得容易:
NSURL *url = [NSURL fileURLWithPath:@"/Applications/System Preferences.app"];
NSString *name;
[url getResourceValue:&name forKey:NSURLLocalizedTypeDescriptionKey error:NULL];
NSLog(@"localized name: %@", name);
// output: System Preferences.app
複製代碼
以上輸出在英語系統中是正確的,可是假設咱們換到了阿拉伯語系統中,系統設置被稱爲「تفضيلات النظام.app」。
構造這樣一個其餘語言的文件名是否包含後綴須要參照用戶 Finder 中的相關選項。若是你須要獲取文件的類型,也能夠這樣調用 NSURLLocalizedTypeDescriptionKey
來從中得到。
本地化以後的文件名僅供顯示使用,不能用來訪問實際的文件資源,能夠參考 Daniel 關於常見字符串模式的文章 以獲取更多關於路徑的細節。
在不一樣的語言中,數字和日期被表現爲各類形式。幸虧蘋果官方已經提供了處理這些問題的方法,因此咱們只須要使用 NSNumberFormatter or NSDateFormatter 類來顯示用戶界面中的數字和日期便可。
請記住數字和日期的格式器是可變對象,所以並不線程安全。
數字格式器對象有不少配置選項,但大多數狀況下你只要使用一種定義好的數字格式就好。畢竟使用數字格式器的緣由就是沒必要再擔憂其餘語言中特定的數字格式。
對於數字 2.5
,在本文做者的機器上使用不一樣的格式器會獲得不一樣的輸出:
數字類型 德語結果 阿拉伯語結果
------------------------------------------------------------------------------------------------------
NSNumberFormatterNoStyle 2 ٢
NSNumberFormatterDecimalStyle 2,5 ٢٫٥
NSNumberFormatterCurrencyStyle 2,50 € ٢٫٥٠٠ د.أ.
NSNumberFormatterScientificStyle 2,5E0 ٢٫٥اس٠
NSNumberFormatterPercentStyle 250 % ٢٥٠٪
NSNumberFormatterSpellOutStyle zwei Komma fünf إثنان فاصل خمسة
複製代碼
在上表中數字格式器的一個很好的特性沒法直觀地表現出來:在貨幣和百分數形式中,貨幣單位和百分號前面插入的不是一個普通空格,而是一個不換行空格,所以實際顯示的時候數字和後面的符號不會被顯示在兩行中。(並且這種加空格的顯示不是很酷嗎?)
默認狀況下格式器會使用系統設置中指定的區域設置。在「字母大小寫」一節中咱們已經說過,根據特定用戶界面的特定要求爲格式器指定正確的區域設置是很是重要的,在後面的小節會進一步討論這一點。
與數字的格式化同樣,日期的格式化也很是複雜,所以咱們有必要讓 NSDateFormatter
來負責這一點。使用日期格式器的時候你能夠選擇蘋果官方提供的適用於全部區域設置的不一樣日期和時間格式。再強調一遍,選擇匹配界面其餘元素的正確區域設置。
有時你想用一種 NSDateFormatter
默認不支持的格式來顯示日期,這時不要使用簡單的格式化字符串(這樣作在應用到其餘語言中時幾乎確定會出錯),而要使用 NSDateFormatter
提供的 dateFormatFromTemplate:options:locale:
方法。
假設你想只顯示天和月份的縮寫,系統並無提供這樣的默認風格的。因此咱們能夠自定義格式器:
NSString *format = [NSDateFormatter dateFormatFromTemplate:@"dMMM"
options:0
locale:locale];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:format];
NSString *output = [dateFormatter stringFromDate:[NSDate date]];
NSLog(@"Today's day and month: %@", output);
複製代碼
相比使用格式化字符串,調用這個方法的一大好處就在於輸出結果在其餘語言中也確定是正確的。舉例來講,在美國英語中,咱們指望輸出「Feb 2」,而在德語中則應該輸出「2. Feb」。dateFormatFromTemplate:options:locale:
方法使用咱們指定的模板和區域設置來構造正確的輸出結果,在美國英語中將模板變爲「MMM d」,在德語中則變爲「d. MMM」。
想要深刻了解模板字符串中可使用的佔位符,能夠參考Unicode 格式的區域設置數據標記語言文檔.
由於建立格式器對象是一個很是消耗資源的操做,因此最好將它緩存起來以供以後使用:
static NSDateFormatter *formatter;
- (NSString *)displayDate:(NSDate *)date
{
if (!formatter) {
formatter = [[NSDateFormatter alloc] init];
formatter.dateStyle = NSDateFormatterShortStyle;
formatter.timeStyle = NSDateFormatterNoStyle;
}
return [formatter stringFromDate:date];
}
複製代碼
這裏有一個小的陷阱須要注意:若是用戶修改了區域設置,咱們就須要廢棄這個緩存。所以咱們須要使用 NSCurrentLocaleDidChangeNotification
註冊一個通知事件:
static NSDateFormatter *formatter;
- (void)setup
{
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:@selector(localeDidChange)
name:NSCurrentLocaleDidChangeNotification
object:nil];
}
- (NSString *)displayDate:(NSDate *)date
{
if (!formatter) {
formatter = [[NSDateFormatter alloc] init];
formatter.dateStyle = NSDateFormatterShortStyle;
formatter.timeStyle = NSDateFormatterNoStyle;
}
return [formatter stringFromDate:date];
}
- (void)localeDidChange
{
formatter = nil;
}
- (void)dealloc
{
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self
name:NSCurrentLocaleDidChangeNotification
object:nil];
}
複製代碼
蘋果官方的數據格式化指南中對此作了註解:
理論上來講你應該使用自動更新的區域設置(
autoupdatingCurrentLocale
),這樣就能夠在用戶作更改時生成對應的區域設置文件,可是這一招對日期格式器不適用。
因此咱們不得不使用爲區域設置的變動設置通知機制。相比格式化日期的那一小段代碼,這一段有點長,可是若是你頻繁使用日期格式器,這樣作是值得的。始終牢記在權衡利弊以後再進行改進。
再次強調,格式器不是線程安全的。蘋果官方文檔中寫道,你能夠在多線程環境下使用格式器,可是不能有多個線程同時修改格式器。若是你想將用到的全部格式器集中在一個對象中,以便在區域設置更改時更方便地廢棄緩存,你必須保證只使用一個隊列存放它們從而依次建立和更新。好比你可使用**併發隊列(concurrent queue)**和 dispatch_sync
來獲取格式器,在區域設置更改時使用 dispatch_barrier_async
來更新格式器。
數字和日期格式器不止能夠根據數字和日期對象生成可本地化字符串,還能以其餘方式工做。每當你須要處理用戶輸入中的數字或日期時,都應該使用合適的格式器類來解析。這是惟一可以保證用戶輸入可以按照當前區域設置正確解析的方法。
雖然格式器在處理用戶輸入時很好用,在已知格式的狀況下處理機器生成的數據有更好的方法,由於爲全部區域設置生成正確輸出的數字和日期格式器有性能上的損失。
舉例來講,若是你從服務器接收到不少日期字符串,在你將它們轉換成日期對象時,日期格式器並非最好的選擇。蘋果官方的日期格式化指南中提到對於這些固定格式且無需進行本地化的日期,使用 UNIX 提供的 strptime_l(3)
函數更高效:
struct tm sometime;
const char *formatString = "%Y-%m-%d %H:%M:%S %z";
(void) strptime_l("2014-02-07 12:00:00 -0700", formatString, &sometime, NULL);
NSLog(@"Issue #9 appeared on %@", [NSDate dateWithTimeIntervalSince1970: mktime(&sometime)]);
// Output: Issue #9 appeared on 2014-02-07 12:00:00 -0700
複製代碼
由於 strptime_l
函數也能夠感知用戶的區域設置,因此確保最後一個參數傳入 NULL
以使用標準 POSIX 區域設置。函數中可用的佔位符請參考 strftime 用戶手冊。
應用支持的語言版本越多,確保全部元素都正確顯示就越難。可是這裏有一些默認的用戶選項和工具能夠減輕你的負擔。
你可使用 NSDoubleLocalizedStrings
、AppleTextDirection
和 NSForceRightToLeftWritingDirection
選項保證你的佈局不會由於長字符串或者從右往左讀的語言而混亂。NSShowNonLocalizedStrings
和 NSShowNonLocalizableStrings
則能夠幫助你找到沒有翻譯的字符串和根本沒有制定字符串本地化宏的字符串。(全部這些工具的選項均可以經過程序設置或者做爲 Xcode 的 Scheme 編輯器啓動選項,如 -NSShowNonLocalizedStrings YES
)
還有兩個選項能夠控制語言和區域設置:AppleLanguages
和 AppleLocale
。你能夠配置這兩個選項讓應用以不一樣於當前系統的語言或者區域設置啓動,讓你在測試時不用頻繁對系統設置進行切換。AppleLanguages
選項接收符合 ISO-639 標準的語言代碼列表做爲參數,以下所示:
-AppleLanguages (de, fr, en)
複製代碼
AppleLocale
則接收符合Unicode 國際組件標準(International Components for Unicode) 的區域設置標識符做爲參數,以下:
-AppleLocale en_US
複製代碼
或
-AppleLocale en_GR
複製代碼
若是你翻譯的字符串沒有正確顯示,你能夠帶上 -lint
選項運行 plutil 命令來檢查一下字符串文件是否有語法錯誤。例如你在行尾漏寫了分號,plutil 會輸出以下警告:
$ plutil Localizable.strings
2014-02-04 15:22:40.395 plutil[92263:507] CFPropertyListCreateFromXMLData(): Old-style plist parser: missing semicolon in dictionary on line 6. Parsing will be abandoned. Break on _CFPropertyListMissingSemicolon to debug.
Localizable.strings: Unexpected character / at line 1
複製代碼
當咱們修正了這個錯誤後,plutil 會告訴咱們一切正常:
$ plutil Localizable.strings
Localizable.strings: OK
複製代碼
對於支持多種語言的應用,還有一個與調試無關的小技巧:你能夠在 iOS 上自動生成應用在多種語言下的屏幕截圖。由於可使用 UIAutomation
來控制應用,使用 AppleLanguages
在啓動時設置語言,因此整個測試過程能夠自動化。GitHub 上的這個項目中能夠找到更多細節。
在使用日期和數字格式器或者相似 [NSString lowercaseStringWithLocale:]
的方法調用時,確保你使用了正確的區域設置是很重要的。若是你想使用系統當前的區域設置,你可使用 [NSLocale currentLocale]
得到,可是要注意這不必定與你的應用實際運行時使用的相同。
假設用戶的系統是中文的,可是你的應用只支持英語、德語、西班牙語和法語。這種狀況下字符串本地化會使用默認的英語來進行,若是你如今使用 [NSLocale currentLocale]
或者使用 [NSNumberFormatter localizedStringFromNumber:numberStyle:]
這種未指定區域設置的格式器類,那麼這些數據會根據中文的區域設置來進行格式化,而界面上的其餘字符串則都是英語。
最終須要你來決定特定狀況下什麼最重要,可是你會想要應用的界面在一些狀況下保持一致。爲了獲取應用實際使用的而非當前系統的區域設置,咱們必須獲取 mainBundle
中的語言屬性來構造區域設置:
NSString *localization = [NSBundle mainBundle].preferredLocalizations.firstObject;
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:localization];
複製代碼
在這樣的區域設置下,咱們能夠將日期格式化爲與界面其餘元素一致的形式:
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.locale = locale;
formatter.dateStyle = NSDateFormatterShortStyle;
formatter.timeStyle = NSDateFormatterNoStyle;
NSString *localizedDate = [formatter stringFromDate:[NSDate date]];
複製代碼
任何適用於本身母語的規律都不必定適用於其餘語言,在本地化字符串時要牢記這一點。衆多框架提供了不少強大的工具將不一樣語言的複雜性抽象出來,咱們只須要一以貫之地運用它們。這會帶來一些額外的工做,可是會爲你在製做本身應用的其餘語言版本時節約大量的時間。