Translation Strings
The five endpoints that search, edit, remove and move translation strings.
Overview
Translation strings live in files rather than the database. Every string has a full key, and that key also says which file holds it, as in needs/button-save.
The head of the key places a string in one of three sets: the client side, the admin panel and the system text left over. A search result comes back grouped under those three.
There are two ways to write, and they differ on purpose. The bulk edit is strict: seeing one key it does not know, it writes nothing. The import is forgiving: it skips what it does not know and writes the rest.
Reference
Searching the Translations
Returns the translation strings carrying your text, in groups.
curl 'https://panel.example.com/api/v1/admin/languages/de/translations?search=Save' \
-H "Authorization: Bearer $API_KEY"const url = new URL(`https://panel.example.com/api/v1/admin/languages/${key}/translations`);
url.searchParams.set('search', term);
const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const { data, meta } = await res.json();
if (meta.truncated) narrowTheSearch();$qs = http_build_query(['search' => $term]);
$ch = curl_init('https://panel.example.com/api/v1/admin/languages/' . $key . '/translations?' . $qs);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The search is CASE SENSITIVE: 'save' and 'Save' do not give the same result.
$r = Api::Languages()->GetLanguageTranslations(['key' => $key], ['search' => 'Save']);
if ($r['meta']['truncated']) $logger->info('the result stopped at a hundred — narrow the search');Editing Translations in Bulk
Writes the values of the keys you give and leaves the rest alone.
curl -X PUT 'https://panel.example.com/api/v1/admin/languages/de/translations' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"translations":{"needs/button-save":"Speichern"}}'const res = await fetch(`https://panel.example.com/api/v1/admin/languages/${key}/translations`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
translations: {
'needs/button-save': 'Speichern',
'admin/services/page-list': 'Dienste',
},
}),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/languages/' . $key . '/translations');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'translations' => ['needs/button-save' => 'Speichern'],
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// ONE invalid key turns the whole batch down; pick the import endpoint for bulk loading.
Api::Languages()->UpdateLanguageTranslations([
'key' => $key, 'translations' => ['needs/button-save' => 'Speichern'],
]);Removing One Translation
Takes one translation string out of its file.
curl -X DELETE 'https://panel.example.com/api/v1/admin/languages/de/translations' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"key":"needs/button-save"}'const res = await fetch(`https://panel.example.com/api/v1/admin/languages/${lang}/translations`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ key: 'needs/button-save' }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/languages/' . $lang . '/translations');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['key' => 'needs/button-save']),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// A screen asking for a removed key prints EMPTY text; consider correcting rather than clearing it.
Api::Languages()->UpdateLanguageTranslations([
'key' => $lang, 'translations' => ['needs/button-save' => 'Speichern'],
]);Exporting the Translations
Returns every translation string of a language as one flat map.
curl 'https://panel.example.com/api/v1/admin/languages/de/translations/export' \
-H "Authorization: Bearer $API_KEY" > de.jsonconst res = await fetch(`https://panel.example.com/api/v1/admin/languages/${key}/translations/export`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const { data, meta } = await res.json();
console.log(`${meta.count} dize`);$ch = curl_init('https://panel.example.com/api/v1/admin/languages/' . $key . '/translations/export');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The output is JSON and NOT a spreadsheet; the sheet format the panel offers is absent here.
$dump = Api::Languages()->ExportLanguageTranslations(['key' => $key])['data'];
file_put_contents("$key.json", json_encode($dump, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));Importing the Translations
Writes a translation map in bulk and skips the keys it does not know.
curl -X POST 'https://panel.example.com/api/v1/admin/languages/de/translations/import' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"translations":{"needs/button-save":"Speichern"}}'const res = await fetch(`https://panel.example.com/api/v1/admin/languages/${key}/translations/import`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ translations: map }),
});
const { data } = await res.json();
if (data.skipped_files.length) reportSkipped(data.skipped_files);$ch = curl_init('https://panel.example.com/api/v1/admin/languages/' . $key . '/translations/import');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['translations' => $map]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// A key it does not know is SKIPPED quietly; without counting them a missing translation goes unseen.
$out = Api::Languages()->ImportLanguageTranslations(['key' => $key, 'translations' => $map])['data'];
if ($out['skipped_files']) $logger->warning('skipped key', $out['skipped_files']);Pitfalls
The bulk edit turns down the whole batch over one invalid key and writes nothing. The import skips that key quietly and writes the rest. Pick the first for careful editing and the second when loading an outside file.
The import lists what it skipped under skipped_files and says so nowhere else. A script that does not read the answer never learns it left part of the translations unwritten. Check that field on every run.
The search stops at the hundredth match and the total field gives what was gathered rather than what exists. A true truncated means there is more and how much stays unknown. Do not tie that number to a progress readout.
The search text is matched as it stands: looking for save does not find the string Save. Search each spelling of a word on its own when replacing text across the installation.
On the removal endpoint the address carries the language key and the body the translation key. Both go by the name key. Writing the language key into the body gives an invalid translation key error, which is not the language going missing.
The spreadsheet output and the remote pull that the panel offers are absent here; the export returns a flat map and the import expects the same map. The two mirror each other, so a language exported from one installation loads into another as it stands.
Related Articles
Thanks for your feedback!
Our support team is here around the clock for anything you can't find above.