含rxjs.Observable属性的复杂JS对象指定字段翻译方案问询
这个场景我之前做国际化的时候碰到过,刚好有几套成熟的方案可以给你参考,分同步、异步以及带RxJS Observable的情况来说:
1. 纯同步对象的递归翻译方案
如果你的翻译逻辑是同步的(比如从本地字典取值),直接用递归遍历整个对象就行,思路就是:遍历每个键值对,遇到字符串且以translate:开头就替换成翻译后内容;遇到嵌套对象/数组就递归处理。
function translateObject(obj, translateFn) { // 处理非对象/空值 if (!obj || typeof obj !== 'object') { if (typeof obj === 'string' && obj.startsWith('translate:')) { const key = obj.replace('translate:', ''); return translateFn(key); } return obj; } // 处理数组 if (Array.isArray(obj)) { return obj.map(item => translateObject(item, translateFn)); } // 处理普通对象 const translatedObj = {}; for (const [key, value] of Object.entries(obj)) { translatedObj[key] = translateObject(value, translateFn); } return translatedObj; } // 示例翻译函数,替换成你的实际逻辑 function mockTranslate(key) { const translations = { 'julius.amazing.bio': 'Julius Caesar was a Roman general and statesman.', 'coliseum.amazing.bio': 'A historic amphitheater located in the center of Rome.' }; return translations[key] || key; } // 测试使用 const originalObj = { "@context": "http://schema.org", "@type": "Celebrity", "name": "Julius Caesar", "description": "translate:julius.amazing.bio", "address": { "@type": "PostalAddress", "addressLocality": "Roma", "postalCode": "9999", "streetAddress": "Coliseum Street 1", "addressCountry": "IT", "description": "translate:coliseum.amazing.bio" } }; const translatedObj = translateObject(originalObj, mockTranslate); console.log(translatedObj);
2. 异步翻译场景(比如调用后端接口)
如果翻译需要异步请求,把递归函数改成async/await版本即可:
async function translateObjectAsync(obj, translateFn) { if (!obj || typeof obj !== 'object') { if (typeof obj === 'string' && obj.startsWith('translate:')) { const key = obj.replace('translate:', ''); return await translateFn(key); } return obj; } if (Array.isArray(obj)) { return Promise.all(obj.map(item => translateObjectAsync(item, translateFn))); } const translatedObj = {}; for (const [key, value] of Object.entries(obj)) { translatedObj[key] = await translateObjectAsync(value, translateFn); } return translatedObj; } // 异步翻译示例(模拟接口请求) async function asyncMockTranslate(key) { const translations = { 'julius.amazing.bio': 'Julius Caesar was a Roman general and statesman.', 'coliseum.amazing.bio': 'A historic amphitheater located in the center of Rome.' }; return new Promise(resolve => setTimeout(() => resolve(translations[key] || key), 100)); } // 使用方式 translateObjectAsync(originalObj, asyncMockTranslate).then(translated => { console.log(translated); });
3. 包含RxJS Observable属性的处理方案
针对你提到的对象包含rxjs.Observable属性的情况,我们可以结合RxJS的操作符来处理,把整个对象转换成Observable,确保所有异步流都完成后返回翻译后的对象:
import { of, forkJoin, isObservable } from 'rxjs'; import { map } from 'rxjs/operators'; function translateObjectWithObservables(obj, translateFn) { // 处理Observable if (isObservable(obj)) { return obj.pipe( map(value => { if (typeof value === 'string' && value.startsWith('translate:')) { const key = value.replace('translate:', ''); return translateFn(key); } return value; }) ); } // 处理非对象/空值 if (!obj || typeof obj !== 'object') { if (typeof obj === 'string' && obj.startsWith('translate:')) { const key = obj.replace('translate:', ''); return of(translateFn(key)); } return of(obj); } // 处理数组 if (Array.isArray(obj)) { const observables = obj.map(item => translateObjectWithObservables(item, translateFn)); return forkJoin(observables); } // 处理普通对象 const entryObservables = Object.entries(obj).map(([key, value]) => { return translateObjectWithObservables(value, translateFn).pipe( map(translatedValue => [key, translatedValue]) ); }); return forkJoin(entryObservables).pipe( map(entries => Object.fromEntries(entries)) ); } // 测试包含Observable的对象 const objWithObservable = { name: 'Julius Caesar', description: of('translate:julius.amazing.bio'), address: { description: of('translate:coliseum.amazing.bio') } }; translateObjectWithObservables(objWithObservable, mockTranslate).subscribe(translatedObj => { console.log(translatedObj); });
4. 边缘情况:处理循环引用
如果你的对象存在循环引用(比如A的属性指向B,B的属性又指向A),递归会导致栈溢出,这时候可以用WeakMap记录已处理的对象:
function translateObject(obj, translateFn, processed = new WeakMap()) { // 避免循环引用重复处理 if (processed.has(obj)) { return processed.get(obj); } if (!obj || typeof obj !== 'object') { if (typeof obj === 'string' && obj.startsWith('translate:')) { const key = obj.replace('translate:', ''); return translateFn(key); } return obj; } // 先标记为已处理 processed.set(obj, obj); let translatedObj; if (Array.isArray(obj)) { translatedObj = obj.map(item => translateObject(item, translateFn, processed)); } else { translatedObj = {}; for (const [key, value] of Object.entries(obj)) { translatedObj[key] = translateObject(value, translateFn, processed); } } // 更新为处理后的对象 processed.set(obj, translatedObj); return translatedObj; }
这些方案可以根据你的实际场景灵活组合,比如如果既有异步翻译又有Observable,就把translateFn改成返回Observable,然后在RxJS版本里调用即可。
内容的提问来源于stack exchange,提问作者Starscream
相关产品推荐
相关产品推荐

