物中保住 WordPress 翻譯調(diào)用)
前端CMS【免費(fèi)下載鏈接】wp-calypsoThe JavaScript and API powered WordPress.com項(xiàng)目地址https://gitcode.com/gh_mirrors/wp/wp-calypso點(diǎn)擊查看免費(fèi)下載本篇文章圍繞 wp-calypso 倉(cāng)庫(kù)中的 Babel 插件 automattic/babel-plugin-preserve-i18n 展開介紹它如何在構(gòu)建與壓縮階段保留__( Hello )這類 WordPress i18n 函數(shù)調(diào)用語法使 WP i18n 提取工具在產(chǎn)物包括被 Terser 壓縮后的 bundle中仍能識(shí)別并抽取翻譯字符串。讀完本文你將掌握該插件的設(shè)計(jì)動(dòng)機(jī)、AST 變換原理、scope 綁定維護(hù)細(xì)節(jié)以及它在 Calypso 共享 Babel 配置中的真實(shí)接入方式。一、插件要解決什么問題Calypso 的代碼中大量使用 WordPress 官方的國(guó)際化函數(shù)例如import { __, _x } from wordpress/i18n; __( Hello ); _x( World );在正常編譯流程中import語句會(huì)被 Babel 降級(jí)為require/ CommonJS 形式進(jìn)入生產(chǎn) bundle 后__、_x這類本地變量名往往會(huì)被壓縮器如 Terser重命名成a、b之類的短名。一旦調(diào)用點(diǎn)變成a( Hello )WordPress 的 i18n 提取工具例如 WP-CLI 的i18n make-pot或 Calypso 自己基于 babel-plugin-i18n-calypso 的 POT 生成流程就無法再通過__( 字符串 )的固定模式掃描到待翻譯文本導(dǎo)致字符串漏翻。babel-plugin-preserve-i18n的思路與社區(qū)中的babel-plugin-optimize-react對(duì) React 導(dǎo)入做類似變換一脈相承通過別名化導(dǎo)入、再用const重新聲明原名把翻譯函數(shù)名釘死在產(chǎn)物中。插件 README 中給出了完整的目標(biāo)輸出import { __ as alias__, _x as alias_x } from wordpress/i18n; const __ alias__; const _x alias_x; __( Hello ); _x( World );變換后調(diào)用點(diǎn)的寫法__( Hello )與變換前逐字一致依舊可以在輸出 bundle 中被 i18n 工具掃描出來而 Terser 壓縮時(shí)若配合mangle.reserved選項(xiàng)將__、_x等名字加入保留名單則即使經(jīng)過 minify這個(gè)可識(shí)別的調(diào)用模式依然完整存在見 README.md。二、源碼實(shí)現(xiàn)AST 變換的完整流程插件的主入口是 src/index.js其 package.json 將main指向該文件。整個(gè)實(shí)現(xiàn)只有兩個(gè)核心部分一個(gè)負(fù)責(zé)收集并別名化導(dǎo)入的函數(shù)以及一個(gè)掛載在ImportDeclaration上的 visitor。1. 翻譯函數(shù)白名單const i18nImports new Set( [ __, _n, _nx, _x ] );插件只對(duì)來自wordpress/i18n的這四個(gè)命名導(dǎo)入做處理覆蓋了 WordPress i18n 中最常用的四個(gè)形態(tài)__( text )普通翻譯、_x( text, context )帶上下文、_n( single, plural, number )復(fù)數(shù)、_nx( single, plural, number, context )復(fù)數(shù) 上下文。不在集合內(nèi)的導(dǎo)入如sprintf、isRTL等原樣保留不做任何干預(yù)。2. 收集導(dǎo)入并生成別名collectAllImportsAndAliasThem核心函數(shù)collectAllImportsAndAliasThem( path )完成三步工作src/index.js#L6-L38第一步校驗(yàn)?zāi)K來源。只有t.isStringLiteral( node.source ) node.source.value wordpress/i18n的導(dǎo)入聲明才會(huì)進(jìn)入處理邏輯避免誤傷其他包的同名函數(shù)。第二步遍歷 specifiers 并別名化。對(duì)每個(gè)ImportSpecifier即具名導(dǎo)入要求imported與local都是Identifierimport { __ as foo }這類帶本地別名的寫法也能正確處理隨后命中白名單后記錄{ original: localNode.name, aliased: alias localNode.name }即原名__的別名是alias__用t.importSpecifier( t.identifier( alias localNode.name ), t.identifier( importedNode.name ) )替換原 specifier把導(dǎo)入目標(biāo)改成別名調(diào)用path.scope.removeBinding( localNode.name )移除原本地綁定的注冊(cè)循環(huán)結(jié)束后path.scope.registerDeclaration( path )把修改后的導(dǎo)入聲明重新注冊(cè)到作用域。第三步返回收集到的別名列表供 visitor 使用。3. 在導(dǎo)入聲明后插入 const 重聲明visitor 只監(jiān)聽一個(gè)節(jié)點(diǎn)類型visitor: { ImportDeclaration( path ) { const aliases collectAllImportsAndAliasThem( path ); if ( aliases.length 0 ) { const declarations aliases.map( ( { original, aliased } ) t.variableDeclarator( t.identifier( original ), t.identifier( aliased ) ) ); const aliasDeclarationNode t.variableDeclaration( const, declarations ); path.insertAfter( aliasDeclarationNode ); const aliasDeclarationPath path.getNextSibling(); path.scope.registerDeclaration( aliasDeclarationPath ); } }, },見 src/index.js#L40-L57要點(diǎn)拆解當(dāng)收集到至少一個(gè)別名時(shí)把每條記錄生成一個(gè)variableDeclarator( 原名, 別名 )最終拼成一個(gè)const聲明節(jié)點(diǎn)例如const __ alias__, _x alias_x;通過path.insertAfter把該聲明緊跟在 import 語句之后插入使__、_x以const綁定形式繼續(xù)存在于模塊作用域中插入后立刻通過path.getNextSibling()拿到新節(jié)點(diǎn)的 Path并registerDeclaration到作用域保證后續(xù)對(duì)__、_x的引用能被正確解析。這種先removeBinding、再registerDeclaration的作用域管理是插件正確性的關(guān)鍵它讓 Babel 在后續(xù)遍歷中不會(huì)因綁定信息過期而報(bào)錯(cuò)也確保同一文件內(nèi)既有的__( Hello )調(diào)用語義不發(fā)生任何變化。4. 插件元信息返回的對(duì)象帶有name: babel-plugin-preserve-i18n便于 Babel 在報(bào)錯(cuò)和調(diào)試信息中標(biāo)識(shí)該插件。包本身以module.exports function ( babel ) { ... }的形式導(dǎo)出標(biāo)準(zhǔn)的 Babel 插件工廠函數(shù)依賴 Babel 傳入的babel.typest完成全部節(jié)點(diǎn)構(gòu)造。三、包配置與工程形態(tài)從 package.json 可以看到該包的完整工程信息字段值說明nameautomattic/babel-plugin-preserve-i18n發(fā)布在 npm 上的包名version1.0.0當(dāng)前版本descriptionA Babel plugin to preserves translation functions even when minified.一句話概括插件目標(biāo)mainsrc/index.js入口即插件實(shí)現(xiàn)licenseGPL-2.0-or-later開源許可證publishConfig.accesspublic允許公開發(fā)布devDependenciesautomattic/calypso-eslint-overrides、automattic/calypso-typescript-config均workspace:^僅用于倉(cāng)庫(kù)內(nèi) lint 與 TS 配置倉(cāng)庫(kù)地址指向githttps://github.com/Automattic/wp-calypso.gitdirectory字段精確標(biāo)注到packages/babel-plugin-preserve-i18n。其 tsconfig.json 僅擴(kuò)展了 automattic/calypso-typescript-config 的js-package.json預(yù)設(shè)——注意插件本身是純 JavaScript 實(shí)現(xiàn)TS 配置只服務(wù)于倉(cāng)庫(kù)統(tǒng)一的工程約束。四、在 Calypso 構(gòu)建管線中的真實(shí)接入babel-plugin-preserve-i18n不是孤立的實(shí)驗(yàn)代碼而是 Calypso 共享 Babel 配置的一等公民。在 packages/calypso-babel-config/package.json 中它被聲明為calypso-babel-config的運(yùn)行時(shí)依賴并在默認(rèn) preset 中直接啟用plugins: [ require.resolve( babel/plugin-proposal-class-properties ), [ require.resolve( babel/plugin-transform-runtime ), { corejs: false, helpers: true, regenerator: false, useESModules: false, /* ... */ }, ], require.resolve( automattic/babel-plugin-preserve-i18n ), require.resolve( emotion/babel-plugin ), ],見 presets/default.js#L38-L54也就是說任何基于automattic/calypso-babel-config默認(rèn)預(yù)設(shè)的 Calypso 模塊包括 client 與 packages 下的 React/JS 源碼在 Babel 編譯階段都會(huì)自動(dòng)經(jīng)過本插件的變換。與之配合的還有 config.js 定義的build_pot環(huán)境該環(huán)境啟用automattic/babel-plugin-i18n-calypso并可通過outputPOT參數(shù)指定 POT 文件輸出目錄、注入content-type與x-generator頭。整體鏈路可以概括為源碼寫入__( Hello )Babel默認(rèn) preset含 preserve-i18n把 import 別名化并插入const重聲明調(diào)用點(diǎn)寫法原樣保留生產(chǎn)構(gòu)建經(jīng) Terser 壓縮時(shí)配合mangle.reserved保留翻譯函數(shù)名構(gòu)建產(chǎn)物bundle中依然存在可被掃描的__( Hello )調(diào)用模式WP i18n 工具或build_pot環(huán)境下的 i18n-calypso 插件據(jù)此抽取全部待翻譯字符串生成 POT。這個(gè)順序保證了翻譯字符串提取與代碼壓縮優(yōu)化兩條需求不互相打架——這正是該插件在大型 JS 應(yīng)用中的價(jià)值所在。五、適用邊界與使用注意事項(xiàng)從源碼結(jié)構(gòu)與 README 可以梳理出以下幾點(diǎn)實(shí)踐須知只處理命名導(dǎo)入不處理默認(rèn)導(dǎo)入visitor 中僅匹配t.isImportSpecifierimport i18n from wordpress/i18n這類默認(rèn)導(dǎo)入不會(huì)觸發(fā)變換模塊來源必須精確匹配wordpress/i18n字符串字面量是唯一目標(biāo)即使其他包也導(dǎo)出了__也不會(huì)被誤傷別名命名規(guī)則別名固定為alias 本地名如alias__、alias_nx如果源碼中恰巧存在同名標(biāo)識(shí)符理論上存在沖突風(fēng)險(xiǎn)這也是使用時(shí)應(yīng)留意的邊界情況與 Terser 的配合是錦上添花而非必需即使不配置mangle.reserved經(jīng)插件變換后的 bundle 在未壓縮狀態(tài)下也完全可被 i18n 工具提取配置mangle.reserved才能保證 minify 之后調(diào)用名不被改寫壓縮階段的保護(hù)對(duì)象是調(diào)用模式而非字符串本身插件只關(guān)心函數(shù)名與調(diào)用形態(tài)的保留字符串常量仍由常規(guī)的 i18n 工具鏈負(fù)責(zé)收集與去重。如果希望在自有項(xiàng)目中復(fù)用它最直接的方式是像 Calypso 一樣把它加入 Babel 配置的plugins數(shù)組本倉(cāng)庫(kù)為 workspace 依賴可通過yarn workspace引用automattic/babel-plugin-preserve-i18n隨后按上文鏈路配置構(gòu)建與提取流程即可。六、小結(jié)babel-plugin-preserve-i18n是一個(gè)小而精準(zhǔn)的 Babel 插件它以一次 AST 變換import 別名化 const原名重聲明 scope 綁定維護(hù)解決了壓縮產(chǎn)物中的翻譯調(diào)用可被提取這一國(guó)際化工程難題。核心證據(jù)集中在 src/index.js 的幾十行實(shí)現(xiàn)以及 presets/default.js 中的真實(shí)接入點(diǎn)它服務(wù)的目標(biāo)函數(shù)集合__、_n、_nx、_x與 WordPress i18n 工具的掃描模式一一對(duì)應(yīng)。理解這個(gè)插件的原理也就理解了大型 JavaScript 應(yīng)用中編譯優(yōu)化與字符串提取如何通過作用域與 AST 操作達(dá)成平衡。贊分享前端CMS【免費(fèi)下載鏈接】wp-calypsoThe JavaScript and API powered WordPress.com項(xiàng)目地址https://gitcode.com/gh_mirrors/wp/wp-calypso點(diǎn)擊查看免費(fèi)下載相關(guān)推薦wp-calypso 國(guó)際化構(gòu)建基石babel-plugin-i18n-calypso 提取 translate 調(diào)用生成 POT 全解析wp calypso 國(guó)際化構(gòu)建基石babel plugin i18n calypso 提取 translate 調(diào)用生成 POT 全解析 本文圍繞 pack前端CMSWordPress Gutenberg 的 Babel 插件 babel-plugin-makepot從 JS 源碼自動(dòng)生成 gettext POT 翻譯模板WordPress Gutenberg 的 Babel 插件 babel plugin makepot從 JS 源碼自動(dòng)生成 gettext POT 翻譯模板后端前端探索Babel插件魔法babel-plugin-macros探索Babel插件魔法 babel plugin macros 項(xiàng)目簡(jiǎn)介 Babel https://babeljs.io/ 是JavaScript的編譯器創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考