iOS国际化及本地化(一)不同语言的差异处理及测试

国际化及本地化概念

将标题取名为国际化及本地化(internationalization and localization),是因为这两个概念是有差异的,而这个差异常常被我们忽略,以下是维基百科的解释:

国际化是指在设计软件,将软件与特定语言及地区脱钩的过程。当软件被移植到不同的语言及地区时,软件本身不用做内部工程上的改变或修正。本地化则是指当移植软件时,加上与特定区域设置有关的信息和翻译文件的过程。

国际化和本地化之间的区别虽然微妙,但却很重要。国际化意味着产品有适用于任何地方的“潜力”;本地化则是为了更适合于“特定”地方的使用,而另外增添的特色。用一项产品来说,国际化只需做一次,但本地化则要针对不同的区域各做一次。这两者之间是互补的,并且两者合起来才能让一个系统适用于各地。

有些时候我们也会用国际化或者全球化代替这两者含义。

作为一款优秀的产品我们做多语言版本时不应仅仅考虑到翻译这一层面,还有更多本地化相关内容需要我们注意,这篇文章主要涉及的也是本地化这一块。

国际化工作流程

本篇文章主要介绍Internationalize和Test这两步。

本文目录为:

1.增加多语言

2.UI元素的本地化

3.资源文件本地化

4.字符串相关的本地化

5.使用NSLocal进行本地化

6.从右到左语言的处理

7.本地化测试

增加多语言

1、在项目导航栏选择项目(不是target)

2、在Localizations一栏,点击“+”号,添加语言

每个条目都是由语言名称和语言id构成,例如Chinese(Simplified)(zh-Hans), Japanese(ja)
至此我们的项目就开启了对应语言的本地化支持。

3、在对话框中选中你想本地化的文件。

语言和区域的影响


通过观察系统日历,我们可以看到即使语言一样,国家区域的不一致也会有一些约定上的区别,关于日期和时间的本地化会在下面介绍。

1、语言设置:Setting -> General -> Language & Region

同样的,关于Region和Calendar的设置也在该页面。

资源文件的本地化

storyboard, xib文件

对于sotryboardxib文件的本地化是Xcode直接支持的。
在添加语言时会提示我们自动选择创建本地化文件,如果是在添加语言之后创建的IB文件,可以通过xcode右侧属性栏中点击Localize...生成本地化文件。

图片文件

1、方法一
对于图片内容我们可以通过同IB文件的方式进行本地化,但是有一个限制就是图片要是放到项目文件层级的,而不能放到Assets文件夹中。
好消息是Xcode 11将放开这种限制,对于Assets引入的图片也可以做本地化处理。

2、方法二
除了Xcode本身支持的方式,我们还可以通过命名来区分图片内容,把图片名当做需要本地化的字符串,各个语言对应不同版本的图片名,这样也可以实现图片文件的本地化。

音视频及其他资源文件

如果是内置的像是音视频,json或者其他类型的配置文件这类内容,可以使用图片文件的方法二进行引入。

更多详细的设置可以参考这个文章:iOS语言国际化/本地化-实践总结

UI元素的本地化

使用Auto layout

Auto layout是相对布局,它有能力在语言和区域变化时进行自适应。以下有几点使用auto layout的技巧:

1、移除宽度的约束
相同含义下不同语言宽度往往不一样,应该让控件能够自适应。

2、使用内容内部大小
fields和label默认是自动调整大小的,如果一个显示本地化内容的视图需要这个功能,选择该view,选择Editor > Size To Fit Content

3、使用leadingtrailing属性
正常leadingtrailing对应leftright,他们含义相同。但是有些国家,像是希伯来和阿拉伯的人使用习惯是从右往左。如果你是使用leadingtrailing,在该环境下将自动对应rightleft

4、将视图固定到相邻视图
就是定义相邻约束,避免某一视图变化导致重叠。

使用伪本地化发现问题

这个功能只支持使用storyboardxib进行布局的UI。
1、选中需要测试的.storyboard或者.xib文件
2、选择菜单栏 View > Assistant Editor > Show Assistant Editor

字符串相关的本地化

使用Unicode字符串

对于所有面向用户的字符串都要使用NSString, NSAttributedString,对于Swift就是String, AttributedString,因为他们支持Unicode,Unicode是世界上所有书写系统的字符编码标准。

对于一些特殊的字符串需求:
1、访问字符串中的字符
使用NSString中的rangeOfComposedCharacterSequenceAtIndex:rangeOfComposedCharacterSequencesForRange:方法,他们会确保你在取字符串时不会破坏原文本。看一个例子你可能会明白:


这两个文字无论是在UTF-16还是UTF-32编码的情况下都是不同的长度,所以我们不能通过长度而要通过以上的两种方式取目标字符串。

2、遍历字符串

如果我们要遍历展示下面的字符串:

可以通过enumerateSubstringsInRange:options:usingBlock:方法,其中options参数如果传递NSStringEnumerationByComposedCharacterSequences将会按照最小字符进行遍历,如果选用NSStringEnumerationByWords将会按照词语进行遍历。
以上例子使用该值遍历的结果是:

