fix: replace hardcoded UI labels with i18n translations

- Add missing i18n keys to common.json, reports.json, dashboard.json,
  tasks.json, git.json (EN + RU)
- Replace all hardcoded labels in LLM report page with .reports.* keys
- Replace hardcoded tab labels (Linked resources, Git history) in
  DashboardDetail page
- Replace hardcoded RU text in DashboardGitManager with .git.* keys
- Remove hardcoded fallback strings from DashboardHeader, DashboardTaskHistory
This commit is contained in:
2026-05-27 16:53:20 +03:00
parent 040d333d7d
commit 8db2b1bb6b
15 changed files with 144 additions and 60 deletions

View File

@@ -30,5 +30,10 @@
"per_page": "per page",
"close_modal": "Close modal",
"prod_context_warning": "PROD context active",
"choose_environment": "-- Choose an environment --"
"choose_environment": "-- Choose an environment --",
"type": "Type",
"status": "Status",
"started": "Started",
"finished": "Finished",
"duration": "Duration"
}

View File

@@ -98,6 +98,13 @@
"status_synced": "Synced",
"status_diff": "Diff",
"status_error": "Error",
"linked_resources": "Linked resources",
"git_history": "Git history",
"run_backup": "Run backup",
"run_llm_check": "Run LLM check",
"thumbnail_generating": "Thumbnail is being generated",
"thumbnail_failed": "Failed to load thumbnail",
"thumbnail_unavailable": "Thumbnail is unavailable",
"empty": "No dashboards found",
"setup_badge": "Initial setup",
"setup_title": "Configure your first environment",

View File

@@ -74,6 +74,14 @@
"diverged": "Diverged",
"error": "Error"
},
"repo_changes_detected": "Configuration changes detected in Superset.",
"repo_synced_with_branch": "Superset configuration matches branch {branch}.",
"charts_label": "charts: {count}",
"datasets_label": "datasets: {count}",
"files_label": "files: {count}",
"sync_and_commit": "Sync & Commit",
"show_diff": "Show diff",
"init_repo_button": "Initialize Git Repository",
"unfinished_merge": {
"title": "Repository has an unfinished merge",
"default_message": "An unfinished merge was detected. Resolve conflicts or abort merge manually, then retry Pull.",

View File

@@ -20,5 +20,25 @@
"updated": "Updated",
"summary": "Summary",
"severity": "Severity",
"message": "Message"
"message": "Message",
"llm_task_id": "Task: {taskId}",
"llm_status": "Status",
"llm_dashboard_check": "Dashboard check",
"llm_started": "Started",
"llm_finished": "Finished",
"llm_duration": "Duration",
"llm_text_report": "Text Report",
"llm_location": "Location: {location}",
"llm_screenshots": "Screenshots",
"llm_no_screenshots": "No screenshots saved.",
"llm_failed_screenshot": "Failed to load screenshot",
"llm_logs_sent": "Logs sent to LLM ({count})",
"llm_no_source_logs": "No source logs were attached.",
"llm_task_logs": "Task execution logs ({count})",
"llm_no_task_logs": "No task logs available.",
"load_failed": "Failed to load report",
"llm_time": "Time",
"llm_level": "Level",
"llm_source": "Source",
"llm_message": "Message"
}

View File

@@ -69,5 +69,7 @@
"show_diff": "Show diff",
"diff_preview": "Diff preview",
"no_diff_available": "Diff is not available",
"summary_link_unavailable": "Deep link or diff is unavailable for this task"
"summary_link_unavailable": "Deep link or diff is unavailable for this task",
"open_llm_report": "LLM report",
"result": "Check"
}

View File

@@ -30,5 +30,10 @@
"per_page": "на страницу",
"close_modal": "Закрыть модальное окно",
"prod_context_warning": "Активен PROD-контекст",
"choose_environment": "-- Выберите окружение --"
"choose_environment": "-- Выберите окружение --",
"type": "Тип",
"status": "Статус",
"started": "Запущено",
"finished": "Завершено",
"duration": "Длительность"
}

