10 Tips For Internationalization and Localization

关于iOS项目的国际化,之前有写过一篇文章,不过不是很系统,也有不少纰漏,还特地写过一篇填坑文,但是因为太坑了,所以不了了事。这段时间又踩了不少坑,也啃了啃官方的文档,特此整理10条Tip,有一些问题不会经常遇到,有些细节也很容易被忽略,不过大部分应该还是很有用的!
如果你之前对iOS的国际化一无所知,推荐看一下这篇文章,很精炼也很完整——《Internationalization Tutorial for iOS [2014 Edition]》

1.使用InfoPlist.string为你的应用名、权限提醒等配置信息做国际化

大家都知道,我们使用Localizable.strings文件为代码中的字符串做国际化,但是实际上还有InfoPlist.strings。系统会自动识别InfoPlist.strings来对项目的一些配置信息做国际化,例如:

CFBundleDisplayName 应用名称

NSHumanReadableCopyright CopyRight

NSCameraUsageDescription 相机权限开启时候弹框的提示文字

NSContactsUsageDescription 通讯录权限开启时候弹框的提示文字

NSLocationWhenInUseUsageDescription 定位权限开启时候弹框的提示文字

NSMicrophoneUsageDescription 麦克风权限开启时候弹框的提示文字

NSPhotoLibraryUsageDescription 相册权限开启时候弹框的提示文字

NSRemindersUsageDescription 推送权限开启时候弹框的提示文字

......

诸如以上这些配置,无法直接在InfoPlist中直接配置(要做国际化),所以使用InfoPlist.strings来配置。

使用的方法很简单,新建一个InfoPlist.strings文件,并且在右侧勾选对应的语言即可。

Tip:如果想要对应用名称做国际化,需要在项目配置里添加参数Application has localized display nameYES

2.关于Base Internationlization选项的正确理解以及误区

开启项目的国际化选项会有一个开关Use Base Internationlization,在Localizable.strings中也会有(Base)文件。一开始我很自以为是地以为Base的意思是如果项目并没有对某个语言做国际化,那么就使用Base语言,然而经过试验,发现事实并非是这样的,只是自己自作聪明罢了。下面看一下官方对Base Internationlization的解释:

Base internationalization separates user-facing strings from .storyboard and .xib files. It relieves localizers of the need to modify .storyboard and .xib files in Interface Builder. Instead, an app has just one set of .storyboard and .xib files where strings are in the development language—the language that you used to create the .storyboard and .xib files. These .storyboard and .xib files are called the base internationalization. When you export localizations, the development language strings are the source that is translated into multiple languages. When you import localizations, Xcode generates language-specific strings files for each .storyboard and .xib file. The strings files don’t appear in the project navigator until you import localizations or add languages.
In Xcode 5 and later, base internationalization is enabled by default. If you have an older project, enable base internationalization before continuing, as described in Enabling Base Internationalization.

简单的说,实际上Base Internationlization是为xib和storyboard来服务的。

这也说明了一个问题,很多你原以为的东西事实上并不是一回事,时间久了你会自然而然地认为那是真理而不是当初的揣测,等真正暴露出为难了就很难发现了。

那么iOS系统中语言选取的原则究竟是什么呢?

我举个例子:假设我的应用使用的开发语言是英语,同时对法语、日语、阿拉伯语做了国际化。然而我的系统语言设置的是中文,那么打开应用会显示什么语言呢? 答案是根据你系统的首选语言列表而定,在系统设置-语言设置的界面,有一栏列表叫做首选语言列表,假设你当前系统语言是中文,但是之前一次选择过阿拉伯文为系统语言,那么此事打开你的应用,将会显示阿拉伯文。

iOS对应用语言的判定遵循的原则是:

1 查看应用是否针对系统语言做国际化,如有,选择系统语言

2.遍历系统对首选语言,如果存在对首选语言有做国际化,选择该首选语言

3.全部没有对情况下使用development language

3.针对不同界面开发模式使用不同的国际化方案

纯代码手写UI

没什么好说的,一个Localizable.strings解决所有问题。

使用NSLocalizedString(@"KEY", nil)的宏方法,大部分教程中并没有提过这个方法的第二个参数是何含义,其实大部分情况下也不会用到,所以可以自己再定一个宏来除却不必要的麻烦:

