ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

使用 @lit/localize 的 Transform 模式为 JavaScript 应用构建多语言版本:transform-js 示例深度解析

2026/9/13 10:41:37 拓冰建站 浏览量
使用 @lit/localize 的 Transform 模式为 JavaScript 应用构建多语言版本:transform-js 示例深度解析 使用 lit/localize 的 Transform 模式为 JavaScript 应用构建多语言版本transform-js 示例深度解析【免费下载链接】litLit is a simple library for building fast, lightweight web components.项目地址: https://gitcode.com/GitHub_Trending/li/lit本文以packages/localize/examples/transform-js示例应用为骨架完整讲解 Lit 官方本地化方案 lit/localize 在transform 模式下的 JavaScript 工程实践如何在构建期把同一份源码编译成多个语言版本的独立 bundle、如何通过 URL 参数切换 locale、如何用 XLIFF 管理翻译内容以及lit/localize-tools与 Rollup 的集成原理。读完本文你将能独立搭建一个零运行时开销、刷新即切换语言的 Web Components 多语言应用。一、示例概览一个三语言的 Hello Worldpackages/localize/examples/transform-js是一个展示 lit/localize 在transform 模式下用于 JavaScript 项目的完整示例其核心特征包括一个用 3 种语言渲染Hello World的简单应用并带有一个下拉语言选择器locale picker采用transform 模式每个 locale 都在构建期被编译为独立的静态文件切换新语言时通过页面刷新加载对应 bundle因此每个 locale 在运行时没有任何渲染开销演示了lit/localize-tools/lib/rollup.js库的用法——它是连接 lit-localize 与 Rollup 构建链的官方桥接工具将当前语言持久化到?localeURL 参数中便于分享链接与刷新后保持语言状态。整个示例的目录结构如下packages/localize/examples/transform-js/ ├── index.html # 入口页面根据 URL 参数动态加载对应语言 bundle ├── lit-localize.json # 本地化配置源语言、目标语言、输出模式 ├── rollup.config.mjs # 为每个 locale 生成独立 bundle 的 Rollup 配置 ├── jsconfig.json # JS 项目的 TypeScript 编译选项 ├── package.json # 构建脚本基于 wireit ├── src/ │ ├── index.js # 应用入口仅引入两个组件 │ ├── localization.js # transform 模式运行时的初始化 │ ├── x-greeter.js # 使用 msg() 标记待翻译文本的 Lit 组件 │ ├── locale-picker.js # 语言下拉选择器组件 │ └── generated/ │ └── locale-codes.js # 由 lit-localize 自动生成的 locale 代码模块 └── xliff/ ├── es-419.xlf # 拉丁美洲西班牙语翻译 └── zh_CN.xlf # 简体中文翻译二、快速启动安装、构建与本地预览原文档给出了完整的运行步骤这里按仓库实际情况展开git clone https://gitcode.com/GitHub_Trending/li/lit.git cd lit/packages/localize/examples/transform-js npm i npm run build npm run serve执行完成后访问控制台日志输出的 URL 即可查看应用。需要说明的是该示例依赖仓库内的packages/localize与packages/localize-tools两个包。在package.json中构建任务通过 wireit 编排scripts: { build: wireit, build:localize: wireit, build:bundle: wireit, serve: web-dev-server }, wireit: { build: { dependencies: [build:localize, build:bundle] }, build:localize: { command: lit-localize build, dependencies: [../../../localize:build, ../../../localize-tools:build], files: [lit-localize.json, jsconfig.json, src/**, xliff/**, !src/generated/**], output: [src/generated/**, locales/**] }, build:bundle: { command: rollup -c, dependencies: [build:localize, ../../../localize:build], files: [rollup.config.mjs], output: [bundled/**] } }整个流水线分为两个阶段build:localize运行lit-localize build命令。它会扫描源码、提取msg()调用中的待翻译文本、合并xliff/目录下的翻译文件并把翻译结果与 locale 代码列表写入src/generated/与locales/目录wireit 的files/output声明了这些输入输出用于增量构建缓存。build:bundle运行rollup -c把每个语言版本分别打包成独立的 ES module bundle 到bundled/目录。三、配置详解lit-localize.json 与 transform 输出模式示例的 lit-localize.json 是理解 transform 模式的关键{ $schema: https://raw.githubusercontent.com/lit/lit/main/packages/localize-tools/config.schema.json, sourceLocale: en, targetLocales: [es-419, zh_CN], inputFiles: [src/**/*.js], output: { mode: transform, outputDir: locales, localeCodesModule: src/generated/locale-codes.js }, interchange: { format: xliff, xliffDir: xliff } }各配置项的含义与作用如下配置项示例值说明sourceLocaleen源码中模板文本所用的语言即源语言targetLocales[es-419, zh_CN]需要翻译成的目标语言列表与源语言共同构成allLocalesinputFiles[src/**/*.js]需要扫描待翻译文本的输入文件 globoutput.modetransform输出模式。transform 模式意味着在构建期生成每个 locale 的独立翻译副本output.outputDirlocalestransform 模式翻译输出的目录output.localeCodesModulesrc/generated/locale-codes.js生成的 locale 代码常量模块路径源语言 目标语言全集interchange.formatxliff翻译交换文件格式interchange.xliffDirxliffXLIFF 翻译文件的存放目录transform 模式与 runtime 模式的本质区别在packages/localize/src/init/transform.ts中可以看到transform 模式的运行时初始化极为轻量export const configureTransformLocalization: ((config: TransformConfiguration) {getLocale: () string}) (config) { _installMsgImplementation(defaultMsg); const sourceLocale config.sourceLocale; return { getLocale: () sourceLocale, }; };关键点在于configureTransformLocalization只接受sourceLocale一个参数返回的getLocale()直接返回源语言代码它安装的是defaultMsg实现——也就是说翻译后的文本在构建期已被直接替换进源码运行时不再存在任何查找翻译表的逻辑对比 runtime 模式需要加载翻译资源、运行时查询msg()的机制transform 模式把全部成本前移到构建期换来的是每个 locale 的 bundle 中零渲染开销。这正是原文档所说for each locale there is no rendering overhead的底层原因。四、源码分析msg() 标记、locale 初始化与语言切换4.1 用 msg() 标记待翻译文本x-greeter.js 演示了最核心的用法——用msg()包裹模板字符串import {LitElement, html} from lit; import {msg} from lit/localize; export class XGreeter extends LitElement { render() { return htmlp${msg(htmlHello bWorld/b!)}/p; } } customElements.define(x-greeter, XGreeter);注意这里msg()的参数是一个Lit 模板html标签模板而不是普通字符串。这样做的价值在于翻译时可以保留b等内嵌 HTML 结构。这一点可以从生成的 XLIFF 文件得到印证xliff/es-419.xlf 中使用了ph占位符来保存内嵌标签trans-unit idh3c44aff2d5f5ef6b sourceHello ph id0lt;b/phWorldph id1lt;/b/ph!/source targetHola ph id0lt;b/phMundoph id1lt;/b/ph!/target /trans-unitphplaceholder机制保证了翻译人员无需触碰 HTML 标签且翻译结果中b的位置由工具自动还原避免破坏组件渲染结构。4.2 初始化 transform 模式的运行时localization.js 是全局唯一的初始化点import {configureTransformLocalization} from lit/localize; export const {getLocale} configureTransformLocalization({ sourceLocale: en, });configureTransformLocalization要求全局只调用一次多次调用会抛错并返回getLocale()供其他模块读取当前 locale 代码。4.3 语言切换写回 URL 参数并刷新locale-picker.js 实现了下拉选择与 locale 持久化import {getLocale} from ./localization.js; import {allLocales} from ./generated/locale-codes.js; const localeNames { en: English, es-419: Español (Latinoamérica), zh_CN: 中文 (简体), }; export class LocalePicker extends LitElement { render() { return html select change${this.localeChanged} ${allLocales.map( (locale) htmloption value${locale} ?selected${locale getLocale()} ${localeNames[locale]} /option )} /select ; } localeChanged(event) { const newLocale event.target.value; if (newLocale ! getLocale()) { const url new URL(window.location.href); url.searchParams.set(locale, newLocale); window.location.assign(url.toString()); } } } customElements.define(locale-picker, LocalePicker);值得注意的实现细节选项列表直接来自自动生成的allLocales常量而不是硬编码这样当lit-localize.json增删目标语言后选择器会自动同步切换语言时把?locale参数写回当前 URL再通过window.location.assign()触发整页刷新——这正是 transform 模式的预期行为新语言对应另一套静态 bundlegetLocale()返回的是构建该 bundle 时固定的语言因此当前选项的选中态是确定且无状态的。4.4 入口页面按语言动态加载 bundleindex.html 展示了 transform 模式的典型部署形态——每个语言一份独立静态 bundlescript const url new URL(window.location.href); const urlLocale url.searchParams.get(locale); const validLocales [en, es-419, zh_CN]; const locale validLocales.includes(urlLocale) ? urlLocale : en; const script document.createElement(script); script.type module; script.src /bundled/${locale}/index.js; document.head.appendChild(script); /script页面在加载阶段就读取 URL 参数校验后拼出/bundled/{locale}/index.js并动态注入 module script。非法或缺失的 locale 值会回退到en。随后页面中的locale-picker与x-greeter自定义元素由对应语言的 bundle 完成定义与渲染。4.5 自动生成的 locale 代码模块lit-localize build会生成 src/generated/locale-codes.js文件头部明确标注不要手工修改请通过运行 lit-localize 重新生成export const sourceLocale en; export const targetLocales [ es-419, zh_CN, ]; export const allLocales [ en, es-419, zh_CN, ];sourceLocale源码模板语言targetLocales目标语言列表按字典序排序allLocales全部有效语言源语言 目标语言按字典序排序。五、构建集成localeTransformers() 与 Rollup 多语言打包这是示例最值得借鉴的工程化部分。transform 模式要求把每个语言版本编译成独立的 bundle而 rollup.config.mjs 通过lit/localize-tools/lib/rollup.js的localeTransformers()优雅地实现了这一点import typescript from rollup/plugin-typescript; import resolve from rollup/plugin-node-resolve; import terser from rollup/plugin-terser; import {summary} from rollup-plugin-summary; import {localeTransformers} from lit/localize-tools/lib/rollup.js; const locales localeTransformers(); export default locales.map(({locale, localeTransformer}) ({ input: src/index.js, plugins: [ typescript({ transformers: { before: [localeTransformer], }, // Specifies the ES version and module format to emit. tsconfig: jsconfig.json, // Temporary directory where transformed modules will be emitted before // Rollup bundles them. outDir: bundled/temp, // rollup/plugin-typescript always matches only .ts files, regardless // of any settings in our jsconfig.json. include: [src/**/*.js], }), resolve(), terser(), summary({ showBrotliSize: true, showGzippedSize: true, }), ], output: { file: bundled/${locale}/index.js, format: es, sourcemap: true, }, }));配置解读localeTransformers()读取lit-localize.json为每个目标 locale 返回一个{locale, localeTransformer}结构其中localeTransformer是一个 TypeScript 程序级 transformertype: program携带ts.Program工厂通过locales.map(...)把同一份配置复制成N 份 Rollup 配置每份只替换其中的 locale transformer输出到bundled/{locale}/index.js——这就是一个语言一个 bundle的来源transformer挂在rollup/plugin-typescript的transformers.before钩子上在编译期把每个msg(...)调用就地替换为该语言对应的翻译文本配置注释还解释了三个容易踩坑的点tsconfig复用jsconfig.json示例中仅声明target: es2021、module: es2020、lib: [esnext, dom]outDir指向bundled/temp作为转换中间产物目录include: [src/**/*.js]是因为该插件默认只匹配.ts文件terser()用于压缩产物summary()会在构建结束时报告每个 bundle 的 gzip 与 brotli 体积方便对比各语言版本的包大小。底层实现TransformLitLocalizerlocaleTransformers()的实现位于 packages/localize-tools/src/rollup.ts。它先通过readConfigFileAndWriteSchema读取配置顺带写出 JSON Schema 供编辑器校验并强制检查if (config.output.mode ! transform) { throw Error(localeTransformers is only supported for transform mode); }即该工具仅支持 transform 模式随后基于TransformLitLocalizer生成每个 locale 的 transformer 工厂并返回。从源码结构可以推断这套设计把翻译替换封装为标准的 TypeScript 编译器转换器因此除了 Rollup理论上任何能接入 TypeScript transformer 的构建工具都能复用同一套翻译逻辑。六、翻译工作流提取、翻译与合入示例通过 XLIFFxliff/目录管理翻译内容。仓库中已包含es-419.xlf与zh_CN.xlf两份翻译文件它们由lit-localize extract从msg()调用中提取生成交给翻译人员填写target节点后再由lit-localize build合入源码。在实际项目中使用本示例的配置典型工作流为编写源码在模板中使用msg()包裹需要翻译的文本普通字符串或 Lit 模板均可提取运行lit-localize extract生成/更新xliff/下的翻译文件与消息 ID如示例中的h3c44aff2d5f5ef6b由消息内容哈希生成翻译在target节点中填写对应语言的译文HTML 标签以ph占位符形式原样保留构建运行lit-localize buildrollup -c为每个 locale 产出独立 bundle部署将bundled/{locale}/index.js作为静态资源按语言路径发布入口页面按?locale参数加载对应文件。七、小结transform 模式的适用场景通过本示例可以看到transform 模式适合语言数量少、切换频率低、但要求极致运行性能的应用每个语言版本都是构建期固化好的静态产物浏览器只需下载与当前语言完全匹配、不含任何翻译运行时的 bundle。代价是语言切换需要整页刷新、且每新增一种语言都会增加一次构建与一份产物。如果你的应用需要运行时动态加载语言或热切换则应在 runtime 模式下参考仓库中runtime-js与runtime-ts示例而本示例所展示的 transform 模式 Rollup 多语言打包方案正是静态化部署场景下最简洁高效的落地形态。【免费下载链接】litLit is a simple library for building fast, lightweight web components.项目地址: https://gitcode.com/GitHub_Trending/li/lit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考