Merge remote-tracking branch 'origin/master'
This commit is contained in:
633
ui/src/pages/TranslationTable.vue
Normal file
633
ui/src/pages/TranslationTable.vue
Normal file
@@ -0,0 +1,633 @@
|
||||
<template>
|
||||
<q-page v-if="canUpdateLanguage" class="q-pa-md">
|
||||
<div class="row q-col-gutter-sm items-end q-mb-md">
|
||||
<div class="col-12 col-md-4">
|
||||
<q-input
|
||||
v-model="filters.q"
|
||||
dense
|
||||
outlined
|
||||
clearable
|
||||
label="Kelime ara"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-btn color="primary" icon="search" label="Getir" @click="loadRows" />
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-btn
|
||||
color="secondary"
|
||||
icon="sync"
|
||||
label="YENİ KELİMELERİ GETİR"
|
||||
:loading="store.saving"
|
||||
@click="syncSources"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-toggle v-model="autoTranslate" dense color="primary" label="Oto Çeviri" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row q-gutter-sm q-mb-sm">
|
||||
<q-btn
|
||||
color="accent"
|
||||
icon="g_translate"
|
||||
label="Seçilenleri Çevir"
|
||||
:disable="selectedKeys.length === 0"
|
||||
:loading="store.saving"
|
||||
@click="translateSelectedRows"
|
||||
/>
|
||||
<q-btn
|
||||
color="secondary"
|
||||
icon="done_all"
|
||||
label="Seçilenleri Onayla"
|
||||
:disable="selectedKeys.length === 0"
|
||||
:loading="store.saving"
|
||||
@click="bulkApproveSelected"
|
||||
/>
|
||||
<q-btn
|
||||
color="primary"
|
||||
icon="save"
|
||||
label="Seçilenleri Toplu Güncelle"
|
||||
:disable="selectedKeys.length === 0"
|
||||
:loading="store.saving"
|
||||
@click="bulkSaveSelected"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<q-table
|
||||
flat
|
||||
bordered
|
||||
dense
|
||||
row-key="t_key"
|
||||
:loading="store.loading || store.saving"
|
||||
:rows="pivotRows"
|
||||
:columns="columns"
|
||||
:rows-per-page-options="[0]"
|
||||
:pagination="{ rowsPerPage: 0 }"
|
||||
>
|
||||
<template #body-cell-actions="props">
|
||||
<q-td :props="props">
|
||||
<q-btn
|
||||
dense
|
||||
color="primary"
|
||||
icon="save"
|
||||
label="Güncelle"
|
||||
:disable="!rowHasChanges(props.row.t_key)"
|
||||
:loading="store.saving"
|
||||
@click="saveRow(props.row.t_key)"
|
||||
/>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template #body-cell-select="props">
|
||||
<q-td :props="props">
|
||||
<q-checkbox
|
||||
dense
|
||||
:model-value="selectedKeys.includes(props.row.t_key)"
|
||||
@update:model-value="(v) => toggleSelected(props.row.t_key, v)"
|
||||
/>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template #body-cell-source_text_tr="props">
|
||||
<q-td :props="props" :class="cellClass(props.row.t_key, 'source_text_tr')">
|
||||
<q-input v-model="rowDraft(props.row.t_key).source_text_tr" dense outlined @blur="queueAutoSave(props.row.t_key)" />
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template #body-cell-source_type="props">
|
||||
<q-td :props="props" :class="cellClass(props.row.t_key, 'source_type')">
|
||||
<q-select
|
||||
v-model="rowDraft(props.row.t_key).source_type"
|
||||
dense
|
||||
outlined
|
||||
emit-value
|
||||
map-options
|
||||
:options="sourceTypeOptions"
|
||||
@update:model-value="() => queueAutoSave(props.row.t_key)"
|
||||
/>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template #body-cell-en="props">
|
||||
<q-td :props="props" :class="cellClass(props.row.t_key, 'en')">
|
||||
<q-input v-model="rowDraft(props.row.t_key).en" dense outlined @blur="queueAutoSave(props.row.t_key)" />
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template #body-cell-de="props">
|
||||
<q-td :props="props" :class="cellClass(props.row.t_key, 'de')">
|
||||
<q-input v-model="rowDraft(props.row.t_key).de" dense outlined @blur="queueAutoSave(props.row.t_key)" />
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template #body-cell-es="props">
|
||||
<q-td :props="props" :class="cellClass(props.row.t_key, 'es')">
|
||||
<q-input v-model="rowDraft(props.row.t_key).es" dense outlined @blur="queueAutoSave(props.row.t_key)" />
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template #body-cell-it="props">
|
||||
<q-td :props="props" :class="cellClass(props.row.t_key, 'it')">
|
||||
<q-input v-model="rowDraft(props.row.t_key).it" dense outlined @blur="queueAutoSave(props.row.t_key)" />
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template #body-cell-ru="props">
|
||||
<q-td :props="props" :class="cellClass(props.row.t_key, 'ru')">
|
||||
<q-input v-model="rowDraft(props.row.t_key).ru" dense outlined @blur="queueAutoSave(props.row.t_key)" />
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template #body-cell-ar="props">
|
||||
<q-td :props="props" :class="cellClass(props.row.t_key, 'ar')">
|
||||
<q-input v-model="rowDraft(props.row.t_key).ar" dense outlined @blur="queueAutoSave(props.row.t_key)" />
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
</q-page>
|
||||
|
||||
<q-page v-else class="q-pa-md flex flex-center">
|
||||
<div class="text-negative text-subtitle1">
|
||||
Bu module erisim yetkiniz yok.
|
||||
</div>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useQuasar } from 'quasar'
|
||||
import { usePermission } from 'src/composables/usePermission'
|
||||
import { useTranslationStore } from 'src/stores/translationStore'
|
||||
|
||||
const $q = useQuasar()
|
||||
const store = useTranslationStore()
|
||||
const { canUpdate } = usePermission()
|
||||
const canUpdateLanguage = canUpdate('language')
|
||||
|
||||
const filters = ref({
|
||||
q: ''
|
||||
})
|
||||
const autoTranslate = ref(false)
|
||||
|
||||
const sourceTypeOptions = [
|
||||
{ label: 'dummy', value: 'dummy' },
|
||||
{ label: 'postgre', value: 'postgre' },
|
||||
{ label: 'mssql', value: 'mssql' }
|
||||
]
|
||||
|
||||
const columns = [
|
||||
{ name: 'actions', label: 'Güncelle', field: 'actions', align: 'left' },
|
||||
{ name: 'select', label: 'Seç', field: 'select', align: 'left' },
|
||||
{ name: 't_key', label: 'Key', field: 't_key', align: 'left', sortable: true },
|
||||
{ name: 'source_text_tr', label: 'Türkçe kaynak', field: 'source_text_tr', align: 'left' },
|
||||
{ name: 'source_type', label: 'Veri tipi', field: 'source_type', align: 'left' },
|
||||
{ name: 'en', label: 'English', field: 'en', align: 'left' },
|
||||
{ name: 'de', label: 'Deutch', field: 'de', align: 'left' },
|
||||
{ name: 'es', label: 'Espanol', field: 'es', align: 'left' },
|
||||
{ name: 'it', label: 'Italiano', field: 'it', align: 'left' },
|
||||
{ name: 'ru', label: 'Русский', field: 'ru', align: 'left' },
|
||||
{ name: 'ar', label: 'العربية', field: 'ar', align: 'left' }
|
||||
]
|
||||
|
||||
const draftByKey = ref({})
|
||||
const originalByKey = ref({})
|
||||
const selectedKeys = ref([])
|
||||
const autoSaveTimers = new Map()
|
||||
|
||||
const pivotRows = computed(() => {
|
||||
const byKey = new Map()
|
||||
for (const row of store.rows) {
|
||||
const key = row.t_key
|
||||
if (!byKey.has(key)) {
|
||||
byKey.set(key, {
|
||||
t_key: key,
|
||||
source_text_tr: '',
|
||||
source_type: 'dummy',
|
||||
en: '',
|
||||
de: '',
|
||||
es: '',
|
||||
it: '',
|
||||
ru: '',
|
||||
ar: '',
|
||||
langs: {}
|
||||
})
|
||||
}
|
||||
|
||||
const target = byKey.get(key)
|
||||
target.langs[row.lang_code] = {
|
||||
id: row.id,
|
||||
status: row.status,
|
||||
is_manual: row.is_manual
|
||||
}
|
||||
|
||||
if (row.lang_code === 'tr') {
|
||||
target.source_text_tr = row.translated_text || row.source_text_tr || ''
|
||||
target.source_type = row.source_type || 'dummy'
|
||||
} else if (row.lang_code === 'en') {
|
||||
target.en = row.translated_text || ''
|
||||
} else if (row.lang_code === 'de') {
|
||||
target.de = row.translated_text || ''
|
||||
} else if (row.lang_code === 'es') {
|
||||
target.es = row.translated_text || ''
|
||||
} else if (row.lang_code === 'it') {
|
||||
target.it = row.translated_text || ''
|
||||
} else if (row.lang_code === 'ru') {
|
||||
target.ru = row.translated_text || ''
|
||||
} else if (row.lang_code === 'ar') {
|
||||
target.ar = row.translated_text || ''
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(byKey.values()).sort((a, b) => a.t_key.localeCompare(b.t_key))
|
||||
})
|
||||
|
||||
function snapshotDrafts () {
|
||||
const draft = {}
|
||||
const original = {}
|
||||
for (const row of pivotRows.value) {
|
||||
draft[row.t_key] = {
|
||||
source_text_tr: row.source_text_tr || '',
|
||||
source_type: row.source_type || 'dummy',
|
||||
en: row.en || '',
|
||||
de: row.de || '',
|
||||
es: row.es || '',
|
||||
it: row.it || '',
|
||||
ru: row.ru || '',
|
||||
ar: row.ar || ''
|
||||
}
|
||||
original[row.t_key] = { ...draft[row.t_key] }
|
||||
}
|
||||
draftByKey.value = draft
|
||||
originalByKey.value = original
|
||||
selectedKeys.value = selectedKeys.value.filter(k => draft[k])
|
||||
}
|
||||
|
||||
function rowDraft (key) {
|
||||
if (!draftByKey.value[key]) {
|
||||
draftByKey.value[key] = {
|
||||
source_text_tr: '',
|
||||
source_type: 'dummy',
|
||||
en: '',
|
||||
de: '',
|
||||
es: '',
|
||||
it: '',
|
||||
ru: '',
|
||||
ar: ''
|
||||
}
|
||||
}
|
||||
return draftByKey.value[key]
|
||||
}
|
||||
|
||||
function buildFilters () {
|
||||
return {
|
||||
q: filters.value.q || undefined
|
||||
}
|
||||
}
|
||||
|
||||
function rowHasChanges (key) {
|
||||
const draft = draftByKey.value[key]
|
||||
const orig = originalByKey.value[key]
|
||||
if (!draft || !orig) return false
|
||||
return (
|
||||
draft.source_text_tr !== orig.source_text_tr ||
|
||||
draft.source_type !== orig.source_type ||
|
||||
draft.en !== orig.en ||
|
||||
draft.de !== orig.de ||
|
||||
draft.es !== orig.es ||
|
||||
draft.it !== orig.it ||
|
||||
draft.ru !== orig.ru ||
|
||||
draft.ar !== orig.ar
|
||||
)
|
||||
}
|
||||
|
||||
function isPending (key, lang) {
|
||||
const row = pivotRows.value.find(r => r.t_key === key)
|
||||
const meta = row?.langs?.[lang]
|
||||
return meta?.status === 'pending'
|
||||
}
|
||||
|
||||
function cellClass (key, field) {
|
||||
const draft = draftByKey.value[key]
|
||||
const orig = originalByKey.value[key]
|
||||
if (!draft || !orig) return ''
|
||||
|
||||
if (draft[field] !== orig[field]) return 'cell-dirty'
|
||||
|
||||
if (field === 'en' && isPending(key, 'en')) return 'cell-new'
|
||||
if (field === 'de' && isPending(key, 'de')) return 'cell-new'
|
||||
if (field === 'es' && isPending(key, 'es')) return 'cell-new'
|
||||
if (field === 'it' && isPending(key, 'it')) return 'cell-new'
|
||||
if (field === 'ru' && isPending(key, 'ru')) return 'cell-new'
|
||||
if (field === 'ar' && isPending(key, 'ar')) return 'cell-new'
|
||||
if (field === 'source_text_tr' && isPending(key, 'tr')) return 'cell-new'
|
||||
return ''
|
||||
}
|
||||
|
||||
function toggleSelected (key, checked) {
|
||||
if (checked) {
|
||||
if (!selectedKeys.value.includes(key)) {
|
||||
selectedKeys.value = [...selectedKeys.value, key]
|
||||
}
|
||||
return
|
||||
}
|
||||
selectedKeys.value = selectedKeys.value.filter(k => k !== key)
|
||||
}
|
||||
|
||||
function queueAutoSave (key) {
|
||||
if (!key) return
|
||||
const existing = autoSaveTimers.get(key)
|
||||
if (existing) {
|
||||
clearTimeout(existing)
|
||||
}
|
||||
const timer = setTimeout(() => {
|
||||
autoSaveTimers.delete(key)
|
||||
if (rowHasChanges(key)) {
|
||||
void saveRow(key)
|
||||
}
|
||||
}, 250)
|
||||
autoSaveTimers.set(key, timer)
|
||||
}
|
||||
|
||||
async function loadRows () {
|
||||
try {
|
||||
await store.fetchRows(buildFilters())
|
||||
snapshotDrafts()
|
||||
} catch (err) {
|
||||
console.error('[translation-sync][ui] loadRows:error', {
|
||||
message: err?.message || 'Ceviri satirlari yuklenemedi'
|
||||
})
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: err?.message || 'Çeviri satırları yüklenemedi'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureMissingLangRows (key, draft, langs) {
|
||||
const missingLangs = []
|
||||
if (!langs.en && String(draft.en || '').trim() !== '') missingLangs.push('en')
|
||||
if (!langs.de && String(draft.de || '').trim() !== '') missingLangs.push('de')
|
||||
if (!langs.es && String(draft.es || '').trim() !== '') missingLangs.push('es')
|
||||
if (!langs.it && String(draft.it || '').trim() !== '') missingLangs.push('it')
|
||||
if (!langs.ru && String(draft.ru || '').trim() !== '') missingLangs.push('ru')
|
||||
if (!langs.ar && String(draft.ar || '').trim() !== '') missingLangs.push('ar')
|
||||
if (missingLangs.length === 0) return false
|
||||
|
||||
await store.upsertMissing([
|
||||
{
|
||||
t_key: key,
|
||||
source_text_tr: draft.source_text_tr || key
|
||||
}
|
||||
], missingLangs)
|
||||
return true
|
||||
}
|
||||
|
||||
function buildRowUpdates (row, draft, original, approveStatus = 'approved') {
|
||||
const items = []
|
||||
const langs = row.langs || {}
|
||||
const sourceTypeChanged = draft.source_type !== original.source_type
|
||||
|
||||
if (langs.tr?.id && (draft.source_text_tr !== original.source_text_tr || sourceTypeChanged)) {
|
||||
items.push({
|
||||
id: langs.tr.id,
|
||||
source_text_tr: draft.source_text_tr,
|
||||
translated_text: draft.source_text_tr,
|
||||
source_type: draft.source_type,
|
||||
status: approveStatus,
|
||||
is_manual: true
|
||||
})
|
||||
}
|
||||
if (langs.en?.id && (draft.en !== original.en || sourceTypeChanged)) {
|
||||
items.push({
|
||||
id: langs.en.id,
|
||||
translated_text: draft.en,
|
||||
source_type: draft.source_type,
|
||||
status: approveStatus,
|
||||
is_manual: true
|
||||
})
|
||||
}
|
||||
if (langs.de?.id && (draft.de !== original.de || sourceTypeChanged)) {
|
||||
items.push({
|
||||
id: langs.de.id,
|
||||
translated_text: draft.de,
|
||||
source_type: draft.source_type,
|
||||
status: approveStatus,
|
||||
is_manual: true
|
||||
})
|
||||
}
|
||||
if (langs.es?.id && (draft.es !== original.es || sourceTypeChanged)) {
|
||||
items.push({
|
||||
id: langs.es.id,
|
||||
translated_text: draft.es,
|
||||
source_type: draft.source_type,
|
||||
status: approveStatus,
|
||||
is_manual: true
|
||||
})
|
||||
}
|
||||
if (langs.it?.id && (draft.it !== original.it || sourceTypeChanged)) {
|
||||
items.push({
|
||||
id: langs.it.id,
|
||||
translated_text: draft.it,
|
||||
source_type: draft.source_type,
|
||||
status: approveStatus,
|
||||
is_manual: true
|
||||
})
|
||||
}
|
||||
if (langs.ru?.id && (draft.ru !== original.ru || sourceTypeChanged)) {
|
||||
items.push({
|
||||
id: langs.ru.id,
|
||||
translated_text: draft.ru,
|
||||
source_type: draft.source_type,
|
||||
status: approveStatus,
|
||||
is_manual: true
|
||||
})
|
||||
}
|
||||
if (langs.ar?.id && (draft.ar !== original.ar || sourceTypeChanged)) {
|
||||
items.push({
|
||||
id: langs.ar.id,
|
||||
translated_text: draft.ar,
|
||||
source_type: draft.source_type,
|
||||
status: approveStatus,
|
||||
is_manual: true
|
||||
})
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
async function saveRow (key) {
|
||||
const row = pivotRows.value.find(r => r.t_key === key)
|
||||
const draft = draftByKey.value[key]
|
||||
const original = originalByKey.value[key]
|
||||
if (!row || !draft || !original || !rowHasChanges(key)) return
|
||||
|
||||
try {
|
||||
const insertedMissing = await ensureMissingLangRows(key, draft, row.langs || {})
|
||||
if (insertedMissing) {
|
||||
await loadRows()
|
||||
}
|
||||
|
||||
const refreshed = pivotRows.value.find(r => r.t_key === key)
|
||||
if (!refreshed) return
|
||||
const refreshDraft = draftByKey.value[key]
|
||||
const refreshOriginal = originalByKey.value[key]
|
||||
const items = buildRowUpdates(refreshed, refreshDraft, refreshOriginal)
|
||||
if (items.length > 0) {
|
||||
await store.bulkUpdate(items)
|
||||
}
|
||||
|
||||
await loadRows()
|
||||
$q.notify({ type: 'positive', message: 'Satır güncellendi' })
|
||||
} catch (err) {
|
||||
$q.notify({ type: 'negative', message: err?.message || 'Güncelleme hatası' })
|
||||
}
|
||||
}
|
||||
|
||||
async function bulkApproveSelected () {
|
||||
try {
|
||||
const ids = []
|
||||
for (const key of selectedKeys.value) {
|
||||
const row = pivotRows.value.find(r => r.t_key === key)
|
||||
if (!row) continue
|
||||
for (const lang of ['tr', 'en', 'de', 'es', 'it', 'ru', 'ar']) {
|
||||
const meta = row.langs?.[lang]
|
||||
if (meta?.id && meta?.status === 'pending') {
|
||||
ids.push(meta.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
const unique = Array.from(new Set(ids))
|
||||
if (unique.length === 0) {
|
||||
$q.notify({ type: 'warning', message: 'Onaylanacak pending kayıt bulunamadı' })
|
||||
return
|
||||
}
|
||||
await store.bulkApprove(unique)
|
||||
await loadRows()
|
||||
$q.notify({ type: 'positive', message: `${unique.length} kayıt onaylandı` })
|
||||
} catch (err) {
|
||||
$q.notify({ type: 'negative', message: err?.message || 'Toplu onay hatası' })
|
||||
}
|
||||
}
|
||||
|
||||
async function translateSelectedRows () {
|
||||
try {
|
||||
const keys = Array.from(new Set(selectedKeys.value.filter(Boolean)))
|
||||
if (keys.length === 0) {
|
||||
$q.notify({ type: 'warning', message: 'Çevrilecek seçim bulunamadı' })
|
||||
return
|
||||
}
|
||||
|
||||
const response = await store.translateSelected({
|
||||
t_keys: keys,
|
||||
languages: ['en', 'de', 'it', 'es', 'ru', 'ar'],
|
||||
limit: Math.min(50000, keys.length * 6)
|
||||
})
|
||||
|
||||
const translated = Number(response?.translated_count || 0)
|
||||
const traceId = response?.trace_id || null
|
||||
|
||||
await loadRows()
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
message: `Seçilenler çevrildi: ${translated}${traceId ? ` | Trace: ${traceId}` : ''}`
|
||||
})
|
||||
} catch (err) {
|
||||
$q.notify({ type: 'negative', message: err?.message || 'Seçili çeviri işlemi başarısız' })
|
||||
}
|
||||
}
|
||||
|
||||
async function bulkSaveSelected () {
|
||||
try {
|
||||
const items = []
|
||||
for (const key of selectedKeys.value) {
|
||||
const row = pivotRows.value.find(r => r.t_key === key)
|
||||
const draft = draftByKey.value[key]
|
||||
const original = originalByKey.value[key]
|
||||
if (!row || !draft || !original) continue
|
||||
if (!rowHasChanges(key)) continue
|
||||
|
||||
const insertedMissing = await ensureMissingLangRows(key, draft, row.langs || {})
|
||||
if (insertedMissing) {
|
||||
await loadRows()
|
||||
}
|
||||
|
||||
const refreshed = pivotRows.value.find(r => r.t_key === key)
|
||||
if (!refreshed) continue
|
||||
const refreshDraft = draftByKey.value[key]
|
||||
const refreshOriginal = originalByKey.value[key]
|
||||
items.push(...buildRowUpdates(refreshed, refreshDraft, refreshOriginal))
|
||||
}
|
||||
|
||||
if (items.length === 0) {
|
||||
$q.notify({ type: 'warning', message: 'Toplu güncellenecek değişiklik yok' })
|
||||
return
|
||||
}
|
||||
|
||||
await store.bulkUpdate(items)
|
||||
await loadRows()
|
||||
$q.notify({ type: 'positive', message: `${items.length} kayıt toplu güncellendi` })
|
||||
} catch (err) {
|
||||
$q.notify({ type: 'negative', message: err?.message || 'Toplu güncelleme hatası' })
|
||||
}
|
||||
}
|
||||
|
||||
async function syncSources () {
|
||||
const startedAt = Date.now()
|
||||
const beforeCount = pivotRows.value.length
|
||||
console.info('[translation-sync][ui] button:click', {
|
||||
at: new Date(startedAt).toISOString(),
|
||||
auto_translate: autoTranslate.value,
|
||||
only_new: true,
|
||||
before_row_count: beforeCount
|
||||
})
|
||||
try {
|
||||
const response = await store.syncSources({
|
||||
auto_translate: autoTranslate.value,
|
||||
languages: ['en', 'de', 'it', 'es', 'ru', 'ar'],
|
||||
limit: 1000,
|
||||
only_new: true
|
||||
})
|
||||
const result = response?.result || response || {}
|
||||
const traceId = result?.trace_id || response?.trace_id || null
|
||||
console.info('[translation-sync][ui] sync:response', {
|
||||
trace_id: traceId,
|
||||
seed_count: result.seed_count || 0,
|
||||
affected_count: result.affected_count || 0,
|
||||
auto_translated: result.auto_translated || 0,
|
||||
duration_ms: result.duration_ms || null
|
||||
})
|
||||
await loadRows()
|
||||
const afterCount = pivotRows.value.length
|
||||
console.info('[translation-sync][ui] chain:reload-complete', {
|
||||
trace_id: traceId,
|
||||
duration_ms: Date.now() - startedAt,
|
||||
before_row_count: beforeCount,
|
||||
after_row_count: afterCount,
|
||||
delta_row_count: afterCount - beforeCount
|
||||
})
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
message: `Tarama tamamlandı. Seed: ${result.seed_count || 0}, Oto çeviri: ${result.auto_translated || 0}`
|
||||
})
|
||||
} catch (err) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: err?.message || 'Kaynak tarama hatası'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadRows()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cell-dirty {
|
||||
background: #fff3cd;
|
||||
}
|
||||
|
||||
.cell-new {
|
||||
background: #d9f7e8;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user