ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AngularJS 国际化数据生成管线全解:i18n 目录如何将 Closure 规则集编译为 ngLocale 语言包

AngularJS 国际化数据生成管线全解:i18n 目录如何将 Closure 规则集编译为 ngLocale 语言包 AngularJS 国际化数据生成管线全解i18n 目录如何将 Closure 规则集编译为 ngLocale 语言包【免费下载链接】angular.jsAngularJS - HTML enhanced for web apps!项目地址: https://gitcode.com/gh_mirrors/an/angular.js导读在 AngularJS 仓库中i18n/目录承载着一套完整的“区域化数据locale data生成管线”它以 Google Closure Library 提供的国际符号货币、数字、日期时间、复数规则为上游数据源通过脚本化的提取、转换、序列化流程产出src/ngLocale/下数百个可直接使用的angular-locale_*.js语言包供date、number、currency过滤器及ngPluralize指令消费。读完本文你将掌握该目录的完整结构、generate.sh与update-closure.sh两个脚本的作用与调用链、从 Closure 符号表到$locale服务的转换原理以及仓库配套的单测与 e2e 验证方式。一、i18n 目录总览一条自动化区域数据流水线i18n/README.md 用五个条目勾勒了整个目录的职责划分closure/—— 用于规则集生成ruleset generation的 Closure 文件即上游原始数据源src/—— 生成管线的源码实现提取器、转换器、解析器spec/—— 针对src/下源码的单元测试generate.sh—— 在closure/数据上运行src/脚本并把输出写入 locale 目录即src/ngLocale/update-closure.sh—— 从公开 git 仓库下载最新版 Closure 文件。README 还给出一个重要工程判断Closure 文件由 Shanjian Li 维护变动非常稀少因此不需要频繁重新生成 locale 文件。这也是该目录被设计为“半自动化、按需触发”流水线而非每次构建必跑的原因。按职责进一步归类i18n 目录实际包含四组内容Closure 上游数据closure/、生成器源码与单测src/、spec/、ucd/、端到端验证e2e/以及两个 shell 脚本generate.sh、update-closure.sh。二、上游数据源closure/ 目录中的六类规则集closure/目录存放从 Google Closure Library 的closure/goog/i18n模块同步来的六个 JavaScript 文件它们是整个流水线的“原料”文件提供的数据closure/currencySymbols.js各货币代码对应的符号如USD → $、EUR → €含CurrencyInfo与CurrencyInfoTier2两级表closure/numberSymbols.js各语言/地区的基础数字符号小数分隔符、分组分隔符、默认货币、正负格式模板closure/numberSymbolsExt.js上述数字符号的扩展集覆盖更多国家/地区变体closure/datetimeSymbols.js各语言/地区的月份、星期、纪元、AM/PM、日期/时间格式模板closure/datetimeSymbolsExt.js上述日期时间符号的扩展集closure/pluralRules.js各语言的复数分类函数zero/one/two/few/many/other文件头部注释标明其由 CLDR 数据自动生成这些文件以goog.provide/goog.i18n.*全局命名空间组织例如数字符号以NumberFormatSymbols_locale命名、日期符号以DateTimeSymbols_locale命名。生成器正是靠解析这些命名约定来识别 locale 的详见第四节。三、两个入口脚本更新数据与触发生成3.1 update-closure.sh拉取最新 Closure 规则集update-closure.sh 是“数据更新”入口。脚本首先cd到自身所在目录随后用curl从 Google Closure Library 的closure/goog/i18n路径下依次下载六个源文件并重命名为closure/目录内的对应文件名currency.js→currencySymbols.js、datetimesymbols.js→datetimeSymbols.js、datetimesymbolsext.js→datetimeSymbolsExt.js、numberformatsymbols.js→numberSymbols.js、numberformatsymbolsext.js→numberSymbolsExt.js、pluralrules.js→pluralRules.js。脚本注释明确选择 git 仓库而非 svn 仓库的原因“use the github repo as it is more up to date than the svn repo”。值得注意的两个 shell 细节set -e保证任一步下载失败即中止set -x打开命令追踪便于观察每一步的下载结果。由于上游数据变动频率低该脚本通常只在同步 Closure 新版本时手动执行一次而非每次构建执行。3.2 generate.sh四步完成整条生成链generate.sh 是“代码生成”入口全文仅 15 行却串起了整条流水线#!/bin/bash set -e BASE_DIRdirname $0 yarn run test-i18n node $BASE_DIR/src/closureSlurper.js yarn run test-i18n-ucd echo Generating ngParseExt node $BASE_DIR/ucd/src/extract.js四个步骤依次为yarn run test-i18n—— 先跑生成器自身的单元测试。在 package.json 中该脚本定义为jasmine-node i18n/spec即对i18n/spec/下的closureI18nExtractorSpec.js、converterSpec.js、parserSpec.js全部执行验证确保提取与转换逻辑在重新生成前是可靠的node src/closureSlurper.js—— 核心生成步骤读取closure/下的六类规则集提取、转换并写出src/ngLocale/angular-locale_*.js语言包详见第四、五节yarn run test-i18n-ucd—— 同样对应package.json中的jasmine-node i18n/ucd/spec验证 Unicode 字符数据库UCD提取代码的正确性node ucd/src/extract.js—— 生成ngParseExt模块从ucd.all.flat.xml.gz中提取 Unicode 标识符属性ID_Start/ID_Continue并写出 src/ngParseExt/ucd.js为 AngularJS 表达式解析提供标识符合法性判断依据详见第七节。整体设计思路是“先生成、先验证、后落地”既有数据生成步骤 2、4也有围绕生成逻辑本身的回归测试步骤 1、3形成可重复执行的受控流程。四、核心生成器closureSlurper.js 与 closureI18nExtractor.js4.1 closureSlurper.js编排“读数据 → 提取 → 写文件”三阶段src/closureSlurper.js 是整个管线的调度器它基于QPromise与q-io/fs以链式异步方式执行三个阶段readSymbols()L13-L38分两条流水并行读取。数值流水线依次读取currencySymbols.js、numberSymbols.js、numberSymbolsExt.js先提取货币符号表再提取数字符号日期流水线读取datetimeSymbols.js与datetimeSymbolsExt.js两次调用提取函数扩展集用于补齐基础集缺失的国家级格式。两条流水线以Q.all汇合extractPlurals()L40-L45读取pluralRules.js并执行复数规则提取writeLocaleFiles()L47-L83遍历内存中累积的localeInfo为每个 locale 生成文件内容后写入src/ngLocale/。代码注释专门解释了为什么要用串行循环而非并行写文件“否则会报 EMFILE 错误即打开的文件句柄过多”——数百个语言包如果并发写盘会耗尽文件描述符这是一个非常典型的工程细节。文件输出路径由NG_LOCALE_DIRL10指向src/ngLocale/文件名规则为angular-locale_correctedLocaleId.js并在写盘前通过createFolder()确保目标目录存在。4.2 closureI18nExtractor.js从 Closure 全局对象中抠出数据src/closureI18nExtractor.js 负责真正“读懂” Closure 文件。由于 Closure 文件是普通 JS 脚本提取器在本地构造一个goog桩对象后通过eval在当前上下文执行文件内容从而拿到goog.i18n命名空间下的全部符号表。其关键能力包括findLocaleId(str, type)L18-L26用正则从符号命名中还原 locale 标识数字用/^NumberFormatSymbols_(.)$/日期用/^DateTimeSymbols_(.)$/extractNumberSymbols()/extractCurrencySymbols()/extractDateTimeSymbols()L37-L73分别遍历goog.i18n上的符号对象交给converter转换为 AngularJS 需要的结构。货币符号提取通过Object.assign({}, CurrencyInfoTier2, CurrencyInfo)合并两级表后者覆盖前者pluralExtractor()L75-L104对每个 locale 设置goog.LOCALE为其语言部分如en_SG→en重新eval复数规则文件后取出goog.i18n.pluralRules.select函数的源码字符串再经过一系列字符串替换goog.i18n.pluralRules.Keyword→PLURAL_CATEGORY、get_vf_→getVF、get_wt_→getWT、decimals_→getDecimals并去除换行得到可在 AngularJS 中独立运行的复数判定函数以...占位符暂存。注释解释了为何忽略国家码“国家码更具体的复数规则总是与其语言码一致”如en_SG与en相同correctedLocaleId()L106-L109把 Closure 的下划线风格 locale 名规范化为 AngularJS 风格如zh_CN→zh-cn、en_US→en-us注意统一小写该值同时用于文件名与语言包中的id字段outputLocale()L144-L236负责组装最终语言包文件。它实现了两条重要的回退fallback策略国家/地区级格式缺失时回退到语言级格式如en_xyz缺少NUMBER_FORMATS就用en的若回退后仍无数字格式则该 locale 被跳过不生成文件——原因是datetimeSymbolsExt.js覆盖的国家比数字符号文件更多只写“只有日期格式”的文件没有意义L155-L163。outputLocale还会按需注入三个复数辅助函数getDecimals/getVF/getWT仅在最终内容确实引用了它们时才保留避免冗余最终拼装出标准的 ngLocale 模块外壳详见第六节。五、数据整形层converter.js 与 parser.js5.1 converter.jsClosure 结构 → AngularJS 结构src/converter.js 的职责是“massage the data into the formats we want”把数据揉成我们想要的格式。convertNumberData(dataObj, currencySymbols)L14-L34产出NUMBER_FORMATS对象包含DECIMAL_SEP、GROUP_SEP、由PATTERNS数组组成的两个解析后的格式模板索引 0 为数字格式、索引 1 为货币格式以及CURRENCY_SYM。货币符号从currencySymbols[DEF_CURRENCY_CODE][1]取值若找不到对应符号则直接以货币代码作为符号兜底代码中打印一条告警并为MTL特殊补上了₤convertDatetimeData(dataObj)L37-L61产出DATETIME_FORMATS对象把MONTHS/WEEKDAYS/AMPMS/FIRSTDAYOFWEEK/WEEKENDRANGE/ERAS/ERANAMES等重命名为 AngularJS 使用的键名MONTH、SHORTMONTH、STANDALONEMONTH、DAY、SHORTDAY等并按下标组合出date过滤器的六种预置格式fullDate/longDate/mediumDate/shortDate、mediumTime/shortTime以及拼合的medium/short例如medium DATEFORMATS[2] TIMEFORMATS[2]。5.2 parser.js把 CLDR 数字模板解析成结构对象src/parser.js 实现parsePattern()负责把#,##0.###、¤#,##0.00;(¤#,##0.00)这类 CLDR 数字格式模板解析为结构化的 pattern 对象以;分隔正负两个模板PATTERN_SEP正模板取第一部分用ensureDecimalSep()保证模板含小数点个别 locale如agq_CM的数字模板没有.则自动在最后一个0之后补上如#,##0$→#,##0.$注释详细解释了为何这一补全能让minFrac/maxFrac保持 0 且posSuf解析正确解析整数部分提取posPre#之前的前缀、按#/0的分布统计minFrac/maxFrac、用,切分组提取gSize主分组位数与lgSize首分组位数如印度数字体系#,##,##0的lgSize: 2负数部分如果存在则从负模板中还原negPre/negSuf并剥掉引号转义不存在时默认使用- 正模板前缀/后缀注释注明“所有 locale 都使用-作为 MINUS_SIGN因此硬编码安全”。该对象的字段与 AngularJSnumber/currency过滤器底层src/ng/filter/filters.js 中的 formatNumber 逻辑消费的PATTERNS结构一一对应是连接“CLDR 原始模板”与“运行时格式化”的关键桥梁。六、输出产物src/ngLocale/angular-locale_*.js 语言包生成器最终把每个 locale 写成一个独立的 AngularJS 模块文件目标目录为src/ngLocale/仓库中已存在数百个angular-locale_*.js。以 src/ngLocale/angular-locale_en-us.js 为例其固定外壳为use strict; angular.module(ngLocale, [], [$provide, function($provide) { var PLURAL_CATEGORY {ZERO: zero, ONE: one, TWO: two, FEW: few, MANY: many, OTHER: other}; function getDecimals(n) { ... } function getVF(n, opt_precision) { ... } $provide.value($locale, { DATETIME_FORMATS: { AMPMS: [...], MONTH: [...], SHORTDAY: [...], ... }, NUMBER_FORMATS: { DECIMAL_SEP: ., GROUP_SEP: ,, PATTERNS: [...], CURRENCY_SYM: $ }, pluralCat: function(n, opt_precision) { ... }, id: en-us, localeID: en_US }); }]);要点如下所有 locale 文件都注册到同一个ngLocale模块通过$provide.value($locale, ...)向注入器注册$locale服务应用只需加载对应语言的文件即可覆盖默认语言环境pluralCat函数来自pluralExtractor的字符串变换结果运行时供ngPluralize指令和$locale复数判断使用序列化由serializeContent()完成内部通过canonicalizeForJsonStringifyL111-L136按字典序排序 JSON 键——注释说明这样能让生成的文件稳定可 diff避免因对象遍历顺序不一致导致无意义的文件改动非 ASCII 字符会被转义为\uXXXX保证文件编码安全。七、Unicode 补充管线ucd/ 与 ngParseExt除 locale 数据外i18n/还包含一条面向“标识符合法性”的 Unicode 数据管线i18n/ucd/src/extract.js 读取ucd.all.flat.xml.gzUnicode Character Database 的压缩 XML只提取ID_StartIDS与ID_ContinueIDC两个属性L7-L8将合法标识符区间交由generateCode生成代码后写入 src/ngParseExt/ucd.js配套的单测位于 i18n/ucd/specextactValuesSpec.js、generateCodeSpec.js由generate.sh第三步yarn run test-i18n-ucd触发产物ngParseExt模块服务于 AngularJS 表达式解析器用于在标识符层面支持非 ASCII 的合法 JS 标识符字符。八、质量保障spec/ 单测与 e2e 场景测试8.1 单元测试spec/i18n/spec/下的三个 spec 文件精确覆盖了生成器各环节i18n/spec/parserSpec.js 对parsePattern做了最详尽的验证覆盖 CLDR 中全部数字与货币模板形态带;分隔的显式负模板、带引号包裹的\u202A/\u202C双向控制符、无小数点的模板、印度式#,##,##0分组等逐一断言posPre/negPre/posSuf/negSuf/minInt/minFrac/maxFrac/gSize/lgSize的期望值i18n/spec/converterSpec.js 验证convertNumberData正确产出PATTERNS长度、分组大小、负后缀以及CURRENCY_SYM含“货币码缺失时回退为代码本身”的分支并验证convertDatetimeData对月份/星期/AM/PM 及六种日期时间格式的组合映射i18n/spec/closureI18nExtractorSpec.js 验证findLocaleId对数字/日期符号命名的识别与“错误类型不匹配时返回 undefined”的边界以及outputLocale的组装行为如fr_CA的完整输出结构。8.2 端到端测试e2e/i18n/e2e/ 用 AngularJS Scenario Runner 对多语言行为做浏览器级验证localeTest_es.html、localeTest_cs.html、localeTest_de.html、localeTest_en.html、localeTest_sk.html、localeTest_zh.html分别加载不同 localei18n/e2e/i18n-e2e.js 通过 i18n/e2e/runner.html 驱动的断言检查date:medium、date:longDate的输出是否符合本地化格式如德语3. Juni 1977、捷克语3. června 1977number过滤器的分组/小数表现如西语234.234.443.432、捷克语用不换行空格分组currency过滤器的货币符号位置与格式如德语234.234.443.432,00 €、西语€ 234.234.443.432,00ng-pluralize在不同输入下的复数文案切换包括带 offset 的复杂场景Shanjian, Di and 2 other people are viewing!与数据绑定联动。这组测试直接证明了生成产物与 AngularJS 各本地化组件之间的运行时契约是“数据正确性”的最终把关。九、工程实践总结何时用哪个脚本综合 README 与源码可将该管线的日常使用归纳为三个场景仅需要重新生成 locale 与 ngParseExt直接运行i18n/generate.sh它会自动完成“单测 → 生成 ngLocale → UCD 单测 → 生成 ngParseExt”的完整链路Closure 上游数据有更新先运行i18n/update-closure.sh拉取closure/下的六个最新规则集文件再执行generate.sh重新生成仅调试生成逻辑可单独运行yarn run test-i18n对应jasmine-node i18n/spec验证提取/转换/解析逻辑或直接执行node i18n/src/closureSlurper.js单独产出 locale 文件再核对 src/ngLocale 下的产物。正是因为上游规则集“变动极少”README 原话The closure files change very rarely该流程才得以保持“按需手动触发 全量回归测试”的轻量姿态在保证数百个语言包一致性与可复现性的同时不增加日常构建负担。对希望扩展 AngularJS 语言支持或研究其国际化机制的开发者而言i18n/目录既是数据生成器也是一份完整、可测试的“CLDR 到运行时 $locale”转换参考实现。【免费下载链接】angular.jsAngularJS - HTML enhanced for web apps!项目地址: https://gitcode.com/gh_mirrors/an/angular.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表