I'd like to use DeepL to translate my Laravel Post model. The problem is, their API requires that we submit a flat array of translations. E.g. our payload would be -
{
"target_lang": "ES",
"text": [
"The post title",
"A quote goes here (translatable)",
"<p>A content block goes here (translatable)</p>",
"A quote goes here",
"<p>A content block goes here (translatable)<\/p>"
]
}
However, my actual post uses a 'content' array to define blocks of content, like below:
[
{
"type": "quote",
"data": {
"name": "A quote name",
"quote": "A quote goes here (translatable)"
}
},
{
"type": "content",
"data": {
"content": "<p>A content block goes here (translatable)<\/p>"
}
},
{
"type": "quote",
"data": {
"name": "A quote name (not translatable)",
"quote": "A quote goes here"
}
},
{
"type": "content",
"data": {
"content": "<p>A content block goes here (translatable)<\/p>"
}
}
]
The data above is very flexible and changeable but always hinges on the block type. I can easily flatten the data down using something like below:
public function translatableAttributes(): array
{
return [
$this->title,
...collect($this->content)
->mapWithKeys(fn (array &$content) => match ($content['type']) {
'quote' => $content['data']['quote'],
'content' => $content['data']['content'],
})->flatten()->toArray(),
];
}
But the big issue I'm having is pushing that back into the array. I just can't think of a way to do this while maintaining its flexibility.
I've thought of perhaps implementing promises and then resolving them for each key but again can't think how to implement this.
I could make separate API calls but again this is very inefficient when I can translate 50 strings at a time in one API call.
I would suggest getting by with a simple array_map()
public function translatableAttributes(): array
{
return [
$this->title,
...array_map(
fn($content) => $content['data'][$content['type']],
$this->content,
),
];
}
public function applyTranslations(array $translations): void
{
$this->title = $translations[0];
$this->content = array_map(
static function ($content, $translation) {
$content['data'][$content['type']] = $translation;
return $content;
},
$this->content,
array_slice($translations, 1),
);
}
And more complex, but more flexible (if, for example, there are several fields for translation in one content; then we will need to use foreach
in getTranslatableKeys())
public function translatableAttributes(): array
{
return [
$this->title,
...array_map(
fn($key) => Arr::get($this->content, $key),
$this->getTranslatableKeys(),
),
];
}
private function getTranslatableKeys(): array
{
return array_map(
fn($content, $index) => $index.'.data.'.$content['type'],
$this->content,
array_keys($this->content),
);
}
public function applyTranslations(array $translations): void
{
$this->title = $translations[0];
foreach ($this->getTranslatableKeys() as $index => $key) {
Arr::set($this->content, $key, $translations[$index + 1]);
}
}