处理i18n国际电话区号的代码实践

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

前言

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

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

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

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

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

1234567891011
//获取当前localeNSLocale *locale = [NSLocale currentLocale];

//获取所有国家的代码NSArray *countryArray = [NSLocale ISOCountryCodes];

for (NSString *countryCode in countryArray) {    //根据当前locale和国家短码 获取指定国家的本地化名称    NSString *localName = [locale displayNameForKey:NSLocaleCountryCode value:countryCode];}

我们简单测试一下

12345678910111213141516
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);    }}

结果

1234567891011121314151617
zh_CN	AD	安道尔zh_CN	AE	阿拉伯联合酋长国zh_CN	AF	阿富汗zh_CN	AG	安提瓜和巴布达zh_CN	AI	安圭拉

en_US	AD	Andorraen_US	AE	United Arab Emiratesen_US	AF	Afghanistanen_US	AG	Antigua and Barbudaen_US	AI	Anguilla

ja_JP	AD	アンドラja_JP	AE	アラブ首長国連邦ja_JP	AF	アフガニスタンja_JP	AG	アンティグア?バーブーダja_JP	AI	アンギラ

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

1234567891011121314151617181920212223242526272829
[    {        "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 用来表示国家相关的信息

12345678
@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 建立索引

12345678910111213141516171819202122
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];}

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

1234567891011121314151617181920212223242526272829
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);    }}

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

12345678910111213
- (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 参见注释掉的代码)

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

12345678910111213141516
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%@\[email protected]",languege,countryCode,displayName,[self latinize:displayName]);    }}

结果

123456789101112131415
zh_CN	AD	安道尔	|	an dao erzh_CN	AE	阿拉伯联合酋长国	|	a la bo lian he qiu zhang guozh_CN	AF	阿富汗	|	a fu hanzh_CN	AG	安提瓜和巴布达	|	an ti gua he ba bu dazh_CN	AI	安圭拉	|	an gui laen_US	AD	Andorra	|	Andorraen_US	AE	United Arab Emirates	|	United Arab Emiratesen_US	AF	Afghanistan	|	Afghanistanen_US	AG	Antigua & Barbuda	|	Antigua & Barbudaen_US	AI	Anguilla	|	Anguillaja_JP	AD	アンドラ	|	andoraja_JP	AE	アラブ首長国連邦	|	arabu shou zhang guo lian banja_JP	AF	アフガニスタン	|	afuganisutanja_JP	AG	アンティグア?バーブーダ	|	antigua?babudaja_JP	AI	アンギラ	|	angira

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

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

123456789101112131415161718192021222324252627282930313233
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];}

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

12345678910111213141516
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可以在这里找到

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

时间: 2024-10-19 12:45:17

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

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

最近在做app登录的时候,因为需要支持国外手机号注册和登录,所以就涉及到国际电话区号的选择.在github上面找了一下,国家名称基本都是只有英文版本,而手动的去把中文一个个加上实在是一件费时费力的事情,所以就写了一段简单的java代码,抓取了某快递网站的数据转换成json格式,以下是处理后的数据 [ { "en": "Angola", "zh": "安哥拉", "locale": "AO"

弹出层,验证码,省市地区选择,国际电话区号选择等插件的使用

1.layer的弹出层,有bug,或许是跟当前的JS冲突,所以,使用了zdialog.js 参考链接:http://www.jq22.com/jquery-info18718 引入三个文件,分别是,jquery.js,zdialog.js,zdialog.css 其中,具体的样式,可以在zdialog.css里进行修改.不过,他的css写的其实挺漂亮了. 比如,他们的提示框,从上到下有三部分,分别是:标题,内容,确定. 我直接修改,标题的样式为display:none:就更加简化了这个提示框.

手机应用注册时,获取国际电话区号列表完美解决方案

一.首先下载Google的libphonenumber库,https://github.com/googlei18n/libphonenumber 通过库中的PhoneNumberUtil类的getSupportedRegions()获取所支持的所有国家和地区,再通过getCountryCodeForRegion()依次迭代出每个国家和地区对应的区号, 二.通过国家代码获取国家名称,下载https://github.com/umpirsky/country-list 里面有各种语言.各种格式的数

全国行政区划数据大放送——包含邮政编码-电话区号-简拼-区划路径

因为一些情况需要用到全国行政区划数据库,本身没有库的就自己在网上找,但是找到的基本上不是我想要的,因为只包含了基本信息, 最后自己下了一个原始库准备自己组装数据,网上的XX数据网啊,web services都有限制,还是自己想办法把数据拿到手最放心. 原始数据是2015年4月的,原始数据只有基本的三级(省市县)区划信息,我自己通过一些查询区划网站,把四级数据抓取导入到自己的数据 库中(省市县乡),数据还是比较新的,觉得应该会有园友用到所以将数据放上来. 组装后的格式:区划ID-区划名称-父级ID

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

-- phpMyAdmin SQL Dump-- version 3.5.2-- http://www.phpmyadmin.net---- Host: localhost-- Generation Time: Oct 12, 2012 at 07:27 PM-- Server version: 5.1.61-- PHP Version: 5.3.3 SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";SET time_zone = "+00:00&qu

如何拨打国际电话

一.先来说几个概念: 1.出口码(exit code):也称为国际接入码或国际直拨(IDD)码,可自动将电话转接到国外,这是你打往国外电话的第一个号码.例如 中国的是00,美国的是011,日本的是010.不管在哪个国家,你都可以用加号(+)来代替它. 2.国家代码(country code):每个国家都有自己的代码,要拨打该国的电话,必须要在实际的电话号码之前加上国家代码.例如 中国的是86,美国的是1,日本的是81. 3.长途前缀(trunk prefix):用于在国内长途呼叫的代码,国际电话

小巧抓取(省、市(区号\邮编)、县)数据

最近项目需要用到 城市的地址信息,但从网上下载的xml数据没有几个是最新的地址信息.....数据太老,导致有些地区不全.所以才想到天气预报官网特定有最新最全的数据.贴出代码,希望能给有同样困惑的朋友,减少一些时间. /** * @param var 城市名称 * @return string数组,0表示邮编 1表示区号 */ @SuppressWarnings("deprecation") private String[] getZipCode(String var) { String

学术休假-区号查询

不得不吐槽一下CSDN的审核速度,前天发表的文章到现在还没有审核完.好吧,寒假理解. 下午无聊想查一下某城市的区号,还是找到度娘,后来索性任性了一会,用C++写了一个区号查询. 程序和先前的写作风格差不多,利用多函数,文件查找,结构比较简单. 上源码: /* *Copyright (c) 2015,烟台大学计算机学院 *All gight reserved. *文件名称:temp.cpp *作者:邵帅 *完成时间:2015年1月30日 *版本号:v1.0 */ #include<fstream>

正则表达式:邮箱格式和手机号(3-4位区号,7-8位直播号码,1-4位分机号)

<input type="text" id="uid" onblur="mail()"  />//文本框,实现失去焦点时触发判断邮箱或电话号码格式是否正确,正确不做反应,错误输出("用户名的邮箱或手机号格式不正确") function mail() { var patten2= new RegExp(/^[0-9|A-z|_]{1,17}[@][0-9|A-z]{1,3}.(com)$/) //邮箱正则表达式 va