讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 9.10 本地化 >

9.10 本地化

用戶可以在設備上將某種語言作為其主語言。你可能希望應用界面的文本能夠對用戶的選擇做出響應,從而以用戶所選的語言來顯示。這是通過應用語言的本地化來實現的。你可能會在應用生命週期相對較晚的時間(應用已經開發完畢,準備發佈)實現本地化。

圖9-15:鑽取到時間表

圖9-16:對我的代碼進行時間分析

本地化是通過項目目錄與構建好的應用包中的本地化目錄來實現的。假設一個本地化目錄中的資源在另外一個本地化目錄中也有對應的一份。在應用加載該資源時,它會自動加載適合用戶首選語言的那個。

任何類型的資源都可以放在這些本地化目錄中;比如,一種語言會加載圖片的一個版本,而另一種語言會加載這張圖片的另一個版本。不過,你最應該關心的還是顯示在界面上的文本。這些文本需要以特殊的格式化.strings文件的形式進行維護,並帶有特殊的名字。比如:

·使用InfoPlist.strings來本地化Info.plist文件。

·使用Main.strings來本地化Main.storyboard。

·使用Localizable.strings來本地化代碼字符串。

無須再手工創建或維護這些文件了。相反,可以通過標準的.xliff格式使用導出的XML文件。Xcode會根據項目的結構與內容自動生成這些文件;還會讀取它們,並將其自動轉換為各種本地化的.strings文件。

為了幫助你理解.xliff導出與導入過程的工作方式,首先介紹如何手工創建和維護.strings文件;接下來介紹如何通過.xliff文件完成同樣的事情。我將使用之前的Empty Window項目作為該示例的基礎。

9.10.1 本地化Info.plist

首先本地化應用圖標下Springboard中的字符串,這也是應用的名字。該字符串是Info.plist文件中CFBundleDisplayName鍵的值。如果Info.plist文件中沒有CFBundleDisplayName鍵,那就先要創建一個:

1.編輯Info.plist文件。

2.選中「Bundle name」,然後單擊右側的「+」按鈕。

3.這時會出現一個新的條目。在彈出菜單中選擇「Bundle display name」。

4.輸入「Empty Window」作為新值並保存。

現在本地化該字符串:如果設備的語言是法語,那麼我們需要在Springboard中顯示出不同的字符串。Info.plist該如何本地化呢?它依賴於另外一個文件,默認情況下應用模板並不會創建這個文件:InfoPlist.strings。因此,需要創建這個文件:

1.選擇File→New→File。

2.選擇iOS→Resource→Strings File,單擊Next按鈕。

3.確保該文件屬於應用目標,將其命名為InfoPlist,注意名字與大小寫,單擊Create按鈕。

4.這時,一個名為InfoPlist.strings的文件會出現在項目導航器中。將其選中,在文件查看器中單擊Localize按鈕。

5.這時會彈出一個對話框,讓我們選擇初始語言。默認是Base,這就可以,單擊Localize按鈕。

現在準備添加語言!下面是具體步驟:

1.編輯項目。在Info下,Localizations表格列出了應用的本地化信息。我們一開始只對開發語言做了本地化(我選擇的是英語)。

2.單擊Localizations表格下方的「+」按鈕。從彈出的菜單中選擇French。

3.這時會彈出一個對話框,列出了當前已經針對英語進行了本地化的文件(因為它們是應用模板的一部分)。我們這裡只操作InfoPlist.strings,因此勾選上它,同時不要勾選其他的文件,單擊Finish按鈕。

我們現在已經創建了InfoPlist.strings供英語與法語本地化使用。在項目導航器中,InfoPlist.strings的清單已經有了一個三角箭頭。展開這個三角箭頭,我們會看到項目現在包含了InfoPlist.strings的兩個副本,一個用於Base(即英語),一個用於法語。現在就可以分別編輯這兩個文件了。

下面編輯InfoPlist.strings文件。.strings文件是個鍵值對的集合,其格式如下所示:


/* Optional comments are C-style comments */
\"key\" = \"value\";
  

對於InfoPlist.strings,鍵是Info.plist中的鍵名,即原始的鍵名而不是類似於英語的那個名字。這樣,英語的InfoPlist.strings文件應該如下所示:


\"CFBundleDisplayName\" = \"Empty Window\";
  

法語的InfoPlist.strings應該如下所示:


\"CFBundleDisplayName\" = \"Fenetre Vide\";
  

就是這些!下面來試一下:

1.在模擬器中構建並運行Empty Window。