#define LOCAL_LANG(LangKey) NSLocalizedString(LangKey, nil)

当然这个第二个参数并非没有用,Tip4来阐述这第二个参数的作用。

使用Storyboard

之前讲Base Internationlization的时候就提过,它主要是为Storyboard和xib文件服务的。开启之后,在xib和storyboard右侧有localize选项,相应的处理方法和strings文件一样,选择国际化后会生成对应的strings文件,Xcode会抽出xib和Storyboard中所用使用的字符,界面上显示的内容会作为basekeyvalue,修改其他语言的.strings文件就给Storyboard做了国际化。

使用Xib

Storyboard和Xib在做国际化中有一个很大的区别在于,一个Storyboard中可以包含多个视图,多个视图的字符串都可以集中在一个Storyboard.strings中,但是Xib往往是一个视图。当然Xib也可以使用同样的方法做国际化,但是如果一个项目中有30个xib,项目需要有8种语言,那就意味着需要维护30*8=240个strings文件,然而很多情况下每个文件里只有少数几个字符,这样的维护成本太大了。

针对Xib的国际化,可以使用如下方法:

直接在xib中用Localization.strings中的键作为值使用,然后对于需要国际化的view,统一调用如下方法:


+ (void)replaceTextWithLocalizedTextInSubviewsForView:(UIView*)targetView {
    for (UIView* view in targetView.subviews) {
        if (view.subviews.count > 0) {
            [self replaceTextWithLocalizedTextInSubviewsForView:view];
        }
        if ([view isKindOfClass:[UILabel class]]) {
            UILabel* label = (UILabel*)view;
            label.text = LOCAL_LANG(label.text);
            [label sizeToFit];
        }
        if ([view isKindOfClass:[UIButton class]]) {
            UIButton* button = (UIButton*)view;
            [button setTitle:LOCAL_LANG(button.titleLabel.text) forState:UIControlStateNormal];
        }
        if ([view isKindOfClass:[UITextField class]]) {
            UITextField* textField = (UITextField*)view;
            [textField setPlaceholder:LOCAL_LANG(textField.placeholder)];
        }
    }
}

这样所有Xib中要翻译的字符又都统一到了localizable.strings中了,然而这必然存在不少的弊端,譬如无法及时预览。具体见Tip7

当然,开发者遇到这样的困难苹果公司不可能没有想到,之前使用这样略黑科技的方法虽然解决了问题,但是其实苹果官方有更彻底的解决方案。具体见Tip5

4.使用genstrings帮助你生成Localizable.strings

genstrings是一个命令行工具,可以自动检测国际化相关的宏,从而生成对应的Localizable.strings文件,对应的命令很简单

find . -name *.m | xargs genstrings -o en.lproj

NSLocalizedString(@"KEY", nil)的后一个参数实际上就是服务于genstrings的,它的意义就是备注,使用genstrings工具可以按照第一个参数为KEYVALUE,第二个参数为Comment来生成对应的键值对

比如NSLocalizedString(@"Hello",@"This is a comment")就能生成

/* This is a comment */

"Hello" = "Hello";

具体的使用方法和效果可以参见视频https://www.youtube.com/watch?v=1XxxmnDL_ls

关于代码中字符串的国际化可以参考博客:字符串本地化,里面有不错好的实践

5.国际化资源的导出和导入

没错!Xcode自带国际化资源的导入和导出功能!

不信?

点开Editor瞧瞧!

有多少公司是直接把Localizable.strings文件拿出来给翻译人员翻译的?我司甚至为了方便翻译写了个自动抓取放到网页上填表的工具,但是实际上Xcode已经给我们做了很多。

使用Xcode导出的国际化资源是.xliff格式的文件,很多第三方的翻译软件都是支持这个格式的文件的。它能把所有需要国际化的.strings文件全部倒出成对应语言的.xliff文件,翻译公司使用翻译软件生成其他语言的.xliff文件,然后开发者可以使用Xcode自带的导入功能将其导入。

不止如此!导入的时候系统还会提供工具检验是否存在没有翻译的字段,检查diff。

