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')}}