1. 程式人生 > >專案國際化 , 應用內修改app語言

專案國際化 , 應用內修改app語言

在做專案中 , 如果是人群使用不那麼侷限的app就可能涉及到國際化問題 , 國際化 大致可以分為 程式碼部分國際化 , xib 和故事板國際化 , 獲取許可權提示國際化 (訪問相簿許可權 , 定位許可權等等..)和應用內切換語言

前言 : 建立的string檔案 , 如果是英文資源的檔案 ,系統會生成一個en.lproj檔案存放string檔案 , 簡體中文則是 zh-Hans.lproj檔案

一 . 首先 , 最簡單的 , 程式碼部分許可權

1.專案配置 —> command + N –>Strings檔案 —>命名 : Localizable.strings (必須命名為Localizable.strings , 因為系統會自動載入這個名字的檔案 . 也可以自己命名 , 但是個人覺得完全沒必要 ,就不說了)

這裡寫圖片描述

2.配置需要支援的語言 , 此處我僅設定了英文 , 和中文 ,如需要可以設定多國語言

這裡寫圖片描述

2.1選中建立好的 Localizable.strings 檔案 ,右側點選此按鈕
這裡寫圖片描述

2.2 選擇新增英文和中文 , 新增 2次
這裡寫圖片描述

2.3 選中需要國際化的模板 , Base基礎模板可以不選
這裡寫圖片描述

3.可以開始配置對應的文字了 , 通常情況來講我們的App都是中文版的 , 所以這對於一箇中國人來講 , 是一件比較好的事 , 中文版的我們就可以不用配置了 . 英文版的 , 在對於的strings檔案中 , 新增鍵值對如下 :

//需要注意的是 , 分號必須要寫
這裡寫圖片描述

4. 可以開始寫程式碼了 , 具體使用方法如下 :

// NSLocalizedString(<#key#>, <#comment#>) 此巨集為foundation框架定義 , 直接在key中書寫中文即可 , 當手機切換成英文版本時 , 程式會自動將key對應的中文換成中文 .如果是手機設定為中文 , 或者其他語言 , 則還是顯示中文
    UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(150, 300, 100, 50)];

    label.textColor = [UIColor redColor];
    label.text
= NSLocalizedString(@"你好世界",nil); [self.view addSubview:label];

————————————————-

二 . 訪問許可權提示國際化

1. 如上述一樣 , 配置語言 . 然後新建 InfoPlist.strings檔案 , 同樣選中,並勾選英文版本和中文版 —-> 在info.plist檔案中新增許可權欄位

這裡寫圖片描述

在中文檔案中 , 新增許可權提示

這裡寫圖片描述

在英文檔案中 , 新增許可權提示 value值
這裡寫圖片描述

配置info.plist檔案 , 對應的提示內容 不寫 , 因為需要根據設定語言 , 載入 string檔案中對應的語言
這裡寫圖片描述

程式碼中請求使用者許可權

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {


    //判斷使用者授權狀態
    ABAuthorizationStatus state = ABAddressBookGetAuthorizationStatus();

    //判斷   如果授權狀態是未決定的,則請求授權
    if (state == kABAuthorizationStatusNotDetermined) {

        //請求
        //1.獲取通訊錄物件
        ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL);

        //2.請求授權 (使用者決定後就會回撥block)
  ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {

            if (granted) {
                NSLog(@"使用者授權成功");
            }else{
                NSLog(@"使用者授權失敗");
            }
        });
    }
    return YES;
}

執行設定語言 中文

這裡寫圖片描述

執行設定語言 英文

這裡寫圖片描述

我試過使用info.plist原生國際化 , 但是一國際化後 , info.plist的目錄結構就發生變化 , 編譯報錯
這裡寫圖片描述

我試著把info.plist檔案還原到原來的目錄下 , 編譯不報錯了 . 執行彈框獲取許可權時 , 國際化並沒有成功 . 不知道是不是不支援 …

———————————————

三 . 應用內修改語言邏輯

//注意 : 通常來說 , 專案中如果使用了國際化 , 那麼專案中語言會跟隨手機系統的語言變化而變化 ,程式會自動去讀取Userdefault中appleLanguage鍵的值作為app的語言. 而咱們要做的 , 是讓應用內的語言不受手機系統的語言影響 , 而是使用者在應用內設定的語言為準 , 並且實現應用內隨時切換整個應用的語言 (比如微信切換語言)

整體實現邏輯 :

common單例 , MYInternationalManager.h檔案

#import <Foundation/Foundation.h>

@interface MYInternationalManager : NSObject

+ (instancetype)sharedInternationalManager;

//當前載入的bundle
@property (strong, nonatomic) NSBundle *languageBunle;

@end

common單例 , MYInternationalManager.m檔案

#import "MYInternationalManager.h"

static MYInternationalManager *manager = nil;

@implementation MYInternationalManager

+ (instancetype)sharedInternationalManager
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        manager = [[MYInternationalManager alloc]init];
    });

    return manager;
}

@end

2. 設定基類 MYBaseViewController

#import <UIKit/UIKit.h>