所以如果使用了Xcode自带的导入导出工具,那么之前提到的Xib情况下生成的.strings文件维护困难的问题也随之解决了。

6.修改xcscheme来帮助你对特定对国家语言和环境做调试

不少同学可能会有这样的体验,假设你开发的默认语言是英文,但是想测试下中文环境下的情况,但是即使系统语言是中文,你使用开发证书编译调试的时候依旧会显示英文的界面,这是为什么呢?一张图就能解释这个问题:

调试的时候选择Edit Scheme可以手动选择需要调试的语言和地区环境,这样针对特定语言做调试的时候你再也不需要手动修改本地语言环境了。

7.使用Pseudolocalizations提前发现界面适配问题(国际化适配测试)

之前提过,如果使用Tip3所提过的xib全局替换的黑科技会带来一定的弊端,其中之一就是无法实时预览。那么就来讲一讲如何对不同的国家语言的界面做实时预览。步骤如下

1.选择对应的已经做了国际化的.xib或者.storyboard文件

2.View -> Assistant Editor -> Show Assistant Editor

3.点击Assistant Editor的左上角按钮点击选择最后一个选项preview

4.此时可以看到该界面文件的预览图,右下角会有语言选择,同时会有一个选项:Double-Length Pseudo-Language检验如果是双倍长度的

能否完整显示

5.当然这个功能还可以预览对不同尺寸屏幕的适配

8.保证你的UILabel和UITextField等完美适应不同语言的不同长度

  • 利用好AutoLayout让你的UILabel根据内容自适应长度

  • 如果对UILabel或者UITextField有硬性的MaxWidth设定,达到了最大宽度但是无法显示全,可以采用AutoShrink的方案。

针对UILabel可以在xib或者storyboard中设置AutoShrink属性,该属性的作用是如果Label内容显示不下,使用何种策略来适应,

iOS提供了多种选项,包括按比例缩放、最小字体、减小字间距。当然在代码中可以统一设置UILabel的adjustsFontSizeToFitWidth、

minimumScaleFactor、allowsDefaultTighteningForTruncation等属性,这些属性默认都是NO。

针对UITextField,也有一个Min Font Size的属性可以调整

  • 配合Pseudolocalizations提早发现界面溢出的问题

  • 特别需要注意的地方可以提前跟翻译人员说明,控制字符长度

9.让你的应用适配不同的布局方向

事先声明,这是个大坑

获取大部分开发者都不会遇到过布局方向这个术语,提起这个我突然想问个问题:有没有人有过疑问,在AutoLayoutleadingtrailingleftright到底有什么区别?明明设置的时候表现是一模一样的。

在Xcode中,leadingtrailing是被推荐的,最好的证明就是即使一般人永远都不会在意他们的区别,拖出来的布局永远都不会出现leftright的字段,为什么呢?官方的解释是这样的:

When adding constraints, use the attributes leading and trailing for horizontal constraints.
For left-to-right languages, such as English, the attributes leading and trailing are
equivalent to left and right. For right-to-left language, such as Hebrew or Arabic, leading
and trailing are equivalent to right and left. The leading and trailing attributes are the
default values for horizontal constraints.

用中文讲就是leadingtrailing会根据屏幕的布局方向做不同的表现,在从左到右的布局中,它的效果会和leftright一样,在从右到左的布局中(比如阿拉伯语、锡伯语)leading相当于righttrailing相当于left

想看看具体效果如何,可以把系统语言设为阿拉伯文看看效果。

值得注意的是:iOS9在原先基础上对从右到左的布局做了更彻底的适配,这会是的使用iOS8的SDK编译的包在iOS9阿拉伯环境下会有奇怪的表现,最简单的解决方法就是使用iOS9的SDK重新编译。

然而,在开发中,你会遇到各种各样的情况,下面我列举几种容易遇到的问题:

1.不是所有的界面你都希望它要根据系统的屏幕方向做适配

举个简单的例子,如果你的主页是一个内容宽度为屏幕的2.5倍的scrollView,那么在从右到左的的环境中,整个界面就完全乱了套了(你可以想象一下)。