View File

@@ -97,6 +97,13 @@
"status_synced": "Синхронизировано",
"status_diff": "Различия",
"status_error": "Ошибка",
"linked_resources": "Связанные ресурсы",
"git_history": "История Git",
"run_backup": "Запустить бэкап",
"run_llm_check": "LLM проверка",
"thumbnail_generating": "Миниатюра генерируется",
"thumbnail_failed": "Не удалось загрузить миниатюру",
"thumbnail_unavailable": "Миниатюра недоступна",
"empty": "Дашборды не найдены",
"setup_badge": "Стартовая настройка",
"setup_title": "Настройте первое окружение",

View File

@@ -74,6 +74,14 @@
"diverged": "Расхождение",
"error": "Ошибка"
},
"repo_changes_detected": "Обнаружены изменения конфигурации в Superset.",
"repo_synced_with_branch": "Конфигурация Superset совпадает с веткой {branch}.",
"charts_label": "чарты: {count}",
"datasets_label": "датасеты: {count}",
"files_label": "файлы: {count}",
"sync_and_commit": "Синхронизировать и зафиксировать",
"show_diff": "Показать diff",
"init_repo_button": "Инициализировать Git-репозиторий",
"unfinished_merge": {
"title": "Незавершённое слияние в репозитории",
"default_message": "В репозитории найдено незавершённое слияние. Завершите или отмените его вручную и повторите Pull.",

View File

@@ -20,5 +20,25 @@
"updated": "Обновлено",
"summary": "Сводка",
"severity": "Серьезность",
"message": "Сообщение"
"message": "Сообщение",
"llm_task_id": "Задача: {taskId}",
"llm_status": "Статус",
"llm_dashboard_check": "Проверка дашборда",
"llm_started": "Запущено",
"llm_finished": "Завершено",
"llm_duration": "Длительность",
"llm_text_report": "Текстовый отчет",
"llm_location": "Локация: {location}",
"llm_screenshots": "Скриншоты",
"llm_no_screenshots": "Скриншоты не сохранены.",
"llm_failed_screenshot": "Не удалось загрузить скриншот",
"llm_logs_sent": "Логи, отправленные LLM ({count})",
"llm_no_source_logs": "Исходные логи не были прикреплены.",
"llm_task_logs": "Логи выполнения задачи ({count})",
"llm_no_task_logs": "Логи задачи недоступны.",
"load_failed": "Не удалось загрузить отчет",
"llm_time": "Время",
"llm_level": "Уровень",
"llm_source": "Источник",
"llm_message": "Сообщение"
}

View File

@@ -69,5 +69,7 @@
"show_diff": "Показать Diff",
"diff_preview": "Diff предпросмотр",
"no_diff_available": "Diff недоступен",
"summary_link_unavailable": "Ссылка или diff недоступны для этой задачи"
"summary_link_unavailable": "Ссылка или diff недоступны для этой задачи",
"open_llm_report": "LLM отчет",
"result": "Проверка"
}

View File

