From fc2ef731a2e0eca915be151aacc9dca8ab74ba5c Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Thu, 9 Apr 2026 00:21:39 +0200 Subject: [PATCH] fixes lang update --- src/pages/admin/Admin.module.css | 16 +++ src/pages/admin/AdminLanguagesPage.tsx | 184 +++++++++++++++++-------- 2 files changed, 141 insertions(+), 59 deletions(-) diff --git a/src/pages/admin/Admin.module.css b/src/pages/admin/Admin.module.css index bf7c98b..74c98cd 100644 --- a/src/pages/admin/Admin.module.css +++ b/src/pages/admin/Admin.module.css @@ -985,3 +985,19 @@ color: var(--text-tertiary, #999); margin-top: 0.5rem; } + +/* i18n language update overlay — second progress bar */ +.i18nKeysProgressTrack { + width: 100%; + height: 8px; + background: var(--border-color, #e2e8f0); + border-radius: 4px; + overflow: hidden; + position: relative; +} + +.i18nKeysProgressFill { + height: 100%; + border-radius: 4px; + background: var(--accent-color, var(--primary-color, #3182ce)); +} diff --git a/src/pages/admin/AdminLanguagesPage.tsx b/src/pages/admin/AdminLanguagesPage.tsx index b84ad67..6b8799f 100644 --- a/src/pages/admin/AdminLanguagesPage.tsx +++ b/src/pages/admin/AdminLanguagesPage.tsx @@ -23,6 +23,8 @@ type ProgressInfo = { total: number; error?: string; done?: boolean; + /** Short title (e.g. language label) to avoid long sentences in the overlay. */ + progressHeading?: string; /** Keys in language set before sync (from sync-diff). */ keysCurrent?: number; /** New keys vs xx before PUT (from sync-diff). */ @@ -96,28 +98,56 @@ const _isoChoices: { value: string; label: string }[] = [ // --------------------------------------------------------------------------- const _ProgressOverlay: React.FC<{ progress: ProgressInfo }> = ({ progress }) => { - const { t } = useLanguage(); - const pct = progress.total > 0 ? Math.round((progress.current / progress.total) * 100) : 0; const master = progress.keysMasterTotal; - let keysLine: string | null = null; - if (master != null && master > 0) { - const pending = progress.keysPending ?? 0; - if (pending > 0 && progress.keysTranslated !== undefined) { - keysLine = t('{tr} / {pending} neue Schlüssel übersetzt · Basis {m}', { - tr: String(progress.keysTranslated), - pending: String(pending), - m: String(master), - }); - } else if (pending > 0) { - keysLine = t('{pending} / {m} (neu / Basis-Schlüssel)', { - pending: String(pending), - m: String(master), - }); - } else { - const cur = progress.keysCurrent ?? 0; - keysLine = t('{cur} / {m} Schlüssel abgedeckt', { cur: String(cur), m: String(master) }); + const pending = progress.keysPending ?? 0; + const cur = progress.keysCurrent ?? 0; + const translated = progress.keysTranslated; + const hasMaster = master != null && master > 0; + + const entryJ = pending > 0 ? (translated ?? 0) : cur; + const entryTotal = pending > 0 ? pending : (master ?? 0); + const showEntryMeter = hasMaster && entryTotal > 0; + const awaitingAi = + pending > 0 && + translated === undefined && + !progress.done && + !progress.error; + + const [softEntryPct, setSoftEntryPct] = useState(0); + useEffect(() => { + if (!awaitingAi) { + setSoftEntryPct(0); + return; } + const started = Date.now(); + const id = window.setInterval(() => { + const elapsed = Date.now() - started; + const cap = 94; + setSoftEntryPct(Math.min(cap, cap * (1 - Math.exp(-elapsed / 14000)))); + }, 220); + return () => clearInterval(id); + }, [awaitingAi]); + + let entryPct = 0; + if (pending > 0) { + if (translated !== undefined && pending > 0) { + entryPct = Math.min(100, Math.round((translated / pending) * 100)); + } + } else if (master && master > 0) { + entryPct = Math.min(100, Math.round((cur / master) * 100)); } + + const entryBarPct = awaitingAi ? softEntryPct : entryPct; + const entryJShown = awaitingAi + ? Math.min(pending, Math.max(0, Math.floor((softEntryPct / 100) * pending))) + : entryJ; + + const showLangMeter = progress.total > 1 || !showEntryMeter; + const pctLang = progress.total > 0 ? Math.round((progress.current / progress.total) * 100) : 0; + const title = progress.progressHeading ?? progress.message; + + const barBg = progress.error ? 'var(--error-color, #c53030)' : 'var(--primary-color, #3182ce)'; + return (
= ({ progress }) => border: '1px solid var(--border-color, #ddd)', borderRadius: 'var(--border-radius, 8px)', padding: '2rem 2.5rem', - minWidth: 340, - maxWidth: 480, + minWidth: 320, + maxWidth: 420, boxShadow: '0 4px 24px rgba(0,0,0,0.12)', textAlign: 'center', }} > -