@interface MYBaseViewController : UIViewController

- (void)UIConfig;

@end

------------------------------------------------
#import "MYBaseViewController.h"

@interface MYBaseViewController ()

@end

@implementation MYBaseViewController

- (void)viewDidLoad {
    [super viewDidLoad];

     self.view.backgroundColor = [UIColor whiteColor];
    //註冊通知
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(UIConfig) name:@"UIConfig" object:nil];

}


- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    //發出通知
    [[NSNotificationCenter defaultCenter]postNotificationName:@"UIConfig" object:nil];
}

//父類方法 (主要抽取來作為專案中需要國際化控制元件內容的賦值)
- (void)UIConfig
{

}

3 . 程式入口Appdelegate 判斷本地沙盒是否有儲存的 userLanguage (app應用中使用的語言) , 如果有 , app使用儲存的語言 . 如果沒有 , app使用手機系統語言 . 程式碼如下

程式入口Appdelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    //假設我們設定使用者在app中設定的語言 , 在沙盒中對應key值為@"userLanguage"

    //1.程式啟動 , 獲取使用者設定的app語言
    NSString *userLanguage = [[NSUserDefaults standardUserDefaults]objectForKey:@"userLanguage"];

    //2.如果沒有 , 則設定手機設定語言為app語言(系統會自動完成,讀取@"appleLanguage") ,儲存手機設定語言為app語言
    if (!userLanguage.length) {

        //2.1獲取手機設定語言
        NSString *phoneLanguage = [[[NSUserDefaults standardUserDefaults]objectForKey:@"AppleLanguages"]lastObject];

        //2.2 儲存app語言到沙盒
        [[NSUserDefaults standardUserDefaults]setObject: phoneLanguage forKey:@"userLanguage"];
        [[NSUserDefaults standardUserDefaults]synchronize];


        //3.有app語言情況
    }else{

        //3.1設定app語言

        //3.11獲取app語言對應的bundle
        NSString *userLanguagePath = [[NSBundle mainBundle]pathForResource:userLanguage ofType:@"lproj"];
        NSBundle *userLanguageBundle = [NSBundle bundleWithPath:userLanguagePath];

        //儲存當前app內語言
        [MYInternationalManager sharedInternationalManager].languageBunle = userLanguageBundle;

    }
    //設定根控制器
    self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];

    OneViewController *oneVc = [[OneViewController alloc]init];

    MYNavigationController *nav = [[MYNavigationController alloc]initWithRootViewController:oneVc];

    self.window.rootViewController = nav;

    [self.window makeKeyAndVisible];


    return YES;
}

4. 配置PCH檔案 , 方便簡化從當前載入bundle獲取需要的語言

這裡寫圖片描述

開始驗證

顯示一級頁面

#import "MYBaseViewController.h"
//繼承該父類
@interface OneViewController : MYBaseViewController

@end
----------------------------------------------------
#import "OneViewController.h"
#import "SecondViewController.h"

@interface OneViewController ()

@property (nonatomic, strong) UILabel *label;

@end

@implementation OneViewController
//懶載入
- (UILabel *)label
{
    if (!_label) {
        _label = [[UILabel alloc]initWithFrame:CGRectMake(100, 200, 100, 50)];
        _label.textColor = [UIColor redColor];
        _label.font = [UIFont systemFontOfSize:25];
    }
    return _label;
}

- (void)viewDidLoad {
    [super viewDidLoad]; //呼叫父類註冊通知

    self.view.backgroundColor = [UIColor whiteColor];


    [self.view addSubview:self.label];


    //按鈕
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];

    [button setTitle:@"點選進入" forState:UIControlStateNormal];
    [button setTitleColor:[UIColor redColor] forState:UIControlStateNormal];

    button.frame = CGRectMake(200, 400, 100, 50);

    [button addTarget:self action:@selector(push) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];

    //按鈕一
    UIButton *button1 = [UIButton buttonWithType:UIButtonTypeCustom];

    [button1 setTitle:@"切換中文" forState:UIControlStateNormal];
    [button1 setTitleColor:[UIColor redColor] forState:UIControlStateNormal];

    button1.frame = CGRectMake(200,100, 100, 50);
    [button1 addTarget:self action:@selector(changeChinese) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button1];

    //按鈕二
    UIButton *button2 = [UIButton buttonWithType:UIButtonTypeCustom];

    [button2 setTitle:@"切換英文" forState:UIControlStateNormal];
    [button2 setTitleColor:[UIColor redColor] forState:UIControlStateNormal];

    button2.frame = CGRectMake(200,200, 100, 50);
    [button2 addTarget:self action:@selector(changeEnglish) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button2];

}

//切換中文
- (void)changeChinese
{

    //對當前語言進行本地儲存
    [[NSUserDefaults  standardUserDefaults]setObject:@"zh-Hans" forKey:@"userLanguage"];
    [[NSUserDefaults standardUserDefaults]synchronize];

    //改變bundle   //中文語言資原始檔  zh-Hans.lproj
    NSString *path = [[NSBundle mainBundle]pathForResource:@"zh-Hans" ofType:@"lproj"];
    NSBundle *needBundle = [NSBundle bundleWithPath:path];
    [MYInternationalManager sharedInternationalManager].languageBunle = needBundle;
    //發出通知
    [[NSNotificationCenter defaultCenter]postNotificationName:@"UIConfig" object:nil];
}

