前言 最近我在开发一个基于 Plasmo 的 Chrome MV3 浏览器扩展,项目主要用于统一管理 ChatGPT、Claude 等 AI 平台的对话记录,支持账号同步、搜索、收藏、标签、批量操作等功能。 随着功能逐渐完善,扩展里出现了大量 UI 文案、提示信息、错误信息。如果这些文本全部硬编码在组件里,后续维护会非常麻烦: 想支持英文、中文等多语言时,需要到处改代码; popup、content script、background 里都有文案,容易遗漏; Chrome 扩展本身也有名称、描述等 manifest 文案,也需要国际化; 文案带变量时,比如“已选中 5 项”,如果手动拼接,会不利于多语言适配。 因此,我最终使用 Chrome 扩展原生提供的 chrome.i18n API,再结合 Plasmo 的目录约定,给项目做了一套轻量、简单、够用的国际化方案。 本文就结合我的项目实践,介绍如何在 Plasmo 浏览器扩展中实现 i18n。 一、项目技术栈 我的项目是一个 Chrome MV3 扩展,主要技术栈如下: Plasmo React TypeScript Tailwind CSS Chrome Extension Manifest V3 项目结构比较简单,所有源文件都放在项目根目录,没有额外的 src 目录。 核心文件大致如下: my-first-extension/ ├── popup.tsx ├── content.ts ├── background.ts ├── logic.ts ├── db.ts ├── utils/ │ └── i18n.ts ├── locales/ │ ├── en/ │ │ └── messages.json │ └── zh_CN/ │ └── messages.json ├── package.json └── ... 其中和国际化相关的主要是: utils/i18n.ts locales/en/messages.json locales/zh_CN/messages.json package.json 二、Plasmo 项目中的语言包目录 在 Chrome 扩展规范中,最终构建产物里通常会有 _locales 目录,例如: _locales/ ├── en/ │ └── messages.json └── zh_CN/ └── messages.json 而在 Plasmo 项目开发阶段,我们可以直接在项目中创建: locales/ ├── en/ │ └── messages.json └── zh_CN/ └── messages.json Plasmo 构建时会处理这些语言资源,让它们符合 Chrome Extension 的国际化规范。 我的项目里目前支持两种语言: locales/en/messages.json locales/zh_CN/messages.json 其中: en 是英文; zh_CN 是简体中文。 三、在 manifest 中配置默认语言 Chrome 扩展要启用国际化,必须在 manifest 中声明 default_locale 。 在 Plasmo 项目里,可以直接在 package.json 的 manifest 字段里配置: { "manifest": { "default_locale": "en", "permissions": [ "storage", "alarms" ], "host_permissions": [ "https://chatgpt.com/*", "https://claude.ai/*" ] } } 这里我把默认语言设置为英文: "default_locale": "en" 这意味着如果用户当前浏览器语言没有对应的语言包,Chrome 会回退使用英文语言包。 四、扩展名称和描述的国际化 浏览器扩展的名称、描述也可以使用 Chrome i18n 的占位符语法。 我的 package.json 中这样写: { "name": "multi-ai-conversation-manager", "displayName": "__MSG_extName__", "description": "__MSG_extDescription__" } 这里的重点是: "displayName": "__MSG_extName__", "description": "__MSG_extDescription__" __MSG_xxx__ 是 Chrome 扩展国际化的特殊语法。 例如: __MSG_extName__ 会去当前语言包的 messages.json 中查找: { "extName": { "message": "ChatGPT & Claude AI Conversation Manager" } } 中文语言包中对应的是: { "extName": { "message": "ChatGPT、Claude AI 对话管理器" } } 这样一来,扩展在 Chrome 扩展管理页、商店信息或者 manifest 中展示时,就能根据用户浏览器语言自动切换名称和描述。 五、messages.json 的基本格式 Chrome 扩展的语言文件必须叫做 messages.json 。 基本格式如下: { "key": { "message": "具体文案" } } 比如英文语言包: { "account_management": { "message": "Account Management" }, "account_add": { "message": "Add Account" }, "btn_confirm": { "message": "Confirm" }, "btn_cancel": { "message": "Cancel" } } 中文语言包: { "account_management": { "message": "账号管理" }, "account_add": { "message": "添加账号" }, "btn_confirm": { "message": "确认" }, "btn_cancel": { "message": "取消" } } 这里需要注意: 不同语言包里的 key 要保持一致。 也就是说,英文里有: "account_add" 中文里也必须有: "account_add" 否则运行时调用这个 key 时,某些语言环境下就可能取不到翻译。 六、封装一个统一的 t 函数 虽然可以在代码里直接写: chrome.i18n.getMessage("btn_confirm") 但如果项目中大量使用这个 API,会有几个问题: 写起来比较长; 后续如果想增加 fallback 逻辑,需要到处改; popup、content script、background 都会重复使用; 不方便统一处理参数替换。 所以我在项目中封装了一个简单的工具函数。 文件位置: utils/i18n.ts 代码如下: /** * Chrome 扩展国际化工具函数 * 封装 chrome.i18n API,提供类型安全的文本获取方法 */ /** * 获取国际化文本 * @param key - messages.json 中定义的 key * @param substitutions - 可选的替换参数,字符串或字符串数组 * @returns 翻译后的文本,如果 key 不存在则返回 key 本身 */ export function t(key: string, substitutions?: string | string[]): string { return chrome.i18n.getMessage(key, substitutions) || key } 这个函数做了两件事: 第一,简化调用: t("btn_confirm") 比下面这样更简洁: chrome.i18n.getMessage("btn_confirm") 第二,增加 fallback: return chrome.i18n.getMessage(key, substitutions) || key 如果某个 key 没有找到,直接返回 key 本身。这样至少页面不会显示空字符串,调试时也很容易发现哪个 key 没配置。 七、在 React Popup 页面中使用 i18n 我的扩展主要 UI 在 popup.tsx 中,它是一个 React 页面。 首先引入工具函数: import { t } from "./utils/i18n" 然后就可以在 JSX 中使用: <button> {t("account_add")} </button> 或者用于错误提示: return ( <div className="p-4 text-red-500 text-sm"> {t("errorBoundaryMessage")} </div> ) 对应的英文语言包: { "errorBoundaryMessage": { "message": "An error occurred. Please close and reopen the extension" } } 对应的中文语言包: { "errorBoundaryMessage": { "message": "出现错误,请关闭后重新打开扩展" } } 这样,当用户浏览器语言是中文时,会显示中文;如果是英文环境,则显示英文。 八、在 Content Script 中使用 i18n 浏览器扩展不仅 popup 页面需要国际化,content script 也经常需要显示文案。 我的项目中, content.ts 会注入到 ChatGPT 或 Claude 页面中,用于检测当前登录账号。如果发现新账号,会在页面右下角显示一个提示卡片,引导用户同步账号。 同样先引入: import { t } from "./utils/i18n" 然后在创建 DOM 时使用: card.innerHTML = ` <button class="close-btn" aria-label="${t('content_closeAriaLabel')}">×</button> <div class="header"> <div class="text-content"> <h3>${t('content_newAccountTitle')}</h3> <p>${t('content_newAccountDesc', platformName)}</p> </div> </div> <div class="actions"> <button class="btn-ignore">${t('content_btnIgnore')}</button> <button class="btn-add">${t('content_btnSync')}</button> </div> ` 这里有一个带变量的文案: t("content_newAccountDesc", platformName) 比如英文里可能是: { "content_newAccountDesc": { "message": "A new $PLATFORM$ account was detected. Do you want to sync it now?", "placeholders": { "platform": { "content": "$1", "example": "ChatGPT" } } } } 中文里可以是: { "content_newAccountDesc": { "message": "检测到新的 $PLATFORM$ 账号,是否立即同步?", "placeholders": { "platform": { "content": "$1", "example": "ChatGPT" } } } } 这样在代码里只需要传入平台名称: t("content_newAccountDesc", "ChatGPT") 最终 Chrome 会自动把 $1 对应的内容替换进去。 九、在 Background 和业务逻辑中使用 i18n 除了 UI,后台逻辑里也会产生提示信息或错误信息。 比如消息处理器、同步逻辑、错误封装中,也可以统一使用: import { t } from "~utils/i18n" 或: import { t } from "./utils/i18n" 然后: throw new Error(t("error_accountMismatch")) 或者: return { success: false, error: t("error_syncFailed") } 这样有一个好处: 前端展示的错误信息和后台返回的错误信息,都可以使用同一套语言包管理。 对于浏览器扩展来说,popup、content script、background 是不同运行环境,如果不统一管理文案,后期维护会很痛苦。 十、带参数文案的写法 国际化里最常见的问题之一,就是文案里带变量。 比如: 已选中 5 项 不能简单写成: "已选中 " + count + " 项" 因为不同语言的语序可能不一样。 正确做法是放到语言包中: 英文: { "batch_selectedCount": { "message": "$COUNT$ items selected", "placeholders": { "count": { "content": "$1", "example": "5" } } } } 中文: { "batch_selectedCount": { "message": "已选中 $COUNT$ 项", "placeholders": { "count": { "content": "$1", "example": "5" } } } } 代码中调用: t("batch_selectedCount", String(selectedCount)) 如果有多个参数,也可以传数组: t("error_accountMismatch", [oldEmail, newEmail]) 语言包中可以使用: { "error_accountMismatch": { "message": "账号不匹配:当前账号是 $CURRENT$,目标账号是 $TARGET$", "placeholders": { "current": { "content": "$1", "example": "[email protected]" }, "target": { "content": "$2", "example": "[email protected]" } } } } 十一、推荐的 key 命名方式 随着项目变大,语言包会越来越长。如果 key 命名不规范,很容易混乱。 我在项目中采用了按业务模块前缀命名的方式,例如: theme_light theme_dark theme_system account_management account_add account_delete account_sync batch_manage batch_addTag batch_removeTag batch_selectedCount tag_management tag_create tag_rename tag_delete conversation_notFound conversation_total conversation_copyLink content_newAccountTitle content_newAccountDesc content_btnIgnore content_btnSync errorBoundaryMessage error_syncFailed 这种命名方式有几个优点: 一眼能看出文案属于哪个模块; 搜索方便; 不容易和其他模块冲突; 语言包比较容易维护。 我个人不太建议全部写成非常短的 key,例如: title desc button message 因为项目一大,这些 key 很快就会失去语义。 十二、Chrome i18n 的语言选择机制 使用 chrome.i18n 后,不需要我们自己判断用户语言。 Chrome 会根据用户浏览器语言自动选择语言包。 比如项目里有: locales/en/messages.json locales/zh_CN/messages.json 如果用户浏览器语言是中文简体,Chrome 会优先使用: zh_CN 如果用户浏览器语言是英文,则使用: en 如果没有找到对应语言,会回退到 manifest 中配置的: "default_locale": "en" 所以在大多数情况下,我们不需要自己写: navigator.language 也不需要自己维护语言切换逻辑。 十三、Plasmo i18n 实现流程总结 整体流程可以总结为五步。 1. 创建语言包目录 locales/ ├── en/ │ └── messages.json └── zh_CN/ └── messages.json 2. 在 package.json 中配置默认语言 { "manifest": { "default_locale": "en" } } 3. 使用 __MSG_xxx__ 国际化扩展名称和描述 { "displayName": "__MSG_extName__", "description": "__MSG_extDescription__" } 4. 封装 i18n 工具函数 export function t(key: string, substitutions?: string | string[]): string { return chrome.i18n.getMessage(key, substitutions) || key } 5. 在 popup、content script、background 中统一调用 t("btn_confirm") t("batch_selectedCount", String(count)) t("content_newAccountDesc", platformName) 十四、这种方案的优点 我目前这个项目采用的是原生 chrome.i18n 方案,而不是引入 i18next 、 react-intl 之类的第三方库。 原因很简单:对于浏览器扩展来说,原生方案已经足够好用。 它的优点包括: 1. 不需要额外依赖 不需要安装额外 npm 包,减少 bundle 体积。 2. 和 Chrome 扩展天然集成 manifest 中的名称、描述都可以直接使用 __MSG_xxx__ 。 3. popup、content script、background 都能用 只要在扩展环境中,都可以调用: chrome.i18n.getMessage() 4. 构建简单 Plasmo 会处理扩展构建,不需要自己手动生成 _locales 。 5. 维护成本低 对于中小型浏览器扩展项目,这种方案非常轻量。 十五、需要注意的坑 1. 必须配置 default_locale 如果使用了语言包,但是 manifest 没有配置: "default_locale": "en" 扩展可能无法正确加载国际化资源。 2. key 必须在所有语言包中保持一致 比如英文有: "btn_confirm" 中文也应该有: "btn_confirm" 否则在某些语言下会取不到翻译。 3. messages.json 格式不能随便写 Chrome i18n 不是普通 JSON 字典,不能写成: { "btn_confirm": "Confirm" } 必须写成: { "btn_confirm": { "message": "Confirm" } } 4. 占位符要用 $1 、 $2 比如: { "conversation_total": { "message": "$COUNT$ conversations", "placeholders": { "count": { "content": "$1", "example": "10" } } } } 代码中: t("conversation_total", "10") 5. 不建议在代码里拼接多语言句子 不推荐: t("selected") + count + t("items") 推荐: t("batch_selectedCount", String(count)) 因为不同语言的语序可能不一样。 6. content script 中使用 innerHTML 时要注意安全 如果语言包内容完全由开发者自己维护,风险较小。但如果文案来自用户输入,就不要直接拼进 innerHTML 。 我的项目中的语言包是静态文件,由开发者维护,所以可以用于构造提示卡片。但如果要插入用户输入,最好使用 textContent 或做好转义。 十六、适合继续优化的方向 目前这个方案已经可以满足我的项目需求,不过后续还可以继续优化。 1. 给 key 增加 TypeScript 类型约束 现在的 t 函数是: export function t(key: string, substitutions?: string | string[]): string 也就是说 key 是普通字符串。 如果写错了: t("btn_confim") TypeScript 不会报错,只有运行时才会发现。 后续可以自动从 messages.json 生成类型,例如: type I18nKey = | "btn_confirm" | "btn_cancel" | "account_add" | "account_delete" 然后改成: export function t(key: I18nKey, substitutions?: string | string[]): string { return chrome.i18n.getMessage(key, substitutions) || key } 这样可以在开发阶段提前发现 key 拼写错误。 2. 编写脚本检查多语言 key 是否一致 可以写一个 Node.js 脚本,对比: locales/en/messages.json locales/zh_CN/messages.json 检查两个文件的 key 是否完全一致。 比如: en 中有但 zh_CN 中没有 zh_CN 中有但 en 中没有 这对项目变大后非常有用。 3. 拆分语言包 当 messages.json 变得非常大时,可以按模块维护源文件,例如: i18n-source/ ├── account.json ├── batch.json ├── tag.json ├── content.json └── error.json 然后通过脚本合并生成最终的: locales/en/messages.json locales/zh_CN/messages.json 不过对于大多数浏览器扩展项目,一个 messages.json 也完全够用。 十七、完整示例 下面给一个简化版完整示例。 package.json { "name": "multi-ai-conversation-manager", "displayName": "__MSG_extName__", "description": "__MSG_extDescription__", "manifest": { "default_locale": "en", "permissions": [ "storage", "alarms" ] } } locales/en/messages.json { "extName": { "message": "ChatGPT & Claude AI Conversation Manager" }, "extDescription": { "message": "Unified management for ChatGPT and Claude conversations" }, "btn_confirm": { "message": "Confirm" }, "btn_cancel": { "message": "Cancel" }, "batch_selectedCount": { "message": "$COUNT$ items selected", "placeholders": { "count": { "content": "$1", "example": "5" } } } } locales/zh_CN/messages.json { "extName": { "message": "ChatGPT、Claude AI 对话管理器" }, "extDescription": { "message": "统一管理 ChatGPT、Claude 平台的 AI 对话" }, "btn_confirm": { "message": "确认" }, "btn_cancel": { "message": "取消" }, "batch_selectedCount": { "message": "已选中 $COUNT$ 项", "placeholders": { "count": { "content": "$1", "example": "5" } } } } utils/i18n.ts export function t(key: string, substitutions?: string | string[]): string { return chrome.i18n.getMessage(key, substitutions) || key } popup.tsx import { t } from "./utils/i18n" export default function Popup() { const selectedCount = 5 return ( <div> <button>{t("btn_confirm")}</button> <button>{t("btn_cancel")}</button> <p>{t("batch_selectedCount", String(selectedCount))}</p> </div> ) } 结语 在 Plasmo 浏览器扩展项目中实现 i18n,并不一定要引入复杂的国际化框架。 对于大多数 Chrome 扩展来说,直接使用浏览器原生的 chrome.i18n API 就已经足够: manifest 文案使用 __MSG_xxx__ ; 页面和脚本中使用 chrome.i18n.getMessage() ; 封装一个简单的 t() 函数; 使用 locales/en/messages.json 、 locales/zh_CN/messages.json 管理语言包; 对变量文案使用 placeholders。 这种方式简单、轻量、和浏览器扩展生态天然兼容,非常适合 Plasmo 架构下的扩展开发。 我目前的项目就是采用这种方案,同时覆盖了 popup 页面、content script、background 消息处理和错误提示。整体使用下来,维护成本低,也方便后续继续增加更多语言支持。 1 个帖子 - 1 位参与者 阅读完整话题
怎么突然国际化失败了,我记得之前这里的翻译是正常的 5 个帖子 - 3 位参与者 阅读完整话题
核心功能:json 文件翻译 核心痛点:codex/cc 翻译国际化容易偷懒 + 慢,适合多语言 10+种语言使用 支持 BYOK (Bring Your Own Key),功能完全免费,实测下载 gpt 5.4 min 、gemini 2.5 flash 速度和性价比最高 省事也可以使用网站内置 ai ,按积分消耗,现在送 30 次翻译, 不够用留下邮箱 再送 100 次翻译 https://www.youtube.com/watch?si=aQ_8kp9e2lGlF39k&v=1Nf2Y_WdP4s&feature=youtu.be 最近做 AI SaaS 出海,发现一个很头疼的问题: 网站国际化( i18n )越来越难搞了。 尤其现在大家都在用 AI 做翻译,实际用了之后会发现几个非常真实的问题: AI 很喜欢偷懒 一大段 JSON ,后面开始乱翻 key 漏翻 placeholders 被改坏 HTML / markdown 被污染 全量翻译成本太高 改了一个字段,也重新翻整个 JSON token 消耗巨大 GPT-5 / Claude 跑一次很贵 速度慢 几千行 locale 文件翻译半天 CI/CD 根本不好接 很多工具过于“重量级” 一堆平台账号 强绑定某个 AI 配置复杂 对开发者不友好 所以我自己做了个小工具: 👉 JsonTranslate.org 核心思路就几个: 增量翻译(最重要) 只翻译缺失字段。 已经翻译过的不重复消耗 token 。 这个对 AI 翻译真的太关键了。 尤其 SaaS 项目经常一天改几十次文案,如果每次全量翻译,成本直接爆炸。 ⸻ 支持自定义 AI 模型 可以自己配置: OpenAI Claude Gemini OpenRouter DeepSeek 任意兼容 OpenAI API 的模型 很多场景其实 DeepSeek / Gemini Flash 已经够用了,成本能低很多。 ⸻ 专门针对 JSON 国际化优化 做了很多脏活处理: 保持 JSON 结构 不破坏 placeholders 不污染 HTML 不修改 key 支持嵌套 JSON 避免 AI 偷懒省略 ⸻ CLI 支持 可以直接接 CI/CD: jsontranslate-cli 适合: Next.js Nuxt React Vue Expo / React Native Electron 任意 i18n JSON 项目 ⸻ 免费、轻量 没有做成那种“大而全翻译平台”。 更像开发者工具: 打开即用 不绑定工作流 功能尽量简单 ⸻ 目前自己出海项目已经在用了。
36氪获悉,东方雨虹公告,为积极推进国际化战略,公司控股子公司印尼东方雨虹拟合计出资约14263.88亿印尼卢比(折合人民币约5.42亿元),分别以增资及股权收购方式取得PT IALK 55%股权,以股权收购方式取得PT ACUM 55%股权。上述交易完成后,印尼东方雨虹将分别持有PT IALK及PT ACUM 55%的股权
36氪获悉,上海市人民政府发布关于推进服务业扩能提质的实施意见。其中提出,推动AI+广告全链路应用,加快人工智能生成内容(AIGC)创意生成、多模态内容生产等技术融合赋能,培育一批拥有核心技术和国际化能力的本土广告品牌企业。拓展人力资源管理咨询、高级人才寻访等高附加值业态,开发招聘大模型、智能面试等新产品。支持咨询服务机构拓展健康咨询、环境社会治理(ESG)咨询等新业务,发展订阅制、解决方案授权制等新模式。
作为我国债券市场高水平对外开放与人民币国际化的重要载体,熊猫债市场自2026年以来延续高景气度,规模与质效同步提升。截至5月20日,年内熊猫债发行规模近1300亿元,同比增幅超90%。伴随市场体量扩容,券商正多维度发力构建全链条服务体系,近期行业接连落地多款信用衍生品创新业务,补齐熊猫债市场风险对冲薄弱环节。(证券日报)
36氪获悉,中信建投证券研报称,2026年上半年医药行业触底回暖,创新药迎来盈利兑现期,国内商业化与海外授权双轮驱动业绩增长,多家企业扭亏。2026年一季度对外授权总额超600亿美元,ADC、双抗等前沿赛道交易持续火热。2026年ASCO年会94项中国研究中选口头报告,依沃西单抗登上全体大会,标志着中国创新药全球学术影响力迈上新台阶。国内支付端医保谈判与商保目录合力支持真创新。建议关注创新药国际化、边际改善等结构性机会。
今年一季度,我国工程机械行业国际化步伐持续加快,海外业务比重显著提升,工程机械产品出口保持快速增长。中国工程机械工业协会的最新数据显示,今年一季度,我国工程机械产品出口额160.66亿美元,同比增长24.3%。从出口产品来看,履带挖掘机、非公路矿用自卸车、电动叉车及升降平台、装载机、混凝土搅拌车等产品出口额增长较快。从出口地区来看,对各大洲的出口均呈现增长态势,其中:对非洲出口28.25亿美元,同比增长61.6%。对欧洲出口31.76亿美元,同比增长20.6%。对北美洲出口11.51亿美元,同比增长7%。(央视新闻)
今年以来,港股市场成为中资企业融资与国际化布局的关键平台,IPO数量与募资额均创下近年新高。Wind数据显示,截至5月6日,年内已经有52家公司成功登陆港股市场,募资总额约1500亿港元,同比增幅超520%。科技、消费、医疗健康及新能源等赛道是主流。(证券时报)
据广汽国际消息,延续一季度海外销售同比增长86%的强劲势能,广汽国际化征程再提速。今年1—4月,广汽累计出口量70474台,同比增幅133.9%。
4月30日,有媒体报道称,米哈游国际化总裁金雯怡发布内部信,表示自己将于5月底离开米哈游/HoYoverse,Seven(王宇阳)将接替她继续带领国际化。对此,米哈游方面回应表示:雯怡因个人职业规划原因,已向公司提出离职。公司感谢其在产品全球化发展中的付出与贡献,也祝雯怡未来发展一切顺利。据公开信息,金雯怡曾在游戏公司FunPlus任职,入职米哈游后负责海外业务。Seven(王宇阳)为大鹅文化创始人,曾担任B站副总裁。2024年,王宇阳加入米哈游联合创始人蔡浩宇成立的AI公司。(界面)
近年来,港股市场凭借其国际化定位、灵活的上市规则以及对新经济企业的包容性,持续成为内地及全球企业重要的融资阵地。然而,在IPO规模和数量攀升的同时,高破发现象也不可忽视,不少投资者频频被套。分析人士指出,港股IPO高破发背后,既有市场流动性等客观因素的影响,但同时在招股阶段也存在一定程度的“高定价”和“高包装”问题,导致定价虚高和过度包装,最终只能靠二级市场的破发来消化估值。(证券时报)
随着2025年年报披露,上市券商近期密集召开年度业绩说明会,围绕经营亮点、战略规划、股价表现等市场关注的热点问题,与投资者展开深入交流。 截至4月15日,已有东方财富、广发证券、华泰证券、招商证券、华林证券等十余家券商先后举行业绩说明会。证券时报记者梳理发现,人工智能(AI)战略落地、国际化业务布局,成为各家券商业绩说明会贯穿始终的两大核心主线。同时,中小券商差异化发展路径、投资者回报机制完善等话题,也备受关注。(证券时报)