added translations export and import

This commit is contained in:
2026-05-27 10:53:05 +02:00
parent 9a75577f14
commit 0f89ed6d6f
3 changed files with 293 additions and 2 deletions

View File

@ -1297,6 +1297,169 @@ function qdb_import_questionnaire_bundle(PDO $pdo, array $item, bool $replaceIfE
];
}
/** One translation row for a portable translations JSON bundle. */
function qdb_translation_entry_to_bundle_row(array $e, ?string $questionnaireID = null, ?string $questionnaireName = null): array {
$tr = $e['translations'] ?? [];
if ($tr instanceof stdClass) {
$tr = (array)$tr;
}
if (is_array($tr) && isset($tr[0]) && is_array($tr[0]) && isset($tr[0]['languageCode'])) {
$tr = qdb_translation_rows_to_map($tr);
}
$row = [
'type' => $e['type'],
'entityId' => $e['entityId'],
'key' => $e['key'] ?? '',
'translations' => is_array($tr) ? $tr : [],
];
if (!empty($e['displayKey'])) {
$row['displayKey'] = $e['displayKey'];
}
if ($questionnaireID !== null && $questionnaireID !== '') {
$row['questionnaireID'] = $questionnaireID;
}
if ($questionnaireName !== null && $questionnaireName !== '') {
$row['questionnaireName'] = $questionnaireName;
}
return $row;
}
/** @return array<string, string> */
function qdb_normalize_bundle_translations($raw): array {
if (!is_array($raw)) {
return [];
}
if (isset($raw[0]) && is_array($raw[0]) && isset($raw[0]['languageCode'])) {
return qdb_translation_rows_to_map($raw);
}
$map = [];
foreach ($raw as $lang => $text) {
if (is_string($lang) && $lang !== '') {
$map[$lang] = (string)$text;
}
}
return $map;
}
function qdb_translation_entity_exists(PDO $pdo, string $type, string $entityId): bool {
if ($type === 'string' || $type === 'app_string') {
return true;
}
if ($type === 'question') {
$stmt = $pdo->prepare('SELECT 1 FROM question WHERE questionID = :id LIMIT 1');
} elseif ($type === 'answer_option') {
$stmt = $pdo->prepare('SELECT 1 FROM answer_option WHERE answerOptionID = :id LIMIT 1');
} else {
return false;
}
$stmt->execute([':id' => $entityId]);
return (bool)$stmt->fetchColumn();
}
/** Export all translations (languages + flat entries) as portable JSON. */
function qdb_export_translations_bundle(PDO $pdo): array {
$languages = $pdo->query('SELECT languageCode, name FROM language ORDER BY languageCode')
->fetchAll(PDO::FETCH_ASSOC);
if (!array_filter($languages, fn($l) => $l['languageCode'] === QDB_SOURCE_LANGUAGE)) {
array_unshift($languages, ['languageCode' => QDB_SOURCE_LANGUAGE, 'name' => 'German']);
}
qdb_ensure_source_language($pdo);
$exportEntries = [];
$appEntries = qdb_app_ui_string_entries_for_website();
$appTranslations = qdb_load_translations_for_entries($pdo, $appEntries);
qdb_attach_translation_texts($appEntries, $appTranslations);
foreach ($appEntries as $e) {
$exportEntries[] = qdb_translation_entry_to_bundle_row($e);
}
$qnRows = $pdo->query('SELECT questionnaireID, name FROM questionnaire ORDER BY orderIndex, name')
->fetchAll(PDO::FETCH_ASSOC);
foreach ($qnRows as $qn) {
$lists = qdb_translation_entry_lists($pdo, $qn['questionnaireID']);
$entries = array_merge($lists['stringEntries'], $lists['contentEntries']);
if (!$entries) {
continue;
}
$translations = qdb_load_translations_for_entries($pdo, $entries);
qdb_attach_translation_texts($entries, $translations);
foreach ($entries as $e) {
$exportEntries[] = qdb_translation_entry_to_bundle_row(
$e,
$qn['questionnaireID'],
$qn['name']
);
}
}
return [
'exportVersion' => 1,
'exportedAt' => time(),
'sourceLanguage' => QDB_SOURCE_LANGUAGE,
'languageCount' => count($languages),
'languages' => $languages,
'entryCount' => count($exportEntries),
'entries' => $exportEntries,
];
}
/** Import translations bundle (upsert languages and translation rows). */
function qdb_import_translations_bundle(PDO $pdo, array $bundle): array {
$entries = $bundle['entries'] ?? null;
if (!is_array($entries)) {
json_error('MISSING_FIELDS', 'bundle.entries array is required', 400);
}
qdb_ensure_source_language($pdo);
foreach ($bundle['languages'] ?? [] as $langRow) {
if (!is_array($langRow)) {
continue;
}
$lc = trim($langRow['languageCode'] ?? '');
$name = trim($langRow['name'] ?? '');
if ($lc === '' || $lc === QDB_SOURCE_LANGUAGE) {
continue;
}
$pdo->prepare('INSERT INTO language (languageCode, name) VALUES (:lc, :n)
ON CONFLICT(languageCode) DO UPDATE SET name = excluded.name')
->execute([':lc' => $lc, ':n' => $name]);
}
$validTypes = ['question', 'answer_option', 'string', 'app_string'];
$imported = 0;
$skipped = 0;
foreach ($entries as $entry) {
if (!is_array($entry)) {
$skipped++;
continue;
}
$type = $entry['type'] ?? '';
$entityId = trim((string)($entry['entityId'] ?? ''));
if (!in_array($type, $validTypes, true) || $entityId === '') {
$skipped++;
continue;
}
if (!qdb_translation_entity_exists($pdo, $type, $entityId)) {
$skipped++;
continue;
}
$map = qdb_normalize_bundle_translations($entry['translations'] ?? []);
if ($map) {
qdb_apply_translation_map($pdo, $type, $entityId, $map);
$imported++;
} else {
$skipped++;
}
}
return [
'imported' => $imported,
'skipped' => $skipped,
'total' => count($entries),
];
}
/** Import a full export bundle. */
function qdb_import_questionnaires_bundle(PDO $pdo, array $bundle, bool $replaceIfExists = false): array {
$items = $bundle['questionnaires'] ?? [];