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