国际化、Popup 与构建流程)
项目地址fthux/GitZipPro。这是 GitZip Pro 源码解析系列最后一篇收束到国际化、Popup 和多浏览器构建。前六篇已经把 GitZip Pro 的主流程串起来了页面注入、URL 解析、GitHub API、zip 打包、background 下载、设置页和历史统计。最后一篇收束三个辅助但很关键的部分popup.js、i18n.js和build.js。它们不直接构成下载核心但决定了扩展的完整体验用户点击扩展图标看到什么不同语言如何切换以及源码如何构建成 Chrome、Firefox、Edge 可发布的扩展包。Popup扩展图标弹窗popup 由manifest.json的action指定action:{default_popup:popup.html}对应源码是source/popup.html source/popup.jspopup.js文件不长主要做五件事popup 打开读取主题并 applyTheme()初始化 GZP_I18N显示 manifest 版本号绑定设置、Star、Issue、评分按钮检测当前 tab 是否为 GitHub 仓库页它首先读取主题constresultawaitchrome.storage.local.get([STORAGE.THEME]);constthemeresult[STORAGE.THEME]||DEFAULTS.THEME;applyTheme(theme);然后初始化国际化awaitGZP_I18N.init();GZP_I18N.applyTranslations();再通过chrome.runtime.getManifest().version显示当前版本号。Popup 如何判断当前页面状态popup 会查询当前活动标签页const[tab]awaitchrome.tabs.query({active:true,currentWindow:true});如果当前 tab 不是 GitHub显示“不在 GitHub 页面”。如果是 GitHub但不是仓库文件页则显示“不在仓库页面”。如果路径符合仓库页结构就把状态点设置为 active并显示当前 owner/repo。判断逻辑和 content script 的目标一致但 popup 并不注入页面也不下载文件。它只是给用户一个快速状态反馈并提供进入 options 页、项目仓库、issue 页面和商店评分页的入口。国际化系统i18n.jsGitZip Pro 有两套国际化资源source/_locales/ source/locales/_locales是浏览器扩展标准目录用于扩展名称、描述等 manifest 相关文案。source/locales则是应用内部 UI 使用的 JSON 语言包例如 popup、options、content、background 中的文案。真正的应用内国际化逻辑在i18n.js。它通过立即执行函数暴露全局 APIglobal.GZP_I18N{init,t,setLocale,getCurrentLocale,applyTranslations,getTranslatedMessage,loadLocale,reloadLocale,SUPPORTED_LOCALES,DETECTED};支持语言定义为constSUPPORTED_LOCALES[en,zh-CN];constDEFAULT_LOCALEen;语言检测与加载首次初始化时initI18n()会从 storage 中读取gzpLocale。如果没有保存过语言就调用detectBrowserLocale()。detectBrowserLocale()会把中文浏览器识别为zh-CN其他情况回退到en。语言文件通过扩展资源 URL 加载fetch(chrome.runtime.getURL(locales/${normalized}.json))如果目标语言加载失败再回退到英文。这样即使某个语言包缺失界面也不会完全失去文案。t() 与 applyTranslations()t(key, vars)支持点路径读取GZP_I18N.t(popup.active_on,{owner,repo})内部会把 key 按.拆开在语言 JSON 中逐层查找。如果文案里有变量例如{owner}则用传入的vars替换。applyTranslations()则负责扫描 DOM[data-i18n] [data-i18n-html] [data-i18n-placeholder] [data-i18n-title] [data-i18n-value]这解释了为什么 popup 和 options 的 HTML 中可以只写data-i18n然后在 JS 初始化后统一替换文案。语言切换时setLocale(locale)会保存 storage、重新加载语言包、调用applyTranslations()并派发document.dispatchEvent(newCustomEvent(gzp-locale-changed,{detail:{locale}}));这样其他脚本也可以响应语言变化。background 中的国际化background 没有普通页面 DOM所以它不会使用applyTranslations()。它会调用GZP_I18N.loadLocale(locale)取得语言对象然后自己实现一个t(key, vars)从backgroundTranslations中取值。这个设计让右键菜单和系统通知也能使用同一套语言 JSON。整体国际化关系如下source/locales/en.jsonsource/locales/zh-CN.jsoni18n.jspopup.jsapplyTranslations()options_init.jsapplyTranslations()content.jsGZP_I18N.t()background.jsloadLocale() 自己的 t()chrome.storage.local: gzpLocale构建入口package.jsonGitZip Pro 的构建命令定义在package.jsonscripts:{build:node build.js,build:chrome:node build.js --channelchrome,build:firefox:node build.js --channelfirefox,build:edge:node build.js --channeledge}这说明项目支持按渠道构建。默认渠道是 Chrome也可以显式指定 Firefox 或 Edge。构建依赖包括terser clean-css html-minifier分别用于 JS、CSS、HTML 压缩。build.js 的处理流程build.js开头定义了构建目录和渠道constSOURCE_DIRsource;constBUILD_ROOT_DIRbuild;constVALID_STORE_CHANNELSnewSet([chrome,firefox,edge]);constBUILD_DIRpath.join(BUILD_ROOT_DIR,STORE_CHANNEL);resolveStoreChannel()会从命令行参数--channel或环境变量GZP_STORE_CHANNEL中读取渠道。如果没有传入则默认chrome。构建开始时脚本会清空当前渠道的 build 目录fs.rmSync(BUILD_DIR,{recursive:true,force:true});然后复制 icons再遍历sourcewalkDir(SOURCE_DIR,(filePath,relativePath){tasks.push(processFile(filePath,relativePath));});不同文件类型进入不同处理函数.js - processJavaScript() .css - processCSS() .html - processHTML() .json - processJSON() 其他 - 直接复制JS、CSS、HTML 与 JSON 处理JavaScript 通过 Terser 压缩。jszip.min.js已经是压缩文件所以直接复制。constants.js有一个特殊处理codecode.replaceAll(__GZP_STORE_CHANNEL__,STORE_CHANNEL);这会把源码中的商店渠道占位符替换成当前构建渠道从而让扩展在运行时知道自己是 Chrome、Firefox 还是 Edge 版本。CSS 使用 CleanCSS 压缩HTML 使用 html-minifier 压缩。JSON 处理主要针对manifest.json。对于 Chrome 和 Edge 这种 Chrome-like 渠道如果 manifest 是 MV3 且存在 background 配置构建脚本会删除deletejson.background.scripts;这是为了适配不同浏览器对 background 字段的要求。多渠道构建图chromefirefoxedgesource/build.jsSTORE_CHANNELbuild/chromebuild/firefoxbuild/edge压缩 JS/CSS/HTML处理 manifest.json替换 constants.js 渠道占位符复制 icons / locales / 静态资源构建完成后build/{channel}目录就是对应商店可以加载或打包的扩展源码。系列总结到这里GitZip Pro 的源码主线已经完整闭环manifest.json - 注入 constants / i18n / jszip / downloader / content - content.js 改造 GitHub 文件列表 - downloader.js 解析 URL、请求 API、生成 zip - background.js 调用 downloads API 并保存历史 - options.js 管理配置、token、历史和统计 - popup.js 提供轻量状态入口 - build.js 构建多浏览器扩展包如果把 GitZip Pro 抽象成一句话它的源码结构就是浏览器扩展装配层 GitHub 页面适配层 GitHub API 下载层 ZIP 打包层 后台下载层 设置与历史数据层这一系列从入口到运行链路再到构建流程已经覆盖了 GitZip Pro 的主要源码结构。后续如果继续深挖可以单独挑content.js的 GitHub DOM 适配、downloader.js的 symlink 和忽略规则、options.js的 OAuth 流程做更细粒度的函数级解析。系列文章对应的完整源码在 fthux/GitZipPro。如果 GitZip Pro 对你有帮助欢迎 Star、试用或继续基于源码学习浏览器扩展开发。如果你也好奇这个 Pro 版为什么会做起来可以接着看这篇项目缘起用了 GitZip 这么多年我动手做了一个「Pro」版。