//切換英文
- (void)changeEnglish
{

    //對當前語言進行本地儲存
    [[NSUserDefaults  standardUserDefaults]setObject:@"en" forKey:@"userLanguage"];
    [[NSUserDefaults standardUserDefaults]synchronize];

    //改變bundle  //英文語言資原始檔 en.lproj
    NSString *path = [[NSBundle mainBundle]pathForResource:@"en" ofType:@"lproj"];
    NSBundle *needBundle = [NSBundle bundleWithPath:path];
    [MYInternationalManager sharedInternationalManager].languageBunle = needBundle;
    //發出通知
    [[NSNotificationCenter defaultCenter]postNotificationName:@"UIConfig" object:nil];

}


//賦值國際化語言
- (void)UIConfig
{
    self.label.text = MY_LocalString(@"你好");
}


//進入下一頁
- (void)push
{

    SecondViewController *secondVc = [[SecondViewController alloc]init];

    [self.navigationController pushViewController:secondVc animated:YES];
}

@end

介面一圖 :
這裡寫圖片描述

跳轉二級介面

#import "MYBaseViewController.h"
//繼承該父類
@interface SecondViewController : MYBaseViewController

@end

------------------------------------------------------
#import "SecondViewController.h"

@interface SecondViewController ()

@property (nonatomic, strong) UILabel *label;

@end

@implementation SecondViewController
//懶載入
- (UILabel *)label
{
    if (!_label) {
        _label = [[UILabel alloc]init];
        _label.frame = CGRectMake(100, 100, 200, 100);
        _label.font = [UIFont systemFontOfSize:25];
        _label.textColor = [UIColor blueColor];

    }
    return _label;
}

- (void)viewDidLoad {
    [super viewDidLoad]; //呼叫父類註冊通知

     self.view.backgroundColor = [UIColor whiteColor];

    [self.view addSubview:self.label];  
}

//重寫父類方法 , 賦值
- (void)UIConfig
{
    self.label.text = MY_LocalString(@"真的會改變喔");
}

跳轉二級介面如下

這裡寫圖片描述

string檔案配置 , 也就是說 , 我可以在這兩種對應的語言間隨意切換 ,

這裡寫圖片描述

實驗效果

初始狀態 , 模擬器預設英文 , 所以專案 幾個label都顯示英文 , 然後通過點選切換 , 所有頁面的label都會隨之改變 .

這裡寫圖片描述

寫在最後 :

整體邏輯 :

1. 當我們國際化一個專案後 , app里加載什麼語言的string檔案是依據手機設定的系統語言而定 , 程式每次執行都會讀取手機系統語言 .

2. 當我們想要專案中的語言 , 不跟隨手機系統 , 而由我們自己決定時 , 我們就必須手動來實現這個功能 , 蘋果並未給我們API . 然而 ,存放國際化語言的檔案 又存在於 Iproj檔案中 , 所以我們可以拿到這個檔案的bundle , 存放於公共全域性單例中 , 並設定成巨集 , 就像系統巨集 NSLocalizedString(<#key#>, <#comment#>)一樣 , 然後所有的賦值 , 用這個巨集來賦值 (如果是簡單的國際化 , 系統已經幫我們實現了從對應bundle中讀取值 , 這裡我們需要手動實現)

3. 通過點選切換按鈕 ,改變當前需要讀取語言的bundle(也就是InternationalManager.languageBunlde的值), 發出通知 ,對涉及國際化的地方 , 進行重新賦值.

4. 程式碼並未進行封裝和抽取

5. 糾正錯誤 ,獲取手機設定語言 : NSString *phoneLanguage = [[[NSUserDefaults standardUserDefaults]objectForKey:@”AppleLanguages”]lastObject]; 網上基本是通過array[0] , 個人覺得不是很準 , 因為英文粗出是以en.Iproj儲存 , array[0]獲取的是 en - US

設定全域性單例 –> 入口Appdelegate判斷是否本地有儲存的使用者設定的語言 . 如果有 , 則當前需要讀取的bundle就是使用者設定語言的bundle . 如果沒有 , 設定bundle為系統語言資源的bundle , 並將語言存入沙盒 —->設定單例 屬性 儲存當前bundle值 , 因為全域性都需要呼叫此單例屬性 —>設定PCH檔案 , 將從單例屬性bundle中讀取對應的語言value抽成巨集 , 方便使用 —>設定基類 , viewDidLoad註冊通知 , viewWillApear 實現通知方法 , 展示頁面繼承該類 ,並重寫父類通知響應方法 —-> 展示頁面 , 重寫父類方法中實現 控制元件賦值

缺陷 : 以後只要涉及國際化語言切換的控制元件賦值 , 都必須抽取在父類響應的通知方法中