最简单的解决方案,如果这个界面你不希望它根据系统的布局方向做适配,那么你就把它的所有的leadingtrailing约束换成leftright,干脆的做法就是使用源码方式打开xib文件,做全局替换,对应的,很多界面还是根据系统自动适配方向对于当地人更友好,那就继续使用leadingleft,所以需要灵活使用。

2.系统的界面适配并不会自动给你的UILabel和UITextField做Alignment的适配

当然,我不确定这个玩意儿能否可以全局设置,之前花了一点时间还是没有找到最佳的方案。的确,坑爹的就是即使界面布局左右颠倒了,但是界面元素中的内容缺没有做更深层次的适配,这点很多时候不得不手动适配。所以你需要手动获取当前系统语言,并做相应处理。可以使用AOP做全局的替换,也可以手动调用Category中的方法去做fix,具体我就不贴代码了

3.参数的位置

假设一段字符串需要显示:"Hello %@",name,然而在其他语言中可能是%@,balabalala,name,怎么办!?

一个处理方案就是在localizable.strings做参数话而不是直接在代码中,这是一个好的习惯。当然在阿拉伯环境中还是会遇到一个坑爹的情况,因为阿拉伯文是从右到左的阅读顺序,而中文、英文不是。举个最坑爹的你无法相信的例子:

"一段阿拉伯文 %@",name""%@ 一段阿拉伯文",name"在界面上显示的结果是一样的,name永远在最右边,然而实际的要求就是name需要在最左边。

后来为了解决这个参数的问题,我使用了一个很黑很黑的方法:


- (void)fixArgumentPositionIfNeed {
    //如果不是阿拉伯文
    return;
    NSString *replacedStr = [NSString stringWithFormat:@"ل%@",self.text];
    NSMutableAttributedString *attributeReplacedStr = [[NSMutableAttributedString alloc] initWithString:replacedStr     attributes:nil];
    [attributeReplacedStr addAttributes:@{NSForegroundColorAttributeName:[UIColor clearColor]} range:NSMakeRange(0, 1)];
    [self setAttributedText:attributeReplacedStr];
}

思路就是在字符串的前面再加入一个阿拉伯文字符,然后再把它设为隐藏。。。。。

10.手动获取系统语言和地区环境

尽管iOS中很多国际化的问题都是直接项目适配并且系统帮忙处理的,但是还是会有不少情况需要手动获取系统当前的语言环境,并针对当前语言环境做判断做相应的处理。具体获取语言的代码如下:

+ (NSString *)systemLanguage {
    NSUserDefaults* defs = [NSUserDefaults standardUserDefaults];
    NSArray* languages = [defs objectForKey:@"AppleLanguages"];
    NSString* preferredLang = [languages objectAtIndex:0];
    return preferredLang;
}

其中languages是一个数组,数组中成员就是你的首选语言,其中第一个就是系统当前语言

不过不知大家是否还记得,iOS9出来后微博、微信等应用(这些应用都有一个共同的特点:可以在应用内手动设置系统语言)都出现了语言错乱的问题,问题的根源就是iOS9系统中这个方法取出来的值变了。

在iOS9以前,如果当前语言版本是英文,那就是en,阿拉伯文就是ar,中文就是zh-Hans,然而在iOS9中,假设我当前的语言是简体中文,当前的地区是埃及,取出来的值就是:zh-Hans-EG,没错,在iOS9新的API中,这个方法返回的参数会带上地区的后缀。所以原先对语言的判断就不能单单的isEquasToString了,起码要使用hasPrefix方法或者做一下split

那么问题来了!对于很多App来说系统语言是一个很重要的东西,为什么iOS9的这次升级苹果官方会在这么一个地方做改动呢。我猜呀,苹果官方肯定是不支持App直接使用系统语言的这个参数来控制本地化,更希望App的本地化工作能够遵循系统。

总结

国际化这个工作虽然边缘但是不得不做,而且如果做的不够专业很容易花费很多的人力,也达不到最好的效果。-.- 表示我们现在就在填之前埋下的坑。。所以还是趁此机会跟大家分享一下。

最后,很抱歉因为涉及到的东西比较多,所以不能做到图文并茂,之后我会专门写一个小的项目尽量把我之前提到过的Tips尽量都浓缩在里面,尽量提供一个最佳时间的Demo。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348

推荐阅读更多精彩内容