2.在Xcode中,停止運行著的項目。在模擬器中會顯示出主界面。

3.查看應用的名字,它顯示在模擬器主界面中(Springboard),名字是Empty Window(也許會有截斷)。

4.在模擬器中,打開設置應用,將語言修改為French(General→Language & Region→iPhone Language→Fran崩is),單擊Done按鈕。系統會提示我們是否要修改為French。確定。

5.短暫的停頓之後,語言就會改變。關掉設置應用,再次在Springboard中查看應用。其名字現在已經顯示為了Fenetre Vide!

有意思吧?操作之後,請將模擬器的語言改回到English。

9.10.2 本地化nib文件

現在介紹一下如何本地化nib文件。曾經,我們需要本地化整個nib副本。比如,如果需要法語版本的nib文件,你就需要維護兩個單獨的nib文件。如果在一個nib文件中創建了一個按鈕,那就需要在另一個nib文件中創建一個相同的按鈕——只不過一個按鈕上的文字是英語,另一個是法語。諸如此類,每個界面對象與每個本地化語言都如此。看起來太枯燥了吧?

時至今日,我們已經有了更好的方式。如果項目使用了基礎國際化,那麼Base.lproj目錄中創建的nib文件與本地化目錄中創建的.strings文件之間就可以形成一種對應關係。這樣,開發者只需維護一個nib文件副本即可。如果應用所運行的設備的本地化語言有對應的.strings文件,那麼.strings文件中的字符串就會替換掉nib文件中的字符串。

在默認情況下,Empty Window項目會使用基礎國際化,其Main.storyboard文件位於Base.lproj目錄中。我們準備將故事板文件本地化為法語。你還需要對故事板文件做些操作才能進行本地化:

1.編輯Main.storyboard,確保初始主視圖包含一個按鈕,按鈕上的文字為\"Hello\"。如果沒有就添加一個。將按鈕寬度設為100像素,保存(這很重要)。

2.繼續編輯Main.storyboard,打開文件查看器。在Localization下,Base應該已經勾選了。此外,勾選French。

3.在項目導航器中,查看Main.storyboard列表。它現在應該有一個小三角,展開這個小三角。當然,現在應該會有一個基於Base的本地化Main.storyboard與一個基於French的本地化Main.strings。

4.編輯French Main.strings。它會自動創建出來,其鍵對應於Main.storyboard中每個有文本的界面元素。你需要從註釋與鍵名中推斷出這種對應關係。對於這個示例來說,Main.storyboard中只有一個界面元素,因此很容易就能猜出來鍵代表的是哪個界面元素。它應該如下所示:


/* Class = \"UIButton\"; normalTitle = \"Hello\"; ObjectID = \"PYn-zN-WlH\"; */
\"PYn-zN-WlH.normalTitle\" = \"Hello\";
  

5.將第2行(包含鍵值對這一行)的值修改為「Bonjour」。不要修改鍵!它是自動生成的,也是正確無誤的,用於指定值與按鈕文本之間的對應關係。

運行項目並查看界面。由於現在是在查看自己的應用,有一個更快的方式可以在各種本地化語言中查看:相對於切換設備語言,可以切換應用語言。要做到這一點,請編輯方案,在運行動作的Options頁簽中修改應用語言彈出菜單。當然,當應用使用法語時,按鈕上的文本顯示為\"Bonjour\"!

如果修改nib會出現什麼結果呢?假設在Main.storyboard中向視圖再添加一個按鈕。這時與nib對應的.strings文件不會發生任何變化;我們需要手工重新生成這些文件(這也是在實際情況下,為何要在界面開發工作基本完成時才開始本地化nib文件的原因所在)。不過內容並未丟失:

1.選中Main.storyboard,然後選擇File→Show in Finder。

2.運行Terminal。輸入命令xcrun ibtool--export-strings-file output.strings,後跟一個空格,然後將Main.storyboard從Finder拖曳到Terminal窗口,按回車鍵。

結果就是基於Main.storyboard的,名為output.strings的新文件會在主目錄(也就是當前目錄)下生成。可以根據Main.storyboard將這部分信息與現有的本地化.strings文件合併到一起。

在該示例中,我讓你提前增加\"Hello\"按鈕的寬度,從而為更長的本地化文本\"Bonjour\"留出足夠的空間。在實際情況下,你可能會使用自動佈局;這樣按鈕與標籤就會自動伸縮了,同時界面的其他部分會相應地進行補償。

