iOS國際化(多語言)漫談

目錄

概覽

各類資源的國際化

1.文本html

2.圖片java

3.nibpython

4.其餘資源git

特定模塊/功能的國際化

1.APP圖標github

2.應用名與權限提示shell

3.啓動圖(LaunchScreen)json

4.app調系統資源頁面的國際化swift

5.涉及服務端數據內容的國際化數組

app內更改語言

1.更改語言的方案xcode

2.未作國際化的舊項目遷移


概覽

國際化的本質是爲每種語言單獨提供一份資源(文本,圖片,音視頻等)。 本文術語 本地化:指單獨一種語言 國際化:多種語言的合體

在工程的Localizations中每新增一種語言,xcode會提示咱們生成對應的文件,然後也生成了對應的文件夾。

新增本地化語言

新增中文簡體後生成的文件夾與文件
兩種語言

iOS爲這些文件提供了快捷的國際化方案。對於字符串資源文件生成相應語言的字符串文件放在對應的文件夾中,而XIB和StoryBoard則可選整個文件和字符串資源。具體的方案後續討論。

image.png

若是忘了添加某個資源的具體語言文件,或者後續增長的資源文件,能夠經過該資源文件的 文件監察器File Inspector 中的 Localize按鈕添加。

image.png

Localizable.stringsInfoPlist.strings在國際化方案中是常見的。

  • Localizable.strings 這個是讀取多語言字符串方法NSLocalizedString默認會加載的文件,若是自定了這個文件名字,則使用NSLocalizedStringFromTable指定table便可
  • InfoPlist.strings info.plist的字符串國際化文件,系統默認讀取,名字固定

各類資源的國際化

1. 文本

添加了多語言的字符串資源文件處於可展開狀態,子級有着相應語言的副本。咱們把相應語言的文本放在副本里面就好了。

字符串文件中具體的格式是"key" = "value";筆者發現寫成key = "value";也是不會有問題的(可是不加雙引號不能有空格,會識別不了),好比應用名稱的本地化:

應用名稱的國際化
看截圖,最終的應用名稱是後面那一個,說明有效且 被覆蓋了

使用NSLocalizedString(key, comment)來讀取字符串。第二個參數comment能夠是nil,能夠是一段爲空的字符串,也能夠是對key的註釋。

看一下這個方法的實現

#define NSLocalizedString(key, comment) \
	    [NSBundle.mainBundle localizedStringForKey:(key) value:@"" table:nil]

...
/* Method for retrieving localized strings. */
- (NSString *)localizedStringForKey:(NSString *)key value:(nullable NSString *)value table:(nullable NSString *)tableName NS_FORMAT_ARGUMENT(1);
複製代碼

localizedStringForKey:value:table:是NSBundle的對象方法,因而可知,能夠加載不一樣的包名和字符串資源表的字符串。也提供了相關宏

#define NSLocalizedStringFromTable(key, tbl, comment) \
	    [NSBundle.mainBundle localizedStringForKey:(key) value:@"" table:(tbl)]
#define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment) \
	    [bundle localizedStringForKey:(key) value:@"" table:(tbl)]
複製代碼

重要:當找不到相應的語言strings或value時會直接返回key,若是你用英文的內容做爲Key,甚至均可以不用維護英文本地化。

2. 圖片

Xcode5以後圖片資產(Assets.xcassets)再也不支持國際化了,單張圖片資源的方式仍然可用,使用方式同字符串。將須要國際化的圖片拖入工程,選擇文件監察器,點擊Localize並選擇多個語言後便可生成如如字符串資源同樣的可展開狀態了。要配置不一樣語言的圖片前往該語言目錄替換便可。

圖片國際化

這個系統提供的方案支持Interface Builder版的nib資源國際化(固然Localizable Strings 方式很顯然只是字符串而已),也支持+ imageNamed:加載方式的國際化。

還有個只適合用於純代碼,不支持nib的方式。就是把圖片的名稱作字符串國際化,而後再使用+ imageNamed:加載。

另外,針對+imageWithContentsOfFile:的加載方式,能夠經過分類的方式,根據語言修改相應的加載路徑。

PS:圖片的國際化帶來的是多份的副本,若是國際化中須要作的具體本地化語言較多,必然形成包的急劇增大。因此建議能避免就避免。

3. nib文件(XIB和StoryBoard)

StoryBoard本地化

nib文件的國際化方式上面提到有兩種方式:

  • 只作字符串資源(Localizable Strings)
  • 整個nib文件(Interface Builder CocoaTouch XIB/StoryBoard)

nib文件有一個大坑,畫重點

各個本地化的nib修改不會同步,nib的修改也不會同步至字符串資源

也就是說第一種方案每增長一種語言就得再畫一個頁面,本地化語言多的話,額外工做量驚人。 第二種方案,也得本身將新增的字符串拷貝出來。

若是要把更新同步的過程作成自動化,固然也是字符串方便一點。用整個nib文件作國際化比字符串資源方式強的地方也就是如下兩點了

  • 圖片
  • 不一樣本地化不一樣佈局

