文章目录
  1. 1. 开始本地化
  2. 2. 应用名称的本地化
  3. 3. 文本的本地化
    1. 3.1. 创建文件
    2. 3.2. 在代码中使用
    3. 3.3. 使用自定义宏
  4. 4. 应用内切换语言
  5. 5. XIB&Storyboard的本地化
    1. 5.1. 设置运行语言环境
    2. 5.2. 生成prefix.pch文件

多语言本地化的核心思想就是为每种语言单独定义一份资源,iOS就是通过xxx.lproj目录来定义每个语言的资源,这里的资源可以是图片,文本,Storyboard,Xib等。

每种语言都有自己的 语言代码.lproj 文件夹,加载资源时只需要加载相应语言文件夹下的资源就OK,这步可以系统为我们完成,也可以手动去做。

项目源代码中如果有多个不同目录的本地化资源,则会有产生多个xxx.lproj

开始本地化

首先点击 项目->PROJECT->Info->Localizations 中添加要支持的语言

此处 Use Base Internationalization 开启状态下,每个本地化资源文件会有个Base选项,主要针对String,Storyboard,Xib作为一个基础的模板

应用名称的本地化

(1)创建InfoPlist.string,并进行Localization配置:

在项目中点击New File–>Resource–>Strings File –>更名为InfoPlist–>点击创建。

选中InfoPlist.strings —>在Xcode右边栏找到Localization—>在Localization中点“+”进行配置。(如没有则点击 XCode—>View—>Utilities—>File Inspector)

(2)添加属性:

在对应InfoPlist.string文件中输入代码: CFBundleDisplayName = "多语言测试";

注意:CFBundleDisplayName加不加双引号都行,”多语言测试”便是对应的应用名。

(3)编辑Info.plist:

打开Info.plist,添加一个新的属性 Application has localized display name, 设置其类型为boolean,并将其value设置为YES即可

文本的本地化

主要针对代码中的字符串进行本地化,比如说一些消息,UI标题等。

创建文件

我们通过一个 Localizable.strings 文件来存储每个语言的文本,它是iOS默认加载的文件,如果想用自定义名称命名,在使用 NSLocalizedString 方法时指定 tableName 为自定义名称就好了。

每个资源文件如果想为一种语言添加支持,通过其属性面板中的 Localization 添加相应语言就行了

此时Localizable.strings处于可展开状态,子级有着相应语言的副本。我们把相应语言的文本放在副本里面就行了

在代码中使用

先在对应语言的 Localizable.strings 中添加 key=value; 字串(各个语言文件中key要相同)

