added app translations for better maintainability
This commit is contained in:
208
common.php
208
common.php
@ -212,19 +212,167 @@ function qdb_ensure_source_language(PDO $pdo): void {
|
||||
}
|
||||
|
||||
function qdb_upsert_source_translation(PDO $pdo, string $type, string $id, string $text): void {
|
||||
qdb_ensure_source_language($pdo);
|
||||
$lang = QDB_SOURCE_LANGUAGE;
|
||||
qdb_put_translation($pdo, $type, $id, QDB_SOURCE_LANGUAGE, $text);
|
||||
}
|
||||
|
||||
/** Catalog of global app UI string keys (LanguageManager). */
|
||||
function qdb_app_string_catalog(): array {
|
||||
static $catalog = null;
|
||||
if ($catalog !== null) {
|
||||
return $catalog;
|
||||
}
|
||||
$path = __DIR__ . '/data/app_ui_strings.json';
|
||||
if (!is_readable($path)) {
|
||||
$catalog = ['keys' => [], 'germanDefaults' => []];
|
||||
return $catalog;
|
||||
}
|
||||
$data = json_decode(file_get_contents($path), true);
|
||||
$catalog = is_array($data) ? $data : ['keys' => [], 'germanDefaults' => []];
|
||||
return $catalog;
|
||||
}
|
||||
|
||||
function qdb_app_string_key_set(): array {
|
||||
$keys = qdb_app_string_catalog()['keys'] ?? [];
|
||||
return array_flip($keys);
|
||||
}
|
||||
|
||||
/** Keys for dev-only UI (database export, etc.) — excluded from website App UI and app sync catalog. */
|
||||
function qdb_dev_only_app_string_key_set(): array {
|
||||
static $set = null;
|
||||
if ($set !== null) {
|
||||
return $set;
|
||||
}
|
||||
$set = array_flip([
|
||||
'database',
|
||||
'database_clients_title',
|
||||
'download_header',
|
||||
'export_success_downloads',
|
||||
'export_failed',
|
||||
'headers',
|
||||
'view_missing',
|
||||
'no_header_template_found',
|
||||
'saved_pdf_csv',
|
||||
'no_pdf_viewer',
|
||||
'save_error',
|
||||
'no_clients_available',
|
||||
'questionnaire_id',
|
||||
'questionnaires',
|
||||
'status',
|
||||
'data',
|
||||
'data_final_warning',
|
||||
'open_client_via_load',
|
||||
'ask_before_upload',
|
||||
'You have completed questionnaires that have not been uploaded yet.',
|
||||
'You have completed questionnaires that have not been uploaded yet. Tap to open the app and upload.',
|
||||
]);
|
||||
return $set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Global app UI strings (screens, toasts, auth, etc.) for the mobile app.
|
||||
* @return list<array{key: string, type: string, entityId: string, sortOrder: int}>
|
||||
*/
|
||||
function qdb_app_ui_string_entries(): array {
|
||||
$catalog = qdb_app_string_catalog();
|
||||
$defaults = $catalog['germanDefaults'] ?? [];
|
||||
$entries = [];
|
||||
$order = 0;
|
||||
foreach ($catalog['keys'] ?? [] as $key) {
|
||||
$entries[] = [
|
||||
'key' => $key,
|
||||
'type' => 'app_string',
|
||||
'entityId' => $key,
|
||||
'sortOrder' => $order++,
|
||||
'germanDefault' => $defaults[$key] ?? $key,
|
||||
];
|
||||
}
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* All string keys referenced by questionnaire content (questions, options, config).
|
||||
* @return array<string, true>
|
||||
*/
|
||||
function qdb_all_questionnaire_translation_key_set(PDO $pdo): array {
|
||||
static $cache = null;
|
||||
if ($cache !== null) {
|
||||
return $cache;
|
||||
}
|
||||
|
||||
$keys = [];
|
||||
$rows = $pdo->query("SELECT defaultText, configJson FROM question")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($rows as $dbQ) {
|
||||
if ($dbQ['defaultText'] !== '') {
|
||||
$keys[$dbQ['defaultText']] = true;
|
||||
}
|
||||
$config = json_decode($dbQ['configJson'] ?: '{}', true) ?: [];
|
||||
foreach (['textKey', 'textKey1', 'textKey2', 'hint', 'hint1', 'hint2'] as $field) {
|
||||
if (!empty($config[$field])) {
|
||||
$keys[$config[$field]] = true;
|
||||
}
|
||||
}
|
||||
if (!empty($config['symptoms']) && is_array($config['symptoms'])) {
|
||||
foreach ($config['symptoms'] as $s) {
|
||||
if ($s !== '') {
|
||||
$keys[$s] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($config['options']) && is_array($config['options'])) {
|
||||
foreach ($config['options'] as $opt) {
|
||||
if (is_string($opt) && $opt !== '') {
|
||||
$keys[$opt] = true;
|
||||
} elseif (is_array($opt) && !empty($opt['key'])) {
|
||||
$keys[$opt['key']] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($pdo->query("SELECT defaultText FROM answer_option")->fetchAll(PDO::FETCH_COLUMN) as $text) {
|
||||
if ($text !== '') {
|
||||
$keys[$text] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$cache = $keys;
|
||||
return $cache;
|
||||
}
|
||||
|
||||
/** App UI catalog entries for coaches (excludes dev-only and questionnaire-scoped keys). */
|
||||
function qdb_app_ui_string_entries_global_only(PDO $pdo): array {
|
||||
$used = qdb_all_questionnaire_translation_key_set($pdo);
|
||||
$dev = qdb_dev_only_app_string_key_set();
|
||||
$entries = [];
|
||||
foreach (qdb_app_ui_string_entries() as $e) {
|
||||
if (!isset($used[$e['key']]) && !isset($dev[$e['key']])) {
|
||||
$entries[] = $e;
|
||||
}
|
||||
}
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/** Upsert one translation row; sync German text into defaultText for questions/options. */
|
||||
function qdb_put_translation(PDO $pdo, string $type, string $id, string $lang, string $text): void {
|
||||
if ($type === 'question') {
|
||||
$pdo->prepare("INSERT INTO question_translation (questionID, languageCode, text)
|
||||
VALUES (:id, :lang, :t)
|
||||
ON CONFLICT(questionID, languageCode) DO UPDATE SET text = excluded.text")
|
||||
->execute([':id' => $id, ':lang' => $lang, ':t' => $text]);
|
||||
if ($lang === QDB_SOURCE_LANGUAGE) {
|
||||
$pdo->prepare("UPDATE question SET defaultText = :t WHERE questionID = :id")
|
||||
->execute([':t' => $text, ':id' => $id]);
|
||||
}
|
||||
} elseif ($type === 'answer_option') {
|
||||
$pdo->prepare("INSERT INTO answer_option_translation (answerOptionID, languageCode, text)
|
||||
VALUES (:id, :lang, :t)
|
||||
ON CONFLICT(answerOptionID, languageCode) DO UPDATE SET text = excluded.text")
|
||||
->execute([':id' => $id, ':lang' => $lang, ':t' => $text]);
|
||||
} elseif ($type === 'string') {
|
||||
if ($lang === QDB_SOURCE_LANGUAGE) {
|
||||
$pdo->prepare("UPDATE answer_option SET defaultText = :t WHERE answerOptionID = :id")
|
||||
->execute([':t' => $text, ':id' => $id]);
|
||||
}
|
||||
} elseif ($type === 'string' || $type === 'app_string') {
|
||||
$pdo->prepare("INSERT INTO string_translation (stringKey, languageCode, text)
|
||||
VALUES (:id, :lang, :t)
|
||||
ON CONFLICT(stringKey, languageCode) DO UPDATE SET text = excluded.text")
|
||||
@ -311,7 +459,14 @@ function qdb_attach_translation_texts(array &$entries, array $translations): voi
|
||||
$tr = $translations[$e['entityId']] ?? [];
|
||||
$e['translations'] = (object)$tr;
|
||||
$de = trim($tr[QDB_SOURCE_LANGUAGE] ?? '');
|
||||
$e['germanText'] = $de !== '' ? $de : $e['key'];
|
||||
if ($de !== '') {
|
||||
$e['germanText'] = $de;
|
||||
} elseif (!empty($e['germanDefault'])) {
|
||||
$e['germanText'] = $e['germanDefault'];
|
||||
} else {
|
||||
$e['germanText'] = $e['key'];
|
||||
}
|
||||
unset($e['germanDefault']);
|
||||
}
|
||||
unset($e);
|
||||
}
|
||||
@ -347,7 +502,7 @@ function qdb_load_translations_for_entries(PDO $pdo, array $entries): array {
|
||||
}
|
||||
|
||||
$sKeys = array_values(array_filter(array_map(
|
||||
fn($e) => $e['type'] === 'string' ? $e['entityId'] : null,
|
||||
fn($e) => ($e['type'] === 'string' || $e['type'] === 'app_string') ? $e['entityId'] : null,
|
||||
$entries
|
||||
)));
|
||||
if ($sKeys) {
|
||||
@ -362,6 +517,49 @@ function qdb_load_translations_for_entries(PDO $pdo, array $entries): array {
|
||||
return $translations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flat language -> stringKey -> text map for global app UI strings only.
|
||||
* @return array<string, array<string, string>>
|
||||
*/
|
||||
function qdb_build_app_translations_map(PDO $pdo): array {
|
||||
$entries = qdb_app_ui_string_entries();
|
||||
$translations = qdb_load_translations_for_entries($pdo, $entries);
|
||||
$defaults = qdb_app_string_catalog()['germanDefaults'] ?? [];
|
||||
$result = [];
|
||||
foreach ($entries as $e) {
|
||||
$key = $e['key'];
|
||||
foreach ($translations[$e['entityId']] ?? [] as $lang => $text) {
|
||||
$result[$lang][$key] = $text;
|
||||
}
|
||||
if (!isset($result[QDB_SOURCE_LANGUAGE][$key]) && !empty($defaults[$key])) {
|
||||
$result[QDB_SOURCE_LANGUAGE][$key] = $defaults[$key];
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flat language -> stringKey -> text map for one questionnaire (questions, options, config strings).
|
||||
* Excludes keys from the global app UI catalog.
|
||||
* @return array<string, array<string, string>>
|
||||
*/
|
||||
function qdb_build_questionnaire_translations_map(PDO $pdo, string $qnID): array {
|
||||
$lists = qdb_translation_entry_lists($pdo, $qnID);
|
||||
$entries = array_merge($lists['stringEntries'], $lists['contentEntries']);
|
||||
if (!$entries) {
|
||||
return [];
|
||||
}
|
||||
$translations = qdb_load_translations_for_entries($pdo, $entries);
|
||||
$result = [];
|
||||
foreach ($entries as $e) {
|
||||
$key = $e['key'];
|
||||
foreach ($translations[$e['entityId']] ?? [] as $lang => $text) {
|
||||
$result[$lang][$key] = $text;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
// --- AES-256-CBC: IV(16) + CIPHERTEXT ---
|
||||
function aes256_cbc_encrypt_bytes(string $plain, string $key): string {
|
||||
$key = str_pad(substr($key, 0, 32), 32, "\0");
|
||||
|
||||
Reference in New Issue
Block a user