[iOS] 國(guó)際化與本地化

簡(jiǎn)介

國(guó)際化(internationalisation,i18n):一個(gè)程序或軟件可給特定的人群使用而無(wú)須修改或重新編譯源代碼。對(duì)于 iOS 來(lái)說(shuō)就是,使App能夠適用于不同的語(yǔ)言,地區(qū),文化的過(guò)程。

本地化 (localisation,l10n):一個(gè)程序或軟件在支持國(guó)際化的基礎(chǔ)上,給定程序特定區(qū)域的語(yǔ)言信息使其在信息的輸入輸出等處理上適應(yīng)特定區(qū)域人群的使用。對(duì)于 iOS 來(lái)說(shuō)就是,將App的內(nèi)容翻譯為多種語(yǔ)言的過(guò)程。

總的來(lái)說(shuō),國(guó)際化是開(kāi)發(fā)者的任務(wù),是一個(gè)一般化的過(guò)程;而本地化則是翻譯者所做的事情,是一個(gè)具體的過(guò)程。國(guó)際化的運(yùn)作為本地化工作提供了可能。一個(gè)軟件的國(guó)際化實(shí)現(xiàn)需要本地化的支持。

用戶(hù)設(shè)置對(duì)于國(guó)際化的影響

在 iOS 設(shè)置應(yīng)用中的通用->語(yǔ)言與地區(qū)中可以分別設(shè)置語(yǔ)言,地區(qū)和日歷。一個(gè)支持國(guó)際化的應(yīng)用會(huì)根據(jù)語(yǔ)言的設(shè)置選擇相應(yīng)的本地化包,語(yǔ)言的改變會(huì)導(dǎo)致系統(tǒng)重新啟動(dòng),以便所有應(yīng)用重新應(yīng)用新的語(yǔ)言設(shè)置。地區(qū)主要影響日期,時(shí)間,數(shù)字,貨幣等數(shù)據(jù)的格式。用戶(hù)可以通過(guò)日歷選擇不同的歷法。地區(qū)和日歷的設(shè)置都是即時(shí)的,不需要重新啟動(dòng)系統(tǒng)。另外,在語(yǔ)言與地區(qū)中還可以設(shè)置首選語(yǔ)言順序列表,應(yīng)用會(huì)以此為依據(jù)選擇本地化語(yǔ)言包(按順序找第一個(gè)應(yīng)用支持的本地化語(yǔ)言包)。

使應(yīng)用支持國(guó)際化

使用Base Internationalization國(guó)際化界面文本

從Xcode5之后,工程默認(rèn)支持Base Internationalization。
Base Internationalization將界面文本與.storyboard和.xib文件分離,使得項(xiàng)目只需要一套.storyboard和.xib文件。在.storyboard和.xib文件中使用你設(shè)置的開(kāi)發(fā)語(yǔ)言填寫(xiě)界面文本,這套.storyboard和.xib文件就叫做Base Internationalization。之后在本地化時(shí),將以開(kāi)發(fā)語(yǔ)言為資源翻譯成多種語(yǔ)言,這些語(yǔ)言以.strings格式的文本文件對(duì)應(yīng)相應(yīng)的.storyboard和.xib文件。

一般情況下,每種語(yǔ)言相對(duì)應(yīng)的文件會(huì)放在相應(yīng)的語(yǔ)言包下,比如en.lproj、zh.lproj等。但是所有的.storyboard和.xib文件會(huì)放在一個(gè)名為Base.lproj的文件夾內(nèi),這就是為了共用同一套.storyboard和.xib文件,這樣,在其他語(yǔ)言包內(nèi)就可以只存放相應(yīng)的.strings格式的文本文件了。由于.storyboard和.xib文件中已經(jīng)使用開(kāi)發(fā)語(yǔ)言填寫(xiě)了界面文本,所以在開(kāi)發(fā)語(yǔ)言的語(yǔ)言包內(nèi)就不需要再有相對(duì)應(yīng)的.strings文件了。
關(guān)于開(kāi)發(fā)語(yǔ)言的設(shè)置:

  1. 在項(xiàng)目的.xcodeproj文件中打開(kāi)project.pbxproj
  2. 搜索developmentRegion,并將該key的值改為所要設(shè)置的開(kāi)發(fā)語(yǔ)言對(duì)應(yīng)的語(yǔ)言ID,比如en、zh等
  3. 保存,效果如下圖所示


使用Auto Layout輔助國(guó)際化界面文本

