From 44ff81642b9b625b9a5ba583c08ecd0dc7561531 Mon Sep 17 00:00:00 2001 From: obchain <167975049+obchain@users.noreply.github.com> Date: Mon, 22 Jun 2026 19:15:57 +0530 Subject: [PATCH] i18n(sync-audit): localize the memory sync-audit panel (#3706) --- .../intelligence/SyncAuditPanel.test.tsx | 15 ++++++++++++- .../intelligence/SyncAuditPanel.tsx | 21 ++++++++++++------- app/src/lib/i18n/ar.ts | 15 +++++++++++++ app/src/lib/i18n/bn.ts | 15 +++++++++++++ app/src/lib/i18n/de.ts | 15 +++++++++++++ app/src/lib/i18n/en.ts | 15 +++++++++++++ app/src/lib/i18n/es.ts | 15 +++++++++++++ app/src/lib/i18n/fr.ts | 15 +++++++++++++ app/src/lib/i18n/hi.ts | 15 +++++++++++++ app/src/lib/i18n/id.ts | 15 +++++++++++++ app/src/lib/i18n/it.ts | 15 +++++++++++++ app/src/lib/i18n/ko.ts | 15 +++++++++++++ app/src/lib/i18n/pl.ts | 15 +++++++++++++ app/src/lib/i18n/pt.ts | 15 +++++++++++++ app/src/lib/i18n/ru.ts | 15 +++++++++++++ app/src/lib/i18n/zh-CN.ts | 15 +++++++++++++ 16 files changed, 237 insertions(+), 9 deletions(-) diff --git a/app/src/components/intelligence/SyncAuditPanel.test.tsx b/app/src/components/intelligence/SyncAuditPanel.test.tsx index 1cd37e9d6..988edbc60 100644 --- a/app/src/components/intelligence/SyncAuditPanel.test.tsx +++ b/app/src/components/intelligence/SyncAuditPanel.test.tsx @@ -17,7 +17,7 @@ import { render, screen, waitFor, within } from '@testing-library/react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { SyncAuditEntry } from '../../utils/tauriCommands'; -import { SyncAuditPanel } from './SyncAuditPanel'; +import { SyncAuditPanel, timeAgo } from './SyncAuditPanel'; const mockAuditLog = vi.fn(); @@ -166,3 +166,16 @@ describe('', () => { expect(within(table).getByText('Cost')).toBeInTheDocument(); }); }); + +describe('timeAgo', () => { + // Resolve to the fallback English so the `{n}` interpolation is exercised. + const t = (_key: string, fallback?: string) => fallback ?? _key; + const isoAgo = (ms: number) => new Date(Date.now() - ms).toISOString(); + + it('covers every relative-time bucket and substitutes the {n} placeholder', () => { + expect(timeAgo(isoAgo(0), t)).toBe('just now'); + expect(timeAgo(isoAgo(5 * 60_000), t)).toBe('5m ago'); + expect(timeAgo(isoAgo(3 * 60 * 60_000), t)).toBe('3h ago'); + expect(timeAgo(isoAgo(2 * 24 * 60 * 60_000), t)).toBe('2d ago'); + }); +}); diff --git a/app/src/components/intelligence/SyncAuditPanel.tsx b/app/src/components/intelligence/SyncAuditPanel.tsx index 852a7b90c..21a998acd 100644 --- a/app/src/components/intelligence/SyncAuditPanel.tsx +++ b/app/src/components/intelligence/SyncAuditPanel.tsx @@ -35,15 +35,18 @@ function scopeLabel(scope: string): string { return scope; } -function timeAgo(iso: string): string { +// `t` is threaded in because this is a module-level helper with no hook scope. +// The `{n}` placeholder follows the codebase's interpolation convention +// (t(...).replace('{n}', value)) — `t()` itself does not interpolate params. +export function timeAgo(iso: string, t: (key: string, fallback?: string) => string): string { const diff = Date.now() - new Date(iso).getTime(); const mins = Math.floor(diff / 60_000); - if (mins < 1) return 'just now'; - if (mins < 60) return `${mins}m ago`; + if (mins < 1) return t('sync.timeAgo.justNow', 'just now'); + if (mins < 60) return t('sync.timeAgo.minutes', '{n}m ago').replace('{n}', String(mins)); const hours = Math.floor(mins / 60); - if (hours < 24) return `${hours}h ago`; + if (hours < 24) return t('sync.timeAgo.hours', '{n}h ago').replace('{n}', String(hours)); const days = Math.floor(hours / 24); - return `${days}d ago`; + return t('sync.timeAgo.days', '{n}d ago').replace('{n}', String(days)); } export function SyncAuditPanel() { @@ -126,7 +129,7 @@ export function SyncAuditPanel() { - {timeAgo(e.timestamp)} + {timeAgo(e.timestamp, t)} {e.success ? ( - + ) : ( - + )} diff --git a/app/src/lib/i18n/ar.ts b/app/src/lib/i18n/ar.ts index ce0a8882e..7aa15da41 100644 --- a/app/src/lib/i18n/ar.ts +++ b/app/src/lib/i18n/ar.ts @@ -108,6 +108,21 @@ const messages: TranslationMap = { 'common.create': 'إنشاء', 'common.search': 'بحث', 'common.loading': 'جارٍ التحميل…', + 'sync.runs': 'عمليات مزامنة', + 'sync.totalCost': 'الإجمالي', + 'sync.when': 'الوقت', + 'sync.source': 'المصدر', + 'sync.items': 'العناصر', + 'sync.tokens': 'الرموز', + 'sync.cost': 'التكلفة', + 'sync.duration': 'المدة', + 'sync.noAuditEntries': 'لم تُسجَّل أي عمليات مزامنة بعد.', + 'sync.timeAgo.justNow': 'الآن', + 'sync.timeAgo.minutes': 'قبل {n} د', + 'sync.timeAgo.hours': 'قبل {n} س', + 'sync.timeAgo.days': 'قبل {n} ي', + 'sync.status.success': 'نجاح', + 'sync.status.failed': 'فشل', 'common.error': 'خطأ', 'common.success': 'نجاح', 'common.back': 'رجوع', diff --git a/app/src/lib/i18n/bn.ts b/app/src/lib/i18n/bn.ts index 23b482783..6a537a5b7 100644 --- a/app/src/lib/i18n/bn.ts +++ b/app/src/lib/i18n/bn.ts @@ -109,6 +109,21 @@ const messages: TranslationMap = { 'common.create': 'তৈরি করুন', 'common.search': 'খুঁজুন', 'common.loading': 'লোড হচ্ছে…', + 'sync.runs': 'সিঙ্ক রান', + 'sync.totalCost': 'মোট', + 'sync.when': 'কখন', + 'sync.source': 'উৎস', + 'sync.items': 'আইটেম', + 'sync.tokens': 'টোকেন', + 'sync.cost': 'খরচ', + 'sync.duration': 'সময়কাল', + 'sync.noAuditEntries': 'এখনও কোনো সিঙ্ক রান রেকর্ড করা হয়নি।', + 'sync.timeAgo.justNow': 'এইমাত্র', + 'sync.timeAgo.minutes': '{n} মি. আগে', + 'sync.timeAgo.hours': '{n} ঘ. আগে', + 'sync.timeAgo.days': '{n} দি. আগে', + 'sync.status.success': 'সফল', + 'sync.status.failed': 'ব্যর্থ', 'common.error': 'ত্রুটি', 'common.success': 'সফল', 'common.back': 'পেছনে', diff --git a/app/src/lib/i18n/de.ts b/app/src/lib/i18n/de.ts index c222e14bd..4fc0e2422 100644 --- a/app/src/lib/i18n/de.ts +++ b/app/src/lib/i18n/de.ts @@ -109,6 +109,21 @@ const messages: TranslationMap = { 'common.create': 'Erstellen', 'common.search': 'Suchen', 'common.loading': 'Laden…', + 'sync.runs': 'Synchronisierungen', + 'sync.totalCost': 'Gesamt', + 'sync.when': 'Wann', + 'sync.source': 'Quelle', + 'sync.items': 'Elemente', + 'sync.tokens': 'Tokens', + 'sync.cost': 'Kosten', + 'sync.duration': 'Dauer', + 'sync.noAuditEntries': 'Noch keine Synchronisierungen aufgezeichnet.', + 'sync.timeAgo.justNow': 'gerade eben', + 'sync.timeAgo.minutes': 'vor {n} Min.', + 'sync.timeAgo.hours': 'vor {n} Std.', + 'sync.timeAgo.days': 'vor {n} T.', + 'sync.status.success': 'Erfolg', + 'sync.status.failed': 'Fehlgeschlagen', 'common.error': 'Fehler', 'common.success': 'Erfolg', 'common.back': 'Zurück', diff --git a/app/src/lib/i18n/en.ts b/app/src/lib/i18n/en.ts index 92eda4da6..ca146bcf3 100644 --- a/app/src/lib/i18n/en.ts +++ b/app/src/lib/i18n/en.ts @@ -97,6 +97,21 @@ const en: TranslationMap = { 'common.create': 'Create', 'common.search': 'Search', 'common.loading': 'Loading…', + 'sync.runs': 'sync runs', + 'sync.totalCost': 'total', + 'sync.when': 'When', + 'sync.source': 'Source', + 'sync.items': 'Items', + 'sync.tokens': 'Tokens', + 'sync.cost': 'Cost', + 'sync.duration': 'Duration', + 'sync.noAuditEntries': 'No sync runs recorded yet.', + 'sync.timeAgo.justNow': 'just now', + 'sync.timeAgo.minutes': '{n}m ago', + 'sync.timeAgo.hours': '{n}h ago', + 'sync.timeAgo.days': '{n}d ago', + 'sync.status.success': 'Success', + 'sync.status.failed': 'Failed', 'common.error': 'Error', 'common.success': 'Success', 'common.back': 'Back', diff --git a/app/src/lib/i18n/es.ts b/app/src/lib/i18n/es.ts index 9187c9039..5ff7800bc 100644 --- a/app/src/lib/i18n/es.ts +++ b/app/src/lib/i18n/es.ts @@ -109,6 +109,21 @@ const messages: TranslationMap = { 'common.create': 'Crear', 'common.search': 'Buscar', 'common.loading': 'cargando…', + 'sync.runs': 'sincronizaciones', + 'sync.totalCost': 'total', + 'sync.when': 'Cuándo', + 'sync.source': 'Origen', + 'sync.items': 'Elementos', + 'sync.tokens': 'Tokens', + 'sync.cost': 'Costo', + 'sync.duration': 'Duración', + 'sync.noAuditEntries': 'Aún no hay sincronizaciones registradas.', + 'sync.timeAgo.justNow': 'ahora mismo', + 'sync.timeAgo.minutes': 'hace {n} min', + 'sync.timeAgo.hours': 'hace {n} h', + 'sync.timeAgo.days': 'hace {n} d', + 'sync.status.success': 'Correcto', + 'sync.status.failed': 'Fallido', 'common.error': 'error', 'common.success': 'Éxito', 'common.back': 'Atrás', diff --git a/app/src/lib/i18n/fr.ts b/app/src/lib/i18n/fr.ts index aad89ddf9..7810a2d6c 100644 --- a/app/src/lib/i18n/fr.ts +++ b/app/src/lib/i18n/fr.ts @@ -110,6 +110,21 @@ const messages: TranslationMap = { 'common.create': 'Créer', 'common.search': 'Rechercher', 'common.loading': 'chargement…', + 'sync.runs': 'synchronisations', + 'sync.totalCost': 'total', + 'sync.when': 'Quand', + 'sync.source': 'Source', + 'sync.items': 'Éléments', + 'sync.tokens': 'Jetons', + 'sync.cost': 'Coût', + 'sync.duration': 'Durée', + 'sync.noAuditEntries': "Aucune synchronisation enregistrée pour l'instant.", + 'sync.timeAgo.justNow': "à l'instant", + 'sync.timeAgo.minutes': 'il y a {n} min', + 'sync.timeAgo.hours': 'il y a {n} h', + 'sync.timeAgo.days': 'il y a {n} j', + 'sync.status.success': 'Réussi', + 'sync.status.failed': 'Échec', 'common.error': 'Erreur', 'common.success': 'Succès', 'common.back': 'Retour', diff --git a/app/src/lib/i18n/hi.ts b/app/src/lib/i18n/hi.ts index 7711ee19d..b73db29bb 100644 --- a/app/src/lib/i18n/hi.ts +++ b/app/src/lib/i18n/hi.ts @@ -108,6 +108,21 @@ const messages: TranslationMap = { 'common.create': 'बनाएं', 'common.search': 'सर्च करें', 'common.loading': 'लोड हो रहा है…', + 'sync.runs': 'सिंक रन', + 'sync.totalCost': 'कुल', + 'sync.when': 'कब', + 'sync.source': 'स्रोत', + 'sync.items': 'आइटम', + 'sync.tokens': 'टोकन', + 'sync.cost': 'लागत', + 'sync.duration': 'अवधि', + 'sync.noAuditEntries': 'अभी तक कोई सिंक रन दर्ज नहीं हुआ।', + 'sync.timeAgo.justNow': 'अभी', + 'sync.timeAgo.minutes': '{n} मि. पहले', + 'sync.timeAgo.hours': '{n} घं. पहले', + 'sync.timeAgo.days': '{n} दि. पहले', + 'sync.status.success': 'सफल', + 'sync.status.failed': 'विफल', 'common.error': 'एरर', 'common.success': 'सफल', 'common.back': 'वापस', diff --git a/app/src/lib/i18n/id.ts b/app/src/lib/i18n/id.ts index 52b0bf6d3..93ed9f276 100644 --- a/app/src/lib/i18n/id.ts +++ b/app/src/lib/i18n/id.ts @@ -108,6 +108,21 @@ const messages: TranslationMap = { 'common.create': 'Buat', 'common.search': 'Cari', 'common.loading': 'memuat…', + 'sync.runs': 'sinkronisasi', + 'sync.totalCost': 'total', + 'sync.when': 'Kapan', + 'sync.source': 'Sumber', + 'sync.items': 'Item', + 'sync.tokens': 'Token', + 'sync.cost': 'Biaya', + 'sync.duration': 'Durasi', + 'sync.noAuditEntries': 'Belum ada sinkronisasi yang tercatat.', + 'sync.timeAgo.justNow': 'baru saja', + 'sync.timeAgo.minutes': '{n} mnt lalu', + 'sync.timeAgo.hours': '{n} jam lalu', + 'sync.timeAgo.days': '{n} hr lalu', + 'sync.status.success': 'Berhasil', + 'sync.status.failed': 'Gagal', 'common.error': 'Kesalahan', 'common.success': 'Berhasil', 'common.back': 'Kembali', diff --git a/app/src/lib/i18n/it.ts b/app/src/lib/i18n/it.ts index 4342635f5..62cd3050a 100644 --- a/app/src/lib/i18n/it.ts +++ b/app/src/lib/i18n/it.ts @@ -110,6 +110,21 @@ const messages: TranslationMap = { 'common.create': 'Crea', 'common.search': 'Cerca', 'common.loading': 'caricamento…', + 'sync.runs': 'sincronizzazioni', + 'sync.totalCost': 'totale', + 'sync.when': 'Quando', + 'sync.source': 'Origine', + 'sync.items': 'Elementi', + 'sync.tokens': 'Token', + 'sync.cost': 'Costo', + 'sync.duration': 'Durata', + 'sync.noAuditEntries': 'Nessuna sincronizzazione registrata finora.', + 'sync.timeAgo.justNow': 'proprio ora', + 'sync.timeAgo.minutes': '{n} min fa', + 'sync.timeAgo.hours': '{n} h fa', + 'sync.timeAgo.days': '{n} g fa', + 'sync.status.success': 'Riuscito', + 'sync.status.failed': 'Non riuscito', 'common.error': 'Errore', 'common.success': 'Successo', 'common.back': 'Indietro', diff --git a/app/src/lib/i18n/ko.ts b/app/src/lib/i18n/ko.ts index 363fc29ad..47f0f7ed6 100644 --- a/app/src/lib/i18n/ko.ts +++ b/app/src/lib/i18n/ko.ts @@ -109,6 +109,21 @@ const messages: TranslationMap = { 'common.create': '생성', 'common.search': '검색', 'common.loading': '로딩 중…', + 'sync.runs': '동기화 실행', + 'sync.totalCost': '합계', + 'sync.when': '시간', + 'sync.source': '소스', + 'sync.items': '항목', + 'sync.tokens': '토큰', + 'sync.cost': '비용', + 'sync.duration': '기간', + 'sync.noAuditEntries': '아직 기록된 동기화 실행이 없습니다.', + 'sync.timeAgo.justNow': '방금', + 'sync.timeAgo.minutes': '{n}분 전', + 'sync.timeAgo.hours': '{n}시간 전', + 'sync.timeAgo.days': '{n}일 전', + 'sync.status.success': '성공', + 'sync.status.failed': '실패', 'common.error': '오류', 'common.success': '성공', 'common.back': '뒤로', diff --git a/app/src/lib/i18n/pl.ts b/app/src/lib/i18n/pl.ts index a67fe5b49..e81bcb04d 100644 --- a/app/src/lib/i18n/pl.ts +++ b/app/src/lib/i18n/pl.ts @@ -109,6 +109,21 @@ const messages: TranslationMap = { 'common.create': 'Utwórz', 'common.search': 'Szukaj', 'common.loading': 'ładowanie…', + 'sync.runs': 'synchronizacji', + 'sync.totalCost': 'razem', + 'sync.when': 'Kiedy', + 'sync.source': 'Źródło', + 'sync.items': 'Elementy', + 'sync.tokens': 'Tokeny', + 'sync.cost': 'Koszt', + 'sync.duration': 'Czas trwania', + 'sync.noAuditEntries': 'Brak zarejestrowanych synchronizacji.', + 'sync.timeAgo.justNow': 'przed chwilą', + 'sync.timeAgo.minutes': '{n} min temu', + 'sync.timeAgo.hours': '{n} godz. temu', + 'sync.timeAgo.days': '{n} dni temu', + 'sync.status.success': 'Sukces', + 'sync.status.failed': 'Niepowodzenie', 'common.error': 'Błąd', 'common.success': 'Sukces', 'common.back': 'Wstecz', diff --git a/app/src/lib/i18n/pt.ts b/app/src/lib/i18n/pt.ts index dfd381d15..963a1c068 100644 --- a/app/src/lib/i18n/pt.ts +++ b/app/src/lib/i18n/pt.ts @@ -110,6 +110,21 @@ const messages: TranslationMap = { 'common.create': 'Criar', 'common.search': 'Pesquisar', 'common.loading': 'carregando…', + 'sync.runs': 'sincronizações', + 'sync.totalCost': 'total', + 'sync.when': 'Quando', + 'sync.source': 'Origem', + 'sync.items': 'Itens', + 'sync.tokens': 'Tokens', + 'sync.cost': 'Custo', + 'sync.duration': 'Duração', + 'sync.noAuditEntries': 'Nenhuma sincronização registrada ainda.', + 'sync.timeAgo.justNow': 'agora mesmo', + 'sync.timeAgo.minutes': 'há {n} min', + 'sync.timeAgo.hours': 'há {n} h', + 'sync.timeAgo.days': 'há {n} d', + 'sync.status.success': 'Sucesso', + 'sync.status.failed': 'Falhou', 'common.error': 'Erro', 'common.success': 'Sucesso', 'common.back': 'Voltar', diff --git a/app/src/lib/i18n/ru.ts b/app/src/lib/i18n/ru.ts index 738b10726..071f421c3 100644 --- a/app/src/lib/i18n/ru.ts +++ b/app/src/lib/i18n/ru.ts @@ -108,6 +108,21 @@ const messages: TranslationMap = { 'common.create': 'Создать', 'common.search': 'Поиск', 'common.loading': 'загрузка…', + 'sync.runs': 'синхронизаций', + 'sync.totalCost': 'итого', + 'sync.when': 'Когда', + 'sync.source': 'Источник', + 'sync.items': 'Элементы', + 'sync.tokens': 'Токены', + 'sync.cost': 'Стоимость', + 'sync.duration': 'Длительность', + 'sync.noAuditEntries': 'Синхронизаций пока не зафиксировано.', + 'sync.timeAgo.justNow': 'только что', + 'sync.timeAgo.minutes': '{n} мин назад', + 'sync.timeAgo.hours': '{n} ч назад', + 'sync.timeAgo.days': '{n} дн назад', + 'sync.status.success': 'Успешно', + 'sync.status.failed': 'Сбой', 'common.error': 'Ошибка', 'common.success': 'Готово', 'common.back': 'Назад', diff --git a/app/src/lib/i18n/zh-CN.ts b/app/src/lib/i18n/zh-CN.ts index 2bb563374..18a1ce40d 100644 --- a/app/src/lib/i18n/zh-CN.ts +++ b/app/src/lib/i18n/zh-CN.ts @@ -108,6 +108,21 @@ const messages: TranslationMap = { 'common.create': '创建', 'common.search': '搜索', 'common.loading': '加载中…', + 'sync.runs': '次同步', + 'sync.totalCost': '合计', + 'sync.when': '时间', + 'sync.source': '来源', + 'sync.items': '条目', + 'sync.tokens': '令牌', + 'sync.cost': '费用', + 'sync.duration': '时长', + 'sync.noAuditEntries': '尚未记录任何同步。', + 'sync.timeAgo.justNow': '刚刚', + 'sync.timeAgo.minutes': '{n}分钟前', + 'sync.timeAgo.hours': '{n}小时前', + 'sync.timeAgo.days': '{n}天前', + 'sync.status.success': '成功', + 'sync.status.failed': '失败', 'common.error': '错误', 'common.success': '成功', 'common.back': '返回',