通常也不會去根據不一樣本地化語言去修改佈局,阿拉伯國家也就改個文本方向,這個特性視圖自帶,全局修改便可。

[UIView appearance].semanticContentAttribute = UISemanticContentAttributeForceRightToLeft;
複製代碼

nib中的圖片資源用代碼也可輕鬆解決,綜上筆者建議針對nib文件只作字符串資源(Localizable Strings)。

因此,上面的圖片方案選了支持nib的方案,而後如今不用了 🤣 意不意外?驚不驚喜?

不說這個了,來講說怎麼作nib新增的須要國際化的字符串同步吧。

Xcode爲咱們提供了ibtool工具來生成nib的strings文件

ibtool FirstViewController.xib --generate-strings-file lanuchScreen.strings 
ibtool Main.storyboard --generate-strings-file storyBoard.strings
複製代碼

可是ibtool生成的strings文件是BaseStoryboard的strings(默認語言的strings),且會把咱們原來的(甚至是翻譯好的)strings替換掉。仍是本身用腳原本作這個工做靠譜點,再借助Xcode 中 Run Script 來運行這段腳本,更新的時候build一下就好了。 具體的腳本代碼在最後的Demo中,Run Script的添加方法: Target->Build Phases->New Run Script Phase,在shell裏面寫入下面指令

python ${SRCROOT}/${TARGET_NAME}/RunScript/AutoGenStrings.py ${SRCROOT}/${TARGET_NAME}
複製代碼

4. 其餘資源

其餘資源國際化

其餘資源(json、音視頻、壓縮包等等)的國際化方式與圖片資源相同,讀的時候讀 主Bundle 便可,不一樣語言環境下iOS自動切。

劇透:後續的應用內切換語言會利用主Bundle的這一特性。

5. 涉及服務端數據內容的國際化

這部份內容的國際化,可考慮如下兩種方案:

  • 服務端不關心當前用戶的本地化語言,返回全部適配的本地化內容,由客戶端本身控制顯示
  • 服務端獲取當前用戶的本地化信息,返回相應的本地化內容

第一種適合適配本地化語言較少的狀況,好比只適配中英文;而第二種,對配置信息的依賴比較高,服務端須要修改的內容也是比較多的。

若是使用第一種方案,一些經常使用的報錯信息或者其餘業務成功等信息能夠整理成特定的code由客戶端直接作解析,減小信息傳輸量(雖然相比單個本地化仍是會大不少)。

若是使用第二種方案,能夠在請求頭中帶入當前用戶的本地化信息,服務端根據這個判斷,能夠簡便得多。


特定模塊/功能的國際化

1. APP圖標

除了動態圖標的方法, 暫時也查不到什麼動態修改圖標的方法了。這個方法多用於 APP的節日活動。 事實上也沒人去作這個的國際化,順帶提一下。

2. 應用名與權限提示

應用名與權限提示的國際化就是依賴info.plist的國際化。不一樣本地化文件放不一樣的鍵值對便可。

應用名稱與相機權限的簡體中文本地化

3. 啓動圖(LaunchScreen)

Xcode Overview 的 Adding Assets章節中有關於啓動圖的描述

Because the launch screen is shown before your app is running, you can only use a single root view of type UIView or UIViewController.

也就是說啓動界面的展現是發生在main函數入口以前,也就決定了咱們沒法動態地修改啓動圖。另外,如下nib的兩種國際化方式也是無效的。

nib的兩種國際化方式
另闢蹊徑,利用info.plist的國際化來作LaunchScreen的靜態國際化,以下:
LaunchScreen的國際化

這裏要吐槽一下,即便作了靜態國際化,如下兩種情況是不會切換的

  • 系統切換語言的時候
  • 重啓系統

只有從新安裝app的時候纔會切換 因此作啓動圖的國際化意義有限。

也看到有人說本身作一個LaunchScreenController做爲啓動頁,可是這個情況下,app啓動會黑屏一段時間,這不是想要的效果啊。

4. app調系統頁面的國際化

關於調用系統資源,相機,相冊,通信錄之類,APP內修改語言暫沒找到刷新的方法。若是你有方法,麻煩告知樓主,很是感謝。

只有在app重啓時,main函數中應用程序代理(AppDelegate)返回以前去設置偏好設置的 AppleLanguages纔是有效的。

因此,目前的解決方案就是這些頁面所有本身實現。另外,導航按鈕的國際化文本也不是在 main bundle 中加載的,通常app也會自定義這個按鈕,這個不是痛點。

若有其它,歡迎補充。

app內更改語言

1.更改語言的方案

app的語言過年聚系統語言設置變化是最基本的國際化需求,更多的時候咱們但願可以作到app內部熱切換。

