iOS國際化詳解

iOS國際化實踐

前言

爲了實現在iOS項目中的多語言切換,我們需要對工程中的各個資源、 各個模塊進行相應的國際化操作。在實際的項目需求中,一般還需要進行應用內語言切換,除了默認的跟隨系統之外,還要實現動態的語言版本切換。

實現思路

字面上理解,國際化就是對應用中出現的文字、圖片資源進行翻譯,然後通過鍵值映射的方式進行切換即可,但是在實踐中需要處理的地方和注意的點還是挺多的, 尤其對於舊項目進行國際化改造時,工作量還是挺大的。

總的來說,國際化就是對資源進行替換,分別涉及到了字符串國際化、圖片文件等資源國際化、xib文件國際化、InfoPlist國際化、網絡數據國際化等。下面我們將通過舉例,介紹在各種情況下的國際化實踐。

字符串國際化

字符串在整個國際化中佔比最高,首先我們需要在Xcode中,創建需要國際化的語言模板,然後在相應的模板中通過"key" = "value"的形式,對項目中出現的字符串進行翻譯即可。

在Xcode中選中項目的PROJECT->Info選項,定位到Localizations標籤,添加需要支持的語言版本:

img_01

然後,我們需要手動創建Localizable.strings 文件,用於生成本地化字符串映射文件。需要注意的是這個文件名必須設置爲指定的Localizable ,這樣系統纔會識別。

爲了簡便,如果我們主語言使用的是中文的話,我們可以將中文作爲key,這樣的話,在代碼中,我們可以直接在宏替換中寫中文,如果國際化文件中沒有對應的翻譯時,會直接返回key,這樣我們就不需要再添加簡體中文的語言模板了。

新建文件,選擇Strings File 模板,創建Localizable.strings文件

img_02

img_03

創建成功後,我們選中Localizable.strings文件,點擊右側的Localization按鈕,勾選剛纔創建的語言類型

img_04

當添加完成後,我們會發現Localizable.strings文件變成了一個文件夾樣式,展開後,我們就可以看到生成的字符串模板文件了。

img_05

現在我們就可以在相應的模板中,對字符串進行國際化了。如下圖所示,等號左邊的是key,等號右邊的是對key的翻譯:

img_06

那麼,在代碼中是如何使用的呢?只需要在代碼中,將字符串進行如下的宏替換即可:

// 本地化字符串宏定義,key:需要國際化的字符串,comment:一個註釋,一般設置爲nil
#define NSLocalizedString(key, comment)

// 舉例
self.title = NSLocalizedString(@"錢包", nil);

對於舊項目中有大量字符串需要替換的時候,手動替換起來想想是不是很頭疼呢?其實,我們也可以通過Xcode的全局替換功能,對符合規則的字符串進行匹配替換,這樣會大大減輕我們的工作量,但是,也不要過度依賴替換功能,可能會對已有代碼造成誤操作,引起不必要的麻煩。查找替換步驟如下所示:

  1. 進入Xcode全局搜索模式
  2. 切換爲Replace 模式,並修改匹配模式爲:Regular Expression
  3. 輸入匹配的正則表達式:(@"[^"]*[\u4E00-\u9FA5]+[^"\n]*?"),作用是匹配到所有包含中文的OC字符串
  4. 在替換輸入欄中輸入替換表達式:NSLocalizedString($1, nil),也就是上面講到的宏,其中$1是正則匹配到的結果值,作爲參數。
  5. 最後執行查找,並點擊按鈕Replace All進行替換。

img_12

上面的方法雖然簡單快捷,但是替換過程中很容易造成語法錯誤,而且很難排查,使用過程中一定要細心、謹慎。

Xib&Storyboard 國際化

xib 和Storyboard 的國際化原理其實和字符串差不多,需要開啓xib 或Storyboard的國際化支持,在右側添加需要國際化的語言,然後Xcode會自動生成xxx.strings文件,我們只需要翻譯該文件中的字符串即可,如下圖所示:

img_08

通過註釋,我們可以看出來是UILabel的text屬性,其中key是xib自動生成的控件的唯一編碼,value則是我們需要國際化的字符串,只需要將翻譯好的字符串進行替換就可以了。

這裏有一點需要特別注意,當我們修改xib文件時,這個字符串模板文件中的內容是不會同步的,我們還需要添加一個腳本用於同步xib文件的更改,下面我們介紹下這個腳本的添加步驟:

  1. 下載腳本文件,並導入到項目根目錄

  2. 打開Xcode 進入Build Settings,找到Deployment Location  Deployment Postprocessing選項,並設置爲YES

  3. 選中項目的Target,定位到Build Phases選項 ,然後添加RunScript,將下面的腳本代碼添加到輸入框中:

python ${SRCROOT}/${TARGET_NAME}/RunScript/AutoGenStrings.py ${SRCROOT}/${TARGET_NAME}

具體操作步驟見下圖:

img_10

img_09

添加完成後,每當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會自動生成國際化模板文件,我們只需要提交模板文件中的內容即可。其他格式的文件也是採用同樣的國際化方式。

img_11

如上圖所示,我們對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)這個宏替換,展開後我們得知,其內部是調用了NSBundlelocalizedStringForKey: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 歡迎下載好評哦 ~ 😘

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章