本文转载至 http://adad184.com/2015/08/18/practice-in-i18n-dialling-code/

前言

上周在忙产品的国际化(i18n)的问题 其中一个很重要的地方就是电话号码的国际化(我们以电话号码为主账号) 电话号码有个很重要的部分就是区号

上图是我们产品的区号选择 除了常规的电话号码之外 后面还有一个区号 代表这个电话号码所属的是哪个国家和地区 关于区号的概念 可以看一下维基百科

看到这里 可能有人奇怪 这有什么难的? 不就是按照列表来展示吗? 这样有几个问题

  • 由于是支持多语言 那么不同的语言环境的系统 显示出来的国家名称是不一样的 比如“中国” 简体中文是“中国” 英文是“China” 韩文是“중화인민공화국” 其在各个语言中的显示排序都是不一样的
  • 如果根据不同国家和语言来维护一张这样的表 工作量太大 一般的公司估计做不来

所以这个工作我们就会放到本地来做 不过iOS已经帮我们做了一部分工作了 我们可以根据国家代码来获取某个国家或在当前区域中的本地化名称

1
2
3
4
5
6
7
8
9
10
11
//获取当前locale
NSLocale *locale = [NSLocale currentLocale]; //获取所有国家的代码
NSArray *countryArray = [NSLocale ISOCountryCodes]; for (NSString *countryCode in countryArray)
{
//根据当前locale和国家短码 获取指定国家的本地化名称
NSString *localName = [locale displayNameForKey:NSLocaleCountryCode value:countryCode];
}

我们简单测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
NSArray *countryArray  = [NSLocale ISOCountryCodes];
NSArray *languageArray = @[@"zh_CN",@"en_US",@"ja_JP"]; for ( NSString *languege in languageArray)
{
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:languege]; for ( int i = 0 ; i < 5 ; ++i )
{
NSString *countryCode = countryArray[i]; NSString *displayName = [locale displayNameForKey:NSLocaleCountryCode value:countryCode]; NSLog(@"%@\t%@\t%@",languege,countryCode,displayName);
}
}

结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
zh_CN	AD	安道尔
zh_CN AE 阿拉伯联合酋长国
zh_CN AF 阿富汗
zh_CN AG 安提瓜和巴布达
zh_CN AI 安圭拉 en_US AD Andorra
en_US AE United Arab Emirates
en_US AF Afghanistan
en_US AG Antigua and Barbuda
en_US AI Anguilla ja_JP AD アンドラ
ja_JP AE アラブ首長国連邦
ja_JP AF アフガニスタン
ja_JP AG アンティグア・バーブーダ
ja_JP AI アンギラ

已经介绍完iOS帮我们做的一部分工作了 那么另一部分就得我们自己来了
我们需要有一张 地区->区号 的列表 不过这个也简单 网上一抓一大把 我也是网上找的 文件内容如下(diallingcode.json)

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
[
{
"name": "Afghanistan",
"dial_code": "+93",
"code": "AF"
},
{
"name": "Albania",
"dial_code": "+355",
"code": "AL"
}, ...
...
//中间省略
...
... {
"name": "Virgin Islands, British",
"dial_code": "+1 284",
"code": "VG"
},
{
"name": "Virgin Islands, U.S.",
"dial_code": "+1 340",
"code": "VI"
}
]

维护这样一张表就很简单了我们可以存在本地 也可以放在服务器(“name”字段其实是不必须的 只是为了好看)

研究

我们暂时先把代码放一放 来看一看其他产品是怎么做的

这个是微信的

微信的问题还是挺多的

  • 左边是中文环境 按拼音分组是分对了 但是文字排序却出错了 “阿”开头的国家并没有排列在一起
  • 右边是法语环境 这些衍生拉丁字母 并没有正确的归类

这个是Twitter的

Twitter在中文环境下还是挺奇怪的 但是却没有犯微信第二个错误

Facebook的呢? 人家的工程师比较聪明(懒) 压根就不支持索引

接下来我们会解决出现的这几个问题

代码

先简历一个Modal 用来表示国家相关的信息