@@ -576,7 +576,7 @@
class="rounded-lg border border-rose-300 bg-rose-50 px-4 py-3 text-sm text-rose-800"
>
<div class="font-semibold">
{$t.dashboard?.llm_not_configured || "LLM is not configured"}
{$t.dashboard?.llm_not_configured}
</div>
<div class="mt-1 text-rose-700">
{#if llmStatusReason === "no_active_provider"}
@@ -709,19 +709,19 @@
class={`rounded-md px-3 py-1.5 text-sm font-medium transition-colors ${activeTab === "resources" ? "bg-slate-900 text-white" : "bg-slate-100 text-slate-700 hover:bg-slate-200"}`}
onclick={() => (activeTab = "resources")}
>
Linked resources
{$t.dashboard?.linked_resources || "Linked resources"}
</button>
<button
class={`rounded-md px-3 py-1.5 text-sm font-medium transition-colors ${activeTab === "git-history" ? "bg-slate-900 text-white" : "bg-slate-100 text-slate-700 hover:bg-slate-200"}`}
onclick={() => (activeTab = "git-history")}
>
Git history
{$t.dashboard?.git_history || "Git history"}
</button>
<button
class={`rounded-md px-3 py-1.5 text-sm font-medium transition-colors ${activeTab === "tasks" ? "bg-slate-900 text-white" : "bg-slate-100 text-slate-700 hover:bg-slate-200"}`}
onclick={() => (activeTab = "tasks")}
>
{$t.nav?.tasks || "Task logs"}
{$t.nav?.tasks}
</button>
</div>

View File

@@ -37,14 +37,14 @@
<div class="rounded-xl border border-slate-200 bg-white p-4 xl:col-span-3">
<div class="mb-3 flex items-center justify-between gap-2">
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">
{$t.git?.management || "Git Repository"}
{$t.git?.management}
</h2>
<button
class="rounded-md border border-slate-300 px-2 py-1 text-xs text-slate-700 hover:bg-slate-50"
onclick={loadGitStatus}
disabled={isGitStatusLoading}
>
{$t.common?.refresh || "Refresh"}
{$t.common?.refresh}
</button>
</div>
@@ -61,34 +61,34 @@
{:else if !hasGitRepo}
<div class="space-y-3 rounded-lg border border-slate-200 bg-slate-50 px-3 py-6 text-sm text-slate-600">
<div>
{$t.git?.not_linked || "Этот дашборд еще не привязан к Git-репозиторию."}
{$t.git?.not_linked}
</div>
<button
class="inline-flex items-center justify-center rounded-lg bg-slate-900 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-slate-800"
onclick={() => (showGitManager = true)}
>
Инициализировать Git-репозиторий
{$t.git?.init_repo_button || $t.git?.init_repo}
</button>
</div>
{:else}
<div class="space-y-4">
<p class="text-sm text-slate-700">
{#if gitSyncState === "CHANGES"}
Обнаружены изменения конфигурации в Superset.
{$t.git?.repo_changes_detected}
{:else}
Конфигурация Superset совпадает с веткой {gitStatus?.current_branch || "main"}.
{$t.git?.repo_synced_with_branch?.replace("{branch}", gitStatus?.current_branch || "main")}
{/if}
</p>
<div class="flex flex-wrap gap-2 text-xs text-slate-600">
<span class="rounded-full bg-slate-100 px-2 py-1">
charts: {changedChartsCount}
{$t.git?.charts_label?.replace("{count}", changedChartsCount)}
</span>
<span class="rounded-full bg-slate-100 px-2 py-1">
datasets: {changedDatasetsCount}
{$t.git?.datasets_label?.replace("{count}", changedDatasetsCount)}
</span>
<span class="rounded-full bg-slate-100 px-2 py-1">
files: {allChangedFiles.length}
{$t.git?.files_label?.replace("{count}", allChangedFiles.length)}
</span>
</div>
@@ -98,14 +98,14 @@
onclick={runGitSyncAndOpenCommit}
disabled={isSyncingGit || !hasChangesToCommit}
>
{isSyncingGit ? $t.common?.loading || "Loading..." : "Синхронизировать и зафиксировать"}
{isSyncingGit ? $t.common?.loading : $t.git?.sync_and_commit}
</button>
<button
class="inline-flex items-center justify-center rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50 disabled:opacity-60"
onclick={loadGitDiffPreview}
disabled={isGitDiffLoading || !hasChangesToCommit}
>
{isGitDiffLoading ? $t.common?.loading || "Loading..." : "Показать diff"}
{isGitDiffLoading ? $t.common?.loading : $t.git?.show_diff}
</button>
</div>
@@ -115,7 +115,7 @@
onclick={runGitPull}
disabled={isPullingGit}
>
{isPullingGit ? $t.common?.loading || "Loading..." : $t.git?.pull || "Pull"}
{isPullingGit ? $t.common?.loading : $t.git?.pull}
</button>
<button
class="inline-flex items-center justify-center rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50 disabled:opacity-60"
@@ -123,9 +123,9 @@
disabled={isPushingGit}
>
{#if isPushingGit}
{$t.common?.loading || "Loading..."}
{$t.common?.loading}
{:else}
{$t.git?.push || "Push"}{#if gitStatus?.ahead_count > 0} ({gitStatus.ahead_count}){/if}
{$t.git?.push}{#if gitStatus?.ahead_count > 0} ({gitStatus.ahead_count}){/if}
{/if}
</button>
</div>

View File

@@ -75,22 +75,22 @@
class="inline-flex items-center justify-center rounded-lg border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50"
onclick={() => (showGitManager = true)}
>
{$t.git?.management || "Manage Git"}
{$t.git?.management}
</button>
<button
class="inline-flex items-center justify-center rounded-lg border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50"
onclick={runBackupTask}
disabled={isStartingBackup}
>
{isStartingBackup ? $t.common?.loading || "Loading..." : $t.dashboard?.run_backup || "Run backup"}
{isStartingBackup ? $t.common?.loading : $t.dashboard?.run_backup}
</button>
<button
class="inline-flex items-center justify-center rounded-lg border px-4 py-2 text-sm font-medium transition-colors {llmReady ? 'border-indigo-300 bg-indigo-50 text-indigo-700 hover:bg-indigo-100' : 'border-rose-300 bg-rose-50 text-rose-700 opacity-70 cursor-not-allowed'}"
onclick={runLlmValidationTask}
disabled={isStartingValidation || !llmReady}
title={!llmReady ? $t.dashboard?.llm_not_configured || "LLM is not configured" : ""}
title={!llmReady ? $t.dashboard?.llm_not_configured : ""}
>
{isStartingValidation ? $t.common?.loading || "Loading..." : $t.dashboard?.run_llm_check || "Run LLM check"}
{isStartingValidation ? $t.common?.loading : $t.dashboard?.run_llm_check}
</button>
<button
class="inline-flex items-center justify-center rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-primary-hover"

View File

@@ -27,14 +27,14 @@
<div class="rounded-xl border border-slate-200 bg-white p-4">
<div class="mb-3 flex items-center justify-between">
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">
{$t.tasks?.recent || "Recent tasks"}
{$t.tasks?.recent}
</h2>
<button
class="rounded-md border border-slate-300 px-2 py-1 text-xs text-slate-700 hover:bg-slate-50"
onclick={loadTaskHistory}
disabled={isTaskHistoryLoading}
>
{$t.common?.refresh || "Refresh"}
{$t.common?.refresh}
</button>
</div>
{#if isTaskHistoryLoading}
@@ -49,19 +49,19 @@
</div>
{:else if taskHistory.length === 0}
<div class="rounded-lg border border-slate-200 bg-slate-50 px-3 py-6 text-center text-sm text-slate-500">
{$t.tasks?.select_task || "No backup/LLM tasks yet"}
{$t.tasks?.select_task}
</div>
{:else}
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-slate-200 text-sm">
<thead class="bg-slate-50">
<tr>
<th class="px-3 py-2 text-left font-semibold text-slate-600">{$t.common?.type || "Type"}</th>
<th class="px-3 py-2 text-left font-semibold text-slate-600">{$t.common?.status || "Status"}</th>
<th class="px-3 py-2 text-left font-semibold text-slate-600">{$t.tasks?.result || "Check"}</th>
<th class="px-3 py-2 text-left font-semibold text-slate-600">{$t.common?.started || "Started"}</th>
<th class="px-3 py-2 text-left font-semibold text-slate-600">{$t.common?.finished || "Finished"}</th>
<th class="px-3 py-2 text-left font-semibold text-slate-600">{$t.common?.actions || "Actions"}</th>
<th class="px-3 py-2 text-left font-semibold text-slate-600">{$t.common?.type}</th>
<th class="px-3 py-2 text-left font-semibold text-slate-600">{$t.common?.status}</th>
<th class="px-3 py-2 text-left font-semibold text-slate-600">{$t.tasks?.result}</th>
<th class="px-3 py-2 text-left font-semibold text-slate-600">{$t.common?.started}</th>
<th class="px-3 py-2 text-left font-semibold text-slate-600">{$t.common?.finished}</th>
<th class="px-3 py-2 text-left font-semibold text-slate-600">{$t.common?.actions}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
@@ -93,7 +93,7 @@
class="inline-flex items-center gap-1 rounded-md border border-indigo-300 bg-indigo-50 px-2 py-1 text-xs text-indigo-700 hover:bg-indigo-100"
onclick={() => openLlmReport(task.id)}
>
{$t.tasks?.open_llm_report || "LLM report"}
{$t.tasks?.open_llm_report}
</button>
{/if}
</div>

View File

@@ -91,7 +91,7 @@
task = taskPayload;
logs = Array.isArray(logsPayload) ? logsPayload : [];
} catch (err) {
error = err?.message || "Failed to load LLM report";
error = err?.message || $t.reports?.load_failed || "Failed to load LLM report";
} finally {
isLoading = false;
}
@@ -129,7 +129,7 @@
const blob = await api.getStorageFileBlob(path);
nextUrls[path] = URL.createObjectURL(blob);
} catch (err) {
nextErrors[path] = err?.message || "Failed to load screenshot";
nextErrors[path] = err?.message || $t.reports?.llm_failed_screenshot || "Failed to load screenshot";
}
}),
);
@@ -192,7 +192,7 @@
<h1 class="mt-2 text-2xl font-bold text-slate-900">
{$t.tasks?.result_llm_validation || "LLM Validation Report"}
</h1>
<p class="mt-1 text-sm text-slate-500">Task: {taskId}</p>
<p class="mt-1 text-sm text-slate-500">{$t.reports?.llm_task_id?.replace("{taskId}", taskId) || `Task: ${taskId}`}</p>
</div>
<div class="flex items-center gap-2">
<button
@@ -234,7 +234,7 @@
<div class="grid grid-cols-1 gap-4 md:grid-cols-5">
<div class="rounded-xl border border-slate-200 bg-white p-4">
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">
Status
{$t.reports?.llm_status || "Status"}
</p>
<p class="mt-2 text-lg font-semibold text-slate-900">
{task?.status || "-"}
@@ -242,7 +242,7 @@
</div>
<div class="rounded-xl border border-slate-200 bg-white p-4">
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">
Dashboard check
{$t.reports?.llm_dashboard_check || "Dashboard check"}
</p>
<span
class={`mt-2 inline-flex items-center gap-1 rounded-full border px-2 py-1 text-sm font-semibold uppercase ${getCheckResultClasses(checkResult.level)}`}
@@ -257,7 +257,7 @@
</div>
<div class="rounded-xl border border-slate-200 bg-white p-4">
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">
Started
{$t.common?.started || "Started"}
</p>
<p class="mt-2 text-sm font-semibold text-slate-900">
{formatDate(task?.started_at)}
@@ -265,7 +265,7 @@
</div>
<div class="rounded-xl border border-slate-200 bg-white p-4">
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">
Finished
{$t.common?.finished || "Finished"}
</p>
<p class="mt-2 text-sm font-semibold text-slate-900">
{formatDate(task?.finished_at)}
@@ -273,7 +273,7 @@
</div>
<div class="rounded-xl border border-slate-200 bg-white p-4">
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">
Duration
{$t.common?.duration || "Duration"}
</p>
<p class="mt-2 text-lg font-semibold text-slate-900">
{formatMs(timings?.validation_duration_ms)}
@@ -283,7 +283,7 @@
<div class="rounded-xl border border-slate-200 bg-white p-4">
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">
Text Report
{$t.reports?.llm_text_report || "Text Report"}
</h2>
<p class="mt-2 text-sm text-slate-800">{result?.summary || "-"}</p>
{#if Array.isArray(result?.issues) && result.issues.length > 0}
@@ -298,7 +298,7 @@
<div class="text-slate-700">{issue?.message || "-"}</div>
{#if issue?.location}
<div class="text-xs text-slate-500">
Location: {issue.location}
{$t.reports?.llm_location?.replace("{location}", issue.location) || `Location: ${issue.location}`}
</div>
{/if}
</div>
@@ -309,10 +309,10 @@
<div class="rounded-xl border border-slate-200 bg-white p-4">
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">
Screenshots
{$t.reports?.llm_screenshots || "Screenshots"}
</h2>
{#if screenshotPaths.length === 0}
<p class="mt-2 text-sm text-slate-500">No screenshots saved.</p>
<p class="mt-2 text-sm text-slate-500">{$t.reports?.llm_no_screenshots || "No screenshots saved."}</p>
{:else}
<div class="mt-3 grid grid-cols-1 gap-4 lg:grid-cols-2">
{#each screenshotPaths as path}
@@ -325,14 +325,14 @@
{#if screenshotBlobUrls[path]}
<img
src={screenshotBlobUrls[path]}
alt="Validation screenshot"
alt={$t.reports?.llm_screenshots || "Validation screenshot"}
class="h-64 w-full rounded-lg border border-slate-200 object-cover"
/>
{:else}
<div
class="flex h-64 w-full items-center justify-center rounded-lg border border-rose-200 bg-rose-50 px-4 text-sm text-rose-700"
>
{screenshotLoadErrors[path] || "Failed to load screenshot"}
{screenshotLoadErrors[path] || $t.reports?.llm_failed_screenshot || "Failed to load screenshot"}
</div>
{/if}
<p class="mt-1 truncate text-xs text-slate-500">{path}</p>
@@ -344,10 +344,10 @@
<div class="rounded-xl border border-slate-200 bg-white p-4">
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">
Logs sent to LLM ({sentLogs.length})
{$t.reports?.llm_logs_sent?.replace("{count}", sentLogs.length) || `Logs sent to LLM (${sentLogs.length})`}
</h2>
{#if sentLogs.length === 0}
<p class="mt-2 text-sm text-slate-500">No source logs were attached.</p>
<p class="mt-2 text-sm text-slate-500">{$t.reports?.llm_no_source_logs || "No source logs were attached."}</p>
{:else}
<pre
class="mt-3 max-h-80 overflow-auto rounded-lg bg-slate-900 p-3 text-xs text-slate-100">{sentLogs.join(
@@ -358,10 +358,10 @@
<div class="rounded-xl border border-slate-200 bg-white p-4">
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">
Task execution logs ({logs.length})
{$t.reports?.llm_task_logs?.replace("{count}", logs.length) || `Task execution logs (${logs.length})`}
</h2>
{#if logs.length === 0}
<p class="mt-2 text-sm text-slate-500">No task logs available.</p>
<p class="mt-2 text-sm text-slate-500">{$t.reports?.llm_no_task_logs || "No task logs available."}</p>
{:else}
<div
class="mt-3 max-h-96 overflow-auto rounded-lg border border-slate-200"
@@ -370,16 +370,16 @@
<thead class="bg-slate-50">
<tr>
<th class="px-3 py-2 text-left font-semibold text-slate-600"
>Time</th
>{$t.reports?.llm_time || "Time"}</th
>
<th class="px-3 py-2 text-left font-semibold text-slate-600"
>Level</th
>{$t.reports?.llm_level || "Level"}</th
>
<th class="px-3 py-2 text-left font-semibold text-slate-600"
>Source</th
>{$t.reports?.llm_source || "Source"}</th
>
<th class="px-3 py-2 text-left font-semibold text-slate-600"
>Message</th
>{$t.reports?.llm_message || "Message"}</th
>
</tr>
</thead>