更多关于字符串相关的本地化问题可以参照该条视频:
WWDC 2013 Making Your App World-Ready

3、关于人名,邮寄地址,电话号码的检测
因为不同国家对于人名和电话号码的规则差别较大,我们可以针对不同国家写正则进行检测,也可以使用苹果提供的一个特殊含义字符的检测类:NSDataDetector

支持检测的类型包括日期,地址,链接,手机号,交通信息。

获取当前语言

将语言设置为English(United Kingdom),区域设置成United States,通过以下API获取到:

1
2
//en
NSString *languageID = [[NSBundle mainBundle] preferredLocalizations].firstObject;

一般获取语言所用的方式是通过Bundle也就是第一种方式。

使用NSLocal进行本地化

NSLocale对象封装关于特定区域格式化标准的信息,包括日期,时间,测量,数字,货币等一系列内容。

将语言设置为English(United Kingdom),区域设置成United States,通过以下API获取到:

1
2
3
4
//en-GB_US
[NSLocale currentLocale].localeIdentifier;
//en
[NSLocale currentLocale].languageCode;

其中languageCode跟通过Bundle获取到的是一样的。

其中localIdentifier表示为en-GB_US,对应为:语言id-国家id_区域码,这几个内容都可以通过NSLocal对象取到。

获取特定语言的引号

因为每种语言对于引号的使用是不一样的,我们可以通过NSLocal获取到引号

1
2
3
4
5
6
7
8
9
//1.Get the language that the app is using.
NSString *languageID = [[NSBundle mainBundle] preferredLocalizations].firstObject;
//2.Get the associated locale object.
NSLocale *locale = [NSLocale localeWithLocaleIdentifier:languageID];
//3.Get the beginning and ending symbols for quotes from the locale object.
bQuote = [locale objectForKey:NSLocaleQuotationBeginDelimiterKey];
eQuote = [locale objectForKey:NSLocaleQuotationEndDelimiterKey];
//4.Format a string using the locale-sensitive quotes.
quotedString = [NSString stringWithFormat:@"%@%@%@", bQuote, myText, eQuote];

以下展示了不同区域对于myText@"iPhone"时的字符串效果。

字符串的本地化

1、创建格式化字符串
应该使用localizedStringWithFormat:而不是stringWithFormat:

1
NSString *localizedString = [NSString localizedStringWithFormat:@"%3.2f", myNumber];

此方法会根据系统Local进行显示。

日期时间本地化

2、日期和时间转字符串
使用NSDateFormatter表示NSDate对象。推荐使用这个方法:localizedStringFromDate:dateStyle:timeStyle:

1
2
//14 Aug 2019 at 11:19
NSString *localizedDateTime = [NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterShortStyle];

下表展示了语言为英语,区域是美国时的日期和时间格式:

下表展示了dateStyleNSDateFormatterMediumStyletimeStyleNSDateFormatterShortStyle在不同语言和地区时的表现形式:


3、使用自定义日期和时间格式

1
2
3
4
5
6
7
8
//1.Create an NSDateFormatter object.
NSDateFormatter *dateFormatter = [NSDateFormatter new];
//2.get a localized format string from a template that you provide.
NSString *localeFormatString = [NSDateFormatter dateFormatFromTemplate:@"MMM d" options:0 locale:dateFormatter.locale];
//3.Set the format of the NSDateFormatter instance to the locale-sensitive format string.
dateFormatter.dateFormat = localeFormatString;
//4.Use the stringFromDate: method to get a localized string representation of the date.
NSString *localizedString = [dateFormatter stringFromDate:[NSDate date]];

在不同语言和区域下localizedString对应的内容为:

3、解析日期字符串

1
2
3
4
5
6
7
8
9
10
//1.Create a date formatter object.
NSDateFormatter *dateFormatter = [NSDateFormatter new];
//2.Set the formatter’s style to a preset style.
dateFormatter.dateStyle = NSDateFormatterMediumStyle;
//3.If the input string is not expected to contain a time, set the time style to none.
dateFormatter.timeStyle = NSDateFormatterNoStyle;
//4.Set the leniency to YES (enables the heuristics).
dateFormatter.lenient = YES;
//5.Convert the string to a date object.
NSDate *date = [dateFormatter dateFromString:inputString];

我们输入的字符串是9/3/14,dateStyle设为NSDateFormatterShortStyle,如果区域为美国,我们得到的NSDate信息为:2014-09-03 07:00:00 +0000,如果区域为德国,我们将得到2014-03-09 08:00:00 +0000

数字本地化

本地化设置会影响小数点符号,千分符,货币符等内容,比如数字1,234.56在意大利应该表示为1.234,56,所以对于数字的格式化我们应该用NSNumberFormatter处理。
注意:NSNumberFormatter不是现成安全的
1、将Number转成本地化的字符串
可以使用NSNumberFormatterlocalizedStringFromNumber:numberStyle:方式

1
NSString *localizedString = [NSNumberFormatter localizedStringFromNumber:myNumber numberStyle:NSNumberFormatterDecimalStyle];