因?yàn)樗С值恼Z(yǔ)言所占界面空間不同,Auto Layout可以使一套界面適配布局不同的文字。使用時(shí)注意:

  1. 顯示文字的組件不使用固定寬度的約束,否則在某些語(yǔ)言下文字可能顯示不完全,text fields和labels默認(rèn)的行為都是自動(dòng)適配到內(nèi)容的合適的尺寸,完全可以使用這種方式。
  2. 添加水平間距約束時(shí),使用leading和trailing,這樣針對(duì)左右順序不同的語(yǔ)言可互換左右間距值。
  3. 因?yàn)檎Z(yǔ)言不同視圖組件所占空間不同,所以約束應(yīng)與相鄰視圖組件建立,這樣當(dāng)語(yǔ)言改變時(shí),其他視圖組件也會(huì)自動(dòng)適配。
國(guó)際化代碼中的文本

在項(xiàng)目中,一些在界面上顯示的文本是需要在代碼中提供的(比如錯(cuò)誤信息提示),而對(duì)這部分文本國(guó)際化的方式是將文本寫(xiě)入.strings文件中,之后使用宏NSLocalizedString去從文件中獲取。在.strings文件中鍵值對(duì)的格式為

"key1" = "value1";
"key2" = "value2";

使用NSLocalizedString去取值的方式為

NSLocalizedString("key1", "comment");
NSLocalizedStringFromTable("key1", "tableName", "comment");

其中標(biāo)準(zhǔn)的NSLocalizedString函數(shù)會(huì)從main bundle中的Localizable.strings文件中找到相應(yīng)鍵的值,而comment是對(duì)該鍵值對(duì)的解釋。同理NSLocalizedStringFromTable函數(shù)的第二個(gè)參數(shù)可以指定其他.strings文件的文件名。

國(guó)際化數(shù)據(jù)格式

不同的國(guó)家和地區(qū)有著不同的日期,時(shí)間,貨幣,數(shù)值等的格式,所以要在代碼中根據(jù)用戶(hù)設(shè)置的地區(qū)來(lái)正確的格式化這些數(shù)據(jù)。
格式化時(shí)用到NSLocale類(lèi),NSLocale類(lèi)封裝了某一個(gè)地區(qū)的相應(yīng)的格式化信息,如果要獲得用戶(hù)當(dāng)前設(shè)置地區(qū)的NSLocale實(shí)例可以使用[NSLocale currentLocale]或者[NSLocale autoupdatingCurrentLocale],兩者的區(qū)別是,后一個(gè)類(lèi)方法返回的值會(huì)根據(jù)用戶(hù)設(shè)置的改變而改變,而前者不會(huì)。可以通過(guò)NSLocal查看很多這一地區(qū)的數(shù)據(jù)格式化信息,例如(其他key值請(qǐng)查閱相關(guān)文檔):

NSNumber *metricSystem = [[NSLocale currentLocale] objectForKey:NSLocaleUsesMetricSystem];
NSString *currencySymbol = [[NSLocale currentLocale] objectForKey:NSLocaleCurrencySymbol];
// 用當(dāng)前地區(qū)的語(yǔ)言顯示語(yǔ)言ID
NSLocale *zhHansLocal = [NSLocale localeWithLocaleIdentifier:@"zh-Hans"];
NSString *currentLocalZH = [zhHansLocal displayNameForKey:NSLocaleIdentifier value:@"zh-Hant-TW"];
NSString *currentLocalEN = [zhHansLocal displayNameForKey:NSLocaleIdentifier value:@"en-US"];
// 獲取該地區(qū)所使用的引號(hào)
NSString *bQuote = [locale objectForKey:NSLocaleQuotationBeginDelimiterKey];
NSString *eQuote = [locale objectForKey:NSLocaleQuotationEndDelimiterKey];
// 國(guó)際化大小寫(xiě)轉(zhuǎn)換
NSString *localizedUppercaseString = [string uppercaseStringWithLocale:[NSLocale currentLocale]];
NSString *localizedlowercaseString = [string lowercaseStringWithLocale:[NSLocale currentLocale]];
NSString *localizedCapitalizedString = [string capitalizedStringWithLocale:[NSLocale currentLocale]];

當(dāng)使用[NSString stringWithFormat]去拼裝字符串時(shí),如果傳入的參數(shù)帶有數(shù)值,日期等需要格式化的內(nèi)容,最好使用以下方式(更好的方式是使用formatter進(jìn)行轉(zhuǎn)換):

// 使[NSLocale systemLocale]
NSString *localizedString = [NSString localizedStringWithFormat:@"%3.2f", myNumber];
NSString *localizedString = [[NSString alloc] initWithFormat:@"%@" locale:[NSLocale localeWithLocaleIdentifier:@"zh"], [NSDate date]];