然后在代码中使用 NSLocalizedString(<#key#>, <#comment#>) 来读取本地化字符串。

使用自定义宏

系统提供的NSLocalizedString宏不能指定语言,所以我一般使用自定义宏。需要添加扩展类:NSBundle+Localizations,然后在Prefix.pch中导入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// NSBundle+Localizations.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
extern NSString *const UserDefaultKey_AppLanguage;
/** 根据用户选择 切换语言 。系统宏定义必须跟随系统的默认语言! */
#define USLocalizedString(key,tbl) [NSString stringWithFormat:@"%@",[[NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"%@",[[NSUserDefaults standardUserDefaults] objectForKey:UserDefaultKey_AppLanguage]] ofType:@"lproj"]] localizedStringForKey:(key) value:nil table:(tbl?:@"Localizable")]]
@interface BundleEx : NSBundle
@end
@interface NSBundle (Localizations)
+ (void)setLanguage:(NSString *)language;
@end
//NSBundle+Localizations.m
#import "NSBundle+Localizations.h"
#import <objc/runtime.h>
static const char kBundleKey = 0;
NSString *const UserDefaultKey_AppLanguage = @"AppLanguage";
@implementation BundleEx
- (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];
}
}
@end
@implementation NSBundle (Localizations)
+ (void)setLanguage:(NSString *)language
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
object_setClass([NSBundle mainBundle],[BundleEx class]);
});
id value = language ? [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]] : nil;
objc_setAssociatedObject([NSBundle mainBundle], &kBundleKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

自定义宏USLocalizedString 的用法和NSLocalizedString是一样的。

应用内切换语言

应用启动时在AppDelegate didFinishLaunchingWithOptions 中先设置默认语言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@implementation AppDelegate
- (void)setPreferredLanguage
{
// 设置默认语言
if (![[NSUserDefaults standardUserDefaults] objectForKey:UserDefaultKey_AppLanguage]) {
NSArray *languages = [NSLocale preferredLanguages]; //系统偏好语言
NSString *language = languages.firstObject;
if ([language hasPrefix:@"zh-Hans"]) {
[[NSUserDefaults standardUserDefaults] setObject:@"zh-Hans" forKey:UserDefaultKey_AppLanguage];
}
else if ([language hasPrefix:@"en"]) {
[[NSUserDefaults standardUserDefaults] setObject:@"en" forKey:UserDefaultKey_AppLanguage];
}
}
NSString *language = [[NSUserDefaults standardUserDefaults] objectForKey:UserDefaultKey_AppLanguage];
[NSBundle setLanguage:language];
}
- (void)resetWindowRootViewController {
// 重新设置window的RootViewController 来刷新所有界面的语言
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[self setPreferredLanguage];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
[self resetWindowRootViewController];
return YES;
}

切换语言时 UserDefaultKey_AppLanguage 的 value 就是选择的 语言代码,例如:en、zh-Hans

1
2
3
4
5
6
7
- (void)didSeleteLanguage:(NSString *)language
{
[[NSUserDefaults standardUserDefaults] setObject:language forKey:UserDefaultKey_AppLanguage];
[NSBundle setLanguage:language];
[(AppDelegate *)[UIApplication sharedApplication].delegate resetWindowRootViewController];
}

注意:设置完语言之后一定要重新刷新下页面

XIB&Storyboard的本地化

XIB本地化就稍微麻烦了点,它有二种方案:

  • 方案一:每种语言定制一套XIB

在上图我们可以看到,每种语言都可以切换为strings或XIB(默认为strings)。如果选用 Interface Builder Cocoa Touch XIB 方案,那么每种语言都有一套相应的XIB,各个语言XIB间的界面改动不关联

  • 方案二:基于基础的Base XIB以及每种语言一套strings

基于一个基础的XIB,可以看作是一个基础的模板,XIB里面所有的文本类资源(如UILabel的text)都会被放在相应语言的strings里面。

首选方案二:

因为采用方案一,意义着你每改动一个界面元素就得去相应语言XIB一一改动,那跟为每个语言新起一个项目是一样的道理。但是采用方案二,我们只需改动Base XIB就行了

注意:方案二中相应语言的strings一旦生成后,Base XIB有任何编辑都不会影响到strings,这就意味着如果我们删除或添加了一个UILabel的text,strings也不能同步改动

还好,Xcode为我们提供了 ibtool 工具来生成XIB的strings文件。

1
ibtool Main.storyboard --generate-strings-file ./NewTemp.string


但是ibtool生成的strings文件是BaseStoryboard的strings(默认语言的strings),且会把我们原来的strings替换掉。所以我们要做的就是把新生成的strings与旧的strings进行冲突处理(新的附加上,删除掉的注释掉)。

这一切可以用这个pythoy脚本来实现(AutoGenStrings.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#!/usr/bin/env python
# encoding: utf-8
"""
untitled.py
Created by linyu on 2015-02-13.
Copyright (c) 2015 __MyCompanyName__. All rights reserved.
"""
import imp
import sys
import os
import glob
import string
import re
import time
import traceback
imp.reload(sys)
sys.setdefaultencoding('utf-8') #设置默认编码,只能是utf-8,下面\u4e00-\u9fa5要求的
KSourceFile = 'Base.lproj/*.xib'
KTargetFile = '*.lproj/*.strings'
KGenerateStringsFile = 'TempfileOfStoryboardNew.strings'
ColonRegex = ur'["](.*?)["]'
KeyParamRegex = ur'["](.*?)["](\s*)=(\s*)["](.*?)["];'
AnotationRegexPrefix = ur'/(.*?)/'
def getCharaset(string_txt):
filedata = bytearray(string_txt[:4])
if len(filedata) < 4 :
return 0
if (filedata[0] == 0xEF) and (filedata[1] == 0xBB) and (filedata[2] == 0xBF):
print 'utf-8'
return 1
elif (filedata[0] == 0xFF) and (filedata[1] == 0xFE) and (filedata[2] == 0x00) and (filedata[3] == 0x00):
print 'utf-32/UCS-4,little endian'
return 3
elif (filedata[0] == 0x00) and (filedata[1] == 0x00) and (filedata[2] == 0xFE) and (filedata[3] == 0xFF):
print 'utf-32/UCS-4,big endian'
return 3
elif (filedata[0] == 0xFE) and (filedata[1] == 0xFF):
print 'utf-16/UCS-2,little endian'
return 2
elif (filedata[0] == 0xFF) and (filedata[1] == 0xFE):
print 'utf-16/UCS-2,big endian'
return 2
else:
print 'can not recognize!'
return 0
def decoder(string_txt):
var = getCharaset(string_txt)
if var == 1:
return string_txt.decode("utf-8")
elif var == 2:
return string_txt.decode("utf-16")
elif var == 3:
return string_txt.decode("utf-32")
else:
return string_txt
def constructAnotationRegex(str):
return AnotationRegexPrefix + '\n' + str
def getAnotationOfString(string_txt,suffix):
anotationRegex = constructAnotationRegex(suffix)
anotationString = ''
try:
anotationMatch = re.search(anotationRegex,unicode(string_txt))
if anotationMatch:
match = re.search(AnotationRegexPrefix,anotationMatch.group(0))
if match:
anotationString = match.group(0)
except Exception as e:
print 'Exception:'
print e
return anotationString
def compareWithFilePath(newStringPath,originalStringPath):
#read newStringfile
nspf=open(newStringPath,"r")
#newString_txt = str(nspf.read(5000000)).decode("utf-16")
newString_txt = decoder(str(nspf.read(5000000)))
nspf.close()
newString_dic = {}
anotation_dic = {}
for stfmatch in re.finditer(KeyParamRegex , newString_txt):
linestr = stfmatch.group(0)
anotationString = getAnotationOfString(newString_txt,linestr)
linematchs = re.findall(ColonRegex, linestr)
if len(linematchs) == 2:
leftvalue = linematchs[0]
rightvalue = linematchs[1]
newString_dic[leftvalue] = rightvalue
anotation_dic[leftvalue] = anotationString
#read originalStringfile
ospf=open(originalStringPath,"r")
originalString_txt = decoder(str(ospf.read(5000000)))
ospf.close()
originalString_dic = {}
for stfmatch in re.finditer(KeyParamRegex , originalString_txt):
linestr = stfmatch.group(0)
linematchs = re.findall(ColonRegex, linestr)
if len(linematchs) == 2:
leftvalue = linematchs[0]
rightvalue = linematchs[1]
originalString_dic[leftvalue] = rightvalue
#compare and remove the useless param in original string
for key in originalString_dic:
if(key not in newString_dic):
keystr = '"%s"'%key
replacestr = '//'+keystr
match = re.search(replacestr , originalString_txt)
if match is None:
originalString_txt = originalString_txt.replace(keystr,replacestr)
#compare and add new param to original string
executeOnce = 1
for key in newString_dic:
values = (key, newString_dic[key])
if(key not in originalString_dic):
newline = ''
if executeOnce == 1:
timestamp = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))
newline = '\n//##################################################################################\n'
newline +='//# AutoGenStrings '+timestamp+'\n'
newline +='//##################################################################################\n'
executeOnce = 0
newline += '\n'+anotation_dic[key]
newline += '\n"%s" = "%s";\n'%values
originalString_txt += newline
#write into origial file
sbfw=open(originalStringPath,"w")
sbfw.write(originalString_txt)
sbfw.close()
def extractFileName(file_path):
seg = file_path.split('/')
lastindex = len(seg) - 1
return seg[lastindex]
def extractFilePrefix(file_path):
seg = file_path.split('/')
lastindex = len(seg) - 1
prefix = seg[lastindex].split('.')[0]
return prefix
def generateStoryboardStringsfile(storyboard_path,tempstrings_path):
cmdstring = 'ibtool '+storyboard_path+' --generate-strings-file '+tempstrings_path
if os.system(cmdstring) == 0:
return 1
def main():
filePath = sys.argv[1]
sourceFilePath = filePath + '/' + KSourceFile
sourceFile_list = glob.glob(sourceFilePath)
if len(sourceFile_list) == 0:
print sourceFilePath + 'error dictionary,you should choose the dic upper the Base.lproj'
return
targetFilePath = filePath + '/' + KTargetFile
targetFile_list = glob.glob(targetFilePath)
tempFile_Path = filePath + '/' + KGenerateStringsFile
if len(targetFile_list) == 0:
print 'error framework , no .lproj dic was found'
return
for sourcePath in sourceFile_list:
sourceprefix = extractFilePrefix(sourcePath)
sourcename = extractFileName(sourcePath)
print 'init with %s'%sourcename
if generateStoryboardStringsfile(sourcePath,tempFile_Path) == 1:
print '- - genstrings %s successfully'%sourcename
for targetPath in targetFile_list:
targetprefix = extractFilePrefix(targetPath)
targetname = extractFileName(targetPath)
if cmp(sourceprefix,targetprefix) == 0:
print '- - dealing with %s'%targetPath
compareWithFilePath(tempFile_Path,targetPath)
print 'finish with %s'%sourcename
os.remove(tempFile_Path)
else:
print '- - genstrings %s error'%sourcename
if __name__ == '__main__':
main()


然后我们将借助 Xcode 中 Run Script 来运行这段脚本。这样每次Build时都会保证语言strings与Base XIB保持一致



1
python ${SRCROOT}/RunScript/AutoGenStrings.py ${SRCROOT}/${TARGET_NAME} //参数为StoryBoard,XIB所在目录


其他:XIB中UITextView本地化是无效的

设置运行语言环境

如果使用系统的宏的情况下,如果我们第一次安装APP时不想默认跟随系统,那么可以通过Xcode的scheme来指定特定语言

生成prefix.pch文件

Xcode6之后, 新建工程默认是没有pch文件(预编译头文件)的,需要手动添加:

$(PROJECT_DIR)/MultiLanguage/prefix.pch




GitHub Demo:https://github.com/marujun/MultiLanguage