要在不同本地化的情況下測試界面,還可以在Xcode中預覽本地化nib文件,而無須運行應用。編輯.storyboard或.xib文件,打開輔助窗格,將追蹤菜單切換至Preview。右下角的菜單會列出本地化信息;可以在菜單中進行切換。「兩倍長度的偽語言」會通過非常長的替換文本來測試界面在這種情況下的反應。

在iOS 9中,當應用運行在自右向左的語言中時,運行時會自動顛倒(鏡像)整個界面及其行為。比如,推動變換會沿著老視圖向右滑動,然後從左側加載新視圖。如果使用了自動佈局和兩端約束,那麼界面就會顛倒過來,但如果代碼依賴於從左向右的方向性,那就需要使用一些新的UIView API。

9.10.3 本地化代碼字符串

如何本地化其值是通過代碼生成的字符串呢?在Empty Window應用中,輕拍按鈕所彈出的警告就是個很好的示例。它會顯示文本—警告的標題與消息,以及用於關閉警告的按鈕文本:


@IBAction func buttonPressed(sender:AnyObject) {
    let alert = UIAlertController(
        title: \"Howdy!\", message: \"You tapped me!\", preferredStyle: .Alert)
    alert.addAction(
        UIAlertAction(title: \"OK\", style: .Cancel, handler: nil))
    self.presentViewController(alert, animated: true, completion: nil)
}
  

該文本該如何本地化呢?方式是一樣的(需要一個.strings文件),不過需要修改代碼才能顯式使用它。代碼會調用全局的NSLocalizedString函數;函數的第1個參數是.strings文件中的鍵,註釋參數給出了非常好的說明,比如,待翻譯的原始文本。NSLocalizedString還接收幾個可選參數;如果省略,那麼默認會使用一個名為Localizable.strings的文件。

比如,我們將buttonPressed:方法修改為下面這個樣子:


@IBAction func buttonPressed(sender:AnyObject) {
    let alert = UIAlertController(
        title: NSLocalizedString(\"ATitle\", comment:\"Howdy!\"),
        message: NSLocalizedString(\"AMessage\", comment:\"You tapped me!\"),
        preferredStyle: .Alert)
        alert.addAction(
            UIAlertAction(title: NSLocalizedString(\"Accept\", comment:\"OK\"),
                style: .Cancel, handler: nil))
        self.presentViewController(alert, animated: true, completion: nil)
}
  

當然,上述代碼是有問題的,因為沒有Localizable.strings文件。下面創建一個,過程與之前一樣:

1.選擇File→New→File。

2.選擇iOS→Resource→Strings File,單擊Next按鈕。

3.確保該文件屬於應用目標,將其命名為Localizable,注意名字與大小寫,單擊Create按鈕。

4.這時,一個名為Localizable.strings的文件會出現在項目導航器中。將其選中,在文件查看器中單擊Localize按鈕。

5.這時會彈出一個對話框,讓我們選擇初始語言。默認是Base,這就可以,單擊Localize按鈕。

6.在文件導航器中勾選French。

現在,Localizable.strings文件對應於兩個本地化,Base(即English)與French。我們需要在文件中添加內容。就像之前使用ibtool那樣,可以通過genstrings工具自動生成初始內容。比如,我會在計算機上打開Terminal,然後輸入xcrun genstrings,後跟空格。接下來將ViewController.swift從Finder拖曳到Terminal窗口,按回車鍵。這會在當前目錄下生成一個Localizable.strings文件,其內容如下所示:


/* OK */
\"Accept\" = \"Accept\";

/* You tapped me! */
\"AMessage\" = \"AMessage\";

/* Howdy! */
\"ATitle\" = \"ATitle\";
  

將上述內容複製並粘貼到項目Localizable.strings文件的English與French版本中,然後檢查鍵值對,修改每一個鍵值對的值,使得值是我們所需要的。比如,在English版的Localizable.strings文件中:


/* Howdy! */
\"ATitle\" = \"Howdy!\";
  

在French版的Localizable.strings文件中:


/* Howdy! */
\"ATitle\" = \"Bonjour!\";
  

以此類推。

9.10.4 使用XML文件進行本地化

從Xcode 6開始,我們可以通過另外一種方式完成之前的工作。從表面來看,文本本地化可以看作對.xliff文件導入與導出的解析。這意味著你實際上無須按下任何Localize按鈕或編輯任何.strings文件!相反,你可以編輯目標並選擇Editor→Export For Localization;在保存時,Xcode會創建一個目錄,裡面包含了用於各種本地化的.xliff文件。接下來編輯這些文件(或讓專門負責編輯的人幫你)並將編輯好的文件導入;編輯目標並選擇Editor→Import Localizations。Xcode會讀取編輯好的.xliff文件並完成其他操作,根據需要自動創建好本地化,生成或修改.strings文件。

為了演示,我們再向本地化添加一種語言——Spanish。

1.編輯目標並選擇Editor→Export For Localization。

2.我們可以在現有的本地化與基礎語言中導入字符串。如果要編輯French本地化,那就需要將其導出,不過我不打算在該示例中這麼做。相反,只需要將Include彈出菜單切換至Development Language Only即可。

3.指定好保存的位置(如桌面)。這時要創建一個目錄,因此請不要讓目錄名與保存位置處的現有目錄重名。比如,如果保存到包含了項目目錄的相同目錄下,那麼可以將其命名為Empty Window Localizations,單擊Save按鈕。

4.在Finder中,打開剛才創建的目錄。它包含了項目基礎語言的.xliff文件。比如,我的文件叫作en.xliff,因為開發語言是English。

查看這個.xliff文件,你會看到Xcode已經幫我們做好了之前需要手工完成的一切。不再需要.strings文件了!Xcode完成了所有工作:

·對於項目中的每個Info.plist文件,Xcode都會創建一個相應的<file>元素。在導入時,這些文件會轉換為本地化的InfoPlist.strings文件。

·對於每個.storyboard與.xib文件,Xcode都會運行ibtool來提取出文本,並且創建相應的<file>元素。在導入時,這些元素會轉換為齊名的本地化.strings文件。

·對於包含了對NSLocalizedString調用的每個代碼文件,Xcode都會調用genstrings,並且創建相應的<file>元素。在導入時,這些元素會轉換為本地化Localizable.strings文件。

我們現在繼續將該文件中的字符串翻譯為其他語言,保存編輯好的.xliff文件,將其導入:

1.在合適的文本編輯器(或XML編輯器)中打開.xliff文件。

2.對於該示例來說,我只對故事板中的\"Hello\"按鈕進行本地化。因此,刪除(請小心,不要搞亂了XML)除original屬性為\"Empty Window/Base.lproj/Main.storyboard\"的其他所有<file>...</file>元素組。刪除除<source>為\"Hello\"的其他所有<trans-unit>...</trans-unit>元素。

3.將Spanish作為目標語言,向<file>元素添加一個屬性:target-language=\"es\"。

4.提供一個翻譯,在<source>元素後添加一個<target>元素,加上一些文本,如\"Hola\"。文件的內容現在應該如下所示:


<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>
<xliff xmlns=\"urn:oasis:names:tc:xliff:document:1.2\"
  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" version=\"1.2\"
  xsi:schemaLocation=\"urn:oasis:names:tc:xliff:document:1.2
  http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd\">
  <file original=\"Empty Window/Base.lproj/Main.storyboard\"
  source-language=\"en\" target-language=\"es\" datatype=\"plaintext\">
    <header>
      <tool tool-id=\"com.apple.dt.xcode\" tool-name=\"Xcode\"
      tool-version=\"6.2\" build-num=\"6C107a\"/>
    </header>
    <body>
      <trans-unit>
        <source>Hello</source>
        <target>Hola</target>
        <note>Class = \"UIButton\"; normalTitle = \"Hello\";
          ObjectID = \"PYn-zN-WlH\";</note>
      </trans-unit>
    </body>
  </file>
</xliff>
  

5.回到Xcode,編輯目標並選擇Editor→Import Localizations。在Open對話框中,選擇編輯好的en.xliff並單擊Open。

6.Xcode會提示我們沒有翻譯全部內容。忽略該提示並單擊Import。

下面就是見證奇跡的時刻!在沒有任何提示的情況下,Xcode為本地化添加了Spanish,並且又創建了一個InfoPlist.strings文件、一個Main.strings文件和一個Localizable.strings文件,所有這些文件都本地化為Spanish。查看Main.strings,你會發現其內容與我們手工編輯的一模一樣:


/* Class = \"UIButton\"; normalTitle = \"Hello\"; ObjectID = \"PYn-zN-WlH\"; */
\"PYn-zN-WlH.normalTitle\" = \"Hola\";
  

顯然,.xliff文件是創建並維護本地化的一種非常便捷的手段。項目中本地化的結構與本節之前介紹的完全一樣,不過.xliff文件將相同的信息具化為可通過單個文件編輯的格式。.xliff導出過程會使用ibtool與genstrings,這樣在添加界面和代碼時就可以輕鬆維護本地化內容了。