使用formatter格式化日期和時(shí)間時(shí)的國(guó)際化

// 使用預(yù)設(shè)的格式
NSString *localizedDateTime = [NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterShortStyle];
// 使用自定義的格式
NSDateFormatter *dateFormatter = [NSDateFormatter new];
NSString *localeFormatString = [NSDateFormatter dateFormatFromTemplate:@"dMMM" options:0 locale:dateFormatter.locale];
dateFormatter.dateFormat = localeFormatString;
NSString *localizedString = [dateFormatter stringFromDate:[NSDate date]];

對(duì)于數(shù)值型數(shù)據(jù)的格式化主要包括小數(shù)、千位分割、貨幣、百分比。同樣也需要支持國(guó)際化。

NSString *localizedString = [NSNumberFormatter localizedStringFromNumber:myNumber numberStyle:NSNumberFormatterDecimalStyle];

同理對(duì)于NSCalendar的使用也受NSLocale的影響。
地區(qū)和時(shí)區(qū)的設(shè)置發(fā)生變化的通知分別是NSCurrentLocaleDidChangeNotificationNSSystemTimeZoneDidChangeNotification

國(guó)際化支持相反的語(yǔ)言方向

有一些語(yǔ)言,比如阿拉伯語(yǔ)等,方向是從右向左的。使用
Base Internationalization和Auto Layout在大部分情況下可以很好的支持這些語(yǔ)言,一些不支持的情況,可以在代碼中進(jìn)行如下判斷:

