Vue2存量項目國際化改造踩坑

Vue2存量項目國際化改造踩坑

一、背景

在各類業務場景中,國際化作為非常重要的一部分已經有非常多成熟的方案,但對于一些存量項目則存在非常的改造成本,本文將分享一個的Vue2項目國際化改造方案,通過自定義Webpack插件自動提取中文文本,大大提升改造效率。

二、核心思路

通過開放自定義Webpack插件,利用AST(抽象語法樹)分析技術,自動掃描Vue組件中的中文文本

  1. 模板中的中文:插值表達式、文本節點、屬性值
  2. 腳本中的中文:字符串字面量、模板字符串
  3. 國際化動態注入:通過插件動態替換原中文片段
  4. 自動生成語言包:輸出標準的i18n語言文件

技術棧

  • vue-template-compiler:解析Vue單文件組件
  • @babel/parser:解析JavaScript代碼為AST
  • @babel/traverse:遍歷AST節點
  • Webpack Plugin API:集成到構建流程

三、插件實現

1.插件基礎結構

class MatureChineseExtractorPlugin {constructor(options = {}) {this.options = {outputPath: './i18n',includePatterns: [/src\/.*\.(vue|js|jsx|ts|tsx)$/, '/src/App.vue'],excludePatterns: [/node_modules/,/dist/,/i18n/,/plugins/,/public/,/webpack\.config\.js/,/package\.json/,],methodPrefix: '$smartT',keyPrefix: '',...options,};// 解析結構this.extractedTexts = new Map();// 文件統計this.fileStats = {totalFiles: 0,processedFiles: 0,extractedCount: 0,injectedFiles: 0,skippedFiles: 0,};}/*** 插件入口文件* @param {*} compiler*/apply(compiler) {// 插件主流程}// 插件核心方法// ......
}

2. AST語法樹抽取

