i18n: translate all shared trip page strings to 9 languages

This commit is contained in:
Maurice
2026-03-30 18:24:22 +02:00
parent 41d4b2a8be
commit 8ddfa8fde0
10 changed files with 160 additions and 16 deletions

View File

@@ -170,6 +170,22 @@ const ar: Record<string, string | { name: string; category: string }[]> = {
'share.permMap': 'الخريطة والخطة',
'share.permBookings': 'الحجوزات',
'share.permPacking': 'الأمتعة',
'shared.expired': 'الرابط منتهي أو غير صالح',
'shared.expiredHint': 'رابط الرحلة المشترك لم يعد نشطًا.',
'shared.readOnly': 'عرض للقراءة فقط',
'shared.tabPlan': 'الخطة',
'shared.tabBookings': 'الحجوزات',
'shared.tabPacking': 'قائمة التعبئة',
'shared.tabBudget': 'الميزانية',
'shared.tabChat': 'الدردشة',
'shared.days': 'أيام',
'shared.places': 'أماكن',
'shared.other': 'أخرى',
'shared.totalBudget': 'إجمالي الميزانية',
'shared.messages': 'رسائل',
'shared.sharedVia': 'تمت المشاركة عبر',
'shared.confirmed': 'مؤكد',
'shared.pending': 'قيد الانتظار',
'share.permBudget': 'الميزانية',
'share.permCollab': 'الدردشة',
'settings.on': 'تشغيل',

View File

@@ -165,6 +165,22 @@ const br: Record<string, string | { name: string; category: string }[]> = {
'share.permMap': 'Mapa e plano',
'share.permBookings': 'Reservas',
'share.permPacking': 'Mala',
'shared.expired': 'Link expirado ou inválido',
'shared.expiredHint': 'Este link de viagem compartilhado não está mais ativo.',
'shared.readOnly': 'Visualização somente leitura',
'shared.tabPlan': 'Plano',
'shared.tabBookings': 'Reservas',
'shared.tabPacking': 'Bagagem',
'shared.tabBudget': 'Orçamento',
'shared.tabChat': 'Chat',
'shared.days': 'dias',
'shared.places': 'lugares',
'shared.other': 'Outros',
'shared.totalBudget': 'Orçamento total',
'shared.messages': 'mensagens',
'shared.sharedVia': 'Compartilhado via',
'shared.confirmed': 'Confirmado',
'shared.pending': 'Pendente',
'share.permBudget': 'Orçamento',
'share.permCollab': 'Chat',
'settings.on': 'Ligado',

View File

@@ -165,6 +165,22 @@ const de: Record<string, string | { name: string; category: string }[]> = {
'share.permMap': 'Karte & Plan',
'share.permBookings': 'Buchungen',
'share.permPacking': 'Packliste',
'shared.expired': 'Link abgelaufen oder ungültig',
'shared.expiredHint': 'Dieser geteilte Reise-Link ist nicht mehr aktiv.',
'shared.readOnly': 'Nur-Lesen Ansicht',
'shared.tabPlan': 'Plan',
'shared.tabBookings': 'Buchungen',
'shared.tabPacking': 'Packliste',
'shared.tabBudget': 'Budget',
'shared.tabChat': 'Chat',
'shared.days': 'Tage',
'shared.places': 'Orte',
'shared.other': 'Sonstige',
'shared.totalBudget': 'Gesamtbudget',
'shared.messages': 'Nachrichten',
'shared.sharedVia': 'Geteilt über',
'shared.confirmed': 'Bestätigt',
'shared.pending': 'Ausstehend',
'share.permBudget': 'Budget',
'share.permCollab': 'Chat',
'settings.on': 'An',

View File

@@ -165,6 +165,22 @@ const en: Record<string, string | { name: string; category: string }[]> = {
'share.permMap': 'Map & Plan',
'share.permBookings': 'Bookings',
'share.permPacking': 'Packing',
'shared.expired': 'Link expired or invalid',
'shared.expiredHint': 'This shared trip link is no longer active.',
'shared.readOnly': 'Read-only shared view',
'shared.tabPlan': 'Plan',
'shared.tabBookings': 'Bookings',
'shared.tabPacking': 'Packing',
'shared.tabBudget': 'Budget',
'shared.tabChat': 'Chat',
'shared.days': 'days',
'shared.places': 'places',
'shared.other': 'Other',
'shared.totalBudget': 'Total Budget',
'shared.messages': 'messages',
'shared.sharedVia': 'Shared via',
'shared.confirmed': 'Confirmed',
'shared.pending': 'Pending',
'share.permBudget': 'Budget',
'share.permCollab': 'Chat',
'settings.on': 'On',

View File

@@ -166,6 +166,22 @@ const es: Record<string, string> = {
'share.permMap': 'Mapa y plan',
'share.permBookings': 'Reservas',
'share.permPacking': 'Equipaje',
'shared.expired': 'Enlace expirado o inválido',
'shared.expiredHint': 'Este enlace de viaje compartido ya no está activo.',
'shared.readOnly': 'Vista de solo lectura',
'shared.tabPlan': 'Plan',
'shared.tabBookings': 'Reservas',
'shared.tabPacking': 'Equipaje',
'shared.tabBudget': 'Presupuesto',
'shared.tabChat': 'Chat',
'shared.days': 'días',
'shared.places': 'lugares',
'shared.other': 'Otro',
'shared.totalBudget': 'Presupuesto total',
'shared.messages': 'mensajes',
'shared.sharedVia': 'Compartido vía',
'shared.confirmed': 'Confirmado',
'shared.pending': 'Pendiente',
'share.permBudget': 'Presupuesto',
'share.permCollab': 'Chat',
'settings.on': 'Activado',

View File

@@ -165,6 +165,22 @@ const fr: Record<string, string> = {
'share.permMap': 'Carte et plan',
'share.permBookings': 'Réservations',
'share.permPacking': 'Bagages',
'shared.expired': 'Lien expiré ou invalide',
'shared.expiredHint': 'Ce lien de partage n\'est plus actif.',
'shared.readOnly': 'Vue en lecture seule',
'shared.tabPlan': 'Plan',
'shared.tabBookings': 'Réservations',
'shared.tabPacking': 'Bagages',
'shared.tabBudget': 'Budget',
'shared.tabChat': 'Chat',
'shared.days': 'jours',
'shared.places': 'lieux',
'shared.other': 'Autre',
'shared.totalBudget': 'Budget total',
'shared.messages': 'messages',
'shared.sharedVia': 'Partagé via',
'shared.confirmed': 'Confirmé',
'shared.pending': 'En attente',
'share.permBudget': 'Budget',
'share.permCollab': 'Chat',
'settings.on': 'Activé',

View File

@@ -165,6 +165,22 @@ const nl: Record<string, string> = {
'share.permMap': 'Kaart en plan',
'share.permBookings': 'Boekingen',
'share.permPacking': 'Paklijst',
'shared.expired': 'Link verlopen of ongeldig',
'shared.expiredHint': 'Deze gedeelde reislink is niet meer actief.',
'shared.readOnly': 'Alleen-lezen weergave',
'shared.tabPlan': 'Plan',
'shared.tabBookings': 'Boekingen',
'shared.tabPacking': 'Paklijst',
'shared.tabBudget': 'Budget',
'shared.tabChat': 'Chat',
'shared.days': 'dagen',
'shared.places': 'plaatsen',
'shared.other': 'Overig',
'shared.totalBudget': 'Totaal budget',
'shared.messages': 'berichten',
'shared.sharedVia': 'Gedeeld via',
'shared.confirmed': 'Bevestigd',
'shared.pending': 'In afwachting',
'share.permBudget': 'Budget',
'share.permCollab': 'Chat',
'settings.on': 'Aan',

View File

@@ -165,6 +165,22 @@ const ru: Record<string, string> = {
'share.permMap': 'Карта и план',
'share.permBookings': 'Бронирования',
'share.permPacking': 'Вещи',
'shared.expired': 'Ссылка устарела или недействительна',
'shared.expiredHint': 'Эта ссылка на поездку больше не активна.',
'shared.readOnly': 'Режим только для чтения',
'shared.tabPlan': 'План',
'shared.tabBookings': 'Бронирования',
'shared.tabPacking': 'Багаж',
'shared.tabBudget': 'Бюджет',
'shared.tabChat': 'Чат',
'shared.days': 'дней',
'shared.places': 'мест',
'shared.other': 'Прочее',
'shared.totalBudget': 'Общий бюджет',
'shared.messages': 'сообщений',
'shared.sharedVia': 'Поделено через',
'shared.confirmed': 'Подтверждено',
'shared.pending': 'Ожидает',
'share.permBudget': 'Бюджет',
'share.permCollab': 'Чат',
'settings.on': 'Вкл.',

View File

@@ -165,6 +165,22 @@ const zh: Record<string, string> = {
'share.permMap': '地图与计划',
'share.permBookings': '预订',
'share.permPacking': '行李',
'shared.expired': '链接已过期或无效',
'shared.expiredHint': '此共享旅行链接已失效。',
'shared.readOnly': '只读共享视图',
'shared.tabPlan': '计划',
'shared.tabBookings': '预订',
'shared.tabPacking': '行李',
'shared.tabBudget': '预算',
'shared.tabChat': '聊天',
'shared.days': '天',
'shared.places': '个地点',
'shared.other': '其他',
'shared.totalBudget': '总预算',
'shared.messages': '条消息',
'shared.sharedVia': '通过以下分享',
'shared.confirmed': '已确认',
'shared.pending': '待确认',
'share.permBudget': '预算',
'share.permCollab': '聊天',
'settings.on': '开',

View File

@@ -55,8 +55,8 @@ export default function SharedTripPage() {
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: '#f3f4f6' }}>
<div style={{ textAlign: 'center', padding: 40 }}>
<div style={{ fontSize: 48, marginBottom: 16 }}>🔒</div>
<h1 style={{ fontSize: 20, fontWeight: 700, color: '#111827' }}>Link expired or invalid</h1>
<p style={{ color: '#6b7280', marginTop: 8 }}>This shared trip link is no longer active.</p>
<h1 style={{ fontSize: 20, fontWeight: 700, color: '#111827' }}>{t('shared.expired')}</h1>
<p style={{ color: '#6b7280', marginTop: 8 }}>{t('shared.expiredHint')}</p>
</div>
</div>
)
@@ -109,11 +109,11 @@ export default function SharedTripPage() {
{[trip.start_date, trip.end_date].filter(Boolean).map((d: string) => new Date(d + 'T00:00:00').toLocaleDateString(locale, { day: 'numeric', month: 'short', year: 'numeric' })).join(' — ')}
</span>
{days?.length > 0 && <span style={{ fontSize: 11, opacity: 0.4 }}>·</span>}
{days?.length > 0 && <span style={{ fontSize: 11, opacity: 0.5 }}>{days.length} days</span>}
{days?.length > 0 && <span style={{ fontSize: 11, opacity: 0.5 }}>{days.length} {t('shared.days')}</span>}
</div>
)}
<div style={{ marginTop: 12, fontSize: 9, fontWeight: 500, letterSpacing: 1.5, textTransform: 'uppercase', opacity: 0.25 }}>Read-only shared view</div>
<div style={{ marginTop: 12, fontSize: 9, fontWeight: 500, letterSpacing: 1.5, textTransform: 'uppercase', opacity: 0.25 }}>{t('shared.readOnly')}</div>
{/* Language picker - top right */}
<div style={{ position: 'absolute', top: 12, right: 12, zIndex: 10 }}>
@@ -142,11 +142,11 @@ export default function SharedTripPage() {
{/* Tabs */}
<div style={{ display: 'flex', gap: 6, marginBottom: 20, overflowX: 'auto', padding: '2px 0' }}>
{[
{ id: 'plan', label: 'Plan', Icon: Map },
...(permissions?.share_bookings ? [{ id: 'bookings', label: 'Bookings', Icon: Ticket }] : []),
...(permissions?.share_packing ? [{ id: 'packing', label: 'Packing', Icon: Luggage }] : []),
...(permissions?.share_budget ? [{ id: 'budget', label: 'Budget', Icon: Wallet }] : []),
...(permissions?.share_collab ? [{ id: 'collab', label: 'Chat', Icon: MessageCircle }] : []),
{ id: 'plan', label: t('shared.tabPlan'), Icon: Map },
...(permissions?.share_bookings ? [{ id: 'bookings', label: t('shared.tabBookings'), Icon: Ticket }] : []),
...(permissions?.share_packing ? [{ id: 'packing', label: t('shared.tabPacking'), Icon: Luggage }] : []),
...(permissions?.share_budget ? [{ id: 'budget', label: t('shared.tabBudget'), Icon: Wallet }] : []),
...(permissions?.share_collab ? [{ id: 'collab', label: t('shared.tabChat'), Icon: MessageCircle }] : []),
].map(tab => (
<button key={tab.id} onClick={() => setActiveTab(tab.id)} style={{
padding: '8px 18px', borderRadius: 12, border: '1.5px solid', cursor: 'pointer',
@@ -202,7 +202,7 @@ export default function SharedTripPage() {
<Hotel size={8} /> {acc.place_name}
</span>
))}
<span style={{ fontSize: 11, color: '#9ca3af' }}>{da.length} {da.length === 1 ? 'place' : 'places'}</span>
<span style={{ fontSize: 11, color: '#9ca3af' }}>{da.length} {t('shared.places')}</span>
</div>
{selectedDay === day.id && merged.length > 0 && (
@@ -287,7 +287,7 @@ export default function SharedTripPage() {
</div>
</div>
<span style={{ fontSize: 10, padding: '2px 8px', borderRadius: 20, fontWeight: 600, background: r.status === 'confirmed' ? 'rgba(22,163,74,0.1)' : 'rgba(217,119,6,0.1)', color: r.status === 'confirmed' ? '#16a34a' : '#d97706' }}>
{r.status}
{r.status === 'confirmed' ? t('shared.confirmed') : t('shared.pending')}
</span>
</div>
)
@@ -298,7 +298,7 @@ export default function SharedTripPage() {
{/* Packing */}
{activeTab === 'packing' && (packing || []).length > 0 && (
<div style={{ background: 'var(--bg-card, white)', borderRadius: 14, border: '1px solid var(--border-faint, #e5e7eb)', overflow: 'hidden' }}>
{Object.entries((packing || []).reduce((g: any, i: any) => { const c = i.category || 'Other'; (g[c] = g[c] || []).push(i); return g }, {})).map(([cat, items]: [string, any]) => (
{Object.entries((packing || []).reduce((g: any, i: any) => { const c = i.category || t('shared.other'); (g[c] = g[c] || []).push(i); return g }, {})).map(([cat, items]: [string, any]) => (
<div key={cat}>
<div style={{ padding: '8px 16px', background: '#f9fafb', fontSize: 11, fontWeight: 700, color: '#6b7280', textTransform: 'uppercase', letterSpacing: '0.05em', borderBottom: '1px solid #f3f4f6' }}>{cat}</div>
{items.map((item: any) => (
@@ -313,13 +313,13 @@ export default function SharedTripPage() {
{/* Budget */}
{activeTab === 'budget' && (budget || []).length > 0 && (() => {
const grouped = (budget || []).reduce((g: any, i: any) => { const c = i.category || 'Other'; (g[c] = g[c] || []).push(i); return g }, {})
const grouped = (budget || []).reduce((g: any, i: any) => { const c = i.category || t('shared.other'); (g[c] = g[c] || []).push(i); return g }, {})
const total = (budget || []).reduce((s: number, i: any) => s + (parseFloat(i.total_price) || 0), 0)
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
{/* Total card */}
<div style={{ background: 'linear-gradient(135deg, #000 0%, #1a1a2e 100%)', borderRadius: 14, padding: '20px 24px', color: 'white' }}>
<div style={{ fontSize: 10, fontWeight: 500, letterSpacing: 1, textTransform: 'uppercase', opacity: 0.5 }}>Total Budget</div>
<div style={{ fontSize: 10, fontWeight: 500, letterSpacing: 1, textTransform: 'uppercase', opacity: 0.5 }}>{t('shared.totalBudget')}</div>
<div style={{ fontSize: 28, fontWeight: 700, marginTop: 4 }}>{total.toLocaleString(locale, { minimumFractionDigits: 2 })} {trip.currency || 'EUR'}</div>
</div>
{/* By category */}
@@ -346,7 +346,7 @@ export default function SharedTripPage() {
<div style={{ background: 'var(--bg-card, white)', borderRadius: 14, border: '1px solid var(--border-faint, #e5e7eb)', overflow: 'hidden' }}>
<div style={{ padding: '12px 16px', background: '#f9fafb', borderBottom: '1px solid #f3f4f6', display: 'flex', alignItems: 'center', gap: 8 }}>
<MessageCircle size={14} color="#6b7280" />
<span style={{ fontSize: 12, fontWeight: 700, color: '#374151' }}>Chat · {(collab || []).length} messages</span>
<span style={{ fontSize: 12, fontWeight: 700, color: '#374151' }}>{t('shared.tabChat')} · {(collab || []).length} {t('shared.messages')}</span>
</div>
<div style={{ maxHeight: 500, overflowY: 'auto', padding: '12px 16px', display: 'flex', flexDirection: 'column', gap: 10 }}>
{(collab || []).map((msg: any, i: number) => {
@@ -382,7 +382,7 @@ export default function SharedTripPage() {
<div style={{ textAlign: 'center', padding: '40px 0 20px' }}>
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 8, padding: '8px 16px', borderRadius: 20, background: 'var(--bg-card, white)', border: '1px solid var(--border-faint, #e5e7eb)', boxShadow: '0 1px 3px rgba(0,0,0,0.04)' }}>
<img src="/icons/icon.svg" alt="TREK" width="18" height="18" style={{ borderRadius: 4 }} />
<span style={{ fontSize: 11, color: '#9ca3af' }}>Shared via <strong style={{ color: '#6b7280' }}>TREK</strong></span>
<span style={{ fontSize: 11, color: '#9ca3af' }}>{t('shared.sharedVia')} <strong style={{ color: '#6b7280' }}>TREK</strong></span>
</div>
<div style={{ marginTop: 8, fontSize: 10, color: '#d1d5db' }}>Made with <span style={{ color: '#ef4444' }}>&hearts;</span> by Maurice · <a href="https://github.com/mauriceboe/TREK" style={{ color: '#9ca3af', textDecoration: 'none' }}>GitHub</a></div>
</div>