123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- // tag::main[]
- == 国际化开发规范
- 国际化不仅包含多语言翻译,还包含各地的使用习惯和习俗等。软件语言的切换选项在代码实现中的唯一标识符参照 link:/ref/language.html[语言标识列表]。
- 为了保证用户体验和便于维护,特作出以下要求。
- === 文件存放
- 翻译文件,必须放置在对应语种缩写的文件夹下,如中文翻译必须放置于 `zh_CH` 文件夹下
- 翻译文件格式为:.properties
- === 字符编码
- 因为语言不同,当存在多种编码的时候,应用展现层由于编码解析问题,容易造成乱码问题或者字符截断问题,故需要统一使用一种国际化的字符编码。
- * 所有文本统一采用 `UTF-8` 编码后进行网络传输。
- * 特殊字符必须进行转译后存入数据库(如 `\`)。
- * 对于会导出给用户编辑,而后再导入的文本文件,如 `csv` 等,统一使用 UTF-8 编码。
- === 可视化界
- * 软件安装或完成时,存在语言切换的选项(允许重启后生效)。
- * 在未获取到多语言时(如:浏览器界面在首次加载时),预加载页面、浏览器标题等信息尽量避免使用文字,可显示为能表示在加载中的动图;必须使用文字的,显示 `Loading` 。
- * 某些国家的语言(如德语、俄语等)翻译后的字符特别长,平均长度是英语的 1.5 到 2 倍,界面在显示文案时会因为超长字符出现破坏页面布局、样式或超长字符无法显示全的情况。
- * 客户端界面所有文案在非中文环境下布局合理、样式正常,对于文案较长的情况,需要对文案进行缩略处理,同时增加 `Title` 提示,确保用户通过 `Title` 可以查看到完整文案。
- * 软件语言在切换后,重启浏览器或重启服务器的操作后仍要保留上次选择的语言。
- * 尽量不直接使用带有文字的图片(图片来源是用户上传的除外),文字须是在无文字的图片上进行二次叠加的,保证图片中文字可翻译。
- === 可视化文案信息处理
- * 软件界面展示的文本字段需要以唯一的字符Key来标识文本字段。
- * 字符 Key 与其对应的语言只允许增加,不允许修改与删除。
- * 在软件通信传输过程中,不允许直接的文本传输(用户输入文本并写入数据库的除外),应将文本转换成翻译标识 `Key` 后再进行传输,由表示层进行翻译展示。
- ===== Key 规则如下:
- - Key中只能包含ASCII字符,包含数字、英文字母、“_”、“-”、“.”,不能包含中文等非ASCII字符
- include::ref/language.adoc[tags=i18nKeySpec]
- === 时间、时区国际化
- 时间格式可配置,如 `周起止时间` 、 `显示样式`
- 软件进程对进程外部任何软件(内部系统其它服务/对外/对前端)的接口,涉及到时间传递的(包括请求参数和返回值),
- 都要以 `ISO 8601` 标准时间格式( `yyyy-MM-dd’T’HH:mm:ss.sss [+-]hh:mm` ,
- 零时区时为 `yyyy-MM-dd’T’HH:mm:ss.sssZ` )进行传输。
- * 默认时间格式选用 `ISO 8601` 标准时间格式。
- ** 带时区:`yyyy-MMdd<T>HH:mm:ss.sss[+|-]hh:mm`
- ** 不带时区: `yyyy-MM-dd<T>HH:mm:ss.sssZ`
- * 传输、存储时采用默认格式,终端展示时允许修改格式。
- [[标准时间转为本地时间]]
- [source,java]
- .app.rb
- ----
- SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
- Date date = format.parse("2020-08-08T00:00:00+08:30");
- ----
- === 输入校验国际化
- * Email 除 `@` 不对特殊字符限制
- * 手机号、证件号等校验规则可配置(长度、字符)
- * 假日跟随国家地区变化或允许自定义
- === 国际化访问
- 公布的资源需要全球可访问
- * 手机号带国际区号,如中国地区 `+86`
- * 附加的网站在全球范围内可访问
- === 包含英文版
- ** 简介、更新日志、用户手册、安装界面
- ** 默认支持中、英文语言,允许以语言包形式扩展语言(多语言安装包要带版本号)。
- === 不使用本地化信息
- * 身份证号、民族、籍贯、省份、城市、所在区县、小区、楼幢、单元
- * 三方应用等需要使用国际版本名称(如:微信应显示为 `WeChat` ),不使用没有国际化版本的第三方应用。
- * 特殊地区敏感信息(不区分大小写):`Blacklist、Whitelist、Master Slave、Master Slave Tracking` 等。
- === 针对使用习惯兼容性测试
- * PC 浏览器
- ** Windows:`Chrome`、`FireFox`、`IE/Edge`;
- ** MAC OS: `Safari`、`Chrome`。
- * 移动端
- ** `Android`
- ** `IOS`
- == 编码和使用 footnote:[Spring 源码分析 MessageSource, https://blog.csdn.net/sid1109217623/article/details/84065725]
- * 合理利用请求上下文、线程上下文保存语言标识,避免在业务方法入参中包含业务无关的参数。
- [[Java 中创建语言标识方式]]
- [source,java]
- .app.rb
- ----
- // 带有语言和国家/地区信息的本地化对象
- Locale locale1 = new Locale("zh","CN");
- // 只有语言信息的本地化对象
- Locale locale2 = new Locale("zh");
- // 等同于Locale("zh","CN")
- Locale locale3 = Locale.CHINA;
- // 等同于Locale("zh")
- Locale locale4 = Locale.CHINESE;
- // 获取本地系统默认的本地化对象
- Locale locale5 = Locale.getDefault();
- ----
- JDK 中提供了几个支持本地化的格式化操作工具类:
- * NumberFormat
- ** NumberFormat.getCurrencyInstance(Locale.CHINA).format(xxx); (`¥123,456.78`)
- * DateFormat
- * MessageFormat
- [[MessageFormat 的使用]]
- [source,java]
- .app.rb
- ----
- // 信息格式化串
- String pattern1 = "{0},你好!你于{1}在工商银行存入{2} 元。";
- String pattern2 = "At {1,time,short} On{1,date,long},{0} paid {2,number, currency}.";
- // 用于动态替换占位符的参数
- Object[] params = {"John", new GregorianCalendar().getTime(),1.0E3};
- // 使用默认本地化对象格式化信息
- String msg1 = MessageFormat.format(pattern1,params);
- // 使用指定的本地化对象格式化信息
- MessageFormat mf = new MessageFormat(pattern2,Locale.US);
- String msg2 = mf.format(params);
- System.out.println(msg1);
- System.out.println(msg2);
- ----
- 输出如下
- ....
- John,你好!你于07-1-8 下午9:58在工商银行存入1,000元。
- At 9:58 PM OnJanuary 8, 2007,John paid $1,000.00.
- ResourceBoundle
- ....
- 如果应用系统中某些信息需要支持国际化功能,则必须为希望支持的不同本地化类型分别提供对应的资源文件,并以规范的方式进行命名。
- 国际化资源文件的命名规范规定资源名称采用以下的方式进行命名:
- `<资源名><语言代码><国家/地区代码>.properties`
- * 语言代码和国家/地区代码都是可选的。
- * `<资源名>.properties` 命名的国际化资源文件是默认的资源文件(某个本地化类型在系统中找不到对应的资源文件,就采用这个默认的资源文件)。
- * `<资源名>_<语言代码>.properties` 命名的国际化资源文件是某一语言默认的资源文件,即某个本地化类型在系统中找不到精确匹配的资源文件,将采用相应语言默认的资源文件。
- * 不同语言的同一资源文件,`value` (属性值)不同,但 `key` (属性名)相同,这样应用程序就可以通过 `Locale` 和特定的 `key` 来找到对应语言翻译后的 `value`。
- * 文件内容 *只能* 包含 `ASCII` 字符。
- 由于直接采用Unicode代码编辑资源文件是很不方便的,所以,通常我们直接使用正常的方式编写资源文件,在测试或部署时再采用工具进行转换。
- JDK 在 `bin` 目录下为我们提供了一个完成此项功能的 `native2ascii` 工具,它可以将中文等资源文件转换为 `Unicode` 代码格式的文件,命令格式如下:
- `native2ascii [-reverse] [-encoding 编码] [输入文件 [输出文件]]`
- * 把 `UTF-8` 格式的多语言资源文件 `resource_zh_CN.properties` 转为 ASCII 资源文件
- ** `native2ascii -encoding utf-8 d:\resource_zh_CN.properties` 即可得到 `d:\resource_zh_CN_1.properties`
- 该命令还是不方便,主流 `IDE` 中一般自带转换。
- === 常用多语言工具类介绍
- ==== JDK 的 ResourceBundle
- 如果应用程序中拥有大量的本地化资源文件,直接通过传统的 `File` 操作资源文件显然太过笨拙。Java为我们提供了用于加载本地化资源文件的方便类 `java.util.ResourceBoundle`。
- * 从相对于类路径的目录中加载一个名为 `resource` 的本地化资源文件,并获取对应的属性值
- ** `ResourceBundle rb = ResourceBundle.getBundle("org/shoulder/i18n/resource", locale)`
- ** `rb.getString("greeting.common")`
- * 只允许指定类路径下的资源文件
- * 如果指定的本地化资源文件不存在,它按以下顺序尝试加载其他的资源
- .. 系统默认本地化对象对应的资源
- .. 不带语言/地区标识的资源文件
- ==== JDK 的 MessageFormat
- 占位符
- `greeting.common=How are you!{0},today is {1}`
- ==== Spring 中的 MessageSource
- 三个用法(详见其注释)
- * 解析code对应的信息进行返回,如果对应的code不能被解析则返回默认信息defaultMessage。
- * 解析code对应的信息进行返回,如果对应的code不能被解析则抛出异常NoSuchMessageException
- * 通过传入的 `MessageSourceResolvable` 对应来解析对应的信息,对于传入的多个 code,只要有一个可以翻译,立即返回
- 它被 `HierarchicalMessageSource` 和 `ApplicationContext` 接口扩展,AbstractApplicationContext 中实现了
- `AbstractApplicationContext#initMessageSource()` 做 `MessageSource` 的初始化工作
- Spring 中提供的三个实现类
- * StaticMessageSource
- ** 主要用于程序测试,它允许通过编程的方式提供国际化信息。
- * ResourceBundleMessageSource
- ** 基于JDK ResourceBundle,会将访问过的ResourceBundle缓存起来,以便于下次直接从缓存中获取进行使用
- ** cacheSeconds
- * ReloadableResourceBundleMessageSource
- ** 基于 `PropertiesPersister` 来加载对应的文件,使用java.util.Properties来保存对应的数据(即可包括 `properties` `xml` 文件)。
- ** 允许指定非类路径下的文件作为对应的资源文件,可以使用 Spring支持的资源文件的前缀,如 `classpath:`、`file:`、`http:`、`ftp:` 等 footnote[Spring 支持的资源文件加载方式, https://blog.csdn.net/hulei19900322/article/details/75200356]
- ** `cacheSeconds` 提供了定时刷新功能,允许在不重启系统的情况下,更新资源的信息。
- * SpringSecurityMessageSource
- ** The default MessageSource used by Spring Security.
- image::i18n/messageSource.png[messageSource]
- 未能成功翻译响应策略:
- * DoNothing
- * ReturnCode
- * TryLocale
- ==== 其他国际化注意点
- ===== 前端
- 界面,错误码,动态表单,请求服务器 + 缓存的方式
- ===== 后端
- 后端的国际化需要注意的点有两部分:api 接口的 msg 字段(全局自定义异常拦截)、系统数据层面翻译。
- JDK 提供的相关类:
- * Locale 地区、语言
- * TimeZone 时区
- * NumberFormat 数字格式化
- * Currency 货币格式化
- * DateTimeFormatter 时间格式化
- * Collator 字符比较,排序
- * CollationKey 字符比较,排序
- * Normalizer 返回str的范化形式
- * MessageFormat
- * Format
- * ResourceBundle
- // end::main[]
|