Reputation: 1148
I have a complex javascript object, such as:
{
"@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"
}
}
The depth of the object is pretty much unlimited. Now some of the fields must be translated. In the example above, it would be name
and address.description
.
My translation function has the following signature:
get(key: string): Observable<string>;
The takeaway here is that it returns an Observable. The goal is to translate fields starting with translate:
and return the object with the exact same structure.
The solution I could come up with is to forkJoin
all translated fields and update the object once done.
const obj = {
'@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 cesar$ = this.translate.get('julius.amazing.bio');
const coliseum$ = this.translate.get('coliseum.amazing.bio');
forkJoin([cesar$, coliseum$]).subscribe(res => {
obj.description = res[0];
obj.address.description = res[1];
});
It's unelegant, not flexible and code needs to be rewritten for every object I have. Any suggestions on how to accomplish this in an elegant and reusable way (with any object depth), allowing me to translate any field starting with translate:
?
Upvotes: 0
Views: 956
Reputation: 6312
as @estus commented, you can traverse source object recursively to collect all strings to be translated, and then to translate all of them, something like this:
const translatePrefix = 'translate:';
// to return array of pairs:
// [string, function to apply result on object]
function getFields(obj) {
const fields = [];
Object.keys(obj).forEach(key => {
const val = obj[key];
if (typeof val === 'string' && val.startsWith(translatePrefix)) {
fields.push([
val.substr(translatePrefix.length),
(translation, resultObj) => {resultObj[key] = translation;},
]);
} else if (typeof val === 'object') {
fields.push(... getFields(val).map(([toTranslate, apply]) => [
toTranslate,
(translation, resultObj) => {apply(translation, resultObj[key]);},
]));
}
});
return fields;
}
function translateObj(obj) {
return forkJoin(
getFields(obj).map(
([str, apply]) => translate.get(str)
.pipe(map(translation => apply.bind(translation)))
)
).pipe(
map(translationUpdates => {
// clone obj to not modify source object (quick and dirty way)
const res = JSON.parse(JSON.stringify(obj));
// apply translations to it
translationUpdates.forEach(apply => apply(res));
return res;
})
);
}
Upvotes: 1
Reputation: 249636
You can traverse the whole object recursively and translate the properties that need translating. You will then need to collect all the observables in an array so as to be able to use forkJoin
to have an observable for when all the translations finish.
My quick stab at it, can probably be improved:
import { delay, map} from 'rxjs/operators';
import { forkJoin } from 'rxjs/observable/forkJoin';
import { of } from 'rxjs/observable/of';
import { Observable } from 'rxjs/Observable';
const obj = {
'@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'
}
};
let translate = {
get(value: string) {
return of("transalted " + value).pipe(delay(1000));
}
}
const translatePrefix = "translate:"
function translateAll<T>(obj:T) {
let obs : Observable<void>[] = []
for(let [key, value] of Object.entries(obj)) {
if(typeof value === "object") {
let o = translateAll(value).pipe(map(v=> { obj[key] = v }))
obs.push(o);
}
else if(typeof value === "string") {
if(!value.startsWith(translatePrefix)) continue;
var translationKey = value.substr(translatePrefix.length);
let o = translate.get(translationKey).pipe(map(v=> { obj[key] = v }));
obs.push(o);
}
}
return forkJoin(obs).pipe(map(v=> obj));
}
translateAll(obj).subscribe(v=>
{
console.log(v)
})
Upvotes: 3