支持实战:从全量 ICU 数据包到定制化 collation)
PGlite 区域化Locale支持实战从全量 ICU 数据包到定制化 collation【免费下载链接】pgliteEmbeddable Postgres with real-time, reactive bindings.项目地址: https://gitcode.com/GitHub_Trending/pg/pglitePGlite 作为运行在 WebAssembly 中的嵌入式 PostgreSQL其区域化Locale支持依托标准 ISO C/POSIX locale 设施与 libicu 数据资源并通过electric-sql/pglite-icu-full包以独立数据包形式分发。本文围绕 docs/docs/localesupport.md 的核心脉络系统讲解如何安装与加载 ICU 数据、查询可用 collation、按需裁剪并自建 ICU 数据包以及如何在 PGlite 中使用 locale 感知的排序、大小写转换与自定义 collation——读完你将掌握一套从全量数据到精准定制的完整方案。什么是 PGlite 的区域化支持区域化Locale support指的是应用程序对文化偏好的尊重包括字母表、排序规则、数字格式化等。例如同是拉丁字母ä在英语排序中紧跟a在瑞典语中却排在z之后——这类差异必须由底层数据库的 locale 与 collation 机制来承载。PGlite 通过libicu使用标准的 ISO C 和 POSIX locale 设施但完整的 ICU 数据资源并不内置于 PGlite 核心而是单独发布在electric-sql/pglite-icu-full包中。该包包含了来自 libicu 的全部数据资源可用于构建本地化的应用程序。从源码看ICU 数据的加载链路非常清晰packages/pglite/src/initdb.ts 定义了ICU_DATA_PATH PG_ROOT /icu即 ICU 数据在虚拟文件系统中的挂载目录packages/pglite/src/pglite.ts 在运行时将mod.ENV.ICU_DATA指向该目录packages/pglite/src/pglite.ts 的#fillIcuDataDir方法在实例化时先递归删除默认 ICU 数据目录再将用户提供的 tarball 解包到该路径实现数据的整体替换。也就是说icuDataDir选项的本质是用一个包含 ICU 数据文件的 tarballBlob/File覆盖 PGlite 默认内置的 ICU 数据目录从而决定数据库内可用的 locale 与 collation 全集。安装与快速使用安装 pglite-icu-full使用你偏好的包管理器安装npm install electric-sql/pglite-icu-full # or yarn add electric-sql/pglite-icu-full # or pnpm add electric-sql/pglite-icu-full该包在 packages/pglite-icu-full/package.json 中声明构建脚本为tsup cp static/* ./dist/即把static/下的 ICU 数据文件icu.76.tgz复制进发布产物随包一起分发。加载全量 ICU 数据import { PGlite } from electric-sql/pglite import { icuDataDir } from electric-sql/pglite-icu-full // Create a PGlite instance with the icu resources const pg await PGlite.create({ icuDataDir: await icuDataDir(), }) // just an example, query the available collations const collations await pg.exec(select * from pg_collation)需要特别注意的是这会加载 libicu 提供的整套 locale 数据体积可能相当大请根据实际场景评估是否真的需要全量数据。icuDataDir()的底层实现见 packages/pglite-icu-full/src/index.ts它定位dist/icu.76.tgz在 Node 环境中通过fs/promises读取文件并包装为 Blob在浏览器环境中则通过fetch下载后调用blob()——因此该函数在 Node 与浏览器含 Worker中均可直接使用。icuDataDir选项的类型定义在 packages/pglite/src/interface.ts 中为Blob | FilePGlite 在初始化流程 packages/pglite/src/pglite.ts 检测到该选项后自动执行数据目录替换无需其他手动步骤。用数据说话全量 ICU 与默认 ICU 的差异仓库中的测试 packages/pglite-icu-full/tests/icuDataDir.test.ts 直接验证了两种模式的差距默认不传icuDataDir时pg_collation返回的 collation 数量约为 10 个级别 10加载icu.76.tgz全量数据后数量达到 879 个以上 879。测试同时验证了全量 ICU 下icu_unicode_version()可正常返回 Unicode 版本号。由此可见是否加载pglite-icu-full直接决定了你的 PGlite 实例能使用的 locale 广度。生成适合 PGlite 的自定义 ICU 数据包如果全量数据过大或者你的应用只需要特定语言环境可以自行生成只包含所需 locale 的 ICU 数据包。整体流程分为四步下载 libicu 源码与数据 → 编写 filters.json → 构建 ICU → 打包数据目录。第 1 步下载 libicu 源码与数据目前 PGlite 实测可配合libicu v76.1工作仓库中的static/icu.76.tgz即对应此版本。下载对应源码和数据$ 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/source/data/locales/root.txt是否存在如果该文件缺失你需要下载icu4c-*-data.zip删除旧的icu4c/source/data目录并用 zip 中的 data 目录替换如果icu4c/source/data/in目录中存在*.dat文件即使你提供了 ICU 自定义过滤规则构建时也会优先使用该.dat文件——因此请确保这里没有残留的旧数据文件否则你的过滤规则不会生效。第 2 步创建 filters.json 过滤规则filters.json用于只生成你真正需要的数据从而大幅缩小数据包体积。一个最简单的例子{ localeFilter: { filterType: locale, includelist: [en_US] } }仓库中提供了一个真实可用的完整示例 packages/pglite-icu-full/examples/Switzerland/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 }该示例只保留瑞士相关的 localeroot、de_CH、fr_CH、it_CH、rm且不包含子地区并排除了分词、货币、时区、音译、emoji 等绝大多数用不到的 feature仅保留排序与大小写转换所需的核心数据。更多过滤器写法可参考 ICU 官方的 Data Build Tool 文档见文末参考资料。第 3 步构建 ICU在 ICU 源码目录下执行 configure 与 make通过环境变量ICU_DATA_FILTER_FILE指定上一步的过滤器$ 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以独立文件而非单个.dat的形式输出数据便于后续按目录打包--disable-shared --enable-static仅生成静态库减少产物体积--disable-tests --disable-samples --disable-extras --disable-icuio --disable-layoutex关闭测试、示例及无关组件加快构建并精简安装目录--prefixyour_install_dir指定安装目录后续打包时只从这里取数据文件。第 4 步打包 ICU 数据目录上述步骤会把 ICU 相关内容安装到your_install_dir但 PGlite 只需要其中的数据文件$ cd your_install_dir/share/icu/76.1/ tar cvfz icu_76.tgz icudt76l/此时icu_76.tgz中即为可用于 PGlite 的本地化数据。仓库内对应的瑞士定制产物是 packages/pglite-icu-full/examples/Switzerland/icu_76_ch.tgz可直接作为参考基线。在 PGlite 中加载自定义 ICU 数据包在 Node 环境读取新生成的 tarball 并传给 PGlite// load your newly generate icu archive const icuDataDir await fs.readFile(resolve(import.meta.dirname, icu_76.tgz)) // now pass it to PGlite pg await PGlite.create({ icuDataDir: new Blob([new Uint8Array(icuDataDir)]), })浏览器环境中也可以直接传入fetch(...).blob()的结果或File对象。加载后 PGlite 会通过loadTar把 tarball 解包到ICU_DATA_PATH随后pg_collation中即可查询到定制范围内的 collation。实战瑞士定制 ICU 数据包能做什么仓库测试 packages/pglite-icu-full/tests/icuDataDir.test.ts 对瑞士定制包进行了全面的功能验证这些用例既可用于验收你自己的定制数据包也是理解 ICU 能力的绝佳素材。1. 查询可用 collation加载瑞士数据包后pg_collation中应出现de-CH-x-icu、fr-CH-x-icu、it-CH-x-icu等瑞士 locale 的 collationcollprovider i即 ICU providerSELECT collname FROM pg_collation WHERE collprovider i ORDER BY collname;2. locale 感知的排序同一份数据在不同 collation 下排序结果完全不同。瑞士德语下ä靠近aSELECT val AS b FROM (VALUES (Birne), (Apfel), (Ärger), (Banane)) AS t(val) ORDER BY val COLLATE de-CH-x-icu -- Ärger 排在 Banane 之前法语瑞士下重音字符按语言习惯排序SELECT val AS b FROM (VALUES (côte), (coté), (cote), (côté)) AS t(val) ORDER BY val COLLATE fr-CH-x-icu -- 首位是 cote全量数据包下的跨语言对比更能说明问题来自测试中的icu functionality套件-- 英语äbc 靠近 abc SELECT b FROM collate_data ORDER BY b COLLATE en-x-icu; -- 瑞典语ä 排在 z 之后 SELECT b FROM collate_data ORDER BY b COLLATE sv-x-icu;3. 大小写转换ICU 的大小写转换同样受 locale 影响。瑞士法语/德语/意大利语SELECT lower(GÉNÈVE COLLATE fr-CH-x-icu), upper(génève COLLATE fr-CH-x-icu); -- génève / GÉNÈVE SELECT lower(ZÜRICH COLLATE de-CH-x-icu), upper(zürich COLLATE de-CH-x-icu); -- zürich / ZÜRICH土耳其语是经典案例lower(I COLLATE tr-x-icu)得到的是无点 iıupper(i COLLATE tr-x-icu)得到的是带点 Iİ与英语行为截然不同。4. 创建自定义 collation 并绑定到数据库瑞士包测试展示了基于 ICU locale 扩展自定义 collation 的两种典型用法德语电话簿排序ö展开为oeCREATE COLLATION IF NOT EXISTS ch_de_phonebook (provider icu, locale de-CHcollationphonebook); -- 标准排序中 Goldmann Götz电话簿排序中 Goldmann Götz SELECT Goldmann Götz COLLATE de-CH-x-icu AS std, Goldmann Götz COLLATE ch_de_phonebook AS phone;数字感知排序Haus-10排在Haus-9之后而非之前CREATE COLLATION IF NOT EXISTS ch_numeric (provider icu, locale de-CHcolNumericyes); SELECT val AS b 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不区分大小写的非确定性 collationCREATE 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; -- true5. 更细粒度的 collation 属性在完整的 ICU 数据下还可以通过 locale 字符串中的后缀组合各种 UCA 属性测试 packages/pglite-icu-full/tests/icuDataDir.test.ts 逐一验证属性作用示例colStrengthprimary;colCaseLevelyes忽略重音差异aaá AAAcolBackwardsyes重音反向比较coté côtecolCaseFirstlower/upper大小写优先级小写在前 / 大写在前colAlternateshifted忽略标点de-luge deanzacolNumericyes数字感知排序A-21 A-123rules a g自定义排序规则将g重排到a之后deterministic false非确定性排序等值判断忽略大小写/归一化abc ABC、NFC 与 NFD 视为相等例如忽略重音的比较CREATE COLLATION IF NOT EXISTS testcoll_ignore_accents (provider icu, locale colStrengthprimary;colCaseLevelyes);6. 让整个数据库使用 ICU locale除了在查询中逐条指定 collation还可以通过initDbStartParams让数据库在初始化时就以 ICU 作为 locale providerconst pg await PGlite.create({ icuDataDir: new Blob([new Uint8Array(icuDataDir)]), initDbStartParams: [--locale-providericu, --icu-localede], })这一参数会被透传到initdb。默认情况下PGlite 的 initdb 使用--localeC.UTF-8 --locale-providerlibc见 packages/pglite/src/initdb.ts覆盖为 ICU provider 后LC_COLLATE与LC_CTYPE便由 ICU 数据驱动ILIKE、lower()/upper()等操作在未显式指定 collation 时也会具备 locale 感知能力。注意事项与常见陷阱体积权衡pglite-icu-full的全量数据包较大且会整体替换默认 ICU 数据目录。如果应用只面向少数语言优先按生成自定义 ICU 数据包一节裁剪。版本对齐仓库目前以 libicu v76.1 为准自定义构建时请保持相近版本避免 ICU 数据格式与 PGlite 内嵌的 ICU 版本不兼容。.dat文件陷阱icu4c/source/data/in中若残留*.dat构建时会绕过你的过滤规则直接使用该文件务必清理。数据目录替换语义#fillIcuDataDir是删除默认目录后整体解包因此你提供的 tarball 必须包含完整的icudt76l数据目录含root等基础数据否则会导致 locale 功能不完整。非确定性 collation 的约束deterministic false的 collation 不能用于某些需要确定性比较的场景如索引默认排序请结合业务谨慎选用。参考资料PostgreSQL 官方 locale 文档https://www.postgresql.org/docs/current/locale.htmlICU Data Build Tool 文档https://unicode-org.github.io/icu/userguide/icu_data/buildtool.html本仓库相关实现packages/pglite-icu-full/src/index.ts、packages/pglite-icu-full/tests/icuDataDir.test.ts、packages/pglite-icu-full/examples/Switzerland/filters_switzerland.json、packages/pglite/src/pglite.ts【免费下载链接】pgliteEmbeddable Postgres with real-time, reactive bindings.项目地址: https://gitcode.com/GitHub_Trending/pg/pglite创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考