1. 準備工作
本文以中英文切換爲例. 因爲系統默認語言是英文, 所以我們需要添加中文到項目中.
打開PROJECT:
添加簡體中文:
需要注意的是, 這一步必須要選一個文件進行本地化, 不然語言添加不成功:
添加完成:
新建Strings文件:
取名Localizable.strings (系統默認名):
然後點擊新建的Localizable.strings, 右邊欄實現本地化:
點擊需要本地化的語言(這些語言只有添加到項目中才可見) :
完成後就看到文件有了下拉按鈕:
這個Localizable.strings文件的目的是針對不同語言提供不同的字符串, 後文中用到.
至此, 準備工作完成.
2. 字符串本地化
添加一個字符串:
分別在中英文的Localizable.strings中添加需要本地化的字符串:
其中, "KK_String"是字符串的key, 系統根據這個key去找對應的本地化目標字符串.
實現字符串本地化:
- (void)viewDidLoad {
[super viewDidLoad];
self.stringLabel.text = NSLocalizedString(@"KK_String", nil);
}
效果:
擴展1
在Xcode中也可切換語言:
注意
但是這樣可能會和通過手機(模擬器)中的設置去更改語言有衝突(優先級不同), 爲避免花時間討論這個非必要討論問題, 本文建議直接在設置裏切換語言.
擴展2
當我們使用key去找目標字符串的時候, 如果沒有找到, 那麼系統就會把key字符串當做value字符串返回. 因此, 我們也可以直接使用英文字符串當做key來使用, 這樣就可以省略Localizable.strings (English)裏內容了.
// 中文Localizable
"String" = "字符串";
// 直接用String當做key
- (void)viewDidLoad {
[super viewDidLoad];
self.stringLabel.text = NSLocalizedString(@"String", nil);
}
3. 圖片本地化
3.1 圖片名稱本地化
其實也就是字符串的本地化. 準備兩張圖片image_en.png和image_cn.png, 然後在中文Localizable裏本地化字符串, 然後使用圖片的時候先本地化圖片名.
"image_en" = "image_cn";
self.imageView.image = [UIImage imageNamed:NSLocalizedString(@"image_en", nil)];
3.2 圖片本地化
點擊需要本地化的圖片, 在右邊欄裏選擇需要本地化的語言:
self.imageView.image = [UIImage imageNamed:@"image"];
這裏順便提一下, App圖標也是圖片, 但是這個圖標沒有Localization的選項, 因此不能對圖標進行本地化 (至少我沒找到方法).
4. App名稱/系統權限提示框本地化
在我們的工程中, 有一個info.plist文件, 裏面包含了App名稱, 請求系統權限的提示文等. 這個info文件在程序運行前就已經被系統加載好了, 我們沒辦法使用代碼去修改這些內容, 也就無法使用Localizable.strings去本地化了.
我們可以新建一個infoPlist.strings文件, 作用是將info.plist裏的字符串進行本地化.
和創建Localizable類似, 過程就不重複了.
分別在中英文的InfoPlist.strings裏面本地化App名稱/權限提示文:
en
// App名稱
"CFBundleDisplayName" = "KKLocalizeDemo";
// 權限提示文
"NSAppleMusicUsageDescription" = "App needs to get your media library permissions when using the play sound feature";
"NSCameraUsageDescription" = "App needs to get camera permissions when using the Scan QR code feature";
"NSMicrophoneUsageDescription" = "App needs to get microphone permission when using the intercom function";
"NSPhotoLibraryAddUsageDescription" = "App needs to get album permission when saving or browsing photos";
"NSPhotoLibraryUsageDescription" = "App needs your consent to access the album when saving or browsing photos";
"NSLocationAlwaysUsageDescription" = "We will use your location to search for nearby WiFi hotspots.";
"NSLocationAlwaysAndWhenInUseUsageDescription" = "We will use your location to search for nearby WiFi hotspots.";
"NSLocationWhenInUseUsageDescription" = "We will use your location to search for nearby WiFi hotspots.";
cn
// App名稱
"CFBundleDisplayName" = "KK本地化";
// 權限提示文
"NSAppleMusicUsageDescription" = "App在使用播放聲音功能時,需要獲取媒體庫權限";
"NSCameraUsageDescription" = "App在使用掃描二維碼功能時,需要獲取相機權限";
"NSMicrophoneUsageDescription" = "App在使用語音對講功能時,需要獲取麥克風權限";
"NSPhotoLibraryAddUsageDescription" = "App在保存或者瀏覽照片/視頻時,需要獲取相冊權限";
"NSPhotoLibraryUsageDescription" = "App在保存或者瀏覽照片/視頻時,需要您的同意才能訪問相冊";
"NSLocationAlwaysUsageDescription" = "我們將使用您的位置搜索附近的WiFi熱點。";
"NSLocationAlwaysAndWhenInUseUsageDescription" = "我們將使用您的位置搜索附近的WiFi熱點。";
"NSLocationWhenInUseUsageDescription" = "我們將使用您的位置搜索附近的WiFi熱點。";
對了, 還需要在info.plist裏面添加以下鍵值對:
<key>CFBundleDisplayName</key>
<string>$(PRODUCT_NAME)</string>
<key>NSAppleMusicUsageDescription</key>
<string>App needs to get your media library permissions when using the play sound feature</string>
<key>NSCameraUsageDescription</key>
<string>App needs to get camera permissions when using the Scan QR code feature</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We will use your location to search for nearby WiFi hotspots.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>We will use your location to search for nearby WiFi hotspots.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>We will use your location to search for nearby WiFi hotspots.</string>
<key>NSMicrophoneUsageDescription</key>
<string>App needs to get microphone permission when using the intercom function</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>App needs to get album permission when saving or browsing photos/videos</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>App needs your consent to access the album when saving or browsing photos/videos</string>
App名稱:
請求相冊權限:
- (void)viewDidLoad {
[super viewDidLoad];
self.stringLabel.text = NSLocalizedString(@"String", nil);
self.imageView.image = [UIImage imageNamed:@"image"];
// 檢查相冊權限
[self checkAlbumAuthorizationWithBlock:nil];
}
// 檢查相冊權限
- (void)checkAlbumAuthorizationWithBlock:(void (^ _Nullable ) (BOOL authorized))block {
// 判斷授權狀態
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
if (status == PHAuthorizationStatusNotDetermined) { // 用戶還沒有做出選擇
// 彈框請求用戶授權
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
if (status == PHAuthorizationStatusAuthorized) { // 用戶第一次同意了訪問相冊權限
if (block) block(YES);
} else { // 用戶第一次拒絕了訪問相機權限
if (block) block(NO);
}
}];
} else if (status == PHAuthorizationStatusAuthorized) { // 用戶允許當前應用訪問相冊
if (block) block(YES);
} else { // 用戶拒絕當前應用訪問相冊
if (block) block(NO);
}
}
- storyboard/xib本地化
點擊Main.storyboard, 進行Localize…
然後點擊那個Label, 右邊欄找到Object ID:
然後在Main.string裏進行本地化:
這裏Object ID對應的是Label, 所以需要添加.text
效果和使用NSLocalizedString一致:
6. 多人開發中本地化
目前爲止, 我們所有例子的工程都是共用系統默認的Localizable.strings來進行本地化, 這在多人開發中很可能造成衝突. 因此, 我們需要設計屬於自己的本地化, 也就是將Localizable.strings改成自定義的. 方法是使用NSLocalizedStringFromTable取代NSLocalizedString.
NSLocalizedString和NSLocalizedStringFromTable是這樣define的:
#define NSLocalizedString(key, comment) \
[NSBundle.mainBundle localizedStringForKey:(key) value:@"" table:nil]
#define NSLocalizedStringFromTable(key, tbl, comment) \
[NSBundle.mainBundle localizedStringForKey:(key) value:@"" table:(tbl)]
從這裏我們推測出兩個信息:
- 本地化字符串的過程實際上是從資源文件(NSBundle)中加載不同的字符串. Localizable.strings是放在mainBundle目錄裏面的, 系統通過判斷App語言加載對應的strings文件, 然後通過key查找對應的字符串;
- table參數傳nil, 系統則默認加載名爲Localizable.strings的文件, 而如果改成我們自定義的名字, 則加載名字對應的文件.
new一個MyLocalizable.strings文件:
使用NSLocalizedStringFromTable:
self.stringLabel.text = NSLocalizedStringFromTable(@"String", @"MyLocalizable", nil);
// 也可做成宏
#define KKLocalizedTring(key) NSLocalizedStringFromTable((key), @"MyLocalizable", nil)
// 使用自定義宏
self.stringLabel.text = KKLocalizedTring(@"String");
結果:
7. 應用內切換語言
到目前爲止, 我們所做的語言本地化都是跟隨系統語言的. 接下來我們討論如何在App中切換語言.
點擊查看Localizable.strings文件在Folder中展示:
我們看到不同語言的文件夾:
每個文件夾對應一種語言(有多少種語言就有多少個文件夾), 裏面裝有該語言的所有本地化strings文件:
這些文件夾其實就是資源文件, 後綴名是lproj, 用於裝載對應語言的本地化strings文件. 這些資源文件是通過相應Bundle去加載的, Bundle有mainBundle和其他Bundle.
Bundle是一個捆綁包, 對應一個資源目錄, 其中包含了程序會使用到的資源. 這些資源可以是圖像, 聲音, 編譯好的代碼, nib文件等等(包括我們剛剛提到的.lproj文件).
回想我們之前使用的NSLocalizedString和NSLocalizedStringFromTable, Bundle使用的是mainBundle.
#define NSLocalizedString(key, comment) \
[NSBundle.mainBundle localizedStringForKey:(key) value:@"" table:nil]
#define NSLocalizedStringFromTable(key, tbl, comment) \
[NSBundle.mainBundle localizedStringForKey:(key) value:@"" table:(tbl)]
當我們的App剛啓動加載時, 系統會將系統語言所對應的.Iproj文件打包到mainBundle裏. 比如說, 當前系統語言是英文, 則系統會將en.Iproj文件打包進mainBundle; 而如果是簡體中文, 那麼就將zh-Hans.Iproj文件打包進mainBundle.
所以, 當我們使用mainBundle調用localizedStringForKey:value:table:方法, 得到的是系統指定好的本地化結果.
那麼, 如何在App內切換語言? 這個問題可以進一步分析成: 如何自主選用指定.Iproj文件(裏面包含的本地化strings文件), 來進行本地化呢?
我們反其道而行之, 將指定.Iproj資源文件生成一個path目錄, 然後使用這個path去生成一個Bundle, 然後使用這個Bundle去調用localizedStringForKey:value:table:方法, 得到的就是我們自主指定好的本地化結果啦! 🍺🍺🍺
當然還有一點, 如何找到指定.Iproj資源呢? 我們可以記錄用戶選擇的語言, 將所選語言與.Iproj資源文件名相對應, 然後將.Iproj資源文件名保存到本地. 最後在需要本地化的地方取出.Iproj資源文件名–>生成path–>生成Bundle–>調用localizedStringForKey:value:table:.
整個過程行雲流水有沒有?! 😝😝😝
當然, 如果看起來覺得頭暈, 那就讓代碼治療一切吧.
// 按鈕事件
- (IBAction)btnAction:(UIButton *)sender {
__weak typeof(self) weakSelf = self;
UIAlertController *alertC = [UIAlertController alertControllerWithTitle:@"設置語言" message:nil preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *systemAction = [UIAlertAction actionWithTitle:@"跟隨系統" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[weakSelf setLanguage:KKLanguageType_System];
}];
UIAlertAction *enAction = [UIAlertAction actionWithTitle:@"英文" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[weakSelf setLanguage:KKLanguageType_en];
}];
UIAlertAction *cnAction = [UIAlertAction actionWithTitle:@"中文" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[weakSelf setLanguage:KKLanguageType_cn];
}];
[alertC addAction:systemAction];
[alertC addAction:enAction];
[alertC addAction:cnAction];
[self presentViewController:alertC animated:YES completion:nil];
}
// 切換語言
- (void)setLanguage:(KKLanguageType)type {
// 記錄當前語言到本地
if (type == KKLanguageType_en) {
[[NSUserDefaults standardUserDefaults] setObject:@"en" forKey:@"appLanguage"];
}else if (type == KKLanguageType_cn) {
[[NSUserDefaults standardUserDefaults] setObject:@"zh-Hans" forKey:@"appLanguage"];
}else {
[[NSUserDefaults standardUserDefaults] setObject:nil forKey:@"appLanguage"];
}
[[NSUserDefaults standardUserDefaults] synchronize];
// 刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
// 刷新按鈕
if (type == KKLanguageType_en) {
[self.btn setTitle:@"英文" forState:UIControlStateNormal];
}else if (type == KKLanguageType_cn) {
[self.btn setTitle:@"中文" forState:UIControlStateNormal];
}else {
[self.btn setTitle:@"跟隨系統" forState:UIControlStateNormal];
}
// 本地化Label
NSString *language = [[NSUserDefaults standardUserDefaults] objectForKey:@"appLanguage"];
NSString *path = [[NSBundle mainBundle] pathForResource:language ofType:@"lproj"];
NSBundle *bundle = language ? [NSBundle bundleWithPath:path] : NSBundle.mainBundle;
self.stringLabel.text = [bundle localizedStringForKey:@"String" value:nil table:@"Localizable"];
// 本地化圖片
self.imageView.image = [UIImage imageNamed:[bundle localizedStringForKey:@"image_en" value:nil table:@"Localizable"]];
});
}
宏定義那一堆本地化代碼, 然後看起來就清爽了:
#define KKLocalized(key) \
[([[NSUserDefaults standardUserDefaults] objectForKey:@"appLanguage"] ? [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:[[NSUserDefaults standardUserDefaults] objectForKey:@"appLanguage"] ofType:@"lproj"]] : NSBundle.mainBundle) localizedStringForKey:(key) value:nil table:@"Localizable"]
// 本地化Label
self.stringLabel.text = KKLocalized(@"String");
// 本地化圖片
self.imageView.image = [UIImage imageNamed:KKLocalized(@"image_en")];
本例中
- 跟隨系統語言是向本地的key"appLanguage"對應的值置nil, 然後取出的時候發現是nil就用mainBundle去調用方法.
- key"appLanguage"是自由定義的, 主要用於保存對應名稱到本地.
- table使用Localizable是整個項目共用的, 如果多人開發需自己定義(如本例的MyLocalizable.strings).
效果圖: