From ebc00bbb0e49ee4e6f51d1178f55f52e1da2fe70 Mon Sep 17 00:00:00 2001
From: busya
Date: Fri, 29 May 2026 16:30:09 +0300
Subject: [PATCH] refactor(translate): extract ConfigTabForm, TargetTabForm,
RunTabContent from page
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Page reduced from 1519 to 909 lines (-40%). Extracted:
- ConfigTabForm.svelte (370 lines) — datasource search, column mapping,
LLM, target languages, dictionaries, batch config
- TargetTabForm.svelte (120 lines) — target schema/table/database,
target column mapping, TargetSchemaHint
- RunTabContent.svelte (170 lines) — run cards, progress, history
All three components use () props for seamless parent
state sync. Sub-components (TargetSchemaHint, TranslationPreview,
TranslationRunProgress, ScheduleConfig, BulkReplaceModal) unchanged.
---
.../components/translate/ConfigTabForm.svelte | 531 +++++++++++++
.../components/translate/RunTabContent.svelte | 201 +++++
.../components/translate/TargetTabForm.svelte | 151 ++++
.../src/routes/translate/[id]/+page.svelte | 744 ++----------------
4 files changed, 950 insertions(+), 677 deletions(-)
create mode 100644 frontend/src/lib/components/translate/ConfigTabForm.svelte
create mode 100644 frontend/src/lib/components/translate/RunTabContent.svelte
create mode 100644 frontend/src/lib/components/translate/TargetTabForm.svelte
diff --git a/frontend/src/lib/components/translate/ConfigTabForm.svelte b/frontend/src/lib/components/translate/ConfigTabForm.svelte
new file mode 100644
index 00000000..facfe25b
--- /dev/null
+++ b/frontend/src/lib/components/translate/ConfigTabForm.svelte
@@ -0,0 +1,531 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {$t.translate?.config?.basic_info}
+
+
+
+ {$t.translate?.config?.name} *
+
+
+ {#if validationErrors.name}
+
{validationErrors.name}
+ {/if}
+
+
+ {$t.translate?.config?.description}
+
+
+
+
+
+
+
+ {$t.translate?.config?.datasource}
+
+
+ {$t.translate?.config?.environment}
+
+ {$t.translate?.config?.select_environment}
+ {#each environments as env}
+ {env.name} ({env.id})
+ {/each}
+
+
+
+
{$t.translate?.config?.datasource_id}
+
searchDatasources('')}
+ onblur={() => setTimeout(() => (showDatasourceDropdown = false), 150)}
+ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus-visible:ring-2 focus-visible:ring-primary-ring focus-visible:border-primary-ring"
+ placeholder={$t.translate?.config?.datasource_search_placeholder || 'Search datasets...'}
+ />
+ {#if datasourceLoading}
+
+ {/if}
+ {#if showDatasourceDropdown}
+
+ {#each datasourceList as ds}
+ selectDatasource(ds)}
+ class="w-full text-left px-3 py-2 text-sm hover:bg-blue-50 border-b border-gray-100 last:border-b-0"
+ >
+ {ds.table_name}
+ {ds.schema}
+ {ds.database_name} · {ds.database_dialect}
+
+ {/each}
+
+ {/if}
+
+ {#if databaseDialect}
+
+
+ {$t.translate?.config?.detected_dialect.replace('{dialect}', databaseDialect)}
+
+
+ {/if}
+
+
+ {#if isColumnsLoading}
+
+ {/if}
+
+ {#if columnList.length > 0}
+
+
+ {$t.translate?.config?.available_columns.replace('{count}', columnList.length)}
+
+
+ {#each columnList as col}
+
+ {col.name}
+ {#if col.type}
+ ({col.type})
+ {/if}
+ {#if isVirtual(col.name)}
+ {$t.translate?.config?.virtual}
+ {/if}
+
+ {/each}
+
+
+ {/if}
+
+
+
+
+ {$t.translate?.config?.column_mapping}
+
+
+
+
+ {$t.translate?.config?.translation_column} *
+
+
+ {$t.translate?.config?.select_column}
+ {#each columnList as col}
+ {col.name}{isVirtual(col.name) ? ` (${$t.translate?.config?.virtual})` : ''}
+ {/each}
+
+ {#if validationErrors.translationColumn}
+
{validationErrors.translationColumn}
+ {/if}
+
+
+
+
+
+ {$t.translate?.config?.key_columns}
+ {#if sourceKeyCols.length > 0}
+ { sourceKeyCols = []; targetKeyCols = []; }}
+ class="text-xs text-red-600 hover:text-red-700"
+ >
+ {$t.translate?.config?.clear_all}
+
+ {/if}
+
+
+ {#if columnList.length === 0}
+
+ {
+ sourceKeyCols = [...sourceKeyCols, ''];
+ targetKeyCols = [...targetKeyCols, ''];
+ }}
+ class="px-3 py-1 text-xs border border-dashed border-gray-300 rounded text-gray-500 hover:border-primary-ring hover:text-primary"
+ >
+ {$t.translate?.config?.add_key_column}
+
+
+ {:else}
+
+ {#each columnList as col}
+ toggleSourceKeyCol(col.name)}
+ class="px-2.5 py-1 text-xs rounded-full border transition-colors
+ {sourceKeyCols.includes(col.name)
+ ? 'bg-blue-100 border-blue-300 text-blue-700'
+ : 'bg-white border-gray-200 text-gray-600 hover:border-gray-300'}"
+ title={isVirtual(col.name) ? $t.translate?.config?.virtual_column_warning.replace('{col}', col.name) : ''}
+ >
+ {col.name}
+ {#if isVirtual(col.name)}
+ *
+ {/if}
+
+ {/each}
+
+ {/if}
+
+ {#if sourceKeyCols.length > 0}
+
+ {#each sourceKeyCols as srcCol, idx}
+
+ {/each}
+
+ {/if}
+ {#if validationErrors.targetKeyCols}
+
{validationErrors.targetKeyCols}
+ {/if}
+
+
+
+
+
{$t.translate?.config?.context_columns}
+ {#if columnList.length === 0}
+
{$t.translate?.config?.select_datasource_hint}
+ {:else}
+
+ {#each columnList as col}
+ toggleContextColumn(col.name)}
+ class="px-2.5 py-1 text-xs rounded-full border transition-colors
+ {contextColumns.includes(col.name)
+ ? 'bg-purple-100 border-purple-300 text-purple-700'
+ : 'bg-white border-gray-200 text-gray-600 hover:border-gray-300'}"
+ >
+ {col.name}
+ {#if isVirtual(col.name)}
+ *
+ {/if}
+
+ {/each}
+
+ {/if}
+
+
+
+
+
+ {$t.translate?.config?.llm_settings}
+
+
+ {$t.translate?.config?.provider}
+
+ {$t.translate?.config?.select_provider}
+ {#each llmProviders as provider}
+ {provider.name || provider.provider_type}
+ {/each}
+
+
+
+
+ {#if targetLanguages.length === 0}
+
{$t.translate?.config?.target_language_required || 'Select at least one target language'}
+ {/if}
+
+
+ {$t.translate?.config?.batch_size}
+
+
+
+ {$t.translate?.config?.upsert_strategy}
+
+ {$t.translate?.config?.upsert_merge}
+ {$t.translate?.config?.upsert_insert}
+ {$t.translate?.config?.upsert_update}
+
+
+
+
+
+
+
+
+
{$t.translate?.config?.include_source_reference || 'Include source language in translations'}
+
+ {$t.translate?.config?.include_source_reference_hint || 'The original text will be stored as a verified reference copy in its detected language'}
+
+
+
+
+
+
+
+
+
+
+
{$t.translate?.config?.disable_reasoning || 'Disable reasoning (save tokens)'}
+
+ {$t.translate?.config?.disable_reasoning_hint || 'Saves output tokens by suppressing Chain of Thought reasoning'}
+
+
+
+
+
+
+
+
+ {$t.translate?.config?.terminology_dictionaries}
+ {#if availableDictionaries.length === 0}
+
+ {$t.translate?.config?.no_dictionaries}
+
+ {:else}
+
+ {#each availableDictionaries as dict}
+
+ {
+ if (dictionaryIds.includes(dict.id)) {
+ dictionaryIds = dictionaryIds.filter(d => d !== dict.id);
+ } else {
+ dictionaryIds = [...dictionaryIds, dict.id];
+ }
+ }}
+ class="rounded border-gray-300 text-primary focus-visible:ring-primary-ring"
+ />
+
+
{dict.name}
+ {#if dict.description}
+
{dict.description}
+ {/if}
+ {#if dict.source_dialect || dict.target_dialect}
+
+ {dict.source_dialect} → {dict.target_dialect}
+
+ {/if}
+
+
+ {/each}
+
+ {/if}
+
+
+
diff --git a/frontend/src/lib/components/translate/RunTabContent.svelte b/frontend/src/lib/components/translate/RunTabContent.svelte
new file mode 100644
index 00000000..cbfe5674
--- /dev/null
+++ b/frontend/src/lib/components/translate/RunTabContent.svelte
@@ -0,0 +1,201 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {$t.translate?.config?.run_translation}
+
+
+
+ {$t.translate?.config?.status}:
+
+ {getJobStatusLabel(status)}
+
+ {#if status === 'DRAFT'}
+
+ {$t.translate?.config?.mark_ready || 'Mark as READY'}
+
+ {/if}
+
+
+
+
+
+
+
+
+
+
+
{$t.translate?.config?.run_incremental}
+
{$t.translate?.config?.run_incremental_desc}
+
onTriggerRun(false)}
+ disabled={isRunning || status === 'DRAFT'}
+ class="mt-3 px-5 py-1.5 text-sm font-medium bg-green-600 text-white rounded-lg hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
+ >
+ {isRunning && !isFullRun ? $t.translate?.config?.running : $t.translate?.config?.run_translation}
+
+
+
+
+
+
+
+
+
+
+
{$t.translate?.config?.run_full}
+
{$t.translate?.config?.run_full_desc}
+
onTriggerRun(true)}
+ disabled={isRunning || status === 'DRAFT'}
+ class="mt-3 px-5 py-1.5 text-sm font-medium bg-primary text-white rounded-lg hover:bg-primary-hover disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
+ >
+ {isRunning && isFullRun ? $t.translate?.config?.running : $t.translate?.config?.full_translate}
+
+
+
+
+
+
+ {#if runError}
+
+ {/if}
+
+ {#if currentRunId && !runComplete}
+
+ {/if}
+
+ {#if completedRuns.length > 0}
+
+
+
{$t.translate?.config?.recent_runs}
+ showBulkReplace = true}
+ class="px-3 py-1.5 text-xs bg-indigo-600 text-white rounded hover:bg-indigo-700 transition-colors"
+ >
+ {$t.translate?.run?.bulk_replace || 'Bulk Replace'}
+
+
+
+ {#each completedRuns as run}
+
+
+
+
+ {$t.translate?.run?.run_id}
+ {run.id}
+
+ {run.status}
+
+
+
+ {$t.translate?.run?.total}: {run.total_records || 0}
+ {$t.translate?.run?.success}: {run.successful_records || 0}
+ {$t.translate?.run?.failed}: {run.failed_records || 0}
+ {$t.translate?.run?.skipped}: {run.skipped_records || 0}
+
+ {#if run.error_message}
+
{run.error_message}
+ {/if}
+
+
onToggleRunDetails(run.id)}
+ class="shrink-0 px-3 py-1.5 text-xs border border-gray-300 text-gray-700 rounded hover:bg-gray-50 transition-colors"
+ >
+ {expandedRunIds.includes(run.id)
+ ? ($t.translate?.run?.hide_details || 'Hide details')
+ : ($t.translate?.run?.show_details || 'Show details')}
+
+
+ {#if expandedRunIds.includes(run.id)}
+
+
+
+ {/if}
+
+ {/each}
+
+
+ {/if}
+
+
+
diff --git a/frontend/src/lib/components/translate/TargetTabForm.svelte b/frontend/src/lib/components/translate/TargetTabForm.svelte
new file mode 100644
index 00000000..d45a4f8e
--- /dev/null
+++ b/frontend/src/lib/components/translate/TargetTabForm.svelte
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {$t.translate?.config?.target_table_title}
+
+
+ {$t.translate?.config?.target_schema}
+
+
+
+ {$t.translate?.config?.target_table}
+
+
+
+
+ {$t.translate?.config?.target_database}
+
+
+ {$t.translate?.config?.select_target_database}
+ {#each databases as db}
+
+ {db.database_name || db.name} ({db.engine || db.backend || '?'})
+
+ {/each}
+
+ {#if databasesLoading}
+
{$t.translate?.config?.loading_databases}
+ {:else if !environmentId}
+
{$t.translate?.config?.select_environment}
+ {:else}
+
{$t.translate?.config?.target_database_hint}
+ {/if}
+
+
+
+
+
+
{$t.translate?.config?.target_column_mapping_title || 'Target Column Mapping'}
+
{$t.translate?.config?.target_column_mapping_description || 'Configure which columns receive translated data, language codes, source text, and detected source language during INSERT.'}
+
+
+
+ {$t.translate?.config?.target_column_label}
+
+
+
{$t.translate?.config?.target_column_hint}
+
+
+
+ {$t.translate?.config?.target_language_column_label}
+
+
+
{$t.translate?.config?.target_language_column_hint}
+
+
+
+ {$t.translate?.config?.target_source_column_label}
+
+
+
{$t.translate?.config?.target_source_column_hint}
+
+
+
+ {$t.translate?.config?.target_source_language_column_label}
+
+
+
{$t.translate?.config?.target_source_language_column_hint}
+
+
+
+
+
+
+
+
diff --git a/frontend/src/routes/translate/[id]/+page.svelte b/frontend/src/routes/translate/[id]/+page.svelte
index aa6f825b..7ffabc51 100644
--- a/frontend/src/routes/translate/[id]/+page.svelte
+++ b/frontend/src/routes/translate/[id]/+page.svelte
@@ -83,7 +83,9 @@
import ScheduleConfig from '$lib/components/translate/ScheduleConfig.svelte';
import { triggerRun, fetchRunHistory, cancelRun } from '$lib/api/translate.js';
import BulkReplaceModal from '$lib/components/translate/BulkReplaceModal.svelte';
- import TargetSchemaHint from '$lib/components/translate/TargetSchemaHint.svelte';
+ import ConfigTabForm from '$lib/components/translate/ConfigTabForm.svelte';
+ import TargetTabForm from '$lib/components/translate/TargetTabForm.svelte';
+ import RunTabContent from '$lib/components/translate/RunTabContent.svelte';
import { fromStore } from 'svelte/store';
import {
translationRunStore,
@@ -92,35 +94,7 @@
clearOnCompleteCallback,
} from '$lib/stores/translationRun.js';
- const LANGUAGE_LABELS = {
- ru: 'Русский',
- en: 'English',
- de: 'Deutsch',
- fr: 'Français',
- es: 'Español',
- it: 'Italiano',
- pt: 'Português',
- zh: '中文',
- ja: '日本語',
- ko: '한국어',
- ar: 'العربية',
- tr: 'Türkçe',
- nl: 'Nederlands',
- pl: 'Polski',
- sv: 'Svenska',
- da: 'Dansk',
- fi: 'Suomi',
- cs: 'Čeština',
- hu: 'Magyar',
- ro: 'Română',
- vi: 'Tiếng Việt',
- th: 'ไทย',
- he: 'עברית',
- id: 'Bahasa Indonesia',
- ms: 'Bahasa Melayu',
- };
- const LANGUAGES = Object.entries(LANGUAGE_LABELS).map(([code, name]) => ({ code, name }));
/** @type {string} idle | loading | configured | saving | validation_error | datasource_unavailable */
let uxState = $state('idle');
@@ -159,13 +133,9 @@
let environments = $state([]);
let datasourceId = $state('');
let datasourceSearch = $state('');
- let datasourceList = $state([]);
- let datasourceLoading = $state(false);
- let showDatasourceDropdown = $state(false);
let availableColumns = $state([]);
let virtualColumns = $state([]);
let databaseDialect = $state('');
- let isColumnsLoading = $state(false);
let llmProviders = $state([]);
let availableDictionaries = $state([]);
let targetDatabaseId = $state('');
@@ -811,375 +781,35 @@
-
-
-
- {$t.translate?.config?.basic_info}
-
-
-
- {$t.translate?.config?.name} *
-
-
- {#if validationErrors.name}
-
{validationErrors.name}
- {/if}
-
-
- {$t.translate?.config?.description}
-
-
-
-
-
-
-
- {$t.translate?.config?.datasource}
-
-
- {$t.translate?.config?.environment}
-
- {$t.translate?.config?.select_environment}
- {#each environments as env}
- {env.name} ({env.id})
- {/each}
-
-
-
-
{$t.translate?.config?.datasource_id}
-
searchDatasources('')}
- onblur={() => setTimeout(() => (showDatasourceDropdown = false), 150)}
- class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus-visible:ring-2 focus-visible:ring-primary-ring focus-visible:border-primary-ring"
- placeholder={$t.translate?.config?.datasource_search_placeholder || 'Search datasets...'}
- />
- {#if datasourceLoading}
-
- {/if}
- {#if showDatasourceDropdown}
-
- {#each datasourceList as ds}
- selectDatasource(ds)}
- class="w-full text-left px-3 py-2 text-sm hover:bg-blue-50 border-b border-gray-100 last:border-b-0"
- >
- {ds.table_name}
- {ds.schema}
- {ds.database_name} · {ds.database_dialect}
-
- {/each}
-
- {/if}
-
- {#if databaseDialect}
-
-
- {$t.translate?.config?.detected_dialect.replace('{dialect}', databaseDialect)}
-
-
- {/if}
-
-
- {#if isColumnsLoading}
-
- {/if}
-
- {#if columnList.length > 0}
-
-
- {$t.translate?.config?.available_columns.replace('{count}', columnList.length)}
-
-
- {#each columnList as col}
-
- {col.name}
- {#if col.type}
- ({col.type})
- {/if}
- {#if isVirtual(col.name)}
- {$t.translate?.config?.virtual}
- {/if}
-
- {/each}
-
-
- {/if}
-
-
-
-
- {$t.translate?.config?.column_mapping}
-
-
-
-
- {$t.translate?.config?.translation_column} *
-
-
- {$t.translate?.config?.select_column}
- {#each columnList as col}
- {col.name}{isVirtual(col.name) ? ` (${$t.translate?.config?.virtual})` : ''}
- {/each}
-
- {#if validationErrors.translationColumn}
-
{validationErrors.translationColumn}
- {/if}
-
-
-
-
-
-
-
- {$t.translate?.config?.key_columns}
- {#if sourceKeyCols.length > 0}
- { sourceKeyCols = []; targetKeyCols = []; }}
- class="text-xs text-red-600 hover:text-red-700"
- >
- {$t.translate?.config?.clear_all}
-
- {/if}
-
-
- {#if columnList.length === 0}
-
- {
- sourceKeyCols = [...sourceKeyCols, ''];
- targetKeyCols = [...targetKeyCols, ''];
- }}
- class="px-3 py-1 text-xs border border-dashed border-gray-300 rounded text-gray-500 hover:border-primary-ring hover:text-primary"
- >
- {$t.translate?.config?.add_key_column}
-
-
- {:else}
-
- {#each columnList as col}
- toggleSourceKeyCol(col.name)}
- class="px-2.5 py-1 text-xs rounded-full border transition-colors
- {sourceKeyCols.includes(col.name)
- ? 'bg-blue-100 border-blue-300 text-blue-700'
- : 'bg-white border-gray-200 text-gray-600 hover:border-gray-300'}"
- title={isVirtual(col.name) ? $t.translate?.config?.virtual_column_warning.replace('{col}', col.name) : ''}
- >
- {col.name}
- {#if isVirtual(col.name)}
- *
- {/if}
-
- {/each}
-
- {/if}
-
- {#if sourceKeyCols.length > 0}
-
- {#each sourceKeyCols as srcCol, idx}
-
- {/each}
-
- {/if}
- {#if validationErrors.targetKeyCols}
-
{validationErrors.targetKeyCols}
- {/if}
-
-
-
-
-
{$t.translate?.config?.context_columns}
- {#if columnList.length === 0}
-
{$t.translate?.config?.select_datasource_hint}
- {:else}
-
- {#each columnList as col}
- toggleContextColumn(col.name)}
- class="px-2.5 py-1 text-xs rounded-full border transition-colors
- {contextColumns.includes(col.name)
- ? 'bg-purple-100 border-purple-300 text-purple-700'
- : 'bg-white border-gray-200 text-gray-600 hover:border-gray-300'}"
- >
- {col.name}
- {#if isVirtual(col.name)}
- *
- {/if}
-
- {/each}
-
- {/if}
-
-
-
-
-
- {$t.translate?.config?.llm_settings}
-
-
- {$t.translate?.config?.provider}
-
- {$t.translate?.config?.select_provider}
- {#each llmProviders as provider}
- {provider.name || provider.provider_type}
- {/each}
-
-
-
-
- {#if targetLanguages.length === 0}
-
{$t.translate?.config?.target_language_required || 'Select at least one target language'}
- {/if}
-
-
- {$t.translate?.config?.batch_size}
-
-
-
- {$t.translate?.config?.upsert_strategy}
-
- {$t.translate?.config?.upsert_merge}
- {$t.translate?.config?.upsert_insert}
- {$t.translate?.config?.upsert_update}
-
-
-
-
-
-
-
-
-
{$t.translate?.config?.include_source_reference || 'Include source language in translations'}
-
- {$t.translate?.config?.include_source_reference_hint || 'The original text will be stored as a verified reference copy in its detected language'}
-
-
-
-
-
-
-
-
-
-
-
{$t.translate?.config?.disable_reasoning || 'Disable reasoning (save tokens)'}
-
- {$t.translate?.config?.disable_reasoning_hint || 'Saves output tokens by suppressing Chain of Thought reasoning'}
-
-
-
-
-
-
-
-
- {$t.translate?.config?.terminology_dictionaries}
- {#if availableDictionaries.length === 0}
-
- {$t.translate?.config?.no_dictionaries}
-
- {:else}
-
- {#each availableDictionaries as dict}
-
- {
- if (dictionaryIds.includes(dict.id)) {
- dictionaryIds = dictionaryIds.filter(d => d !== dict.id);
- } else {
- dictionaryIds = [...dictionaryIds, dict.id];
- }
- }}
- class="rounded border-gray-300 text-primary focus-visible:ring-primary-ring"
- />
-
-
{dict.name}
- {#if dict.description}
-
{dict.description}
- {/if}
- {#if dict.source_dialect || dict.target_dialect}
-
- {dict.source_dialect} → {dict.target_dialect}
-
- {/if}
-
-
- {/each}
-
- {/if}
-
-
-
+
@@ -1204,10 +834,7 @@
{:else}
-
- {$t.translate?.preview?.title}
-
-
+
{/if}
{:else}
@@ -1219,123 +846,20 @@
{#if !isNewJob && existingJob}
-
- {$t.translate?.config?.target_table_title}
-
-
- {$t.translate?.config?.target_schema}
-
-
-
- {$t.translate?.config?.target_table}
-
-
-
-
- {$t.translate?.config?.target_database}
-
-
- {$t.translate?.config?.select_target_database}
- {#each databases as db}
-
- {db.database_name || db.name} ({db.engine || db.backend || '?'})
-
- {/each}
-
- {#if databasesLoading}
-
{$t.translate?.config?.loading_databases}
- {:else if !environmentId}
-
{$t.translate?.config?.select_environment}
- {:else}
-
{$t.translate?.config?.target_database_hint}
- {/if}
-
-
-
-
-
-
{$t.translate?.config?.target_column_mapping_title || 'Target Column Mapping'}
-
{$t.translate?.config?.target_column_mapping_description || 'Configure which columns receive translated data, language codes, source text, and detected source language during INSERT.'}
-
-
-
- {$t.translate?.config?.target_column_label}
-
-
-
{$t.translate?.config?.target_column_hint}
-
-
-
- {$t.translate?.config?.target_language_column_label}
-
-
-
{$t.translate?.config?.target_language_column_hint}
-
-
-
- {$t.translate?.config?.target_source_column_label}
-
-
-
{$t.translate?.config?.target_source_column_hint}
-
-
-
- {$t.translate?.config?.target_source_language_column_label}
-
-
-
{$t.translate?.config?.target_source_language_column_hint}
-
-
-
-
-
-
-
+
{:else}
{$t.translate?.config?.save_job_first || 'Save the job first'}
@@ -1346,160 +870,26 @@
{#if !isNewJob && existingJob}
-
- {$t.translate?.config?.run_translation}
-
-
-
- {$t.translate?.config?.status}:
-
- {getJobStatusLabel(status)}
-
- {#if status === 'DRAFT'}
- {
- try {
- await updateJob(jobId, { status: 'READY' });
- status = 'READY';
- addToast($t.translate?.config?.job_updated, 'success');
- } catch (e) {
- addToast(e?.message || 'Failed to update status', 'error');
- }
- }}
- class="ml-auto px-3 py-1 text-xs bg-primary text-white rounded hover:bg-primary-hover transition-colors"
- >
- {$t.translate?.config?.mark_ready || 'Mark as READY'}
-
- {/if}
-
-
-
-
-
-
-
-
-
-
-
{$t.translate?.config?.run_incremental}
-
{$t.translate?.config?.run_incremental_desc}
-
handleTriggerRun(false)}
- disabled={isRunning || status === 'DRAFT'}
- class="mt-3 px-5 py-1.5 text-sm font-medium bg-green-600 text-white rounded-lg hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
- >
- {isRunning && !isFullRun ? $t.translate?.config?.running : $t.translate?.config?.run_translation}
-
-
-
-
-
-
-
-
-
-
-
{$t.translate?.config?.run_full}
-
{$t.translate?.config?.run_full_desc}
-
handleTriggerRun(true)}
- disabled={isRunning || status === 'DRAFT'}
- class="mt-3 px-5 py-1.5 text-sm font-medium bg-primary text-white rounded-lg hover:bg-primary-hover disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
- >
- {isRunning && isFullRun ? $t.translate?.config?.running : $t.translate?.config?.full_translate}
-
-
-
-
-
-
- {#if runError}
-
- {/if}
-
- {#if currentRunId && !runComplete}
-
- {/if}
-
- {#if completedRuns.length > 0}
-
-
-
{$t.translate?.config?.recent_runs}
- showPageBulkReplace = true}
- class="px-3 py-1.5 text-xs bg-indigo-600 text-white rounded hover:bg-indigo-700 transition-colors"
- >
- {$t.translate?.run?.bulk_replace || 'Bulk Replace'}
-
-
-
- {#each completedRuns as run}
-
-
-
-
- {$t.translate?.run?.run_id}
- {run.id}
-
- {run.status}
-
-
-
- {$t.translate?.run?.total}: {run.total_records || 0}
- {$t.translate?.run?.success}: {run.successful_records || 0}
- {$t.translate?.run?.failed}: {run.failed_records || 0}
- {$t.translate?.run?.skipped}: {run.skipped_records || 0}
-
- {#if run.error_message}
-
{run.error_message}
- {/if}
-
-
toggleRunDetails(run.id)}
- class="shrink-0 px-3 py-1.5 text-xs border border-gray-300 text-gray-700 rounded hover:bg-gray-50 transition-colors"
- >
- {expandedRunIds.includes(run.id)
- ? ($t.translate?.run?.hide_details || 'Hide details')
- : ($t.translate?.run?.show_details || 'Show details')}
-
-
- {#if expandedRunIds.includes(run.id)}
-
-
-
- {/if}
-
- {/each}
-
-
- {/if}
-
-
+
{ showPageBulkReplace = false; loadRunHistory(); }}
+ onBulkReplaceClose={() => showPageBulkReplace = false}
+ {getJobStatusLabel}
+ />
{:else}
{$t.translate?.config?.save_job_first || 'Save the job first'}