if ([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:view.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft) {
    …
}

本地化應(yīng)用

當(dāng)我們使應(yīng)用支持國(guó)際化后,就可以配合翻譯人員完成應(yīng)用本地化的工作了。大致的過(guò)程是,通過(guò) Xcode 將開(kāi)發(fā)語(yǔ)言的文本資源導(dǎo)出為 XLIFF (XML Localisation Interchange File Format) 文件,翻譯人員在完成該文件內(nèi)的相應(yīng)翻譯內(nèi)容后,再通過(guò) Xcode 將文件導(dǎo)入,這時(shí)所翻譯的語(yǔ)言資源就會(huì)被加入到工程中了。

導(dǎo)出 XLIFF
  1. 在 Xcode 中選中工程或 target。
  2. 選擇 Editor > Export For Localization。

之后 Xcode 會(huì)將 .xliff 文件導(dǎo)出到你指定的目錄中。如果你在工程的 Localizations 設(shè)置項(xiàng)中還沒(méi)有添加過(guò)其他的語(yǔ)言,而只有開(kāi)發(fā)語(yǔ)言(如 English)一種,那么導(dǎo)出的文件為 en.xliff。這時(shí)的 en.xliff 文件中并沒(méi)有指明要翻譯的目標(biāo)語(yǔ)言,而這會(huì)導(dǎo)致在翻譯完成后文件無(wú)法正常導(dǎo)入,需要在文件源碼中添加target-language=""字段。更好的做法是,在創(chuàng)建工程時(shí)就把要從開(kāi)發(fā)語(yǔ)言翻譯的其他語(yǔ)言(如 Chinese)添加到 Localizations 中,這樣導(dǎo)出的文件就為 zh.xliff,且其中已標(biāo)明目標(biāo)語(yǔ)言,可以正常的導(dǎo)入。另一個(gè)要注意的地方是,.strings文件支持國(guó)際化時(shí),開(kāi)始的開(kāi)發(fā)語(yǔ)言版本不要放到Base.lproj文件夾中,Base.lproj文件夾中存放的全部是界面文件,而是應(yīng)直接放入對(duì)應(yīng)的開(kāi)發(fā)語(yǔ)言的.lproj文件中。

命令行方式:

xcodebuild -exportLocalizations -localizationPath <dirpath> -project <projectname> [[-exportLanguage <targetlanguage>]]

在等待翻譯資源的時(shí)候,你并不希望界面或者僅僅是界面中的文本再發(fā)生變化,這個(gè)時(shí)候可以使用 Xcode 的一項(xiàng)特性 Locking Views。可以只針對(duì)某一 view 修改,在該 view 的 Identity inspector 中修改 Lock 項(xiàng)。也可設(shè)置整個(gè) nib 文件的該屬性,選中對(duì)應(yīng) nib 文件后,修改 Editor > Localization Locking 菜單項(xiàng)。

導(dǎo)入 XLIFF
  1. 在 Xcode 中選中工程或 target。
  2. 選擇 Editor > Import Localizations。

Xcode 會(huì)從 XLIFF 文件中解析出翻譯內(nèi)容的 .strings 文件,然后放到對(duì)應(yīng)語(yǔ)言的 .lproj 目錄中,而在工程中,nib 文件和 .strings 文件都以組的形式管理。

命令行方式:

xcodebuild -importLocalizations -localizationPath <filepath> -project <projectname>
其他資源

除了文本資源外,其他的資源文件,比如圖片、音視頻文件等可能也需要針對(duì)不同地區(qū)使用不同內(nèi)容。

在國(guó)際化資源文件后,就可以將本地化版本的文件加到對(duì)應(yīng)的 .lproj 目錄中。

最佳實(shí)踐

對(duì)于大部分的國(guó)內(nèi)開(kāi)發(fā)者來(lái)說(shuō),其應(yīng)用主要針對(duì)使用中文的用戶(hù),而鑒于翻譯資源的有限,可能針對(duì)其他地區(qū)用戶(hù)僅能支持國(guó)際通用的英文。在這種情況下,最合適的流程是,在開(kāi)發(fā)過(guò)程中,開(kāi)發(fā)者使用中文填寫(xiě)界面文本,而之后由翻譯人員翻譯為英文,最后使應(yīng)用在所有不匹配中文地區(qū)的設(shè)備上,都能使用英文資源。

要達(dá)到上述效果,首先要設(shè)置工程的開(kāi)發(fā)語(yǔ)言為中文,如上文所述,修改工程文件中的字段 developmentRegion 為 zh,同時(shí)確保需要翻譯的英文已添加在列表中。在完成國(guó)際化的工作后,就可導(dǎo)出 en.xliff 文件交由翻譯人員翻譯,并最終導(dǎo)入。最后一步,是要確認(rèn) Info.plist 文件中的 CFBundleDevelopmentRegion 字段設(shè)置為 en,該字段決定在用戶(hù)當(dāng)前地區(qū)未匹配到翻譯資源時(shí),使用應(yīng)用已有的哪一套翻譯資源作為界面文本顯示。

需要注意的一點(diǎn)是,盡管文檔中提到:

You can choose from more than 100 different languages and dialects designated by regions to localize your app. However, the more general you make your localized resources, the more regions you can support with a single set of resources. This can save a lot of space in your app bundle and help reduce localization costs. For example, if you don’t need to distinguish between different regions that use the English language, you can add English to support users in the United States, United Kingdom, and Australia. Even if you provide region-specific resources always provide a complete set of language-specific resources for all the languages you support.

但當(dāng)用戶(hù)設(shè)備的語(yǔ)言設(shè)置為繁體中文時(shí),并不會(huì)匹配到中文(zh)的語(yǔ)言資源,所以在上述配置后,最終在繁體中文的設(shè)備上,應(yīng)用會(huì)使用英文資源。如果想要所有中文設(shè)備都顯示作為開(kāi)發(fā)語(yǔ)言的簡(jiǎn)體中文,最后需要多添加一種語(yǔ)言 zh-Hant,但不需要進(jìn)一步翻譯,直接以開(kāi)發(fā)語(yǔ)言為模板生成即可。

測(cè)試

在 Xcode 中,可以使用預(yù)覽功能,在不運(yùn)行應(yīng)用的情況下,檢測(cè)國(guó)際化和本地化的結(jié)果。選擇一個(gè) .storyboard 或 .xib 文件,打開(kāi)輔助編輯器,在相關(guān)文件中選擇 Preview,通過(guò)右下角語(yǔ)言選項(xiàng),可以在國(guó)際化后選擇 Double-Length Pseudolocalizations 進(jìn)行檢測(cè),本地化后選擇對(duì)應(yīng)語(yǔ)言進(jìn)行檢測(cè)。

預(yù)覽過(guò)后,可以通過(guò)設(shè)置應(yīng)用啟動(dòng)時(shí)的參數(shù),在設(shè)備上測(cè)試國(guó)際化和本地化的結(jié)果,而不需要修改設(shè)備的設(shè)置。編輯 Scheme,選擇 Run -> Options,在 Application Language 一項(xiàng)中,可以選擇對(duì)應(yīng)語(yǔ)言資源,Double-Length Pseudolocalizations 以及 Right to Left Pseudolocalizations。勾選 Show non-localized strings 項(xiàng),可以檢測(cè)未本地化的文本,其會(huì)以大寫(xiě)形式顯示。

參考資料

Internationalization and Localization Guide

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,382評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,038評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,853評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,616評(píng)論 6 408
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,112評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,355評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,869評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,727評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,928評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,165評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,570評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,813評(píng)論 1 282
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,585評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,892評(píng)論 2 372

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