- {progress.message} +

+ {title}

-
-
-
- {progress.total > 1 && ( -

- {t('Schritt {cur} von {tot}', { cur: String(progress.current), tot: String(progress.total) })} - {progress.done && !progress.error && ` — ${t('fertig')}`} -

+ {showLangMeter && ( + <> +

+ {progress.current} / {progress.total} +

+
+
+
+ )} - {progress.total === 1 && progress.done && !progress.error && ( -

{t('fertig')}

- )} - {keysLine && ( -

{keysLine}

+ {showEntryMeter && ( + <> +

+ {entryJShown} / {entryTotal} +

+
+
+
+ )} {progress.error && ( -

+

{progress.error}

)} @@ -321,6 +377,7 @@ export const AdminLanguagesPage: React.FC = () => { } setProgress({ message: t('Aktualisiere {lang}…', { lang: label }), + progressHeading: label, current: 0, total: 1, keysCurrent, @@ -333,6 +390,7 @@ export const AdminLanguagesPage: React.FC = () => { const pendingAfterPut = Array.isArray(d.added) ? d.added.length : (keysPending ?? 0); setProgress({ message: t('{lang} aktualisiert.', { lang: label }), + progressHeading: label, current: 1, total: 1, done: true, @@ -346,7 +404,14 @@ export const AdminLanguagesPage: React.FC = () => { await reloadLanguage(); } catch (e: any) { const msg = e.response?.data?.detail || e.message; - setProgress({ message: t('Fehler bei {lang}', { lang: label }), current: 0, total: 1, error: msg, done: true }); + setProgress({ + message: t('Fehler bei {lang}', { lang: label }), + progressHeading: label, + current: 0, + total: 1, + error: msg, + done: true, + }); setError(msg); } finally { setTimeout(() => { setProgress(null); busyRef.current = false; }, 2000); @@ -411,6 +476,7 @@ export const AdminLanguagesPage: React.FC = () => { const pendingAfterPut = Array.isArray(d.added) ? d.added.length : (keysPending ?? 0); setProgress({ message: t('Aktualisiere {lang}…', { lang: label }), + progressHeading: label, current: step + 1, total: totalSteps, keysCurrent: typeof d.entriesCount === 'number' ? d.entriesCount : keysCurrent, @@ -453,7 +519,7 @@ export const AdminLanguagesPage: React.FC = () => { const _delete = async (code: string) => { if (busyRef.current) return; - if (code === 'xx' || code === 'de') return; + if (code === 'xx') return; const ok = await confirm(t('Sprachset {code} wirklich löschen?', { code }), { confirmLabel: t('Löschen'), cancelLabel: t('Abbrechen'), @@ -653,7 +719,7 @@ export const AdminLanguagesPage: React.FC = () => { title: t('Löschen'), icon: , onClick: (row: LangRow) => _delete(row.id), - visible: (row: LangRow) => row.id !== 'xx' && row.id !== 'de', + visible: (row: LangRow) => row.id !== 'xx', }, ]} emptyMessage={t('Keine Einträge')}