I have two objects that represent translations for two different languages. They are exactly the same in structure but the values have been translated.
English
{
about: {
title: "About",
subtitle: "Something",
table: {
columns: [...],
}
},
products: {
columns: [...]
},
games: {
details: {
title: "Game Details",
columns: [...]
}
}
}
French
{
about: {
title: "À propos de",
subtitle: "Quelque chose",
table: {
columns: [...],
}
},
products: {
columns: [...]
},
games: {
details: {
title: "Détails du jeu",
columns: [...]
}
}
}
I want to retain the French object as is, but replace all instances of columns
with the English version from the first object. How can I do that?
The objects I'm using are quite big and deeply nested, so I imagine I need some kind of recursive function. I'm not sure how I can keep track of which key I'm on to do the replacement though.
concrete example
First we establish a well-defined example, filling in some values for en
columns -
let en =
{
about: {
title: "About",
subtitle: "Something",
table: {
columns: ["en_about_1", "en_about_2"] // <-
}
},
products: {
columns: ["en_products"] // <-
},
games: {
details: {
title: "Game Details",
columns: ["en_games_1", "en_games_2"] // <-
}
}
}
And we do the same for fr
-
let fr =
{
about: {
title: "À propos de",
subtitle: "Quelque chose",
table: {
columns: ["fr_apropos_1", "fr_apropos_2"], // <-
}
},
products: {
columns: ["fr_produit"] // <-
},
games: {
details: {
title: "Détails du jeu",
columns: ["fr_details_1", "fr_details_2"] // <-
}
}
}
traverse
Next we need a way to traverse all paths
in a given object -
function* paths (t)
{ switch(t?.constructor)
{ case Object:
for (const [k,v] of Object.entries(t))
for (const path of paths(v))
yield [k, ...path]
break
default:
yield []
}
}
let fr =
{about: {title: "À propos de",subtitle: "Quelque chose",table: {columns: ["fr_apropos_1", "fr_apropos_2"],}},products: {columns: ["fr_produit"]},games: {details: {title: "Détails du jeu",columns: ["fr_details_1", "fr_details_2"]}}}
for (const path of paths(fr))
console.log(JSON.stringify(path))
["about","title"]
["about","subtitle"]
["about","table","columns"]
["products","columns"]
["games","details","title"]
["games","details","columns"]
read and write
Next we'll need a way to read and write values from one object to another -
getAt
which takes an object and a path, and returns a valuesetAt
which takes an object, a path, and a value, and sets a valuefunction getAt (t, [k, ...path])
{ if (k == null)
return t
else
return getAt(t?.[k], path)
}
function setAt (t, [k, ...path], v)
{ if (k == null)
return v
else
return {...t, [k]: setAt(t?.[k] ?? {}, path, v) }
}
copy at path
For each path
of fr
, where the path ends in "columns"
, update fr
at path
with the value from en
at path
-
for (const path of paths(fr)) // for each path of fr
if (path.slice(-1)[0] == "columns") // where the path ends in "columns"
fr = setAt(fr, path, getAt(en, path)) // update fr at path with value from en at path
console.log(JSON.stringify(fr))
Expand the snippet below and verify the results in your own browser -
function* paths (t)
{ switch(t?.constructor)
{ case Object:
for (const [k,v] of Object.entries(t))
for (const path of paths(v))
yield [k, ...path]
break
default:
yield []
}
}
function getAt (t, [k, ...path])
{ if (k == null)
return t
else
return getAt(t?.[k], path)
}
function setAt (t, [k, ...path], v)
{ if (k == null)
return v
else
return {...t, [k]: setAt(t?.[k] ?? {}, path, v) }
}
let en =
{about: {title: "About",subtitle: "Something",table: {columns: ["en_about_1", "en_about_2"]}},products: {columns: ["en_products"]},games: {details: {title: "Game Details",columns: ["en_games_1", "en_games_2"]}}}
let fr =
{about: {title: "À propos de",subtitle: "Quelque chose",table: {columns: ["fr_apropos_1", "fr_apropos_2"],}},products: {columns: ["fr_produit"]},games: {details: {title: "Détails du jeu",columns: ["fr_details_1", "fr_details_2"]}}}
for (const path of paths(fr))
if (path.slice(-1)[0] == "columns")
fr = setAt(fr, path, getAt(en, path))
console.log(JSON.stringify(fr, null, 2))
{
"about": {
"title": "À propos de",
"subtitle": "Quelque chose",
"table": {
"columns": [ // <-
"en_about_1",
"en_about_2"
]
}
},
"products": {
"columns": [ // <-
"en_products"
]
},
"games": {
"details": {
"title": "Détails du jeu",
"columns": [ // <-
"en_games_1",
"en_games_2"
]
}
}
}
All en
values are copied to fr
for each "columns"
.