Proxy 的 get 拦截仅支持单层属性访问,无法处理 'user.profile.name' 类动态嵌套路径;需通过逐级返回新 Proxy 实现路径累积,并在 toString/valueOf 或函数调用时触发翻译。

为什么直接用 Proxy 拦截 get 就不够用
因为翻译引擎的核心诉求不是“访问某个固定属性”,而是“根据动态路径(如 'user.profile.name')实时解析、匹配规则、查表、返回译文”。Proxy 的 get 只能捕获单层访问(比如 obj.user),无法天然处理点号分隔的嵌套路径。若强行把 'user.profile.name' 当作一个字符串键存进对象,又会失去 JS 原生属性访问的语义和工具链支持(比如 IDE 自动补全、TypeScript 类型推导)。
所以得绕一层:让 Proxy 返回的每个中间对象仍是代理,形成链式响应。关键不是“一次拦截”,而是“逐级代理 + 路径累积”。
- 每次
get不返回真实值,而是返回一个新的Proxy,并记住当前已拼接的路径(如从''→'user'→'user.profile') - 最终在
get遇到函数调用(比如tr('user.profile.name')())或触发toString/valueOf时,才真正执行路径匹配与翻译 - 必须拦截
toString和valueOf,否则像`${tr.user.profile.name}`这类模板字符串不会触发翻译逻辑
如何让 tr 对象支持任意深度路径且不报 undefined
常见错误是代理只在顶层初始化,子属性访问返回 undefined 或原始对象,导致链式中断。正确做法是:所有中间访问都返回同一个构造器生成的新代理,且携带当前路径上下文。
示例核心结构:
FastAPI + Flask 混合部署最佳实践,解决路由定义、API 代理等常见问题,适用于同时运行 FastAPI API 与 Flask 前端的场景。
function createTranslator(basePath = '') {
return new Proxy({}, {
get(target, key) {
if (key === Symbol.toPrimitive || key === 'toString' || key === 'valueOf') {
return () => translate(basePath);
}
const nextPath = basePath ? `${basePath}.${String(key)}` : String(key);
return createTranslator(nextPath);
}
});
}
const tr = createTranslator();
-
tr.user返回新代理,路径为'user';tr.user.profile返回另一个代理,路径为'user.profile' - 只要没触发
toString,就一直保持代理链,不会提前求值或报错 - 注意
String(key)是必须的——避免 Symbol 键导致路径拼接失败
路径匹配策略:正则优先还是前缀树更稳
翻译规则往往不是全等匹配,而是带通配('user.*.id')、模糊前缀('api.')、甚至动态参数('order.{id}.status')。用 switch 或 Object.keys().find() 线性遍历规则,在规则数超 50 条后性能明显下降。
- 简单场景(规则 Array.find 配合
RegExp.test(path)最直观,但注意缓存正则实例,避免重复new RegExp - 中高负载(规则 > 50 条):改用前缀树(Trie),把
'user.profile.name'拆成['user', 'profile', 'name']逐段匹配,支持'user.*'类通配只需在节点标记wildcard: true - 绝对别用
path.includes(ruleKey)—— 它会误匹配'user'到'user.profile'和'superuser'
容易被忽略的兼容性陷阱
这套机制在 Node.js 里跑得顺,但在浏览器里可能突然失效,原因往往藏在几个冷门细节里:
-
JSON.stringify(tr.user.name)会得到空对象{},因为JSON.stringify忽略代理的自定义toString,只看自有属性 —— 解决方案是给代理加toJSON方法,返回翻译结果 - Vue 3 的响应式系统(
reactive)会对代理再包一层,导致路径累积错乱 —— 必须在创建翻译器前用markRaw标记 - TypeScript 不会自动推导链式代理的类型,
tr.user.profile.name类型始终是any—— 需手动写声明文件,用declare module+ 泛型约束路径字符串字面量
最麻烦的其实是调试:Chrome 控制台默认只显示代理的 [[Handler]],看不出当前路径。建议在代理的 get 里加一句 console.debug('[tr]', basePath),并用 debugger 断点卡在 toString 触发前,才能看清真实路径流。

