1
2
3
4
5
6
7
8
@interface MMCountry : NSObject

@property (nonatomic, strong) NSString *name;   //国家名(本地化后的版本)
@property (nonatomic, strong) NSString *code; //国家代号
@property (nonatomic, strong) NSString *latin; //国家名的拉丁文(只包含基本拉丁字母)
@property (nonatomic, strong) NSString *dial_code; //区号 @end

然后我们要把区号从配置文件中读取出来 并以区号为key 建立索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"diallingcode" ofType:@"json"]];
NSError *error = nil; NSArray *arrayCode = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; if ( error ) { return;
} //读取文件
NSMutableDictionary *dicCode = [@{} mutableCopy]; for ( NSDictionary *item in arrayCode )
{
MMCountry *c = [MMCountry new]; c.code = item[@"code"];
c.dial_code = item[@"dial_code"]; [dicCode setObject:c forKey:c.code];
}

接着获取这些国家的本地话名称

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
NSLocale *locale = [NSLocale currentLocale];
NSArray *countryArray = [NSLocale ISOCountryCodes]; NSMutableDictionary *dicCountry = [@{} mutableCopy]; for (NSString *countryCode in countryArray) { if ( dicCode[countryCode] )
{
MMCountry *c = dicCode[countryCode]; //这里 你懂的
c.name = [locale displayNameForKey:NSLocaleCountryCode value:countryCode];
if ( [c.name isEqualToString:@"台湾"] )
{
c.name = @"中国台湾";
} //把名称拉丁字母化
c.latin = [self latinize:c.name]; [dicCountry setObject:c forKey:c.code];
}
else
{
//找不到则说明配置文件不全 可以补全
NSLog(@"missed %@ %@",[locale displayNameForKey:NSLocaleCountryCode value:countryCode],countryCode);
}
}

这里要注意的是 把字母拉丁文化 解决了微信的第二个问题 使非基本拉丁字母也可以按照基本拉丁字母来排序 其函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
- (NSString*)latinize:(NSString*)str
{
NSMutableString *source = [str mutableCopy]; CFStringTransform((__bridge CFMutableStringRef)source, NULL, kCFStringTransformToLatin, NO); //微信是这样做的
//CFStringTransform((__bridge CFMutableStringRef)source, NULL, kCFStringTransformMandarinLatin, NO); CFStringTransform((__bridge CFMutableStringRef)source, NULL, kCFStringTransformStripDiacritics, NO); return source;
}

这里有两步

  1. 先将文字 转成拉丁字母(kCFStringTransformToLatin)
  2. 再将拉丁字母去掉变音符(kCFStringTransformStripDiacritics)

这里是微信犯的第一个错误 也就是没有正确归类的错误 因为微信在第一步的时候只针对汉字进行了处理 其他字符则没有处理 导致第二步没有得到正确的基本拉丁字符(kCFStringTransformMandarinLatin 参见注释掉的代码)

我们来测试一下这两步会造成得到效果 还是之前的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
NSArray *countryArray  = [NSLocale ISOCountryCodes];
NSArray *languageArray = @[@"zh_CN",@"en_US",@"ja_JP"]; for ( NSString *languege in languageArray)
{
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:languege]; for ( int i = 0 ; i < 5 ; ++i )
{
NSString *countryCode = countryArray[i]; NSString *displayName = [locale displayNameForKey:NSLocaleCountryCode value:countryCode]; NSLog(@"%@\t%@\t%@\t@",languege,countryCode,displayName,[self latinize:displayName]);
}
}

结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
zh_CN	AD	安道尔	|	an dao er
zh_CN AE 阿拉伯联合酋长国 | a la bo lian he qiu zhang guo
zh_CN AF 阿富汗 | a fu han
zh_CN AG 安提瓜和巴布达 | an ti gua he ba bu da
zh_CN AI 安圭拉 | an gui la
en_US AD Andorra | Andorra
en_US AE United Arab Emirates | United Arab Emirates
en_US AF Afghanistan | Afghanistan
en_US AG Antigua & Barbuda | Antigua & Barbuda
en_US AI Anguilla | Anguilla
ja_JP AD アンドラ | andora
ja_JP AE アラブ首長国連邦 | arabu shou zhang guo lian ban
ja_JP AF アフガニスタン | afuganisutan
ja_JP AG アンティグア・バーブーダ | antigua・babuda
ja_JP AI アンギラ | angira