APP中的資源加載(Storyboard、圖片、字符串)基本是在NSBundle.mainBundle()上操做的(自建私有庫或者是三方庫可能會本身作國際化,把國際化字符串資源放在本身的bundle中,如MJRefresh),那麼咱們只要在語言切換後把相應資源加載的bundle替換成當前語言的bundle就好了。以下替換爲 字符串資源加載的主要代碼:

id value = language ? [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"zh-Hans" ofType:@"lproj"]] : nil;
objc_setAssociatedObject([NSBundle mainBundle], &kBundleKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
……
- (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName {
    NSBundle *bundle = objc_getAssociatedObject(self, &kBundleKey);
    if (bundle) {
        return [bundle localizedStringForKey:key value:value table:tableName];
    } else {
        return [super localizedStringForKey:key value:value table:tableName];
    }
}
複製代碼

相應的nib文件中的圖片加載和UITextView中的文本也須要

若是,項目是純代碼的,也就是說國際化涉及不到nib文件,那就不須要這個實現方式了。本身存個當前語言標記,切換時換一個,作好判斷便可。

若是想要在第一次安裝的時候跟隨系統的語言,應用啓動後從NSUserDefault中能夠讀到語言數組,其中數組的第一個元素便是主要語言(Primary Language),系統的當前語言。 這些語言字符串當中的最後一部分是地區,會根據地區變化,後續根據這個列表判斷的時候須要注意。

NSArray *array = [[NSUserDefaults standardUserDefaults] arrayForKey:@"AppleLanguages"];
複製代碼

語言列表(地區爲中國)
語言列表(地區爲美國)

不論哪一種方式,切換語言都是須要刷新視圖的,這個沒法避免。 那麼,如何優雅地刷新UI

  • 微博的思路是,在切換語言時,發送通知NSNotification,全部的UI控件監聽通知,而後在適當的時候刷新UI。 那麼其實這麼寫,須要作的東西不少,或是經過Base類來實現,或是經過runtime實現,總之Button、Label、TextField等等都須要有一套統一的更新機制,可能不是一個最簡單的辦法。
  • 而微信切換的方案是,刷新keyWindow的rootViewController,而後跳轉到設置頁。

這個思路有篇文章說的比較詳細,直接看:在iOS App內優雅的動態切換語言

2.未作國際化的舊項目遷移

老項目的國際化遷移也都會牽涉到以上提到的各類問題。可是以上的問題都不是主要的,主要的是那些散落在代碼中的各類須要國際化的文本。一個一個去摳出來確定不現實。 Xcode爲咱們提供了一個工具genstrings,這個工具與ibtool相似,也是導出字符串資源文件的。只不過ibtool適用於nib文件,而genstrings適用於源代碼文件。支持C,Objective-C,swift(官方未明確指出,筆者嘗試經過),java等語言文件,以下官方描述:

The genstrings tool can parse C, Objective-C, and Java code files with the .c.m, or .java filename extensions.

然而,仍是有不少工做要作,看一下官方的來那個外一個描述:

If you wrote your code using the Core Foundation and Foundation macros, the simplest way to create your strings files is using the genstrings command-line tool. You can use this tool to generate a new set of strings files or update a set of existing files based on your source code.

也就是說,這個腳本生效的前提是必需要使用NSLocalizedString系列宏,一個一個去替換字符串爲這個宏的讀取的這個工做仍是得本身作的。不過,想一想也是符合邏輯的,畢竟哪一個字符串要國際化仍是得開發者本身確認。經過Find navigator本身作吧。

如何使用

//指定到en.lproj目錄下的Localizable.strings文件,直接覆蓋
genstrings -o en.lproj *.swift
//指定到en.lproj目錄下的Localizable.strings文件,追加內容
genstrings -a -o en.lproj *.swift
複製代碼

其餘參數可使用man genstrings命令查看,再也不贅述。 這個命令行工具一樣有ibtool的詬病,全量輸出。因此,在使用的時候千萬當心別覆蓋了已經翻譯的內容。

看下效果:

效果

另外,這個命令一次只能解析一個文件,簡單寫了一個遞歸腳本:

#!/bin/bash
function getdir(){
for element in `ls $1`
do
dir_or_file=$1"/"$element
if [ -d $dir_or_file ]
then
getdir $dir_or_file
else
echo $dir_or_file
    suffix="${dir_or_file##*.}"
    if [ "$suffix"x = "swift"x ]||[ "$suffix"x = "m"x ]||[ "$suffix"x = "mm"x ];
    then
        genstrings -a -o en.lproj $dir_or_file
    fi
fi
done
}
root_dir="./"
getdir $root_dir
複製代碼

iOS國際化至此結束,若有哪裏不清楚,歡迎查看demo,或者留言。 最後附上:demo地址

參考文章: Internationalization and Localization Guide Using the genstrings Tool to Create Strings Files iOS國際化——經過腳本使storyboard翻譯自增 iOS國際化 在iOS App內優雅的動態切換語言


筆者和朋友作了淘寶優惠券公衆號,購物領券省錢,幫忙關注一下。

微信公衆號
相關文章
相關標籤/搜索