From 9c42a01391d2547803354e3acc7d9864c8ef89a9 Mon Sep 17 00:00:00 2001 From: Isaias Tavares Date: Sun, 12 Apr 2026 09:35:22 -0300 Subject: [PATCH] fix(i18n): comprehensive translation audit and fixes across all 14 languages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix critical bug: Photos and Files pages had German text hardcoded in JSX, now use t() keys visible correctly in all languages - Add 16 new translation keys (photos/files UI, login validation, common errors, rate limit message) across all 14 language files - Add missing keys in packing, memories, and budget sections for br, de, it, es, fr, nl, pl, cs, hu, ru, zh, zh-TW, ar - Add 152+ missing keys for zh-TW (entire sections were absent) - Change Vacay addon name to 'Férias' in pt-BR only - Add client-side HTTP 429 interceptor that shows translated rate limit message - Replace hardcoded English fallbacks in TripPlannerPage, DayPlanSidebar, DisplaySettingsTab, MapSettingsTab, AccountTab, and TodoListPanel with t() Co-Authored-By: Claude Sonnet 4.6 --- client/src/api/client.ts | 41 ++++++++++++++++++- client/src/components/Files/FileManager.tsx | 2 +- .../src/components/Photos/PhotoLightbox.tsx | 4 +- client/src/components/Photos/PhotoUpload.tsx | 8 ++-- .../src/components/Planner/DayPlanSidebar.tsx | 34 +++++++-------- client/src/components/Settings/AccountTab.tsx | 2 +- .../Settings/DisplaySettingsTab.tsx | 12 +++--- .../components/Settings/MapSettingsTab.tsx | 2 +- client/src/components/Todo/TodoListPanel.tsx | 8 ++-- client/src/i18n/translations/ar.ts | 18 ++++++++ client/src/i18n/translations/br.ts | 23 ++++++++++- client/src/i18n/translations/cs.ts | 18 ++++++++ client/src/i18n/translations/de.ts | 17 ++++++++ client/src/i18n/translations/en.ts | 18 +++++++- client/src/i18n/translations/es.ts | 21 ++++++++++ client/src/i18n/translations/fr.ts | 21 ++++++++++ client/src/i18n/translations/hu.ts | 18 ++++++++ client/src/i18n/translations/it.ts | 18 ++++++++ client/src/i18n/translations/nl.ts | 18 ++++++++ client/src/i18n/translations/pl.ts | 21 ++++++++++ client/src/i18n/translations/ru.ts | 18 ++++++++ client/src/i18n/translations/zh.ts | 18 ++++++++ client/src/i18n/translations/zhTw.ts | 19 +++++++++ client/src/pages/FilesPage.tsx | 4 +- client/src/pages/LoginPage.tsx | 10 ++--- client/src/pages/PhotosPage.tsx | 4 +- client/src/pages/TripPlannerPage.tsx | 16 ++++---- 27 files changed, 357 insertions(+), 56 deletions(-) diff --git a/client/src/api/client.ts b/client/src/api/client.ts index 49894e51..156da726 100644 --- a/client/src/api/client.ts +++ b/client/src/api/client.ts @@ -1,5 +1,34 @@ import axios, { AxiosInstance } from 'axios' import { getSocketId } from './websocket' +import en from '../i18n/translations/en' +import br from '../i18n/translations/br' +import de from '../i18n/translations/de' +import es from '../i18n/translations/es' +import fr from '../i18n/translations/fr' +import it from '../i18n/translations/it' +import nl from '../i18n/translations/nl' +import pl from '../i18n/translations/pl' +import cs from '../i18n/translations/cs' +import hu from '../i18n/translations/hu' +import ru from '../i18n/translations/ru' +import zh from '../i18n/translations/zh' +import zhTw from '../i18n/translations/zhTw' +import ar from '../i18n/translations/ar' + +const rateLimitTranslations: Record> = { + en, br, de, es, fr, it, nl, pl, cs, hu, ru, zh, 'zh-TW': zhTw, ar, +} + +function translateRateLimit(): string { + const fallback = 'Too many attempts. Please try again later.' + try { + const lang = localStorage.getItem('app_language') || 'en' + const table = rateLimitTranslations[lang] || rateLimitTranslations.en + return (table['common.tooManyAttempts'] as string) || (rateLimitTranslations.en['common.tooManyAttempts'] as string) || fallback + } catch { + return fallback + } +} export const apiClient: AxiosInstance = axios.create({ baseURL: '/api', @@ -21,7 +50,7 @@ apiClient.interceptors.request.use( (error) => Promise.reject(error) ) -// Response interceptor - handle 401 +// Response interceptor - handle 401, 403 MFA, 429 rate limit apiClient.interceptors.response.use( (response) => response, (error) => { @@ -38,6 +67,16 @@ apiClient.interceptors.response.use( ) { window.location.href = '/settings?mfa=required' } + if (error.response?.status === 429) { + const translated = translateRateLimit() + const data = error.response.data as { error?: string } | undefined + if (data && typeof data === 'object') { + data.error = translated + } else { + error.response.data = { error: translated } + } + error.message = translated + } return Promise.reject(error) } ) diff --git a/client/src/components/Files/FileManager.tsx b/client/src/components/Files/FileManager.tsx index 4295c46a..b4b4e200 100644 --- a/client/src/components/Files/FileManager.tsx +++ b/client/src/components/Files/FileManager.tsx @@ -778,7 +778,7 @@ export default function FileManager({ files = [], onUpload, onDelete, onUpdate, title={previewFile.original_name} >

- +

diff --git a/client/src/components/Photos/PhotoLightbox.tsx b/client/src/components/Photos/PhotoLightbox.tsx index cbd483b5..ba6a5738 100644 --- a/client/src/components/Photos/PhotoLightbox.tsx +++ b/client/src/components/Photos/PhotoLightbox.tsx @@ -149,7 +149,7 @@ export function PhotoLightbox({ photos, initialIndex, onClose, onUpdate, onDelet value={caption} onChange={e => setCaption(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleSaveCaption()} - placeholder="Beschriftung hinzufügen..." + placeholder={t('photos.addCaption')} className="flex-1 bg-white/10 text-white border border-white/20 rounded-lg px-3 py-1.5 text-sm focus:outline-none focus:border-white/40" autoFocus /> @@ -173,7 +173,7 @@ export function PhotoLightbox({ photos, initialIndex, onClose, onUpdate, onDelet className="text-white text-sm flex-1 cursor-pointer hover:text-white/80" onClick={() => setEditCaption(true)} > - {photo.caption || Beschriftung hinzufügen...} + {photo.caption || {t('photos.addCaption')}}