Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
3
.github/workflows/test.yml
vendored
3
.github/workflows/test.yml
vendored
@@ -1,5 +1,8 @@
|
||||
name: Tests
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, dev]
|
||||
|
||||
@@ -119,7 +119,7 @@ export default function GitHubPanel() {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{/* Support cards */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||
<a
|
||||
href="https://ko-fi.com/mauriceboe"
|
||||
target="_blank"
|
||||
@@ -156,6 +156,24 @@ export default function GitHubPanel() {
|
||||
</div>
|
||||
<ExternalLink size={14} className="ml-auto flex-shrink-0" style={{ color: 'var(--text-faint)' }} />
|
||||
</a>
|
||||
<a
|
||||
href="https://discord.gg/nSdKaXgN"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="rounded-xl border overflow-hidden flex items-center gap-4 px-5 py-4 transition-all"
|
||||
style={{ background: 'var(--bg-card)', borderColor: 'var(--border-primary)', textDecoration: 'none' }}
|
||||
onMouseEnter={e => { e.currentTarget.style.borderColor = '#5865F2'; e.currentTarget.style.boxShadow = '0 0 0 1px #5865F222' }}
|
||||
onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border-primary)'; e.currentTarget.style.boxShadow = 'none' }}
|
||||
>
|
||||
<div style={{ width: 40, height: 40, borderRadius: 10, background: '#5865F215', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="#5865F2"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-semibold" style={{ color: 'var(--text-primary)' }}>Discord</div>
|
||||
<div className="text-xs" style={{ color: 'var(--text-faint)' }}>Join the community</div>
|
||||
</div>
|
||||
<ExternalLink size={14} className="ml-auto flex-shrink-0" style={{ color: 'var(--text-faint)' }} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Loading / Error / Releases */}
|
||||
|
||||
@@ -232,9 +232,18 @@ export default function Navbar({ tripTitle, tripId, onBack, showBack, onShare }:
|
||||
</button>
|
||||
{appVersion && (
|
||||
<div className="px-4 pt-2 pb-2.5 text-center" style={{ marginTop: 4, borderTop: '1px solid var(--border-secondary)' }}>
|
||||
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 5, background: 'var(--bg-tertiary)', borderRadius: 99, padding: '4px 12px' }}>
|
||||
<img src={dark ? '/text-light.svg' : '/text-dark.svg'} alt="TREK" style={{ height: 10, opacity: 0.5 }} />
|
||||
<span style={{ fontSize: 10, fontWeight: 600, color: 'var(--text-faint)' }}>v{appVersion}</span>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6 }}>
|
||||
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 5, background: 'var(--bg-tertiary)', borderRadius: 99, padding: '4px 12px' }}>
|
||||
<img src={dark ? '/text-light.svg' : '/text-dark.svg'} alt="TREK" style={{ height: 10, opacity: 0.5 }} />
|
||||
<span style={{ fontSize: 10, fontWeight: 600, color: 'var(--text-faint)' }}>v{appVersion}</span>
|
||||
</div>
|
||||
<a href="https://discord.gg/nSdKaXgN" target="_blank" rel="noopener noreferrer"
|
||||
style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 24, height: 24, borderRadius: 99, background: 'var(--bg-tertiary)', transition: 'background 0.15s' }}
|
||||
onMouseEnter={e => e.currentTarget.style.background = '#5865F220'}
|
||||
onMouseLeave={e => e.currentTarget.style.background = 'var(--bg-tertiary)'}
|
||||
title="Discord">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="var(--text-faint)"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1494,17 +1494,17 @@ const ar: Record<string, string | { name: string; category: string }[]> = {
|
||||
'perm.actionHint.collab_edit': 'من يمكنه إنشاء ملاحظات واستطلاعات وإرسال رسائل',
|
||||
'perm.actionHint.share_manage': 'من يمكنه إنشاء أو حذف روابط المشاركة العامة',
|
||||
// Undo
|
||||
'undo.button': 'Undo',
|
||||
'undo.tooltip': 'Undo: {action}',
|
||||
'undo.assignPlace': 'Place assigned to day',
|
||||
'undo.removeAssignment': 'Place removed from day',
|
||||
'undo.reorder': 'Places reordered',
|
||||
'undo.optimize': 'Route optimized',
|
||||
'undo.deletePlace': 'Place deleted',
|
||||
'undo.moveDay': 'Place moved to another day',
|
||||
'undo.lock': 'Place lock toggled',
|
||||
'undo.importGpx': 'GPX import',
|
||||
'undo.importGoogleList': 'Google Maps import',
|
||||
'undo.button': 'تراجع',
|
||||
'undo.tooltip': 'تراجع: {action}',
|
||||
'undo.assignPlace': 'تم تعيين المكان لليوم',
|
||||
'undo.removeAssignment': 'تم إزالة المكان من اليوم',
|
||||
'undo.reorder': 'تمت إعادة ترتيب الأماكن',
|
||||
'undo.optimize': 'تم تحسين المسار',
|
||||
'undo.deletePlace': 'تم حذف المكان',
|
||||
'undo.moveDay': 'تم نقل المكان إلى يوم آخر',
|
||||
'undo.lock': 'تم تبديل قفل المكان',
|
||||
'undo.importGpx': 'استيراد GPX',
|
||||
'undo.importGoogleList': 'استيراد خرائط Google',
|
||||
|
||||
// Notifications
|
||||
'notifications.title': 'الإشعارات',
|
||||
@@ -1519,6 +1519,29 @@ const ar: Record<string, string | { name: string; category: string }[]> = {
|
||||
'notifications.markUnread': 'تحديد كغير مقروء',
|
||||
'notifications.delete': 'حذف',
|
||||
'notifications.system': 'النظام',
|
||||
'memories.error.loadAlbums': 'فشل تحميل الألبومات',
|
||||
'memories.error.linkAlbum': 'فشل ربط الألبوم',
|
||||
'memories.error.unlinkAlbum': 'فشل إلغاء ربط الألبوم',
|
||||
'memories.error.syncAlbum': 'فشل مزامنة الألبوم',
|
||||
'memories.error.loadPhotos': 'فشل تحميل الصور',
|
||||
'memories.error.addPhotos': 'فشل إضافة الصور',
|
||||
'memories.error.removePhoto': 'فشل حذف الصورة',
|
||||
'memories.error.toggleSharing': 'فشل تحديث إعدادات المشاركة',
|
||||
'undo.addPlace': 'تمت إضافة المكان',
|
||||
'undo.done': 'تم التراجع: {action}',
|
||||
'notifications.test.title': 'إشعار تجريبي من {actor}',
|
||||
'notifications.test.text': 'هذا إشعار تجريبي بسيط.',
|
||||
'notifications.test.booleanTitle': 'يطلب منك {actor} الموافقة',
|
||||
'notifications.test.booleanText': 'إشعار تجريبي يتطلب إجابة.',
|
||||
'notifications.test.accept': 'موافقة',
|
||||
'notifications.test.decline': 'رفض',
|
||||
'notifications.test.navigateTitle': 'تحقق من شيء ما',
|
||||
'notifications.test.navigateText': 'إشعار تجريبي للتنقل.',
|
||||
'notifications.test.goThere': 'اذهب إلى هناك',
|
||||
'notifications.test.adminTitle': 'إذاعة المسؤول',
|
||||
'notifications.test.adminText': 'أرسل {actor} إشعاراً تجريبياً لجميع المسؤولين.',
|
||||
'notifications.test.tripTitle': 'نشر {actor} في رحلتك',
|
||||
'notifications.test.tripText': 'إشعار تجريبي للرحلة "{trip}".',
|
||||
}
|
||||
|
||||
export default ar
|
||||
@@ -1489,17 +1489,17 @@ const br: Record<string, string | { name: string; category: string }[]> = {
|
||||
'perm.actionHint.collab_edit': 'Quem pode criar notas, enquetes e enviar mensagens',
|
||||
'perm.actionHint.share_manage': 'Quem pode criar ou excluir links de compartilhamento públicos',
|
||||
// Undo
|
||||
'undo.button': 'Undo',
|
||||
'undo.tooltip': 'Undo: {action}',
|
||||
'undo.assignPlace': 'Place assigned to day',
|
||||
'undo.removeAssignment': 'Place removed from day',
|
||||
'undo.reorder': 'Places reordered',
|
||||
'undo.optimize': 'Route optimized',
|
||||
'undo.deletePlace': 'Place deleted',
|
||||
'undo.moveDay': 'Place moved to another day',
|
||||
'undo.lock': 'Place lock toggled',
|
||||
'undo.importGpx': 'GPX import',
|
||||
'undo.importGoogleList': 'Google Maps import',
|
||||
'undo.button': 'Desfazer',
|
||||
'undo.tooltip': 'Desfazer: {action}',
|
||||
'undo.assignPlace': 'Local atribuído ao dia',
|
||||
'undo.removeAssignment': 'Local removido do dia',
|
||||
'undo.reorder': 'Locais reordenados',
|
||||
'undo.optimize': 'Rota otimizada',
|
||||
'undo.deletePlace': 'Local excluído',
|
||||
'undo.moveDay': 'Local movido para outro dia',
|
||||
'undo.lock': 'Bloqueio do local alternado',
|
||||
'undo.importGpx': 'Importação de GPX',
|
||||
'undo.importGoogleList': 'Importação do Google Maps',
|
||||
|
||||
// Notifications
|
||||
'notifications.title': 'Notificações',
|
||||
@@ -1514,6 +1514,29 @@ const br: Record<string, string | { name: string; category: string }[]> = {
|
||||
'notifications.markUnread': 'Marcar como não lido',
|
||||
'notifications.delete': 'Excluir',
|
||||
'notifications.system': 'Sistema',
|
||||
'memories.error.loadAlbums': 'Falha ao carregar álbuns',
|
||||
'memories.error.linkAlbum': 'Falha ao vincular álbum',
|
||||
'memories.error.unlinkAlbum': 'Falha ao desvincular álbum',
|
||||
'memories.error.syncAlbum': 'Falha ao sincronizar álbum',
|
||||
'memories.error.loadPhotos': 'Falha ao carregar fotos',
|
||||
'memories.error.addPhotos': 'Falha ao adicionar fotos',
|
||||
'memories.error.removePhoto': 'Falha ao remover foto',
|
||||
'memories.error.toggleSharing': 'Falha ao atualizar compartilhamento',
|
||||
'undo.addPlace': 'Local adicionado',
|
||||
'undo.done': 'Desfeito: {action}',
|
||||
'notifications.test.title': 'Notificação de teste de {actor}',
|
||||
'notifications.test.text': 'Esta é uma notificação de teste simples.',
|
||||
'notifications.test.booleanTitle': '{actor} solicita sua aprovação',
|
||||
'notifications.test.booleanText': 'Notificação de teste booleana.',
|
||||
'notifications.test.accept': 'Aprovar',
|
||||
'notifications.test.decline': 'Recusar',
|
||||
'notifications.test.navigateTitle': 'Confira algo',
|
||||
'notifications.test.navigateText': 'Notificação de teste de navegação.',
|
||||
'notifications.test.goThere': 'Ir lá',
|
||||
'notifications.test.adminTitle': 'Transmissão do admin',
|
||||
'notifications.test.adminText': '{actor} enviou uma notificação de teste para todos os admins.',
|
||||
'notifications.test.tripTitle': '{actor} postou na sua viagem',
|
||||
'notifications.test.tripText': 'Notificação de teste para a viagem "{trip}".',
|
||||
}
|
||||
|
||||
export default br
|
||||
@@ -1492,17 +1492,17 @@ const cs: Record<string, string | { name: string; category: string }[]> = {
|
||||
'perm.actionHint.collab_edit': 'Kdo může vytvářet poznámky, hlasování a posílat zprávy',
|
||||
'perm.actionHint.share_manage': 'Kdo může vytvářet nebo mazat veřejné odkazy ke sdílení',
|
||||
// Undo
|
||||
'undo.button': 'Undo',
|
||||
'undo.tooltip': 'Undo: {action}',
|
||||
'undo.assignPlace': 'Place assigned to day',
|
||||
'undo.removeAssignment': 'Place removed from day',
|
||||
'undo.reorder': 'Places reordered',
|
||||
'undo.optimize': 'Route optimized',
|
||||
'undo.deletePlace': 'Place deleted',
|
||||
'undo.moveDay': 'Place moved to another day',
|
||||
'undo.lock': 'Place lock toggled',
|
||||
'undo.importGpx': 'GPX import',
|
||||
'undo.importGoogleList': 'Google Maps import',
|
||||
'undo.button': 'Zpět',
|
||||
'undo.tooltip': 'Zpět: {action}',
|
||||
'undo.assignPlace': 'Místo přiřazeno ke dni',
|
||||
'undo.removeAssignment': 'Místo odebráno ze dne',
|
||||
'undo.reorder': 'Místa přeseřazena',
|
||||
'undo.optimize': 'Trasa optimalizována',
|
||||
'undo.deletePlace': 'Místo smazáno',
|
||||
'undo.moveDay': 'Místo přesunuto na jiný den',
|
||||
'undo.lock': 'Zámek místa přepnut',
|
||||
'undo.importGpx': 'Import GPX',
|
||||
'undo.importGoogleList': 'Import z Google Maps',
|
||||
|
||||
// Notifications
|
||||
'notifications.title': 'Oznámení',
|
||||
@@ -1517,6 +1517,31 @@ const cs: Record<string, string | { name: string; category: string }[]> = {
|
||||
'notifications.markUnread': 'Označit jako nepřečtené',
|
||||
'notifications.delete': 'Smazat',
|
||||
'notifications.system': 'Systém',
|
||||
'settings.mustChangePassword': 'Před pokračováním musíte změnit heslo.',
|
||||
'atlas.searchCountry': 'Hledat zemi...',
|
||||
'memories.error.loadAlbums': 'Načtení alb se nezdařilo',
|
||||
'memories.error.linkAlbum': 'Propojení alba se nezdařilo',
|
||||
'memories.error.unlinkAlbum': 'Odpojení alba se nezdařilo',
|
||||
'memories.error.syncAlbum': 'Synchronizace alba se nezdařila',
|
||||
'memories.error.loadPhotos': 'Načtení fotek se nezdařilo',
|
||||
'memories.error.addPhotos': 'Přidání fotek se nezdařilo',
|
||||
'memories.error.removePhoto': 'Odebrání fotky se nezdařilo',
|
||||
'memories.error.toggleSharing': 'Aktualizace sdílení se nezdařila',
|
||||
'undo.addPlace': 'Místo přidáno',
|
||||
'undo.done': 'Vráceno zpět: {action}',
|
||||
'notifications.test.title': 'Testovací oznámení od {actor}',
|
||||
'notifications.test.text': 'Toto je jednoduché testovací oznámení.',
|
||||
'notifications.test.booleanTitle': '{actor} žádá o vaše schválení',
|
||||
'notifications.test.booleanText': 'Testovací oznámení s volbou.',
|
||||
'notifications.test.accept': 'Schválit',
|
||||
'notifications.test.decline': 'Odmítnout',
|
||||
'notifications.test.navigateTitle': 'Podívejte se na toto',
|
||||
'notifications.test.navigateText': 'Testovací navigační oznámení.',
|
||||
'notifications.test.goThere': 'Přejít tam',
|
||||
'notifications.test.adminTitle': 'Hromadná zpráva pro správce',
|
||||
'notifications.test.adminText': '{actor} odeslal testovací oznámení všem správcům.',
|
||||
'notifications.test.tripTitle': '{actor} přispěl do vašeho výletu',
|
||||
'notifications.test.tripText': 'Testovací oznámení pro výlet "{trip}".',
|
||||
}
|
||||
|
||||
export default cs
|
||||
@@ -1491,17 +1491,17 @@ const de: Record<string, string | { name: string; category: string }[]> = {
|
||||
'perm.actionHint.collab_edit': 'Wer kann Notizen, Umfragen erstellen und Nachrichten senden',
|
||||
'perm.actionHint.share_manage': 'Wer kann öffentliche Freigabelinks erstellen oder löschen',
|
||||
// Undo
|
||||
'undo.button': 'Undo',
|
||||
'undo.tooltip': 'Undo: {action}',
|
||||
'undo.assignPlace': 'Place assigned to day',
|
||||
'undo.removeAssignment': 'Place removed from day',
|
||||
'undo.reorder': 'Places reordered',
|
||||
'undo.optimize': 'Route optimized',
|
||||
'undo.deletePlace': 'Place deleted',
|
||||
'undo.moveDay': 'Place moved to another day',
|
||||
'undo.lock': 'Place lock toggled',
|
||||
'undo.importGpx': 'GPX import',
|
||||
'undo.importGoogleList': 'Google Maps import',
|
||||
'undo.button': 'Rückgängig',
|
||||
'undo.tooltip': 'Rückgängig: {action}',
|
||||
'undo.assignPlace': 'Ort einem Tag zugewiesen',
|
||||
'undo.removeAssignment': 'Ort von Tag entfernt',
|
||||
'undo.reorder': 'Orte neu sortiert',
|
||||
'undo.optimize': 'Route optimiert',
|
||||
'undo.deletePlace': 'Ort gelöscht',
|
||||
'undo.moveDay': 'Ort zu anderem Tag verschoben',
|
||||
'undo.lock': 'Ortssperre umgeschaltet',
|
||||
'undo.importGpx': 'GPX-Import',
|
||||
'undo.importGoogleList': 'Google Maps-Import',
|
||||
|
||||
// Notifications
|
||||
'notifications.title': 'Benachrichtigungen',
|
||||
@@ -1516,6 +1516,29 @@ const de: Record<string, string | { name: string; category: string }[]> = {
|
||||
'notifications.markUnread': 'Als ungelesen markieren',
|
||||
'notifications.delete': 'Löschen',
|
||||
'notifications.system': 'System',
|
||||
'memories.error.loadAlbums': 'Alben konnten nicht geladen werden',
|
||||
'memories.error.linkAlbum': 'Album konnte nicht verknüpft werden',
|
||||
'memories.error.unlinkAlbum': 'Album konnte nicht getrennt werden',
|
||||
'memories.error.syncAlbum': 'Album konnte nicht synchronisiert werden',
|
||||
'memories.error.loadPhotos': 'Fotos konnten nicht geladen werden',
|
||||
'memories.error.addPhotos': 'Fotos konnten nicht hinzugefügt werden',
|
||||
'memories.error.removePhoto': 'Foto konnte nicht entfernt werden',
|
||||
'memories.error.toggleSharing': 'Freigabe konnte nicht aktualisiert werden',
|
||||
'undo.addPlace': 'Ort hinzugefügt',
|
||||
'undo.done': 'Rückgängig gemacht: {action}',
|
||||
'notifications.test.title': 'Testbenachrichtigung von {actor}',
|
||||
'notifications.test.text': 'Dies ist eine einfache Testbenachrichtigung.',
|
||||
'notifications.test.booleanTitle': '{actor} bittet um Ihre Zustimmung',
|
||||
'notifications.test.booleanText': 'Dies ist eine boolesche Testbenachrichtigung.',
|
||||
'notifications.test.accept': 'Genehmigen',
|
||||
'notifications.test.decline': 'Ablehnen',
|
||||
'notifications.test.navigateTitle': 'Etwas ansehen',
|
||||
'notifications.test.navigateText': 'Dies ist eine Navigations-Testbenachrichtigung.',
|
||||
'notifications.test.goThere': 'Dorthin',
|
||||
'notifications.test.adminTitle': 'Admin-Broadcast',
|
||||
'notifications.test.adminText': '{actor} hat eine Testbenachrichtigung an alle Admins gesendet.',
|
||||
'notifications.test.tripTitle': '{actor} hat in Ihrer Reise gepostet',
|
||||
'notifications.test.tripText': 'Testbenachrichtigung für Reise "{trip}".',
|
||||
}
|
||||
|
||||
export default de
|
||||
@@ -1496,17 +1496,17 @@ const es: Record<string, string> = {
|
||||
'perm.actionHint.collab_edit': 'Quién puede crear notas, encuestas y enviar mensajes',
|
||||
'perm.actionHint.share_manage': 'Quién puede crear o eliminar enlaces compartidos públicos',
|
||||
// Undo
|
||||
'undo.button': 'Undo',
|
||||
'undo.tooltip': 'Undo: {action}',
|
||||
'undo.assignPlace': 'Place assigned to day',
|
||||
'undo.removeAssignment': 'Place removed from day',
|
||||
'undo.reorder': 'Places reordered',
|
||||
'undo.optimize': 'Route optimized',
|
||||
'undo.deletePlace': 'Place deleted',
|
||||
'undo.moveDay': 'Place moved to another day',
|
||||
'undo.lock': 'Place lock toggled',
|
||||
'undo.importGpx': 'GPX import',
|
||||
'undo.importGoogleList': 'Google Maps import',
|
||||
'undo.button': 'Deshacer',
|
||||
'undo.tooltip': 'Deshacer: {action}',
|
||||
'undo.assignPlace': 'Lugar asignado al día',
|
||||
'undo.removeAssignment': 'Lugar eliminado del día',
|
||||
'undo.reorder': 'Lugares reordenados',
|
||||
'undo.optimize': 'Ruta optimizada',
|
||||
'undo.deletePlace': 'Lugar eliminado',
|
||||
'undo.moveDay': 'Lugar movido a otro día',
|
||||
'undo.lock': 'Bloqueo de lugar activado/desactivado',
|
||||
'undo.importGpx': 'Importación GPX',
|
||||
'undo.importGoogleList': 'Importación de Google Maps',
|
||||
|
||||
// Notifications
|
||||
'notifications.title': 'Notificaciones',
|
||||
@@ -1521,6 +1521,29 @@ const es: Record<string, string> = {
|
||||
'notifications.markUnread': 'Marcar como no leída',
|
||||
'notifications.delete': 'Eliminar',
|
||||
'notifications.system': 'Sistema',
|
||||
'memories.error.loadAlbums': 'Error al cargar los álbumes',
|
||||
'memories.error.linkAlbum': 'Error al vincular el álbum',
|
||||
'memories.error.unlinkAlbum': 'Error al desvincular el álbum',
|
||||
'memories.error.syncAlbum': 'Error al sincronizar el álbum',
|
||||
'memories.error.loadPhotos': 'Error al cargar las fotos',
|
||||
'memories.error.addPhotos': 'Error al agregar las fotos',
|
||||
'memories.error.removePhoto': 'Error al eliminar la foto',
|
||||
'memories.error.toggleSharing': 'Error al actualizar el uso compartido',
|
||||
'undo.addPlace': 'Lugar agregado',
|
||||
'undo.done': 'Deshecho: {action}',
|
||||
'notifications.test.title': 'Notificación de prueba de {actor}',
|
||||
'notifications.test.text': 'Esta es una notificación de prueba simple.',
|
||||
'notifications.test.booleanTitle': '{actor} solicita tu aprobación',
|
||||
'notifications.test.booleanText': 'Notificación de prueba booleana.',
|
||||
'notifications.test.accept': 'Aprobar',
|
||||
'notifications.test.decline': 'Rechazar',
|
||||
'notifications.test.navigateTitle': 'Mira esto',
|
||||
'notifications.test.navigateText': 'Notificación de prueba de navegación.',
|
||||
'notifications.test.goThere': 'Ir allí',
|
||||
'notifications.test.adminTitle': 'Difusión de administrador',
|
||||
'notifications.test.adminText': '{actor} envió una notificación de prueba a todos los administradores.',
|
||||
'notifications.test.tripTitle': '{actor} publicó en tu viaje',
|
||||
'notifications.test.tripText': 'Notificación de prueba para el viaje "{trip}".',
|
||||
}
|
||||
|
||||
export default es
|
||||
@@ -1490,17 +1490,17 @@ const fr: Record<string, string> = {
|
||||
'perm.actionHint.collab_edit': 'Qui peut créer des notes, des sondages et envoyer des messages',
|
||||
'perm.actionHint.share_manage': 'Qui peut créer ou supprimer des liens de partage publics',
|
||||
// Undo
|
||||
'undo.button': 'Undo',
|
||||
'undo.tooltip': 'Undo: {action}',
|
||||
'undo.assignPlace': 'Place assigned to day',
|
||||
'undo.removeAssignment': 'Place removed from day',
|
||||
'undo.reorder': 'Places reordered',
|
||||
'undo.optimize': 'Route optimized',
|
||||
'undo.deletePlace': 'Place deleted',
|
||||
'undo.moveDay': 'Place moved to another day',
|
||||
'undo.lock': 'Place lock toggled',
|
||||
'undo.importGpx': 'GPX import',
|
||||
'undo.importGoogleList': 'Google Maps import',
|
||||
'undo.button': 'Annuler',
|
||||
'undo.tooltip': 'Annuler : {action}',
|
||||
'undo.assignPlace': 'Lieu ajouté au jour',
|
||||
'undo.removeAssignment': 'Lieu retiré du jour',
|
||||
'undo.reorder': 'Lieux réorganisés',
|
||||
'undo.optimize': 'Itinéraire optimisé',
|
||||
'undo.deletePlace': 'Lieu supprimé',
|
||||
'undo.moveDay': 'Lieu déplacé vers un autre jour',
|
||||
'undo.lock': 'Verrouillage du lieu modifié',
|
||||
'undo.importGpx': 'Import GPX',
|
||||
'undo.importGoogleList': 'Import Google Maps',
|
||||
|
||||
// Notifications
|
||||
'notifications.title': 'Notifications',
|
||||
@@ -1515,6 +1515,29 @@ const fr: Record<string, string> = {
|
||||
'notifications.markUnread': 'Marquer comme non lu',
|
||||
'notifications.delete': 'Supprimer',
|
||||
'notifications.system': 'Système',
|
||||
'memories.error.loadAlbums': 'Impossible de charger les albums',
|
||||
'memories.error.linkAlbum': 'Impossible de lier l\'album',
|
||||
'memories.error.unlinkAlbum': 'Impossible de dissocier l\'album',
|
||||
'memories.error.syncAlbum': 'Impossible de synchroniser l\'album',
|
||||
'memories.error.loadPhotos': 'Impossible de charger les photos',
|
||||
'memories.error.addPhotos': 'Impossible d\'ajouter les photos',
|
||||
'memories.error.removePhoto': 'Impossible de supprimer la photo',
|
||||
'memories.error.toggleSharing': 'Impossible de mettre à jour le partage',
|
||||
'undo.addPlace': 'Lieu ajouté',
|
||||
'undo.done': 'Annulé : {action}',
|
||||
'notifications.test.title': 'Notification test de {actor}',
|
||||
'notifications.test.text': 'Ceci est une simple notification de test.',
|
||||
'notifications.test.booleanTitle': '{actor} demande votre approbation',
|
||||
'notifications.test.booleanText': 'Notification de test booléenne.',
|
||||
'notifications.test.accept': 'Approuver',
|
||||
'notifications.test.decline': 'Refuser',
|
||||
'notifications.test.navigateTitle': 'Allez voir quelque chose',
|
||||
'notifications.test.navigateText': 'Notification de test de navigation.',
|
||||
'notifications.test.goThere': 'Y aller',
|
||||
'notifications.test.adminTitle': 'Diffusion admin',
|
||||
'notifications.test.adminText': '{actor} a envoyé une notification de test à tous les admins.',
|
||||
'notifications.test.tripTitle': '{actor} a publié dans votre voyage',
|
||||
'notifications.test.tripText': 'Notification de test pour le voyage "{trip}".',
|
||||
}
|
||||
|
||||
export default fr
|
||||
@@ -1491,17 +1491,17 @@ const hu: Record<string, string | { name: string; category: string }[]> = {
|
||||
'perm.actionHint.collab_edit': 'Ki hozhat létre jegyzeteket, szavazásokat és küldhet üzeneteket',
|
||||
'perm.actionHint.share_manage': 'Ki hozhat létre vagy törölhet nyilvános megosztási linkeket',
|
||||
// Undo
|
||||
'undo.button': 'Undo',
|
||||
'undo.tooltip': 'Undo: {action}',
|
||||
'undo.assignPlace': 'Place assigned to day',
|
||||
'undo.removeAssignment': 'Place removed from day',
|
||||
'undo.reorder': 'Places reordered',
|
||||
'undo.optimize': 'Route optimized',
|
||||
'undo.deletePlace': 'Place deleted',
|
||||
'undo.moveDay': 'Place moved to another day',
|
||||
'undo.lock': 'Place lock toggled',
|
||||
'undo.importGpx': 'GPX import',
|
||||
'undo.importGoogleList': 'Google Maps import',
|
||||
'undo.button': 'Visszavonás',
|
||||
'undo.tooltip': 'Visszavonás: {action}',
|
||||
'undo.assignPlace': 'Hely naphoz rendelve',
|
||||
'undo.removeAssignment': 'Hely eltávolítva a napról',
|
||||
'undo.reorder': 'Helyek átrendezve',
|
||||
'undo.optimize': 'Útvonal optimalizálva',
|
||||
'undo.deletePlace': 'Hely törölve',
|
||||
'undo.moveDay': 'Hely áthelyezve másik napra',
|
||||
'undo.lock': 'Hely zárolása váltva',
|
||||
'undo.importGpx': 'GPX importálás',
|
||||
'undo.importGoogleList': 'Google Maps importálás',
|
||||
|
||||
// Notifications
|
||||
'notifications.title': 'Értesítések',
|
||||
@@ -1516,6 +1516,29 @@ const hu: Record<string, string | { name: string; category: string }[]> = {
|
||||
'notifications.markUnread': 'Olvasatlannak jelölés',
|
||||
'notifications.delete': 'Törlés',
|
||||
'notifications.system': 'Rendszer',
|
||||
'memories.error.loadAlbums': 'Az albumok betöltése sikertelen',
|
||||
'memories.error.linkAlbum': 'Az album csatolása sikertelen',
|
||||
'memories.error.unlinkAlbum': 'Az album leválasztása sikertelen',
|
||||
'memories.error.syncAlbum': 'Az album szinkronizálása sikertelen',
|
||||
'memories.error.loadPhotos': 'A fotók betöltése sikertelen',
|
||||
'memories.error.addPhotos': 'A fotók hozzáadása sikertelen',
|
||||
'memories.error.removePhoto': 'A fotó eltávolítása sikertelen',
|
||||
'memories.error.toggleSharing': 'A megosztás frissítése sikertelen',
|
||||
'undo.addPlace': 'Hely hozzáadva',
|
||||
'undo.done': 'Visszavonva: {action}',
|
||||
'notifications.test.title': 'Teszt értesítés {actor} részéről',
|
||||
'notifications.test.text': 'Ez egy egyszerű teszt értesítés.',
|
||||
'notifications.test.booleanTitle': '{actor} jóváhagyásodat kéri',
|
||||
'notifications.test.booleanText': 'Teszt igen/nem értesítés.',
|
||||
'notifications.test.accept': 'Jóváhagyás',
|
||||
'notifications.test.decline': 'Elutasítás',
|
||||
'notifications.test.navigateTitle': 'Nézz meg valamit',
|
||||
'notifications.test.navigateText': 'Teszt navigációs értesítés.',
|
||||
'notifications.test.goThere': 'Odamegyek',
|
||||
'notifications.test.adminTitle': 'Adminisztrátor üzenet',
|
||||
'notifications.test.adminText': '{actor} teszt értesítést küldött az összes adminisztrátornak.',
|
||||
'notifications.test.tripTitle': '{actor} üzenetet küldött az utazásodba',
|
||||
'notifications.test.tripText': 'Teszt értesítés a(z) "{trip}" utazáshoz.',
|
||||
}
|
||||
|
||||
export default hu
|
||||
@@ -1518,6 +1518,27 @@ const it: Record<string, string | { name: string; category: string }[]> = {
|
||||
'notifications.markUnread': 'Segna come non letto',
|
||||
'notifications.delete': 'Elimina',
|
||||
'notifications.system': 'Sistema',
|
||||
'memories.error.loadAlbums': 'Caricamento album non riuscito',
|
||||
'memories.error.linkAlbum': 'Collegamento album non riuscito',
|
||||
'memories.error.unlinkAlbum': 'Scollegamento album non riuscito',
|
||||
'memories.error.syncAlbum': 'Sincronizzazione album non riuscita',
|
||||
'memories.error.loadPhotos': 'Caricamento foto non riuscito',
|
||||
'memories.error.addPhotos': 'Aggiunta foto non riuscita',
|
||||
'memories.error.removePhoto': 'Rimozione foto non riuscita',
|
||||
'memories.error.toggleSharing': 'Aggiornamento condivisione non riuscito',
|
||||
'notifications.test.title': 'Notifica di test da {actor}',
|
||||
'notifications.test.text': 'Questa è una semplice notifica di test.',
|
||||
'notifications.test.booleanTitle': '{actor} richiede la tua approvazione',
|
||||
'notifications.test.booleanText': 'Notifica di test con risposta.',
|
||||
'notifications.test.accept': 'Approva',
|
||||
'notifications.test.decline': 'Rifiuta',
|
||||
'notifications.test.navigateTitle': 'Dai un\'occhiata',
|
||||
'notifications.test.navigateText': 'Notifica di test con navigazione.',
|
||||
'notifications.test.goThere': 'Vai',
|
||||
'notifications.test.adminTitle': 'Comunicazione admin',
|
||||
'notifications.test.adminText': '{actor} ha inviato una notifica di test a tutti gli amministratori.',
|
||||
'notifications.test.tripTitle': '{actor} ha pubblicato nel tuo viaggio',
|
||||
'notifications.test.tripText': 'Notifica di test per il viaggio "{trip}".',
|
||||
}
|
||||
|
||||
export default it
|
||||
@@ -1490,17 +1490,17 @@ const nl: Record<string, string> = {
|
||||
'perm.actionHint.collab_edit': 'Wie kan notities, polls aanmaken en berichten versturen',
|
||||
'perm.actionHint.share_manage': 'Wie kan openbare deellinks aanmaken of verwijderen',
|
||||
// Undo
|
||||
'undo.button': 'Undo',
|
||||
'undo.tooltip': 'Undo: {action}',
|
||||
'undo.assignPlace': 'Place assigned to day',
|
||||
'undo.removeAssignment': 'Place removed from day',
|
||||
'undo.reorder': 'Places reordered',
|
||||
'undo.optimize': 'Route optimized',
|
||||
'undo.deletePlace': 'Place deleted',
|
||||
'undo.moveDay': 'Place moved to another day',
|
||||
'undo.lock': 'Place lock toggled',
|
||||
'undo.importGpx': 'GPX import',
|
||||
'undo.importGoogleList': 'Google Maps import',
|
||||
'undo.button': 'Ongedaan maken',
|
||||
'undo.tooltip': 'Ongedaan maken: {action}',
|
||||
'undo.assignPlace': 'Locatie aan dag toegewezen',
|
||||
'undo.removeAssignment': 'Locatie uit dag verwijderd',
|
||||
'undo.reorder': 'Locaties hergeordend',
|
||||
'undo.optimize': 'Route geoptimaliseerd',
|
||||
'undo.deletePlace': 'Locatie verwijderd',
|
||||
'undo.moveDay': 'Locatie naar andere dag verplaatst',
|
||||
'undo.lock': 'Vergrendeling locatie gewijzigd',
|
||||
'undo.importGpx': 'GPX-import',
|
||||
'undo.importGoogleList': 'Google Maps-import',
|
||||
|
||||
// Notifications
|
||||
'notifications.title': 'Meldingen',
|
||||
@@ -1515,6 +1515,29 @@ const nl: Record<string, string> = {
|
||||
'notifications.markUnread': 'Markeren als ongelezen',
|
||||
'notifications.delete': 'Verwijderen',
|
||||
'notifications.system': 'Systeem',
|
||||
'memories.error.loadAlbums': 'Albums laden mislukt',
|
||||
'memories.error.linkAlbum': 'Album koppelen mislukt',
|
||||
'memories.error.unlinkAlbum': 'Album ontkoppelen mislukt',
|
||||
'memories.error.syncAlbum': 'Album synchroniseren mislukt',
|
||||
'memories.error.loadPhotos': 'Foto\'s laden mislukt',
|
||||
'memories.error.addPhotos': 'Foto\'s toevoegen mislukt',
|
||||
'memories.error.removePhoto': 'Foto verwijderen mislukt',
|
||||
'memories.error.toggleSharing': 'Delen bijwerken mislukt',
|
||||
'undo.addPlace': 'Locatie toegevoegd',
|
||||
'undo.done': 'Ongedaan gemaakt: {action}',
|
||||
'notifications.test.title': 'Testmelding van {actor}',
|
||||
'notifications.test.text': 'Dit is een eenvoudige testmelding.',
|
||||
'notifications.test.booleanTitle': '{actor} vraagt om uw goedkeuring',
|
||||
'notifications.test.booleanText': 'Booleaanse testmelding.',
|
||||
'notifications.test.accept': 'Goedkeuren',
|
||||
'notifications.test.decline': 'Afwijzen',
|
||||
'notifications.test.navigateTitle': 'Bekijk iets',
|
||||
'notifications.test.navigateText': 'Navigatie-testmelding.',
|
||||
'notifications.test.goThere': 'Ga erheen',
|
||||
'notifications.test.adminTitle': 'Admin-broadcast',
|
||||
'notifications.test.adminText': '{actor} heeft een testmelding naar alle admins gestuurd.',
|
||||
'notifications.test.tripTitle': '{actor} heeft gepost in uw reis',
|
||||
'notifications.test.tripText': 'Testmelding voor reis "{trip}".',
|
||||
}
|
||||
|
||||
export default nl
|
||||
@@ -661,7 +661,6 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
|
||||
'atlas.statsTab': 'Statystyki',
|
||||
'atlas.bucketTab': 'Lista marzeń',
|
||||
'atlas.addBucket': 'Dodaj do listy marzeń',
|
||||
'atlas.bucketNamePlaceholder': 'Miejsce lub cel podróży...',
|
||||
'atlas.bucketNotesPlaceholder': 'Notatki (opcjonalnie)',
|
||||
'atlas.bucketEmpty': 'Twoja lista marzeń jest pusta',
|
||||
'atlas.bucketEmptyHint': 'Dodaj miejsca, które chcesz odwiedzić',
|
||||
@@ -674,7 +673,6 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
|
||||
'atlas.nextTrip': 'Następna podróż',
|
||||
'atlas.daysLeft': 'dni do wyjazdu',
|
||||
'atlas.streak': 'Streak',
|
||||
'atlas.year': 'rok',
|
||||
'atlas.years': 'lata',
|
||||
'atlas.yearInRow': 'rok z rzędu',
|
||||
'atlas.yearsInRow': 'lat z rzędu',
|
||||
@@ -1388,6 +1386,151 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
|
||||
'collab.polls.options': 'Opcje',
|
||||
'collab.polls.delete': 'Usuń',
|
||||
'collab.polls.closedSection': 'Zamknięte',
|
||||
'common.import': 'Importuj',
|
||||
'common.saved': 'Zapisano',
|
||||
'trips.reminder': 'Przypomnienie',
|
||||
'trips.reminderNone': 'Brak',
|
||||
'trips.reminderDay': 'dzień',
|
||||
'trips.reminderDays': 'dni',
|
||||
'trips.reminderCustom': 'Niestandardowe',
|
||||
'trips.reminderDaysBefore': 'dni przed wyjazdem',
|
||||
'trips.reminderDisabledHint': 'Przypomnienia o podróżach są wyłączone.',
|
||||
'dashboard.members': 'Towarzysze',
|
||||
'dashboard.copyTrip': 'Kopiuj',
|
||||
'dashboard.copySuffix': 'kopia',
|
||||
'dashboard.toast.copied': 'Podróż skopiowana!',
|
||||
'dashboard.toast.copyError': 'Nie udało się skopiować podróży',
|
||||
'admin.notifications.title': 'Powiadomienia',
|
||||
'admin.notifications.hint': 'Wybierz jeden kanał powiadomień.',
|
||||
'admin.notifications.none': 'Wyłączone',
|
||||
'admin.notifications.email': 'Email (SMTP)',
|
||||
'admin.notifications.webhook': 'Webhook',
|
||||
'admin.notifications.events': 'Zdarzenia powiadomień',
|
||||
'admin.notifications.eventsHint': 'Wybierz zdarzenia wyzwalające powiadomienia.',
|
||||
'admin.notifications.configureFirst': 'Najpierw skonfiguruj ustawienia SMTP lub webhook.',
|
||||
'admin.notifications.save': 'Zapisz ustawienia powiadomień',
|
||||
'admin.notifications.saved': 'Ustawienia powiadomień zapisane',
|
||||
'admin.notifications.testWebhook': 'Wyślij testowy webhook',
|
||||
'admin.notifications.testWebhookSuccess': 'Testowy webhook wysłany pomyślnie',
|
||||
'admin.notifications.testWebhookFailed': 'Testowy webhook nie powiódł się',
|
||||
'admin.webhook.hint': 'Wysyłaj powiadomienia do zewnętrznego webhooka.',
|
||||
'settings.notificationsDisabled': 'Powiadomienia nie są skonfigurowane.',
|
||||
'settings.notificationsActive': 'Aktywny kanał',
|
||||
'settings.notificationsManagedByAdmin': 'Zdarzenia konfigurowane przez administratora.',
|
||||
'settings.mustChangePassword': 'Musisz zmienić hasło przed kontynuowaniem.',
|
||||
'login.setNewPassword': 'Ustaw nowe hasło',
|
||||
'login.setNewPasswordHint': 'Musisz zmienić hasło.',
|
||||
'atlas.searchCountry': 'Szukaj kraju...',
|
||||
'trip.loadingPhotos': 'Ładowanie zdjęć...',
|
||||
'places.importGoogleList': 'Lista Google',
|
||||
'places.googleListHint': 'Wklej link do listy Google Maps.',
|
||||
'places.googleListImported': 'Zaimportowano {count} miejsc',
|
||||
'places.googleListError': 'Nie udało się zaimportować listy',
|
||||
'places.viewDetails': 'Zobacz szczegóły',
|
||||
'inspector.trackStats': 'Statystyki trasy',
|
||||
'budget.exportCsv': 'Eksportuj CSV',
|
||||
'budget.table.date': 'Data',
|
||||
'memories.testFirst': 'Najpierw przetestuj połączenie',
|
||||
'memories.linkAlbum': 'Połącz album',
|
||||
'memories.selectAlbum': 'Wybierz album Immich',
|
||||
'memories.noAlbums': 'Nie znaleziono albumów',
|
||||
'memories.syncAlbum': 'Synchronizuj album',
|
||||
'memories.unlinkAlbum': 'Odłącz album',
|
||||
'memories.photos': 'zdjęcia',
|
||||
'memories.error.loadAlbums': 'Nie udało się załadować albumów',
|
||||
'memories.error.linkAlbum': 'Nie udało się połączyć albumu',
|
||||
'memories.error.unlinkAlbum': 'Nie udało się odłączyć albumu',
|
||||
'memories.error.syncAlbum': 'Nie udało się zsynchronizować albumu',
|
||||
'memories.error.loadPhotos': 'Nie udało się załadować zdjęć',
|
||||
'memories.error.addPhotos': 'Nie udało się dodać zdjęć',
|
||||
'memories.error.removePhoto': 'Nie udało się usunąć zdjęcia',
|
||||
'memories.error.toggleSharing': 'Nie udało się zaktualizować udostępniania',
|
||||
'collab.chat.reply': 'Odpowiedz',
|
||||
'admin.tabs.permissions': 'Uprawnienia',
|
||||
'perm.title': 'Ustawienia uprawnień',
|
||||
'perm.subtitle': 'Kontroluj uprawnienia w aplikacji',
|
||||
'perm.saved': 'Ustawienia uprawnień zapisane',
|
||||
'perm.resetDefaults': 'Przywróć domyślne',
|
||||
'perm.customized': 'dostosowane',
|
||||
'perm.level.admin': 'Tylko admin',
|
||||
'perm.level.tripOwner': 'Właściciel podróży',
|
||||
'perm.level.tripMember': 'Członkowie podróży',
|
||||
'perm.level.everybody': 'Wszyscy',
|
||||
'perm.cat.trip': 'Zarządzanie podróżami',
|
||||
'perm.cat.members': 'Zarządzanie członkami',
|
||||
'perm.cat.files': 'Pliki',
|
||||
'perm.cat.content': 'Treść i harmonogram',
|
||||
'perm.cat.extras': 'Budżet, pakowanie i współpraca',
|
||||
'perm.action.trip_create': 'Tworzenie podróży',
|
||||
'perm.action.trip_edit': 'Edytowanie podróży',
|
||||
'perm.action.trip_delete': 'Usuwanie podróży',
|
||||
'perm.action.trip_archive': 'Archiwizacja podróży',
|
||||
'perm.action.trip_cover_upload': 'Przesyłanie okładki',
|
||||
'perm.action.member_manage': 'Zarządzanie członkami',
|
||||
'perm.action.file_upload': 'Przesyłanie plików',
|
||||
'perm.action.file_edit': 'Edytowanie plików',
|
||||
'perm.action.file_delete': 'Usuwanie plików',
|
||||
'perm.action.place_edit': 'Zarządzanie miejscami',
|
||||
'perm.action.day_edit': 'Edytowanie dni',
|
||||
'perm.action.reservation_edit': 'Zarządzanie rezerwacjami',
|
||||
'perm.action.budget_edit': 'Zarządzanie budżetem',
|
||||
'perm.action.packing_edit': 'Zarządzanie pakowaniem',
|
||||
'perm.action.collab_edit': 'Współpraca',
|
||||
'perm.action.share_manage': 'Zarządzanie udostępnianiem',
|
||||
'perm.actionHint.trip_create': 'Kto może tworzyć nowe podróże',
|
||||
'perm.actionHint.trip_edit': 'Kto może edytować szczegóły podróży',
|
||||
'perm.actionHint.trip_delete': 'Kto może usunąć podróż',
|
||||
'perm.actionHint.trip_archive': 'Kto może archiwizować podróż',
|
||||
'perm.actionHint.trip_cover_upload': 'Kto może zmieniać okładkę',
|
||||
'perm.actionHint.member_manage': 'Kto może zapraszać lub usuwać członków',
|
||||
'perm.actionHint.file_upload': 'Kto może przesyłać pliki',
|
||||
'perm.actionHint.file_edit': 'Kto może edytować pliki',
|
||||
'perm.actionHint.file_delete': 'Kto może usuwać pliki',
|
||||
'perm.actionHint.place_edit': 'Kto może zarządzać miejscami',
|
||||
'perm.actionHint.day_edit': 'Kto może edytować dni i przypisania',
|
||||
'perm.actionHint.reservation_edit': 'Kto może zarządzać rezerwacjami',
|
||||
'perm.actionHint.budget_edit': 'Kto może zarządzać budżetem',
|
||||
'perm.actionHint.packing_edit': 'Kto może zarządzać pakowaniem',
|
||||
'perm.actionHint.collab_edit': 'Kto może korzystać ze współpracy',
|
||||
'perm.actionHint.share_manage': 'Kto może zarządzać linkami',
|
||||
'undo.button': 'Cofnij',
|
||||
'undo.tooltip': 'Cofnij: {action}',
|
||||
'undo.assignPlace': 'Miejsce przypisane do dnia',
|
||||
'undo.removeAssignment': 'Miejsce usunięte z dnia',
|
||||
'undo.reorder': 'Kolejność zmieniona',
|
||||
'undo.optimize': 'Trasa zoptymalizowana',
|
||||
'undo.deletePlace': 'Miejsce usunięte',
|
||||
'undo.moveDay': 'Miejsce przeniesione',
|
||||
'undo.lock': 'Blokada przełączona',
|
||||
'undo.importGpx': 'Import GPX',
|
||||
'undo.importGoogleList': 'Import Google Maps',
|
||||
'undo.addPlace': 'Miejsce dodane',
|
||||
'undo.done': 'Cofnięto: {action}',
|
||||
'notifications.title': 'Powiadomienia',
|
||||
'notifications.markAllRead': 'Oznacz wszystkie jako przeczytane',
|
||||
'notifications.deleteAll': 'Usuń wszystkie',
|
||||
'notifications.showAll': 'Pokaż wszystkie',
|
||||
'notifications.empty': 'Brak powiadomień',
|
||||
'notifications.emptyDescription': "You're all caught up!",
|
||||
'notifications.all': 'Wszystkie',
|
||||
'notifications.unreadOnly': 'Nieprzeczytane',
|
||||
'notifications.markRead': 'Oznacz jako przeczytane',
|
||||
'notifications.markUnread': 'Oznacz jako nieprzeczytane',
|
||||
'notifications.delete': 'Usuń',
|
||||
'notifications.system': 'System',
|
||||
'notifications.test.title': 'Testowe powiadomienie od {actor}',
|
||||
'notifications.test.text': 'To jest powiadomienie testowe.',
|
||||
'notifications.test.booleanTitle': '{actor} prosi o akceptację',
|
||||
'notifications.test.booleanText': 'Testowe powiadomienie z wyborem.',
|
||||
'notifications.test.accept': 'Zatwierdź',
|
||||
'notifications.test.decline': 'Odrzuć',
|
||||
'notifications.test.navigateTitle': 'Sprawdź coś',
|
||||
'notifications.test.navigateText': 'Testowe powiadomienie nawigacyjne.',
|
||||
'notifications.test.goThere': 'Przejdź tam',
|
||||
'notifications.test.adminTitle': 'Komunikat administracyjny',
|
||||
'notifications.test.adminText': '{actor} wysłał testowe powiadomienie.',
|
||||
'notifications.test.tripTitle': '{actor} opublikował w Twojej podróży',
|
||||
'notifications.test.tripText': 'Testowe powiadomienie dla podróży "{trip}".',
|
||||
}
|
||||
|
||||
export default pl
|
||||
|
||||
@@ -1490,17 +1490,17 @@ const ru: Record<string, string> = {
|
||||
'perm.actionHint.collab_edit': 'Кто может создавать заметки, опросы и отправлять сообщения',
|
||||
'perm.actionHint.share_manage': 'Кто может создавать или удалять публичные ссылки для обмена',
|
||||
// Undo
|
||||
'undo.button': 'Undo',
|
||||
'undo.tooltip': 'Undo: {action}',
|
||||
'undo.assignPlace': 'Place assigned to day',
|
||||
'undo.removeAssignment': 'Place removed from day',
|
||||
'undo.reorder': 'Places reordered',
|
||||
'undo.optimize': 'Route optimized',
|
||||
'undo.deletePlace': 'Place deleted',
|
||||
'undo.moveDay': 'Place moved to another day',
|
||||
'undo.lock': 'Place lock toggled',
|
||||
'undo.importGpx': 'GPX import',
|
||||
'undo.importGoogleList': 'Google Maps import',
|
||||
'undo.button': 'Отменить',
|
||||
'undo.tooltip': 'Отменить: {action}',
|
||||
'undo.assignPlace': 'Место добавлено в день',
|
||||
'undo.removeAssignment': 'Место удалено из дня',
|
||||
'undo.reorder': 'Места переупорядочены',
|
||||
'undo.optimize': 'Маршрут оптимизирован',
|
||||
'undo.deletePlace': 'Место удалено',
|
||||
'undo.moveDay': 'Место перемещено в другой день',
|
||||
'undo.lock': 'Блокировка места изменена',
|
||||
'undo.importGpx': 'Импорт GPX',
|
||||
'undo.importGoogleList': 'Импорт из Google Maps',
|
||||
|
||||
// Notifications
|
||||
'notifications.title': 'Уведомления',
|
||||
@@ -1515,6 +1515,29 @@ const ru: Record<string, string> = {
|
||||
'notifications.markUnread': 'Отметить как непрочитанное',
|
||||
'notifications.delete': 'Удалить',
|
||||
'notifications.system': 'Система',
|
||||
'memories.error.loadAlbums': 'Не удалось загрузить альбомы',
|
||||
'memories.error.linkAlbum': 'Не удалось привязать альбом',
|
||||
'memories.error.unlinkAlbum': 'Не удалось отвязать альбом',
|
||||
'memories.error.syncAlbum': 'Не удалось синхронизировать альбом',
|
||||
'memories.error.loadPhotos': 'Не удалось загрузить фотографии',
|
||||
'memories.error.addPhotos': 'Не удалось добавить фотографии',
|
||||
'memories.error.removePhoto': 'Не удалось удалить фотографию',
|
||||
'memories.error.toggleSharing': 'Не удалось обновить настройки доступа',
|
||||
'undo.addPlace': 'Место добавлено',
|
||||
'undo.done': 'Отменено: {action}',
|
||||
'notifications.test.title': 'Тестовое уведомление от {actor}',
|
||||
'notifications.test.text': 'Это простое тестовое уведомление.',
|
||||
'notifications.test.booleanTitle': '{actor} запрашивает подтверждение',
|
||||
'notifications.test.booleanText': 'Тестовое уведомление с выбором.',
|
||||
'notifications.test.accept': 'Подтвердить',
|
||||
'notifications.test.decline': 'Отклонить',
|
||||
'notifications.test.navigateTitle': 'Посмотрите на это',
|
||||
'notifications.test.navigateText': 'Тестовое уведомление с переходом.',
|
||||
'notifications.test.goThere': 'Перейти',
|
||||
'notifications.test.adminTitle': 'Рассылка администратора',
|
||||
'notifications.test.adminText': '{actor} отправил тестовое уведомление всем администраторам.',
|
||||
'notifications.test.tripTitle': '{actor} написал в вашей поездке',
|
||||
'notifications.test.tripText': 'Тестовое уведомление для поездки "{trip}".',
|
||||
}
|
||||
|
||||
export default ru
|
||||
@@ -1490,17 +1490,17 @@ const zh: Record<string, string> = {
|
||||
'perm.actionHint.collab_edit': '谁可以创建笔记、投票和发送消息',
|
||||
'perm.actionHint.share_manage': '谁可以创建或删除公开分享链接',
|
||||
// Undo
|
||||
'undo.button': 'Undo',
|
||||
'undo.tooltip': 'Undo: {action}',
|
||||
'undo.assignPlace': 'Place assigned to day',
|
||||
'undo.removeAssignment': 'Place removed from day',
|
||||
'undo.reorder': 'Places reordered',
|
||||
'undo.optimize': 'Route optimized',
|
||||
'undo.deletePlace': 'Place deleted',
|
||||
'undo.moveDay': 'Place moved to another day',
|
||||
'undo.lock': 'Place lock toggled',
|
||||
'undo.importGpx': 'GPX import',
|
||||
'undo.importGoogleList': 'Google Maps import',
|
||||
'undo.button': '撤销',
|
||||
'undo.tooltip': '撤销:{action}',
|
||||
'undo.assignPlace': '地点已分配至某天',
|
||||
'undo.removeAssignment': '地点已从某天移除',
|
||||
'undo.reorder': '地点已重新排序',
|
||||
'undo.optimize': '路线已优化',
|
||||
'undo.deletePlace': '地点已删除',
|
||||
'undo.moveDay': '地点已移至另一天',
|
||||
'undo.lock': '地点锁定已切换',
|
||||
'undo.importGpx': 'GPX 导入',
|
||||
'undo.importGoogleList': 'Google 地图导入',
|
||||
|
||||
// Notifications
|
||||
'notifications.title': '通知',
|
||||
@@ -1515,6 +1515,29 @@ const zh: Record<string, string> = {
|
||||
'notifications.markUnread': '标为未读',
|
||||
'notifications.delete': '删除',
|
||||
'notifications.system': '系统',
|
||||
'memories.error.loadAlbums': '加载相册失败',
|
||||
'memories.error.linkAlbum': '关联相册失败',
|
||||
'memories.error.unlinkAlbum': '取消关联相册失败',
|
||||
'memories.error.syncAlbum': '同步相册失败',
|
||||
'memories.error.loadPhotos': '加载照片失败',
|
||||
'memories.error.addPhotos': '添加照片失败',
|
||||
'memories.error.removePhoto': '删除照片失败',
|
||||
'memories.error.toggleSharing': '更新共享设置失败',
|
||||
'undo.addPlace': '地点已添加',
|
||||
'undo.done': '已撤销:{action}',
|
||||
'notifications.test.title': '来自 {actor} 的测试通知',
|
||||
'notifications.test.text': '这是一条简单的测试通知。',
|
||||
'notifications.test.booleanTitle': '{actor} 请求您的审批',
|
||||
'notifications.test.booleanText': '测试布尔通知。',
|
||||
'notifications.test.accept': '批准',
|
||||
'notifications.test.decline': '拒绝',
|
||||
'notifications.test.navigateTitle': '查看详情',
|
||||
'notifications.test.navigateText': '测试跳转通知。',
|
||||
'notifications.test.goThere': '前往',
|
||||
'notifications.test.adminTitle': '管理员广播',
|
||||
'notifications.test.adminText': '{actor} 向所有管理员发送了测试通知。',
|
||||
'notifications.test.tripTitle': '{actor} 在您的行程中发帖',
|
||||
'notifications.test.tripText': '行程"{trip}"的测试通知。',
|
||||
}
|
||||
|
||||
export default zh
|
||||
@@ -48,6 +48,9 @@ interface AuthState {
|
||||
demoLogin: () => Promise<AuthResponse>
|
||||
}
|
||||
|
||||
// Sequence counter to prevent stale loadUser responses from overwriting fresh auth state
|
||||
let authSequence = 0
|
||||
|
||||
export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
@@ -61,6 +64,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
tripRemindersEnabled: false,
|
||||
|
||||
login: async (email: string, password: string) => {
|
||||
authSequence++
|
||||
set({ isLoading: true, error: null })
|
||||
try {
|
||||
const data = await authApi.login({ email, password }) as AuthResponse & { mfa_required?: boolean; mfa_token?: string }
|
||||
@@ -84,6 +88,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
},
|
||||
|
||||
completeMfaLogin: async (mfaToken: string, code: string) => {
|
||||
authSequence++
|
||||
set({ isLoading: true, error: null })
|
||||
try {
|
||||
const data = await authApi.verifyMfaLogin({ mfa_token: mfaToken, code: code.replace(/\s/g, '') })
|
||||
@@ -103,6 +108,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
},
|
||||
|
||||
register: async (username: string, email: string, password: string, invite_token?: string) => {
|
||||
authSequence++
|
||||
set({ isLoading: true, error: null })
|
||||
try {
|
||||
const data = await authApi.register({ username, email, password, invite_token })
|
||||
@@ -138,10 +144,12 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
},
|
||||
|
||||
loadUser: async (opts?: { silent?: boolean }) => {
|
||||
const seq = authSequence
|
||||
const silent = !!opts?.silent
|
||||
if (!silent) set({ isLoading: true })
|
||||
try {
|
||||
const data = await authApi.me()
|
||||
if (seq !== authSequence) return // stale response — a login/register happened meanwhile
|
||||
set({
|
||||
user: data.user,
|
||||
isAuthenticated: true,
|
||||
@@ -149,6 +157,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
})
|
||||
connect()
|
||||
} catch (err: unknown) {
|
||||
if (seq !== authSequence) return // stale response — ignore
|
||||
// Only clear auth state on 401 (invalid/expired token), not on network errors
|
||||
const isAuthError = err && typeof err === 'object' && 'response' in err &&
|
||||
(err as { response?: { status?: number } }).response?.status === 401
|
||||
@@ -219,6 +228,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
setTripRemindersEnabled: (val: boolean) => set({ tripRemindersEnabled: val }),
|
||||
|
||||
demoLogin: async () => {
|
||||
authSequence++
|
||||
set({ isLoading: true, error: null })
|
||||
try {
|
||||
const data = await authApi.demoLogin()
|
||||
|
||||
@@ -3,10 +3,10 @@ import multer from 'multer';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { canAccessTrip } from '../db/database';
|
||||
import { db, canAccessTrip } from '../db/database';
|
||||
import { authenticate, demoUploadBlock } from '../middleware/auth';
|
||||
import { broadcast } from '../websocket';
|
||||
import { AuthRequest } from '../types';
|
||||
import { AuthRequest, Trip } from '../types';
|
||||
import { writeAudit, getClientIp, logInfo } from '../services/auditLog';
|
||||
import { checkPermission } from '../services/permissions';
|
||||
import {
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
verifyTripAccess,
|
||||
NotFoundError,
|
||||
ValidationError,
|
||||
TRIP_SELECT,
|
||||
} from '../services/tripService';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -7,7 +7,7 @@ export function cookieOptions(clear = false) {
|
||||
return {
|
||||
httpOnly: true,
|
||||
secure,
|
||||
sameSite: 'strict' as const,
|
||||
sameSite: 'lax' as const,
|
||||
path: '/',
|
||||
...(clear ? {} : { maxAge: 24 * 60 * 60 * 1000 }), // 24h — matches JWT expiry
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Trip, User } from '../types';
|
||||
export const MS_PER_DAY = 86400000;
|
||||
export const MAX_TRIP_DAYS = 365;
|
||||
|
||||
const TRIP_SELECT = `
|
||||
export const TRIP_SELECT = `
|
||||
SELECT t.*,
|
||||
(SELECT COUNT(*) FROM days d WHERE d.trip_id = t.id) as day_count,
|
||||
(SELECT COUNT(*) FROM places p WHERE p.trip_id = t.id) as place_count,
|
||||
|
||||
@@ -11,8 +11,8 @@ describe('cookieOptions', () => {
|
||||
expect(cookieOptions()).toHaveProperty('httpOnly', true);
|
||||
});
|
||||
|
||||
it('always sets sameSite: strict', () => {
|
||||
expect(cookieOptions()).toHaveProperty('sameSite', 'strict');
|
||||
it('always sets sameSite: lax', () => {
|
||||
expect(cookieOptions()).toHaveProperty('sameSite', 'lax');
|
||||
});
|
||||
|
||||
it('always sets path: /', () => {
|
||||
|
||||
Reference in New Issue
Block a user