diff --git a/client/src/components/Admin/AddonManager.tsx b/client/src/components/Admin/AddonManager.tsx
index b8eb170..b82d6df 100644
--- a/client/src/components/Admin/AddonManager.tsx
+++ b/client/src/components/Admin/AddonManager.tsx
@@ -136,8 +136,21 @@ interface AddonRowProps {
t: (key: string) => string
}
+function getAddonLabel(t: (key: string) => string, addon: Addon): { name: string; description: string } {
+ const nameKey = `admin.addons.catalog.${addon.id}.name`
+ const descKey = `admin.addons.catalog.${addon.id}.description`
+ const translatedName = t(nameKey)
+ const translatedDescription = t(descKey)
+
+ return {
+ name: translatedName !== nameKey ? translatedName : addon.name,
+ description: translatedDescription !== descKey ? translatedDescription : addon.description,
+ }
+}
+
function AddonRow({ addon, onToggle, t }: AddonRowProps) {
const isComingSoon = false
+ const label = getAddonLabel(t, addon)
return (
{/* Icon */}
@@ -148,7 +161,7 @@ function AddonRow({ addon, onToggle, t }: AddonRowProps) {
{/* Info */}
- {addon.name}
+ {label.name}
{isComingSoon && (
Coming Soon
@@ -161,7 +174,7 @@ function AddonRow({ addon, onToggle, t }: AddonRowProps) {
{addon.type === 'global' ? t('admin.addons.type.global') : t('admin.addons.type.trip')}
-
{addon.description}
+
{label.description}
{/* Toggle */}
diff --git a/client/src/components/Admin/GitHubPanel.tsx b/client/src/components/Admin/GitHubPanel.tsx
index c31f375..141b701 100644
--- a/client/src/components/Admin/GitHubPanel.tsx
+++ b/client/src/components/Admin/GitHubPanel.tsx
@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react'
import { Tag, Calendar, ExternalLink, ChevronDown, ChevronUp, Loader2, Heart, Coffee } from 'lucide-react'
-import { useTranslation } from '../../i18n'
+import { getLocaleForLanguage, useTranslation } from '../../i18n'
import apiClient from '../../api/client'
const REPO = 'mauriceboe/NOMAD'
@@ -46,7 +46,7 @@ export default function GitHubPanel() {
const formatDate = (dateStr) => {
const d = new Date(dateStr)
- return d.toLocaleDateString(language === 'de' ? 'de-DE' : 'en-US', { day: 'numeric', month: 'short', year: 'numeric' })
+ return d.toLocaleDateString(getLocaleForLanguage(language), { day: 'numeric', month: 'short', year: 'numeric' })
}
// Simple markdown-to-html for release notes (handles headers, bold, lists, links)
diff --git a/client/src/components/Dashboard/TimezoneWidget.tsx b/client/src/components/Dashboard/TimezoneWidget.tsx
index 70ffff4..087937a 100644
--- a/client/src/components/Dashboard/TimezoneWidget.tsx
+++ b/client/src/components/Dashboard/TimezoneWidget.tsx
@@ -23,9 +23,9 @@ const POPULAR_ZONES = [
{ label: 'Cairo', tz: 'Africa/Cairo' },
]
-function getTime(tz) {
+function getTime(tz, locale) {
try {
- return new Date().toLocaleTimeString('de-DE', { timeZone: tz, hour: '2-digit', minute: '2-digit' })
+ return new Date().toLocaleTimeString(locale, { timeZone: tz, hour: '2-digit', minute: '2-digit' })
} catch { return '—' }
}
@@ -41,7 +41,7 @@ function getOffset(tz) {
}
export default function TimezoneWidget() {
- const { t } = useTranslation()
+ const { t, locale } = useTranslation()
const [zones, setZones] = useState(() => {
const saved = localStorage.getItem('dashboard_timezones')
return saved ? JSON.parse(saved) : [
@@ -51,6 +51,9 @@ export default function TimezoneWidget() {
})
const [now, setNow] = useState(Date.now())
const [showAdd, setShowAdd] = useState(false)
+ const [customLabel, setCustomLabel] = useState('')
+ const [customTz, setCustomTz] = useState('')
+ const [customError, setCustomError] = useState('')
useEffect(() => {
const i = setInterval(() => setNow(Date.now()), 10000)
@@ -61,6 +64,20 @@ export default function TimezoneWidget() {
localStorage.setItem('dashboard_timezones', JSON.stringify(zones))
}, [zones])
+ const isValidTz = (tz: string) => {
+ try { Intl.DateTimeFormat('en-US', { timeZone: tz }).format(new Date()); return true } catch { return false }
+ }
+
+ const addCustomZone = () => {
+ const tz = customTz.trim()
+ if (!tz) { setCustomError(t('dashboard.timezoneCustomErrorEmpty')); return }
+ if (!isValidTz(tz)) { setCustomError(t('dashboard.timezoneCustomErrorInvalid')); return }
+ if (zones.find(z => z.tz === tz)) { setCustomError(t('dashboard.timezoneCustomErrorDuplicate')); return }
+ const label = customLabel.trim() || tz.split('/').pop()?.replace(/_/g, ' ') || tz
+ setZones([...zones, { label, tz }])
+ setCustomLabel(''); setCustomTz(''); setCustomError(''); setShowAdd(false)
+ }
+
const addZone = (zone) => {
if (!zones.find(z => z.tz === zone.tz)) {
setZones([...zones, zone])
@@ -70,7 +87,7 @@ export default function TimezoneWidget() {
const removeZone = (tz) => setZones(zones.filter(z => z.tz !== tz))
- const localTime = new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })
+ const localTime = new Date().toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit' })
const rawZone = Intl.DateTimeFormat().resolvedOptions().timeZone
const localZone = rawZone.split('/').pop().replace(/_/g, ' ')
// Show abbreviated timezone name (e.g. CET, CEST, EST)
@@ -96,7 +113,7 @@ export default function TimezoneWidget() {
{zones.map(z => (
-
{getTime(z.tz)}
+
{getTime(z.tz, locale)}
{z.label} {getOffset(z.tz)}
diff --git a/client/src/components/Photos/PhotoGallery.tsx b/client/src/components/Photos/PhotoGallery.tsx
index fdbdf35..fe64a19 100644
--- a/client/src/components/Photos/PhotoGallery.tsx
+++ b/client/src/components/Photos/PhotoGallery.tsx
@@ -3,7 +3,7 @@ import { PhotoLightbox } from './PhotoLightbox'
import { PhotoUpload } from './PhotoUpload'
import { Upload, Camera } from 'lucide-react'
import Modal from '../shared/Modal'
-import { useTranslation } from '../../i18n'
+import { getLocaleForLanguage, useTranslation } from '../../i18n'
import type { Photo, Place, Day } from '../../types'
interface PhotoGalleryProps {
@@ -17,7 +17,7 @@ interface PhotoGalleryProps {
}
export default function PhotoGallery({ photos, onUpload, onDelete, onUpdate, places, days, tripId }: PhotoGalleryProps) {
- const { t } = useTranslation()
+ const { t, language } = useTranslation()
const [lightboxIndex, setLightboxIndex] = useState(null)
const [showUpload, setShowUpload] = useState(false)
const [filterDayId, setFilterDayId] = useState('')
@@ -53,7 +53,7 @@ export default function PhotoGallery({ photos, onUpload, onDelete, onUpdate, pla
Fotos
- {photos.length} Foto{photos.length !== 1 ? 's' : ''}
+ {photos.length} {photos.length !== 1 ? 'Fotos' : 'Foto'}
@@ -65,7 +65,7 @@ export default function PhotoGallery({ photos, onUpload, onDelete, onUpdate, pla
{(days || []).map(day => (
))}
@@ -84,7 +84,7 @@ export default function PhotoGallery({ photos, onUpload, onDelete, onUpdate, pla
className="flex items-center gap-2 bg-slate-900 text-white px-4 py-2 rounded-lg hover:bg-slate-700 text-sm font-medium whitespace-nowrap"
>
- Fotos hochladen
+ {t('common.upload')}
@@ -101,7 +101,7 @@ export default function PhotoGallery({ photos, onUpload, onDelete, onUpdate, pla
style={{ display: 'inline-flex', margin: '0 auto' }}
>