iOS國際化實踐
前言
爲了實現在iOS項目中的多語言切換,我們需要對工程中的各個資源、 各個模塊進行相應的國際化操作。在實際的項目需求中,一般還需要進行應用內語言切換,除了默認的跟隨系統之外,還要實現動態的語言版本切換。
實現思路
字面上理解,國際化就是對應用中出現的文字、圖片資源進行翻譯,然後通過鍵值映射的方式進行切換即可,但是在實踐中需要處理的地方和注意的點還是挺多的, 尤其對於舊項目進行國際化改造時,工作量還是挺大的。
總的來說,國際化就是對資源進行替換,分別涉及到了字符串國際化、圖片文件等資源國際化、xib文件國際化、InfoPlist國際化、網絡數據國際化等。下面我們將通過舉例,介紹在各種情況下的國際化實踐。
字符串國際化
字符串在整個國際化中佔比最高,首先我們需要在Xcode中,創建需要國際化的語言模板,然後在相應的模板中通過"key" = "value"
的形式,對項目中出現的字符串進行翻譯即可。
在Xcode中選中項目的PROJECT->Info
選項,定位到Localizations
標籤,添加需要支持的語言版本:
然後,我們需要手動創建Localizable.strings
文件,用於生成本地化字符串映射文件。需要注意的是這個文件名必須設置爲指定的Localizable
,這樣系統纔會識別。
爲了簡便,如果我們主語言使用的是中文的話,我們可以將中文作爲key,這樣的話,在代碼中,我們可以直接在宏替換中寫中文,如果國際化文件中沒有對應的翻譯時,會直接返回key,這樣我們就不需要再添加簡體中文的語言模板了。
新建文件,選擇Strings File
模板,創建Localizable.strings
文件
創建成功後,我們選中Localizable.strings
文件,點擊右側的Localization
按鈕,勾選剛纔創建的語言類型
當添加完成後,我們會發現Localizable.strings
文件變成了一個文件夾樣式,展開後,我們就可以看到生成的字符串模板文件了。
現在我們就可以在相應的模板中,對字符串進行國際化了。如下圖所示,等號左邊的是key,等號右邊的是對key的翻譯:
那麼,在代碼中是如何使用的呢?只需要在代碼中,將字符串進行如下的宏替換即可:
// 本地化字符串宏定義,key:需要國際化的字符串,comment:一個註釋,一般設置爲nil
#define NSLocalizedString(key, comment)
// 舉例
self.title = NSLocalizedString(@"錢包", nil);
對於舊項目中有大量字符串需要替換的時候,手動替換起來想想是不是很頭疼呢?其實,我們也可以通過Xcode的全局替換功能,對符合規則的字符串進行匹配替換,這樣會大大減輕我們的工作量,但是,也不要過度依賴替換功能,可能會對已有代碼造成誤操作,引起不必要的麻煩。查找替換步驟如下所示:
- 進入Xcode全局搜索模式
- 切換爲Replace 模式,並修改匹配模式爲:
Regular Expression
- 輸入匹配的正則表達式:
(@"[^"]*[\u4E00-\u9FA5]+[^"\n]*?")
,作用是匹配到所有包含中文的OC字符串 - 在替換輸入欄中輸入替換表達式:
NSLocalizedString($1, nil)
,也就是上面講到的宏,其中$1
是正則匹配到的結果值,作爲參數。 - 最後執行查找,並點擊按鈕
Replace All
進行替換。
上面的方法雖然簡單快捷,但是替換過程中很容易造成語法錯誤,而且很難排查,使用過程中一定要細心、謹慎。
Xib&Storyboard 國際化
xib 和Storyboard 的國際化原理其實和字符串差不多,需要開啓xib 或Storyboard的國際化支持,在右側添加需要國際化的語言,然後Xcode會自動生成xxx.strings
文件,我們只需要翻譯該文件中的字符串即可,如下圖所示:
通過註釋,我們可以看出來是UILabel
的text屬性,其中key是xib自動生成的控件的唯一編碼,value則是我們需要國際化的字符串,只需要將翻譯好的字符串進行替換就可以了。
這裏有一點需要特別注意,當我們修改xib文件時,這個字符串模板文件中的內容是不會同步的,我們還需要添加一個腳本用於同步xib文件的更改,下面我們介紹下這個腳本的添加步驟:
-
下載腳本文件,並導入到項目根目錄
-
打開Xcode 進入
Build Settings
,找到Deployment Location
和Deployment Postprocessing
選項,並設置爲YES
。 -
選中項目的Target,定位到
Build Phases
選項 ,然後添加RunScript,將下面的腳本代碼添加到輸入框中:
python ${SRCROOT}/${TARGET_NAME}/RunScript/AutoGenStrings.py ${SRCROOT}/${TARGET_NAME}
具體操作步驟見下圖:
添加完成後,每當xib或storyboard文件有改動時,只需要重新Build一下,就會更新國際化模板文件了。
圖片&資源文件國際化
圖片國際化
Xcode並沒有給我們提供,專門針對圖片資源的國際化方式。在實際開發中,我們一般會將icon圖標,及常用的展示圖放置到Assets.xcassets
文件中進行管理,將不常用且比較大的圖片資源,直接拖放到Resource
文件夾中,針對這兩種圖片管理方式,我們也採取了不同的國際化方式,下面將介紹實現方式:
對於Assets.xcassets
目錄中的圖片,我們將通過字符串國際化的方式,加載不同語言本版的圖片資源,前提是需要將多個版本的圖片資源先要導入到Assets.xcassets
中,使用方法見下面示例代碼:
// Localizable.strings 文件中
// Assets
"profile_banner" = "profile_banner_en";
"profile_banner" = "profile_banner_hant";
// 使用示例,這裏只需要進行圖片名稱的字符串替換即可
UIImage *img = [UIImage imageNamed:NSLocalizedString(@"profile_banner", nil)];
文件國際化
Resource
中的圖片,由於圖片資源會以文件的形式,直接導入到Xcode工程中,所以可以使用文件國際化的方式,直接在Xcode中打開文件的inspector
面板,選擇需要國際化的Localization版本,Xcode會自動生成國際化模板文件,我們只需要提交模板文件中的內容即可。其他格式的文件也是採用同樣的國際化方式。
如上圖所示,我們對Expression.plist
文件進行了國際化,只需要選擇右側的語言版本,即可以生成對應的模板文件,是不是感覺很簡單呢,趕緊行動起來實踐一下吧!
還有部分特殊的配置文件,比如:InfoPlist
文件等,具體實現和文件國際化的方式是一樣的,這裏就不再贅述了。
應用內語言切換
在實際項目需求中,我們實現了國際化功能後,也會相應的實現應用內語言切換功能,所以在國際化實踐中,是相當重要的一個環節。
通過分析,我們知道,要實現應用內動態語言切換,實際是需要將整個項目的UI,重新進行一次初始化,根據當前選擇的語言類型,加載對應的語言包(例如:en.lproj),這樣就實現了語言的動態切換了。
下面我們通過代碼,展示下實現思路:
- xxxViewModel.m 文件中語言切換相關的代碼實現
### xxxViewModel.m 文件
#define kUserDefaults [NSUserDefaults standardUserDefaults]
// 1. 切換語言入口方法
- (void)switchLanguageWithType:(WLTLanguageType)languageType{
if (languageType == self.currentLanguageType) {
return;
}
self.currentLanguageType = languageType;
NSString *lanCode = nil;
switch (languageType) {
case WLTLanguageTypeFollowSystem:
lanCode = nil;
break;
case WLTLanguageTypeSimplified:
lanCode = @"zh-Hans"; //中文簡體
break;
case WLTLanguageTypeTraditional:
lanCode = @"zh-Hant"; // 中文繁體
break;
case WLTLanguageTypeEnglish:
lanCode = @"en"; // 英文
break;
}
// 切換語言
[self setUserLanguage:lanCode];
// 刷新keyWindow
[kWLTAppDelegate resetKeyWindowRootViewController];
}
// 切換語言
// 註釋:這裏需要將用戶選擇的語言類型進行本地緩存,系統在加載國際化語言bundle的時候會
// 根據這個值來加載語言版本的
- (void)setUserLanguage:(NSString *)userLanguage{
//跟隨手機系統
if (!userLanguage.length) {
[self resetSystemLanguage];
return;
}
//用戶自定義
[kUserDefaults setObject:userLanguage forKey:WLTUserLanguageKey];
[kUserDefaults setObject:@[userLanguage] forKey:@"AppleLanguages"];
[kUserDefaults synchronize];
}
+ (NSString *)userLanguage{
return [kUserDefaults objectForKey:WLTUserLanguageKey];
}
+ (NSString *)systemLanguage{
return [[kUserDefaults objectForKey:@"AppleLanguages"] firstObject];
}
/// 重置系統語言
- (void)resetSystemLanguage{
[kUserDefaults removeObjectForKey:WLTUserLanguageKey];
[kUserDefaults setObject:nil forKey:@"AppleLanguages"];
[kUserDefaults synchronize];
}
- AppDelegate.m 文件中,重新初始化
KeyWindow
的根控制器
### AppDelegate.m 文件
- (void)resetKeyWindowRootViewController{
WLTMainViewModel *mainVm = [WLTMainViewModel viewModel];
mainVm.tabBarSelectedIndex = 3; //註釋:這裏要提前設置爲“語言切換控制器”相應的tabbarIndex
WLTMainViewController *newMainVc = [[WLTMainViewController alloc] initWithViewModel:mainVm];
WLTLanguageViewController *lanVc = [[WLTLanguageViewController alloc] initWithViewModel:[WLTLanguageViewModel viewModel]];
lanVc.hidesBottomBarWhenPushed = YES; //註釋:如果“語言切換控制器”不是導航控制器的根控制器時,需要提前設置tabbar隱藏
dispatch_async(dispatch_get_main_queue(), ^{
// 給出友好提示...
[MBProgressHUD showActivityWithStatus:NSLocalizedString(@"語言切換中...", nil)];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUD];
// 重置keyWindow,會初始化整個UI
self.mainVC = newMainVc;
self.window.rootViewController = newMainVc;
// 跳轉到語言設置頁,很關鍵的一步,讓用戶不會感知到UI的刷新
WLTNavigationController *currentNav = newMainVc.tabBarController.selectedViewController;
NSMutableArray *navVcs = currentNav.viewControllers.mutableCopy;
[navVcs addObject:lanVc];
currentNav.viewControllers = navVcs;
});
});
}
- 這一步是整個語言動態切換最關鍵也是最核心的一步,我們知道OC是一門動態語言,可以在運行時對類進行自定義操作。那麼,我們將利用這一特性,來實現動態語言切換。
在上面介紹中,我們知道國際化最關鍵的一句代碼是#define NSLocalizedString(key, comment)
這個宏替換,展開後我們得知,其內部是調用了NSBundle
的localizedStringForKey:value:
方法。所以,我們只需要在運行時,重寫這個方法,返回用戶選擇的語言版本就可以了。
我們首先創建一個NSBundle
的分類 NSBundle+WLTUtils
,然後創建一個繼承自NSBundle
的子類WLTBundle
,在子類中覆寫-localizedStringForKey: value:
方法。
其實,我們也可以在分類中的+ (void)load
方法中,通過runtime 的Method Swizzle
交換默認的-localizedStringForKey: value:
方法實現,來達到此目的。
下面我們介紹第一種實現方式:
### NSBundle+WLTUtils.m 文件中
// 子類的定義
@interface WLTBundle : NSBundle
@end
@implementation NSBundle (WLTUtils)
// 處理應用內國際化語言切換
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 修改 mainBundle 是所屬類爲"WLTBundle"類
// mainBundle -> NSBundle 修改爲: mainBundle -> WLTBundle -> NSBundle
object_setClass([NSBundle mainBundle], [WLTBundle class]);
});
}
+ (NSString *)currentLanguage{
return [WLTLanguageViewModel userLanguage];
}
@end
@implementation WLTBundle
// 覆寫本地化關鍵發放,進行自定義操作
- (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName{
if ([WLTBundle wlt_mainBundle]) {
return [[WLTBundle wlt_mainBundle] localizedStringForKey:key value:value table:tableName];
} else {
return [super localizedStringForKey:key value:value table:tableName];
}
}
+ (NSBundle *)wlt_mainBundle{
if ([NSBundle currentLanguage].length) {
NSString *path = [[NSBundle mainBundle] pathForResource:[NSBundle currentLanguage] ofType:@"lproj"];
if (path.length) {
return [NSBundle bundleWithPath:path];
}
}
return nil;
}
@end
輔助工具
我們知道,如果是舊項目進行國際化改造的時候,工作量巨大,而且可能出現遺漏,覆蓋不全等問題。那麼,我們如何將3天的工作量,縮短到10分鐘完成呢?聰明且喜歡動手的小夥伴,已經幫我們寫好了自動化工具,是不是有種很崇拜的感覺呢?恩恩🤔,是的👍。
TCZLocalizableTool是GitHub 上的一位小夥伴開發的iOS國際化工具,大大縮減了iOSer們的工作量,這個工具實現了下面幾個功能:
- 可以快速找出國際化文件的語法錯誤
- 可以快速找出未國際化的文本
- 可以找出國際化文件中未國際化的文本
- 更人性化的找出項目中未使用的圖片
有興趣的小夥伴可以在這裏查看使用方法,同時也感謝這位小夥伴的開源貢獻!
總結
至此,iOS國際化的相關實踐就全部介紹完了,其中講到了字符串國際化、xib&stroryboard 文件的國際化、圖片&資源文件的國際化,還介紹了應用內語言切換的實現方式,最後介紹了GitHub上一位小夥伴開發的自動化工具。
總的來說iOS的國際化不算很難掌握, 但是,一般項目中可能涉及不到這個需求,希望有這方面學習需要的小夥伴能看到本篇文章,也算是盡一份綿薄之力。
文中講到的知識點,可能有遺漏,或者不合理的地方,希望各位讀者能幫筆者勘誤,有什麼意見或建議,可在下面進行留言交流,感謝閱讀。
我開發的小電影播放 App 歡迎下載好評哦 ~ 😘