资讯中心

PGlite 自定义 ICU 语言包生成指南:为嵌入式 Postgres 构建精简本地化数据

📅 2026/10/22 17:51:30
PGlite 自定义 ICU 语言包生成指南:为嵌入式 Postgres 构建精简本地化数据
PGlite 自定义 ICU 语言包生成指南为嵌入式 Postgres 构建精简本地化数据【免费下载链接】pgliteEmbeddable Postgres with real-time, reactive bindings.项目地址: https://gitcode.com/GitHub_Trending/pg/pglite导读本文基于 PGlite 仓库中的 pglite-icu-full 示例文档 展开完整讲解如何从 libicu 源码出发借助 ICU Data Build Tool 生成只包含你需要的 locale的精简数据包.tgz并将其通过icuDataDir选项注入 PGlite让嵌入式 Postgres 获得完整、正确的本地化排序、大小写转换与索引能力。读完本文你将掌握从下载 ICU 源码到构建、打包、加载自定义 locale 数据的完整闭环并能参考仓库中的瑞士Switzerland示例为自己的应用裁剪出体积最小的 ICU 数据。背景为什么 PGlite 需要独立的 ICU 数据包PGlite 是运行在 WebAssembly 中的嵌入式 Postgres其本地化能力依赖 libicu。仓库文档 localesupport.md 明确说明PGlite 通过libicu使用标准的 ISO C 与 POSIX locale 设施而完整的 ICU 数据资源由单独分发的electric-sql/pglite-icu-full包提供。electric-sql/pglite-icu-full的定位见 package.json是包含 libicu 全部数据资源的包用于构建本地化应用。它的入口实现位于 src/index.tsimport { pglUtils } from electric-sql/pglite-utils export async function icuDataDir(): PromiseBlob { const moduleUrl new URL(../dist/icu.76.tgz, import.meta.url) if (pglUtils.IN_NODE) { const fs await import(fs/promises) const buffer await fs.readFile(moduleUrl) return new Blob([new Uint8Array(buffer)]) } else { const downloadPromise await fetch(moduleUrl) return downloadPromise.blob() } }该函数在 Node 环境直接读取dist/icu.76.tgz在浏览器环境则通过fetch拉取最终都归一化为Blob返回——这正是 PGliteicuDataDir选项所需的输入类型。静态数据文件位于 static/icu.76.tgz构建脚本build: tsup cp static/* ./dist/见 package.json会把它复制进dist目录随包发布。使用完整包非常简单npm install electric-sql/pglite-icu-full # 或 yarn add electric-sql/pglite-icu-full # 或 pnpm add electric-sql/pglite-icu-fullimport { PGlite } from electric-sql/pglite import { icuDataDir } from electric-sql/pglite-icu-full // 创建携带完整 ICU 资源的 PGlite 实例 const pg await PGlite.create({ icuDataDir: await icuDataDir(), }) // 示例查询当前可用的所有 collation const collations await pg.exec(select * from pg_collation)但完整包might be quite large可能相当大——它加载的是 libicu 提供的全部 locale 集合。对于只面向少数几个语言区域的 Web 应用这会造成不必要的体积浪费。这正是本文核心主题的由来如何生成只包含所需 locale 的自定义 ICU 数据包。PGlite 侧如何消费 ICU 数据icuDataDir的底层实现在深入构建流程之前先看 PGlite 主包是如何接收并应用这个数据包的这有助于理解最终产物的格式要求。icuDataDir是 PGliteOptions 中的一个可选配置项类型为Blob | File。在 pglite.ts 中PGlite 实例创建后若检测到该选项会立即调用私有方法#fillIcuDataDir// packages/pglite/src/pglite.ts#L660-L668 async #fillIcuDataDir(icuDataDir: Blob | File) { this.#log( pglite: icuDataDir specified, removing default icu data dir at ${ICU_DATA_PATH}, ) pglUtils.rmdirRecursive(this.mod!.FS, ICU_DATA_PATH) this.#log(pglite: loading icu data from tarball ${icuDataDir}) this.mod!.FS.mkdirTree(ICU_DATA_PATH) await loadTar(this.mod!.FS, icuDataDir, ICU_DATA_PATH) }关键点如下默认 ICU 数据目录被整体移除ICU_DATA_PATH定义在 initdb.ts 中即PG_ROOT /icu。也就是说你提供的自定义数据会替换内置的默认 ICU 数据而不是叠加。数据以 tarball 形式加载loadTar会把传入的Blob/File解包到ICU_DATA_PATH下这解释了为什么最终的归档文件必须是 tar/tgz 格式且内部目录结构要与默认布局一致如icudt76l/。ICU_DATA环境变量指向该目录在 initdb 实例的preRun阶段initdb.ts和主模块中pglite.ts都会设置mod.ENV.ICU_DATA ICU_DATA_PATH让 libicu 从该目录读取数据。因此你生成的icu_*.tgz本质上是一个遵循 libicu 文件数据布局file data packaging的 tar 归档PGlite 会在启动时把它解压进虚拟文件系统的 ICU 数据目录。第一步下载 libicu 源码与数据PGlite 当前测试基于 libicu v76.1。需要同时下载两部分wget https://github.com/unicode-org/icu/releases/download/release-76-1/icu4c-76_1-src.tgz wget https://github.com/unicode-org/icu/releases/download/release-78.3/icu4c-78.3-data.zip注意原文档中的第二个链接下载的是ICU Data Build Tool 对应版本的数据包icu4c-*-data.zip它并非与 76.1 源码同版本而是与你要使用的 Data Build Tool 版本配套。下载后需解压并替换源码目录中的data目录详见下文注意事项。⚠️ 重要注意事项务必逐条核对使用ICU Data Build Tool的前提是必须拥有数据源。解压icu4c-76_1-src.tgz后请检查icu4c/source/data/locales/root.txt是否存在如果root.txt缺失需要下载icu4c-*-data.zip删除旧的icu4c/source/data目录并用 zip 包中的 data 目录替换它如果icu4c/source/data/in目录下存在*.dat文件即使你提供了自定义 filter 规则该 .dat 文件仍会被优先使用。这意味着你设置的 locale 过滤将不会生效——构建前必须确保该目录干净不含旧.dat文件。第二步编写 filters.json 过滤规则ICU Data Build Tool 通过ICU_DATA_FILTER_FILE环境变量指向的 JSON 文件来决定哪些数据要保留、哪些要剔除。这是实现只打包所需 locale的核心机制。最简单的 locale 过滤示例只保留en_US{ localeFilter: { filterType: locale, includelist: [en_US] } }字段说明filterType过滤类型值为locale时按 locale 名称过滤includelist需要包含的 locale 标识列表如en_US、de_CH除此之外ICU 还支持excludelist、includeChildren等选项以及featureFilters按功能模块过滤例如剔除断词器、时区数据等可以进一步压缩体积。仓库中的瑞士示例 filters_switzerland.json 给出了一个生产级的完整配置{ localeFilter: { filterType: locale, includelist: [root, de_CH, fr_CH, it_CH, rm], includeChildren: false }, featureFilters: { brkitr_rules: exclude, brkitr_dictionaries: exclude, brkitr_tree: exclude, conversion_mappings: exclude, confusables: exclude, curr_supplemental: exclude, curr_tree: exclude, lang_tree: exclude, normalization: exclude, region_tree: exclude, rbnf_tree: exclude, stringprep: exclude, zone_tree: exclude, translit: exclude, unames: exclude, ulayout: exclude, uemoji: exclude, unit_tree: exclude, cnvalias: exclude }, collationUCAData: implicithan }这个配置体现了两个关键实践localeFilterincludelist只保留瑞士四种官方语言 localede_CH德语、fr_CH法语、it_CH意大利语、rm罗曼什语加root根 locale作为回退基础并设置includeChildren: false不引入子 localefeatureFilters把断词器brkitr、货币curr、语言树lang、时区zone、转写translit、emojiuemoji、归一化normalization等与排序/大小写无关的功能模块全部exclude进一步削减体积——排序与大小写转换只需要 collation 相关的 UCA 数据。结合测试用例可以验证使用该瑞士数据包后icuDataDir.test.ts 断言pg_collation中包含了de-CH-x-icu、fr-CH-x-icu、it-CH-x-icu说明过滤后的数据足以支撑这些 locale 的排序与大小写功能。第三步构建 ICU应用过滤器并编译写好 filters.json 后进入 ICU 源码目录执行配置与编译。ICU_DATA_FILTER_FILE环境变量指向你 filter 文件的绝对路径ICU_DATA_FILTER_FILEfull_path_to_your_filters.json ./icu/source/configure \ --with-data-packagingfiles \ --disable-shared \ --enable-static \ --disable-tests \ --disable-samples \ --disable-extras \ --disable-icuio \ --disable-layoutex \ --prefixyour_install_dir make -j make install各配置项的作用与取舍配置项作用--with-data-packagingfiles关键选项。将 ICU 数据以松散文件形式安装而非打包成单个.dat文件这是后续tar归档的前提也规避了.dat文件会覆盖 filter 规则的问题--disable-shared --enable-static只构建静态库适合 WASM 嵌入式场景--disable-tests --disable-samples跳过测试与示例加速构建--disable-extras --disable-icuio --disable-layoutex剔除附加工具、ICU I/O 层与旧版布局引擎进一步裁剪--prefixyour_install_dir指定安装目录后续打包只取其中的数据子目录make -j并行编译make install会把编译产物与过滤后的数据安装到--prefix指定的目录。第四步将 ICU 数据打成 tar 归档安装目录中包含了所有与 ICU 相关的内容但 PGlite只需要数据文件。数据位于安装目录的share/icu/76.1/下打包如下cd your_install_dir/share/icu/76.1/ tar cvfz icu_76.tgz icudt76l/此时生成的icu_76.tgz就是可以直接交给 PGlite 的本地化数据包。注意版本号路径76.1必须与 PGlite 测试的 libicu 版本一致归档根目录下的icudt76l/目录名是 libicu 按版本生成的76对应 ICU 76.xl表示 little-endian 小端序不要改名——PGlite 解包时依赖这套默认布局用tar cvfzgzip 压缩可以在保留目录结构的同时显著减小体积。第五步把自定义归档交给 PGlite生成归档后用它与仓库中electric-sql/pglite-icu-full同等的加载方式注入 PGliteNode 侧示例import { PGlite } from electric-sql/pglite import { readFile } from fs/promises import { resolve } from path // 读取你生成的 icu 归档 const icuDataDir await readFile( resolve(import.meta.dirname, icu_76.tgz), ) // 包装成 Blob 后传给 PGlite const pg await PGlite.create({ icuDataDir: new Blob([new Uint8Array(icuDataDir)]), })在浏览器/Worker 环境也可以用fetch(icu_76.tgz).then(r r.blob())直接得到 Blob。PGlite 会像上文分析的#fillIcuDataDir那样移除默认 ICU 数据目录并解包你的归档。实战验证瑞士自定义语言包能做到什么仓库在 examples/Switzerland/ 目录下同时提供了过滤配置 filters_switzerland.json 和生成好的数据文件icu_76_ch.tgz可直接用于 PGlite。其配套测试 icuDataDir.test.ts 展示了该自定义包能提供的完整能力1. locale 感知的排序-- 德语瑞士ä 紧跟在 a 附近排序 SELECT val FROM (VALUES (Birne), (Apfel), (Ärger), (Banane)) AS t(val) ORDER BY val COLLATE de-CH-x-icu; -- 期望Ärger 排在 Banane 之前 -- 法语瑞士带重音字符正确排序 SELECT val FROM (VALUES (côte), (coté), (cote), (côté)) AS t(val) ORDER BY val COLLATE fr-CH-x-icu; -- 期望cote 排在最前 -- 意大利语瑞士重音元音正确排序 SELECT val FROM (VALUES (perché), (pera), (perciò), (percorso)) AS t(val) ORDER BY val COLLATE it-CH-x-icu; -- 期望pera 排在 perché 之前2. 语言相关的大小写转换SELECT lower(GÉNÈVE COLLATE fr-CH-x-icu) AS lo, -- 结果génève upper(génève COLLATE fr-CH-x-icu) AS up; -- 结果GÉNÈVE SELECT lower(ZÜRICH COLLATE de-CH-x-icu) AS lo, -- 结果zürich upper(zürich COLLATE de-CH-x-icu) AS up; -- 结果ZÜRICH3. 自定义 ICU collation 属性-- 德语电话簿排序ö 按 oe 展开 CREATE COLLATION IF NOT EXISTS ch_de_phonebook (provider icu, locale de-CHcollationphonebook); SELECT Goldmann Götz COLLATE ch_de_phonebook AS phone; -- 结果true -- 数字感知排序 CREATE COLLATION IF NOT EXISTS ch_numeric (provider icu, locale de-CHcolNumericyes); SELECT val FROM (VALUES (Haus-9), (Haus-10), (Haus-2), (Haus-1)) AS t(val) ORDER BY val COLLATE ch_numeric; -- 期望顺序Haus-1, Haus-2, Haus-9, Haus-10 -- 不区分大小写的非确定性 collation CREATE COLLATION IF NOT EXISTS ch_ci (provider icu, locale de-CHcolStrengthsecondary, deterministic false); SELECT Grüezi COLLATE ch_ci grüezi COLLATE ch_ci AS eq; -- 结果true这些测试用例说明即便过滤掉了时区、货币、emoji 等大量功能模块排序collation与大小写case conversion能力依然完好——这正是业务应用最依赖的本地化能力也是裁剪体积不裁剪功能的最佳佐证。全量 ICU 与精简 ICU 的对比验证仓库测试 icuDataDir.test.ts 还直观对比了默认 PGlite 与加载全量 ICU 数据包后的差异默认实例pg_collation返回的 collation 数量 ≥ 10加载dist/icu.76.tgz全量包后collation 数量≥ 879且明显多于默认实例。这从测试层面证实了两点一是全量 ICU 包显著扩充了可用 locale 数量879 个 collation二是icuDataDir的替换机制生效——默认内置数据被完整替换为外部数据。对自定义精简包如瑞士示例而言虽然 collation 数量少于全量包但体积更小、加载更快适合对包体积敏感的嵌入式与浏览器场景。关键注意事项汇总版本匹配PGlite 当前针对 libicu v76.1 测试构建时版本号路径share/icu/76.1/与归档目录名icudt76l/需与该版本一致数据源必须完整确保icu4c/source/data/locales/root.txt存在缺失时用icu4c-*-data.zip的 data 目录替换否则 ICU Data Build Tool 无法工作警惕残留 .dat 文件icu4c/source/data/in下的任何*.dat都会绕过你的 filter 规则被直接采用构建前务必清理打包格式--with-data-packagingfiles是必须的产物必须是包含icudt76l/目录结构的 tar/tgz因为 PGlite 侧通过loadTar解包到ICU_DATA_PATHPG_ROOT/icu替换而非叠加传入icuDataDir后PGlite 会先递归删除默认 ICU 数据目录再解包你的归档见 pglite.ts因此你的包需要自带完整可用的数据通常应包含rootlocale 作为回退基础参考瑞士示例体积与功能的平衡通过featureFilters剔除排序无关的模块时区、货币、emoji、转写等可显著减小包体积且不影响 collation 与大小写能力——瑞士示例的 filters_switzerland.json 是可直接复用的参考模板。参考文档本文主体来源pglite-icu-full/examples/README.md完整 ICU 包使用说明pglite-icu-full/README.mdPGlite 官方本地化文档docs/docs/localesupport.mdICU 数据加载实现packages/pglite/src/pglite.ts、packages/pglite/src/initdb.tsicuDataDir选项定义packages/pglite/src/interface.ts瑞士示例过滤配置packages/pglite-icu-full/examples/Switzerland/filters_switzerland.json功能验证测试packages/pglite-icu-full/tests/icuDataDir.test.ts【免费下载链接】pgliteEmbeddable Postgres with real-time, reactive bindings.项目地址: https://gitcode.com/GitHub_Trending/pg/pglite创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案