可以到看 系统会根据不同国家和不同语言的特点 将同一个国家的不同表达形式转化成不同的拉丁字母

接下来 我们把获取过的数据根据’A’-‘Z’进行归类

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
NSMutableDictionary *dicSort = [@{} mutableCopy];

for ( MMCountry *c in dicCountry.allValues )
{
NSString *indexKey = @""; if ( c.latin.length > 0 )
{
indexKey = [[c.latin substringToIndex:1] uppercaseString]; char c = [indexKey characterAtIndex:0]; if ( ( c < 'A') || ( c > 'Z' ) )
{
continue;
}
}
else
{
continue;
} NSMutableArray *array = dicSort[indexKey]; if ( !array )
{
array = [NSMutableArray array]; dicSort[indexKey] = array;
} [array addObject:c];
}

最后 将每个归类下面的数据 排序重新整理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for ( NSString *key in dicSort.allKeys )
{
NSArray *array = dicSort[key]; array = [array sortedArrayUsingComparator:^NSComparisonResult(MMCountry *obj1, MMCountry *obj2) { return [obj1.name localizedStandardCompare:obj2.name];
}]; // array = [array sortedArrayUsingComparator:^NSComparisonResult(CSCountry *obj1, CSCountry *obj2) {
//
// return obj1.latin > obj2.latin;
// }]; dicSort[key] = array;
}

这样dicSort就是我们最终得到的结果集

这里是微信犯的第二个错误 微信的排序是按照latin来排序的(见注释掉的代码) 所以导致了相同汉字的国家排不到一起的情况 正确的方式是用localizedStandardCompare来排序 这也是iOS已为我们提供好了的本地化比较函数
看看之前的图中 挑三个国家出来 比如:阿尔巴尼亚 爱尔兰 阿鲁巴 他们的拼音是 aerbabiya aierlan aluba 如果按照拼音排序的话 这样的排序就是正确的

我们来看看最终的效果

是不是比微信的更好?

讨论

虽然代码是写完了 但是问题并没有结果 有一个关键的问题就是 为什么我们要按照’A’-‘Z’来索引排序呢? 比如Twitter在日文和韩文环境下是这样的

