1. 从一次插件多语言翻车说起
Vscode 插件国际化多语言这件事,看起来只是把中文换成英文,真做起来坑比想象中多。我最近在做一个配置导入导出类的插件,界面文案要支持中英切换,同时插件内部还要调用大模型接口做配置校验和文案润色。结果遇到两个层面的问题:一是文案层面,vscode.l10n的bundle.l10n.json到底该放英文还是中文,我一开始理解反了,导致英文环境死活加载不出来;二是接口层面,插件里散落着好几处 API Key,测试环境、正式环境、不同模型各一份,改一次配置要翻五六个文件,多语言切换时还容易把 Key 和语言配置混在一起。
这篇文章就围绕这两个痛点展开:用 TaoToken 统一 Key 和 API 通道,把模型调用收敛到一处;同时在settings.json里搭一套可复制的配置骨架,让多语言切换和 Key 管理互不干扰。适合正在写 Vscode 插件、被l10n和配置分散折磨过的开发者。下面所有步骤都可以直接跟做,代码和配置都给了完整版本。
2. TaoToken 前置:统一 Key 与 API 通道
插件里调用大模型,最怕的就是 Key 满天飞。我的做法是把所有模型请求都指向 TaoToken 的 API 通道,插件只认一个baseURL和一个 Key,语言配置和模型配置彻底解耦。
TaoToken 在这里扮演的角色是统一的 API 入口,兼容常见的 OpenAI 风格接口,插件里用fetch或axios都能直接对接。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 根地址是 https://taotoken.net/api ,注意 API 地址后面不加任何查询参数。
你需要先拿到一个 Key。进入控制台创建 API Key,地址是 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。创建时建议按用途命名,比如vscode-plugin-dev,方便后面在插件配置里区分环境。Key 只在创建时完整显示一次,复制后先存到本地安全位置。
如果你还想在浏览器里先验证模型通不通,可以用模型对话页面快速试一条请求,地址是 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 。确认通道没问题后,再回到插件里配置。
对于长期做插件开发、需要频繁调试模型调用的场景,可以考虑 Coding Plan,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,适合把编码类请求集中管理。接入细节和参数说明看文档: https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。
3. settings.json 配置骨架:把 Key 和语言拆开
Vscode 插件的配置分两层:一层是package.json里的contributes.configuration,决定用户在设置界面能看到哪些项;另一层是用户实际的settings.json,存具体值。多语言和 Key 混在一起,就是在这两层里没分清楚。
先看package.json里的配置声明。我把它拆成三组:模型通道、语言、功能开关。模型通道只放baseURL和apiKey的引用,语言只放locale,功能开关放是否启用校验、是否启用润色。
{ "contributes": { "configuration": { "title": "My Plugin", "properties": { "myPlugin.api.baseUrl": { "type": "string", "default": "https://taotoken.net/api", "description": "模型 API 根地址" }, "myPlugin.api.apiKey": { "type": "string", "default": "", "description": "TaoToken API Key,建议用 SecretStorage 存储" }, "myPlugin.locale": { "type": "string", "enum": ["auto", "zh-cn", "en"], "default": "auto", "description": "界面语言,auto 跟随 Vscode" }, "myPlugin.feature.enableValidate": { "type": "boolean", "default": true, "description": "启用配置校验" } } } } }这里有个关键点:apiKey不要直接明文写进settings.json。Vscode 提供了SecretStorage,插件激活时把 Key 存进去,配置里只留一个占位或空值。下面是在extension.ts里读写 Key 的骨架。
import * as vscode from 'vscode'; const SECRET_KEY = 'myPlugin.apiKey'; export async function getApiKey(context: vscode.ExtensionContext): Promise<string> { const fromSecret = await context.secrets.get(SECRET_KEY); if (fromSecret) { return fromSecret; } const fromConfig = vscode.workspace .getConfiguration('myPlugin') .get<string>('api.apiKey', ''); if (fromConfig) { await context.secrets.store(SECRET_KEY, fromConfig); return fromConfig; } return ''; } export async function setApiKey( context: vscode.ExtensionContext, key: string ): Promise<void> { await context.secrets.store(SECRET_KEY, key); }用户实际settings.json里只需要这样写,语言和 Key 完全分开:
{ "myPlugin.api.baseUrl": "https://taotoken.net/api", "myPlugin.locale": "auto", "myPlugin.feature.enableValidate": true }注意baseUrl后面不要加/v1之类的路径,具体路径在请求时拼接。这样一套骨架,多语言切换只动locale,Key 走 SecretStorage,互不影响。
4. l10n 多语言配置:bundle 文件的正确姿势
回到文案层面。vscode.l10n的坑集中在bundle.l10n.json这个文件上。很多人以为它是英文翻译文件,其实它是模板文件,左边是源码里的英文原文,右边留空或由工具生成,打包时默认不加载。真正生效的是bundle.l10n.zh-cn.json这类带 locale 后缀的文件。
第一步,在package.json里声明 l10n 目录:
{ "l10n": "./l10n" }第二步,安装依赖:
npm install @vscode/l10n第三步,代码里用vscode.l10n.t包裹所有文案。注意带参数的写法,占位符用{0}:
import * as vscode from 'vscode'; vscode.window.showErrorMessage( vscode.l10n.t('Please configure working directory.') ); vscode.window.showErrorMessage( vscode.l10n.t('Packaging failed: {0}', errorMsg) ); const confirm = await vscode.window.showWarningMessage( vscode.l10n.t('Are you sure.'), { modal: true }, vscode.l10n.t('Confirm') ); if (confirm === vscode.l10n.t('Confirm')) { // 执行确认逻辑 }第四步,提取文案生成模板:
npx @vscode/l10n-dev export --outDir ./l10n ./src执行完会得到l10n/bundle.l10n.json,内容长这样:
{ "Please configure working directory.": "Please configure working directory.", "Packaging failed: {0}": "Packaging failed: {0}", "Are you sure.": "Are you sure.", "Confirm": "Confirm" }这个文件默认不加载,英文环境直接用源码里的原文。要支持中文,复制一份改名为bundle.l10n.zh-cn.json,左边保持英文不动,右边翻译:
{ "Please configure working directory.": "请先配置工作目录", "Packaging failed: {0}": "打包失败: {0}", "Are you sure.": "你确定吗?", "Confirm": "确定" }放到l10n目录下。每多一种语言就多一个bundle.l10n.<locale>.json。这里最容易踩的坑就是:左边永远是英文原文,它是键,不是翻译目标。我一开始把中文写在左边,结果英文环境调不出来,排查了半天。
5. 验证请求与多语言切换
配置搭好后,要验证两件事:模型通道通不通,多语言切不切得动。
先验证模型通道。在插件里写一个最小请求函数,指向 TaoToken 的 API:
export async function validateConfig( context: vscode.ExtensionContext, text: string ): Promise<string> { const apiKey = await getApiKey(context); const baseUrl = vscode.workspace .getConfiguration('myPlugin') .get<string>('api.baseUrl', 'https://taotoken.net/api'); const res = await fetch(`${baseUrl}/v1/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` }, body: JSON.stringify({ model: 'gpt-4o-mini', messages: [ { role: 'system', content: 'You are a config validator.' }, { role: 'user', content: text } ] }) }); if (!res.ok) { throw new Error(`Request failed: ${res.status}`); } const data = await res.json(); return data.choices[0].message.content; }调用后如果返回正常文本,说明 Key 和通道都没问题。如果返回 401,检查 Key 是否存进了 SecretStorage;返回 404,检查baseUrl是否多了路径。
再验证多语言。把 Vscode 显示语言切成中文,重启插件,vscode.l10n.t('Confirm')应该返回「确定」;切回英文,返回「Confirm」。如果中文环境没生效,检查bundle.l10n.zh-cn.json是否在l10n目录下,文件名 locale 是否和 Vscode 语言标识一致。Vscode 中文是zh-cn,不是zh-CN,大小写敏感。
一个实用的调试技巧:在插件激活时打印vscode.env.language和vscode.l10n.uri,前者告诉你当前语言,后者在非英文且有对应 bundle 时才有值。如果uri是undefined,说明 bundle 没被加载。
6. 常见错排查
报错一:Cannot find module '@vscode/l10n'依赖没装或没打进 bundle。检查package.json的dependencies里有没有@vscode/l10n,打包时确认它被包含。用 esbuild 的话,别把它标记为 external。
报错二:中文环境文案还是英文三个可能:bundle.l10n.zh-cn.json文件名不对;文件不在package.json里l10n指向的目录;Vscode 没装中文语言包。vscode.l10n.uri只有在用户语言非英文且提供了对应文件时才有值,这是设计如此,不是 bug。
报错三:带参数的文案占位符没替换vscode.l10n.t('Packaging failed: {0}', errorMsg)里占位符必须是{0}、{1}这种,不能用${}。翻译文件里也要保留{0},只翻译周围的文字。
报错四:API 请求 401Key 没读到。先确认context.secrets.get返回了值,再确认请求头Authorization格式是Bearer <key>,中间有空格。如果 Key 是从settings.json读的,检查有没有被 SecretStorage 覆盖。
报错五:API 请求 404baseUrl拼接路径错了。TaoToken 的根地址是https://taotoken.net/api,请求时拼/v1/chat/completions。如果baseUrl末尾带了/,拼接会变成双斜杠,部分服务端会 404。
报错六:多语言切换后插件没刷新Vscode 的语言切换需要重启窗口才生效。改完bundle.l10n.<locale>.json后,用Developer: Reload Window重载,不要只重启插件。
7. 下一步:把配置和调用收敛到一处
整套骨架跑通后,插件里的模型调用只剩一个入口函数,Key 走 SecretStorage,语言走 l10n bundle,settings.json里只留baseUrl、locale和功能开关。多语言切换不再牵动 Key,Key 轮换也不影响文案。
如果你要长期维护这个插件,建议把 API Key 的创建和管理固定在一个地方。控制台地址是 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite ,按环境建不同 Key,插件里通过baseUrl或环境变量区分。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,参数和错误码都列得比较清楚。
最后留一个我踩过的坑:bundle.l10n.json这个模板文件,打包时可以删掉,它不参与运行时加载。但别删bundle.l10n.zh-cn.json,那个才是真正生效的。左边英文是键,右边译文是值,记住这一点,l10n 的坑就少一半。