i18n.adoc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. // tag::main[]
  2. == 国际化开发规范
  3. 国际化不仅包含多语言翻译,还包含各地的使用习惯和习俗等。软件语言的切换选项在代码实现中的唯一标识符参照 link:/ref/language.html[语言标识列表]。
  4. 为了保证用户体验和便于维护,特作出以下要求。
  5. === 文件存放
  6. 翻译文件,必须放置在对应语种缩写的文件夹下,如中文翻译必须放置于 `zh_CH` 文件夹下
  7. 翻译文件格式为:.properties
  8. === 字符编码
  9. 因为语言不同,当存在多种编码的时候,应用展现层由于编码解析问题,容易造成乱码问题或者字符截断问题,故需要统一使用一种国际化的字符编码。
  10. * 所有文本统一采用 `UTF-8` 编码后进行网络传输。
  11. * 特殊字符必须进行转译后存入数据库(如 `\`)。
  12. * 对于会导出给用户编辑,而后再导入的文本文件,如 `csv` 等,统一使用 UTF-8 编码。
  13. === 可视化界
  14. * 软件安装或完成时,存在语言切换的选项(允许重启后生效)。
  15. * 在未获取到多语言时(如:浏览器界面在首次加载时),预加载页面、浏览器标题等信息尽量避免使用文字,可显示为能表示在加载中的动图;必须使用文字的,显示 `Loading` 。
  16. * 某些国家的语言(如德语、俄语等)翻译后的字符特别长,平均长度是英语的 1.5 到 2 倍,界面在显示文案时会因为超长字符出现破坏页面布局、样式或超长字符无法显示全的情况。
  17. * 客户端界面所有文案在非中文环境下布局合理、样式正常,对于文案较长的情况,需要对文案进行缩略处理,同时增加 `Title` 提示,确保用户通过 `Title` 可以查看到完整文案。
  18. * 软件语言在切换后,重启浏览器或重启服务器的操作后仍要保留上次选择的语言。
  19. * 尽量不直接使用带有文字的图片(图片来源是用户上传的除外),文字须是在无文字的图片上进行二次叠加的,保证图片中文字可翻译。
  20. === 可视化文案信息处理
  21. * 软件界面展示的文本字段需要以唯一的字符Key来标识文本字段。
  22. * 字符 Key 与其对应的语言只允许增加,不允许修改与删除。
  23. * 在软件通信传输过程中,不允许直接的文本传输(用户输入文本并写入数据库的除外),应将文本转换成翻译标识 `Key` 后再进行传输,由表示层进行翻译展示。
  24. ===== Key 规则如下:
  25. - Key中只能包含ASCII字符,包含数字、英文字母、“_”、“-”、“.”,不能包含中文等非ASCII字符
  26. include::ref/language.adoc[tags=i18nKeySpec]
  27. === 时间、时区国际化
  28. 时间格式可配置,如 `周起止时间` 、 `显示样式`
  29. 软件进程对进程外部任何软件(内部系统其它服务/对外/对前端)的接口,涉及到时间传递的(包括请求参数和返回值),
  30. 都要以 `ISO 8601` 标准时间格式( `yyyy-MM-dd’T’HH:mm:ss.sss [+-]hh:mm` ,
  31. 零时区时为 `yyyy-MM-dd’T’HH:mm:ss.sssZ` )进行传输。
  32. * 默认时间格式选用 `ISO 8601` 标准时间格式。
  33. ** 带时区:`yyyy-MMdd<T>HH:mm:ss.sss[+|-]hh:mm`
  34. ** 不带时区: `yyyy-MM-dd<T>HH:mm:ss.sssZ`
  35. * 传输、存储时采用默认格式,终端展示时允许修改格式。
  36. [[标准时间转为本地时间]]
  37. [source,java]
  38. .app.rb
  39. ----
  40. SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
  41. Date date = format.parse("2020-08-08T00:00:00+08:30");
  42. ----
  43. === 输入校验国际化
  44. * Email 除 `@` 不对特殊字符限制
  45. * 手机号、证件号等校验规则可配置(长度、字符)
  46. * 假日跟随国家地区变化或允许自定义
  47. === 国际化访问
  48. 公布的资源需要全球可访问
  49. * 手机号带国际区号,如中国地区 `+86`
  50. * 附加的网站在全球范围内可访问
  51. === 包含英文版
  52. ** 简介、更新日志、用户手册、安装界面
  53. ** 默认支持中、英文语言,允许以语言包形式扩展语言(多语言安装包要带版本号)。
  54. === 不使用本地化信息
  55. * 身份证号、民族、籍贯、省份、城市、所在区县、小区、楼幢、单元
  56. * 三方应用等需要使用国际版本名称(如:微信应显示为 `WeChat` ),不使用没有国际化版本的第三方应用。
  57. * 特殊地区敏感信息(不区分大小写):`Blacklist、Whitelist、Master Slave、Master Slave Tracking` 等。
  58. === 针对使用习惯兼容性测试
  59. * PC 浏览器
  60. ** Windows:`Chrome`、`FireFox`、`IE/Edge`;
  61. ** MAC OS: `Safari`、`Chrome`。
  62. * 移动端
  63. ** `Android`
  64. ** `IOS`
  65. == 编码和使用 footnote:[Spring 源码分析 MessageSource, https://blog.csdn.net/sid1109217623/article/details/84065725]
  66. * 合理利用请求上下文、线程上下文保存语言标识,避免在业务方法入参中包含业务无关的参数。
  67. [[Java 中创建语言标识方式]]
  68. [source,java]
  69. .app.rb
  70. ----
  71. // 带有语言和国家/地区信息的本地化对象
  72. Locale locale1 = new Locale("zh","CN");
  73. // 只有语言信息的本地化对象
  74. Locale locale2 = new Locale("zh");
  75. // 等同于Locale("zh","CN")
  76. Locale locale3 = Locale.CHINA;
  77. // 等同于Locale("zh")
  78. Locale locale4 = Locale.CHINESE;
  79. // 获取本地系统默认的本地化对象
  80. Locale locale5 = Locale.getDefault();
  81. ----
  82. JDK 中提供了几个支持本地化的格式化操作工具类:
  83. * NumberFormat
  84. ** NumberFormat.getCurrencyInstance(Locale.CHINA).format(xxx); (`¥123,456.78`)
  85. * DateFormat
  86. * MessageFormat
  87. [[MessageFormat 的使用]]
  88. [source,java]
  89. .app.rb
  90. ----
  91. // 信息格式化串
  92. String pattern1 = "{0},你好!你于{1}在工商银行存入{2} 元。";
  93. String pattern2 = "At {1,time,short} On{1,date,long},{0} paid {2,number, currency}.";
  94. // 用于动态替换占位符的参数
  95. Object[] params = {"John", new GregorianCalendar().getTime(),1.0E3};
  96. // 使用默认本地化对象格式化信息
  97. String msg1 = MessageFormat.format(pattern1,params);
  98. // 使用指定的本地化对象格式化信息
  99. MessageFormat mf = new MessageFormat(pattern2,Locale.US);
  100. String msg2 = mf.format(params);
  101. System.out.println(msg1);
  102. System.out.println(msg2);
  103. ----
  104. 输出如下
  105. ....
  106. John,你好!你于07-1-8 下午9:58在工商银行存入1,000元。
  107. At 9:58 PM OnJanuary 8, 2007,John paid $1,000.00.
  108. ResourceBoundle
  109. ....
  110. 如果应用系统中某些信息需要支持国际化功能,则必须为希望支持的不同本地化类型分别提供对应的资源文件,并以规范的方式进行命名。
  111. 国际化资源文件的命名规范规定资源名称采用以下的方式进行命名:
  112. `<资源名><语言代码><国家/地区代码>.properties`
  113. * 语言代码和国家/地区代码都是可选的。
  114. * `<资源名>.properties` 命名的国际化资源文件是默认的资源文件(某个本地化类型在系统中找不到对应的资源文件,就采用这个默认的资源文件)。
  115. * `<资源名>_<语言代码>.properties` 命名的国际化资源文件是某一语言默认的资源文件,即某个本地化类型在系统中找不到精确匹配的资源文件,将采用相应语言默认的资源文件。
  116. * 不同语言的同一资源文件,`value` (属性值)不同,但 `key` (属性名)相同,这样应用程序就可以通过 `Locale` 和特定的 `key` 来找到对应语言翻译后的 `value`。
  117. * 文件内容 *只能* 包含 `ASCII` 字符。
  118. 由于直接采用Unicode代码编辑资源文件是很不方便的,所以,通常我们直接使用正常的方式编写资源文件,在测试或部署时再采用工具进行转换。
  119. JDK 在 `bin` 目录下为我们提供了一个完成此项功能的 `native2ascii` 工具,它可以将中文等资源文件转换为 `Unicode` 代码格式的文件,命令格式如下:
  120. `native2ascii [-reverse] [-encoding 编码] [输入文件 [输出文件]]`
  121. * 把 `UTF-8` 格式的多语言资源文件 `resource_zh_CN.properties` 转为 ASCII 资源文件
  122. ** `native2ascii -encoding utf-8 d:\resource_zh_CN.properties` 即可得到 `d:\resource_zh_CN_1.properties`
  123. 该命令还是不方便,主流 `IDE` 中一般自带转换。
  124. === 常用多语言工具类介绍
  125. ==== JDK 的 ResourceBundle
  126. 如果应用程序中拥有大量的本地化资源文件,直接通过传统的 `File` 操作资源文件显然太过笨拙。Java为我们提供了用于加载本地化资源文件的方便类 `java.util.ResourceBoundle`。
  127. * 从相对于类路径的目录中加载一个名为 `resource` 的本地化资源文件,并获取对应的属性值
  128. ** `ResourceBundle rb = ResourceBundle.getBundle("org/shoulder/i18n/resource", locale)`
  129. ** `rb.getString("greeting.common")`
  130. * 只允许指定类路径下的资源文件
  131. * 如果指定的本地化资源文件不存在,它按以下顺序尝试加载其他的资源
  132. .. 系统默认本地化对象对应的资源
  133. .. 不带语言/地区标识的资源文件
  134. ==== JDK 的 MessageFormat
  135. 占位符
  136. `greeting.common=How are you!{0},today is {1}`
  137. ==== Spring 中的 MessageSource
  138. 三个用法(详见其注释)
  139. * 解析code对应的信息进行返回,如果对应的code不能被解析则返回默认信息defaultMessage。
  140. * 解析code对应的信息进行返回,如果对应的code不能被解析则抛出异常NoSuchMessageException
  141. * 通过传入的 `MessageSourceResolvable` 对应来解析对应的信息,对于传入的多个 code,只要有一个可以翻译,立即返回
  142. 它被 `HierarchicalMessageSource` 和 `ApplicationContext` 接口扩展,AbstractApplicationContext 中实现了
  143. `AbstractApplicationContext#initMessageSource()` 做 `MessageSource` 的初始化工作
  144. Spring 中提供的三个实现类
  145. * StaticMessageSource
  146. ** 主要用于程序测试,它允许通过编程的方式提供国际化信息。
  147. * ResourceBundleMessageSource
  148. ** 基于JDK ResourceBundle,会将访问过的ResourceBundle缓存起来,以便于下次直接从缓存中获取进行使用
  149. ** cacheSeconds
  150. * ReloadableResourceBundleMessageSource
  151. ** 基于 `PropertiesPersister` 来加载对应的文件,使用java.util.Properties来保存对应的数据(即可包括 `properties` `xml` 文件)。
  152. ** 允许指定非类路径下的文件作为对应的资源文件,可以使用 Spring支持的资源文件的前缀,如 `classpath:`、`file:`、`http:`、`ftp:` 等 footnote[Spring 支持的资源文件加载方式, https://blog.csdn.net/hulei19900322/article/details/75200356]
  153. ** `cacheSeconds` 提供了定时刷新功能,允许在不重启系统的情况下,更新资源的信息。
  154. * SpringSecurityMessageSource
  155. ** The default MessageSource used by Spring Security.
  156. image::i18n/messageSource.png[messageSource]
  157. 未能成功翻译响应策略:
  158. * DoNothing
  159. * ReturnCode
  160. * TryLocale
  161. ==== 其他国际化注意点
  162. ===== 前端
  163. 界面,错误码,动态表单,请求服务器 + 缓存的方式
  164. ===== 后端
  165. 后端的国际化需要注意的点有两部分:api 接口的 msg 字段(全局自定义异常拦截)、系统数据层面翻译。
  166. JDK 提供的相关类:
  167. * Locale 地区、语言
  168. * TimeZone 时区
  169. * NumberFormat 数字格式化
  170. * Currency 货币格式化
  171. * DateTimeFormatter 时间格式化
  172. * Collator 字符比较,排序
  173. * CollationKey 字符比较,排序
  174. * Normalizer 返回str的范化形式
  175. * MessageFormat
  176. * Format
  177. * ResourceBundle
  178. // end::main[]