其实按照不同国家的语言特点来进行对应的索引 应该才是最优的解决办法(PS:看到Twitter在中文环境下的糟糕结果 我也不确定其在日文和韩文下的结果是否是正确的(¯﹃¯)
当然 如果真要这样做 其实改动量也不大 只要在索引的那块稍微修改一下就行了

小结


文中的demo可以在这里找到

正如讨论中说的一样 本文所讨论的方案 并不是最终的解决方案 如果需要更好的体验的话 还要深入研究各国的文化才行 所以 国际化并不单纯是个技术问题 更是个社会工程啊~~~~

处理i18n国际电话区号的代码实践的更多相关文章

  1. 支持中英文和国旗的android国家代码/国际电话区号选择器

    最近在做app登录的时候,因为需要支持国外手机号注册和登录,所以就涉及到国际电话区号的选择.在github上面找了一下,国家名称基本都是只有英文版本,而手动的去把中文一个个加上实在是一件费时费力的事情 ...

  2. 国际电话区号SQL

    CREATE TABLE `phone_prefix` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `country` varchar(30) N ...

  3. 国际电话号码的区号mysql数据表

    -- phpMyAdmin SQL Dump-- version 3.5.2-- http://www.phpmyadmin.net---- Host: localhost-- Generation ...

  4. 分享,iOS国家手机区号代码.plist

    APP注册需要手机号码的时候,如果有在其他国家的时候需要填写手机区号 一份有国家名字和区号的plist 参照微信注册的时候 格式是 <Array> <Array> <Ar ...

  5. 生活常用类API调用的代码示例合集:邮编查询、今日热门新闻查询、区号查询等

    以下示例代码适用于 www.apishop.net 网站下的API,使用本文提及的接口调用代码示例前,您需要先申请相应的API服务. 邮编查询:通过邮编查询地名:通过地名查询邮编 今日热门新闻查询:提 ...

  6. Android 国际区号注册手机号编码 以及常用城市列表

    附上 国际区号编码:我是定义到arrays.xml里面了 <?xml version="1.0" encoding="utf-8"?> <re ...

  7. [原创]JAVA号码工具类:实现手机固话号码判断与区号截取

    工具类说明 该工具类主要是用于判断号码的类型,如果是手机类型,则返回号码前7位,便于后面继续判断号码归属地:如果是固话类型,则截取固话的区号,同样也是为了后面判断号码的归属地. 在获取到这些信息之后, ...

  8. HtmlAgilityPack解析全国区号页面到XML

    需求:完成一个城市和区号的xml配置文件 处理思路:通过HtmlAgilityPack解析一个区号页面,生产xml文件 页面:http://www.hljboli.gov.cn/html/code.h ...

  9. java利用爬虫技术抓取(省、市(区号\邮编)、县)数据

    近期项目须要用到 城市的地址信息,但从网上下载的xml数据没有几个是最新的地址信息.....数据太老,导致有些地区不全.所以才想到天气预报官网特定有最新最全的数据.贴出代码,希望能给有相同困惑的朋友. ...

随机推荐

  1. Java数据通讯中使用Googgle Protobuf 序列化与反序列化

    概念 1.什么是protocol buffer ProtocolBuffer是用于结构化数据串行化的灵活.高效.自动的方法,有如XML,不过它更小.更快.也更简单.你可以定义自己的数据结构,然后使用代 ...

  2. Window7 Cocos2d-x配置开发环境

    1.到Cocos2d-x官方网下载最新版,解压后在目录cocos2d-x-2.1.5\tools\project-creator\create_project.py 2.安装Python,到网站htt ...

  3. JAVA组成原理及使用方法编辑环境及实现过程

    JAVA组成原理一.由四方面组成:1.Java编程语言2.Java类文件格式3.Java虚拟机4.Java应用程序接口 当编辑并运行一个Java程序时,需要同时涉及到这四种方面.二.使用文字编辑软件: ...

  4. (转)【多媒体封装格式详解】--- AAC ADTS格式分析

     出自:http://blog.csdn.net/tx3344/article/details/7414543 http://www.it6655.com/2012/08/aac-adts-html ...

  5. 弹出输入框后,将listview内容遮住,解决方案

    转自http://blog.csdn.net/silence_cdsn/article/details/7987063 更改listview的布局属性 之前的布局: <ListView andr ...

  6. linux stat 命令查看文件信息

    在Linux中,没有文件创建时间的概念.只有文件的访问时间.修改时间.状态改变时间.也就是说不能知道文件的创建时间.但如果文件创建后就没有修改过,修改时间=创建时间;如果文件创建后,状态就没有改变过, ...

  7. Invalid input for operation: physical_network 'physnet1' unknown for flat provider network.

    在devstack中  按照这个教程给bare metal创建flat network,一切都配置好之后, 执行net-create时遇到错误: Invalid input for operation ...

  8. Linux系统教程:设置GRUB菜单密码

    1.认识启动配置选项 [root@server5 ~]# cat /boot/grub/grub.conf     # grub.conf generated by anaconda # # Note ...

  9. Java多线程——线程范围内共享变量和ThreadLocal

    多个线程访问共享对象和数据的方式 1.如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,买票系统就可以这么做. package java_ ...

  10. IP冲突解决方案

    客人在我所供职的酒店上网的时候,经常会弹出一个对话框,显示一些提示,如上网的注意事项和消费标准等信息;并且有自己的电影和歌曲服务器,DHCP-server也是其中的一台服务器,宾馆.酒店就是用这台机器 ...