  apply(compiler) {compiler.hooks.done.tap('MatureChineseExtractorPlugin', (stats) => {const projectRoot = compiler.context;const filePath = path.resolve(projectRoot, './src/App.vue');// 解析Vue組件const content = fs.readFileSync(filePath, 'utf-8');const component = parseComponent(content);// 解析模版AST語法樹const templateAst = compile(component.template.content, {preserveWhitespace: false,whitespace: 'condense',});this.traverseTemplateAST(templateAst.ast, filePath);// 解析Script語法樹const scriptAst = parse(component.script.content, {sourceType: 'module',plugins: ['jsx', 'typescript', 'decorators-legacy', 'classProperties'],});this.traverseScriptAst(scriptAst, filePath);// 替換代碼注入this.injectReplaceCodes();// 輸出結果this.outputResults();});}

3.Vue Template AST解析

 /*** 模版AST語法樹處理* @param {AST節點} node*/traverseTemplateAST(node, filePath) {if (!node) return;// 處理元素節點的屬性if (node.type === 1) {// 處理靜態屬性if (node.attrsList) {node.attrsList.forEach((attr) => {if (attr.value && this.containsChinese(attr.value)) {this.addExtractedText(attr.value, filePath, `template-attr-${attr.name}`, {line: node.start || 0,column: 0,});}});}// 處理動態屬性if (node.attrs) {node.attrs.forEach((attr) => {if (attr.value && this.containsChinese(attr.value)) {this.addExtractedText(attr.value, filePath, `template-dynamic-attr-${attr.name}`, {line: 0,column: 0,});}});}}// 處理{{}}表達式節點if (node.type === 2 && node.expression) {// 檢查表達式中是否包含中文字符串const chineseMatches =node.expression.match(/'([^']*[\u4e00-\u9fa5][^']*)'/g) ||node.expression.match(/"([^"]*[\u4e00-\u9fa5][^"]*)"/g) ||node.expression.match(/`([^`]*[\u4e00-\u9fa5][^`]*)`/g);if (chineseMatches) {chineseMatches.forEach((match) => {// 去掉引號const text = match.slice(1, -1);if (this.containsChinese(text)) {this.addExtractedText(text, filePath, 'template-expression', {line: node.start || 0,column: 0,});}});}// 處理模板字符串中的中文if (node.expression.includes('`') && this.containsChinese(node.expression)) {// 簡單提取模板字符串中的中文部分const templateStringMatch = node.expression.match(/`([^`]*)`/);if (templateStringMatch) {const templateContent = templateStringMatch[1];// 提取非變量部分的中文const chineseParts = templateContent.split('${').map((part) => {return part.split('}')[part.includes('}') ? 1 : 0];}).filter((part) => part && this.containsChinese(part));chineseParts.forEach((part) => {this.addExtractedText(part, filePath, 'template-string', {line: node.start || 0,column: 0,});});}}}// 處理文本節點if (node.type === 3 && node.text) {const text = node.text.trim();if (this.containsChinese(text)) {this.addExtractedText(text,filePath,'template-expression',{line: node.start || 0,column: 0,},{oldText: text,newText: `{{${this.options.methodPrefix}('${text}')}}`,});}}// 遞歸處理子節點if (node.children) {node.children.forEach((child) => {this.traverseTemplateAST(child, filePath);});}}

4. Vue Script AST解析

/*** 腳本AST語法樹處理* @param {*} astTree* @param {*} filePath*/traverseScriptAst(astTree, filePath) {traverse(astTree, {// 捕獲所有字符串字面量中的中文StringLiteral: (path) => {const value = path.node.value;if (this.containsChinese(value)) {this.addExtractedText(value, filePath, 'script-string', {line: path.node.loc ? path.node.loc.start.line : 0,column: path.node.loc ? path.node.loc.start.column : 0,});}},// 捕獲模板字符串中的中文TemplateLiteral: (path) => {path.node.quasis.forEach((quasi) => {if (quasi.value.raw && this.containsChinese(quasi.value.raw)) {this.addExtractedText(quasi.value.raw, filePath, 'script-template', {line: quasi.loc ? quasi.loc.start.line : 0,column: quasi.loc ? quasi.loc.start.column : 0,});}});},});}

5. 代碼替換(最小化案例)

  /*** 注入替換代碼片段*/injectReplaceCodes() {// 對所有文件分組const injectionMap = new Map();this.extractedTexts.forEach((extractedText) => {const { filePath } = extractedText;if (injectionMap.has(filePath)) {injectionMap.get(filePath).push(extractedText);} else {injectionMap.set(filePath, [extractedText]);}});// 處理每個文件[...injectionMap.keys()].forEach((filePath) => {let content = fs.readFileSync(filePath, 'utf-8');const extractedTextList = injectionMap.get(filePath);for (const extractedText of extractedTextList) {const { text, replaceConfig } = extractedText;// 在遍歷AST樹時,請自行處理if (replaceConfig) {content = content.replace(replaceConfig.oldText, `${replaceConfig.newText}`);} }fs.writeFileSync(filePath, content, 'utf-8');});}

6. 語言包生成

 /*** 輸出結果*/outputResults() {const results = Array.from(this.extractedTexts.values());console.log(results);// 生成中文映射文件const chineseMap = {};results.forEach((item) => {chineseMap[item.text] = item.text;});const entries = Object.entries(chineseMap);// 寫入JSON文件const outputFile = path.join(this.options.outputPath, 'extracted-chinese.json');fs.writeFileSync(outputFile, JSON.stringify(chineseMap, null, 2), 'utf-8');// 生成對象屬性字符串const properties = entries.map(([key, value]) => {// 轉義特殊字符const escapedValue = value.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n').replace(/\r/g, '\\r');return `  '${key}': '${escapedValue}'`;}).join(',\n');// 生成zh.js文件const zhJsPath = path.join(this.options.outputPath, 'zh.js');fs.writeFileSync(zhJsPath,`// 自動生成的中文語言包
// 生成時間: ${new Date().toLocaleString()}
// 共提取 ${entries.length} 個中文片段export default {
${properties}
};`,'utf-8');console.log(`提取完成,共提取 ${results.length} 個中文片段`);console.log(`結果已保存到: ${outputFile}`);}

7. 其它輔助方法

 /*** 添加提取的文本*/addExtractedText(text, filePath, type, location) {this.extractedTexts.set(text, {text,filePath,type,location,});this.fileStats.extractedCount++;}/*** 檢查是否包含中文*/containsChinese(text) {return /[\u4e00-\u9fa5]/.test(text);}

四、使用方式

1. Webpack配置

// webpack.config.extract.js
const MatureChineseExtractorPlugin = require('./MatureChineseExtractorPlugin');module.exports = {// ... 其他配置plugins: [new MatureChineseExtractorPlugin({outputPath: './i18n',verbose: true})]
};

2. 運行配置

package.json(script)

"extract": "webpack --config webpack.config.extract.js  --mode development "

運行

npm run extract

3. 項目案例

<template><div id="app"><header class="header"><h1>{{ '歡迎使用Vue2國際化演示' }}</h1><p>{{ '這是一個完整的國際化解決方案演示項目' }}</p></header><nav class="nav"><button @click="currentView = 'home'" :class="{ active: currentView === 'home' }">{{ '首頁' }}</button><button @click="currentView = 'user'" :class="{ active: currentView === 'user' }">{{ '用戶管理' }}</button><button @click="currentView = 'product'" :class="{ active: currentView === 'product' }">{{ '商品管理' }}</button></nav><main class="main"><div class="status-bar"><span>當前頁面:{{ currentComponent }}</span><span>{{ userInfo.name }},歡迎使用系統</span><span>今天是{{ currentDate }},祝您工作愉快</span></div><component :is="currentComponent"></component></main><footer class="footer"><p>{{ '版權所有 ? 2024 Vue2國際化演示項目' }}</p></footer></div>
</template><script>
import HomePage from './components/HomePage.vue';
import UserManagement from './components/UserManagement.vue';
import ProductManagement from './components/ProductManagement.vue';export default {name: 'App',components: {HomePage,UserManagement,ProductManagement,},data() {return {currentView: 'home',userInfo: {name: '張三',},currentDate: new Date().toLocaleDateString(),};},methods: {sayHello() {console.log('你好,這是一個Vue2國際化改造案例~');},},computed: {currentComponent() {const components = {home: '首頁',user: '用戶管理',product: '商品管理',};return components[this.currentView];},},
};
</script>

4. 提取結果

i18n/
└──zh.js                    # 中文語言包// 自動生成的中文語言包
// 生成時間: 2025/9/1 11:38:04
// 共提取 12 個中文片段export default {'歡迎使用Vue2國際化演示': '歡迎使用Vue2國際化演示','這是一個完整的國際化解決方案演示項目': '這是一個完整的國際化解決方案演示項目','首頁': '首頁','用戶管理': '用戶管理','商品管理': '商品管理','當前頁面:': '當前頁面:',',歡迎使用系統': ',歡迎使用系統','今天是': '今天是',',祝您工作愉快': ',祝您工作愉快','版權所有 ? 2024 Vue2國際化演示項目': '版權所有 ? 2024 Vue2國際化演示項目','張三': '張三','你好,這是一個Vue2國際化改造案例~': '你好,這是一個Vue2國際化改造案例~'
};

五、總結

通過自定義Webpack插件的方式,我們成功實現了Vue2項目中文文本的自動提取,大大提升了國際化改造的效率。這種基于AST分析的方案不僅準確率高,而且可以靈活擴展,是大型項目國際化改造的理想選擇,而且不僅針對Vue幾乎所有的webpack項目都可以用類似的方案再結合i8n,進行國際化改造~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/95420.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/95420.shtml
英文地址,請注明出處:http://en.pswp.cn/web/95420.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

硬件開發(1)—單片機(1)

1.單片機相關概念1.CPU&#xff1a;中央處理器&#xff0c;數據運算、指令處理&#xff0c;CPU性能越高&#xff0c;完成指令處理和數據運算的速度越快核心&#xff1a;指令解碼執行數據運算處理2.MCU&#xff1a;微控制器&#xff0c;集成度比較高&#xff0c;將所有功能集成到…

Elasticsearch面試精講 Day 4:集群發現與節點角色

【Elasticsearch面試精講 Day 4】集群發現與節點角色 在“Elasticsearch面試精講”系列的第四天&#xff0c;我們將深入探討Elasticsearch分布式架構中的核心機制——集群發現&#xff08;Cluster Discovery&#xff09;與節點角色&#xff08;Node Roles&#xff09;。這是構…

微信小程序長按識別圖片二維碼

提示&#xff1a;二維碼圖片才能顯示識別菜單1.第一種方法添加屬性&#xff1a;show-menu-by-longpress添加屬性&#xff1a;show-menu-by-longpress <image src"{{shop.wx_qrcode}}" mode"widthFix" show-menu-by-longpress></image>2.第二種…

智能化數據平臺:AI 與大模型驅動的架構升級

在前面的文章中,我們探討了 存算分離與云原生,以及 流批一體化計算架構 的演進趨勢。這些演進解決了“算力與數據效率”的問題。但在今天,企業在數據平臺上的需求已經從 存儲與計算的統一,逐步走向 智能化與自動化。 尤其是在 AI 與大模型快速發展的背景下,數據平臺正在發…

解鎖 Vue 動畫的終極指南:Vue Bits 實戰進階教程,讓你的Vue動畫比原生動畫還絲滑,以及動畫不生效解決方案。

一條 Splash Cursor 的 10 秒 Demo 視頻曾創下 200 萬 播放量&#xff0c;讓 React Bits 風靡全球。如今&#xff0c;Vue 開發者終于迎來了官方移植版 —— Vue Bits。 在現代 Web 開發中&#xff0c;UI 動效已成為提升用戶體驗的關鍵因素。Vue Bits 作為 React Bits 的官方 Vu…

《微服務協作實戰指南:構建全鏈路穩健性的防御體系》

在微服務架構從“技術嘗鮮”邁向“規模化落地”的進程中&#xff0c;服務間的協作不再是簡單的接口調用&#xff0c;而是涉及超時控制、事務一致性、依賴容錯、配置同步等多維度的復雜博弈。那些潛藏于協作鏈路中的隱性Bug&#xff0c;往往不是單一服務的功能缺陷&#xff0c;而…

STM32F103C8T6的智能醫療藥品存儲柜系統設計與華為云實現

項目開發背景 隨著現代醫療技術的快速發展&#xff0c;藥品的安全存儲與管理成為醫療質量控制中的重要環節。許多藥品對存儲環境的溫濕度具有嚴格的要求&#xff0c;一旦超出允許范圍&#xff0c;藥品的理化性質可能發生改變&#xff0c;甚至失效&#xff0c;直接影響患者的用藥…

python批量調用大模型API:多線程和異步協程

文章目錄 多線程批量調用 基本原理 實現代碼 代碼解析 使用注意事項 異步協程實現批量調用 異步協程實現方式 異步實現的核心原理 多線程 vs 異步協程 選擇建議 多線程批量調用 基本原理 多線程批量調用大模型API的核心思想是通過并發處理提高效率,主要原理包括: 并發請求:…

硬件開發1-51單片機1

一、嵌入式1、概念&#xff1a;以應用為中心&#xff0c;以計算機技術為基礎&#xff0c;軟硬件可裁剪的專用計算機系統以應用為中心&#xff1a;系統設計的起點是 “具體應用場景”&#xff0c;按照應用需求出發以計算機技術為基礎&#xff1a; 硬件技術&#xff1a;嵌…

Redis核心數據類型解析——string篇

Redis的常見數據類型Redis 提供了 5 種數據結構&#xff0c;理解每種數據結構的特點對于 Redis 開發運維?常重要&#xff0c;同時掌握每 種數據結構的常?命令&#xff0c;會在使? Redis 的時候做到游刃有余。預備在正式介紹 5 種數據結構之前&#xff0c;了解?下 Redis 的?…

爬蟲逆向--Day20Day21--扣JS逆向練習【案例4:深證信服務平臺】

一、案例【深證信數據服務平臺】案例地址鏈接&#xff1a;https://webapi.cninfo.com.cn/#/marketDataDate案例爬取鏈接&#xff1a;https://webapi.cninfo.com.cn/api/sysapi/p_sysapi10071.1、入口定位當進行入口定位時&#xff0c;我們首先需要進行查看響應、載荷、請求頭是…

ExcelJS實現導入轉換HTML展示(附源碼可直接使用)

目錄 簡介 開始實踐 難點 文件示例 效果預覽 具體實現 安裝 完整代碼 總結 簡介 在日常工作中&#xff0c;我們可能會遇到需要上傳并展示 Excel 文件的需求&#xff0c;實現文件內容的在線預覽。 這里給大家接收一個組件庫exceljs&#xff0c;這個組件庫進過實踐發現…

ECDH和數字簽名

文章目錄一、核心區別&#xff1a;目的完全不同二、協同工作關系&#xff1a;缺一不可的安全組合三、技術結合點&#xff1a;都基于ECC(橢圓曲線密碼學)ECDH&#xff08;橢圓曲線迪菲-赫爾曼密鑰交換&#xff09;和數字簽名&#xff08;如ECDSA&#xff0c;橢圓曲線數字簽名算法…

withCredentials(簡單說:帶不帶憑證)

一、withCredentials是什么&#xff1f;withCredentials 是瀏覽器 XMLHttpRequest 或 Fetch API&#xff08;以及 axios 等基于它們的庫&#xff09;中的一個配置項&#xff0c;作用是控制跨域請求時是否攜帶 Cookie、HTTP 認證信息等憑證。用更通俗的方式解釋&#xff1a;二、…

window系統使用命令行來安裝OpenSSH服務器或客戶端

可以通過 PowerShell 命令行來安裝&#xff0c;這種方式更直接可靠&#xff1a;以管理員身份打開 PowerShell&#xff1a; 按下 Win S 搜索 “PowerShell”右鍵點擊 “Windows PowerShell”&#xff0c;選擇"以管理員身份運行"安裝 OpenSSH 客戶端&#xff1a; Add-…

vim中常見操作及命令

在 Vim 中為所有行的行首添加相同字符&#xff0c;可以使用以下方法&#xff1a; 方法1&#xff1a;使用 :%s 替換命令&#xff08;推薦&#xff09; vim :%s/^/要添加的字符/ 例如要在所有行首添加 #&#xff1a;vim :%s/^/#/ 方法2&#xff1a;使用塊選擇模式&#xff08;可視…

開發使用mybatis是用混合模式還是全注解模式

在使用 MyBatis 開發項目時&#xff0c;Mapper 接口是為數據庫操作提供最直觀的方法&#xff0c;但在實現方式上&#xff0c;我們有兩種選擇&#xff1a;全注解模式和混合模式。那么&#xff0c;他們有什么區別&#xff0c;應該如何選擇&#xff1f;我們一起來討論一下。一、全…

WS2812燈帶效果設計器上位機

軟件使用方法介紹&#xff1a;bilibili地址 【免寫單片機代碼WS2812燈帶效果設計軟件-嗶哩嗶哩】 https://b23.tv/xFhxMGm

Docker 容器(二)

Docker四、Docker容器數據卷1.數據卷的主要特點2.卷的共享與繼承&#xff08;1&#xff09;卷的共享&#xff08;Sharing&#xff09;(2) 卷的繼承&#xff08;Inheritance&#xff09;3.數據卷運行實例五、Dockerfile1.Dockerfile2. 創建一個名為 myubuntu的自定義鏡像第 1 步…

PCB基礎細節--工藝篇

pcb基礎細節&#xff08;工藝篇&#xff09; 1. 孔與焊盤2. PCB各層之間的作用3. 阻抗匹配 3.1. 什么是傳輸線&#xff1f;我們只看特性阻抗&#xff0c;時延以后再說。 在畫原理圖時&#xff0c;我們把電阻&#xff0c;電容&#xff0c;電感是抽象成一個點了。兩邊加一個電壓&…