以下是不同语言和区域关于数字的显示效果,左侧的style及numberStyle

2、将字符串转成NSNumber对象
这个类似日期的转换

1
2
3
4
5
6
7
8
//1.Create a number formatter object.
NSNumberFormatter *numberFormatter = [NSNumberFormatter new];
//2.Set the formatter’s style to a preset style.
numberFormatter.numberStyle = NSNumberFormatterDecimalStyle;
//3.Set the leniency to YES (enables the heuristics).
numberFormatter.lenient = YES;
//4.Convert the string to a number object.
NSNumber *number = [numberFormatter numberFromString:inputString];

3、通过NSCalendar计算日期
NSCalendar类封装了日历的所有区域差异和复杂性。说他具有复杂性是因为在不同国家,一年之中的月份可能是12或者13,一月中的天数可能是5到31的任意值,每周第一天可能是周六,周日或者周一。可以看下表

因此使用NSCalendar取这些值将会很方便。
获取Calendar unit的方式为

1
2
3
4
5
6
7
8
9
//1.Create an NSDateComponents object.
NSDateComponents *components = [[NSCalendar currentCalendar]
components:NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit | NSEraCalendarUnit
fromDate:[NSDate date]];
//2.Access the values for day, month, year, and era.
NSInteger day = [components day];
NSInteger month = [components month];
NSInteger year = [components year];
NSInteger era = [components era];

4、监听本地化信息或者时区修改
可以通过NSCurrentLocaleDidChangeNotification监听区域的改变

1
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(localeDidChange:) name:NSCurrentLocaleDidChangeNotification object:nil];

同样的监听时区变化可以通过NSSystemTimeZoneDidChangeNotification

从右到左语言的处理

创建从右到左语言的交互界面

支持从右到左向的语言,在约束层面应该使用Auto layout中的leadingtrailing属性,而不是rightright。可以通过以下对比看到区别

整体像是做了水平的翻转,有很多控件像是segmented控件, 进度指示器系统会自动做翻转。但是有些情况是不需要翻转的:

  • 视频控制和时间线指示器
  • 图片,除非他们传达方向感,如箭头
  • 时钟
  • 乐谱
  • 图表(x轴和y轴总是在一致的方向)

获取布局的方向性

如果我们想获取当前语言是否应该是从右到左项的语言可以通过以下方法:

1
2
3
4
//right-to-left language
if ([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:view.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft) {

}

设置文本的对齐方式

在iOS中默认的文本对齐方式是“natural”,在OS X中默认方式是“left”。natural的含义就是会感觉语言的方向自动调整为left或者right。
如果你想NSMutableParagraphStyle对象的对齐方式为自然的方向,可以:

1
[[(NSMutableParagraphStyle *)paraStyle setAlignment:NSNaturalTextAlignment];

对双向文本的处理

双向文本就是一段文本中及含有从右往左的文本还含有从左往右的文本。是不是感觉很诧异?因为即使像阿拉伯和希伯来国家这些书写习惯为从右往左,但是对于数字和拉丁文是从左往右写的。如果你使用的是标准控件,像Label,TextView,Textfiled他们会自动处理双向文本内容。如果你是使用自定义控件,那这些问题就需要你手动处理。

向双向文本添加Unicode标记

在某些特殊的时候,系统默认的行为可能会导致一些不正确的结果,这时我们可以通过添加Unicode标记进行纠正。

例如,手机号在所有语言中都是从左往右读的,如果一个需要本地化的字符串变量表示一个手机号,如果我们需要保证他是从左往右的顺序展示,需要再字符串首部增加一个从左往右的嵌入字符(LRE):U+202A,在字符尾部增加定向格式字符(PDF):U+202C

1
2
3
// Wrap the plus (+) prefix and phone number in left-to-right directional markers
NSString *phoneNumber = @"408-555-1212";
NSString *localizedPhoneNumber = [NSString stringWithFormat:@"\u202A%@\u202C", phoneNumber];

翻转Cocoa Touch视图

有些视图是不应该翻转的,在iOS9之后可以通过UIView的semanticContentAttribute属性手动指定视图应该是从左到后还是从右往左的方式展示。

如果是想翻转图片可以通过UIImageimageFlippedForRightToLeftLayoutDirection方法。

本地化测试

通过IB预览测试本地化

这个功能只能在.storyboard和.xib文件实现。

选中preview之后我们可以通过其右下角的语言选项切换不同语言,然后我们可以实时观察调换语言之后的效果。

通过伪语言功能测试

通过Edit Schema > Run > Options 然后点开语言选项,除了各种系统支持语言外,翻到最下面可以看到这几个选项。

1.Double-Length Pseudolanguage
可以将文本内容变成两倍长度。

2.Right-to-Left Pseudolanguage
将语言方向改成从右往左,也可以将语言改成阿拉伯文或者希伯来文。

3.Accented Pseudolanguage
带重音符号。

4.Bounded String Pseudolanguage
带边界的字符串。

5.Right-to-Left Pseudolanguage With Right-to-Left Strings
同从右往左语言。

引用

苹果文档:Internationzlization and Localization Guide

Donate comment here