diff --git a/shared/src/i18n/fr/inspector.ts b/shared/src/i18n/fr/inspector.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2d6d35ca19691b5e19b40224eed4cdce6381f3a2
--- /dev/null
+++ b/shared/src/i18n/fr/inspector.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const inspector: TranslationStrings = {
+ 'inspector.opened': 'Ouvert',
+ 'inspector.closed': 'Fermé',
+ 'inspector.openingHours': "Horaires d'ouverture",
+ 'inspector.showHours': 'Afficher les horaires',
+ 'inspector.files': 'Fichiers',
+ 'inspector.filesCount': '{count} fichiers',
+ 'inspector.removeFromDay': 'Retirer du jour',
+ 'inspector.remove': 'Supprimer',
+ 'inspector.addToDay': 'Ajouter au jour',
+ 'inspector.confirmedRes': 'Réservation confirmée',
+ 'inspector.pendingRes': 'Réservation en attente',
+ 'inspector.google': 'Ouvrir dans Google Maps',
+ 'inspector.website': 'Ouvrir le site web',
+ 'inspector.addRes': 'Réservation',
+ 'inspector.editRes': 'Modifier la réservation',
+ 'inspector.participants': 'Participants',
+ 'inspector.trackStats': 'Données du parcours',
+};
+export default inspector;
diff --git a/shared/src/i18n/fr/journey.ts b/shared/src/i18n/fr/journey.ts
new file mode 100644
index 0000000000000000000000000000000000000000..897fdf30726867f316a0e6878b5befe06f5ac7c6
--- /dev/null
+++ b/shared/src/i18n/fr/journey.ts
@@ -0,0 +1,246 @@
+import type { TranslationStrings } from '../types';
+
+const journey: TranslationStrings = {
+ 'journey.search.placeholder': 'Rechercher des journaux…',
+ 'journey.search.noResults': 'Aucun journal ne correspond à « {query} »',
+ 'journey.title': 'Journal de voyage',
+ 'journey.subtitle': 'Suivez vos voyages en temps réel',
+ 'journey.new': 'Nouveau journal',
+ 'journey.create': 'Créer',
+ 'journey.titlePlaceholder': 'Où allez-vous ?',
+ 'journey.empty': 'Aucun journal pour le moment',
+ 'journey.emptyHint': 'Commencez à documenter votre prochain voyage',
+ 'journey.deleted': 'Journal supprimé',
+ 'journey.createError': 'Impossible de créer le journal',
+ 'journey.deleteError': 'Impossible de supprimer le journal',
+ 'journey.deleteConfirmTitle': 'Supprimer',
+ 'journey.deleteConfirmMessage':
+ 'Supprimer « {title} » ? Cette action est irréversible.',
+ 'journey.deleteConfirmGeneric': 'Êtes-vous sûr de vouloir supprimer ceci ?',
+ 'journey.notFound': 'Journal introuvable',
+ 'journey.photos': 'Photos',
+ 'journey.timelineEmpty': 'Aucune étape pour le moment',
+ 'journey.timelineEmptyHint':
+ 'Ajoutez un check-in ou écrivez une entrée de journal pour commencer',
+ 'journey.status.draft': 'Brouillon',
+ 'journey.status.active': 'Actif',
+ 'journey.status.completed': 'Terminé',
+ 'journey.status.upcoming': 'À venir',
+ 'journey.status.archived': 'Archivé',
+ 'journey.checkin.add': 'Check-in',
+ 'journey.checkin.namePlaceholder': 'Nom du lieu',
+ 'journey.checkin.notesPlaceholder': 'Notes (facultatif)',
+ 'journey.checkin.save': 'Enregistrer',
+ 'journey.checkin.error': "Impossible d'enregistrer le check-in",
+ 'journey.entry.add': 'Journal',
+ 'journey.entry.edit': "Modifier l'entrée",
+ 'journey.entry.titlePlaceholder': 'Titre (facultatif)',
+ 'journey.entry.bodyPlaceholder': "Que s'est-il passé aujourd'hui ?",
+ 'journey.entry.save': 'Enregistrer',
+ 'journey.entry.error': "Impossible d'enregistrer l'entrée",
+ 'journey.photo.add': 'Photo',
+ 'journey.photo.uploadError': 'Échec du téléversement',
+ 'journey.share.share': 'Partager',
+ 'journey.share.public': 'Public',
+ 'journey.share.linkCopied': 'Lien public copié',
+ 'journey.share.disabled': 'Partage public désactivé',
+ 'journey.editor.titlePlaceholder': 'Donnez un nom à ce moment...',
+ 'journey.editor.bodyPlaceholder': "Racontez l'histoire de cette journée...",
+ 'journey.editor.placePlaceholder': 'Lieu (facultatif)',
+ 'journey.editor.tagsPlaceholder':
+ 'Tags : pépite cachée, meilleur repas, à revisiter...',
+ 'journey.visibility.private': 'Privé',
+ 'journey.visibility.shared': 'Partagé',
+ 'journey.visibility.public': 'Public',
+ 'journey.emptyState.title': 'Votre histoire commence ici',
+ 'journey.emptyState.subtitle':
+ 'Faites un check-in ou écrivez votre première entrée de journal',
+ 'journey.frontpage.subtitle':
+ 'Transformez vos voyages en histoires inoubliables',
+ 'journey.frontpage.createJourney': 'Créer un journal',
+ 'journey.frontpage.activeJourney': 'Journal actif',
+ 'journey.frontpage.allJourneys': 'Tous les journaux',
+ 'journey.frontpage.journeys': 'journaux',
+ 'journey.frontpage.createNew': 'Créer un nouveau journal',
+ 'journey.frontpage.createNewSub':
+ 'Choisissez des voyages, écrivez des récits, partagez vos aventures',
+ 'journey.frontpage.live': 'En direct',
+ 'journey.frontpage.synced': 'Synchronisé',
+ 'journey.frontpage.continueWriting': 'Continuer à écrire',
+ 'journey.frontpage.updated': 'Mis à jour {time}',
+ 'journey.frontpage.suggestionLabel': 'Voyage terminé récemment',
+ 'journey.frontpage.suggestionText':
+ 'Transformez {title} en journal de voyage',
+ 'journey.frontpage.dismiss': 'Ignorer',
+ 'journey.frontpage.journeyName': 'Nom du journal',
+ 'journey.frontpage.namePlaceholder': 'ex. Asie du Sud-Est 2026',
+ 'journey.frontpage.selectTrips': 'Sélectionner des voyages',
+ 'journey.frontpage.tripsSelected': 'voyages sélectionnés',
+ 'journey.frontpage.trips': 'voyages',
+ 'journey.frontpage.placesImported': 'lieux seront importés',
+ 'journey.frontpage.places': 'lieux',
+ 'journey.detail.backToJourney': 'Retour au journal',
+ 'journey.detail.syncedWithTrips': 'Synchronisé avec les voyages',
+ 'journey.detail.addEntry': 'Ajouter une entrée',
+ 'journey.detail.newEntry': 'Nouvelle entrée',
+ 'journey.detail.editEntry': "Modifier l'entrée",
+ 'journey.detail.noEntries': 'Aucune entrée pour le moment',
+ 'journey.detail.noEntriesHint':
+ 'Ajoutez un voyage pour commencer avec des entrées préremplies',
+ 'journey.detail.noPhotos': 'Aucune photo pour le moment',
+ 'journey.detail.noPhotosHint':
+ 'Téléversez des photos dans les entrées ou parcourez votre bibliothèque Immich/Synology',
+ 'journey.detail.journeyStats': 'Statistiques du journal',
+ 'journey.detail.syncedTrips': 'Voyages synchronisés',
+ 'journey.detail.noTripsLinked': 'Aucun voyage lié pour le moment',
+ 'journey.detail.contributors': 'Contributeurs',
+ 'journey.detail.readMore': 'Lire la suite',
+ 'journey.detail.prosCons': 'Pour et contre',
+ 'journey.detail.photos': 'photos',
+ 'journey.detail.day': 'Jour {number}',
+ 'journey.detail.places': 'lieux',
+ 'journey.stats.days': 'Jours',
+ 'journey.stats.cities': 'Villes',
+ 'journey.stats.entries': 'Entrées',
+ 'journey.stats.photos': 'Photos',
+ 'journey.stats.places': 'Lieux',
+ 'journey.skeletons.show': 'Afficher les suggestions',
+ 'journey.skeletons.hide': 'Masquer les suggestions',
+ 'journey.verdict.lovedIt': 'Adoré',
+ 'journey.verdict.couldBeBetter': 'Pourrait être mieux',
+ 'journey.synced.places': 'lieux',
+ 'journey.synced.synced': 'synchronisé',
+ 'journey.editor.discardChangesConfirm':
+ 'Vous avez des modifications non enregistrées. Les ignorer ?',
+ 'journey.editor.uploadFailed': 'Échec du téléversement des photos',
+ 'journey.editor.uploadPhotos': 'Téléverser des photos',
+ 'journey.editor.uploading': 'Envoi...',
+ 'journey.editor.uploadingProgress': 'Téléversement {done}/{total}…',
+ 'journey.editor.uploadPartialFailed':
+ '{failed} sur {total} photos ont échoué — sauvegardez à nouveau pour réessayer',
+ 'journey.editor.fromGallery': 'Depuis la galerie',
+ 'journey.editor.allPhotosAdded': 'Toutes les photos ont déjà été ajoutées',
+ 'journey.editor.writeStory': 'Écrivez votre histoire...',
+ 'journey.editor.prosCons': 'Pour et contre',
+ 'journey.editor.pros': 'Pour',
+ 'journey.editor.cons': 'Contre',
+ 'journey.editor.proPlaceholder': 'Quelque chose de génial...',
+ 'journey.editor.conPlaceholder': 'Pas si génial...',
+ 'journey.editor.addAnother': 'Ajouter un autre',
+ 'journey.editor.date': 'Date',
+ 'journey.editor.location': 'Lieu',
+ 'journey.editor.searchLocation': 'Rechercher un lieu...',
+ 'journey.editor.mood': 'Humeur',
+ 'journey.editor.weather': 'Météo',
+ 'journey.editor.photoFirst': '1er',
+ 'journey.editor.makeFirst': 'Mettre en 1er',
+ 'journey.editor.searching': 'Recherche...',
+ 'journey.mood.amazing': 'Incroyable',
+ 'journey.mood.good': 'Bien',
+ 'journey.mood.neutral': 'Neutre',
+ 'journey.mood.rough': 'Difficile',
+ 'journey.weather.sunny': 'Ensoleillé',
+ 'journey.weather.partly': 'Partiellement nuageux',
+ 'journey.weather.cloudy': 'Nuageux',
+ 'journey.weather.rainy': 'Pluvieux',
+ 'journey.weather.stormy': 'Orageux',
+ 'journey.weather.cold': 'Neigeux',
+ 'journey.trips.linkTrip': 'Lier un voyage',
+ 'journey.trips.searchTrip': 'Rechercher un voyage',
+ 'journey.trips.searchPlaceholder': 'Nom du voyage ou destination...',
+ 'journey.trips.noTripsAvailable': 'Aucun voyage disponible',
+ 'journey.trips.link': 'Lier',
+ 'journey.trips.tripLinked': 'Voyage lié',
+ 'journey.trips.linkFailed': 'Échec de la liaison du voyage',
+ 'journey.trips.addTrip': 'Ajouter un voyage',
+ 'journey.trips.unlinkTrip': 'Délier le voyage',
+ 'journey.trips.unlinkMessage':
+ 'Délier « {title} » ? Toutes les entrées et photos synchronisées de ce voyage seront définitivement supprimées. Cette action est irréversible.',
+ 'journey.trips.unlink': 'Délier',
+ 'journey.trips.tripUnlinked': 'Voyage délié',
+ 'journey.trips.unlinkFailed': 'Échec de la suppression du lien',
+ 'journey.trips.noTripsLinkedSettings': 'Aucun voyage lié',
+ 'journey.contributors.invite': 'Inviter un contributeur',
+ 'journey.contributors.searchUser': 'Rechercher un utilisateur',
+ 'journey.contributors.searchPlaceholder': "Nom d'utilisateur ou e-mail...",
+ 'journey.contributors.noUsers': 'Aucun utilisateur trouvé',
+ 'journey.contributors.role': 'Rôle',
+ 'journey.contributors.added': 'Contributeur ajouté',
+ 'journey.contributors.addFailed': "Échec de l'ajout du contributeur",
+ 'journey.share.publicShare': 'Partage public',
+ 'journey.share.createLink': 'Créer un lien de partage',
+ 'journey.share.linkCreated': 'Lien de partage créé',
+ 'journey.share.createFailed': 'Échec de la création du lien',
+ 'journey.share.copy': 'Copier',
+ 'journey.share.copied': 'Copié !',
+ 'journey.share.timeline': 'Chronologie',
+ 'journey.share.gallery': 'Galerie',
+ 'journey.share.map': 'Carte',
+ 'journey.share.removeLink': 'Supprimer le lien de partage',
+ 'journey.share.linkDeleted': 'Lien de partage supprimé',
+ 'journey.share.deleteFailed': 'Échec de la suppression',
+ 'journey.share.updateFailed': 'Échec de la mise à jour',
+ 'journey.invite.role': 'Rôle',
+ 'journey.invite.viewer': 'Lecteur',
+ 'journey.invite.editor': 'Éditeur',
+ 'journey.invite.invite': 'Inviter',
+ 'journey.invite.inviting': 'Invitation...',
+ 'journey.settings.title': 'Paramètres du journal',
+ 'journey.settings.coverImage': 'Image de couverture',
+ 'journey.settings.changeCover': 'Changer la couverture',
+ 'journey.settings.addCover': 'Ajouter une image de couverture',
+ 'journey.settings.name': 'Nom',
+ 'journey.settings.subtitle': 'Sous-titre',
+ 'journey.settings.subtitlePlaceholder': 'ex. Thaïlande, Vietnam et Cambodge',
+ 'journey.settings.endJourney': 'Archiver le journal',
+ 'journey.settings.reopenJourney': 'Restaurer le journal',
+ 'journey.settings.archived': 'Journal archivé',
+ 'journey.settings.reopened': 'Journal rouvert',
+ 'journey.settings.endDescription':
+ "Masque l'indicateur En direct. Vous pouvez rouvrir à tout moment.",
+ 'journey.settings.delete': 'Supprimer',
+ 'journey.settings.deleteJourney': 'Supprimer le journal',
+ 'journey.settings.deleteMessage':
+ 'Supprimer « {title} » ? Toutes les entrées et photos seront perdues.',
+ 'journey.settings.saved': 'Paramètres enregistrés',
+ 'journey.settings.saveFailed': "Échec de l'enregistrement",
+ 'journey.settings.coverUpdated': 'Couverture mise à jour',
+ 'journey.settings.coverFailed': 'Échec du téléversement',
+ 'journey.settings.failedToDelete': 'Échec de la suppression',
+ 'journey.entries.deleteTitle': "Supprimer l'entrée",
+ 'journey.photosUploaded': '{count} photos téléversées',
+ 'journey.photosUploadFailed':
+ "Certaines photos n'ont pas pu être téléversées",
+ 'journey.photosAdded': '{count} photos ajoutées',
+ 'journey.public.notFound': 'Introuvable',
+ 'journey.public.notFoundMessage':
+ "Ce journal n'existe pas ou le lien a expiré.",
+ 'journey.public.readOnly': 'Lecture seule · Journal public',
+ 'journey.public.tagline': 'Travel Resource & Exploration Kit',
+ 'journey.public.sharedVia': 'Partagé via',
+ 'journey.public.madeWith': 'Créé avec',
+ 'journey.pdf.journeyBook': 'Carnet de voyage',
+ 'journey.pdf.madeWith': 'Créé avec TREK',
+ 'journey.pdf.day': 'Jour',
+ 'journey.pdf.theEnd': 'Fin',
+ 'journey.pdf.saveAsPdf': 'Enregistrer en PDF',
+ 'journey.pdf.pages': 'pages',
+ 'journey.picker.tripPeriod': 'Période du voyage',
+ 'journey.picker.dateRange': 'Plage de dates',
+ 'journey.picker.allPhotos': 'Toutes les photos',
+ 'journey.picker.albums': 'Albums',
+ 'journey.picker.selected': 'sélectionnés',
+ 'journey.picker.addTo': 'Ajouter à',
+ 'journey.picker.newGallery': 'Nouvelle galerie',
+ 'journey.picker.selectAll': 'Tout sélectionner',
+ 'journey.picker.deselectAll': 'Tout désélectionner',
+ 'journey.picker.noAlbums': 'Aucun album trouvé',
+ 'journey.picker.selectDate': 'Sélectionner une date',
+ 'journey.picker.search': 'Rechercher',
+ 'journey.detail.journeyTab': 'Journey', // en-fallback
+ 'journey.contributors.remove': 'Remove contributor', // en-fallback
+ 'journey.contributors.removeConfirm': 'Remove {username} from this journey?', // en-fallback
+ 'journey.contributors.removed': 'Contributor removed', // en-fallback
+ 'journey.contributors.removeFailed': 'Failed to remove contributor', // en-fallback
+};
+export default journey;
diff --git a/shared/src/i18n/fr/login.ts b/shared/src/i18n/fr/login.ts
new file mode 100644
index 0000000000000000000000000000000000000000..24e972e82074c3d258ee946f9f572b14bd85f968
--- /dev/null
+++ b/shared/src/i18n/fr/login.ts
@@ -0,0 +1,106 @@
+import type { TranslationStrings } from '../types';
+
+const login: TranslationStrings = {
+ 'login.error': 'Échec de la connexion. Veuillez vérifier vos identifiants.',
+ 'login.tagline': 'Vos voyages.\nVotre organisation.',
+ 'login.description':
+ 'Planifiez vos voyages en collaboration avec des cartes interactives, des budgets et la synchronisation en temps réel.',
+ 'login.features.maps': 'Cartes interactives',
+ 'login.features.mapsDesc': 'Google Places, itinéraires et regroupement',
+ 'login.features.realtime': 'Synchronisation en temps réel',
+ 'login.features.realtimeDesc': 'Planifiez ensemble via WebSocket',
+ 'login.features.budget': 'Suivi du budget',
+ 'login.features.budgetDesc': 'Catégories, graphiques et coûts par personne',
+ 'login.features.collab': 'Collaboration',
+ 'login.features.collabDesc': 'Multi-utilisateurs avec voyages partagés',
+ 'login.features.packing': 'Listes de bagages',
+ 'login.features.packingDesc': 'Catégories, progression et suggestions',
+ 'login.features.bookings': 'Réservations',
+ 'login.features.bookingsDesc': 'Vols, hôtels, restaurants et plus',
+ 'login.features.files': 'Documents',
+ 'login.features.filesDesc': 'Importez et gérez vos documents',
+ 'login.features.routes': 'Itinéraires intelligents',
+ 'login.features.routesDesc': 'Optimisation automatique et export Google Maps',
+ 'login.selfHosted':
+ 'Auto-hébergé · Open Source · Vos données restent les vôtres',
+ 'login.title': 'Connexion',
+ 'login.subtitle': 'Bon retour',
+ 'login.signingIn': 'Connexion en cours…',
+ 'login.signIn': 'Se connecter',
+ 'login.createAdmin': 'Créer un compte administrateur',
+ 'login.createAdminHint':
+ 'Configurez le premier compte administrateur pour TREK.',
+ 'login.setNewPassword': 'Définir un nouveau mot de passe',
+ 'login.setNewPasswordHint':
+ 'Vous devez changer votre mot de passe avant de continuer.',
+ 'login.createAccount': 'Créer un compte',
+ 'login.createAccountHint': 'Créez un nouveau compte.',
+ 'login.creating': 'Création…',
+ 'login.noAccount': 'Pas encore de compte ?',
+ 'login.hasAccount': 'Vous avez déjà un compte ?',
+ 'login.register': "S'inscrire",
+ 'login.emailPlaceholder': 'votre@email.com',
+ 'login.username': "Nom d'utilisateur",
+ 'login.oidc.registrationDisabled':
+ 'Les inscriptions sont désactivées. Contactez votre administrateur.',
+ 'login.oidc.noEmail': 'Aucun e-mail reçu du fournisseur.',
+ 'login.mfaTitle': 'Authentification à deux facteurs',
+ 'login.mfaSubtitle':
+ "Entrez le code à 6 chiffres de votre application d'authentification.",
+ 'login.mfaCodeLabel': 'Code de vérification',
+ 'login.mfaCodeRequired':
+ "Entrez le code de votre application d'authentification.",
+ 'login.mfaHint':
+ 'Ouvrez Google Authenticator, Authy ou une autre application TOTP.',
+ 'login.mfaBack': '← Retour à la connexion',
+ 'login.mfaVerify': 'Vérifier',
+ 'login.invalidInviteLink': "Lien d'invitation invalide ou expiré",
+ 'login.oidcFailed': 'Échec de connexion OIDC',
+ 'login.usernameRequired': "Le nom d'utilisateur est obligatoire",
+ 'login.passwordMinLength':
+ 'Le mot de passe doit comporter au moins 8 caractères',
+ 'login.forgotPassword': 'Mot de passe oublié ?',
+ 'login.rememberMe': 'Se souvenir de moi',
+ 'login.forgotPasswordTitle': 'Réinitialiser votre mot de passe',
+ 'login.forgotPasswordBody':
+ "Entrez l'adresse e-mail associée à votre compte. Si un compte existe, nous enverrons un lien de réinitialisation.",
+ 'login.forgotPasswordSubmit': 'Envoyer le lien',
+ 'login.forgotPasswordSentTitle': 'Vérifiez vos e-mails',
+ 'login.forgotPasswordSentBody':
+ 'Si un compte existe pour cette adresse, un lien de réinitialisation est en route. Il expire dans 60 minutes.',
+ 'login.forgotPasswordSmtpHintOff':
+ "Remarque : votre administrateur n'a pas configuré SMTP. Le lien de réinitialisation sera écrit dans la console du serveur au lieu d'être envoyé par e-mail.",
+ 'login.backToLogin': 'Retour à la connexion',
+ 'login.newPassword': 'Nouveau mot de passe',
+ 'login.confirmPassword': 'Confirmer le nouveau mot de passe',
+ 'login.passwordsDontMatch': 'Les mots de passe ne correspondent pas',
+ 'login.mfaCode': 'Code 2FA',
+ 'login.resetPasswordTitle': 'Définir un nouveau mot de passe',
+ 'login.resetPasswordBody':
+ "Choisissez un mot de passe fort que vous n'avez pas encore utilisé ici. 8 caractères minimum.",
+ 'login.resetPasswordMfaBody':
+ 'Entrez votre code 2FA ou un code de secours pour finaliser la réinitialisation.',
+ 'login.resetPasswordSubmit': 'Réinitialiser',
+ 'login.resetPasswordVerify': 'Vérifier et réinitialiser',
+ 'login.resetPasswordSuccessTitle': 'Mot de passe mis à jour',
+ 'login.resetPasswordSuccessBody':
+ 'Vous pouvez maintenant vous connecter avec votre nouveau mot de passe.',
+ 'login.resetPasswordInvalidLink': 'Lien de réinitialisation invalide',
+ 'login.resetPasswordInvalidLinkBody':
+ 'Ce lien est manquant ou invalide. Demandez-en un nouveau pour continuer.',
+ 'login.resetPasswordFailed':
+ 'Échec de la réinitialisation. Le lien a peut-être expiré.',
+ 'login.oidc.tokenFailed': "L'authentification a échoué.",
+ 'login.oidc.invalidState': 'Session invalide. Veuillez réessayer.',
+ 'login.demoFailed': 'Échec de la connexion démo',
+ 'login.oidcSignIn': 'Se connecter avec {name}',
+ 'login.oidcOnly':
+ "L'authentification par mot de passe est désactivée. Veuillez vous connecter via votre fournisseur SSO.",
+ 'login.oidcLoggedOut':
+ 'Vous avez été déconnecté. Reconnectez-vous via votre fournisseur SSO.',
+ 'login.demoHint': 'Essayez la démo — aucune inscription nécessaire',
+ 'login.passkey.signIn': 'Se connecter avec une passkey',
+ 'login.passkey.failed':
+ 'Échec de la connexion par passkey. Veuillez réessayer.',
+};
+export default login;
diff --git a/shared/src/i18n/fr/map.ts b/shared/src/i18n/fr/map.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3462064f36de1bc76ba1748aace5c4560c3c2631
--- /dev/null
+++ b/shared/src/i18n/fr/map.ts
@@ -0,0 +1,17 @@
+import type { TranslationStrings } from '../types';
+
+const map: TranslationStrings = {
+ 'map.connections': 'Connexions',
+ 'map.showConnections': 'Afficher les itinéraires',
+ 'map.hideConnections': 'Masquer les itinéraires',
+ 'poi.searchThisArea': 'Rechercher dans cette zone',
+ 'poi.cat.restaurants': 'Restaurants',
+ 'poi.cat.cafes': 'Cafés',
+ 'poi.cat.bars': 'Bars & vie nocturne',
+ 'poi.cat.hotels': 'Hébergement',
+ 'poi.cat.sights': 'Sites touristiques',
+ 'poi.cat.museums': 'Musées & culture',
+ 'poi.cat.nature': 'Nature & parcs',
+ 'poi.cat.activities': 'Activités',
+};
+export default map;
diff --git a/shared/src/i18n/fr/members.ts b/shared/src/i18n/fr/members.ts
new file mode 100644
index 0000000000000000000000000000000000000000..46f1078c27d019341bacae1b4b74c95628582c0f
--- /dev/null
+++ b/shared/src/i18n/fr/members.ts
@@ -0,0 +1,24 @@
+import type { TranslationStrings } from '../types';
+
+const members: TranslationStrings = {
+ 'members.shareTrip': 'Partager le voyage',
+ 'members.inviteUser': 'Inviter un utilisateur',
+ 'members.selectUser': 'Sélectionner un utilisateur…',
+ 'members.invite': 'Inviter',
+ 'members.allHaveAccess': 'Tous les utilisateurs ont déjà accès.',
+ 'members.access': 'Accès',
+ 'members.person': 'personne',
+ 'members.persons': 'personnes',
+ 'members.you': 'vous',
+ 'members.owner': 'Propriétaire',
+ 'members.leaveTrip': 'Quitter le voyage',
+ 'members.removeAccess': "Retirer l'accès",
+ 'members.confirmLeave': "Quitter le voyage ? Vous perdrez l'accès.",
+ 'members.confirmRemove': "Retirer l'accès à cet utilisateur ?",
+ 'members.loadError': 'Impossible de charger les membres',
+ 'members.added': 'ajouté',
+ 'members.addError': "Échec de l'ajout",
+ 'members.removed': 'Membre retiré',
+ 'members.removeError': 'Échec de la suppression',
+};
+export default members;
diff --git a/shared/src/i18n/fr/memories.ts b/shared/src/i18n/fr/memories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8659cab5b6dbcf73780a6a717739cd73ad1f30a7
--- /dev/null
+++ b/shared/src/i18n/fr/memories.ts
@@ -0,0 +1,84 @@
+import type { TranslationStrings } from '../types';
+
+const memories: TranslationStrings = {
+ 'memories.title': 'Photos',
+ 'memories.notConnected': 'Immich non connecté',
+ 'memories.notConnectedHint':
+ 'Connectez votre instance Immich dans les paramètres pour voir vos photos de voyage ici.',
+ 'memories.notConnectedMultipleHint':
+ 'Connectez un de ces fournisseurs de photos : {provider_names} dans les Paramètres pour pouvoir ajouter des photos à ce voyage.',
+ 'memories.noDates':
+ 'Ajoutez des dates à votre voyage pour charger les photos.',
+ 'memories.noPhotos': 'Aucune photo trouvée',
+ 'memories.noPhotosHint':
+ 'Aucune photo trouvée dans Immich pour la période de ce voyage.',
+ 'memories.photosFound': 'photos',
+ 'memories.fromOthers': "d'autres",
+ 'memories.sharePhotos': 'Partager les photos',
+ 'memories.sharing': 'Partagé',
+ 'memories.reviewTitle': 'Vérifier vos photos',
+ 'memories.reviewHint': 'Cliquez sur les photos pour les exclure du partage.',
+ 'memories.shareCount': 'Partager {count} photos',
+ 'memories.providerUrl': 'URL du serveur',
+ 'memories.providerApiKey': 'Clé API',
+ 'memories.providerUsername': "Nom d'utilisateur",
+ 'memories.providerPassword': 'Mot de passe',
+ 'memories.providerOTP': 'Code MFA (si activé)',
+ 'memories.skipSSLVerification': 'Ignorer la vérification du certificat SSL',
+ 'memories.immichAutoUpload':
+ 'Répliquer les photos du journey vers Immich au téléversement',
+ 'memories.providerUrlHintSynology':
+ "Incluez le chemin de l'application Photos dans l'URL, ex. https://nas:5001/photo",
+ 'memories.testConnection': 'Tester la connexion',
+ 'memories.testShort': 'Tester',
+ 'memories.testFirst': 'Testez la connexion avant de sauvegarder',
+ 'memories.connected': 'Connecté',
+ 'memories.disconnected': 'Non connecté',
+ 'memories.connectionSuccess': 'Connecté à Immich',
+ 'memories.connectionError': 'Impossible de se connecter à Immich',
+ 'memories.saved': 'Paramètres {provider_name} enregistrés',
+ 'memories.providerDisconnectedBanner':
+ 'Votre connexion {provider_name} est perdue. Reconnectez-vous dans les Paramètres pour voir les photos.',
+ 'memories.saveError':
+ "Impossible d'enregistrer les paramètres de {provider_name}",
+ 'memories.saveRouteNotConfigured':
+ "La route de sauvegarde n'est pas configurée pour ce fournisseur",
+ 'memories.testRouteNotConfigured':
+ "La route de test n'est pas configurée pour ce fournisseur",
+ 'memories.fillRequiredFields':
+ 'Veuillez remplir tous les champs obligatoires',
+ 'memories.oldest': 'Plus anciennes',
+ 'memories.newest': 'Plus récentes',
+ 'memories.allLocations': 'Tous les lieux',
+ 'memories.addPhotos': 'Ajouter des photos',
+ 'memories.linkAlbum': 'Lier un album',
+ 'memories.selectAlbum': 'Choisir un album Immich',
+ 'memories.selectAlbumMultiple': 'Sélectionner un album',
+ 'memories.noAlbums': 'Aucun album trouvé',
+ 'memories.syncAlbum': 'Synchroniser',
+ 'memories.unlinkAlbum': 'Délier',
+ 'memories.photos': 'photos',
+ 'memories.selectPhotos': 'Sélectionner des photos depuis Immich',
+ 'memories.selectPhotosMultiple': 'Sélectionner des photos',
+ 'memories.selectHint': 'Appuyez sur les photos pour les sélectionner.',
+ 'memories.selected': 'sélectionné(s)',
+ 'memories.addSelected': 'Ajouter {count} photos',
+ 'memories.alreadyAdded': 'Ajouté',
+ 'memories.private': 'Privé',
+ 'memories.stopSharing': 'Arrêter le partage',
+ 'memories.tripDates': 'Dates du voyage',
+ 'memories.allPhotos': 'Toutes les photos',
+ 'memories.confirmShareTitle': 'Partager avec les membres du voyage ?',
+ 'memories.confirmShareHint':
+ '{count} photos seront visibles par tous les membres de ce voyage. Vous pourrez rendre des photos individuelles privées plus tard.',
+ 'memories.confirmShareButton': 'Partager les photos',
+ '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',
+};
+export default memories;
diff --git a/shared/src/i18n/fr/nav.ts b/shared/src/i18n/fr/nav.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f41a37b4328ac45c4001f00f94e50c203570460c
--- /dev/null
+++ b/shared/src/i18n/fr/nav.ts
@@ -0,0 +1,20 @@
+import type { TranslationStrings } from '../types';
+
+const nav: TranslationStrings = {
+ 'nav.trip': 'Voyage',
+ 'nav.share': 'Partager',
+ 'nav.settings': 'Paramètres',
+ 'nav.admin': 'Admin',
+ 'nav.logout': 'Déconnexion',
+ 'nav.lightMode': 'Mode clair',
+ 'nav.darkMode': 'Mode sombre',
+ 'nav.autoMode': 'Mode auto',
+ 'nav.administrator': 'Administrateur',
+ 'nav.myTrips': 'Mes voyages',
+ 'nav.profile': 'Profil',
+ 'nav.bottomSettings': 'Paramètres',
+ 'nav.bottomAdmin': 'Administration',
+ 'nav.bottomLogout': 'Déconnexion',
+ 'nav.bottomAdminBadge': 'Admin',
+};
+export default nav;
diff --git a/shared/src/i18n/fr/notif.ts b/shared/src/i18n/fr/notif.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9793c45397e9687d8893e104d74da41d25079197
--- /dev/null
+++ b/shared/src/i18n/fr/notif.ts
@@ -0,0 +1,45 @@
+import type { TranslationStrings } from '../types';
+
+const notif: TranslationStrings = {
+ 'notif.test.title': '[Test] Notification',
+ 'notif.test.simple.text': 'Ceci est une simple notification de test.',
+ 'notif.test.boolean.text': 'Acceptez-vous cette notification de test ?',
+ 'notif.test.navigate.text':
+ 'Cliquez ci-dessous pour accéder au tableau de bord.',
+ 'notif.trip_invite.title': 'Invitation au voyage',
+ 'notif.trip_invite.text': '{actor} vous a invité à {trip}',
+ 'notif.booking_change.title': 'Réservation mise à jour',
+ 'notif.booking_change.text':
+ '{actor} a mis à jour une réservation dans {trip}',
+ 'notif.trip_reminder.title': 'Rappel de voyage',
+ 'notif.trip_reminder.text': 'Votre voyage {trip} approche !',
+ 'notif.todo_due.title': 'Tâche à échéance',
+ 'notif.todo_due.text': '{todo} dans {trip} est due le {due}',
+ 'notif.vacay_invite.title': 'Invitation Vacay Fusion',
+ 'notif.vacay_invite.text':
+ '{actor} vous invite à fusionner les plans de vacances',
+ 'notif.photos_shared.title': 'Photos partagées',
+ 'notif.photos_shared.text': '{actor} a partagé {count} photo(s) dans {trip}',
+ 'notif.collab_message.title': 'Nouveau message',
+ 'notif.collab_message.text': '{actor} a envoyé un message dans {trip}',
+ 'notif.packing_tagged.title': 'Affectation bagages',
+ 'notif.packing_tagged.text':
+ '{actor} vous a assigné à {category} dans {trip}',
+ 'notif.version_available.title': 'Nouvelle version disponible',
+ 'notif.version_available.text': 'TREK {version} est maintenant disponible',
+ 'notif.action.view_trip': 'Voir le voyage',
+ 'notif.action.view_collab': 'Voir les messages',
+ 'notif.action.view_packing': 'Voir les bagages',
+ 'notif.action.view_photos': 'Voir les photos',
+ 'notif.action.view_vacay': 'Voir Vacay',
+ 'notif.action.view_admin': "Aller à l'admin",
+ 'notif.action.view': 'Voir',
+ 'notif.action.accept': 'Accepter',
+ 'notif.action.decline': 'Refuser',
+ 'notif.generic.title': 'Notification',
+ 'notif.generic.text': 'Vous avez une nouvelle notification',
+ 'notif.dev.unknown_event.title': '[DEV] Événement inconnu',
+ 'notif.dev.unknown_event.text':
+ 'Le type d\'événement "{event}" n\'est pas enregistré dans EVENT_NOTIFICATION_CONFIG',
+};
+export default notif;
diff --git a/shared/src/i18n/fr/notifications.ts b/shared/src/i18n/fr/notifications.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fec4b0966549192fc2e3d38583f91edc659c23e9
--- /dev/null
+++ b/shared/src/i18n/fr/notifications.ts
@@ -0,0 +1,39 @@
+import type { TranslationStrings } from '../types';
+
+const notifications: TranslationStrings = {
+ 'notifications.title': 'Notifications',
+ 'notifications.markAllRead': 'Tout marquer comme lu',
+ 'notifications.deleteAll': 'Tout supprimer',
+ 'notifications.showAll': 'Voir toutes les notifications',
+ 'notifications.empty': 'Aucune notification',
+ 'notifications.emptyDescription': 'Vous êtes à jour !',
+ 'notifications.all': 'Toutes',
+ 'notifications.unreadOnly': 'Non lues',
+ 'notifications.markRead': 'Marquer comme lu',
+ 'notifications.markUnread': 'Marquer comme non lu',
+ 'notifications.delete': 'Supprimer',
+ 'notifications.system': 'Système',
+ 'notifications.synologySessionCleared.title': 'Synology Photos déconnecté',
+ 'notifications.synologySessionCleared.text':
+ 'Votre serveur ou compte a changé — allez dans Paramètres pour tester à nouveau votre connexion.',
+ '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}".',
+ 'notifications.versionAvailable.title': 'Mise à jour disponible',
+ 'notifications.versionAvailable.text':
+ 'TREK {version} est maintenant disponible.',
+ 'notifications.versionAvailable.button': 'Voir les détails',
+};
+export default notifications;
diff --git a/shared/src/i18n/fr/oauth.ts b/shared/src/i18n/fr/oauth.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6cf851e81a584922c85b7647eaed768529e06b61
--- /dev/null
+++ b/shared/src/i18n/fr/oauth.ts
@@ -0,0 +1,123 @@
+import type { TranslationStrings } from '../types';
+
+const oauth: TranslationStrings = {
+ 'oauth.scope.group.trips': 'Voyages',
+ 'oauth.scope.group.places': 'Lieux',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'Bagages',
+ 'oauth.scope.group.todos': 'Tâches',
+ 'oauth.scope.group.budget': 'Budget',
+ 'oauth.scope.group.reservations': 'Réservations',
+ 'oauth.scope.group.collab': 'Collaboration',
+ 'oauth.scope.group.notifications': 'Notifications',
+ 'oauth.scope.group.vacay': 'Congés',
+ 'oauth.scope.group.geo': 'Géo',
+ 'oauth.scope.group.weather': 'Météo',
+ 'oauth.scope.group.journey': 'Journal de voyage',
+ 'oauth.scope.trips:read.label': 'Voir les voyages et itinéraires',
+ 'oauth.scope.trips:read.description':
+ 'Lire les voyages, jours, notes et membres',
+ 'oauth.scope.trips:write.label': 'Modifier les voyages et itinéraires',
+ 'oauth.scope.trips:write.description':
+ 'Créer et mettre à jour les voyages, jours, notes et gérer les membres',
+ 'oauth.scope.trips:delete.label': 'Supprimer des voyages',
+ 'oauth.scope.trips:delete.description':
+ 'Supprimer définitivement des voyages entiers — cette action est irréversible',
+ 'oauth.scope.trips:share.label': 'Gérer les liens de partage',
+ 'oauth.scope.trips:share.description':
+ 'Créer, modifier et révoquer des liens de partage publics',
+ 'oauth.scope.places:read.label': 'Voir les lieux et données cartographiques',
+ 'oauth.scope.places:read.description':
+ 'Lire les lieux, affectations de jours, étiquettes et catégories',
+ 'oauth.scope.places:write.label': 'Gérer les lieux',
+ 'oauth.scope.places:write.description':
+ 'Créer, modifier et supprimer des lieux, affectations et étiquettes',
+ 'oauth.scope.atlas:read.label': "Voir l'Atlas",
+ 'oauth.scope.atlas:read.description':
+ 'Lire les pays visités, régions et liste de souhaits',
+ 'oauth.scope.atlas:write.label': "Gérer l'Atlas",
+ 'oauth.scope.atlas:write.description':
+ 'Marquer des pays et régions visités, gérer la liste de souhaits',
+ 'oauth.scope.packing:read.label': 'Voir les listes de bagages',
+ 'oauth.scope.packing:read.description':
+ 'Lire les articles, sacs et assignations de catégories',
+ 'oauth.scope.packing:write.label': 'Gérer les listes de bagages',
+ 'oauth.scope.packing:write.description':
+ 'Ajouter, modifier, supprimer, cocher et réordonner les articles et sacs',
+ 'oauth.scope.todos:read.label': 'Voir les listes de tâches',
+ 'oauth.scope.todos:read.description':
+ 'Lire les tâches et assignations de catégories',
+ 'oauth.scope.todos:write.label': 'Gérer les listes de tâches',
+ 'oauth.scope.todos:write.description':
+ 'Créer, modifier, cocher, supprimer et réordonner les tâches',
+ 'oauth.scope.budget:read.label': 'Voir le budget',
+ 'oauth.scope.budget:read.description':
+ 'Lire les dépenses et la répartition du budget',
+ 'oauth.scope.budget:write.label': 'Gérer le budget',
+ 'oauth.scope.budget:write.description':
+ 'Créer, modifier et supprimer des dépenses',
+ 'oauth.scope.reservations:read.label': 'Voir les réservations',
+ 'oauth.scope.reservations:read.description':
+ "Lire les réservations et détails d'hébergement",
+ 'oauth.scope.reservations:write.label': 'Gérer les réservations',
+ 'oauth.scope.reservations:write.description':
+ 'Créer, modifier, supprimer et réordonner les réservations',
+ 'oauth.scope.collab:read.label': 'Voir la collaboration',
+ 'oauth.scope.collab:read.description':
+ 'Lire les notes, sondages et messages collaboratifs',
+ 'oauth.scope.collab:write.label': 'Gérer la collaboration',
+ 'oauth.scope.collab:write.description':
+ 'Créer, modifier et supprimer des notes, sondages et messages',
+ 'oauth.scope.notifications:read.label': 'Voir les notifications',
+ 'oauth.scope.notifications:read.description':
+ 'Lire les notifications et le nombre de non-lus',
+ 'oauth.scope.notifications:write.label': 'Gérer les notifications',
+ 'oauth.scope.notifications:write.description':
+ 'Marquer les notifications comme lues et y répondre',
+ 'oauth.scope.vacay:read.label': 'Voir les plans de congés',
+ 'oauth.scope.vacay:read.description':
+ 'Lire les données, entrées et statistiques de congés',
+ 'oauth.scope.vacay:write.label': 'Gérer les plans de congés',
+ 'oauth.scope.vacay:write.description':
+ "Créer et gérer les entrées de congés, jours fériés et plans d'équipe",
+ 'oauth.scope.geo:read.label': 'Cartes et géocodage',
+ 'oauth.scope.geo:read.description':
+ 'Chercher des lieux, résoudre des URL cartographiques et géocoder des coordonnées',
+ 'oauth.scope.weather:read.label': 'Prévisions météo',
+ 'oauth.scope.weather:read.description':
+ 'Obtenir les prévisions météo pour les lieux et dates de voyage',
+ 'oauth.scope.journey:read.label': 'Voir les journaux de voyage',
+ 'oauth.scope.journey:read.description':
+ 'Lire les journaux de voyage, les entrées et la liste des contributeurs',
+ 'oauth.scope.journey:write.label': 'Gérer les journaux de voyage',
+ 'oauth.scope.journey:write.description':
+ 'Créer, modifier et supprimer les journaux de voyage et leurs entrées',
+ 'oauth.scope.journey:share.label': 'Gérer les liens de journaux de voyage',
+ 'oauth.scope.journey:share.description':
+ 'Créer, modifier et révoquer des liens de partage publics pour les journaux de voyage',
+ 'oauth.authorize.authorizing': 'Authorizing…', // en-fallback
+ 'oauth.authorize.loading': 'Loading…', // en-fallback
+ 'oauth.authorize.errorTitle': 'Authorization Error', // en-fallback
+ 'oauth.authorize.loginTitle': 'Sign in to continue', // en-fallback
+ 'oauth.authorize.loginDescription':
+ '{client} wants access to your TREK account. Please sign in first.', // en-fallback
+ 'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
+ 'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
+ 'oauth.authorize.requestDescription':
+ 'This application is requesting access to your TREK account.', // en-fallback
+ 'oauth.authorize.trustNote':
+ 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
+ 'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
+ 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
+ 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
+ 'oauth.authorize.deny': 'Deny', // en-fallback
+ 'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
+ 'oauth.authorize.permissionsRequested': 'Permissions requested', // en-fallback
+ 'oauth.authorize.alwaysIncluded': 'Always included', // en-fallback
+ 'oauth.authorize.alwaysTool.listTrips':
+ 'List your trips so the AI can discover trip IDs', // en-fallback
+ 'oauth.authorize.alwaysTool.getTripSummary':
+ 'Read a trip overview needed to use any other tool', // en-fallback
+};
+export default oauth;
diff --git a/shared/src/i18n/fr/packing.ts b/shared/src/i18n/fr/packing.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dded6afe88408f4f2a33775250dc78b2adce262a
--- /dev/null
+++ b/shared/src/i18n/fr/packing.ts
@@ -0,0 +1,186 @@
+import type { TranslationStrings } from '../types';
+
+const packing: TranslationStrings = {
+ 'packing.title': 'Liste de bagages',
+ 'packing.empty': 'La liste de bagages est vide',
+ 'packing.import': 'Importer',
+ 'packing.importTitle': 'Importer la liste',
+ 'packing.importHint':
+ 'Un élément par ligne. Catégorie et quantité optionnelles séparées par virgule, point-virgule ou tabulation : Nom, Catégorie, Quantité',
+ 'packing.importPlaceholder':
+ 'Brosse à dents\nCrème solaire, Hygiène\nT-Shirts, Vêtements, 5\nPasseport, Documents',
+ 'packing.importCsv': 'Charger CSV/TXT',
+ 'packing.importAction': 'Importer {count}',
+ 'packing.importSuccess': '{count} éléments importés',
+ 'packing.importError': "Échec de l'import",
+ 'packing.importEmpty': 'Aucun élément à importer',
+ 'packing.progress': '{packed} sur {total} emballés ({percent} %)',
+ 'packing.clearChecked': 'Supprimer {count} cochés',
+ 'packing.clearCheckedShort': 'Supprimer {count}',
+ 'packing.suggestions': 'Suggestions',
+ 'packing.suggestionsTitle': 'Ajouter des suggestions',
+ 'packing.allSuggested': 'Toutes les suggestions ajoutées',
+ 'packing.allPacked': 'Tout est emballé !',
+ 'packing.addPlaceholder': 'Ajouter un nouvel article…',
+ 'packing.categoryPlaceholder': 'Catégorie…',
+ 'packing.filterAll': 'Tous',
+ 'packing.filterOpen': 'À faire',
+ 'packing.filterDone': 'Fait',
+ 'packing.emptyTitle': 'La liste de bagages est vide',
+ 'packing.emptyHint': 'Ajoutez des articles ou utilisez les suggestions',
+ 'packing.emptyFiltered': 'Aucun article ne correspond à ce filtre',
+ 'packing.menuRename': 'Renommer',
+ 'packing.menuCheckAll': 'Tout cocher',
+ 'packing.menuUncheckAll': 'Tout décocher',
+ 'packing.menuDeleteCat': 'Supprimer la catégorie',
+ 'packing.addItem': 'Ajouter un article',
+ 'packing.addItemPlaceholder': "Nom de l'article...",
+ 'packing.addCategory': 'Ajouter une catégorie',
+ 'packing.newCategoryPlaceholder': 'Nom de catégorie (ex. Vêtements)',
+ 'packing.applyTemplate': 'Appliquer un modèle',
+ 'packing.template': 'Modèle',
+ 'packing.templateApplied': '{count} articles ajoutés depuis le modèle',
+ 'packing.templateError': "Erreur lors de l'application du modèle",
+ 'packing.saveAsTemplate': 'Enregistrer comme modèle',
+ 'packing.templateName': 'Nom du modèle',
+ 'packing.templateSaved': 'Liste de voyage enregistrée comme modèle',
+ 'packing.noMembers': 'Aucun membre',
+ 'packing.bags': 'Bagages',
+ 'packing.noBag': 'Non assigné',
+ 'packing.totalWeight': 'Poids total',
+ 'packing.bagName': 'Nom...',
+ 'packing.addBag': 'Ajouter un bagage',
+ 'packing.changeCategory': 'Changer de catégorie',
+ 'packing.confirm.clearChecked':
+ 'Voulez-vous vraiment supprimer {count} articles cochés ?',
+ 'packing.confirm.deleteCat':
+ 'Voulez-vous vraiment supprimer la catégorie « {name} » avec {count} articles ?',
+ 'packing.defaultCategory': 'Autre',
+ 'packing.toast.saveError': "Échec de l'enregistrement",
+ 'packing.toast.deleteError': 'Échec de la suppression',
+ 'packing.toast.renameError': 'Échec du renommage',
+ 'packing.toast.addError': "Échec de l'ajout",
+ 'packing.suggestions.items': [
+ {
+ name: 'Passeport',
+ category: 'Documents',
+ },
+ {
+ name: "Carte d'identité",
+ category: 'Documents',
+ },
+ {
+ name: 'Assurance voyage',
+ category: 'Documents',
+ },
+ {
+ name: "Billets d'avion",
+ category: 'Documents',
+ },
+ {
+ name: 'Carte bancaire',
+ category: 'Finances',
+ },
+ {
+ name: 'Espèces',
+ category: 'Finances',
+ },
+ {
+ name: 'Visa',
+ category: 'Documents',
+ },
+ {
+ name: 'T-shirts',
+ category: 'Vêtements',
+ },
+ {
+ name: 'Pantalons',
+ category: 'Vêtements',
+ },
+ {
+ name: 'Sous-vêtements',
+ category: 'Vêtements',
+ },
+ {
+ name: 'Chaussettes',
+ category: 'Vêtements',
+ },
+ {
+ name: 'Veste',
+ category: 'Vêtements',
+ },
+ {
+ name: 'Pyjama',
+ category: 'Vêtements',
+ },
+ {
+ name: 'Maillot de bain',
+ category: 'Vêtements',
+ },
+ {
+ name: 'Imperméable',
+ category: 'Vêtements',
+ },
+ {
+ name: 'Chaussures confortables',
+ category: 'Vêtements',
+ },
+ {
+ name: 'Brosse à dents',
+ category: 'Hygiène',
+ },
+ {
+ name: 'Dentifrice',
+ category: 'Hygiène',
+ },
+ {
+ name: 'Shampooing',
+ category: 'Hygiène',
+ },
+ {
+ name: 'Déodorant',
+ category: 'Hygiène',
+ },
+ {
+ name: 'Crème solaire',
+ category: 'Hygiène',
+ },
+ {
+ name: 'Rasoir',
+ category: 'Hygiène',
+ },
+ {
+ name: 'Chargeur',
+ category: 'Électronique',
+ },
+ {
+ name: 'Batterie externe',
+ category: 'Électronique',
+ },
+ {
+ name: 'Écouteurs',
+ category: 'Électronique',
+ },
+ {
+ name: 'Adaptateur de voyage',
+ category: 'Électronique',
+ },
+ {
+ name: 'Appareil photo',
+ category: 'Électronique',
+ },
+ {
+ name: 'Antidouleurs',
+ category: 'Santé',
+ },
+ {
+ name: 'Pansements',
+ category: 'Santé',
+ },
+ {
+ name: 'Désinfectant',
+ category: 'Santé',
+ },
+ ],
+};
+export default packing;
diff --git a/shared/src/i18n/fr/pdf.ts b/shared/src/i18n/fr/pdf.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c4a493eea293c802fc6d3f55d425f0021b320c24
--- /dev/null
+++ b/shared/src/i18n/fr/pdf.ts
@@ -0,0 +1,10 @@
+import type { TranslationStrings } from '../types';
+
+const pdf: TranslationStrings = {
+ 'pdf.travelPlan': 'Plan de voyage',
+ 'pdf.planned': 'Planifié',
+ 'pdf.costLabel': 'Coût EUR',
+ 'pdf.preview': 'Aperçu PDF',
+ 'pdf.saveAsPdf': 'Enregistrer en PDF',
+};
+export default pdf;
diff --git a/shared/src/i18n/fr/perm.ts b/shared/src/i18n/fr/perm.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cff493291cb6af6cd2a8b2492619d6ef3e42d91f
--- /dev/null
+++ b/shared/src/i18n/fr/perm.ts
@@ -0,0 +1,65 @@
+import type { TranslationStrings } from '../types';
+
+const perm: TranslationStrings = {
+ 'perm.title': 'Paramètres des permissions',
+ 'perm.subtitle':
+ "Contrôlez qui peut effectuer des actions dans l'application",
+ 'perm.saved': 'Paramètres des permissions enregistrés',
+ 'perm.resetDefaults': 'Réinitialiser par défaut',
+ 'perm.customized': 'personnalisé',
+ 'perm.level.admin': 'Administrateur uniquement',
+ 'perm.level.tripOwner': 'Propriétaire du voyage',
+ 'perm.level.tripMember': 'Membres du voyage',
+ 'perm.level.everybody': 'Tout le monde',
+ 'perm.cat.trip': 'Gestion des voyages',
+ 'perm.cat.members': 'Gestion des membres',
+ 'perm.cat.files': 'Fichiers',
+ 'perm.cat.content': 'Contenu et planning',
+ 'perm.cat.extras': 'Budget, bagages et collaboration',
+ 'perm.action.trip_create': 'Créer des voyages',
+ 'perm.action.trip_edit': 'Modifier les détails du voyage',
+ 'perm.action.trip_delete': 'Supprimer des voyages',
+ 'perm.action.trip_archive': 'Archiver / désarchiver des voyages',
+ 'perm.action.trip_cover_upload': "Télécharger l'image de couverture",
+ 'perm.action.member_manage': 'Ajouter / supprimer des membres',
+ 'perm.action.file_upload': 'Télécharger des fichiers',
+ 'perm.action.file_edit': 'Modifier les métadonnées des fichiers',
+ 'perm.action.file_delete': 'Supprimer des fichiers',
+ 'perm.action.place_edit': 'Ajouter / modifier / supprimer des lieux',
+ 'perm.action.day_edit': 'Modifier les jours, notes et affectations',
+ 'perm.action.reservation_edit': 'Gérer les réservations',
+ 'perm.action.budget_edit': 'Gérer le budget',
+ 'perm.action.packing_edit': 'Gérer les listes de bagages',
+ 'perm.action.collab_edit': 'Collaboration (notes, sondages, chat)',
+ 'perm.action.share_manage': 'Gérer les liens de partage',
+ 'perm.actionHint.trip_create': 'Qui peut créer de nouveaux voyages',
+ 'perm.actionHint.trip_edit':
+ 'Qui peut modifier le nom, les dates, la description et la devise du voyage',
+ 'perm.actionHint.trip_delete': 'Qui peut supprimer définitivement un voyage',
+ 'perm.actionHint.trip_archive': 'Qui peut archiver ou désarchiver un voyage',
+ 'perm.actionHint.trip_cover_upload':
+ "Qui peut télécharger ou modifier l'image de couverture",
+ 'perm.actionHint.member_manage':
+ 'Qui peut inviter ou supprimer des membres du voyage',
+ 'perm.actionHint.file_upload':
+ 'Qui peut télécharger des fichiers vers un voyage',
+ 'perm.actionHint.file_edit':
+ 'Qui peut modifier les descriptions et liens des fichiers',
+ 'perm.actionHint.file_delete':
+ 'Qui peut déplacer des fichiers vers la corbeille ou les supprimer définitivement',
+ 'perm.actionHint.place_edit':
+ 'Qui peut ajouter, modifier ou supprimer des lieux',
+ 'perm.actionHint.day_edit':
+ 'Qui peut modifier les jours, notes de jours et affectations de lieux',
+ 'perm.actionHint.reservation_edit':
+ 'Qui peut créer, modifier ou supprimer des réservations',
+ 'perm.actionHint.budget_edit':
+ 'Qui peut créer, modifier ou supprimer des éléments de budget',
+ 'perm.actionHint.packing_edit':
+ 'Qui peut gérer les articles de bagages et les sacs',
+ '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',
+};
+export default perm;
diff --git a/shared/src/i18n/fr/photos.ts b/shared/src/i18n/fr/photos.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eca8367c5484a91c5dd02cb2a14428a05bb02c84
--- /dev/null
+++ b/shared/src/i18n/fr/photos.ts
@@ -0,0 +1,25 @@
+import type { TranslationStrings } from '../types';
+
+const photos: TranslationStrings = {
+ 'photos.title': 'Photos',
+ 'photos.subtitle': '{count} photos pour {trip}',
+ 'photos.dropHere': 'Déposez des photos ici...',
+ 'photos.dropHereActive': 'Déposez des photos ici',
+ 'photos.captionForAll': 'Légende (pour tous)',
+ 'photos.captionPlaceholder': 'Légende optionnelle...',
+ 'photos.addCaption': 'Ajouter une légende...',
+ 'photos.allDays': 'Tous les jours',
+ 'photos.noPhotos': 'Aucune photo',
+ 'photos.uploadHint': 'Importez vos photos de voyage',
+ 'photos.clickToSelect': 'ou cliquez pour sélectionner',
+ 'photos.linkPlace': 'Lier au lieu',
+ 'photos.noPlace': 'Aucun lieu',
+ 'photos.uploadN': '{n} photo(s) importée(s)',
+ 'photos.linkDay': 'Lier le jour',
+ 'photos.noDay': 'Aucun jour',
+ 'photos.dayLabel': 'Jour {number}',
+ 'photos.photoSelected': 'Photo sélectionnée',
+ 'photos.photosSelected': 'Photos sélectionnées',
+ 'photos.fileTypeHint': "JPG, PNG, WebP · max. 10 Mo · jusqu'à 30 photos",
+};
+export default photos;
diff --git a/shared/src/i18n/fr/places.ts b/shared/src/i18n/fr/places.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1e8a3f1bc3dae90cdbe81d2dba88830f930ef58d
--- /dev/null
+++ b/shared/src/i18n/fr/places.ts
@@ -0,0 +1,98 @@
+import type { TranslationStrings } from '../types';
+
+const places: TranslationStrings = {
+ 'places.addPlace': 'Ajouter un lieu/activité',
+ 'places.importFile': 'Importer un fichier',
+ 'places.sidebarDrop': 'Déposer pour importer',
+ 'places.importFileHint':
+ 'Importez des fichiers .gpx, .kml ou .kmz depuis des outils comme Google My Maps, Google Earth ou un traceur GPS.',
+ 'places.importFileDropHere':
+ 'Cliquez pour sélectionner un fichier ou glissez-déposez ici',
+ 'places.importFileDropActive': 'Déposez le fichier pour le sélectionner',
+ 'places.importFileUnsupported':
+ 'Type de fichier non pris en charge. Utilisez .gpx, .kml ou .kmz.',
+ 'places.importFileTooLarge':
+ 'Le fichier est trop volumineux. La taille maximale est de {maxMb} MB.',
+ 'places.importFileError': 'Importation échouée',
+ 'places.importAllSkipped': 'Tous les lieux étaient déjà dans le voyage.',
+ 'places.gpxImported': '{count} lieux importés depuis GPX',
+ 'places.gpxImportTypes': 'Que voulez-vous importer?',
+ 'places.gpxImportWaypoints': 'Points de passage',
+ 'places.gpxImportRoutes': 'Itinéraires',
+ 'places.gpxImportTracks': 'Traces (avec géométrie)',
+ 'places.gpxImportNoneSelected': 'Sélectionnez au moins un type à importer.',
+ 'places.kmlImportTypes': 'Que souhaitez-vous importer ?',
+ 'places.kmlImportPoints': 'Points (Placemarks)',
+ 'places.kmlImportPaths': 'Chemins (LineStrings)',
+ 'places.kmlImportNoneSelected': 'Sélectionnez au moins un type.',
+ 'places.selectionCount': '{count} sélectionné(s)',
+ 'places.deleteSelected': 'Supprimer la sélection',
+ 'places.kmlKmzImported': '{count} lieux importés depuis KMZ/KML',
+ 'places.urlResolved': "Lieu importé depuis l'URL",
+ 'places.importList': 'Import de liste',
+ 'places.kmlKmzSummaryValues':
+ 'Placemarks : {total} • Importés : {created} • Ignorés : {skipped}',
+ 'places.importGoogleList': 'Liste Google',
+ 'places.importNaverList': 'Liste Naver',
+ 'places.googleListHint':
+ 'Collez un lien de liste Google Maps partagée pour importer tous les lieux.',
+ 'places.googleListImported': '{count} lieux importés depuis "{list}"',
+ 'places.googleListError': "Impossible d'importer la liste Google Maps",
+ 'places.naverListHint':
+ 'Collez un lien de liste Naver Maps partagée pour importer tous les lieux.',
+ 'places.naverListImported': '{count} lieux importés depuis "{list}"',
+ 'places.naverListError': "Impossible d'importer la liste Naver Maps",
+ 'places.viewDetails': 'Voir les détails',
+ 'places.assignToDay': 'Ajouter à quel jour ?',
+ 'places.all': 'Tous',
+ 'places.unplanned': 'Non planifiés',
+ 'places.filterTracks': 'Traces',
+ 'places.search': 'Rechercher des lieux…',
+ 'places.allCategories': 'Toutes les catégories',
+ 'places.categoriesSelected': 'catégories',
+ 'places.clearFilter': 'Effacer le filtre',
+ 'places.count': '{count} lieux',
+ 'places.countSingular': '1 lieu',
+ 'places.allPlanned': 'Tous les lieux sont planifiés',
+ 'places.noneFound': 'Aucun lieu trouvé',
+ 'places.editPlace': 'Modifier le lieu',
+ 'places.formName': 'Nom',
+ 'places.formNamePlaceholder': 'ex. Tour Eiffel',
+ 'places.formDescription': 'Description',
+ 'places.formDescriptionPlaceholder': 'Brève description…',
+ 'places.formAddress': 'Adresse',
+ 'places.formAddressPlaceholder': 'Rue, ville, pays',
+ 'places.formLat': 'Latitude (ex. 48.8566)',
+ 'places.formLng': 'Longitude (ex. 2.3522)',
+ 'places.formCategory': 'Catégorie',
+ 'places.noCategory': 'Sans catégorie',
+ 'places.categoryNamePlaceholder': 'Nom de la catégorie',
+ 'places.formTime': 'Heure',
+ 'places.startTime': 'Début',
+ 'places.endTime': 'Fin',
+ 'places.endTimeBeforeStart':
+ "L'heure de fin est antérieure à l'heure de début",
+ 'places.timeCollision': 'Chevauchement horaire avec :',
+ 'places.formWebsite': 'Site web',
+ 'places.formNotes': 'Notes',
+ 'places.formNotesPlaceholder': 'Notes personnelles…',
+ 'places.formReservation': 'Réservation',
+ 'places.reservationNotesPlaceholder':
+ 'Notes de réservation, numéro de confirmation…',
+ 'places.mapsSearchPlaceholder': 'Rechercher des lieux…',
+ 'places.mapsSearchError': 'La recherche de lieu a échoué.',
+ 'places.loadingDetails': 'Chargement des détails du lieu…',
+ 'places.osmHint':
+ 'Recherche via OpenStreetMap (pas de photos, horaires ni notes). Ajoutez une clé API Google dans les paramètres pour plus de détails.',
+ 'places.osmActive':
+ 'Recherche via OpenStreetMap (pas de photos, notes ni horaires). Ajoutez une clé API Google dans les paramètres pour des données enrichies.',
+ 'places.categoryCreateError': 'Impossible de créer la catégorie',
+ 'places.nameRequired': 'Veuillez saisir un nom',
+ 'places.saveError': "Échec de l'enregistrement",
+ 'places.duplicateExists': "'{name}' est déjà dans ce voyage.",
+ 'places.addAnyway': 'Ajouter quand même',
+ 'places.enrichOnImport': 'Enrichir les lieux via Google',
+ 'places.enrichOnImportHint':
+ 'Recherche chaque lieu importé pour ajouter photos, adresse et coordonnées. Utilise votre clé Google Maps.',
+};
+export default places;
diff --git a/shared/src/i18n/fr/planner.ts b/shared/src/i18n/fr/planner.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9e3576b52b93e29ebfeaae4a80dbaf7752590445
--- /dev/null
+++ b/shared/src/i18n/fr/planner.ts
@@ -0,0 +1,69 @@
+import type { TranslationStrings } from '../types';
+
+const planner: TranslationStrings = {
+ 'planner.places': 'Lieux',
+ 'planner.bookings': 'Réservations',
+ 'planner.packingList': 'Liste de bagages',
+ 'planner.documents': 'Documents',
+ 'planner.dayPlan': 'Plan du jour',
+ 'planner.reservations': 'Réservations',
+ 'planner.minTwoPlaces': 'Au moins 2 lieux avec coordonnées nécessaires',
+ 'planner.noGeoPlaces': 'Aucun lieu avec coordonnées disponible',
+ 'planner.routeCalculated': 'Itinéraire calculé',
+ 'planner.routeCalcFailed': "L'itinéraire n'a pas pu être calculé",
+ 'planner.routeError': "Erreur lors du calcul de l'itinéraire",
+ 'planner.icsExportFailed': "Échec de l'export ICS",
+ 'planner.routeOptimized': 'Itinéraire optimisé',
+ 'planner.reservationUpdated': 'Réservation mise à jour',
+ 'planner.reservationAdded': 'Réservation ajoutée',
+ 'planner.confirmDeleteReservation': 'Supprimer la réservation ?',
+ 'planner.reservationDeleted': 'Réservation supprimée',
+ 'planner.days': 'Jours',
+ 'planner.allPlaces': 'Tous les lieux',
+ 'planner.totalPlaces': '{n} lieux au total',
+ 'planner.noDaysPlanned': 'Aucun jour planifié',
+ 'planner.editTrip': 'Modifier le voyage →',
+ 'planner.placeOne': '1 lieu',
+ 'planner.placeN': '{n} lieux',
+ 'planner.addNote': 'Ajouter une note',
+ 'planner.noEntries': 'Aucune entrée pour ce jour',
+ 'planner.addPlace': 'Ajouter un lieu ou une activité',
+ 'planner.addPlaceShort': '+ Ajouter un lieu ou une activité',
+ 'planner.resPending': 'Réservation en attente · ',
+ 'planner.resConfirmed': 'Réservation confirmée · ',
+ 'planner.notePlaceholder': 'Note…',
+ 'planner.noteTimePlaceholder': 'Heure (facultatif)',
+ 'planner.noteExamplePlaceholder':
+ 'ex. S3 à 14h30 depuis la gare centrale, ferry depuis le quai 7, pause déjeuner…',
+ 'planner.totalCost': 'Coût total',
+ 'planner.searchPlaces': 'Rechercher des lieux…',
+ 'planner.allCategories': 'Toutes les catégories',
+ 'planner.noPlacesFound': 'Aucun lieu trouvé',
+ 'planner.addFirstPlace': 'Ajouter un premier lieu',
+ 'planner.noReservations': 'Aucune réservation',
+ 'planner.addFirstReservation': 'Ajouter une première réservation',
+ 'planner.new': 'Nouveau',
+ 'planner.addToDay': '+ Jour',
+ 'planner.calculating': 'Calcul…',
+ 'planner.route': 'Itinéraire',
+ 'planner.optimize': 'Optimiser',
+ 'planner.openGoogleMaps': 'Ouvrir dans Google Maps',
+ 'planner.selectDayHint':
+ 'Sélectionnez un jour dans la liste de gauche pour voir le plan du jour',
+ 'planner.noPlacesForDay': 'Aucun lieu pour ce jour',
+ 'planner.addPlacesLink': 'Ajouter des lieux →',
+ 'planner.minTotal': 'min. total',
+ 'planner.noReservation': 'Pas de réservation',
+ 'planner.removeFromDay': 'Retirer du jour',
+ 'planner.addToThisDay': 'Ajouter au jour',
+ 'planner.overview': 'Aperçu',
+ 'planner.noDays': 'Aucun jour',
+ 'planner.editTripToAddDays': 'Modifiez le voyage pour ajouter des jours',
+ 'planner.dayCount': '{n} jours',
+ 'planner.clickToUnlock': 'Cliquez pour déverrouiller',
+ 'planner.keepPosition':
+ "Maintenir la position lors de l'optimisation de l'itinéraire",
+ 'planner.dayDetails': 'Détails du jour',
+ 'planner.dayN': 'Jour {n}',
+};
+export default planner;
diff --git a/shared/src/i18n/fr/register.ts b/shared/src/i18n/fr/register.ts
new file mode 100644
index 0000000000000000000000000000000000000000..03aba507a40ca92019688591c8082e9a8e94422e
--- /dev/null
+++ b/shared/src/i18n/fr/register.ts
@@ -0,0 +1,27 @@
+import type { TranslationStrings } from '../types';
+
+const register: TranslationStrings = {
+ 'register.passwordMismatch': 'Les mots de passe ne correspondent pas',
+ 'register.passwordTooShort':
+ 'Le mot de passe doit comporter au moins 8 caractères',
+ 'register.failed': "Échec de l'inscription",
+ 'register.getStarted': 'Commencer',
+ 'register.subtitle':
+ 'Créez un compte et commencez à planifier vos voyages de rêve.',
+ 'register.feature1': 'Plans de voyage illimités',
+ 'register.feature2': 'Vue carte interactive',
+ 'register.feature3': 'Gérez les lieux et catégories',
+ 'register.feature4': 'Suivez les réservations',
+ 'register.feature5': 'Créez des listes de bagages',
+ 'register.feature6': 'Stockez photos et fichiers',
+ 'register.createAccount': 'Créer un compte',
+ 'register.startPlanning': 'Commencez à planifier vos voyages',
+ 'register.minChars': 'Min. 6 caractères',
+ 'register.confirmPassword': 'Confirmer le mot de passe',
+ 'register.repeatPassword': 'Répéter le mot de passe',
+ 'register.registering': 'Inscription en cours…',
+ 'register.register': "S'inscrire",
+ 'register.hasAccount': 'Vous avez déjà un compte ?',
+ 'register.signIn': 'Se connecter',
+};
+export default register;
diff --git a/shared/src/i18n/fr/reservations.ts b/shared/src/i18n/fr/reservations.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e69a09fc9dcd2be86a5d17e686ec02ce2e3fb3c1
--- /dev/null
+++ b/shared/src/i18n/fr/reservations.ts
@@ -0,0 +1,163 @@
+import type { TranslationStrings } from '../types';
+
+const reservations: TranslationStrings = {
+ 'reservations.title': 'Réservations',
+ 'reservations.empty': 'Aucune réservation',
+ 'reservations.emptyHint':
+ 'Ajoutez des réservations pour les vols, hôtels et plus',
+ 'reservations.add': 'Ajouter une réservation',
+ 'reservations.addManual': 'Réservation manuelle',
+ 'reservations.placeHint':
+ 'Conseil : les réservations sont mieux créées directement depuis un lieu pour les lier à votre plan du jour.',
+ 'reservations.confirmed': 'Confirmée',
+ 'reservations.pending': 'En attente',
+ 'reservations.summary': '{confirmed} confirmées, {pending} en attente',
+ 'reservations.fromPlan': 'Du plan',
+ 'reservations.showFiles': 'Afficher les fichiers',
+ 'reservations.editTitle': 'Modifier la réservation',
+ 'reservations.status': 'Statut',
+ 'reservations.datetime': 'Date et heure',
+ 'reservations.startTime': 'Heure de début',
+ 'reservations.endTime': 'Heure de fin',
+ 'reservations.date': 'Date',
+ 'reservations.time': 'Heure',
+ 'reservations.timeAlt': 'Heure (alternative, ex. 19h30)',
+ 'reservations.notes': 'Notes',
+ 'reservations.notesPlaceholder': 'Notes supplémentaires…',
+ 'reservations.meta.airline': 'Compagnie aérienne',
+ 'reservations.meta.flightNumber': 'N° de vol',
+ 'reservations.meta.from': 'De',
+ 'reservations.meta.to': 'À',
+ 'reservations.layover.route': 'Itinéraire',
+ 'reservations.layover.stop': 'Escale',
+ 'reservations.layover.addStop': 'Ajouter une escale',
+ 'reservations.layover.connection': 'Correspondance',
+ 'reservations.layover.layover': 'Escale',
+ 'reservations.needsReview': 'Vérifier',
+ 'reservations.needsReviewHint':
+ "L'aéroport n'a pas pu être identifié automatiquement — veuillez confirmer l'emplacement.",
+ 'reservations.searchLocation': 'Rechercher une gare, un port, une adresse…',
+ 'reservations.meta.trainNumber': 'N° de train',
+ 'reservations.meta.platform': 'Quai',
+ 'reservations.meta.seat': 'Place',
+ 'reservations.meta.checkIn': 'Arrivée',
+ 'reservations.meta.checkInUntil': "Check-in jusqu'à",
+ 'reservations.meta.checkOut': 'Départ',
+ 'reservations.meta.linkAccommodation': 'Hébergement',
+ 'reservations.meta.pickAccommodation': 'Lier à un hébergement',
+ 'reservations.meta.noAccommodation': 'Aucun',
+ 'reservations.meta.hotelPlace': 'Hébergement',
+ 'reservations.meta.pickHotel': 'Sélectionner un hébergement',
+ 'reservations.meta.fromDay': 'Du',
+ 'reservations.meta.toDay': 'Au',
+ 'reservations.meta.selectDay': 'Sélectionner un jour',
+ 'reservations.type.flight': 'Vol',
+ 'reservations.type.hotel': 'Hébergement',
+ 'reservations.type.restaurant': 'Restaurant',
+ 'reservations.type.train': 'Train',
+ 'reservations.type.car': 'Voiture',
+ 'reservations.type.cruise': 'Croisière',
+ 'reservations.type.event': 'Événement',
+ 'reservations.type.tour': 'Visite',
+ 'reservations.type.other': 'Autre',
+ 'reservations.type.bus': 'Bus',
+ 'reservations.type.ferry': 'Ferry',
+ 'reservations.type.bicycle': 'Vélo',
+ 'reservations.type.taxi': 'Taxi',
+ 'reservations.type.transport_other': 'Autre',
+ 'reservations.confirm.delete':
+ 'Voulez-vous vraiment supprimer la réservation « {name} » ?',
+ 'reservations.confirm.deleteTitle': 'Supprimer la réservation ?',
+ 'reservations.confirm.deleteBody': '« {name} » sera définitivement supprimé.',
+ 'reservations.toast.updated': 'Réservation mise à jour',
+ 'reservations.toast.removed': 'Réservation supprimée',
+ 'reservations.toast.fileUploaded': 'Fichier importé',
+ 'reservations.toast.uploadError': "Échec de l'import",
+ 'reservations.newTitle': 'Nouvelle réservation',
+ 'reservations.bookingType': 'Type de réservation',
+ 'reservations.titleLabel': 'Titre',
+ 'reservations.titlePlaceholder': 'ex. Lufthansa LH123, Hôtel Adlon, …',
+ 'reservations.locationAddress': 'Lieu / Adresse',
+ 'reservations.locationPlaceholder': 'Adresse, aéroport, hôtel…',
+ 'reservations.confirmationCode': 'Code de réservation',
+ 'reservations.confirmationPlaceholder': 'ex. ABC12345',
+ 'reservations.day': 'Jour',
+ 'reservations.noDay': 'Aucun jour',
+ 'reservations.place': 'Lieu',
+ 'reservations.noPlace': 'Aucun lieu',
+ 'reservations.pendingSave': 'sera enregistré…',
+ 'reservations.uploading': 'Importation…',
+ 'reservations.attachFile': 'Joindre un fichier',
+ 'reservations.linkExisting': 'Lier un fichier existant',
+ 'reservations.toast.saveError': "Échec de l'enregistrement",
+ 'reservations.toast.updateError': 'Échec de la mise à jour',
+ 'reservations.toast.deleteError': 'Échec de la suppression',
+ 'reservations.confirm.remove': 'Supprimer la réservation pour « {name} » ?',
+ 'reservations.linkAssignment': "Lier à l'affectation du jour",
+ 'reservations.pickAssignment': 'Sélectionnez une affectation de votre plan…',
+ 'reservations.noAssignment': 'Aucun lien (autonome)',
+ 'reservations.price': 'Prix',
+ 'reservations.budgetCategory': 'Catégorie budgétaire',
+ 'reservations.budgetCategoryPlaceholder': 'ex. Transport, Hébergement',
+ 'reservations.budgetCategoryAuto': 'Auto (selon le type de réservation)',
+ 'reservations.budgetHint':
+ "Une entrée budgétaire sera créée automatiquement lors de l'enregistrement.",
+ 'reservations.departureDate': 'Départ',
+ 'reservations.arrivalDate': 'Arrivée',
+ 'reservations.departureTime': 'Heure dép.',
+ 'reservations.arrivalTime': 'Heure arr.',
+ 'reservations.pickupDate': 'Prise en charge',
+ 'reservations.returnDate': 'Restitution',
+ 'reservations.pickupTime': 'Heure prise en charge',
+ 'reservations.returnTime': 'Heure restitution',
+ 'reservations.endDate': 'Date de fin',
+ 'reservations.meta.departureTimezone': 'TZ dép.',
+ 'reservations.meta.arrivalTimezone': 'TZ arr.',
+ 'reservations.span.departure': 'Départ',
+ 'reservations.span.arrival': 'Arrivée',
+ 'reservations.span.inTransit': 'En transit',
+ 'reservations.span.pickup': 'Prise en charge',
+ 'reservations.span.return': 'Restitution',
+ 'reservations.span.active': 'Actif',
+ 'reservations.span.start': 'Début',
+ 'reservations.span.end': 'Fin',
+ 'reservations.span.ongoing': 'En cours',
+ 'reservations.validation.endBeforeStart':
+ 'La date/heure de fin doit être postérieure à la date/heure de début',
+ 'reservations.addBooking': 'Ajouter une réservation',
+ 'reservations.import.title': 'Importer des confirmations de réservation',
+ 'reservations.import.cta': 'Importer depuis un fichier',
+ 'reservations.import.dropHere': 'Déposez les fichiers de confirmation de réservation ici ou cliquez pour sélectionner',
+ 'reservations.import.dropActive': 'Déposez les fichiers pour importer',
+ 'reservations.import.acceptedFormats': "Acceptés : EML, PDF, PKPass, HTML, TXT (max. 10 Mo chacun, jusqu'à 5 fichiers)",
+ 'reservations.import.parsing': 'Analyse des fichiers…',
+ 'reservations.import.previewHeading': '{count} réservation(s) trouvée(s)',
+ 'reservations.import.previewEmpty': "Aucune réservation n'a pu être extraite des fichiers envoyés.",
+ 'reservations.import.removeItem': 'Supprimer',
+ 'reservations.import.confirm': 'Importer {count} réservation(s)',
+ 'reservations.import.back': 'Retour',
+ 'reservations.import.success': '{count} réservation(s) importée(s)',
+ 'reservations.import.partialFailure': '{created} importée(s), {failed} échouée(s)',
+ 'reservations.import.error': 'Analyse échouée. Assurez-vous que le fichier est une confirmation de réservation valide.',
+ 'reservations.import.unavailable': "L'import de réservations n'est pas disponible sur ce serveur.",
+ 'reservations.import.unsupportedFormat': 'Format de fichier non pris en charge. Utilisez EML, PDF, PKPass, HTML ou TXT.',
+ 'reservations.import.fileTooLarge': 'Le fichier « {name} » dépasse la limite de 10 Mo.',
+ 'reservations.airtrail.title': 'Importer depuis AirTrail',
+ 'reservations.airtrail.cta': 'AirTrail',
+ 'reservations.airtrail.synced': 'AirTrail',
+ 'reservations.airtrail.syncedHint': 'Synchronisé depuis AirTrail — les modifications restent synchronisées dans les deux sens.',
+ 'reservations.airtrail.notSynced': 'Non synchronisé',
+ 'reservations.airtrail.notSyncedHint': "Ce vol a été supprimé dans AirTrail et n'est plus synchronisé.",
+ 'reservations.airtrail.loadError': 'Impossible de charger vos vols AirTrail.',
+ 'reservations.airtrail.imported': '{count} vol(s) importé(s)',
+ 'reservations.airtrail.skippedDuplicate': '{count} déjà dans ce voyage, ignoré(s)',
+ 'reservations.airtrail.nothingImported': 'Rien à importer.',
+ 'reservations.airtrail.importError': "Échec de l'importation. Veuillez réessayer.",
+ 'reservations.airtrail.undo': 'Importer depuis AirTrail',
+ 'reservations.airtrail.alreadyImported': 'Importé',
+ 'reservations.airtrail.duringTrip': 'Pendant ce voyage',
+ 'reservations.airtrail.otherFlights': 'Autres vols',
+ 'reservations.airtrail.empty': 'Aucun vol trouvé dans votre compte AirTrail.',
+ 'reservations.airtrail.importCta': 'Importer {count}',
+};
+export default reservations;
diff --git a/shared/src/i18n/fr/settings.ts b/shared/src/i18n/fr/settings.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d196f56caf687c2dd74df4aebe3a11cfdc30f665
--- /dev/null
+++ b/shared/src/i18n/fr/settings.ts
@@ -0,0 +1,351 @@
+import type { TranslationStrings } from '../types';
+
+const settings: TranslationStrings = {
+ 'settings.title': 'Paramètres',
+ 'settings.subtitle': 'Configurez vos paramètres personnels',
+ 'settings.tabs.display': 'Affichage',
+ 'settings.tabs.map': 'Carte',
+ 'settings.tabs.notifications': 'Notifications',
+ 'settings.tabs.integrations': 'Intégrations',
+ 'settings.tabs.account': 'Compte',
+ 'settings.tabs.offline': 'Offline',
+ 'settings.tabs.about': 'À propos',
+ 'settings.map': 'Carte',
+ 'settings.mapTemplate': 'Modèle de carte',
+ 'settings.mapTemplatePlaceholder.select': 'Sélectionner un modèle…',
+ 'settings.mapDefaultHint': 'Laissez vide pour OpenStreetMap (par défaut)',
+ 'settings.mapTemplatePlaceholder':
+ 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
+ 'settings.mapHint': "Modèle d'URL pour les tuiles de carte",
+ 'settings.mapProvider': 'Fournisseur de carte',
+ 'settings.mapProviderHint':
+ 'Affecte les cartes Trip Planner et Journey. Atlas utilise toujours Leaflet.',
+ 'settings.mapLeafletSubtitle': 'Classique 2D, toutes tuiles raster',
+ 'settings.mapMapboxSubtitle': 'Tuiles vectorielles, bâtiments 3D & terrain',
+ 'settings.mapExperimental': 'Expérimental',
+ 'settings.mapMapboxToken': "Jeton d'accès Mapbox",
+ 'settings.mapMapboxTokenHint': 'Jeton public (pk.*) depuis',
+ 'settings.mapMapboxTokenLink': "mapbox.com → Jetons d'accès",
+ 'settings.mapStyle': 'Style de carte',
+ 'settings.mapStylePlaceholder': 'Sélectionner un style Mapbox',
+ 'settings.mapStyleHint': 'Preset ou votre propre URL mapbox://styles/USER/ID',
+ 'settings.map3dBuildings': 'Bâtiments 3D & terrain',
+ 'settings.map3dHint':
+ 'Inclinaison + extrusions 3D réelles des bâtiments — fonctionne avec tous les styles, y compris satellite.',
+ 'settings.mapHighQuality': 'Mode haute qualité',
+ 'settings.mapHighQualityHint':
+ 'Anticrénelage + projection globe pour des bords plus nets et une vue réaliste du monde.',
+ 'settings.mapHighQualityWarning':
+ 'Peut affecter les performances sur les appareils moins puissants.',
+ 'settings.mapTipLabel': 'Astuce :',
+ 'settings.mapTip':
+ 'Clic droit et glisser pour pivoter/incliner la carte. Clic milieu pour ajouter un lieu (le clic droit est réservé à la rotation).',
+ 'settings.latitude': 'Latitude',
+ 'settings.longitude': 'Longitude',
+ 'settings.saveMap': 'Enregistrer la carte',
+ 'settings.apiKeys': 'Clés API',
+ 'settings.mapsKey': 'Clé API Google Maps',
+ 'settings.mapsKeyHint':
+ "Pour la recherche de lieux. Nécessite l'API Places (New). Obtenez-la sur console.cloud.google.com",
+ 'settings.weatherKey': 'Clé API OpenWeatherMap',
+ 'settings.weatherKeyHint':
+ 'Pour les données météo. Gratuit sur openweathermap.org/api',
+ 'settings.keyPlaceholder': 'Saisir la clé…',
+ 'settings.configured': 'Configuré',
+ 'settings.saveKeys': 'Enregistrer les clés',
+ 'settings.display': 'Affichage',
+ 'settings.colorMode': 'Mode de couleur',
+ 'settings.light': 'Clair',
+ 'settings.dark': 'Sombre',
+ 'settings.auto': 'Auto',
+ 'settings.language': 'Langue',
+ 'settings.temperature': 'Unité de température',
+ 'settings.timeFormat': "Format de l'heure",
+ 'settings.blurBookingCodes': 'Masquer les codes de réservation',
+ 'settings.optimizeFromAccommodation':
+ "Optimiser l'itinéraire depuis l'hébergement",
+ 'settings.optimizeFromAccommodationHint':
+ "Lors de l'optimisation d'une journée, commencez l'itinéraire à l'hôtel où vous vous réveillez et terminez-le à celui où vous arrivez le soir.",
+ 'settings.notifications': 'Notifications',
+ 'settings.notifyTripInvite': 'Invitations de voyage',
+ 'settings.notifyBookingChange': 'Modifications de réservation',
+ 'settings.notifyTripReminder': 'Rappels de voyage',
+ 'settings.notifyTodoDue': 'Tâche à échéance',
+ 'settings.notifyVacayInvite': 'Invitations de fusion Vacay',
+ 'settings.notifyPhotosShared': 'Photos partagées (Immich)',
+ 'settings.notifyCollabMessage': 'Messages de chat (Collab)',
+ 'settings.notifyPackingTagged': 'Liste de bagages : attributions',
+ 'settings.notifyWebhook': 'Notifications webhook',
+ 'settings.notificationsDisabled':
+ "Les notifications ne sont pas configurées. Demandez à un administrateur d'activer les notifications par e-mail ou webhook.",
+ 'settings.notificationsActive': 'Canal actif',
+ 'settings.notificationsManagedByAdmin':
+ 'Les événements de notification sont configurés par votre administrateur.',
+ 'settings.on': 'Activé',
+ 'settings.off': 'Désactivé',
+ 'settings.mcp.title': 'Configuration MCP',
+ 'settings.mcp.endpoint': 'Point de terminaison MCP',
+ 'settings.mcp.clientConfig': 'Configuration du client',
+ 'settings.mcp.clientConfigHint':
+ 'Remplacez par un token API de la liste ci-dessous. Le chemin vers npx devra peut-être être ajusté selon votre système (ex. C:\\PROGRA~1\\nodejs\\npx.cmd sous Windows).',
+ 'settings.mcp.clientConfigHintOAuth':
+ "Remplacez et par les identifiants affichés dans le client OAuth 2.1 créé ci-dessus. mcp-remote ouvrira votre navigateur pour finaliser l'autorisation lors de la première connexion. Le chemin vers npx devra peut-être être ajusté selon votre système (ex. C:\\PROGRA~1\\nodejs\\npx.cmd sous Windows).",
+ 'settings.mcp.copy': 'Copier',
+ 'settings.mcp.copied': 'Copié !',
+ 'settings.mcp.apiTokens': 'Tokens API',
+ 'settings.mcp.createToken': 'Créer un token',
+ 'settings.mcp.noTokens':
+ "Aucun token pour l'instant. Créez-en un pour connecter des clients MCP.",
+ 'settings.mcp.tokenCreatedAt': 'Créé',
+ 'settings.mcp.tokenUsedAt': 'Utilisé',
+ 'settings.mcp.deleteTokenTitle': 'Supprimer le token',
+ 'settings.mcp.deleteTokenMessage':
+ "Ce token cessera de fonctionner immédiatement. Tout client MCP l'utilisant perdra l'accès.",
+ 'settings.mcp.modal.createTitle': 'Créer un token API',
+ 'settings.mcp.modal.tokenName': 'Nom du token',
+ 'settings.mcp.modal.tokenNamePlaceholder':
+ 'ex. Claude Desktop, Ordinateur pro',
+ 'settings.mcp.modal.creating': 'Création…',
+ 'settings.mcp.modal.create': 'Créer le token',
+ 'settings.mcp.modal.createdTitle': 'Token créé',
+ 'settings.mcp.modal.createdWarning':
+ "Ce token ne sera affiché qu'une seule fois. Copiez-le et conservez-le maintenant — il ne pourra pas être récupéré.",
+ 'settings.mcp.modal.done': 'Terminé',
+ 'settings.mcp.toast.created': 'Token créé',
+ 'settings.mcp.toast.createError': 'Impossible de créer le token',
+ 'settings.mcp.toast.deleted': 'Token supprimé',
+ 'settings.mcp.toast.deleteError': 'Impossible de supprimer le token',
+ 'settings.mcp.apiTokensDeprecated':
+ 'Les tokens API sont dépréciés et seront supprimés dans une prochaine version. Veuillez utiliser les clients OAuth 2.1 à la place.',
+ 'settings.oauth.clients': 'Clients OAuth 2.1',
+ 'settings.oauth.clientsHint':
+ 'Enregistrez des clients OAuth 2.1 pour permettre à des applications MCP tierces (Claude Web, Cursor, etc.) de se connecter sans tokens statiques.',
+ 'settings.oauth.createClient': 'Nouveau client',
+ 'settings.oauth.noClients': 'Aucun client OAuth enregistré.',
+ 'settings.oauth.clientId': 'ID client',
+ 'settings.oauth.clientSecret': 'Secret client',
+ 'settings.oauth.deleteClient': 'Supprimer le client',
+ 'settings.oauth.deleteClientMessage':
+ "Ce client et toutes les sessions actives seront définitivement supprimés. Toute application l'utilisant perdra immédiatement l'accès.",
+ 'settings.oauth.rotateSecret': 'Renouveler le secret',
+ 'settings.oauth.rotateSecretMessage':
+ 'Un nouveau secret client sera généré et toutes les sessions existantes seront immédiatement invalidées. Mettez à jour votre application avant de fermer cette fenêtre.',
+ 'settings.oauth.rotateSecretConfirm': 'Renouveler',
+ 'settings.oauth.rotateSecretConfirming': 'Renouvellement…',
+ 'settings.oauth.rotateSecretDoneTitle': 'Nouveau secret généré',
+ 'settings.oauth.rotateSecretDoneWarning':
+ "Ce secret n'est affiché qu'une seule fois. Copiez-le maintenant et mettez à jour votre application — toutes les sessions précédentes ont été invalidées.",
+ 'settings.oauth.activeSessions': 'Sessions OAuth actives',
+ 'settings.oauth.sessionScopes': 'Portées',
+ 'settings.oauth.sessionExpires': 'Expire',
+ 'settings.oauth.revoke': 'Révoquer',
+ 'settings.oauth.revokeSession': 'Révoquer la session',
+ 'settings.oauth.revokeSessionMessage':
+ "Cela révoquera immédiatement l'accès pour cette session OAuth.",
+ 'settings.oauth.modal.createTitle': 'Enregistrer un client OAuth',
+ 'settings.oauth.modal.presets': 'Préréglages rapides',
+ 'settings.oauth.modal.clientName': "Nom de l'application",
+ 'settings.oauth.modal.clientNamePlaceholder': 'ex. Claude Web, Mon app MCP',
+ 'settings.oauth.modal.redirectUris': 'URIs de redirection',
+ 'settings.oauth.modal.redirectUrisPlaceholder':
+ 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint':
+ 'Une URI par ligne. HTTPS requis (localhost exempté). Correspondance exacte.',
+ 'settings.oauth.modal.scopes': 'Portées autorisées',
+ 'settings.oauth.modal.scopesHint':
+ "list_trips et get_trip_summary sont toujours disponibles — aucune portée requise. Ils permettent à l'IA de découvrir les IDs de voyage nécessaires.",
+ 'settings.oauth.modal.selectAll': 'Tout sélectionner',
+ 'settings.oauth.modal.deselectAll': 'Tout désélectionner',
+ 'settings.oauth.modal.creating': 'Enregistrement…',
+ 'settings.oauth.modal.create': 'Enregistrer le client',
+ 'settings.oauth.modal.createdTitle': 'Client enregistré',
+ 'settings.oauth.modal.createdWarning':
+ "Le secret client n'est affiché qu'une seule fois. Copiez-le maintenant — il ne peut pas être récupéré.",
+ 'settings.oauth.toast.createError':
+ "Impossible d'enregistrer le client OAuth",
+ 'settings.oauth.toast.deleted': 'Client OAuth supprimé',
+ 'settings.oauth.toast.deleteError': 'Impossible de supprimer le client OAuth',
+ 'settings.oauth.toast.revoked': 'Session révoquée',
+ 'settings.oauth.toast.revokeError': 'Impossible de révoquer la session',
+ 'settings.oauth.toast.rotateError':
+ 'Impossible de renouveler le secret client',
+ 'settings.oauth.modal.machineClient':
+ 'Client machine (sans connexion navigateur)',
+ 'settings.oauth.modal.machineClientHint':
+ 'Utilise le grant client_credentials — aucune URI de redirection requise. Le token est émis directement via client_id + client_secret et agit en votre nom dans les portées sélectionnées.',
+ 'settings.oauth.modal.machineClientUsage':
+ 'Obtenir un token : POST /oauth/token avec grant_type=client_credentials, client_id et client_secret. Sans navigateur, sans token de rafraîchissement.',
+ 'settings.oauth.badge.machine': 'machine',
+ 'settings.account': 'Compte',
+ 'settings.about': 'À propos',
+ 'settings.about.reportBug': 'Signaler un bug',
+ 'settings.about.reportBugHint': 'Un problème ? Faites-le nous savoir',
+ 'settings.about.featureRequest': 'Proposer une fonctionnalité',
+ 'settings.about.featureRequestHint': 'Suggérez une nouvelle fonctionnalité',
+ 'settings.about.wikiHint': 'Documentation et guides',
+ 'settings.about.supporters.badge': 'Soutiens Mensuels',
+ 'settings.about.supporters.title': 'Compagnons de voyage pour TREK',
+ 'settings.about.supporters.subtitle':
+ "Pendant que tu planifies ton prochain itinéraire, ces personnes aident à planifier l'avenir de TREK. Leur contribution mensuelle va directement au développement et aux heures réellement passées — pour que TREK reste Open Source.",
+ 'settings.about.supporters.since': 'soutien depuis {date}',
+ 'settings.about.supporters.tierEmpty': 'Sois le premier',
+ 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket',
+ 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP',
+ 'settings.about.supporter.tier.businessClassDreamer':
+ 'Business Class Dreamer',
+ 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller',
+ 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate',
+ 'settings.about.description':
+ 'TREK est un planificateur de voyage auto-hébergé qui vous aide à organiser vos voyages de la première idée au dernier souvenir. Planification journalière, budget, listes de bagages, photos et bien plus — le tout au même endroit, sur votre propre serveur.',
+ 'settings.about.madeWith': 'Fait avec',
+ 'settings.about.madeBy':
+ 'par Maurice et une communauté open-source grandissante.',
+ 'settings.username': "Nom d'utilisateur",
+ 'settings.email': 'E-mail',
+ 'settings.role': 'Rôle',
+ 'settings.roleAdmin': 'Administrateur',
+ 'settings.oidcLinked': 'Lié avec',
+ 'settings.changePassword': 'Changer le mot de passe',
+ 'settings.mustChangePassword':
+ 'Vous devez changer votre mot de passe avant de continuer. Veuillez définir un nouveau mot de passe ci-dessous.',
+ 'settings.currentPassword': 'Mot de passe actuel',
+ 'settings.currentPasswordRequired': 'Le mot de passe actuel est requis',
+ 'settings.newPassword': 'Nouveau mot de passe',
+ 'settings.confirmPassword': 'Confirmer le nouveau mot de passe',
+ 'settings.updatePassword': 'Mettre à jour le mot de passe',
+ 'settings.passwordRequired':
+ 'Veuillez saisir le mot de passe actuel et le nouveau',
+ 'settings.passwordTooShort':
+ 'Le mot de passe doit comporter au moins 8 caractères',
+ 'settings.passwordMismatch': 'Les mots de passe ne correspondent pas',
+ 'settings.passwordWeak':
+ 'Le mot de passe doit contenir des majuscules, des minuscules, un chiffre et un caractère spécial',
+ 'settings.passwordChanged': 'Mot de passe modifié avec succès',
+ 'settings.deleteAccount': 'Supprimer le compte',
+ 'settings.deleteAccountTitle': 'Supprimer votre compte ?',
+ 'settings.deleteAccountWarning':
+ 'Votre compte ainsi que tous vos voyages, lieux et fichiers seront définitivement supprimés. Cette action est irréversible.',
+ 'settings.deleteAccountConfirm': 'Supprimer définitivement',
+ 'settings.deleteBlockedTitle': 'Suppression impossible',
+ 'settings.deleteBlockedMessage':
+ "Vous êtes le seul administrateur. Promouvez un autre utilisateur en tant qu'administrateur avant de supprimer votre compte.",
+ 'settings.roleUser': 'Utilisateur',
+ 'settings.saveProfile': 'Enregistrer le profil',
+ 'settings.mfa.title': 'Authentification à deux facteurs (2FA)',
+ 'settings.mfa.description':
+ "Ajoute une étape supplémentaire lors de la connexion. Utilisez une application d'authentification (Google Authenticator, Authy, etc.).",
+ 'settings.mfa.requiredByPolicy':
+ "Votre administrateur exige l'authentification à deux facteurs. Configurez une application d'authentification ci-dessous avant de continuer.",
+ 'settings.mfa.backupTitle': 'Codes de secours',
+ 'settings.mfa.backupDescription':
+ "Utilisez ces codes à usage unique si vous perdez l'accès à votre application d'authentification.",
+ 'settings.mfa.backupWarning':
+ "Enregistrez ces codes maintenant. Chaque code n'est utilisable qu'une seule fois.",
+ 'settings.mfa.backupCopy': 'Copier les codes',
+ 'settings.mfa.backupDownload': 'Télécharger TXT',
+ 'settings.mfa.backupPrint': 'Imprimer / PDF',
+ 'settings.mfa.backupCopied': 'Codes de secours copiés',
+ 'settings.mfa.enabled': '2FA est activé sur votre compte.',
+ 'settings.mfa.disabled': "2FA n'est pas activé.",
+ 'settings.mfa.setup': "Configurer l'authentificateur",
+ 'settings.mfa.scanQr':
+ 'Scannez ce code QR avec votre application ou entrez la clé manuellement.',
+ 'settings.mfa.secretLabel': 'Clé secrète (saisie manuelle)',
+ 'settings.mfa.codePlaceholder': 'Code à 6 chiffres',
+ 'settings.mfa.enable': 'Activer 2FA',
+ 'settings.mfa.cancelSetup': 'Annuler',
+ 'settings.mfa.disableTitle': 'Désactiver 2FA',
+ 'settings.mfa.disableHint':
+ 'Entrez votre mot de passe et un code actuel de votre authentificateur.',
+ 'settings.mfa.disable': 'Désactiver 2FA',
+ 'settings.mfa.toastEnabled': 'Authentification à deux facteurs activée',
+ 'settings.mfa.toastDisabled': 'Authentification à deux facteurs désactivée',
+ 'settings.mfa.demoBlocked': 'Non disponible en mode démo',
+ 'settings.toast.mapSaved': 'Paramètres de carte enregistrés',
+ 'settings.toast.keysSaved': 'Clés API enregistrées',
+ 'settings.toast.displaySaved': "Paramètres d'affichage enregistrés",
+ 'settings.toast.profileSaved': 'Profil enregistré',
+ 'settings.uploadAvatar': 'Importer une photo de profil',
+ 'settings.removeAvatar': 'Supprimer la photo de profil',
+ 'settings.avatarUploaded': 'Photo de profil mise à jour',
+ 'settings.avatarRemoved': 'Photo de profil supprimée',
+ 'settings.avatarError': "Échec de l'import",
+ 'settings.bookingLabels': 'Étiquettes des itinéraires',
+ 'settings.bookingLabelsHint':
+ "Affiche les noms des gares / aéroports sur la carte. Si désactivé, seule l'icône est affichée.",
+ 'settings.notifyVersionAvailable': 'Nouvelle version disponible',
+ 'settings.notificationPreferences.noChannels':
+ "Aucun canal de notification n'est configuré. Demandez à un administrateur de configurer les notifications par e-mail ou webhook.",
+ 'settings.webhookUrl.label': 'URL du webhook',
+ 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...',
+ 'settings.webhookUrl.hint':
+ 'Entrez votre URL de webhook Discord, Slack ou personnalisée pour recevoir des notifications.',
+ 'settings.webhookUrl.saved': 'URL du webhook enregistrée',
+ 'settings.webhookUrl.test': 'Tester',
+ 'settings.webhookUrl.testSuccess': 'Webhook de test envoyé avec succès',
+ 'settings.webhookUrl.testFailed': 'Échec du webhook de test',
+ 'settings.ntfyUrl.topicLabel': 'Sujet Ntfy',
+ 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts',
+ 'settings.ntfyUrl.serverLabel': 'URL du serveur Ntfy (optionnel)',
+ 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh',
+ 'settings.ntfyUrl.hint':
+ 'Entrez votre sujet Ntfy pour recevoir des notifications push. Laissez le serveur vide pour utiliser la valeur par défaut configurée par votre administrateur.',
+ 'settings.ntfyUrl.tokenLabel': "Jeton d'accès (optionnel)",
+ 'settings.ntfyUrl.tokenHint':
+ 'Requis pour les sujets protégés par mot de passe.',
+ 'settings.ntfyUrl.saved': 'Paramètres Ntfy enregistrés',
+ 'settings.ntfyUrl.test': 'Tester',
+ 'settings.ntfyUrl.testSuccess':
+ 'Notification de test Ntfy envoyée avec succès',
+ 'settings.ntfyUrl.testFailed': 'Échec de la notification de test Ntfy',
+ 'settings.ntfyUrl.tokenCleared': "Jeton d'accès effacé",
+ 'settings.notificationPreferences.inapp': 'In-App',
+ 'settings.notificationPreferences.webhook': 'Webhook',
+ 'settings.notificationPreferences.email': 'Email',
+ 'settings.notificationPreferences.ntfy': 'Ntfy',
+ "settings.currency": "Currency",
+ "settings.currencyHint": "All amounts in Costs are converted to and shown in this currency.",
+ 'settings.passkey.title': 'Passkeys',
+ 'settings.passkey.description':
+ "Connectez-vous plus rapidement et de façon résistante au phishing avec une passkey — votre empreinte digitale, votre visage, votre code PIN ou une clé matérielle. Votre mot de passe reste disponible en secours.",
+ 'settings.passkey.notConfigured':
+ "Les passkeys sont activées mais ne sont pas encore entièrement configurées sur ce serveur. Demandez à votre administrateur de définir le domaine WebAuthn.",
+ 'settings.passkey.add': 'Ajouter une passkey',
+ 'settings.passkey.addTitle': 'Ajouter une passkey',
+ 'settings.passkey.passwordPrompt':
+ "Confirmez votre mot de passe actuel, puis suivez les instructions de votre appareil.",
+ 'settings.passkey.passwordRequired': 'Votre mot de passe actuel est requis.',
+ 'settings.passkey.namePlaceholder': 'Nom (facultatif, ex. "iPhone")',
+ 'settings.passkey.addedToast': 'Passkey ajoutée',
+ 'settings.passkey.added': 'Ajoutée',
+ 'settings.passkey.addError': "Impossible d'ajouter la passkey",
+ 'settings.passkey.cancelled': 'Configuration de la passkey annulée',
+ 'settings.passkey.deleted': 'Passkey supprimée',
+ 'settings.passkey.deleteConfirm':
+ 'Supprimer cette passkey ? Confirmez avec votre mot de passe.',
+ 'settings.passkey.rename': 'Renommer',
+ 'settings.passkey.defaultName': 'Passkey',
+ 'settings.passkey.synced': 'Synchronisée',
+ 'settings.passkey.deviceBound': 'Cet appareil',
+ 'settings.passkey.lastUsed': 'Dernière utilisation',
+ 'settings.passkey.neverUsed': 'Jamais utilisée',
+ 'settings.mapPoiPill': 'Explorer les lieux sur la carte',
+ 'settings.mapPoiPillHint': 'Afficher une pastille de catégorie sur la carte du voyage pour trouver à proximité des restaurants, hébergements et plus encore depuis OpenStreetMap.',
+ 'settings.airtrail.title': 'AirTrail',
+ 'settings.airtrail.hint': 'Connectez votre instance AirTrail auto-hébergée pour importer et synchroniser vos vols. Créez une clé API dans AirTrail sous Paramètres → Sécurité.',
+ 'settings.airtrail.url': "URL de l'instance",
+ 'settings.airtrail.apiKey': 'Clé API',
+ 'settings.airtrail.apiKeyPlaceholder': 'Clé API Bearer',
+ 'settings.airtrail.apiKeyHint': 'Générée dans AirTrail sous Paramètres → Sécurité. Stockée chiffrée.',
+ 'settings.airtrail.allowInsecureTls': 'Autoriser les certificats auto-signés',
+ 'settings.airtrail.allowInsecureTlsHint': 'À activer uniquement pour une instance de confiance sur votre propre réseau.',
+ 'settings.airtrail.connected': 'Connecté',
+ 'settings.airtrail.notConnected': 'Non connecté',
+ 'settings.airtrail.toast.saved': 'Connexion AirTrail enregistrée',
+ 'settings.airtrail.toast.saveError': "Impossible d'enregistrer la connexion",
+ 'settings.airtrail.test.button': 'Tester la connexion',
+ 'settings.airtrail.test.success': 'Connecté — {count} vol(s) trouvé(s)',
+ 'settings.airtrail.test.failed': 'Échec de la connexion',
+};
+
+export default settings;
diff --git a/shared/src/i18n/fr/share.ts b/shared/src/i18n/fr/share.ts
new file mode 100644
index 0000000000000000000000000000000000000000..65e8e3863ea5ef64ac89d025e3f5d8beb6654b57
--- /dev/null
+++ b/shared/src/i18n/fr/share.ts
@@ -0,0 +1,16 @@
+import type { TranslationStrings } from '../types';
+
+const share: TranslationStrings = {
+ 'share.linkTitle': 'Lien public',
+ 'share.linkHint':
+ "Créez un lien que n'importe qui peut utiliser pour consulter ce voyage sans se connecter. Lecture seule — aucune modification possible.",
+ 'share.createLink': 'Créer un lien',
+ 'share.deleteLink': 'Supprimer le lien',
+ 'share.createError': 'Impossible de créer le lien',
+ 'share.permMap': 'Carte et plan',
+ 'share.permBookings': 'Réservations',
+ 'share.permPacking': 'Bagages',
+ 'share.permBudget': 'Budget',
+ 'share.permCollab': 'Chat',
+};
+export default share;
diff --git a/shared/src/i18n/fr/shared.ts b/shared/src/i18n/fr/shared.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b2b2201bc5b98d4fadcfc2404f4374868ed13b97
--- /dev/null
+++ b/shared/src/i18n/fr/shared.ts
@@ -0,0 +1,21 @@
+import type { TranslationStrings } from '../types';
+
+const shared: TranslationStrings = {
+ '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',
+};
+export default shared;
diff --git a/shared/src/i18n/fr/stats.ts b/shared/src/i18n/fr/stats.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c1deee3597907716bd530082a90bedb2ca31331c
--- /dev/null
+++ b/shared/src/i18n/fr/stats.ts
@@ -0,0 +1,13 @@
+import type { TranslationStrings } from '../types';
+
+const stats: TranslationStrings = {
+ 'stats.countries': 'Pays',
+ 'stats.cities': 'Villes',
+ 'stats.trips': 'Voyages',
+ 'stats.places': 'Lieux',
+ 'stats.worldProgress': 'Progression mondiale',
+ 'stats.visited': 'visités',
+ 'stats.remaining': 'restants',
+ 'stats.visitedCountries': 'Pays visités',
+};
+export default stats;
diff --git a/shared/src/i18n/fr/system_notice.ts b/shared/src/i18n/fr/system_notice.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c33d3172c3fd3d19eb3d0481533c6052d208fcc6
--- /dev/null
+++ b/shared/src/i18n/fr/system_notice.ts
@@ -0,0 +1,63 @@
+import type { TranslationStrings } from '../types';
+
+const system_notice: TranslationStrings = {
+ 'system_notice.welcome_v1.title': 'Bienvenue sur TREK',
+ 'system_notice.welcome_v1.body':
+ 'Votre planificateur de voyage tout-en-un. Créez des itinéraires, partagez vos voyages et restez organisé — en ligne ou hors ligne.',
+ 'system_notice.welcome_v1.cta_label': 'Planifier un voyage',
+ 'system_notice.welcome_v1.hero_alt':
+ "Destination de voyage pittoresque avec l'interface TREK",
+ 'system_notice.welcome_v1.highlight_plan': 'Itinéraires jour par jour',
+ 'system_notice.welcome_v1.highlight_share': 'Collaborez avec vos partenaires',
+ 'system_notice.welcome_v1.highlight_offline':
+ 'Fonctionne hors ligne sur mobile',
+ 'system_notice.dev_test_modal.title': '[Dev] Test notice',
+ 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.',
+ 'system_notice.pager.prev': 'Avis précédent',
+ 'system_notice.pager.next': 'Avis suivant',
+ 'system_notice.pager.counter': '{current} / {total}',
+ 'system_notice.pager.goto': "Aller à l'avis {n}",
+ 'system_notice.pager.position': 'Avis {current} sur {total}',
+ 'system_notice.v3_photos.title': 'Les photos ont bougé dans 3.0',
+ 'system_notice.v3_photos.body':
+ "**Photos** dans le planificateur ont été supprimées. Tes photos sont en sécurité — TREK n'a jamais modifié ta bibliothèque Immich ou Synology.\n\nLes photos vivent désormais dans l'addon **Journey**. Journey est optionnel — s'il n'est pas encore disponible, demande à ton admin de l'activer dans Admin → Modules.",
+ 'system_notice.v3_journey.title': 'Découvrez Journey — journal de voyage',
+ 'system_notice.v3_journey.body':
+ 'Documente tes voyages sous forme de récits enrichis avec chronologies, galeries photos et cartes interactives.',
+ 'system_notice.v3_journey.cta_label': 'Ouvrir Journey',
+ 'system_notice.v3_journey.highlight_timeline':
+ 'Chronologie et galerie par jour',
+ 'system_notice.v3_journey.highlight_photos':
+ 'Import depuis Immich ou Synology',
+ 'system_notice.v3_journey.highlight_share':
+ 'Partage public — sans connexion requise',
+ 'system_notice.v3_journey.highlight_export': 'Export en livre photo PDF',
+ 'system_notice.v3_features.title': 'Plus de nouveautés en 3.0',
+ 'system_notice.v3_features.body':
+ 'Quelques autres choses à savoir sur cette version.',
+ 'system_notice.v3_features.highlight_dashboard':
+ 'Tableau de bord repensé mobile-first',
+ 'system_notice.v3_features.highlight_offline':
+ 'Mode hors ligne complet en PWA',
+ 'system_notice.v3_features.highlight_search':
+ 'Autocomplétion des lieux en temps réel',
+ 'system_notice.v3_features.highlight_import':
+ 'Importer des lieux depuis KMZ/KML',
+ 'system_notice.v3_mcp.title': 'MCP : mise à niveau OAuth 2.1',
+ 'system_notice.v3_mcp.body':
+ "L'intégration MCP a été entièrement repensée. OAuth 2.1 est désormais la méthode d'authentification recommandée. Les tokens statiques (trek_…) sont dépréciés et seront supprimés dans une future version.",
+ 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 recommandé (mcp-remote)',
+ 'system_notice.v3_mcp.highlight_scopes':
+ '24 scopes de permissions granulaires',
+ 'system_notice.v3_mcp.highlight_deprecated':
+ 'Tokens statiques trek_ dépréciés',
+ 'system_notice.v3_mcp.highlight_tools': 'Outils et prompts étendus',
+ 'system_notice.v3_thankyou.title': 'Un mot personnel de ma part',
+ 'system_notice.v3_thankyou.body':
+ "Avant de continuer — je veux prendre un instant.\n\nTREK a commencé comme un projet perso que j'ai construit pour mes propres voyages. Je n'aurais jamais imaginé qu'il grandirait au point que 4 000 d'entre vous lui fassent confiance pour planifier vos aventures. Chaque étoile, chaque issue, chaque demande de fonctionnalité — je les lis toutes, et ce sont elles qui me font tenir pendant les nuits blanches entre un travail à temps plein et l'université.\n\nJe veux que vous sachiez : TREK sera toujours open source, toujours auto-hébergé, toujours à vous. Pas de tracking, pas d'abonnements, pas de conditions cachées. Juste un outil construit par quelqu'un qui aime voyager autant que vous.\n\nUn merci tout particulier à [jubnl](https://github.com/jubnl) — tu es devenu un collaborateur incroyable. Une grande partie de ce qui rend la 3.0 géniale porte ton empreinte. Merci d'avoir cru en ce projet quand il était encore brut.\n\nEt à chacun d'entre vous qui a signalé un bug, traduit une chaîne, partagé TREK avec un ami ou simplement l'a utilisé pour planifier un voyage — **merci**. Vous êtes la raison pour laquelle tout ceci existe.\n\nÀ de nombreuses autres aventures ensemble.\n\n— Maurice\n\n---\n\n[Rejoins la communauté sur Discord](https://discord.gg/7Q6M6jDwzf)\n\nSi TREK rend tes voyages meilleurs, un [petit café](https://ko-fi.com/mauriceboe) aide toujours à garder les lumières allumées.",
+ 'system_notice.v3014_whitespace_collision.title':
+ 'Action requise : conflit de compte utilisateur',
+ 'system_notice.v3014_whitespace_collision.body':
+ "La mise à niveau 3.0.14 a détecté un ou plusieurs conflits de nom d'utilisateur ou d'adresse e-mail causés par des espaces en début ou en fin de valeur dans les comptes enregistrés. Les comptes concernés ont été renommés automatiquement. Consultez les journaux du serveur pour les lignes commençant par **[migration] WHITESPACE COLLISION** afin d'identifier les comptes nécessitant une vérification.",
+};
+export default system_notice;
diff --git a/shared/src/i18n/fr/todo.ts b/shared/src/i18n/fr/todo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0f0db819923e8ed7cbdb4c223c8b2141aba9b2e7
--- /dev/null
+++ b/shared/src/i18n/fr/todo.ts
@@ -0,0 +1,41 @@
+import type { TranslationStrings } from '../types';
+
+const todo: TranslationStrings = {
+ 'todo.subtab.packing': 'Liste de bagages',
+ 'todo.subtab.todo': 'À faire',
+ 'todo.completed': 'terminé(s)',
+ 'todo.filter.all': 'Tout',
+ 'todo.filter.open': 'En cours',
+ 'todo.filter.done': 'Terminé',
+ 'todo.uncategorized': 'Sans catégorie',
+ 'todo.namePlaceholder': 'Nom de la tâche',
+ 'todo.descriptionPlaceholder': 'Description (facultative)',
+ 'todo.unassigned': 'Non assigné',
+ 'todo.noCategory': 'Aucune catégorie',
+ 'todo.hasDescription': 'Avec description',
+ 'todo.addItem': 'Nouvelle tâche',
+ 'todo.sidebar.sortBy': 'Trier par',
+ 'todo.priority': 'Priorité',
+ 'todo.newCategoryLabel': 'nouvelle',
+ 'todo.newCategory': 'Nom de la catégorie',
+ 'todo.addCategory': 'Ajouter une catégorie',
+ 'todo.newItem': 'Nouvelle tâche',
+ 'todo.empty':
+ "Aucune tâche pour l'instant. Ajoutez une tâche pour commencer !",
+ 'todo.filter.my': 'Mes tâches',
+ 'todo.filter.overdue': 'En retard',
+ 'todo.sidebar.tasks': 'Tâches',
+ 'todo.sidebar.categories': 'Catégories',
+ 'todo.detail.title': 'Tâche',
+ 'todo.detail.description': 'Description',
+ 'todo.detail.category': 'Catégorie',
+ 'todo.detail.dueDate': "Date d'échéance",
+ 'todo.detail.assignedTo': 'Assigné à',
+ 'todo.detail.delete': 'Supprimer',
+ 'todo.detail.save': 'Enregistrer les modifications',
+ 'todo.detail.create': 'Créer la tâche',
+ 'todo.detail.priority': 'Priorité',
+ 'todo.detail.noPriority': 'Aucune',
+ 'todo.sortByPrio': 'Priorité',
+};
+export default todo;
diff --git a/shared/src/i18n/fr/transport.ts b/shared/src/i18n/fr/transport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ab8591df9291c084d384018c34cb81c2f0f21045
--- /dev/null
+++ b/shared/src/i18n/fr/transport.ts
@@ -0,0 +1,10 @@
+import type { TranslationStrings } from '../types';
+
+const transport: TranslationStrings = {
+ 'transport.addTransport': 'Ajouter un transport',
+ 'transport.modalTitle.create': 'Ajouter un transport',
+ 'transport.modalTitle.edit': 'Modifier le transport',
+ 'transport.title': 'Transports',
+ 'transport.addManual': 'Transport manuel',
+};
+export default transport;
diff --git a/shared/src/i18n/fr/trip.ts b/shared/src/i18n/fr/trip.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fc97df5f1cdba4bb245acd0c029023f1425892f8
--- /dev/null
+++ b/shared/src/i18n/fr/trip.ts
@@ -0,0 +1,31 @@
+import type { TranslationStrings } from '../types';
+
+const trip: TranslationStrings = {
+ 'trip.tabs.plan': 'Plan',
+ 'trip.tabs.transports': 'Transports',
+ 'trip.tabs.reservations': 'Réservations',
+ 'trip.tabs.reservationsShort': 'Résa',
+ 'trip.tabs.packing': 'Liste de bagages',
+ 'trip.tabs.packingShort': 'Bagages',
+ 'trip.tabs.lists': 'Listes',
+ 'trip.tabs.listsShort': 'Listes',
+ 'trip.tabs.budget': "Costs",
+ 'trip.tabs.files': 'Fichiers',
+ 'trip.loading': 'Chargement du voyage…',
+ 'trip.loadingPhotos': 'Chargement des photos des lieux...',
+ 'trip.mobilePlan': 'Plan',
+ 'trip.mobilePlaces': 'Lieux',
+ 'trip.toast.placeUpdated': 'Lieu mis à jour',
+ 'trip.toast.placeAdded': 'Lieu ajouté',
+ 'trip.toast.placeDeleted': 'Lieu supprimé',
+ 'trip.toast.selectDay': "Veuillez d'abord sélectionner un jour",
+ 'trip.toast.assignedToDay': 'Lieu attribué au planning',
+ 'trip.toast.reorderError': 'Échec de la réorganisation',
+ 'trip.toast.reservationUpdated': 'Réservation mise à jour',
+ 'trip.toast.reservationAdded': 'Réservation ajoutée',
+ 'trip.toast.deleted': 'Supprimé',
+ 'trip.confirm.deletePlace': 'Voulez-vous vraiment supprimer ce lieu ?',
+ 'trip.confirm.deletePlaces': 'Supprimer {count} lieux?',
+ 'trip.toast.placesDeleted': '{count} lieux supprimés',
+};
+export default trip;
diff --git a/shared/src/i18n/fr/trips.ts b/shared/src/i18n/fr/trips.ts
new file mode 100644
index 0000000000000000000000000000000000000000..46268fa19c03d05c014f816cfff3bbe452a8c401
--- /dev/null
+++ b/shared/src/i18n/fr/trips.ts
@@ -0,0 +1,17 @@
+import type { TranslationStrings } from '../types';
+
+const trips: TranslationStrings = {
+ 'trips.memberRemoved': '{username} supprimé',
+ 'trips.memberRemoveError': 'Échec de la suppression',
+ 'trips.memberAdded': '{username} ajouté',
+ 'trips.memberAddError': "Échec de l'ajout",
+ 'trips.reminder': 'Rappel',
+ 'trips.reminderNone': 'Aucun',
+ 'trips.reminderDay': 'jour',
+ 'trips.reminderDays': 'jours',
+ 'trips.reminderCustom': 'Personnalisé',
+ 'trips.reminderDaysBefore': 'jours avant le départ',
+ 'trips.reminderDisabledHint':
+ 'Les rappels de voyage sont désactivés. Activez-les dans Admin > Paramètres > Notifications.',
+};
+export default trips;
diff --git a/shared/src/i18n/fr/undo.ts b/shared/src/i18n/fr/undo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b241c3c84ace5d7e58789cdcbad0092ff6203416
--- /dev/null
+++ b/shared/src/i18n/fr/undo.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const undo: TranslationStrings = {
+ '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.deletePlaces': 'Lieux supprimés',
+ 'undo.moveDay': 'Lieu déplacé vers un autre jour',
+ 'undo.lock': 'Verrouillage du lieu modifié',
+ 'undo.importGpx': 'Import GPX',
+ 'undo.importKeyholeMarkup': 'Import KMZ/KML',
+ 'undo.importGoogleList': 'Import Google Maps',
+ 'undo.importNaverList': 'Import Naver Maps',
+ 'undo.addPlace': 'Lieu ajouté',
+ 'undo.done': 'Annulé : {action}',
+ 'undo.importBooking': 'Import de confirmation de réservation',
+};
+export default undo;
diff --git a/shared/src/i18n/fr/vacay.ts b/shared/src/i18n/fr/vacay.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f8b03b16f0a5d6f04d785e565bb70e15f73c94b8
--- /dev/null
+++ b/shared/src/i18n/fr/vacay.ts
@@ -0,0 +1,109 @@
+import type { TranslationStrings } from '../types';
+
+const vacay: TranslationStrings = {
+ 'vacay.subtitle': 'Planifiez et gérez vos jours de congés',
+ 'vacay.settings': 'Paramètres',
+ 'vacay.year': 'Année',
+ 'vacay.addYear': "Ajouter l'année suivante",
+ 'vacay.addPrevYear': "Ajouter l'année précédente",
+ 'vacay.removeYear': "Supprimer l'année",
+ 'vacay.removeYearConfirm': 'Supprimer {year} ?',
+ 'vacay.removeYearHint':
+ "Toutes les entrées de vacances et jours fériés d'entreprise de cette année seront définitivement supprimés.",
+ 'vacay.remove': 'Supprimer',
+ 'vacay.persons': 'Personnes',
+ 'vacay.noPersons': 'Aucune personne ajoutée',
+ 'vacay.addPerson': 'Ajouter une personne',
+ 'vacay.editPerson': 'Modifier la personne',
+ 'vacay.removePerson': 'Supprimer la personne',
+ 'vacay.removePersonConfirm': 'Supprimer {name} ?',
+ 'vacay.removePersonHint':
+ 'Toutes les entrées de vacances de cette personne seront définitivement supprimées.',
+ 'vacay.personName': 'Nom',
+ 'vacay.personNamePlaceholder': 'Saisir le nom',
+ 'vacay.color': 'Couleur',
+ 'vacay.add': 'Ajouter',
+ 'vacay.legend': 'Légende',
+ 'vacay.publicHoliday': 'Jour férié',
+ 'vacay.companyHoliday': "Jour férié d'entreprise",
+ 'vacay.weekend': 'Week-end',
+ 'vacay.modeVacation': 'Vacances',
+ 'vacay.modeCompany': "Jour férié d'entreprise",
+ 'vacay.entitlement': 'Droits',
+ 'vacay.entitlementDays': 'Jours',
+ 'vacay.used': 'Utilisés',
+ 'vacay.remaining': 'Restants',
+ 'vacay.carriedOver': 'de {year}',
+ 'vacay.weekendDays': 'Jours de week-end',
+ 'vacay.mon': 'Lun',
+ 'vacay.tue': 'Mar',
+ 'vacay.wed': 'Mer',
+ 'vacay.thu': 'Jeu',
+ 'vacay.fri': 'Ven',
+ 'vacay.sat': 'Sam',
+ 'vacay.sun': 'Dim',
+ 'vacay.blockWeekends': 'Bloquer les week-ends',
+ 'vacay.blockWeekendsHint':
+ 'Empêcher les entrées de vacances les samedis et dimanches',
+ 'vacay.publicHolidays': 'Jours fériés',
+ 'vacay.publicHolidaysHint': 'Marquer les jours fériés dans le calendrier',
+ 'vacay.selectCountry': 'Sélectionner un pays',
+ 'vacay.selectRegion': 'Sélectionner une région (facultatif)',
+ 'vacay.companyHolidays': "Jours fériés d'entreprise",
+ 'vacay.companyHolidaysHint':
+ "Autoriser le marquage des jours fériés d'entreprise",
+ 'vacay.companyHolidaysNoDeduct':
+ "Les jours fériés d'entreprise ne sont pas déduits des jours de vacances.",
+ 'vacay.weekStart': 'La semaine commence le',
+ 'vacay.weekStartHint':
+ 'Choisissez si la semaine commence le lundi ou le dimanche',
+ 'vacay.carryOver': 'Report',
+ 'vacay.carryOverHint':
+ "Reporter automatiquement les jours de vacances restants à l'année suivante",
+ 'vacay.sharing': 'Partage',
+ 'vacay.sharingHint':
+ "Partagez votre plan de vacances avec d'autres utilisateurs TREK",
+ 'vacay.owner': 'Propriétaire',
+ 'vacay.shareEmailPlaceholder': "E-mail de l'utilisateur TREK",
+ 'vacay.shareSuccess': 'Plan partagé avec succès',
+ 'vacay.shareError': 'Impossible de partager le plan',
+ 'vacay.dissolve': 'Séparer les calendriers',
+ 'vacay.dissolveHint':
+ 'Séparer à nouveau les calendriers. Vos entrées seront conservées.',
+ 'vacay.dissolveAction': 'Dissoudre',
+ 'vacay.dissolved': 'Calendrier séparé',
+ 'vacay.fusedWith': 'Partagé avec',
+ 'vacay.you': 'vous',
+ 'vacay.noData': 'Aucune donnée',
+ 'vacay.changeColor': 'Changer la couleur',
+ 'vacay.inviteUser': 'Inviter un utilisateur',
+ 'vacay.inviteHint':
+ 'Invitez un autre utilisateur TREK à partager un calendrier de vacances combiné.',
+ 'vacay.selectUser': 'Sélectionner un utilisateur',
+ 'vacay.sendInvite': "Envoyer l'invitation",
+ 'vacay.inviteSent': 'Invitation envoyée',
+ 'vacay.inviteError': "Impossible d'envoyer l'invitation",
+ 'vacay.pending': 'en attente',
+ 'vacay.noUsersAvailable': 'Aucun utilisateur disponible',
+ 'vacay.accept': 'Accepter',
+ 'vacay.decline': 'Refuser',
+ 'vacay.acceptFusion': 'Accepter et fusionner',
+ 'vacay.inviteTitle': 'Demande de fusion',
+ 'vacay.inviteWantsToFuse':
+ 'souhaite partager un calendrier de vacances avec vous.',
+ 'vacay.fuseInfo1':
+ 'Vous verrez tous les deux toutes les entrées de vacances dans un calendrier partagé.',
+ 'vacay.fuseInfo2':
+ "Les deux parties peuvent créer et modifier des entrées pour l'autre.",
+ 'vacay.fuseInfo3':
+ 'Les deux parties peuvent supprimer des entrées et modifier les droits aux vacances.',
+ 'vacay.fuseInfo4':
+ "Les paramètres comme les jours fériés et les jours d'entreprise sont partagés.",
+ 'vacay.fuseInfo5':
+ "La fusion peut être dissoute à tout moment par l'une ou l'autre partie. Vos entrées seront préservées.",
+ 'vacay.addCalendar': 'Ajouter un calendrier',
+ 'vacay.calendarColor': 'Couleur',
+ 'vacay.calendarLabel': 'Libellé',
+ 'vacay.noCalendars': 'Aucun calendrier',
+};
+export default vacay;
diff --git a/shared/src/i18n/gr/admin.ts b/shared/src/i18n/gr/admin.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c38e5b5c9395f6a8375e98ca6823dd58b5106404
--- /dev/null
+++ b/shared/src/i18n/gr/admin.ts
@@ -0,0 +1,403 @@
+import type { TranslationStrings } from '../types';
+
+const admin: TranslationStrings = {
+ 'admin.notifications.title': 'Ειδοποιήσεις',
+ 'admin.notifications.hint':
+ 'Επιλέξτε ένα κανάλι ειδοποιήσεων. Μόνο ένα μπορεί να είναι ενεργό κάθε φορά.',
+ 'admin.notifications.none': 'Απενεργοποιημένο',
+ 'admin.notifications.email': 'Email (SMTP)',
+ 'admin.notifications.webhook': 'Webhook',
+ 'admin.notifications.ntfy': 'Ntfy',
+ 'admin.ntfy.hint':
+ 'Επιτρέψτε στους χρήστες να διαμορφώνουν τα δικά τους θέματα ntfy για push ειδοποιήσεις. Ορίστε τον προεπιλεγμένο server παρακάτω για να προ-συμπληρώνει τις ρυθμίσεις χρηστών.',
+ 'admin.notifications.save': 'Αποθήκευση ρυθμίσεων ειδοποιήσεων',
+ 'admin.notifications.saved': 'Οι ρυθμίσεις ειδοποιήσεων αποθηκεύτηκαν',
+ 'admin.notifications.testWebhook': 'Αποστολή δοκιμαστικού webhook',
+ 'admin.notifications.testWebhookSuccess':
+ 'Η δοκιμή webhook στάλθηκε επιτυχώς',
+ 'admin.notifications.testWebhookFailed': 'Η δοκιμή webhook απέτυχε',
+ 'admin.notifications.testNtfy': 'Αποστολή δοκιμαστικού ntfy',
+ 'admin.notifications.testNtfySuccess': 'Η δοκιμή ntfy στάλθηκε επιτυχώς',
+ 'admin.notifications.testNtfyFailed': 'Η δοκιμή ntfy απέτυχε',
+ 'admin.notifications.emailPanel.title': 'Email (SMTP)',
+ 'admin.notifications.webhookPanel.title': 'Webhook',
+ 'admin.notifications.inappPanel.title': 'Εντός εφαρμογής',
+ 'admin.notifications.inappPanel.hint':
+ 'Οι ειδοποιήσεις εντός εφαρμογής είναι πάντα ενεργές και δεν μπορούν να απενεργοποιηθούν καθολικά.',
+ 'admin.notifications.adminWebhookPanel.title': 'Webhook Διαχειριστή',
+ 'admin.notifications.adminWebhookPanel.hint':
+ 'Αυτό το webhook χρησιμοποιείται αποκλειστικά για ειδοποιήσεις διαχειριστή (π.χ. ειδοποιήσεις έκδοσης). Είναι ξεχωριστό από τα webhooks των χρηστών και ενεργοποιείται πάντα όταν έχει οριστεί.',
+ 'admin.notifications.adminWebhookPanel.saved':
+ 'Το URL του webhook διαχειριστή αποθηκεύτηκε',
+ 'admin.notifications.adminWebhookPanel.testSuccess':
+ 'Η δοκιμή webhook στάλθηκε επιτυχώς',
+ 'admin.notifications.adminWebhookPanel.testFailed':
+ 'Η δοκιμή webhook απέτυχε',
+ 'admin.notifications.adminWebhookPanel.alwaysOnHint':
+ 'Το webhook διαχειριστή ενεργοποιείται πάντα όταν έχει διαμορφωθεί ένα URL',
+ 'admin.notifications.adminNtfyPanel.title': 'Ntfy Διαχειριστή',
+ 'admin.notifications.adminNtfyPanel.hint':
+ 'Αυτό το θέμα ntfy χρησιμοποιείται αποκλειστικά για ειδοποιήσεις διαχειριστή (π.χ. ειδοποιήσεις έκδοσης). Είναι ξεχωριστό από τα θέματα των χρηστών και ενεργοποιείται πάντα όταν έχει διαμορφωθεί.',
+ 'admin.notifications.adminNtfyPanel.serverLabel': 'URL Ntfy Server',
+ 'admin.notifications.adminNtfyPanel.serverHint':
+ 'Χρησιμοποιείται επίσης ως ο προεπιλεγμένος server για τις ντόπιες ntfy ειδοποιήσεις χρηστών. Αφήστε κενό για προεπιλογή ntfy.sh. Οι χρήστες μπορούν να το παρακάμψουν στις δικές τους ρυθμίσεις.',
+ 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh',
+ 'admin.notifications.adminNtfyPanel.topicLabel': 'Θέμα Διαχειριστή',
+ 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts',
+ 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (προαιρετικό)',
+ 'admin.notifications.adminNtfyPanel.tokenCleared':
+ 'Το access token διαχειριστή εκκαθαρίστηκε',
+ 'admin.notifications.adminNtfyPanel.saved':
+ 'Οι ρυθμίσεις ntfy διαχειριστή αποθηκεύτηκαν',
+ 'admin.notifications.adminNtfyPanel.test': 'Αποστολή δοκιμαστικού ntfy',
+ 'admin.notifications.adminNtfyPanel.testSuccess':
+ 'Η δοκιμή ntfy στάλθηκε επιτυχώς',
+ 'admin.notifications.adminNtfyPanel.testFailed': 'Η δοκιμή ntfy απέτυχε',
+ 'admin.notifications.adminNtfyPanel.alwaysOnHint':
+ 'Το ntfy διαχειριστή ενεργοποιείται πάντα όταν έχει διαμορφωθεί ένα θέμα',
+ 'admin.notifications.adminNotificationsHint':
+ 'Διαμορφώστε ποια κανάλια παραδίδουν ειδοποιήσεις μόνο για διαχειριστές (π.χ. ειδοποιήσεις έκδοσης).',
+ 'admin.notifications.tripReminders.title': 'Υπενθυμίσεις Ταξιδιού',
+ 'admin.notifications.tripReminders.hint':
+ 'Αποστολή ειδοποίησης υπενθύμισης πριν την έναρξη ενός ταξιδιού (απαιτεί να έχουν οριστεί ημέρες υπενθύμισης στο ταξίδι).',
+ 'admin.notifications.tripReminders.enabled':
+ 'Οι υπενθυμίσεις ταξιδιού ενεργοποιήθηκαν',
+ 'admin.notifications.tripReminders.disabled':
+ 'Οι υπενθυμίσεις ταξιδιού απενεργοποιήθηκαν',
+ 'admin.smtp.title': 'Email & Ειδοποιήσεις',
+ 'admin.smtp.hint': 'Διαμόρφωση SMTP για αποστολή ειδοποιήσεων email.',
+ 'admin.smtp.testButton': 'Αποστολή δοκιμαστικού email',
+ 'admin.webhook.hint':
+ 'Επιτρέψτε στους χρήστες να διαμορφώνουν τα δικά τους webhook URLs για ειδοποιήσεις (Discord, Slack, κ.λπ.).',
+ 'admin.smtp.testSuccess': 'Το δοκιμαστικό email στάλθηκε επιτυχώς',
+ 'admin.smtp.testFailed': 'Το δοκιμαστικό email απέτυχε',
+ 'admin.title': 'Διαχείριση',
+ 'admin.subtitle': 'Διαχείριση χρηστών και ρυθμίσεις συστήματος',
+ 'admin.tabs.users': 'Χρήστες',
+ 'admin.tabs.categories': 'Κατηγορίες',
+ 'admin.tabs.backup': 'Αντίγραφο ασφαλείας',
+ 'admin.tabs.notifications': 'Ειδοποιήσεις',
+ 'admin.tabs.audit': 'Έλεγχος',
+ 'admin.stats.users': 'Χρήστες',
+ 'admin.stats.trips': 'Ταξίδια',
+ 'admin.stats.places': 'Τόποι',
+ 'admin.stats.photos': 'Φωτογραφίες',
+ 'admin.stats.files': 'Αρχεία',
+ 'admin.table.user': 'Χρήστης',
+ 'admin.table.email': 'Email',
+ 'admin.table.role': 'Ρόλος',
+ 'admin.table.created': 'Δημιουργήθηκε',
+ 'admin.table.lastLogin': 'Τελευταία Σύνδεση',
+ 'admin.table.actions': 'Ενέργειες',
+ 'admin.you': '(Εσείς)',
+ 'admin.editUser': 'Επεξεργασία Χρήστη',
+ 'admin.newPassword': 'Νέος Κωδικός',
+ 'admin.newPasswordHint': 'Αφήστε κενό για να διατηρηθεί ο τρέχων κωδικός',
+ 'admin.deleteUser':
+ 'Διαγραφή χρήστη "{name}"; Όλα τα ταξίδια θα διαγραφούν μόνιμα.',
+ 'admin.deleteUserTitle': 'Διαγραφή χρήστη',
+ 'admin.newPasswordPlaceholder': 'Εισάγετε νέο κωδικό…',
+ 'admin.toast.loadError': 'Αποτυχία φόρτωσης δεδομένων διαχείρισης',
+ 'admin.toast.userUpdated': 'Ο χρήστης ενημερώθηκε',
+ 'admin.toast.updateError': 'Αποτυχία ενημέρωσης',
+ 'admin.toast.userDeleted': 'Ο χρήστης διαγράφηκε',
+ 'admin.toast.deleteError': 'Αποτυχία διαγραφής',
+ 'admin.toast.cannotDeleteSelf':
+ 'Δεν μπορείτε να διαγράψετε τον δικό σας λογαριασμό',
+ 'admin.toast.userCreated': 'Ο χρήστης δημιουργήθηκε',
+ 'admin.toast.createError': 'Αποτυχία δημιουργίας χρήστη',
+ 'admin.toast.fieldsRequired':
+ 'Το όνομα χρήστη, το email και ο κωδικός είναι υποχρεωτικά',
+ 'admin.createUser': 'Δημιουργία Χρήστη',
+ 'admin.invite.title': 'Σύνδεσμοι Πρόσκλησης',
+ 'admin.invite.subtitle': 'Δημιουργία συνδέσμων εγγραφής μίας χρήσης',
+ 'admin.invite.create': 'Δημιουργία Συνδέσμου',
+ 'admin.invite.createAndCopy': 'Δημιουργία & Αντιγραφή',
+ 'admin.invite.empty': 'Δεν έχουν δημιουργηθεί σύνδεσμοι πρόσκλησης ακόμη',
+ 'admin.invite.maxUses': 'Μέγ. Χρήσεις',
+ 'admin.invite.expiry': 'Λήγει μετά από',
+ 'admin.invite.uses': 'χρησιμοποιήθηκε',
+ 'admin.invite.expiresAt': 'λήγει',
+ 'admin.invite.createdBy': 'από',
+ 'admin.invite.active': 'Ενεργός',
+ 'admin.invite.expired': 'Έληξε',
+ 'admin.invite.usedUp': 'Εξαντλήθηκε',
+ 'admin.invite.copied': 'Ο σύνδεσμος πρόσκλησης αντιγράφηκε στο πρόχειρο',
+ 'admin.invite.copyLink': 'Αντιγραφή συνδέσμου',
+ 'admin.invite.deleted': 'Ο σύνδεσμος πρόσκλησης διαγράφηκε',
+ 'admin.invite.createError': 'Αποτυχία δημιουργίας συνδέσμου πρόσκλησης',
+ 'admin.invite.deleteError': 'Αποτυχία διαγραφής συνδέσμου πρόσκλησης',
+ 'admin.tabs.settings': 'Ρυθμίσεις',
+ 'admin.allowRegistration': 'Να επιτρέπονται οι εγγραφές',
+ 'admin.allowRegistrationHint':
+ 'Οι νέοι χρήστες μπορούν να εγγράφονται μόνοι τους',
+ 'admin.authMethods': 'Μέθοδοι Ελέγχου Ταυτότητας',
+ 'admin.passwordLogin': 'Σύνδεση με Κωδικό',
+ 'admin.passwordLoginHint':
+ 'Επιτρέψτε στους χρήστες να συνδέονται με email και κωδικό',
+ 'admin.passwordRegistration': 'Εγγραφή με Κωδικό',
+ 'admin.passwordRegistrationHint':
+ 'Επιτρέψτε σε νέους χρήστες να εγγράφονται με email και κωδικό',
+ 'admin.oidcLogin': 'Σύνδεση SSO',
+ 'admin.oidcLoginHint': 'Επιτρέψτε στους χρήστες να συνδέονται με SSO',
+ 'admin.oidcRegistration': 'Αυτόματη Δημιουργία SSO',
+ 'admin.oidcRegistrationHint':
+ 'Αυτόματη δημιουργία λογαριασμών για νέους χρήστες SSO',
+ 'admin.envOverrideHint':
+ 'Οι ρυθμίσεις σύνδεσης με κωδικό ελέγχονται από τη μεταβλητή περιβάλλοντος OIDC_ONLY και δεν μπορούν να αλλάξουν εδώ.',
+ 'admin.lockoutWarning':
+ 'Πρέπει να παραμείνει ενεργοποιημένη τουλάχιστον μία μέθοδος σύνδεσης',
+ 'admin.requireMfa': 'Απαίτηση ελέγχου ταυτότητας δύο παραγόντων (2FA)',
+ 'admin.requireMfaHint':
+ 'Οι χρήστες χωρίς 2FA πρέπει να ολοκληρώσουν τη ρύθμιση στις Ρυθμίσεις πριν χρησιμοποιήσουν την εφαρμογή.',
+ 'admin.apiKeys': 'Κλειδιά API',
+ 'admin.apiKeysHint':
+ 'Προαιρετικά. Ενεργοποιεί εκτεταμένα δεδομένα τόπων όπως φωτογραφίες και καιρό.',
+ 'admin.mapsKey': 'Google Maps API Key',
+ 'admin.mapsKeyHint':
+ 'Απαιτείται για αναζήτηση τόπων. Λάβετε το από console.cloud.google.com',
+ 'admin.mapsKeyHintLong':
+ 'Χωρίς κλειδί API, χρησιμοποιείται το OpenStreetMap για αναζήτηση τόπων. Με κλειδί Google API, μπορούν επίσης να φορτωθούν φωτογραφίες, βαθμολογίες και ώρες λειτουργίας. Λάβετε ένα στο console.cloud.google.com.',
+ 'admin.recommended': 'Συνιστάται',
+ 'admin.weatherKey': 'OpenWeatherMap API Key',
+ 'admin.weatherKeyHint': 'Για δεδομένα καιρού. Δωρεάν στο openweathermap.org',
+ 'admin.validateKey': 'Δοκιμή',
+ 'admin.keyValid': 'Συνδέθηκε',
+ 'admin.keyInvalid': 'Μη έγκυρο',
+ 'admin.keySaved': 'Τα κλειδιά API αποθηκεύτηκαν',
+ 'admin.oidcTitle': 'Ενιαία Σύνδεση (OIDC)',
+ 'admin.oidcSubtitle':
+ 'Επιτρέψτε τη σύνδεση μέσω εξωτερικών παρόχων όπως Google, Apple, Authentik ή Keycloak.',
+ 'admin.oidcDisplayName': 'Εμφανιζόμενο Όνομα',
+ 'admin.oidcIssuer': 'Issuer URL',
+ 'admin.oidcIssuerHint':
+ 'Το OpenID Connect Issuer URL του παρόχου. π.χ. https://accounts.google.com',
+ 'admin.oidcSaved': 'Η διαμόρφωση OIDC αποθηκεύτηκε',
+ 'admin.oidcOnlyMode': 'Απενεργοποίηση ελέγχου ταυτότητας με κωδικό',
+ 'admin.oidcOnlyModeHint':
+ 'Όταν είναι ενεργοποιημένο, επιτρέπεται μόνο η σύνδεση SSO. Η σύνδεση και εγγραφή με κωδικό αποκλείονται.',
+ 'admin.fileTypes': 'Επιτρεπόμενοι Τύποι Αρχείων',
+ 'admin.fileTypesHint':
+ 'Ρυθμίστε ποιους τύπους αρχείων μπορούν να μεταφορτώνουν οι χρήστες.',
+ 'admin.fileTypesFormat':
+ 'Επεκτάσεις χωρισμένες με κόμμα (π.χ. jpg,png,pdf,doc). Χρησιμοποιήστε * για να επιτρέπονται όλοι οι τύποι.',
+ 'admin.fileTypesSaved': 'Οι ρυθμίσεις τύπων αρχείων αποθηκεύτηκαν',
+ 'admin.placesPhotos.title': 'Φωτογραφίες Τόπων',
+ 'admin.placesPhotos.subtitle':
+ 'Λήψη φωτογραφιών από το Google Places API. Απενεργοποιήστε για εξοικονόμηση ποσοστώσης API. Οι φωτογραφίες Wikimedia δεν επηρεάζονται.',
+ 'admin.placesAutocomplete.title': 'Αυτόματη Συμπλήρωση Τόπων',
+ 'admin.placesAutocomplete.subtitle':
+ 'Χρήση του Google Places API για προτάσεις αναζήτησης. Απενεργοποιήστε για εξοικονόμηση ποσοστώσης API.',
+ 'admin.placesDetails.title': 'Λεπτομέρειες Τόπου',
+ 'admin.placesDetails.subtitle':
+ 'Λήψη λεπτομερών πληροφοριών τόπου (ώρες, βαθμολογία, ιστοσελίδα) από το Google Places API. Απενεργοποιήστε για εξοικονόμηση ποσοστώσης API.',
+ 'admin.bagTracking.title': 'Παρακολούθηση Αποσκευών',
+ 'admin.bagTracking.subtitle':
+ 'Ενεργοποίηση βάρους και ανάθεσης τσάντας για αντικείμενα πακεταρίσματος',
+ 'admin.collab.chat.title': 'Συνομιλία',
+ 'admin.collab.chat.subtitle':
+ 'Μηνύματα πραγματικού χρόνου για συνεργασία ταξιδιού',
+ 'admin.collab.notes.title': 'Σημειώσεις',
+ 'admin.collab.notes.subtitle': 'Κοινόχρηστες σημειώσεις και έγγραφα',
+ 'admin.collab.polls.title': 'Δημοσκοπήσεις',
+ 'admin.collab.polls.subtitle': 'Ομαδικές δημοσκοπήσεις και ψηφοφορίες',
+ 'admin.collab.whatsnext.title': 'Τι Ακολουθεί',
+ 'admin.collab.whatsnext.subtitle':
+ 'Προτάσεις δραστηριοτήτων και επόμενα βήματα',
+ 'admin.tabs.config': 'Εξατομίκευση',
+ 'admin.tabs.defaults': 'Προεπιλογές Χρήστη',
+ 'admin.defaultSettings.title': 'Προεπιλεγμένες Ρυθμίσεις Χρήστη',
+ 'admin.defaultSettings.description':
+ 'Ορίστε προεπιλογές για όλη την υπηρεσία. Οι χρήστες που δεν έχουν αλλάξει μια ρύθμιση θα βλέπουν αυτές τις τιμές. Οι δικές τους αλλαγές έχουν πάντα προτεραιότητα.',
+ 'admin.defaultSettings.saved': 'Η προεπιλογή αποθηκεύτηκε',
+ 'admin.defaultSettings.reset': 'Επαναφορά στην ενσωματωμένη προεπιλογή',
+ 'admin.defaultSettings.resetToBuiltIn': 'επαναφορά',
+ 'admin.tabs.templates': 'Πρότυπα Πακεταρίσματος',
+ 'admin.packingTemplates.title': 'Πρότυπα Πακεταρίσματος',
+ 'admin.packingTemplates.subtitle':
+ 'Δημιουργήστε επαναχρησιμοποιήσιμες λίστες πακεταρίσματος για τα ταξίδια σας',
+ 'admin.packingTemplates.create': 'Νέο Πρότυπο',
+ 'admin.packingTemplates.namePlaceholder':
+ 'Όνομα προτύπου (π.χ. Διακοπές στην Παραλία)',
+ 'admin.packingTemplates.empty': 'Δεν έχουν δημιουργηθεί πρότυπα ακόμη',
+ 'admin.packingTemplates.items': 'αντικείμενα',
+ 'admin.packingTemplates.categories': 'κατηγορίες',
+ 'admin.packingTemplates.itemName': 'Όνομα αντικειμένου',
+ 'admin.packingTemplates.itemCategory': 'Κατηγορία',
+ 'admin.packingTemplates.categoryName': 'Όνομα κατηγορίας (π.χ. Ρούχα)',
+ 'admin.packingTemplates.addCategory': 'Προσθήκη κατηγορίας',
+ 'admin.packingTemplates.created': 'Το πρότυπο δημιουργήθηκε',
+ 'admin.packingTemplates.deleted': 'Το πρότυπο διαγράφηκε',
+ 'admin.packingTemplates.loadError': 'Αποτυχία φόρτωσης προτύπων',
+ 'admin.packingTemplates.createError': 'Αποτυχία δημιουργίας προτύπου',
+ 'admin.packingTemplates.deleteError': 'Αποτυχία διαγραφής προτύπου',
+ 'admin.packingTemplates.saveError': 'Αποτυχία αποθήκευσης',
+ 'admin.tabs.addons': 'Πρόσθετα',
+ 'admin.addons.title': 'Πρόσθετα',
+ 'admin.addons.subtitle':
+ 'Ενεργοποιήστε ή απενεργοποιήστε λειτουργίες για να εξατομικεύσετε την εμπειρία σας στο TREK.',
+ 'admin.addons.catalog.packing.name': 'Λίστες',
+ 'admin.addons.catalog.packing.description':
+ 'Λίστες πακεταρίσματος και εργασίες προς εκτέλεση για τα ταξίδια σας',
+ 'admin.addons.catalog.budget.name': 'Προϋπολογισμός',
+ 'admin.addons.catalog.budget.description':
+ 'Παρακολουθήστε τα έξοδα και σχεδιάστε τον προϋπολογισμό του ταξιδιού σας',
+ 'admin.addons.catalog.documents.name': 'Έγγραφα',
+ 'admin.addons.catalog.documents.description':
+ 'Αποθηκεύστε και διαχειριστείτε ταξιδιωτικά έγγραφα',
+ 'admin.addons.catalog.vacay.name': 'Vacay',
+ 'admin.addons.catalog.vacay.description':
+ 'Προσωπικός σχεδιαστής διακοπών με προβολή ημερολογίου',
+ 'admin.addons.catalog.atlas.name': 'Atlas',
+ 'admin.addons.catalog.atlas.description':
+ 'Παγκόσμιος χάρτης με χώρες που έχετε επισκεφθεί και ταξιδιωτικά στατιστικά',
+ 'admin.addons.catalog.collab.name': 'Collab',
+ 'admin.addons.catalog.collab.description':
+ 'Σημειώσεις, δημοσκοπήσεις και συνομιλία πραγματικού χρόνου για σχεδιασμό ταξιδιού',
+ 'admin.addons.catalog.memories.name': 'Φωτογραφίες (Immich)',
+ 'admin.addons.catalog.memories.description':
+ 'Μοιραστείτε φωτογραφίες ταξιδιού μέσω της δικής σας εγκατάστασης Immich',
+ 'admin.addons.catalog.mcp.name': 'MCP',
+ 'admin.addons.catalog.mcp.description':
+ 'Model Context Protocol για ενσωμάτωση βοηθού AI',
+ 'admin.addons.subtitleBefore':
+ 'Ενεργοποιήστε ή απενεργοποιήστε λειτουργίες για να εξατομικεύσετε την εμπειρία σας στο ',
+ 'admin.addons.subtitleAfter': '.',
+ 'admin.addons.enabled': 'Ενεργοποιημένο',
+ 'admin.addons.disabled': 'Απενεργοποιημένο',
+ 'admin.addons.type.trip': 'Ταξίδι',
+ 'admin.addons.type.global': 'Καθολικό',
+ 'admin.addons.type.integration': 'Ενσωμάτωση',
+ 'admin.addons.tripHint': 'Διαθέσιμο ως καρτέλα μέσα σε κάθε ταξίδι',
+ 'admin.addons.globalHint':
+ 'Διαθέσιμο ως αυτόνομη ενότητα στην κύρια πλοήγηση',
+ 'admin.addons.integrationHint':
+ 'Υπηρεσίες backend και ενσωματώσεις API χωρίς ειδική σελίδα',
+ 'admin.addons.toast.updated': 'Το πρόσθετο ενημερώθηκε',
+ 'admin.addons.toast.error': 'Αποτυχία ενημέρωσης προσθέτου',
+ 'admin.addons.noAddons': 'Δεν υπάρχουν διαθέσιμα πρόσθετα',
+ 'admin.weather.title': 'Δεδομένα Καιρού',
+ 'admin.weather.badge': 'Από 24 Μαρτίου 2026',
+ 'admin.weather.description':
+ 'Το TREK χρησιμοποιεί το Open-Meteo ως πηγή δεδομένων καιρού. Το Open-Meteo είναι μια δωρεάν, ανοιχτού κώδικα υπηρεσία καιρού — δεν απαιτείται κλειδί API.',
+ 'admin.weather.forecast': 'Πρόγνωση 16 ημερών',
+ 'admin.weather.forecastDesc': 'Προηγουμένως 5 ημέρες (OpenWeatherMap)',
+ 'admin.weather.climate': 'Ιστορικά δεδομένα κλίματος',
+ 'admin.weather.climateDesc':
+ 'Μέσοι όροι από τα τελευταία 85 χρόνια για ημέρες πέρα από την πρόγνωση 16 ημερών',
+ 'admin.weather.requests': '10.000 αιτήματα / ημέρα',
+ 'admin.weather.requestsDesc': 'Δωρεάν, δεν απαιτείται κλειδί API',
+ 'admin.weather.locationHint':
+ 'Ο καιρός βασίζεται στον πρώτο τόπο με συντεταγμένες σε κάθε ημέρα. Αν δεν έχει ανατεθεί τόπος σε μια ημέρα, χρησιμοποιείται ως αναφορά οποιοσδήποτε τόπος από τη λίστα.',
+ 'admin.tabs.mcpTokens': 'Πρόσβαση MCP',
+ 'admin.mcpTokens.title': 'Πρόσβαση MCP',
+ 'admin.mcpTokens.subtitle':
+ 'Διαχείριση συνεδριών OAuth και διακριτικών API για όλους τους χρήστες',
+ 'admin.mcpTokens.sectionTitle': 'Διακριτικά API',
+ 'admin.mcpTokens.owner': 'Κάτοχος',
+ 'admin.mcpTokens.tokenName': 'Όνομα Διακριτικού',
+ 'admin.mcpTokens.created': 'Δημιουργήθηκε',
+ 'admin.mcpTokens.lastUsed': 'Τελευταία Χρήση',
+ 'admin.mcpTokens.never': 'Ποτέ',
+ 'admin.mcpTokens.empty': 'Δεν έχουν δημιουργηθεί διακριτικά MCP ακόμη',
+ 'admin.mcpTokens.deleteTitle': 'Διαγραφή Διακριτικού',
+ 'admin.mcpTokens.deleteMessage':
+ 'Αυτό θα ανακαλέσει το διακριτικό άμεσα. Ο χρήστης θα χάσει την πρόσβαση MCP μέσω αυτού του διακριτικού.',
+ 'admin.mcpTokens.deleteSuccess': 'Το διακριτικό διαγράφηκε',
+ 'admin.mcpTokens.deleteError': 'Αποτυχία διαγραφής διακριτικού',
+ 'admin.mcpTokens.loadError': 'Αποτυχία φόρτωσης διακριτικών',
+ 'admin.oauthSessions.sectionTitle': 'Συνεδρίες OAuth',
+ 'admin.oauthSessions.clientName': 'Πελάτης',
+ 'admin.oauthSessions.owner': 'Κάτοχος',
+ 'admin.oauthSessions.scopes': 'Scopes',
+ 'admin.oauthSessions.created': 'Δημιουργήθηκε',
+ 'admin.oauthSessions.empty': 'Δεν υπάρχουν ενεργές συνεδρίες OAuth',
+ 'admin.oauthSessions.revokeTitle': 'Ανάκληση Συνεδρίας',
+ 'admin.oauthSessions.revokeMessage':
+ 'Αυτό θα ανακαλέσει τη συνεδρία OAuth άμεσα. Ο πελάτης θα χάσει την πρόσβαση MCP.',
+ 'admin.oauthSessions.revokeSuccess': 'Η συνεδρία ανακλήθηκε',
+ 'admin.oauthSessions.revokeError': 'Αποτυχία ανάκλησης συνεδρίας',
+ 'admin.oauthSessions.loadError': 'Αποτυχία φόρτωσης συνεδριών OAuth',
+ 'admin.tabs.github': 'GitHub',
+ 'admin.audit.subtitle':
+ 'Συμβάντα ασφαλείας και διαχείρισης (αντίγραφα ασφαλείας, χρήστες, MFA, ρυθμίσεις).',
+ 'admin.audit.empty': 'Δεν υπάρχουν εγγραφές ελέγχου ακόμη.',
+ 'admin.audit.refresh': 'Ανανέωση',
+ 'admin.audit.loadMore': 'Φόρτωση περισσότερων',
+ 'admin.audit.showing': '{count} φορτώθηκαν · {total} σύνολο',
+ 'admin.audit.col.time': 'Ώρα',
+ 'admin.audit.col.user': 'Χρήστης',
+ 'admin.audit.col.action': 'Ενέργεια',
+ 'admin.audit.col.resource': 'Πόρος',
+ 'admin.audit.col.ip': 'IP',
+ 'admin.audit.col.details': 'Λεπτομέρειες',
+ 'admin.github.title': 'Ιστορικό Εκδόσεων',
+ 'admin.github.subtitle': 'Τελευταίες ενημερώσεις από {repo}',
+ 'admin.github.latest': 'Τελευταία',
+ 'admin.github.prerelease': 'Προ-έκδοση',
+ 'admin.github.showDetails': 'Εμφάνιση λεπτομερειών',
+ 'admin.github.hideDetails': 'Απόκρυψη λεπτομερειών',
+ 'admin.github.loadMore': 'Φόρτωση περισσότερων',
+ 'admin.github.loading': 'Φόρτωση...',
+ 'admin.github.error': 'Αποτυχία φόρτωσης εκδόσεων',
+ 'admin.github.by': 'από',
+ 'admin.github.support': 'Βοηθά να συνεχίσω την ανάπτυξη του TREK',
+ 'admin.update.available': 'Διαθέσιμη ενημέρωση',
+ 'admin.update.text':
+ 'Το TREK {version} είναι διαθέσιμο. Εκτελείτε το {current}.',
+ 'admin.update.button': 'Προβολή στο GitHub',
+ 'admin.update.install': 'Εγκατάσταση Ενημέρωσης',
+ 'admin.update.confirmTitle': 'Εγκατάσταση Ενημέρωσης;',
+ 'admin.update.confirmText':
+ 'Το TREK θα ενημερωθεί από {current} σε {version}. Ο server θα επανεκκινήσει αυτόματα στη συνέχεια.',
+ 'admin.update.dataInfo':
+ 'Όλα τα δεδομένα σας (ταξίδια, χρήστες, κλειδιά API, μεταφορτώσεις, Vacay, Atlas, προϋπολογισμοί) θα διατηρηθούν.',
+ 'admin.update.warning':
+ 'Η εφαρμογή θα είναι σύντομα μη διαθέσιμη κατά την επανεκκίνηση.',
+ 'admin.update.confirm': 'Ενημέρωση Τώρα',
+ 'admin.update.installing': 'Ενημέρωση…',
+ 'admin.update.success': 'Η ενημέρωση εγκαταστάθηκε! Ο server επανεκκινεί…',
+ 'admin.update.failed': 'Η ενημέρωση απέτυχε',
+ 'admin.update.backupHint':
+ 'Συνιστούμε να δημιουργήσετε ένα αντίγραφο ασφαλείας πριν την ενημέρωση.',
+ 'admin.update.backupLink': 'Μετάβαση στο Αντίγραφο Ασφαλείας',
+ 'admin.update.howTo': 'Πώς να Ενημερώσετε',
+ 'admin.update.dockerText':
+ 'Η εγκατάστασή σας TREK εκτελείται σε Docker. Για να ενημερωθείτε στο {version}, εκτελέστε τις ακόλουθες εντολές στον server σας:',
+ 'admin.update.reloadHint':
+ 'Παρακαλώ ανανεώστε τη σελίδα σε λίγα δευτερόλεπτα.',
+ 'admin.tabs.permissions': 'Δικαιώματα',
+ 'admin.addons.catalog.journey.name': 'Ταξίδι',
+ 'admin.addons.catalog.journey.description':
+ 'Παρακολούθηση ταξιδιών & ημερολόγιο ταξιδιών με αφίξεις, φωτογραφίες και καθημερινές ιστορίες',
+ 'admin.passkey.title': 'Σύνδεση με passkey',
+ 'admin.passkey.cardHint':
+ 'Επιτρέψτε στους χρήστες να συνδέονται με passkeys (WebAuthn). Απενεργοποιημένο από προεπιλογή.',
+ 'admin.passkey.login': 'Ενεργοποίηση σύνδεσης με passkey',
+ 'admin.passkey.loginHint':
+ 'Εμφανίστε μια επιλογή "Σύνδεση με passkey" και επιτρέψτε στους χρήστες να καταχωρούν passkeys στις ρυθμίσεις τους.',
+ 'admin.passkey.notConfigured':
+ 'Δεν αναλύεται ακόμη κανένας τομέας WebAuthn για αυτή την εγκατάσταση. Ορίστε το APP_URL ή το Relying Party ID παρακάτω — τα passkeys παραμένουν κρυφά μέχρι τότε.',
+ 'admin.passkey.rpId': 'Relying Party ID (τομέας)',
+ 'admin.passkey.rpIdHint':
+ 'Ο σκέτος τομέας στον οποίο δεσμεύονται τα passkeys, π.χ. trek.example.org. Αφήστε το κενό για να παραχθεί από το APP_URL. Η μεταγενέστερη αλλαγή του ακυρώνει τα υπάρχοντα passkeys.',
+ 'admin.passkey.origins': 'Επιτρεπόμενες προελεύσεις',
+ 'admin.passkey.originsHint':
+ 'Πλήρεις προελεύσεις χωρισμένες με κόμμα, π.χ. https://trek.example.org. Αφήστε το κενό για χρήση του APP_URL.',
+ 'admin.passkey.reset': 'Επαναφορά passkeys',
+ 'admin.passkey.resetHint':
+ 'Αφαιρέστε όλα τα passkeys αυτού του χρήστη (π.χ. σε περίπτωση χαμένης συσκευής). Μπορούν ακόμη να συνδεθούν με τον κωδικό τους.',
+ 'admin.passkey.resetConfirm': 'Αφαίρεση όλων των passkeys για τον/την {name};',
+ 'admin.passkey.resetDone': 'Αφαιρέθηκαν {count} passkey(s)',
+ 'admin.defaultSettings.mapProvider': 'Μηχανή χάρτη',
+ 'admin.defaultSettings.mapProviderHint': 'Ο προεπιλεγμένος χάρτης για όλους σε αυτή την εγκατάσταση. Κάθε χρήστης μπορεί να τον αλλάξει στις δικές του ρυθμίσεις.',
+ 'admin.defaultSettings.providerLeaflet': 'Τυπικός (δωρεάν)',
+ 'admin.defaultSettings.providerMapbox': 'Mapbox (3D)',
+ 'admin.defaultSettings.mapboxToken': 'Κοινόχρηστο διακριτικό Mapbox',
+ 'admin.defaultSettings.mapboxTokenHint': 'Χρησιμοποιείται για κάθε χρήστη που δεν έχει εισαγάγει το δικό του διακριτικό — έτσι ολόκληρη η εγκατάσταση αποκτά Mapbox χωρίς να μοιράζεται το κλειδί ξεχωριστά. Αποθηκεύεται κρυπτογραφημένο.',
+ 'admin.defaultSettings.mapboxStyle': 'Στυλ χάρτη',
+ 'admin.defaultSettings.mapboxStylePlaceholder': 'Επιλέξτε ένα στυλ…',
+ 'admin.defaultSettings.mapbox3d': 'Κτίρια & ανάγλυφο 3D',
+ 'admin.defaultSettings.mapboxQuality': 'Λειτουργία υψηλής ποιότητας',
+};
+export default admin;
diff --git a/shared/src/i18n/gr/airport.ts b/shared/src/i18n/gr/airport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..482d431d714e7db62d552e870a66315049b41cb8
--- /dev/null
+++ b/shared/src/i18n/gr/airport.ts
@@ -0,0 +1,6 @@
+import type { TranslationStrings } from '../types';
+
+const airport: TranslationStrings = {
+ 'airport.searchPlaceholder': 'Κωδικός αεροδρομίου ή πόλη (π.χ. FRA)',
+};
+export default airport;
diff --git a/shared/src/i18n/gr/atlas.ts b/shared/src/i18n/gr/atlas.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bffd66fca1777e7fa3449ff61cfff3f9d7110d1e
--- /dev/null
+++ b/shared/src/i18n/gr/atlas.ts
@@ -0,0 +1,61 @@
+import type { TranslationStrings } from '../types';
+
+const atlas: TranslationStrings = {
+ 'atlas.subtitle': 'Το ταξιδιωτικό σας αποτύπωμα σε όλο τον κόσμο',
+ 'atlas.countries': 'Χώρες',
+ 'atlas.trips': 'Ταξίδια',
+ 'atlas.places': 'Μέρη',
+ 'atlas.unmark': 'Αφαίρεση',
+ 'atlas.confirmMark': 'Σήμανση αυτής της χώρας ως επισκεφθείσας;',
+ 'atlas.confirmUnmark': 'Αφαίρεση αυτής της χώρας από τη λίστα επισκεφθέντων;',
+ 'atlas.confirmUnmarkRegion':
+ 'Αφαίρεση αυτής της περιοχής από τη λίστα επισκεφθέντων;',
+ 'atlas.markVisited': 'Σήμανση ως επισκεφθείσα',
+ 'atlas.markVisitedHint': 'Προσθέστε αυτή τη χώρα στη λίστα επισκεφθέντων',
+ 'atlas.markRegionVisitedHint':
+ 'Προσθέστε αυτή την περιοχή στη λίστα επισκεφθέντων',
+ 'atlas.addToBucket': 'Προσθήκη στη λίστα επιθυμιών',
+ 'atlas.addPoi': 'Προσθήκη μέρους',
+ 'atlas.searchCountry': 'Αναζήτηση χώρας...',
+ 'atlas.bucketNamePlaceholder': 'Όνομα (χώρα, πόλη, μέρος...)',
+ 'atlas.month': 'Μήνας',
+ 'atlas.year': 'Έτος',
+ 'atlas.addToBucketHint': 'Αποθηκεύστε ως μέρος που θέλετε να επισκεφθείτε',
+ 'atlas.bucketWhen': 'Πότε σκοπεύετε να το επισκεφθείτε;',
+ 'atlas.statsTab': 'Στατιστικά',
+ 'atlas.bucketTab': 'Λίστα Επιθυμιών',
+ 'atlas.addBucket': 'Προσθήκη στη λίστα επιθυμιών',
+ 'atlas.bucketNotesPlaceholder': 'Σημειώσεις (προαιρετικό)',
+ 'atlas.bucketEmpty': 'Η λίστα επιθυμιών σας είναι κενή',
+ 'atlas.bucketEmptyHint': 'Προσθέστε μέρη που ονειρεύεστε να επισκεφθείτε',
+ 'atlas.days': 'Ημέρες',
+ 'atlas.visitedCountries': 'Επισκεφθείσες Χώρες',
+ 'atlas.cities': 'Πόλεις',
+ 'atlas.noData': 'Δεν υπάρχουν ταξιδιωτικά δεδομένα ακόμη',
+ 'atlas.noDataHint':
+ 'Δημιουργήστε ένα ταξίδι και προσθέστε μέρη για να δείτε τον παγκόσμιο χάρτη σας',
+ 'atlas.lastTrip': 'Τελευταίο ταξίδι',
+ 'atlas.nextTrip': 'Επόμενο ταξίδι',
+ 'atlas.daysLeft': 'ημέρες απομένουν',
+ 'atlas.streak': 'Σερί',
+ 'atlas.years': 'έτη',
+ 'atlas.yearInRow': 'έτος στη σειρά',
+ 'atlas.yearsInRow': 'έτη στη σειρά',
+ 'atlas.tripIn': 'ταξίδι σε',
+ 'atlas.tripsIn': 'ταξίδια σε',
+ 'atlas.since': 'από',
+ 'atlas.europe': 'Ευρώπη',
+ 'atlas.asia': 'Ασία',
+ 'atlas.northAmerica': 'Β. Αμερική',
+ 'atlas.southAmerica': 'Ν. Αμερική',
+ 'atlas.africa': 'Αφρική',
+ 'atlas.oceania': 'Ωκεανία',
+ 'atlas.other': 'Άλλο',
+ 'atlas.firstVisit': 'Πρώτο ταξίδι',
+ 'atlas.lastVisitLabel': 'Τελευταίο ταξίδι',
+ 'atlas.tripSingular': 'Ταξίδι',
+ 'atlas.tripPlural': 'Ταξίδια',
+ 'atlas.placeVisited': 'Μέρος που επισκεφθήκατε',
+ 'atlas.placesVisited': 'Μέρη που επισκεφθήκατε',
+};
+export default atlas;
diff --git a/shared/src/i18n/gr/backup.ts b/shared/src/i18n/gr/backup.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ba4f12045678f6765a4066d8f096755571489f48
--- /dev/null
+++ b/shared/src/i18n/gr/backup.ts
@@ -0,0 +1,79 @@
+import type { TranslationStrings } from '../types';
+
+const backup: TranslationStrings = {
+ 'backup.title': 'Αντίγραφο Ασφαλείας Δεδομένων',
+ 'backup.subtitle': 'Βάση δεδομένων και όλα τα μεταφορτωμένα αρχεία',
+ 'backup.refresh': 'Ανανέωση',
+ 'backup.upload': 'Μεταφόρτωση Αντιγράφου',
+ 'backup.uploading': 'Μεταφόρτωση…',
+ 'backup.create': 'Δημιουργία Αντιγράφου',
+ 'backup.creating': 'Δημιουργία…',
+ 'backup.empty': 'Δεν υπάρχουν αντίγραφα ασφαλείας ακόμη',
+ 'backup.createFirst': 'Δημιουργία πρώτου αντιγράφου',
+ 'backup.download': 'Λήψη',
+ 'backup.restore': 'Επαναφορά',
+ 'backup.confirm.restore':
+ 'Επαναφορά αντιγράφου "{name}";\n\nΌλα τα τρέχοντα δεδομένα θα αντικατασταθούν με το αντίγραφο.',
+ 'backup.confirm.uploadRestore':
+ 'Μεταφόρτωση και επαναφορά αρχείου αντιγράφου "{name}";\n\nΌλα τα τρέχοντα δεδομένα θα αντικατασταθούν.',
+ 'backup.confirm.delete': 'Διαγραφή αντιγράφου "{name}";',
+ 'backup.toast.loadError': 'Αποτυχία φόρτωσης αντιγράφων',
+ 'backup.toast.created': 'Το αντίγραφο δημιουργήθηκε επιτυχώς',
+ 'backup.toast.createError': 'Αποτυχία δημιουργίας αντιγράφου',
+ 'backup.toast.restored':
+ 'Το αντίγραφο επαναφέρθηκε. Η σελίδα θα επαναφορτωθεί…',
+ 'backup.toast.restoreError': 'Αποτυχία επαναφοράς',
+ 'backup.toast.uploadError': 'Αποτυχία μεταφόρτωσης',
+ 'backup.toast.deleted': 'Το αντίγραφο διαγράφηκε',
+ 'backup.toast.deleteError': 'Αποτυχία διαγραφής',
+ 'backup.toast.downloadError': 'Η λήψη απέτυχε',
+ 'backup.toast.settingsSaved':
+ 'Οι ρυθμίσεις αυτόματου αντιγράφου αποθηκεύτηκαν',
+ 'backup.toast.settingsError': 'Αποτυχία αποθήκευσης ρυθμίσεων',
+ 'backup.auto.title': 'Αυτόματο Αντίγραφο',
+ 'backup.auto.subtitle': 'Αυτόματο αντίγραφο ασφαλείας με χρονοπρόγραμμα',
+ 'backup.auto.enable': 'Ενεργοποίηση αυτόματου αντιγράφου',
+ 'backup.auto.enableHint':
+ 'Τα αντίγραφα θα δημιουργούνται αυτόματα στο επιλεγμένο χρονοπρόγραμμα',
+ 'backup.auto.interval': 'Διάστημα',
+ 'backup.auto.hour': 'Εκτέλεση την ώρα',
+ 'backup.auto.hourHint': 'Τοπική ώρα διακομιστή (μορφή {format})',
+ 'backup.auto.dayOfWeek': 'Ημέρα εβδομάδας',
+ 'backup.auto.dayOfMonth': 'Ημέρα μήνα',
+ 'backup.auto.dayOfMonthHint':
+ 'Περιορίζεται σε 1–28 για συμβατότητα με όλους τους μήνες',
+ 'backup.auto.scheduleSummary': 'Χρονοπρόγραμμα',
+ 'backup.auto.summaryDaily': 'Κάθε ημέρα στις {hour}:00',
+ 'backup.auto.summaryWeekly': 'Κάθε {day} στις {hour}:00',
+ 'backup.auto.summaryMonthly': 'Ημέρα {day} κάθε μήνα στις {hour}:00',
+ 'backup.auto.envLocked': 'Docker',
+ 'backup.auto.envLockedHint':
+ 'Το αυτόματο αντίγραφο είναι ρυθμισμένο μέσω μεταβλητών περιβάλλοντος Docker. Για να αλλάξετε αυτές τις ρυθμίσεις, ενημερώστε το docker-compose.yml και επανεκκινήστε το container.',
+ 'backup.auto.copyEnv': 'Αντιγραφή μεταβλητών Docker',
+ 'backup.auto.envCopied': 'Οι μεταβλητές Docker αντιγράφηκαν στο πρόχειρο',
+ 'backup.auto.keepLabel': 'Διαγραφή παλιών αντιγράφων μετά από',
+ 'backup.dow.sunday': 'Κυρ',
+ 'backup.dow.monday': 'Δευ',
+ 'backup.dow.tuesday': 'Τρι',
+ 'backup.dow.wednesday': 'Τετ',
+ 'backup.dow.thursday': 'Πεμ',
+ 'backup.dow.friday': 'Παρ',
+ 'backup.dow.saturday': 'Σαβ',
+ 'backup.interval.hourly': 'Ωριαία',
+ 'backup.interval.daily': 'Ημερήσια',
+ 'backup.interval.weekly': 'Εβδομαδιαία',
+ 'backup.interval.monthly': 'Μηνιαία',
+ 'backup.keep.1day': '1 ημέρα',
+ 'backup.keep.3days': '3 ημέρες',
+ 'backup.keep.7days': '7 ημέρες',
+ 'backup.keep.14days': '14 ημέρες',
+ 'backup.keep.30days': '30 ημέρες',
+ 'backup.keep.forever': 'Διατήρηση για πάντα',
+ 'backup.restoreConfirmTitle': 'Επαναφορά Αντιγράφου;',
+ 'backup.restoreWarning':
+ 'Όλα τα τρέχοντα δεδομένα (ταξίδια, τοποθεσίες, χρήστες, μεταφορτώσεις) θα αντικατασταθούν μόνιμα με το αντίγραφο. Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.',
+ 'backup.restoreTip':
+ 'Συμβουλή: Δημιουργήστε ένα αντίγραφο της τρέχουσας κατάστασης πριν την επαναφορά.',
+ 'backup.restoreConfirm': 'Ναι, επαναφορά',
+};
+export default backup;
diff --git a/shared/src/i18n/gr/budget.ts b/shared/src/i18n/gr/budget.ts
new file mode 100644
index 0000000000000000000000000000000000000000..501313c3996233d366e524f9d3ddbb6e86749c97
--- /dev/null
+++ b/shared/src/i18n/gr/budget.ts
@@ -0,0 +1,117 @@
+import type { TranslationStrings } from '../types';
+
+const budget: TranslationStrings = {
+ 'budget.title': 'Προϋπολογισμός',
+ 'budget.exportCsv': 'Εξαγωγή CSV',
+ 'budget.emptyTitle': 'Δεν έχει δημιουργηθεί προϋπολογισμός ακόμη',
+ 'budget.emptyText':
+ 'Δημιουργήστε κατηγορίες και εγγραφές για να σχεδιάσετε τον ταξιδιωτικό σας προϋπολογισμό',
+ 'budget.emptyPlaceholder': 'Εισαγάγετε όνομα κατηγορίας...',
+ 'budget.createCategory': 'Δημιουργία Κατηγορίας',
+ 'budget.category': 'Κατηγορία',
+ 'budget.categoryName': 'Όνομα Κατηγορίας',
+ 'budget.table.name': 'Όνομα',
+ 'budget.table.total': 'Σύνολο',
+ 'budget.table.persons': 'Άτομα',
+ 'budget.table.days': 'Ημέρες',
+ 'budget.table.perPerson': 'Ανά Άτομο',
+ 'budget.table.perDay': 'Ανά Ημέρα',
+ 'budget.table.perPersonDay': 'Α.Α. / Ημέρα',
+ 'budget.table.note': 'Σημείωση',
+ 'budget.table.date': 'Ημερομηνία',
+ 'budget.newEntry': 'Νέα Εγγραφή',
+ 'budget.defaultEntry': 'Νέα Εγγραφή',
+ 'budget.defaultCategory': 'Νέα Κατηγορία',
+ 'budget.total': 'Σύνολο',
+ 'budget.totalBudget': 'Συνολικός Προϋπολογισμός',
+ 'budget.byCategory': 'Ανά Κατηγορία',
+ 'budget.editTooltip': 'Κάντε κλικ για επεξεργασία',
+ 'budget.linkedToReservation':
+ 'Συνδεδεμένο με κράτηση — επεξεργαστείτε το όνομα εκεί',
+ 'budget.confirm.deleteCategory':
+ 'Είστε σίγουροι ότι θέλετε να διαγράψετε την κατηγορία "{name}" με {count} εγγραφές;',
+ 'budget.deleteCategory': 'Διαγραφή Κατηγορίας',
+ 'budget.perPerson': 'Ανά Άτομο',
+ 'budget.paid': 'Πληρωμένο',
+ 'budget.open': 'Ανοιχτό',
+ 'budget.noMembers': 'Δεν έχουν ανατεθεί μέλη',
+ 'budget.settlement': 'Εκκαθάριση',
+ 'budget.settlementInfo':
+ 'Κάντε κλικ στο avatar ενός μέλους σε μια εγγραφή προϋπολογισμού για να το επισημάνετε πράσινο — αυτό σημαίνει ότι πλήρωσε. Η εκκαθάριση δείχνει στη συνέχεια ποιος χρωστάει σε ποιον και πόσα.',
+ 'budget.netBalances': 'Καθαρά Υπόλοιπα',
+ 'budget.categoriesLabel': 'κατηγορίες',
+ "costs.you": "Εσείς",
+ "costs.youShort": "Ε",
+ "costs.youLower": "εσείς",
+ "costs.youOwe": "Χρωστάτε",
+ "costs.youOweSub": "Πρέπει να πληρώσετε άλλους",
+ "costs.youreOwed": "Σας χρωστούν",
+ "costs.youreOwedSub": "Άλλοι πρέπει να σας πληρώσουν",
+ "costs.totalSpend": "Συνολικές δαπάνες ταξιδιού",
+ "costs.totalSpendSub": "Όλων των ταξιδιωτών",
+ "costs.to": "Προς",
+ "costs.from": "Από",
+ "costs.allSettled": "Έχετε εξοφλήσει τα πάντα",
+ "costs.nothingOwed": "Δεν σας χρωστάει κανείς",
+ "costs.yourShare": "Το μερίδιό σας",
+ "costs.youPaid": "Πληρώσατε",
+ "costs.expenses": "Έξοδα",
+ "costs.entries": "{count} εγγραφές",
+ "costs.searchPlaceholder": "Αναζήτηση εξόδων…",
+ "costs.filter.all": "Όλα",
+ "costs.filter.mine": "Πληρωμένα από εμένα",
+ "costs.filter.owed": "Μου χρωστούν",
+ "costs.addExpense": "Προσθήκη εξόδου",
+ "costs.editExpense": "Επεξεργασία εξόδου",
+ "costs.noMatch": "Κανένα έξοδο δεν ταιριάζει με την αναζήτησή σας.",
+ "costs.emptyText": "Δεν υπάρχουν έξοδα ακόμη. Προσθέστε το πρώτο σας.",
+ "costs.spent": "{amount} δαπάνη",
+ "costs.noDate": "Χωρίς ημερομηνία",
+ "costs.noOnePaid": "Δεν πλήρωσε κανείς ακόμη",
+ "costs.youLent": "δανείσατε {amount}",
+ "costs.youBorrowed": "δανειστήκατε {amount}",
+ "costs.settleUp": "Εξόφληση",
+ "costs.history": "Ιστορικό",
+ "costs.everyoneSquare": "Όλοι είναι ξεκάθαροι",
+ "costs.nothingOutstanding": "Δεν υπάρχουν εκκρεμείς πληρωμές αυτή τη στιγμή.",
+ "costs.pay": "πληρώνει",
+ "costs.pays": "πληρώνει",
+ "costs.settle": "Εξόφληση",
+ "costs.balances": "Υπόλοιπα",
+ "costs.byCategory": "Ανά κατηγορία",
+ "costs.noCategories": "Δεν υπάρχουν έξοδα ακόμη.",
+ "costs.settleHistory": "Ιστορικό εξοφλήσεων",
+ "costs.noSettlements": "Δεν υπάρχουν εξοφλημένες πληρωμές ακόμη.",
+ "costs.paymentsSettled": "{count} πληρωμές εξοφλήθηκαν",
+ "costs.paid": "πλήρωσε",
+ "costs.undo": "Αναίρεση",
+ "costs.whatFor": "Για τι ήταν;",
+ "costs.namePlaceholder": "π.χ. Δείπνο, σουβενίρ, βενζίνη…",
+ "costs.totalAmount": "Συνολικό ποσό",
+ "costs.currency": "Νόμισμα",
+ "costs.day": "Ημέρα",
+ "costs.rateLabel": "1 {from} σε {to}",
+ "costs.category": "Κατηγορία",
+ "costs.whoPaid": "Ποιος πλήρωσε;",
+ "costs.splitBetween": "Ισόποση κατανομή μεταξύ",
+ "costs.pickSomeone": "Επιλέξτε τουλάχιστον ένα άτομο για τον διαμοιρασμό.",
+ "costs.splitSummary": "Κατανομή σε {count} μέρη · {amount} το καθένα",
+ "costs.cat.accommodation": "Διαμονή",
+ "costs.cat.food": "Φαγητό & ποτό",
+ "costs.cat.groceries": "Ψώνια σούπερ μάρκετ",
+ "costs.cat.transport": "Μεταφορά",
+ "costs.cat.flights": "Πτήσεις",
+ "costs.cat.activities": "Δραστηριότητες",
+ "costs.cat.sightseeing": "Αξιοθέατα",
+ "costs.cat.shopping": "Ψώνια",
+ "costs.cat.fees": "Τέλη & εισιτήρια",
+ "costs.cat.health": "Υγεία",
+ "costs.cat.tips": "Φιλοδωρήματα",
+ "costs.cat.other": "Άλλα",
+ "costs.daysCount": "{count} ημέρες",
+ "costs.travelers": "{count} ταξιδιώτες",
+ "costs.liveRate": "ζωντανή ισοτιμία",
+ "costs.settleAll": "Εξόφληση όλων",
+};
+
+export default budget;
diff --git a/shared/src/i18n/gr/categories.ts b/shared/src/i18n/gr/categories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3e4ea7868243b49aad0bc8016293b7271d5f1a89
--- /dev/null
+++ b/shared/src/i18n/gr/categories.ts
@@ -0,0 +1,26 @@
+import type { TranslationStrings } from '../types';
+
+const categories: TranslationStrings = {
+ 'categories.title': 'Κατηγορίες',
+ 'categories.subtitle': 'Διαχείριση κατηγοριών για τοποθεσίες',
+ 'categories.new': 'Νέα Κατηγορία',
+ 'categories.empty': 'Δεν υπάρχουν κατηγορίες ακόμη',
+ 'categories.namePlaceholder': 'Όνομα κατηγορίας',
+ 'categories.icon': 'Εικονίδιο',
+ 'categories.color': 'Χρώμα',
+ 'categories.customColor': 'Επιλογή προσαρμοσμένου χρώματος',
+ 'categories.preview': 'Προεπισκόπηση',
+ 'categories.defaultName': 'Κατηγορία',
+ 'categories.update': 'Ενημέρωση',
+ 'categories.create': 'Δημιουργία',
+ 'categories.confirm.delete':
+ 'Διαγραφή κατηγορίας; Οι τοποθεσίες σε αυτή την κατηγορία δεν θα διαγραφούν.',
+ 'categories.toast.loadError': 'Αποτυχία φόρτωσης κατηγοριών',
+ 'categories.toast.nameRequired': 'Παρακαλώ εισάγετε όνομα',
+ 'categories.toast.updated': 'Η κατηγορία ενημερώθηκε',
+ 'categories.toast.created': 'Η κατηγορία δημιουργήθηκε',
+ 'categories.toast.saveError': 'Αποτυχία αποθήκευσης',
+ 'categories.toast.deleted': 'Η κατηγορία διαγράφηκε',
+ 'categories.toast.deleteError': 'Αποτυχία διαγραφής',
+};
+export default categories;
diff --git a/shared/src/i18n/gr/collab.ts b/shared/src/i18n/gr/collab.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5f9b762ac74a853f6318c03bc82b51d9fb35d4a0
--- /dev/null
+++ b/shared/src/i18n/gr/collab.ts
@@ -0,0 +1,77 @@
+import type { TranslationStrings } from '../types';
+
+const collab: TranslationStrings = {
+ 'collab.tabs.chat': 'Συνομιλία',
+ 'collab.tabs.notes': 'Σημειώσεις',
+ 'collab.tabs.polls': 'Δημοσκοπήσεις',
+ 'collab.whatsNext.title': 'Τι Επόμενο',
+ 'collab.whatsNext.today': 'Σήμερα',
+ 'collab.whatsNext.tomorrow': 'Αύριο',
+ 'collab.whatsNext.empty': 'Καμία επερχόμενη δραστηριότητα',
+ 'collab.whatsNext.until': 'έως',
+ 'collab.whatsNext.emptyHint': 'Οι δραστηριότητες με ώρες θα εμφανίζονται εδώ',
+ 'collab.chat.send': 'Αποστολή',
+ 'collab.chat.placeholder': 'Πληκτρολογήστε ένα μήνυμα...',
+ 'collab.chat.empty': 'Ξεκινήστε τη συζήτηση',
+ 'collab.chat.emptyHint':
+ 'Τα μηνύματα κοινοποιούνται σε όλα τα μέλη του ταξιδιού',
+ 'collab.chat.emptyDesc':
+ 'Μοιραστείτε ιδέες, σχέδια και ενημερώσεις με την ταξιδιωτική σας ομάδα',
+ 'collab.chat.today': 'Σήμερα',
+ 'collab.chat.yesterday': 'Χθες',
+ 'collab.chat.deletedMessage': 'διέγραψε ένα μήνυμα',
+ 'collab.chat.reply': 'Απάντηση',
+ 'collab.chat.loadMore': 'Φόρτωση παλαιότερων μηνυμάτων',
+ 'collab.chat.justNow': 'μόλις τώρα',
+ 'collab.chat.minutesAgo': '{n}λ πριν',
+ 'collab.chat.hoursAgo': '{n}ω πριν',
+ 'collab.notes.title': 'Σημειώσεις',
+ 'collab.notes.new': 'Νέα Σημείωση',
+ 'collab.notes.empty': 'Δεν υπάρχουν σημειώσεις ακόμη',
+ 'collab.notes.emptyHint': 'Ξεκινήστε να καταγράφετε ιδέες και σχέδια',
+ 'collab.notes.all': 'Όλες',
+ 'collab.notes.titlePlaceholder': 'Τίτλος σημείωσης',
+ 'collab.notes.contentPlaceholder': 'Γράψτε κάτι...',
+ 'collab.notes.categoryPlaceholder': 'Κατηγορία',
+ 'collab.notes.newCategory': 'Νέα κατηγορία...',
+ 'collab.notes.category': 'Κατηγορία',
+ 'collab.notes.noCategory': 'Χωρίς κατηγορία',
+ 'collab.notes.color': 'Χρώμα',
+ 'collab.notes.save': 'Αποθήκευση',
+ 'collab.notes.cancel': 'Ακύρωση',
+ 'collab.notes.edit': 'Επεξεργασία',
+ 'collab.notes.delete': 'Διαγραφή',
+ 'collab.notes.confirmDeleteTitle': 'Διαγραφή σημείωσης;',
+ 'collab.notes.confirmDeleteBody': 'Αυτή η σημείωση θα διαγραφεί οριστικά.',
+ 'collab.notes.pin': 'Καρφίτσωμα',
+ 'collab.notes.unpin': 'Ξεκαρφίτσωμα',
+ 'collab.notes.daysAgo': '{n}η πριν',
+ 'collab.notes.categorySettings': 'Διαχείριση Κατηγοριών',
+ 'collab.notes.create': 'Δημιουργία',
+ 'collab.notes.website': 'Ιστότοπος',
+ 'collab.notes.websitePlaceholder': 'https://...',
+ 'collab.notes.attachFiles': 'Επισύναψη αρχείων',
+ 'collab.notes.noCategoriesYet': 'Δεν υπάρχουν κατηγορίες ακόμη',
+ 'collab.notes.emptyDesc': 'Δημιουργήστε μια σημείωση για να ξεκινήσετε',
+ 'collab.polls.title': 'Δημοσκοπήσεις',
+ 'collab.polls.new': 'Νέα Δημοσκόπηση',
+ 'collab.polls.empty': 'Δεν υπάρχουν δημοσκοπήσεις ακόμη',
+ 'collab.polls.emptyHint': 'Ρωτήστε την ομάδα και ψηφίστε μαζί',
+ 'collab.polls.question': 'Ερώτηση',
+ 'collab.polls.questionPlaceholder': 'Τι πρέπει να κάνουμε;',
+ 'collab.polls.addOption': '+ Προσθήκη επιλογής',
+ 'collab.polls.optionPlaceholder': 'Επιλογή {n}',
+ 'collab.polls.create': 'Δημιουργία Δημοσκόπησης',
+ 'collab.polls.close': 'Κλείσιμο',
+ 'collab.polls.closed': 'Κλειστή',
+ 'collab.polls.votes': '{n} ψήφοι',
+ 'collab.polls.vote': '{n} ψήφος',
+ 'collab.polls.multipleChoice': 'Πολλαπλή επιλογή',
+ 'collab.polls.multiChoice': 'Πολλαπλή επιλογή',
+ 'collab.polls.deadline': 'Προθεσμία',
+ 'collab.polls.option': 'Επιλογή',
+ 'collab.polls.options': 'Επιλογές',
+ 'collab.polls.delete': 'Διαγραφή',
+ 'collab.polls.closedSection': 'Κλειστές',
+};
+export default collab;
diff --git a/shared/src/i18n/gr/common.ts b/shared/src/i18n/gr/common.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0e874f82a066758cede9f232734fdba5f0cbbef4
--- /dev/null
+++ b/shared/src/i18n/gr/common.ts
@@ -0,0 +1,55 @@
+import type { TranslationStrings } from '../types';
+
+const common: TranslationStrings = {
+ 'common.save': 'Αποθήκευση',
+ 'common.showMore': 'Εμφάνιση περισσότερων',
+ 'common.showLess': 'Εμφάνιση λιγότερων',
+ 'common.cancel': 'Ακύρωση',
+ 'common.clear': 'Εκκαθάριση',
+ 'common.delete': 'Διαγραφή',
+ 'common.edit': 'Επεξεργασία',
+ 'common.add': 'Προσθήκη',
+ 'common.loading': 'Φόρτωση...',
+ 'common.import': 'Εισαγωγή',
+ 'common.select': 'Επιλογή',
+ 'common.selectAll': 'Επιλογή όλων',
+ 'common.deselectAll': 'Απο-επιλογή όλων',
+ 'common.error': 'Σφάλμα',
+ 'common.unknownError': 'Άγνωστο σφάλμα',
+ 'common.tooManyAttempts':
+ 'Πολλαπλές προσπάθειες. Παρακαλώ δοκιμάστε αργότερα.',
+ 'common.back': 'Πίσω',
+ 'common.all': 'Όλα',
+ 'common.close': 'Κλείσιμο',
+ 'common.open': 'Άνοιγμα',
+ 'common.upload': 'Μεταφόρτωση',
+ 'common.search': 'Αναζήτηση',
+ 'common.confirm': 'Επιβεβαίωση',
+ 'common.ok': 'OK',
+ 'common.yes': 'Ναι',
+ 'common.no': 'Όχι',
+ 'common.or': 'ή',
+ 'common.none': 'Κανένα',
+ 'common.date': 'Ημερομηνία',
+ 'common.rename': 'Μετονομασία',
+ 'common.discardChanges': 'Απόρριψη αλλαγών',
+ 'common.discard': 'Απόρριψη',
+ 'common.name': 'Όνομα',
+ 'common.email': 'Email',
+ 'common.password': 'Κωδικός',
+ 'common.saving': 'Αποθήκευση...',
+ 'common.justNow': 'Μόλις τώρα',
+ 'common.hoursAgo': '{count} ώρες πρίν',
+ 'common.daysAgo': '{count} μέρες πρίν',
+ 'common.saved': 'Αποθηκεύτηκε',
+ 'common.update': 'Ενημέρωση',
+ 'common.change': 'Αλλαγή',
+ 'common.uploading': 'Μεταφόρτωση...',
+ 'common.backToPlanning': 'Πίσω στον Σχεδιασμό',
+ 'common.reset': 'Επαναφορά',
+ 'common.expand': 'Επέκταση',
+ 'common.collapse': 'Σύμπτυξη',
+ 'common.copy': 'Αντιγραφή',
+ 'common.copied': 'Αντιγράφηκε',
+};
+export default common;
diff --git a/shared/src/i18n/gr/dashboard.ts b/shared/src/i18n/gr/dashboard.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ee81cbc98a65ae076329862ed399237ca6bb3fa7
--- /dev/null
+++ b/shared/src/i18n/gr/dashboard.ts
@@ -0,0 +1,171 @@
+import type { TranslationStrings } from '../types';
+
+const dashboard: TranslationStrings = {
+ 'dashboard.title': 'Τα Ταξίδια μου',
+ 'dashboard.subtitle.loading': 'Φόρτωση των ταξιδιών...',
+ 'dashboard.subtitle.trips': '{count} ταξίδια ({archived} αρχειοθετημένα)',
+ 'dashboard.subtitle.empty': 'Ξεκινήστε το πρώτο σας ταξίδι',
+ 'dashboard.subtitle.activeOne': '{count} ενεργό ταξίδι',
+ 'dashboard.subtitle.activeMany': '{count} ενεργά ταξίδια',
+ 'dashboard.subtitle.archivedSuffix': ' · {count} αρχειοθετημένα',
+ 'dashboard.newTrip': 'Νέο Ταξίδι',
+ 'dashboard.gridView': 'Προβολή στοιχείων',
+ 'dashboard.listView': 'Προβολή λίστας',
+ 'dashboard.currency': 'Συνάλλαγμα',
+ 'dashboard.timezone': 'Ζώνες ώρας',
+ 'dashboard.localTime': 'Τοπική',
+ 'dashboard.timezoneCustomTitle': 'Προσαρμοσμένη Ζώνη ώρας',
+ 'dashboard.timezoneCustomLabelPlaceholder': 'Επιθετικό (προαιρετικό)',
+ 'dashboard.timezoneCustomTzPlaceholder': 'π.χ. Αμερική/Νέα Υόρκη',
+ 'dashboard.timezoneCustomAdd': 'Προσθήκη',
+ 'dashboard.timezoneCustomErrorEmpty': 'Εισάγετε μια ζώνη ώρας',
+ 'dashboard.timezoneCustomErrorInvalid':
+ 'Μη έγκυρη ζώνη ώρας. Χρησιμοποιήστε την μορφή π.χ. Europe/Berlin',
+ 'dashboard.timezoneCustomErrorDuplicate': 'Έχει προστεθεί ήδη',
+ 'dashboard.emptyTitle': 'Δεν υπάρχουν ταξίδια ακόμη',
+ 'dashboard.emptyText':
+ 'Δημιουργήστε το πρώτο σας ταξίδι και ξεκινήστε τα σχέδια!',
+ 'dashboard.emptyButton': 'Δημιουργία Πρώτου Ταξίδιου',
+ 'dashboard.nextTrip': 'Επόμενο Ταξίδι',
+ 'dashboard.shared': 'Κοινοποιημένο',
+ 'dashboard.sharedBy': 'Κοινοποιήθηκε από {name}',
+ 'dashboard.days': 'Ημέρες',
+ 'dashboard.places': 'Τόποι',
+ 'dashboard.members': 'Μέλη',
+ 'dashboard.archive': 'Αρχειοθήκευση',
+ 'dashboard.copyTrip': 'Αντιγραφή',
+ 'dashboard.copySuffix': 'αντιγραφή',
+ 'dashboard.restore': 'Επαναφορά',
+ 'dashboard.archived': 'Αρχειοθετημένο',
+ 'dashboard.status.ongoing': 'Τρέχων',
+ 'dashboard.status.today': 'Σήμερα',
+ 'dashboard.status.tomorrow': 'Αύριο',
+ 'dashboard.status.past': 'Παρελθόν',
+ 'dashboard.status.daysLeft': '{count} μέρες έμειναν',
+ 'dashboard.toast.loadError': 'Αποτυχία φόρτωσης ταξιδιών',
+ 'dashboard.toast.created': 'Ταξίδι δημιουργήθηκε επιτυχώς!',
+ 'dashboard.toast.createError': 'Αποτυχία δημιουργίας ταξιδιού',
+ 'dashboard.toast.updated': 'Ταξίδι ενημερώθηκε!',
+ 'dashboard.toast.updateError': 'Αποτυχία ενημέρωσης ταξιδιού',
+ 'dashboard.toast.deleted': 'Ταξίδι διαγράφηκε',
+ 'dashboard.toast.deleteError': 'Αποτυχία διαγραφής ταξιδιού',
+ 'dashboard.toast.archived': 'Ταξίδι αρχειοθετήθηκε',
+ 'dashboard.toast.archiveError': 'Αποτυχία αρχειοθήκευσης ταξιδιού',
+ 'dashboard.toast.restored': 'Ταξίδι επαναφέρθηκε',
+ 'dashboard.toast.restoreError': 'Αποτυχία επαναφοράς ταξιδιού',
+ 'dashboard.toast.copied': 'Ταξίδι αντιγράφηκε!',
+ 'dashboard.toast.copyError': 'Αποτυχία αντιγραφής ταξιδιού',
+ 'dashboard.confirm.delete':
+ 'Διαγραφή ταξιδιού "{title}"; Όλα τα τόποι και τα σχέδια θα διαγραφούν επίσης.',
+ 'dashboard.confirm.copy.title': 'Αντιγραφή αυτού του ταξιδιού;',
+ 'dashboard.confirm.copy.willCopy': 'Θα αντιγραφεί',
+ 'dashboard.confirm.copy.will1': 'Μέρες, μέρη και σχέδια μέρας',
+ 'dashboard.confirm.copy.will2': 'Καταλύματα και κρατήσεις',
+ 'dashboard.confirm.copy.will3': 'Αντικείμενα budget και σειρά κατηγορίας',
+ 'dashboard.confirm.copy.will4': 'Λίστα πακεταρίσματος (Μη ελεγμένα)',
+ 'dashboard.confirm.copy.will5': 'TODOs (Μη ορισμένα & Μη ελεγμένα)',
+ 'dashboard.confirm.copy.will6': 'Σημειώσεις μέρας',
+ 'dashboard.confirm.copy.wontCopy': 'Δεν θα αντιγραφούν',
+ 'dashboard.confirm.copy.wont1': 'Συνεργάτες & αναθέσεις μελών',
+ 'dashboard.confirm.copy.wont2':
+ 'Σημειώσεις συνεργασίας, ψηφοφορίες & μηνύματα',
+ 'dashboard.confirm.copy.wont3': 'Αρχεία & φωτογραφίες',
+ 'dashboard.confirm.copy.wont4': 'Σύνδεσμοι διαμοιρασμού',
+ 'dashboard.confirm.copy.confirm': 'Αντιγραφή ταξιδιού',
+ 'dashboard.editTrip': 'Επεξεργασία Ταξιδιού',
+ 'dashboard.createTrip': 'Δημιουργία Νέου Ταξιδιού',
+ 'dashboard.tripTitle': 'Τίτλος',
+ 'dashboard.tripTitlePlaceholder': 'π.χ. Καλοκαίρι στην Ιαπωνία',
+ 'dashboard.tripDescription': 'Περιγραφή',
+ 'dashboard.tripDescriptionPlaceholder': 'Σχετικά με τι είναι αυτό το ταξίδι;',
+ 'dashboard.startDate': 'Ημερομηνία Έναρξης',
+ 'dashboard.endDate': 'Ημερομηνία Λήξης',
+ 'dashboard.dayCount': 'Αριθμός Ημερών',
+ 'dashboard.dayCountHint':
+ 'Πόσες ημέρες να σχεδιαστούν όταν δεν έχουν οριστεί ημερομηνίες ταξιδιού.',
+ 'dashboard.noDateHint':
+ 'Δεν έχει οριστεί ημερομηνία — θα δημιουργηθούν 7 προεπιλεγμένες ημέρες. Μπορείτε να το αλλάξετε οποτεδήποτε.',
+ 'dashboard.coverImage': 'Εικόνα Εξωφύλλου',
+ 'dashboard.addCoverImage': 'Προσθήκη εικόνας εξωφύλλου (ή σύρετε & αποθέστε)',
+ 'dashboard.addMembers': 'Συνταξιδιώτες',
+ 'dashboard.addMember': 'Προσθήκη μέλους',
+ 'dashboard.coverSaved': 'Η εικόνα εξωφύλλου αποθηκεύτηκε',
+ 'dashboard.coverUploadError': 'Αποτυχία μεταφόρτωσης',
+ 'dashboard.coverRemoveError': 'Αποτυχία αφαίρεσης',
+ 'dashboard.titleRequired': 'Ο τίτλος είναι υποχρεωτικός',
+ 'dashboard.endDateError':
+ 'Η ημερομηνία λήξης πρέπει να είναι μετά την ημερομηνία έναρξης',
+ 'dashboard.greeting.morning': 'Καλημέρα,',
+ 'dashboard.greeting.afternoon': 'Καλό απόγευμα,',
+ 'dashboard.greeting.evening': 'Καλησπέρα,',
+ 'dashboard.mobile.liveNow': 'Ζωντανά Τώρα',
+ 'dashboard.mobile.tripProgress': 'Πρόοδος ταξιδιού',
+ 'dashboard.mobile.daysLeft': '{count} ημέρες ακόμα',
+ 'dashboard.mobile.places': 'Τοποθεσίες',
+ 'dashboard.mobile.buddies': 'Συνταξιδιώτες',
+ 'dashboard.mobile.newTrip': 'Νέο Ταξίδι',
+ 'dashboard.mobile.currency': 'Νόμισμα',
+ 'dashboard.mobile.timezone': 'Ζώνη ώρας',
+ 'dashboard.mobile.upcomingTrips': 'Επερχόμενα Ταξίδια',
+ 'dashboard.mobile.yourTrips': 'Τα Ταξίδια σας',
+ 'dashboard.mobile.trips': 'ταξίδια',
+ 'dashboard.mobile.starts': 'Ξεκινά',
+ 'dashboard.mobile.duration': 'Διάρκεια',
+ 'dashboard.mobile.day': 'ημέρα',
+ 'dashboard.mobile.days': 'ημέρες',
+ 'dashboard.mobile.ongoing': 'Σε εξέλιξη',
+ 'dashboard.mobile.startsToday': 'Ξεκινά σήμερα',
+ 'dashboard.mobile.tomorrow': 'Αύριο',
+ 'dashboard.mobile.inDays': 'Σε {count} ημέρες',
+ 'dashboard.mobile.inMonths': 'Σε {count} μήνες',
+ 'dashboard.mobile.completed': 'Ολοκληρώθηκε',
+ 'dashboard.mobile.currencyConverter': 'Μετατροπέας Νομισμάτων',
+ 'dashboard.newTripSub': 'Plan a new trip from scratch', // en-fallback
+ 'dashboard.filter.planned': 'Planned', // en-fallback
+ 'dashboard.hero.badgeLive': 'LIVE NOW', // en-fallback
+ 'dashboard.hero.badgeToday': 'STARTS TODAY', // en-fallback
+ 'dashboard.hero.badgeTomorrow': 'TOMORROW', // en-fallback
+ 'dashboard.hero.badgeNext': 'UP NEXT', // en-fallback
+ 'dashboard.hero.badgeRecent': 'RECENT', // en-fallback
+ 'dashboard.hero.tripDates': 'Trip dates', // en-fallback
+ 'dashboard.hero.noDates': 'No dates set', // en-fallback
+ 'dashboard.hero.travelerOne': '{count} traveler', // en-fallback
+ 'dashboard.hero.travelerMany': '{count} travelers', // en-fallback
+ 'dashboard.hero.destinationOne': '{count} destination', // en-fallback
+ 'dashboard.hero.destinationMany': '{count} destinations', // en-fallback
+ 'dashboard.hero.dayUnitOne': 'day', // en-fallback
+ 'dashboard.hero.dayUnitMany': 'days', // en-fallback
+ 'dashboard.hero.dayLeft': 'Day left', // en-fallback
+ 'dashboard.hero.daysLeft': 'Days left', // en-fallback
+ 'dashboard.hero.lastDay': 'Last day', // en-fallback
+ 'dashboard.hero.untilStart': 'Until start', // en-fallback
+ 'dashboard.hero.startsIn': 'Trip starts in', // en-fallback
+ 'dashboard.atlas.countriesVisited': 'Atlas · Countries visited', // en-fallback
+ 'dashboard.atlas.ofTotal': 'of {total}', // en-fallback
+ 'dashboard.atlas.tripsTotal': 'Trips total', // en-fallback
+ 'dashboard.atlas.placesMapped': '{count} places mapped', // en-fallback
+ 'dashboard.atlas.daysTraveled': 'Days traveled', // en-fallback
+ 'dashboard.atlas.daysUnit': 'days', // en-fallback
+ 'dashboard.atlas.acrossAllTrips': 'across all trips', // en-fallback
+ 'dashboard.atlas.distanceFlown': 'Distance flown', // en-fallback
+ 'dashboard.atlas.kmUnit': 'km', // en-fallback
+ 'dashboard.atlas.aroundEquator': '≈ {count}× around the equator', // en-fallback
+ 'dashboard.card.idea': 'Idea', // en-fallback
+ 'dashboard.card.buddyOne': 'Buddy', // en-fallback
+ 'dashboard.fx.from': 'From', // en-fallback
+ 'dashboard.fx.to': 'To', // en-fallback
+ 'dashboard.fx.unavailable': 'Rate unavailable', // en-fallback
+ 'dashboard.tz.searchPlaceholder': 'Search timezone…', // en-fallback
+ 'dashboard.tz.empty': 'No other timezones yet — add one with +', // en-fallback
+ 'dashboard.upcoming.title': 'Upcoming reservations', // en-fallback
+ 'dashboard.upcoming.empty': 'Nothing booked yet.', // en-fallback
+ 'dashboard.aria.toggleView': 'Toggle view', // en-fallback
+ 'dashboard.aria.filter': 'Filter', // en-fallback
+ 'dashboard.aria.duplicate': 'Duplicate', // en-fallback
+ 'dashboard.aria.refreshRates': 'Refresh rates', // en-fallback
+ 'dashboard.aria.swapCurrencies': 'Swap currencies', // en-fallback
+ 'dashboard.aria.addTimezone': 'Add timezone', // en-fallback
+ 'dashboard.aria.removeTimezone': 'Remove {city}', // en-fallback
+ 'dashboard.dayCountRequired': 'Ο αριθμός ημερών είναι υποχρεωτικός',
+};
+export default dashboard;
diff --git a/shared/src/i18n/gr/day.ts b/shared/src/i18n/gr/day.ts
new file mode 100644
index 0000000000000000000000000000000000000000..44c02ba2ea1c454a790d75d79ef760612daaa8a2
--- /dev/null
+++ b/shared/src/i18n/gr/day.ts
@@ -0,0 +1,27 @@
+import type { TranslationStrings } from '../types';
+
+const day: TranslationStrings = {
+ 'day.precipProb': 'Πιθανότητα βροχής',
+ 'day.precipitation': 'Κατακρημνίσματα',
+ 'day.wind': 'Άνεμος',
+ 'day.sunrise': 'Ανατολή',
+ 'day.sunset': 'Δύση',
+ 'day.hourlyForecast': 'Ωριαία Πρόγνωση',
+ 'day.climateHint':
+ 'Ιστορικοί μέσοι όροι — η πραγματική πρόγνωση είναι διαθέσιμη εντός 16 ημερών από αυτή την ημερομηνία.',
+ 'day.noWeather':
+ 'Δεν υπάρχουν διαθέσιμα δεδομένα καιρού. Προσθέστε μια τοποθεσία με συντεταγμένες.',
+ 'day.overview': 'Ημερήσια Επισκόπηση',
+ 'day.accommodation': 'Διαμονή',
+ 'day.addAccommodation': 'Προσθήκη διαμονής',
+ 'day.hotelDayRange': 'Εφαρμογή σε ημέρες',
+ 'day.noPlacesForHotel': 'Προσθέστε πρώτα τοποθεσίες στο ταξίδι σας',
+ 'day.allDays': 'Όλες',
+ 'day.checkIn': 'Check-in',
+ 'day.checkInUntil': 'Έως',
+ 'day.checkOut': 'Check-out',
+ 'day.confirmation': 'Επιβεβαίωση',
+ 'day.editAccommodation': 'Επεξεργασία διαμονής',
+ 'day.reservations': 'Κρατήσεις',
+};
+export default day;
diff --git a/shared/src/i18n/gr/dayplan.ts b/shared/src/i18n/gr/dayplan.ts
new file mode 100644
index 0000000000000000000000000000000000000000..586b8e8751579eb8b38c72e3bf8f772ba8b5f60b
--- /dev/null
+++ b/shared/src/i18n/gr/dayplan.ts
@@ -0,0 +1,62 @@
+import type { TranslationStrings } from '../types';
+
+const dayplan: TranslationStrings = {
+ 'dayplan.icsTooltip': 'Εξαγωγή ημερολογίου (ICS)',
+ 'dayplan.emptyDay': 'Δεν υπάρχουν προγραμματισμένα μέρη για αυτή την ημέρα',
+ 'dayplan.cannotReorderTransport':
+ 'Οι κρατήσεις με σταθερή ώρα δεν μπορούν να αναδιαταχθούν',
+ 'dayplan.confirmRemoveTimeTitle': 'Αφαίρεση ώρας;',
+ 'dayplan.confirmRemoveTimeBody':
+ 'Αυτό το μέρος έχει σταθερή ώρα ({time}). Η μετακίνησή του θα αφαιρέσει την ώρα και θα επιτρέψει ελεύθερη ταξινόμηση.',
+ 'dayplan.confirmRemoveTimeAction': 'Αφαίρεση ώρας & μετακίνηση',
+ 'dayplan.confirmDeleteNoteTitle': 'Διαγραφή σημείωσης;',
+ 'dayplan.confirmDeleteNoteBody':
+ 'Αυτή η σημείωση θα διαγραφεί οριστικά.',
+ 'dayplan.cannotDropOnTimed':
+ 'Τα στοιχεία δεν μπορούν να τοποθετηθούν μεταξύ καταχωρήσεων με ώρα',
+ 'dayplan.cannotBreakChronology':
+ 'Αυτό θα έσπαγε τη χρονολογική σειρά των στοιχείων με ώρα και των κρατήσεων',
+ 'dayplan.addNote': 'Προσθήκη Σημείωσης',
+ 'dayplan.expandAll': 'Ανάπτυξη όλων των ημερών',
+ 'dayplan.collapseAll': 'Σύμπτυξη όλων των ημερών',
+ 'dayplan.editNote': 'Επεξεργασία Σημείωσης',
+ 'dayplan.noteAdd': 'Προσθήκη Σημείωσης',
+ 'dayplan.noteEdit': 'Επεξεργασία Σημείωσης',
+ 'dayplan.noteTitle': 'Σημείωση',
+ 'dayplan.noteSubtitle': 'Ημερήσια Σημείωση',
+ 'dayplan.totalCost': 'Συνολικό Κόστος',
+ 'dayplan.days': 'Ημέρες',
+ 'dayplan.dayN': 'Ημέρα {n}',
+ 'dayplan.calculating': 'Υπολογισμός...',
+ 'dayplan.route': 'Διαδρομή',
+ 'dayplan.optimize': 'Βελτιστοποίηση',
+ 'dayplan.optimized': 'Η διαδρομή βελτιστοποιήθηκε',
+ 'dayplan.routeError': 'Αποτυχία υπολογισμού διαδρομής',
+ 'dayplan.toast.needTwoPlaces':
+ 'Χρειάζονται τουλάχιστον δύο μέρη για βελτιστοποίηση διαδρομής',
+ 'dayplan.toast.routeOptimized': 'Η διαδρομή βελτιστοποιήθηκε',
+ 'dayplan.toast.routeOptimizedFromHotel':
+ 'Η διαδρομή βελτιστοποιήθηκε από το κατάλυμά σας',
+ 'dayplan.toast.noGeoPlaces':
+ 'Δεν βρέθηκαν μέρη με συντεταγμένες για τον υπολογισμό διαδρομής',
+ 'dayplan.confirmed': 'Επιβεβαιωμένο',
+ 'dayplan.pendingRes': 'Εκκρεμές',
+ 'dayplan.pdf': 'PDF',
+ 'dayplan.pdfTooltip': 'Εξαγωγή ημερήσιου πλάνου ως PDF',
+ 'dayplan.pdfError': 'Αποτυχία εξαγωγής PDF',
+ 'dayplan.mobile.addPlace': 'Προσθήκη Τοποθεσίας',
+ 'dayplan.mobile.searchPlaces': 'Αναζήτηση τοποθεσιών...',
+ 'dayplan.mobile.allAssigned': 'Όλες οι τοποθεσίες έχουν ανατεθεί',
+ 'dayplan.mobile.noMatch': 'Καμία αντιστοιχία',
+ 'dayplan.mobile.createNew': 'Δημιουργία νέας τοποθεσίας',
+ 'dayplan.reorderDays': 'Αναδιάταξη ημερών',
+ 'dayplan.reorderTitle': 'Αναδιάταξη ημερών',
+ 'dayplan.reorderHint': 'Τα μέρη, οι σημειώσεις και οι κρατήσεις μιας ημέρας μετακινούνται μαζί της.',
+ 'dayplan.addDay': 'Προσθήκη ημέρας',
+ 'dayplan.moveUp': 'Μετακίνηση πάνω',
+ 'dayplan.moveDown': 'Μετακίνηση κάτω',
+ 'dayplan.reorderUndo': 'Αναδιάταξη ημερών',
+ 'dayplan.reorderError': 'Η αναδιάταξη των ημερών απέτυχε',
+ 'dayplan.addDayError': 'Η προσθήκη ημέρας απέτυχε',
+};
+export default dayplan;
diff --git a/shared/src/i18n/gr/externalNotifications.ts b/shared/src/i18n/gr/externalNotifications.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e904a8f963a26ef600d4306120ad37bdb5a22b4e
--- /dev/null
+++ b/shared/src/i18n/gr/externalNotifications.ts
@@ -0,0 +1,64 @@
+import type { NotificationLocale } from '../externalNotifications/types';
+
+const gr: NotificationLocale = {
+ email: {
+ footer:
+ 'Λάβατε αυτό το μήνυμα επειδή έχετε ενεργοποιήσει τις ειδοποιήσεις στο TREK.',
+ manage: 'Διαχείριση προτιμήσεων στις Ρυθμίσεις',
+ madeWith: 'Δημιουργήθηκε με',
+ openTrek: 'Άνοιγμα TREK',
+ },
+ events: {
+ trip_invite: (p) => ({
+ title: `Πρόσκληση ταξιδιού: "${p.trip}"`,
+ body: `Ο/Η ${p.actor} προσκάλεσε ${p.invitee || 'ένα μέλος'} στο ταξίδι "${p.trip}".`,
+ }),
+ booking_change: (p) => ({
+ title: `Νέα κράτηση: ${p.booking}`,
+ body: `Ο/Η ${p.actor} πρόσθεσε μια νέα κράτηση "${p.booking}" (${p.type}) στο "${p.trip}".`,
+ }),
+ trip_reminder: (p) => ({
+ title: `Υπενθύμιση ταξιδιού: ${p.trip}`,
+ body: `Το ταξίδι σας "${p.trip}" πλησιάζει!`,
+ }),
+ todo_due: (p) => ({
+ title: `Εκκρεμότητα προς εκτέλεση: ${p.todo}`,
+ body: `Η εκκρεμότητα "${p.todo}" στο "${p.trip}" λήγει στις ${p.due}.`,
+ }),
+ vacay_invite: (p) => ({
+ title: 'Πρόσκληση συγχώνευσης διακοπών',
+ body: `Ο/Η ${p.actor} σας προσκάλεσε να συγχωνεύσετε τα σχέδια διακοπών σας. Ανοίξτε το TREK για να αποδεχτείτε ή να απορρίψετε.`,
+ }),
+ photos_shared: (p) => ({
+ title: `${p.count} φωτογραφίες κοινοποιήθηκαν`,
+ body: `Ο/Η ${p.actor} κοινοποίησε ${p.count} φωτογραφία/ες στο "${p.trip}".`,
+ }),
+ collab_message: (p) => ({
+ title: `Νέο μήνυμα στο "${p.trip}"`,
+ body: `${p.actor}: ${p.preview}`,
+ }),
+ packing_tagged: (p) => ({
+ title: `Λίστα συσκευασίας: ${p.category}`,
+ body: `Ο/Η ${p.actor} σας ανέθεσε στην κατηγορία "${p.category}" της λίστας συσκευασίας στο "${p.trip}".`,
+ }),
+ version_available: (p) => ({
+ title: 'Νέα έκδοση TREK διαθέσιμη',
+ body: `Η έκδοση TREK ${p.version} είναι τώρα διαθέσιμη. Επισκεφθείτε τον πίνακα διαχείρισης για να ενημερώσετε.`,
+ }),
+ synology_session_cleared: () => ({
+ title: 'Η σύνδεση Synology τερματίστηκε',
+ body: 'Ο λογαριασμός σας Synology ή το URL άλλαξε. Έχετε αποσυνδεθεί από το Synology Photos.',
+ }),
+ },
+ passwordReset: {
+ subject: 'Επαναφορά κωδικού πρόσβασης',
+ greeting: 'Γεια σας',
+ body: 'Λάβαμε ένα αίτημα επαναφοράς του κωδικού πρόσβασης για τον λογαριασμό σας στο TREK. Κάντε κλικ στο παρακάτω κουμπί για να ορίσετε νέο κωδικό πρόσβασης.',
+ ctaIntro: 'Επαναφορά κωδικού',
+ expiry: 'Αυτός ο σύνδεσμος λήγει σε 60 λεπτά.',
+ ignore:
+ 'Εάν δεν ζητήσατε αυτή την αλλαγή, μπορείτε να αγνοήσετε αυτό το μήνυμα — ο κωδικός σας δεν θα αλλάξει.',
+ },
+};
+
+export default gr;
diff --git a/shared/src/i18n/gr/files.ts b/shared/src/i18n/gr/files.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0573df022019d847ef5dfceddc306081c6c577ec
--- /dev/null
+++ b/shared/src/i18n/gr/files.ts
@@ -0,0 +1,64 @@
+import type { TranslationStrings } from '../types';
+
+const files: TranslationStrings = {
+ 'files.title': 'Αρχεία',
+ 'files.pageTitle': 'Αρχεία & Έγγραφα',
+ 'files.subtitle': '{count} αρχεία για {trip}',
+ 'files.download': 'Λήψη',
+ 'files.openError': 'Δεν ήταν δυνατό το άνοιγμα του αρχείου',
+ 'files.downloadPdf': 'Λήψη PDF',
+ 'files.count': '{count} αρχεία',
+ 'files.countSingular': '1 αρχείο',
+ 'files.uploaded': '{count} μεταφορτώθηκαν',
+ 'files.uploadError': 'Η μεταφόρτωση απέτυχε',
+ 'files.dropzone': 'Αποθέστε αρχεία εδώ',
+ 'files.dropzoneHint': 'ή κάντε κλικ για περιήγηση',
+ 'files.allowedTypes':
+ 'Εικόνες, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Μέγ. 50 MB',
+ 'files.uploading': 'Μεταφόρτωση...',
+ 'files.filterAll': 'Όλα',
+ 'files.filterPdf': 'PDF',
+ 'files.filterImages': 'Εικόνες',
+ 'files.filterDocs': 'Έγγραφα',
+ 'files.filterCollab': 'Σημειώσεις Collab',
+ 'files.sourceCollab': 'Από Σημειώσεις Collab',
+ 'files.empty': 'Δεν υπάρχουν αρχεία ακόμη',
+ 'files.emptyHint': 'Μεταφορτώστε αρχεία για να τα επισυνάψετε στο ταξίδι σας',
+ 'files.openTab': 'Άνοιγμα σε νέα καρτέλα',
+ 'files.confirm.delete':
+ 'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το αρχείο;',
+ 'files.toast.deleted': 'Το αρχείο διαγράφηκε',
+ 'files.toast.deleteError': 'Αποτυχία διαγραφής αρχείου',
+ 'files.sourcePlan': 'Ημερήσιο Πλάνο',
+ 'files.sourceBooking': 'Κράτηση',
+ 'files.sourceTransport': 'Μεταφορά',
+ 'files.attach': 'Επισύναψη',
+ 'files.pasteHint':
+ 'Μπορείτε επίσης να επικολλήσετε εικόνες από το πρόχειρο (Ctrl+V)',
+ 'files.trash': 'Κάδος',
+ 'files.trashEmpty': 'Ο κάδος είναι άδειος',
+ 'files.emptyTrash': 'Άδειασμα Κάδου',
+ 'files.restore': 'Επαναφορά',
+ 'files.star': 'Επισήμανση',
+ 'files.unstar': 'Αφαίρεση επισήμανσης',
+ 'files.assign': 'Ανάθεση',
+ 'files.assignTitle': 'Ανάθεση Αρχείου',
+ 'files.assignPlace': 'Μέρος',
+ 'files.assignBooking': 'Κράτηση',
+ 'files.assignTransport': 'Μεταφορά',
+ 'files.unassigned': 'Χωρίς ανάθεση',
+ 'files.unlink': 'Αφαίρεση σύνδεσης',
+ 'files.toast.trashed': 'Μετακινήθηκε στον κάδο',
+ 'files.toast.restored': 'Το αρχείο επαναφέρθηκε',
+ 'files.toast.trashEmptied': 'Ο κάδος αδειάστηκε',
+ 'files.toast.assigned': 'Το αρχείο ανατέθηκε',
+ 'files.toast.assignError': 'Η ανάθεση απέτυχε',
+ 'files.toast.restoreError': 'Η επαναφορά απέτυχε',
+ 'files.confirm.permanentDelete':
+ 'Οριστική διαγραφή αυτού του αρχείου; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.',
+ 'files.confirm.emptyTrash':
+ 'Οριστική διαγραφή όλων των αρχείων στον κάδο; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.',
+ 'files.noteLabel': 'Σημείωση',
+ 'files.notePlaceholder': 'Προσθήκη σημείωσης...',
+};
+export default files;
diff --git a/shared/src/i18n/gr/index.ts b/shared/src/i18n/gr/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..77aeb6a09fb62e2522e8b4c04e4169a94c2f8d99
--- /dev/null
+++ b/shared/src/i18n/gr/index.ts
@@ -0,0 +1,86 @@
+import admin from './admin';
+import airport from './airport';
+import atlas from './atlas';
+import backup from './backup';
+import budget from './budget';
+import categories from './categories';
+import collab from './collab';
+import common from './common';
+import dashboard from './dashboard';
+import day from './day';
+import dayplan from './dayplan';
+import files from './files';
+import inspector from './inspector';
+import journey from './journey';
+import login from './login';
+import map from './map';
+import members from './members';
+import memories from './memories';
+import nav from './nav';
+import notif from './notif';
+import notifications from './notifications';
+import oauth from './oauth';
+import packing from './packing';
+import pdf from './pdf';
+import perm from './perm';
+import photos from './photos';
+import places from './places';
+import planner from './planner';
+import register from './register';
+import reservations from './reservations';
+import settings from './settings';
+import share from './share';
+import shared from './shared';
+import stats from './stats';
+import system_notice from './system_notice';
+import todo from './todo';
+import transport from './transport';
+import trip from './trip';
+import trips from './trips';
+import undo from './undo';
+import vacay from './vacay';
+
+const locale = {
+ ...common,
+ ...trips,
+ ...nav,
+ ...dashboard,
+ ...settings,
+ ...admin,
+ ...dayplan,
+ ...share,
+ ...shared,
+ ...login,
+ ...register,
+ ...vacay,
+ ...atlas,
+ ...trip,
+ ...places,
+ ...inspector,
+ ...reservations,
+ ...airport,
+ ...map,
+ ...budget,
+ ...files,
+ ...packing,
+ ...members,
+ ...categories,
+ ...backup,
+ ...photos,
+ ...pdf,
+ ...planner,
+ ...stats,
+ ...day,
+ ...memories,
+ ...collab,
+ ...perm,
+ ...undo,
+ ...notifications,
+ ...todo,
+ ...notif,
+ ...journey,
+ ...oauth,
+ ...system_notice,
+ ...transport,
+};
+export default locale;
diff --git a/shared/src/i18n/gr/inspector.ts b/shared/src/i18n/gr/inspector.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8a9825b72d82fe9c3c9276b3ff4587ffb08ac2b7
--- /dev/null
+++ b/shared/src/i18n/gr/inspector.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const inspector: TranslationStrings = {
+ 'inspector.opened': 'Ανοιχτό',
+ 'inspector.closed': 'Κλειστό',
+ 'inspector.openingHours': 'Ώρες Λειτουργίας',
+ 'inspector.showHours': 'Εμφάνιση ωρών λειτουργίας',
+ 'inspector.files': 'Αρχεία',
+ 'inspector.filesCount': '{count} αρχεία',
+ 'inspector.remove': 'Αφαίρεση',
+ 'inspector.removeFromDay': 'Αφαίρεση από την Ημέρα',
+ 'inspector.addToDay': 'Προσθήκη στην Ημέρα',
+ 'inspector.confirmedRes': 'Επιβεβαιωμένη Κράτηση',
+ 'inspector.pendingRes': 'Εκκρεμής Κράτηση',
+ 'inspector.google': 'Άνοιγμα στο Google Maps',
+ 'inspector.website': 'Άνοιγμα Ιστοσελίδας',
+ 'inspector.addRes': 'Κράτηση',
+ 'inspector.editRes': 'Επεξεργασία Κράτησης',
+ 'inspector.participants': 'Συμμετέχοντες',
+ 'inspector.trackStats': 'Στατιστικά Διαδρομής',
+};
+export default inspector;
diff --git a/shared/src/i18n/gr/journey.ts b/shared/src/i18n/gr/journey.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c4a49ad9ee78e0f59204e2c08e5730bd656e0f42
--- /dev/null
+++ b/shared/src/i18n/gr/journey.ts
@@ -0,0 +1,247 @@
+import type { TranslationStrings } from '../types';
+
+const journey: TranslationStrings = {
+ 'journey.search.placeholder': 'Αναζήτηση ταξιδιών…',
+ 'journey.search.noResults': 'Κανένα ταξίδι δεν ταιριάζει με «{query}»',
+ 'journey.title': 'Ταξίδι',
+ 'journey.subtitle': 'Παρακολουθήστε τα ταξίδια σας καθώς συμβαίνουν',
+ 'journey.new': 'Νέο Ταξίδι',
+ 'journey.create': 'Δημιουργία',
+ 'journey.titlePlaceholder': 'Πού πηγαίνετε;',
+ 'journey.empty': 'Δεν υπάρχουν ταξίδια ακόμα',
+ 'journey.emptyHint': 'Ξεκινήστε να καταγράφετε το επόμενο ταξίδι σας',
+ 'journey.deleted': 'Το ταξίδι διαγράφηκε',
+ 'journey.createError': 'Δεν ήταν δυνατή η δημιουργία ταξιδιού',
+ 'journey.deleteError': 'Δεν ήταν δυνατή η διαγραφή ταξιδιού',
+ 'journey.deleteConfirmTitle': 'Διαγραφή',
+ 'journey.deleteConfirmMessage':
+ 'Διαγραφή «{title}»; Αυτό δεν μπορεί να αναιρεθεί.',
+ 'journey.deleteConfirmGeneric': 'Είστε σίγουροι ότι θέλετε να το διαγράψετε;',
+ 'journey.notFound': 'Το ταξίδι δεν βρέθηκε',
+ 'journey.photos': 'Φωτογραφίες',
+ 'journey.timelineEmpty': 'Δεν υπάρχουν στάσεις ακόμα',
+ 'journey.timelineEmptyHint':
+ 'Προσθέστε μια άφιξη ή γράψτε μια καταχώρηση ημερολογίου για να ξεκινήσετε',
+ 'journey.status.draft': 'Πρόχειρο',
+ 'journey.status.active': 'Ενεργό',
+ 'journey.status.completed': 'Ολοκληρωμένο',
+ 'journey.status.upcoming': 'Επερχόμενο',
+ 'journey.status.archived': 'Αρχειοθετημένο',
+ 'journey.checkin.add': 'Άφιξη',
+ 'journey.checkin.namePlaceholder': 'Όνομα τοποθεσίας',
+ 'journey.checkin.notesPlaceholder': 'Σημειώσεις (προαιρετικό)',
+ 'journey.checkin.save': 'Αποθήκευση',
+ 'journey.checkin.error': 'Δεν ήταν δυνατή η αποθήκευση άφιξης',
+ 'journey.entry.add': 'Ημερολόγιο',
+ 'journey.entry.edit': 'Επεξεργασία καταχώρησης',
+ 'journey.entry.titlePlaceholder': 'Τίτλος (προαιρετικό)',
+ 'journey.entry.bodyPlaceholder': 'Τι συνέβη σήμερα;',
+ 'journey.entry.save': 'Αποθήκευση',
+ 'journey.entry.error': 'Δεν ήταν δυνατή η αποθήκευση καταχώρησης',
+ 'journey.photo.add': 'Φωτογραφία',
+ 'journey.photo.uploadError': 'Η μεταφόρτωση απέτυχε',
+ 'journey.share.share': 'Κοινοποίηση',
+ 'journey.share.public': 'Δημόσιο',
+ 'journey.share.linkCopied': 'Ο δημόσιος σύνδεσμος αντιγράφηκε',
+ 'journey.share.disabled': 'Η δημόσια κοινοποίηση απενεργοποιήθηκε',
+ 'journey.editor.titlePlaceholder': 'Δώστε ένα όνομα σε αυτή τη στιγμή...',
+ 'journey.editor.bodyPlaceholder': 'Πείτε την ιστορία αυτής της ημέρας...',
+ 'journey.editor.placePlaceholder': 'Τοποθεσία (προαιρετικό)',
+ 'journey.editor.tagsPlaceholder':
+ 'Ετικέτες: κρυμμένος θησαυρός, καλύτερο γεύμα, πρέπει να ξαναπάω...',
+ 'journey.visibility.private': 'Ιδιωτικό',
+ 'journey.visibility.shared': 'Κοινόχρηστο',
+ 'journey.visibility.public': 'Δημόσιο',
+ 'journey.emptyState.title': 'Η ιστορία σας ξεκινά εδώ',
+ 'journey.emptyState.subtitle':
+ 'Κάντε άφιξη σε ένα μέρος ή γράψτε την πρώτη σας καταχώρηση ημερολογίου',
+ 'journey.frontpage.subtitle':
+ 'Μετατρέψτε τα ταξίδια σας σε ιστορίες που δεν θα ξεχάσετε ποτέ',
+ 'journey.frontpage.createJourney': 'Δημιουργία Ταξιδιού',
+ 'journey.frontpage.activeJourney': 'Ενεργό Ταξίδι',
+ 'journey.frontpage.allJourneys': 'Όλα τα Ταξίδια',
+ 'journey.frontpage.journeys': 'ταξίδια',
+ 'journey.frontpage.createNew': 'Δημιουργία νέου Ταξιδιού',
+ 'journey.frontpage.createNewSub':
+ 'Επιλέξτε ταξίδια, γράψτε ιστορίες, μοιραστείτε τις περιπέτειές σας',
+ 'journey.frontpage.live': 'Ζωντανά',
+ 'journey.frontpage.synced': 'Συγχρονισμένο',
+ 'journey.frontpage.continueWriting': 'Συνέχιση γραφής',
+ 'journey.frontpage.updated': 'Ενημερώθηκε {time}',
+ 'journey.frontpage.suggestionLabel': 'Το ταξίδι μόλις τελείωσε',
+ 'journey.frontpage.suggestionText':
+ 'Μετατρέψτε το {title} σε Ταξίδι',
+ 'journey.frontpage.dismiss': 'Απόρριψη',
+ 'journey.frontpage.journeyName': 'Όνομα Ταξιδιού',
+ 'journey.frontpage.namePlaceholder': 'π.χ. Νοτιοανατολική Ασία 2026',
+ 'journey.frontpage.selectTrips': 'Επιλογή Ταξιδιών',
+ 'journey.frontpage.tripsSelected': 'επιλεγμένα ταξίδια',
+ 'journey.frontpage.trips': 'ταξίδια',
+ 'journey.frontpage.placesImported': 'τοποθεσίες θα εισαχθούν',
+ 'journey.frontpage.places': 'τοποθεσίες',
+ 'journey.detail.backToJourney': 'Πίσω στο Ταξίδι',
+ 'journey.detail.syncedWithTrips': 'Συγχρονισμένο με Ταξίδια',
+ 'journey.detail.addEntry': 'Προσθήκη Καταχώρησης',
+ 'journey.detail.newEntry': 'Νέα Καταχώρηση',
+ 'journey.detail.editEntry': 'Επεξεργασία Καταχώρησης',
+ 'journey.detail.noEntries': 'Δεν υπάρχουν καταχωρήσεις ακόμα',
+ 'journey.detail.noEntriesHint':
+ 'Προσθέστε ένα ταξίδι για να ξεκινήσετε με προτεινόμενες καταχωρήσεις',
+ 'journey.detail.noPhotos': 'Δεν υπάρχουν φωτογραφίες ακόμα',
+ 'journey.detail.noPhotosHint':
+ 'Μεταφορτώστε φωτογραφίες σε καταχωρήσεις ή περιηγηθείτε τη βιβλιοθήκη Immich/Synology',
+ 'journey.detail.journeyTab': 'Ταξίδι',
+ 'journey.detail.journeyStats': 'Στατιστικά Ταξιδιού',
+ 'journey.detail.syncedTrips': 'Συγχρονισμένα Ταξίδια',
+ 'journey.detail.noTripsLinked': 'Δεν έχουν συνδεθεί ταξίδια ακόμα',
+ 'journey.detail.contributors': 'Συνεισφέροντες',
+ 'journey.detail.readMore': 'Διαβάστε περισσότερα',
+ 'journey.detail.prosCons': 'Υπέρ & Κατά',
+ 'journey.detail.photos': 'φωτογραφίες',
+ 'journey.detail.day': 'Ημέρα {number}',
+ 'journey.detail.places': 'τοποθεσίες',
+ 'journey.stats.days': 'Ημέρες',
+ 'journey.stats.cities': 'Πόλεις',
+ 'journey.stats.entries': 'Καταχωρήσεις',
+ 'journey.stats.photos': 'Φωτογραφίες',
+ 'journey.stats.places': 'Τοποθεσίες',
+ 'journey.skeletons.show': 'Εμφάνιση προτάσεων',
+ 'journey.skeletons.hide': 'Απόκρυψη προτάσεων',
+ 'journey.verdict.lovedIt': 'Το λάτρεψα',
+ 'journey.verdict.couldBeBetter': 'Θα μπορούσε να ήταν καλύτερο',
+ 'journey.synced.places': 'τοποθεσίες',
+ 'journey.synced.synced': 'συγχρονισμένα',
+ 'journey.editor.discardChangesConfirm':
+ 'Έχετε μη αποθηκευμένες αλλαγές. Απόρριψη;',
+ 'journey.editor.uploadFailed': 'Η μεταφόρτωση φωτογραφίας απέτυχε',
+ 'journey.editor.uploadPhotos': 'Μεταφόρτωση φωτογραφιών',
+ 'journey.editor.uploading': 'Μεταφόρτωση...',
+ 'journey.editor.uploadingProgress': 'Μεταφόρτωση {done}/{total}.',
+ 'journey.editor.uploadPartialFailed':
+ '{failed} από {total} φωτογραφίες απέτυχαν — αποθηκεύστε ξανά για να δοκιμάσετε εκ νέου',
+ 'journey.editor.fromGallery': 'Από τη Συλλογή',
+ 'journey.editor.allPhotosAdded': 'Όλες οι φωτογραφίες έχουν ήδη προστεθεί',
+ 'journey.editor.writeStory': 'Γράψτε την ιστορία σας...',
+ 'journey.editor.prosCons': 'Υπέρ & Κατά',
+ 'journey.editor.pros': 'Υπέρ',
+ 'journey.editor.cons': 'Κατά',
+ 'journey.editor.proPlaceholder': 'Κάτι υπέροχο...',
+ 'journey.editor.conPlaceholder': 'Όχι και τόσο καλό...',
+ 'journey.editor.addAnother': 'Προσθήκη άλλου',
+ 'journey.editor.date': 'Ημερομηνία',
+ 'journey.editor.location': 'Τοποθεσία',
+ 'journey.editor.searchLocation': 'Αναζήτηση τοποθεσίας...',
+ 'journey.editor.mood': 'Διάθεση',
+ 'journey.editor.weather': 'Καιρός',
+ 'journey.editor.photoFirst': '1η',
+ 'journey.editor.makeFirst': 'Ορισμός ως 1η',
+ 'journey.editor.searching': 'Αναζήτηση...',
+ 'journey.mood.amazing': 'Καταπληκτική',
+ 'journey.mood.good': 'Καλή',
+ 'journey.mood.neutral': 'Ουδέτερη',
+ 'journey.mood.rough': 'Δύσκολη',
+ 'journey.weather.sunny': 'Ηλιόλουστος',
+ 'journey.weather.partly': 'Μερικώς συννεφιασμένος',
+ 'journey.weather.cloudy': 'Συννεφιασμένος',
+ 'journey.weather.rainy': 'Βροχερός',
+ 'journey.weather.stormy': 'Καταιγιδοφόρος',
+ 'journey.weather.cold': 'Χιονισμένος',
+ 'journey.trips.linkTrip': 'Σύνδεση Ταξιδιού',
+ 'journey.trips.searchTrip': 'Αναζήτηση Ταξιδιού',
+ 'journey.trips.searchPlaceholder': 'Όνομα ταξιδιού ή προορισμός...',
+ 'journey.trips.noTripsAvailable': 'Δεν υπάρχουν διαθέσιμα ταξίδια',
+ 'journey.trips.link': 'Σύνδεση',
+ 'journey.trips.tripLinked': 'Το ταξίδι συνδέθηκε',
+ 'journey.trips.linkFailed': 'Αποτυχία σύνδεσης ταξιδιού',
+ 'journey.trips.addTrip': 'Προσθήκη Ταξιδιού',
+ 'journey.trips.unlinkTrip': 'Αποσύνδεση Ταξιδιού',
+ 'journey.trips.unlinkMessage':
+ 'Αποσύνδεση «{title}»; Όλες οι συγχρονισμένες καταχωρήσεις και φωτογραφίες αυτού του ταξιδιού θα διαγραφούν οριστικά. Αυτό δεν μπορεί να αναιρεθεί.',
+ 'journey.trips.unlink': 'Αποσύνδεση',
+ 'journey.trips.tripUnlinked': 'Το ταξίδι αποσυνδέθηκε',
+ 'journey.trips.unlinkFailed': 'Αποτυχία αποσύνδεσης ταξιδιού',
+ 'journey.trips.noTripsLinkedSettings': 'Δεν έχουν συνδεθεί ταξίδια',
+ 'journey.contributors.invite': 'Πρόσκληση Συνεισφέροντα',
+ 'journey.contributors.searchUser': 'Αναζήτηση Χρήστη',
+ 'journey.contributors.searchPlaceholder': 'Όνομα χρήστη ή email...',
+ 'journey.contributors.noUsers': 'Δεν βρέθηκαν χρήστες',
+ 'journey.contributors.role': 'Ρόλος',
+ 'journey.contributors.added': 'Ο συνεισφέρων προστέθηκε',
+ 'journey.contributors.addFailed': 'Αποτυχία προσθήκης συνεισφέροντα',
+ 'journey.contributors.remove': 'Αφαίρεση συνεισφέροντα',
+ 'journey.contributors.removeConfirm':
+ 'Αφαίρεση {username} από αυτό το ταξίδι;',
+ 'journey.contributors.removed': 'Ο συνεισφέρων αφαιρέθηκε',
+ 'journey.contributors.removeFailed': 'Αποτυχία αφαίρεσης συνεισφέροντα',
+ 'journey.share.publicShare': 'Δημόσια Κοινοποίηση',
+ 'journey.share.createLink': 'Δημιουργία συνδέσμου κοινής χρήσης',
+ 'journey.share.linkCreated': 'Ο σύνδεσμος κοινής χρήσης δημιουργήθηκε',
+ 'journey.share.createFailed': 'Αποτυχία δημιουργίας συνδέσμου',
+ 'journey.share.copy': 'Αντιγραφή',
+ 'journey.share.copied': 'Αντιγράφηκε!',
+ 'journey.share.timeline': 'Χρονολόγιο',
+ 'journey.share.gallery': 'Συλλογή',
+ 'journey.share.map': 'Χάρτης',
+ 'journey.share.removeLink': 'Αφαίρεση συνδέσμου κοινής χρήσης',
+ 'journey.share.linkDeleted': 'Ο σύνδεσμος κοινής χρήσης διαγράφηκε',
+ 'journey.share.deleteFailed': 'Αποτυχία διαγραφής',
+ 'journey.share.updateFailed': 'Αποτυχία ενημέρωσης',
+ 'journey.invite.role': 'Ρόλος',
+ 'journey.invite.viewer': 'Θεατής',
+ 'journey.invite.editor': 'Συντάκτης',
+ 'journey.invite.invite': 'Πρόσκληση',
+ 'journey.invite.inviting': 'Αποστολή πρόσκλησης...',
+ 'journey.settings.title': 'Ρυθμίσεις Ταξιδιού',
+ 'journey.settings.coverImage': 'Εικόνα Εξωφύλλου',
+ 'journey.settings.changeCover': 'Αλλαγή εξωφύλλου',
+ 'journey.settings.addCover': 'Προσθήκη εικόνας εξωφύλλου',
+ 'journey.settings.name': 'Όνομα',
+ 'journey.settings.subtitle': 'Υπότιτλος',
+ 'journey.settings.subtitlePlaceholder': 'π.χ. Ταϊλάνδη, Βιετνάμ & Καμπότζη',
+ 'journey.settings.endJourney': 'Αρχειοθέτηση Ταξιδιού',
+ 'journey.settings.reopenJourney': 'Επαναφορά Ταξιδιού',
+ 'journey.settings.archived': 'Το ταξίδι αρχειοθετήθηκε',
+ 'journey.settings.reopened': 'Το ταξίδι επανενεργοποιήθηκε',
+ 'journey.settings.endDescription':
+ 'Αποκρύπτει το σήμα «Ζωντανά». Μπορείτε να το επαναφέρετε ανά πάσα στιγμή.',
+ 'journey.settings.delete': 'Διαγραφή',
+ 'journey.settings.deleteJourney': 'Διαγραφή Ταξιδιού',
+ 'journey.settings.deleteMessage':
+ 'Διαγραφή «{title}»; Όλες οι καταχωρήσεις και οι φωτογραφίες θα χαθούν.',
+ 'journey.settings.saved': 'Οι ρυθμίσεις αποθηκεύτηκαν',
+ 'journey.settings.saveFailed': 'Αποτυχία αποθήκευσης',
+ 'journey.settings.coverUpdated': 'Το εξώφυλλο ενημερώθηκε',
+ 'journey.settings.coverFailed': 'Η μεταφόρτωση απέτυχε',
+ 'journey.settings.failedToDelete': 'Αποτυχία διαγραφής',
+ 'journey.entries.deleteTitle': 'Διαγραφή Καταχώρησης',
+ 'journey.photosUploaded': '{count} φωτογραφίες μεταφορτώθηκαν',
+ 'journey.photosUploadFailed':
+ 'Ορισμένες φωτογραφίες απέτυχαν να μεταφορτωθούν',
+ 'journey.photosAdded': '{count} φωτογραφίες προστέθηκαν',
+ 'journey.public.notFound': 'Δεν Βρέθηκε',
+ 'journey.public.notFoundMessage':
+ 'Αυτό το ταξίδι δεν υπάρχει ή ο σύνδεσμος έχει λήξει.',
+ 'journey.public.readOnly': 'Μόνο ανάγνωση · Δημόσιο Ταξίδι',
+ 'journey.public.tagline': 'Travel Resource & Exploration Kit',
+ 'journey.public.sharedVia': 'Κοινοποιήθηκε μέσω',
+ 'journey.public.madeWith': 'Φτιαγμένο με',
+ 'journey.pdf.journeyBook': 'Βιβλίο Ταξιδιού',
+ 'journey.pdf.madeWith': 'Φτιαγμένο με TREK',
+ 'journey.pdf.day': 'Ημέρα',
+ 'journey.pdf.theEnd': 'Τέλος',
+ 'journey.pdf.saveAsPdf': 'Αποθήκευση ως PDF',
+ 'journey.pdf.pages': 'σελίδες',
+ 'journey.picker.tripPeriod': 'Περίοδος Ταξιδιού',
+ 'journey.picker.dateRange': 'Εύρος Ημερομηνιών',
+ 'journey.picker.allPhotos': 'Όλες οι Φωτογραφίες',
+ 'journey.picker.albums': 'Άλμπουμ',
+ 'journey.picker.selected': 'επιλεγμένες',
+ 'journey.picker.addTo': 'Προσθήκη σε',
+ 'journey.picker.newGallery': 'Νέα Συλλογή',
+ 'journey.picker.selectAll': 'Επιλογή όλων',
+ 'journey.picker.deselectAll': 'Αποεπιλογή όλων',
+ 'journey.picker.noAlbums': 'Δεν βρέθηκαν άλμπουμ',
+ 'journey.picker.selectDate': 'Επιλογή ημερομηνίας',
+ 'journey.picker.search': 'Αναζήτηση',
+};
+export default journey;
diff --git a/shared/src/i18n/gr/login.ts b/shared/src/i18n/gr/login.ts
new file mode 100644
index 0000000000000000000000000000000000000000..008d202e1ab5e7320adeda9c90423301c18531cc
--- /dev/null
+++ b/shared/src/i18n/gr/login.ts
@@ -0,0 +1,107 @@
+import type { TranslationStrings } from '../types';
+
+const login: TranslationStrings = {
+ 'login.error': 'Η σύνδεση απέτυχε. Παρακαλώ ελέγξτε τα στοιχεία σας.',
+ 'login.tagline': 'Τα Ταξίδια σας.\nΤο Σχέδιό σας.',
+ 'login.description':
+ 'Σχεδιάστε ταξίδια συνεργατικά με διαδραστικούς χάρτες, προϋπολογισμούς και συγχρονισμό σε πραγματικό χρόνο.',
+ 'login.features.maps': 'Διαδραστικοί Χάρτες',
+ 'login.features.mapsDesc': 'Google Places, διαδρομές & ομαδοποίηση',
+ 'login.features.realtime': 'Συγχρονισμός Πραγματικού Χρόνου',
+ 'login.features.realtimeDesc': 'Σχεδιάστε μαζί μέσω WebSocket',
+ 'login.features.budget': 'Παρακολούθηση Προϋπολογισμού',
+ 'login.features.budgetDesc': 'Κατηγορίες, γραφήματα & κόστος ανά άτομο',
+ 'login.features.collab': 'Συνεργασία',
+ 'login.features.collabDesc': 'Πολλαπλοί χρήστες με κοινόχρηστα ταξίδια',
+ 'login.features.packing': 'Λίστες Πακεταρίσματος',
+ 'login.features.packingDesc': 'Κατηγορίες, πρόοδος & προτάσεις',
+ 'login.features.bookings': 'Κρατήσεις',
+ 'login.features.bookingsDesc': 'Πτήσεις, ξενοδοχεία, εστιατόρια & άλλα',
+ 'login.features.files': 'Έγγραφα',
+ 'login.features.filesDesc': 'Μεταφόρτωση & διαχείριση εγγράφων',
+ 'login.features.routes': 'Έξυπνες Διαδρομές',
+ 'login.features.routesDesc':
+ 'Αυτόματη βελτιστοποίηση & εξαγωγή στο Google Maps',
+ 'login.selfHosted':
+ 'Self-hosted \u00B7 Open Source \u00B7 Τα δεδομένα σας παραμένουν δικά σας',
+ 'login.title': 'Σύνδεση',
+ 'login.subtitle': 'Καλώς ήρθατε πίσω',
+ 'login.signingIn': 'Σύνδεση…',
+ 'login.signIn': 'Σύνδεση',
+ 'login.createAdmin': 'Δημιουργία Λογαριασμού Διαχειριστή',
+ 'login.createAdminHint':
+ 'Ρυθμίστε τον πρώτο λογαριασμό διαχειριστή για το TREK.',
+ 'login.setNewPassword': 'Ορισμός Νέου Κωδικού',
+ 'login.setNewPasswordHint':
+ 'Πρέπει να αλλάξετε τον κωδικό σας πριν συνεχίσετε.',
+ 'login.createAccount': 'Δημιουργία Λογαριασμού',
+ 'login.createAccountHint': 'Καταχωρήστε έναν νέο λογαριασμό.',
+ 'login.creating': 'Δημιουργία…',
+ 'login.noAccount': 'Δεν έχετε λογαριασμό;',
+ 'login.hasAccount': 'Έχετε ήδη λογαριασμό;',
+ 'login.register': 'Εγγραφή',
+ 'login.emailPlaceholder': 'your@email.com',
+ 'login.username': 'Όνομα χρήστη',
+ 'login.oidc.registrationDisabled':
+ 'Η εγγραφή είναι απενεργοποιημένη. Επικοινωνήστε με τον διαχειριστή σας.',
+ 'login.oidc.noEmail': 'Δεν ελήφθη email από τον πάροχο.',
+ 'login.oidc.tokenFailed': 'Ο έλεγχος ταυτότητας απέτυχε.',
+ 'login.oidc.invalidState': 'Μη έγκυρη συνεδρία. Παρακαλώ δοκιμάστε ξανά.',
+ 'login.demoFailed': 'Η σύνδεση demo απέτυχε',
+ 'login.oidcSignIn': 'Σύνδεση με {name}',
+ 'login.oidcOnly':
+ 'Ο έλεγχος ταυτότητας με κωδικό είναι απενεργοποιημένος. Παρακαλώ συνδεθείτε χρησιμοποιώντας τον πάροχο SSO σας.',
+ 'login.oidcLoggedOut':
+ 'Έχετε αποσυνδεθεί. Συνδεθείτε ξανά χρησιμοποιώντας τον πάροχο SSO σας.',
+ 'login.demoHint': 'Δοκιμάστε το demo — δεν απαιτείται εγγραφή',
+ 'login.mfaTitle': 'Έλεγχος ταυτότητας δύο παραγόντων',
+ 'login.mfaSubtitle':
+ 'Εισάγετε τον 6ψήφιο κωδικό από την εφαρμογή authenticator σας.',
+ 'login.mfaCodeLabel': 'Κωδικός επαλήθευσης',
+ 'login.mfaCodeRequired':
+ 'Εισάγετε τον κωδικό από την εφαρμογή authenticator σας.',
+ 'login.mfaHint':
+ 'Ανοίξτε το Google Authenticator, Authy ή άλλη εφαρμογή TOTP.',
+ 'login.mfaBack': '← Πίσω στη σύνδεση',
+ 'login.mfaVerify': 'Επαλήθευση',
+ 'login.invalidInviteLink': 'Μη έγκυρος ή ληγμένος σύνδεσμος πρόσκλησης',
+ 'login.oidcFailed': 'Η σύνδεση OIDC απέτυχε',
+ 'login.usernameRequired': 'Το όνομα χρήστη είναι υποχρεωτικό',
+ 'login.passwordMinLength':
+ 'Ο κωδικός πρέπει να έχει τουλάχιστον 8 χαρακτήρες',
+ 'login.forgotPassword': 'Ξεχάσατε τον κωδικό;',
+ 'login.rememberMe': 'Να με θυμάσαι',
+ 'login.forgotPasswordTitle': 'Επαναφορά του κωδικού σας',
+ 'login.forgotPasswordBody':
+ 'Εισάγετε το email με το οποίο εγγραφήκατε. Αν υπάρχει λογαριασμός, θα στείλουμε έναν σύνδεσμο επαναφοράς.',
+ 'login.forgotPasswordSubmit': 'Αποστολή συνδέσμου επαναφοράς',
+ 'login.forgotPasswordSentTitle': 'Ελέγξτε το email σας',
+ 'login.forgotPasswordSentBody':
+ "Αν υπάρχει λογαριασμός για αυτό το email, ένας σύνδεσμος επαναφοράς είναι καθ'οδόν. Λήγει σε 60 λεπτά.",
+ 'login.forgotPasswordSmtpHintOff':
+ 'Προσοχή: ο διαχειριστής σας δεν έχει διαμορφώσει SMTP, οπότε ο σύνδεσμος επαναφοράς θα γραφτεί στην κονσόλα του server αντί να σταλεί με email.',
+ 'login.backToLogin': 'Πίσω στη σύνδεση',
+ 'login.newPassword': 'Νέος κωδικός',
+ 'login.confirmPassword': 'Επιβεβαίωση νέου κωδικού',
+ 'login.passwordsDontMatch': 'Οι κωδικοί δεν ταιριάζουν',
+ 'login.mfaCode': 'Κωδικός 2FA',
+ 'login.resetPasswordTitle': 'Ορίστε έναν νέο κωδικό',
+ 'login.resetPasswordBody':
+ 'Επιλέξτε έναν ισχυρό κωδικό που δεν έχετε χρησιμοποιήσει εδώ ξανά. Ελάχιστο 8 χαρακτήρες.',
+ 'login.resetPasswordMfaBody':
+ 'Εισάγετε τον κωδικό 2FA ή έναν εφεδρικό κωδικό για να ολοκληρωθεί η επαναφορά.',
+ 'login.resetPasswordSubmit': 'Επαναφορά κωδικού',
+ 'login.resetPasswordVerify': 'Επαλήθευση & επαναφορά',
+ 'login.resetPasswordSuccessTitle': 'Ο κωδικός ενημερώθηκε',
+ 'login.resetPasswordSuccessBody':
+ 'Μπορείτε τώρα να συνδεθείτε με τον νέο σας κωδικό.',
+ 'login.resetPasswordInvalidLink': 'Μη έγκυρος σύνδεσμος επαναφοράς',
+ 'login.resetPasswordInvalidLinkBody':
+ 'Αυτός ο σύνδεσμος λείπει ή έχει χαλάσει. Ζητήστε έναν νέο για να συνεχίσετε.',
+ 'login.resetPasswordFailed':
+ 'Η επαναφορά απέτυχε. Ο σύνδεσμος μπορεί να έχει λήξει.',
+ 'login.passkey.signIn': 'Σύνδεση με passkey',
+ 'login.passkey.failed':
+ 'Η σύνδεση με passkey απέτυχε. Παρακαλώ δοκιμάστε ξανά.',
+};
+export default login;
diff --git a/shared/src/i18n/gr/map.ts b/shared/src/i18n/gr/map.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8e973f04f17db30af937e8b4001ee4f25815523f
--- /dev/null
+++ b/shared/src/i18n/gr/map.ts
@@ -0,0 +1,17 @@
+import type { TranslationStrings } from '../types';
+
+const map: TranslationStrings = {
+ 'map.connections': 'Συνδέσεις',
+ 'map.showConnections': 'Εμφάνιση διαδρομών κρατήσεων',
+ 'map.hideConnections': 'Απόκρυψη διαδρομών κρατήσεων',
+ 'poi.searchThisArea': 'Αναζήτηση σε αυτήν την περιοχή',
+ 'poi.cat.restaurants': 'Εστιατόρια',
+ 'poi.cat.cafes': 'Καφέ',
+ 'poi.cat.bars': 'Μπαρ & νυχτερινή ζωή',
+ 'poi.cat.hotels': 'Διαμονή',
+ 'poi.cat.sights': 'Αξιοθέατα',
+ 'poi.cat.museums': 'Μουσεία & πολιτισμός',
+ 'poi.cat.nature': 'Φύση & πάρκα',
+ 'poi.cat.activities': 'Δραστηριότητες',
+};
+export default map;
diff --git a/shared/src/i18n/gr/members.ts b/shared/src/i18n/gr/members.ts
new file mode 100644
index 0000000000000000000000000000000000000000..759bda6b70a1a64fb3ea9e9e1003f1d69fb790e3
--- /dev/null
+++ b/shared/src/i18n/gr/members.ts
@@ -0,0 +1,24 @@
+import type { TranslationStrings } from '../types';
+
+const members: TranslationStrings = {
+ 'members.shareTrip': 'Κοινή Χρήση Ταξιδιού',
+ 'members.inviteUser': 'Πρόσκληση Χρήστη',
+ 'members.selectUser': 'Επιλέξτε χρήστη…',
+ 'members.invite': 'Πρόσκληση',
+ 'members.allHaveAccess': 'Όλοι οι χρήστες έχουν ήδη πρόσβαση.',
+ 'members.access': 'Πρόσβαση',
+ 'members.person': 'άτομο',
+ 'members.persons': 'άτομα',
+ 'members.you': 'εσείς',
+ 'members.owner': 'Ιδιοκτήτης',
+ 'members.leaveTrip': 'Αποχώρηση από ταξίδι',
+ 'members.removeAccess': 'Αφαίρεση πρόσβασης',
+ 'members.confirmLeave': 'Αποχώρηση από το ταξίδι; Θα χάσετε την πρόσβαση.',
+ 'members.confirmRemove': 'Αφαίρεση πρόσβασης για αυτόν τον χρήστη;',
+ 'members.loadError': 'Αποτυχία φόρτωσης μελών',
+ 'members.added': 'προστέθηκε',
+ 'members.addError': 'Αποτυχία προσθήκης',
+ 'members.removed': 'Το μέλος αφαιρέθηκε',
+ 'members.removeError': 'Αποτυχία αφαίρεσης',
+};
+export default members;
diff --git a/shared/src/i18n/gr/memories.ts b/shared/src/i18n/gr/memories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..982dad5120c42ba5e022bab7396b4b60d9755af4
--- /dev/null
+++ b/shared/src/i18n/gr/memories.ts
@@ -0,0 +1,85 @@
+import type { TranslationStrings } from '../types';
+
+const memories: TranslationStrings = {
+ 'memories.title': 'Φωτογραφίες',
+ 'memories.notConnected': 'Το {provider_name} δεν είναι συνδεδεμένο',
+ 'memories.notConnectedHint':
+ 'Συνδέστε την υπηρεσία {provider_name} στις Ρυθμίσεις για να μπορείτε να προσθέτετε φωτογραφίες σε αυτό το ταξίδι.',
+ 'memories.notConnectedMultipleHint':
+ 'Συνδέστε οποιονδήποτε από αυτούς τους παρόχους φωτογραφιών: {provider_names} στις Ρυθμίσεις για να μπορείτε να προσθέτετε φωτογραφίες σε αυτό το ταξίδι.',
+ 'memories.noDates':
+ 'Προσθέστε ημερομηνίες στο ταξίδι σας για να φορτωθούν φωτογραφίες.',
+ 'memories.noPhotos': 'Δεν βρέθηκαν φωτογραφίες',
+ 'memories.noPhotosHint':
+ 'Δεν βρέθηκαν φωτογραφίες στο {provider_name} για το εύρος ημερομηνιών αυτού του ταξιδιού.',
+ 'memories.photosFound': 'φωτογραφίες',
+ 'memories.fromOthers': 'από άλλους',
+ 'memories.sharePhotos': 'Κοινή χρήση φωτογραφιών',
+ 'memories.sharing': 'Κοινή χρήση',
+ 'memories.reviewTitle': 'Ελέγξτε τις φωτογραφίες σας',
+ 'memories.reviewHint':
+ 'Κάντε κλικ στις φωτογραφίες για να τις εξαιρέσετε από την κοινή χρήση.',
+ 'memories.shareCount': 'Κοινή χρήση {count} φωτογραφιών',
+ 'memories.providerUrl': 'URL Διακομιστή',
+ 'memories.providerApiKey': 'API Key',
+ 'memories.providerUsername': 'Όνομα χρήστη',
+ 'memories.providerPassword': 'Κωδικός',
+ 'memories.providerOTP': 'Κωδικός MFA (αν είναι ενεργοποιημένος)',
+ 'memories.skipSSLVerification': 'Παράλειψη επαλήθευσης πιστοποιητικού SSL',
+ 'memories.immichAutoUpload':
+ 'Αντικατοπτρισμός φωτογραφιών journey στο Immich κατά τη μεταφόρτωση',
+ 'memories.providerUrlHintSynology':
+ 'Συμπεριλάβετε τη διαδρομή της εφαρμογής Photos στο URL, π.χ. https://nas:5001/photo',
+ 'memories.testConnection': 'Δοκιμή σύνδεσης',
+ 'memories.testShort': 'Δοκιμή',
+ 'memories.testFirst': 'Δοκιμάστε πρώτα τη σύνδεση',
+ 'memories.connected': 'Συνδεδεμένο',
+ 'memories.disconnected': 'Μη συνδεδεμένο',
+ 'memories.connectionSuccess': 'Συνδέθηκε στο {provider_name}',
+ 'memories.connectionError': 'Δεν ήταν δυνατή η σύνδεση στο {provider_name}',
+ 'memories.saved': 'Οι ρυθμίσεις {provider_name} αποθηκεύτηκαν',
+ 'memories.providerDisconnectedBanner':
+ 'Η σύνδεση {provider_name} χάθηκε. Επανασυνδεθείτε στις Ρυθμίσεις για να δείτε φωτογραφίες.',
+ 'memories.saveError':
+ 'Δεν ήταν δυνατή η αποθήκευση των ρυθμίσεων {provider_name}',
+ 'memories.addPhotos': 'Προσθήκη φωτογραφιών',
+ 'memories.linkAlbum': 'Σύνδεση Άλμπουμ',
+ 'memories.selectAlbum': 'Επιλογή Άλμπουμ {provider_name}',
+ 'memories.selectAlbumMultiple': 'Επιλογή Άλμπουμ',
+ 'memories.noAlbums': 'Δεν βρέθηκαν άλμπουμ',
+ 'memories.syncAlbum': 'Συγχρονισμός άλμπουμ',
+ 'memories.unlinkAlbum': 'Αποσύνδεση άλμπουμ',
+ 'memories.photos': 'φωτογραφίες',
+ 'memories.selectPhotos': 'Επιλογή φωτογραφιών από {provider_name}',
+ 'memories.selectPhotosMultiple': 'Επιλογή Φωτογραφιών',
+ 'memories.selectHint': 'Πατήστε στις φωτογραφίες για να τις επιλέξετε.',
+ 'memories.selected': 'επιλεγμένες',
+ 'memories.addSelected': 'Προσθήκη {count} φωτογραφιών',
+ 'memories.alreadyAdded': 'Προστέθηκε',
+ 'memories.private': 'Ιδιωτικό',
+ 'memories.stopSharing': 'Διακοπή κοινής χρήσης',
+ 'memories.oldest': 'Παλαιότερες πρώτα',
+ 'memories.newest': 'Νεότερες πρώτα',
+ 'memories.allLocations': 'Όλες οι τοποθεσίες',
+ 'memories.tripDates': 'Ημερομηνίες ταξιδιού',
+ 'memories.allPhotos': 'Όλες οι φωτογραφίες',
+ 'memories.confirmShareTitle': 'Κοινή χρήση με τα μέλη του ταξιδιού;',
+ 'memories.confirmShareHint':
+ '{count} φωτογραφίες θα είναι ορατές σε όλα τα μέλη αυτού του ταξιδιού. Μπορείτε να κάνετε μεμονωμένες φωτογραφίες ιδιωτικές αργότερα.',
+ 'memories.confirmShareButton': 'Κοινή χρήση φωτογραφιών',
+ 'memories.error.loadAlbums': 'Αποτυχία φόρτωσης άλμπουμ',
+ 'memories.error.linkAlbum': 'Αποτυχία σύνδεσης άλμπουμ',
+ 'memories.error.unlinkAlbum': 'Αποτυχία αποσύνδεσης άλμπουμ',
+ 'memories.error.syncAlbum': 'Αποτυχία συγχρονισμού άλμπουμ',
+ 'memories.error.loadPhotos': 'Αποτυχία φόρτωσης φωτογραφιών',
+ 'memories.error.addPhotos': 'Αποτυχία προσθήκης φωτογραφιών',
+ 'memories.error.removePhoto': 'Αποτυχία αφαίρεσης φωτογραφίας',
+ 'memories.error.toggleSharing': 'Αποτυχία ενημέρωσης κοινής χρήσης',
+ 'memories.saveRouteNotConfigured':
+ 'Η διαδρομή αποθήκευσης δεν έχει ρυθμιστεί για αυτόν τον πάροχο',
+ 'memories.testRouteNotConfigured':
+ 'Η διαδρομή δοκιμής δεν έχει ρυθμιστεί για αυτόν τον πάροχο',
+ 'memories.fillRequiredFields':
+ 'Παρακαλώ συμπληρώστε όλα τα απαιτούμενα πεδία',
+};
+export default memories;
diff --git a/shared/src/i18n/gr/nav.ts b/shared/src/i18n/gr/nav.ts
new file mode 100644
index 0000000000000000000000000000000000000000..03eff074395868b073e09233233fbcb6185dbf0a
--- /dev/null
+++ b/shared/src/i18n/gr/nav.ts
@@ -0,0 +1,20 @@
+import type { TranslationStrings } from '../types';
+
+const nav: TranslationStrings = {
+ 'nav.trip': 'Ταξίδι',
+ 'nav.share': 'Διαμοιρασμός',
+ 'nav.settings': 'Ρυθμίσεις',
+ 'nav.admin': 'Διαχείριση',
+ 'nav.logout': 'Αποσύνδεση',
+ 'nav.lightMode': 'Light mode',
+ 'nav.darkMode': 'Dark mode',
+ 'nav.autoMode': 'Auto mode',
+ 'nav.administrator': 'Διαχειριστής',
+ 'nav.myTrips': 'Τα Ταξίδια μου',
+ 'nav.profile': 'Προφίλ',
+ 'nav.bottomSettings': 'Ρυθμίσεις',
+ 'nav.bottomAdmin': 'Ρυθμίσεις Διαχειριστή',
+ 'nav.bottomLogout': 'Αποσύνδεση',
+ 'nav.bottomAdminBadge': 'Διαχειριστής',
+};
+export default nav;
diff --git a/shared/src/i18n/gr/notif.ts b/shared/src/i18n/gr/notif.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f22f0203521509ce0f106eefa1f184c16d1226ad
--- /dev/null
+++ b/shared/src/i18n/gr/notif.ts
@@ -0,0 +1,45 @@
+import type { TranslationStrings } from '../types';
+
+const notif: TranslationStrings = {
+ 'notif.test.title': '[Δοκιμή] Ειδοποίηση',
+ 'notif.test.simple.text': 'Αυτή είναι μια απλή δοκιμαστική ειδοποίηση.',
+ 'notif.test.boolean.text': 'Αποδέχεστε αυτή τη δοκιμαστική ειδοποίηση;',
+ 'notif.test.navigate.text':
+ 'Κάντε κλικ παρακάτω για μετάβαση στον πίνακα ελέγχου.',
+ 'notif.trip_invite.title': 'Πρόσκληση Ταξιδιού',
+ 'notif.trip_invite.text': 'Ο/Η {actor} σας προσκάλεσε στο {trip}',
+ 'notif.booking_change.title': 'Η Κράτηση Ενημερώθηκε',
+ 'notif.booking_change.text': 'Ο/Η {actor} ενημέρωσε μια κράτηση στο {trip}',
+ 'notif.trip_reminder.title': 'Υπενθύμιση Ταξιδιού',
+ 'notif.trip_reminder.text': 'Το ταξίδι σας {trip} πλησιάζει!',
+ 'notif.todo_due.title': 'Εργασία προς λήξη',
+ 'notif.todo_due.text': 'Η εργασία {todo} στο {trip} λήγει στις {due}',
+ 'notif.vacay_invite.title': 'Πρόσκληση Σύντηξης Vacay',
+ 'notif.vacay_invite.text':
+ 'Ο/Η {actor} σας προσκάλεσε να συγχωνεύσετε τα σχέδια διακοπών',
+ 'notif.photos_shared.title': 'Κοινοποίηση Φωτογραφιών',
+ 'notif.photos_shared.text':
+ 'Ο/Η {actor} μοιράστηκε {count} φωτογραφία(ες) στο {trip}',
+ 'notif.collab_message.title': 'Νέο Μήνυμα',
+ 'notif.collab_message.text': 'Ο/Η {actor} έστειλε ένα μήνυμα στο {trip}',
+ 'notif.packing_tagged.title': 'Ανάθεση Αποσκευών',
+ 'notif.packing_tagged.text':
+ 'Ο/Η {actor} σας ανέθεσε στο {category} του {trip}',
+ 'notif.version_available.title': 'Νέα Έκδοση Διαθέσιμη',
+ 'notif.version_available.text': 'Το TREK {version} είναι τώρα διαθέσιμο',
+ 'notif.action.view_trip': 'Προβολή Ταξιδιού',
+ 'notif.action.view_collab': 'Προβολή Μηνυμάτων',
+ 'notif.action.view_packing': 'Προβολή Αποσκευών',
+ 'notif.action.view_photos': 'Προβολή Φωτογραφιών',
+ 'notif.action.view_vacay': 'Προβολή Vacay',
+ 'notif.action.view_admin': 'Μετάβαση στη Διαχείριση',
+ 'notif.action.view': 'Προβολή',
+ 'notif.action.accept': 'Αποδοχή',
+ 'notif.action.decline': 'Απόρριψη',
+ 'notif.generic.title': 'Ειδοποίηση',
+ 'notif.generic.text': 'Έχετε μια νέα ειδοποίηση',
+ 'notif.dev.unknown_event.title': '[DEV] Άγνωστο Συμβάν',
+ 'notif.dev.unknown_event.text':
+ 'Ο τύπος συμβάντος "{event}" δεν είναι εγγεγραμμένος στο EVENT_NOTIFICATION_CONFIG',
+};
+export default notif;
diff --git a/shared/src/i18n/gr/notifications.ts b/shared/src/i18n/gr/notifications.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3379d5067dd5f7867e6632919b19b3d6cdc0a7c8
--- /dev/null
+++ b/shared/src/i18n/gr/notifications.ts
@@ -0,0 +1,42 @@
+import type { TranslationStrings } from '../types';
+
+const notifications: TranslationStrings = {
+ 'notifications.title': 'Ειδοποιήσεις',
+ 'notifications.markAllRead': 'Σήμανση όλων ως αναγνωσμένα',
+ 'notifications.deleteAll': 'Διαγραφή όλων',
+ 'notifications.showAll': 'Εμφάνιση όλων των ειδοποιήσεων',
+ 'notifications.empty': 'Καμία ειδοποίηση',
+ 'notifications.emptyDescription': 'Είστε ενημερωμένοι!',
+ 'notifications.all': 'Όλες',
+ 'notifications.unreadOnly': 'Μη αναγνωσμένες',
+ 'notifications.markRead': 'Σήμανση ως αναγνωσμένη',
+ 'notifications.markUnread': 'Σήμανση ως μη αναγνωσμένη',
+ 'notifications.delete': 'Διαγραφή',
+ 'notifications.system': 'Σύστημα',
+ 'notifications.synologySessionCleared.title':
+ 'Το Synology Photos αποσυνδέθηκε',
+ 'notifications.synologySessionCleared.text':
+ 'Ο διακομιστής ή ο λογαριασμός σας άλλαξε — μεταβείτε στις Ρυθμίσεις για να δοκιμάσετε ξανά τη σύνδεσή σας.',
+ 'notifications.versionAvailable.title': 'Διαθέσιμη Ενημέρωση',
+ 'notifications.versionAvailable.text':
+ 'Το TREK {version} είναι τώρα διαθέσιμο.',
+ 'notifications.versionAvailable.button': 'Προβολή Λεπτομερειών',
+ 'notifications.test.title': 'Δοκιμαστική ειδοποίηση από {actor}',
+ 'notifications.test.text': 'Αυτή είναι μια απλή δοκιμαστική ειδοποίηση.',
+ 'notifications.test.booleanTitle': 'Ο {actor} ζητά την έγκρισή σας',
+ 'notifications.test.booleanText':
+ 'Αυτή είναι μια δοκιμαστική ειδοποίηση boolean. Επιλέξτε μια ενέργεια παρακάτω.',
+ '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 notifications;
diff --git a/shared/src/i18n/gr/oauth.ts b/shared/src/i18n/gr/oauth.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4b6ed2c444b26ffe338b6a1e13592007aa1bb3ff
--- /dev/null
+++ b/shared/src/i18n/gr/oauth.ts
@@ -0,0 +1,123 @@
+import type { TranslationStrings } from '../types';
+
+const oauth: TranslationStrings = {
+ 'oauth.scope.group.trips': 'Ταξίδια',
+ 'oauth.scope.group.places': 'Τοποθεσίες',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'Αποσκευές',
+ 'oauth.scope.group.todos': 'Εργασίες',
+ 'oauth.scope.group.budget': 'Προϋπολογισμός',
+ 'oauth.scope.group.reservations': 'Κρατήσεις',
+ 'oauth.scope.group.collab': 'Συνεργασία',
+ 'oauth.scope.group.notifications': 'Ειδοποιήσεις',
+ 'oauth.scope.group.vacay': 'Διακοπές',
+ 'oauth.scope.group.geo': 'Γεωγραφικά',
+ 'oauth.scope.group.weather': 'Καιρός',
+ 'oauth.scope.group.journey': 'Ταξίδι',
+ 'oauth.scope.trips:read.label': 'Προβολή ταξιδιών & δρομολογίων',
+ 'oauth.scope.trips:read.description':
+ 'Ανάγνωση ταξιδιών, ημερών, σημειώσεων και μελών',
+ 'oauth.scope.trips:write.label': 'Επεξεργασία ταξιδιών & δρομολογίων',
+ 'oauth.scope.trips:write.description':
+ 'Δημιουργία και ενημέρωση ταξιδιών, ημερών, σημειώσεων και διαχείριση μελών',
+ 'oauth.scope.trips:delete.label': 'Διαγραφή ταξιδιών',
+ 'oauth.scope.trips:delete.description':
+ 'Οριστική διαγραφή ολόκληρων ταξιδιών — αυτή η ενέργεια είναι μη ανατρέψιμη',
+ 'oauth.scope.trips:share.label': 'Διαχείριση συνδέσμων κοινής χρήσης',
+ 'oauth.scope.trips:share.description':
+ 'Δημιουργία, ενημέρωση και ανάκληση δημόσιων συνδέσμων κοινής χρήσης για ταξίδια',
+ 'oauth.scope.places:read.label': 'Προβολή τοποθεσιών & δεδομένων χάρτη',
+ 'oauth.scope.places:read.description':
+ 'Ανάγνωση τοποθεσιών, αναθέσεων ημερών, ετικετών και κατηγοριών',
+ 'oauth.scope.places:write.label': 'Διαχείριση τοποθεσιών',
+ 'oauth.scope.places:write.description':
+ 'Δημιουργία, ενημέρωση και διαγραφή τοποθεσιών, αναθέσεων και ετικετών',
+ 'oauth.scope.atlas:read.label': 'Προβολή Atlas',
+ 'oauth.scope.atlas:read.description':
+ 'Ανάγνωση περιοχών που έχουν επισκεφθεί, χωρών και λίστας επιθυμιών',
+ 'oauth.scope.atlas:write.label': 'Διαχείριση Atlas',
+ 'oauth.scope.atlas:write.description':
+ 'Σήμανση χωρών και περιοχών ως επισκεφθεισών, διαχείριση λίστας επιθυμιών',
+ 'oauth.scope.packing:read.label': 'Προβολή λιστών αποσκευών',
+ 'oauth.scope.packing:read.description':
+ 'Ανάγνωση αντικειμένων αποσκευών, τσαντών και αναθέσεων κατηγοριών',
+ 'oauth.scope.packing:write.label': 'Διαχείριση λιστών αποσκευών',
+ 'oauth.scope.packing:write.description':
+ 'Προσθήκη, ενημέρωση, διαγραφή, εναλλαγή και αναδιάταξη αντικειμένων και τσαντών',
+ 'oauth.scope.todos:read.label': 'Προβολή λιστών εργασιών',
+ 'oauth.scope.todos:read.description':
+ 'Ανάγνωση εργασιών ταξιδιού και αναθέσεων κατηγοριών',
+ 'oauth.scope.todos:write.label': 'Διαχείριση λιστών εργασιών',
+ 'oauth.scope.todos:write.description':
+ 'Δημιουργία, ενημέρωση, εναλλαγή, διαγραφή και αναδιάταξη εργασιών',
+ 'oauth.scope.budget:read.label': 'Προβολή προϋπολογισμού',
+ 'oauth.scope.budget:read.description':
+ 'Ανάγνωση στοιχείων προϋπολογισμού και ανάλυσης εξόδων',
+ 'oauth.scope.budget:write.label': 'Διαχείριση προϋπολογισμού',
+ 'oauth.scope.budget:write.description':
+ 'Δημιουργία, ενημέρωση και διαγραφή στοιχείων προϋπολογισμού',
+ 'oauth.scope.reservations:read.label': 'Προβολή κρατήσεων',
+ 'oauth.scope.reservations:read.description':
+ 'Ανάγνωση κρατήσεων και λεπτομερειών διαμονής',
+ 'oauth.scope.reservations:write.label': 'Διαχείριση κρατήσεων',
+ 'oauth.scope.reservations:write.description':
+ 'Δημιουργία, ενημέρωση, διαγραφή και αναδιάταξη κρατήσεων',
+ 'oauth.scope.collab:read.label': 'Προβολή συνεργασίας',
+ 'oauth.scope.collab:read.description':
+ 'Ανάγνωση σημειώσεων, δημοσκοπήσεων και μηνυμάτων συνεργασίας',
+ 'oauth.scope.collab:write.label': 'Διαχείριση συνεργασίας',
+ 'oauth.scope.collab:write.description':
+ 'Δημιουργία, ενημέρωση και διαγραφή σημειώσεων, δημοσκοπήσεων και μηνυμάτων συνεργασίας',
+ 'oauth.scope.notifications:read.label': 'Προβολή ειδοποιήσεων',
+ 'oauth.scope.notifications:read.description':
+ 'Ανάγνωση ειδοποιήσεων εντός εφαρμογής και μετρητών μη αναγνωσμένων',
+ 'oauth.scope.notifications:write.label': 'Διαχείριση ειδοποιήσεων',
+ 'oauth.scope.notifications:write.description':
+ 'Σήμανση ειδοποιήσεων ως αναγνωσμένων και απάντηση σε αυτές',
+ 'oauth.scope.vacay:read.label': 'Προβολή πλάνων διακοπών',
+ 'oauth.scope.vacay:read.description':
+ 'Ανάγνωση δεδομένων σχεδιασμού διακοπών, καταχωρήσεων και στατιστικών',
+ 'oauth.scope.vacay:write.label': 'Διαχείριση πλάνων διακοπών',
+ 'oauth.scope.vacay:write.description':
+ 'Δημιουργία και διαχείριση καταχωρήσεων διακοπών, αργιών και κοινών πλάνων',
+ 'oauth.scope.geo:read.label': 'Χάρτες & γεωκωδικοποίηση',
+ 'oauth.scope.geo:read.description':
+ 'Αναζήτηση τοποθεσιών, επίλυση URL χαρτών και αντίστροφη γεωκωδικοποίηση συντεταγμένων',
+ 'oauth.scope.weather:read.label': 'Προγνώσεις καιρού',
+ 'oauth.scope.weather:read.description':
+ 'Λήψη προγνώσεων καιρού για τοποθεσίες και ημερομηνίες ταξιδιών',
+ 'oauth.scope.journey:read.label': 'Προβολή ταξιδιών',
+ 'oauth.scope.journey:read.description':
+ 'Ανάγνωση ταξιδιών, καταχωρήσεων και λίστας συνεισφερόντων',
+ 'oauth.scope.journey:write.label': 'Διαχείριση ταξιδιών',
+ 'oauth.scope.journey:write.description':
+ 'Δημιουργία, ενημέρωση και διαγραφή ταξιδιών και των καταχωρήσεων τους',
+ 'oauth.scope.journey:share.label': 'Διαχείριση συνδέσμων ταξιδιών',
+ 'oauth.scope.journey:share.description':
+ 'Δημιουργία, ενημέρωση και ανάκληση δημόσιων συνδέσμων κοινής χρήσης για ταξίδια',
+ 'oauth.authorize.authorizing': 'Authorizing…', // en-fallback
+ 'oauth.authorize.loading': 'Loading…', // en-fallback
+ 'oauth.authorize.errorTitle': 'Authorization Error', // en-fallback
+ 'oauth.authorize.loginTitle': 'Sign in to continue', // en-fallback
+ 'oauth.authorize.loginDescription':
+ '{client} wants access to your TREK account. Please sign in first.', // en-fallback
+ 'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
+ 'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
+ 'oauth.authorize.requestDescription':
+ 'This application is requesting access to your TREK account.', // en-fallback
+ 'oauth.authorize.trustNote':
+ 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
+ 'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
+ 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
+ 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
+ 'oauth.authorize.deny': 'Deny', // en-fallback
+ 'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
+ 'oauth.authorize.permissionsRequested': 'Permissions requested', // en-fallback
+ 'oauth.authorize.alwaysIncluded': 'Always included', // en-fallback
+ 'oauth.authorize.alwaysTool.listTrips':
+ 'List your trips so the AI can discover trip IDs', // en-fallback
+ 'oauth.authorize.alwaysTool.getTripSummary':
+ 'Read a trip overview needed to use any other tool', // en-fallback
+};
+export default oauth;
diff --git a/shared/src/i18n/gr/packing.ts b/shared/src/i18n/gr/packing.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5bf53bdf888af281ffce1fd693da3a72a18c81c2
--- /dev/null
+++ b/shared/src/i18n/gr/packing.ts
@@ -0,0 +1,96 @@
+import type { TranslationStrings } from '../types';
+
+const packing: TranslationStrings = {
+ 'packing.title': 'Λίστα Αποσκευών',
+ 'packing.empty': 'Η λίστα αποσκευών είναι κενή',
+ 'packing.import': 'Εισαγωγή',
+ 'packing.importTitle': 'Εισαγωγή Λίστας Αποσκευών',
+ 'packing.importHint':
+ 'Ένα αντικείμενο ανά γραμμή. Μορφή: Κατηγορία, Όνομα, Βάρος σε g (προαιρετικό), Τσάντα (προαιρετικό), checked/unchecked (προαιρετικό)',
+ 'packing.importPlaceholder':
+ 'Υγιεινή, Οδοντόβουρτσα\nΡούχα, T-Shirts, 200\nΈγγραφα, Διαβατήριο, , Χειραποσκευή\nΗλεκτρονικά, Φορτιστής, 50, Βαλίτσα, checked',
+ 'packing.importCsv': 'Φόρτωση CSV/TXT',
+ 'packing.importAction': 'Εισαγωγή {count}',
+ 'packing.importSuccess': '{count} αντικείμενα εισήχθησαν',
+ 'packing.importError': 'Η εισαγωγή απέτυχε',
+ 'packing.importEmpty': 'Δεν υπάρχουν αντικείμενα προς εισαγωγή',
+ 'packing.progress': '{packed} από {total} συσκευασμένα ({percent}%)',
+ 'packing.clearChecked': 'Αφαίρεση {count} επιλεγμένων',
+ 'packing.clearCheckedShort': 'Αφαίρεση {count}',
+ 'packing.suggestions': 'Προτάσεις',
+ 'packing.suggestionsTitle': 'Προσθήκη Προτάσεων',
+ 'packing.allSuggested': 'Όλες οι προτάσεις προστέθηκαν',
+ 'packing.allPacked': 'Όλα έτοιμα!',
+ 'packing.addPlaceholder': 'Προσθήκη νέου αντικειμένου...',
+ 'packing.categoryPlaceholder': 'Κατηγορία...',
+ 'packing.filterAll': 'Όλα',
+ 'packing.filterOpen': 'Ανοιχτά',
+ 'packing.filterDone': 'Ολοκληρωμένα',
+ 'packing.emptyTitle': 'Η λίστα αποσκευών είναι κενή',
+ 'packing.emptyHint': 'Προσθέστε αντικείμενα ή χρησιμοποιήστε τις προτάσεις',
+ 'packing.emptyFiltered': 'Κανένα αντικείμενο δεν ταιριάζει με αυτό το φίλτρο',
+ 'packing.menuRename': 'Μετονομασία',
+ 'packing.menuCheckAll': 'Επιλογή Όλων',
+ 'packing.menuUncheckAll': 'Αποεπιλογή Όλων',
+ 'packing.menuDeleteCat': 'Διαγραφή Κατηγορίας',
+ 'packing.noMembers': 'Δεν υπάρχουν μέλη ταξιδιού',
+ 'packing.addItem': 'Προσθήκη αντικειμένου',
+ 'packing.addItemPlaceholder': 'Όνομα αντικειμένου...',
+ 'packing.addCategory': 'Προσθήκη κατηγορίας',
+ 'packing.newCategoryPlaceholder': 'Όνομα κατηγορίας (π.χ. Ρούχα)',
+ 'packing.applyTemplate': 'Εφαρμογή προτύπου',
+ 'packing.template': 'Πρότυπο',
+ 'packing.templateApplied': '{count} αντικείμενα προστέθηκαν από το πρότυπο',
+ 'packing.templateError': 'Αποτυχία εφαρμογής προτύπου',
+ 'packing.saveAsTemplate': 'Αποθήκευση ως πρότυπο',
+ 'packing.templateName': 'Όνομα προτύπου',
+ 'packing.templateSaved': 'Η λίστα αποσκευών αποθηκεύτηκε ως πρότυπο',
+ 'packing.bags': 'Τσάντες',
+ 'packing.noBag': 'Χωρίς ανάθεση',
+ 'packing.totalWeight': 'Συνολικό βάρος',
+ 'packing.bagName': 'Όνομα τσάντας...',
+ 'packing.addBag': 'Προσθήκη τσάντας',
+ 'packing.changeCategory': 'Αλλαγή Κατηγορίας',
+ 'packing.confirm.clearChecked':
+ 'Είστε σίγουροι ότι θέλετε να αφαιρέσετε {count} επιλεγμένα αντικείμενα;',
+ 'packing.confirm.deleteCat':
+ 'Είστε σίγουροι ότι θέλετε να διαγράψετε την κατηγορία "{name}" με {count} αντικείμενα;',
+ 'packing.defaultCategory': 'Άλλα',
+ 'packing.toast.saveError': 'Αποτυχία αποθήκευσης',
+ 'packing.toast.deleteError': 'Αποτυχία διαγραφής',
+ 'packing.toast.renameError': 'Αποτυχία μετονομασίας',
+ 'packing.toast.addError': 'Αποτυχία προσθήκης',
+ 'packing.suggestions.items': [
+ { name: 'Διαβατήριο', category: 'Έγγραφα' },
+ { name: 'Ταυτότητα', category: 'Έγγραφα' },
+ { name: 'Ταξιδιωτική Ασφάλιση', category: 'Έγγραφα' },
+ { name: 'Αεροπορικά Εισιτήρια', category: 'Έγγραφα' },
+ { name: 'Πιστωτική Κάρτα', category: 'Οικονομικά' },
+ { name: 'Μετρητά', category: 'Οικονομικά' },
+ { name: 'Visa', category: 'Έγγραφα' },
+ { name: 'T-Shirts', category: 'Ρούχα' },
+ { name: 'Παντελόνια', category: 'Ρούχα' },
+ { name: 'Εσώρουχα', category: 'Ρούχα' },
+ { name: 'Κάλτσες', category: 'Ρούχα' },
+ { name: 'Μπουφάν', category: 'Ρούχα' },
+ { name: 'Πιτζάμες', category: 'Ρούχα' },
+ { name: 'Μαγιό', category: 'Ρούχα' },
+ { name: 'Αδιάβροχο', category: 'Ρούχα' },
+ { name: 'Άνετα Παπούτσια', category: 'Ρούχα' },
+ { name: 'Οδοντόβουρτσα', category: 'Είδη Υγιεινής' },
+ { name: 'Οδοντόκρεμα', category: 'Είδη Υγιεινής' },
+ { name: 'Σαμπουάν', category: 'Είδη Υγιεινής' },
+ { name: 'Αποσμητικό', category: 'Είδη Υγιεινής' },
+ { name: 'Αντηλιακό', category: 'Είδη Υγιεινής' },
+ { name: 'Ξυριστική Μηχανή', category: 'Είδη Υγιεινής' },
+ { name: 'Φορτιστής', category: 'Ηλεκτρονικά' },
+ { name: 'Power Bank', category: 'Ηλεκτρονικά' },
+ { name: 'Ακουστικά', category: 'Ηλεκτρονικά' },
+ { name: 'Ταξιδιωτικός Αντάπτορας', category: 'Ηλεκτρονικά' },
+ { name: 'Κάμερα', category: 'Ηλεκτρονικά' },
+ { name: 'Παυσίπονα', category: 'Υγεία' },
+ { name: 'Τσιρότα', category: 'Υγεία' },
+ { name: 'Απολυμαντικό', category: 'Υγεία' },
+ ],
+};
+export default packing;
diff --git a/shared/src/i18n/gr/pdf.ts b/shared/src/i18n/gr/pdf.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8915c19962776ffb474401389057bce6e39d1504
--- /dev/null
+++ b/shared/src/i18n/gr/pdf.ts
@@ -0,0 +1,10 @@
+import type { TranslationStrings } from '../types';
+
+const pdf: TranslationStrings = {
+ 'pdf.travelPlan': 'Ταξιδιωτικό Πρόγραμμα',
+ 'pdf.planned': 'Προγραμματισμένο',
+ 'pdf.costLabel': 'Κόστος EUR',
+ 'pdf.preview': 'Προεπισκόπηση PDF',
+ 'pdf.saveAsPdf': 'Αποθήκευση ως PDF',
+};
+export default pdf;
diff --git a/shared/src/i18n/gr/perm.ts b/shared/src/i18n/gr/perm.ts
new file mode 100644
index 0000000000000000000000000000000000000000..715e190c7fb1d4e0d938e324f01a9a6f17aff483
--- /dev/null
+++ b/shared/src/i18n/gr/perm.ts
@@ -0,0 +1,68 @@
+import type { TranslationStrings } from '../types';
+
+const perm: TranslationStrings = {
+ 'perm.title': 'Ρυθμίσεις Δικαιωμάτων',
+ 'perm.subtitle':
+ 'Ελέγξτε ποιος μπορεί να εκτελεί ενέργειες σε όλη την εφαρμογή',
+ 'perm.saved': 'Οι ρυθμίσεις δικαιωμάτων αποθηκεύτηκαν',
+ 'perm.resetDefaults': 'Επαναφορά προεπιλογών',
+ 'perm.customized': 'προσαρμοσμένο',
+ 'perm.level.admin': 'Μόνο διαχειριστής',
+ 'perm.level.tripOwner': 'Ιδιοκτήτης ταξιδιού',
+ 'perm.level.tripMember': 'Μέλη ταξιδιού',
+ 'perm.level.everybody': 'Όλοι',
+ 'perm.cat.trip': 'Διαχείριση Ταξιδιού',
+ 'perm.cat.members': 'Διαχείριση Μελών',
+ 'perm.cat.files': 'Αρχεία',
+ 'perm.cat.content': 'Περιεχόμενο & Πρόγραμμα',
+ 'perm.cat.extras': 'Προϋπολογισμός, Αποσκευές & Συνεργασία',
+ 'perm.action.trip_create': 'Δημιουργία ταξιδιών',
+ 'perm.action.trip_edit': 'Επεξεργασία λεπτομερειών ταξιδιού',
+ 'perm.action.trip_delete': 'Διαγραφή ταξιδιών',
+ 'perm.action.trip_archive': 'Αρχειοθέτηση / επαναφορά ταξιδιών',
+ 'perm.action.trip_cover_upload': 'Μεταφόρτωση εικόνας εξωφύλλου',
+ 'perm.action.member_manage': 'Προσθήκη / αφαίρεση μελών',
+ 'perm.action.file_upload': 'Μεταφόρτωση αρχείων',
+ 'perm.action.file_edit': 'Επεξεργασία μεταδεδομένων αρχείου',
+ 'perm.action.file_delete': 'Διαγραφή αρχείων',
+ 'perm.action.place_edit': 'Προσθήκη / επεξεργασία / διαγραφή τοποθεσιών',
+ 'perm.action.day_edit': 'Επεξεργασία ημερών, σημειώσεων & αναθέσεων',
+ 'perm.action.reservation_edit': 'Διαχείριση κρατήσεων',
+ 'perm.action.budget_edit': 'Διαχείριση προϋπολογισμού',
+ 'perm.action.packing_edit': 'Διαχείριση λιστών αποσκευών',
+ 'perm.action.collab_edit':
+ 'Συνεργασία (σημειώσεις, δημοσκοπήσεις, συνομιλία)',
+ 'perm.action.share_manage': 'Διαχείριση συνδέσμων κοινής χρήσης',
+ 'perm.actionHint.trip_create': 'Ποιος μπορεί να δημιουργεί νέα ταξίδια',
+ 'perm.actionHint.trip_edit':
+ 'Ποιος μπορεί να αλλάζει όνομα, ημερομηνίες, περιγραφή και νόμισμα ταξιδιού',
+ 'perm.actionHint.trip_delete':
+ 'Ποιος μπορεί να διαγράφει οριστικά ένα ταξίδι',
+ 'perm.actionHint.trip_archive':
+ 'Ποιος μπορεί να αρχειοθετεί ή να επαναφέρει ένα ταξίδι',
+ 'perm.actionHint.trip_cover_upload':
+ 'Ποιος μπορεί να μεταφορτώνει ή να αλλάζει την εικόνα εξωφύλλου',
+ 'perm.actionHint.member_manage':
+ 'Ποιος μπορεί να προσκαλεί ή να αφαιρεί μέλη ταξιδιού',
+ 'perm.actionHint.file_upload':
+ 'Ποιος μπορεί να μεταφορτώνει αρχεία σε ένα ταξίδι',
+ 'perm.actionHint.file_edit':
+ 'Ποιος μπορεί να επεξεργάζεται περιγραφές και συνδέσμους αρχείων',
+ 'perm.actionHint.file_delete':
+ 'Ποιος μπορεί να μετακινεί αρχεία στον κάδο ή να τα διαγράφει οριστικά',
+ 'perm.actionHint.place_edit':
+ 'Ποιος μπορεί να προσθέτει, να επεξεργάζεται ή να διαγράφει τοποθεσίες',
+ 'perm.actionHint.day_edit':
+ 'Ποιος μπορεί να επεξεργάζεται ημέρες, σημειώσεις ημέρας και αναθέσεις τοποθεσιών',
+ 'perm.actionHint.reservation_edit':
+ 'Ποιος μπορεί να δημιουργεί, να επεξεργάζεται ή να διαγράφει κρατήσεις',
+ 'perm.actionHint.budget_edit':
+ 'Ποιος μπορεί να δημιουργεί, να επεξεργάζεται ή να διαγράφει στοιχεία προϋπολογισμού',
+ 'perm.actionHint.packing_edit':
+ 'Ποιος μπορεί να διαχειρίζεται αντικείμενα και τσάντες αποσκευών',
+ 'perm.actionHint.collab_edit':
+ 'Ποιος μπορεί να δημιουργεί σημειώσεις, δημοσκοπήσεις και να στέλνει μηνύματα',
+ 'perm.actionHint.share_manage':
+ 'Ποιος μπορεί να δημιουργεί ή να διαγράφει δημόσιους συνδέσμους κοινής χρήσης',
+};
+export default perm;
diff --git a/shared/src/i18n/gr/photos.ts b/shared/src/i18n/gr/photos.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8a65035b5115efa73998ad4c0fab2d70f3eb5853
--- /dev/null
+++ b/shared/src/i18n/gr/photos.ts
@@ -0,0 +1,25 @@
+import type { TranslationStrings } from '../types';
+
+const photos: TranslationStrings = {
+ 'photos.title': 'Φωτογραφίες',
+ 'photos.subtitle': '{count} φωτογραφίες για {trip}',
+ 'photos.dropHere': 'Αποθέστε φωτογραφίες εδώ...',
+ 'photos.dropHereActive': 'Αποθέστε φωτογραφίες εδώ',
+ 'photos.captionForAll': 'Λεζάντα (για όλες)',
+ 'photos.captionPlaceholder': 'Προαιρετική λεζάντα...',
+ 'photos.addCaption': 'Προσθήκη λεζάντας...',
+ 'photos.allDays': 'Όλες οι Ημέρες',
+ 'photos.noPhotos': 'Δεν υπάρχουν φωτογραφίες ακόμη',
+ 'photos.uploadHint': 'Μεταφορτώστε τις ταξιδιωτικές σας φωτογραφίες',
+ 'photos.clickToSelect': 'ή κάντε κλικ για επιλογή',
+ 'photos.linkPlace': 'Σύνδεση Τοποθεσίας',
+ 'photos.noPlace': 'Καμία Τοποθεσία',
+ 'photos.uploadN': 'Μεταφόρτωση {n} φωτογραφιών',
+ 'photos.linkDay': 'Σύνδεση Ημέρας',
+ 'photos.noDay': 'Καμία Ημέρα',
+ 'photos.dayLabel': 'Ημέρα {number}',
+ 'photos.photoSelected': 'Επιλέχθηκε φωτογραφία',
+ 'photos.photosSelected': 'Επιλέχθηκαν φωτογραφίες',
+ 'photos.fileTypeHint': 'JPG, PNG, WebP · μέγ. 10 MB · έως 30 φωτογραφίες',
+};
+export default photos;
diff --git a/shared/src/i18n/gr/places.ts b/shared/src/i18n/gr/places.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8da867eb7253ef998504c2562a1d645217b4c20b
--- /dev/null
+++ b/shared/src/i18n/gr/places.ts
@@ -0,0 +1,99 @@
+import type { TranslationStrings } from '../types';
+
+const places: TranslationStrings = {
+ 'places.addPlace': 'Προσθήκη Μέρους/Δραστηριότητας',
+ 'places.importFile': 'Εισαγωγή αρχείου',
+ 'places.sidebarDrop': 'Αφήστε για εισαγωγή',
+ 'places.importFileHint':
+ 'Εισαγωγή αρχείων .gpx, .kml ή .kmz από εργαλεία όπως Google My Maps, Google Earth ή GPS tracker.',
+ 'places.importFileDropHere':
+ 'Κάντε κλικ για να επιλέξετε αρχείο ή σύρετε εδώ',
+ 'places.importFileDropActive': 'Αφήστε το αρχείο για επιλογή',
+ 'places.importFileUnsupported':
+ 'Μη υποστηριζόμενος τύπος αρχείου. Χρησιμοποιήστε .gpx, .kml ή .kmz.',
+ 'places.importFileTooLarge':
+ 'Το αρχείο είναι πολύ μεγάλο. Το μέγιστο μέγεθος μεταφόρτωσης είναι {maxMb} MB.',
+ 'places.importFileError': 'Η εισαγωγή απέτυχε',
+ 'places.importAllSkipped': 'Όλα τα μέρη υπήρχαν ήδη στο ταξίδι.',
+ 'places.gpxImported': '{count} μέρη εισήχθησαν από GPX',
+ 'places.gpxImportTypes': 'Τι θέλετε να εισάγετε;',
+ 'places.gpxImportWaypoints': 'Σημεία διαδρομής',
+ 'places.gpxImportRoutes': 'Διαδρομές',
+ 'places.gpxImportTracks': 'Ίχνη (με γεωμετρία διαδρομής)',
+ 'places.gpxImportNoneSelected':
+ 'Επιλέξτε τουλάχιστον έναν τύπο για εισαγωγή.',
+ 'places.kmlImportTypes': 'Τι θέλετε να εισάγετε;',
+ 'places.kmlImportPoints': 'Σημεία (Placemarks)',
+ 'places.kmlImportPaths': 'Διαδρομές (LineStrings)',
+ 'places.kmlImportNoneSelected':
+ 'Επιλέξτε τουλάχιστον έναν τύπο για εισαγωγή.',
+ 'places.selectionCount': '{count} επιλεγμένα',
+ 'places.deleteSelected': 'Διαγραφή επιλεγμένων',
+ 'places.kmlKmzImported': '{count} μέρη εισήχθησαν από KMZ/KML',
+ 'places.urlResolved': 'Το μέρος εισήχθη από URL',
+ 'places.importList': 'Εισαγωγή Λίστας',
+ 'places.kmlKmzSummaryValues':
+ 'Placemarks: {total} • Εισήχθησαν: {created} • Παραλείφθηκαν: {skipped}',
+ 'places.importGoogleList': 'Λίστα Google',
+ 'places.importNaverList': 'Λίστα Naver',
+ 'places.googleListHint':
+ 'Επικολλήστε έναν κοινοποιημένο σύνδεσμο λίστας Google Maps για να εισάγετε όλα τα μέρη.',
+ 'places.googleListImported': '{count} μέρη εισήχθησαν από "{list}"',
+ 'places.googleListError': 'Αποτυχία εισαγωγής λίστας Google Maps',
+ 'places.naverListHint':
+ 'Επικολλήστε έναν κοινοποιημένο σύνδεσμο λίστας Naver Maps για να εισάγετε όλα τα μέρη.',
+ 'places.naverListImported': '{count} μέρη εισήχθησαν από "{list}"',
+ 'places.naverListError': 'Αποτυχία εισαγωγής λίστας Naver Maps',
+ 'places.viewDetails': 'Προβολή Λεπτομερειών',
+ 'places.assignToDay': 'Σε ποια ημέρα να προστεθεί;',
+ 'places.all': 'Όλα',
+ 'places.unplanned': 'Μη προγραμματισμένα',
+ 'places.filterTracks': 'Ίχνη',
+ 'places.search': 'Αναζήτηση μερών...',
+ 'places.allCategories': 'Όλες οι Κατηγορίες',
+ 'places.categoriesSelected': 'κατηγορίες',
+ 'places.clearFilter': 'Καθαρισμός φίλτρου',
+ 'places.count': '{count} μέρη',
+ 'places.countSingular': '1 μέρος',
+ 'places.allPlanned': 'Όλα τα μέρη είναι προγραμματισμένα',
+ 'places.noneFound': 'Δεν βρέθηκαν μέρη',
+ 'places.editPlace': 'Επεξεργασία Μέρους',
+ 'places.formName': 'Όνομα',
+ 'places.formNamePlaceholder': 'π.χ. Πύργος του Άιφελ',
+ 'places.formDescription': 'Περιγραφή',
+ 'places.formDescriptionPlaceholder': 'Σύντομη περιγραφή...',
+ 'places.formAddress': 'Διεύθυνση',
+ 'places.formAddressPlaceholder': 'Οδός, Πόλη, Χώρα',
+ 'places.formLat': 'Γεωγραφικό Πλάτος (π.χ. 48.8566)',
+ 'places.formLng': 'Γεωγραφικό Μήκος (π.χ. 2.3522)',
+ 'places.formCategory': 'Κατηγορία',
+ 'places.noCategory': 'Χωρίς Κατηγορία',
+ 'places.categoryNamePlaceholder': 'Όνομα κατηγορίας',
+ 'places.formTime': 'Ώρα',
+ 'places.startTime': 'Έναρξη',
+ 'places.endTime': 'Λήξη',
+ 'places.endTimeBeforeStart': 'Η ώρα λήξης είναι πριν την ώρα έναρξης',
+ 'places.timeCollision': 'Επικάλυψη ώρας με:',
+ 'places.formWebsite': 'Ιστοσελίδα',
+ 'places.formNotes': 'Σημειώσεις',
+ 'places.formNotesPlaceholder': 'Προσωπικές σημειώσεις...',
+ 'places.formReservation': 'Κράτηση',
+ 'places.reservationNotesPlaceholder':
+ 'Σημειώσεις κράτησης, αριθμός επιβεβαίωσης...',
+ 'places.mapsSearchPlaceholder': 'Αναζήτηση μερών...',
+ 'places.mapsSearchError': 'Η αναζήτηση μέρους απέτυχε.',
+ 'places.loadingDetails': 'Φόρτωση λεπτομερειών μέρους…',
+ 'places.osmHint':
+ 'Χρήση αναζήτησης OpenStreetMap (χωρίς φωτογραφίες, ώρες λειτουργίας ή αξιολογήσεις). Προσθέστε ένα κλειδί Google API στις ρυθμίσεις για πλήρεις λεπτομέρειες.',
+ 'places.osmActive':
+ 'Αναζήτηση μέσω OpenStreetMap (χωρίς φωτογραφίες, αξιολογήσεις ή ώρες λειτουργίας). Προσθέστε ένα κλειδί Google API στις Ρυθμίσεις για εμπλουτισμένα δεδομένα.',
+ 'places.categoryCreateError': 'Αποτυχία δημιουργίας κατηγορίας',
+ 'places.nameRequired': 'Παρακαλώ εισαγάγετε ένα όνομα',
+ 'places.saveError': 'Αποτυχία αποθήκευσης',
+ 'places.duplicateExists': "Το '{name}' υπάρχει ήδη σε αυτό το ταξίδι.",
+ 'places.addAnyway': 'Προσθήκη ούτως ή άλλως',
+ 'places.enrichOnImport': 'Εμπλουτισμός τόπων μέσω Google',
+ 'places.enrichOnImportHint':
+ 'Αναζητά κάθε εισαγόμενο μέρος για να προσθέσει φωτογραφίες, διεύθυνση και στοιχεία επικοινωνίας. Απαιτεί κλειδί Google Maps.',
+};
+export default places;
diff --git a/shared/src/i18n/gr/planner.ts b/shared/src/i18n/gr/planner.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1b5c4fdb9763ccdcf02af6f4291aaeb6435faae3
--- /dev/null
+++ b/shared/src/i18n/gr/planner.ts
@@ -0,0 +1,69 @@
+import type { TranslationStrings } from '../types';
+
+const planner: TranslationStrings = {
+ 'planner.places': 'Τοποθεσίες',
+ 'planner.bookings': 'Κρατήσεις',
+ 'planner.packingList': 'Λίστα Αποσκευών',
+ 'planner.documents': 'Έγγραφα',
+ 'planner.dayPlan': 'Πρόγραμμα Ημέρας',
+ 'planner.reservations': 'Κρατήσεις',
+ 'planner.minTwoPlaces':
+ 'Απαιτούνται τουλάχιστον 2 τοποθεσίες με συντεταγμένες',
+ 'planner.noGeoPlaces': 'Δεν υπάρχουν διαθέσιμες τοποθεσίες με συντεταγμένες',
+ 'planner.routeCalculated': 'Η διαδρομή υπολογίστηκε',
+ 'planner.routeCalcFailed': 'Δεν ήταν δυνατός ο υπολογισμός της διαδρομής',
+ 'planner.routeError': 'Σφάλμα υπολογισμού διαδρομής',
+ 'planner.icsExportFailed': 'Η εξαγωγή ICS απέτυχε',
+ 'planner.routeOptimized': 'Η διαδρομή βελτιστοποιήθηκε',
+ 'planner.reservationUpdated': 'Η κράτηση ενημερώθηκε',
+ 'planner.reservationAdded': 'Η κράτηση προστέθηκε',
+ 'planner.confirmDeleteReservation': 'Διαγραφή κράτησης;',
+ 'planner.reservationDeleted': 'Η κράτηση διαγράφηκε',
+ 'planner.days': 'Ημέρες',
+ 'planner.allPlaces': 'Όλες οι Τοποθεσίες',
+ 'planner.totalPlaces': '{n} τοποθεσίες συνολικά',
+ 'planner.noDaysPlanned': 'Δεν έχουν προγραμματιστεί ημέρες ακόμη',
+ 'planner.editTrip': 'Επεξεργασία ταξιδιού \u2192',
+ 'planner.placeOne': '1 τοποθεσία',
+ 'planner.placeN': '{n} τοποθεσίες',
+ 'planner.addNote': 'Προσθήκη σημείωσης',
+ 'planner.noEntries': 'Καμία καταχώρηση για αυτή την ημέρα',
+ 'planner.addPlace': 'Προσθήκη τοποθεσίας/δραστηριότητας',
+ 'planner.addPlaceShort': '+ Προσθήκη τοποθεσίας/δραστηριότητας',
+ 'planner.resPending': 'Κράτηση σε εκκρεμότητα · ',
+ 'planner.resConfirmed': 'Κράτηση επιβεβαιωμένη · ',
+ 'planner.notePlaceholder': 'Σημείωση\u2026',
+ 'planner.noteTimePlaceholder': 'Ώρα (προαιρετικό)',
+ 'planner.noteExamplePlaceholder':
+ 'π.χ. S3 στις 14:30 από τον κεντρικό σταθμό, φέρι από την προβλήτα 7, διάλειμμα μεσημεριανού\u2026',
+ 'planner.totalCost': 'Συνολικό κόστος',
+ 'planner.searchPlaces': 'Αναζήτηση τοποθεσιών\u2026',
+ 'planner.allCategories': 'Όλες οι Κατηγορίες',
+ 'planner.noPlacesFound': 'Δεν βρέθηκαν τοποθεσίες',
+ 'planner.addFirstPlace': 'Προσθήκη πρώτης τοποθεσίας',
+ 'planner.noReservations': 'Καμία κράτηση',
+ 'planner.addFirstReservation': 'Προσθήκη πρώτης κράτησης',
+ 'planner.new': 'Νέα',
+ 'planner.addToDay': '+ Ημέρα',
+ 'planner.calculating': 'Υπολογισμός\u2026',
+ 'planner.route': 'Διαδρομή',
+ 'planner.optimize': 'Βελτιστοποίηση',
+ 'planner.openGoogleMaps': 'Άνοιγμα στο Google Maps',
+ 'planner.selectDayHint':
+ 'Επιλέξτε μια ημέρα από την αριστερή λίστα για να δείτε το πρόγραμμα',
+ 'planner.noPlacesForDay': 'Δεν υπάρχουν τοποθεσίες για αυτή την ημέρα ακόμη',
+ 'planner.addPlacesLink': 'Προσθήκη τοποθεσιών \u2192',
+ 'planner.minTotal': 'λεπτά σύνολο',
+ 'planner.noReservation': 'Καμία κράτηση',
+ 'planner.removeFromDay': 'Αφαίρεση από την ημέρα',
+ 'planner.addToThisDay': 'Προσθήκη στην ημέρα',
+ 'planner.overview': 'Επισκόπηση',
+ 'planner.noDays': 'Δεν υπάρχουν ημέρες ακόμη',
+ 'planner.editTripToAddDays': 'Επεξεργαστείτε το ταξίδι για προσθήκη ημερών',
+ 'planner.dayCount': '{n} Ημέρες',
+ 'planner.clickToUnlock': 'Κάντε κλικ για ξεκλείδωμα',
+ 'planner.keepPosition': 'Διατήρηση θέσης κατά τη βελτιστοποίηση διαδρομής',
+ 'planner.dayDetails': 'Λεπτομέρειες ημέρας',
+ 'planner.dayN': 'Ημέρα {n}',
+};
+export default planner;
diff --git a/shared/src/i18n/gr/register.ts b/shared/src/i18n/gr/register.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ee8a6d722cd9651cbb639bd0d09d4d3e85ef7c84
--- /dev/null
+++ b/shared/src/i18n/gr/register.ts
@@ -0,0 +1,27 @@
+import type { TranslationStrings } from '../types';
+
+const register: TranslationStrings = {
+ 'register.passwordMismatch': 'Οι κωδικοί δεν ταιριάζουν',
+ 'register.passwordTooShort':
+ 'Ο κωδικός πρέπει να έχει τουλάχιστον 8 χαρακτήρες',
+ 'register.failed': 'Η εγγραφή απέτυχε',
+ 'register.getStarted': 'Ξεκινήστε',
+ 'register.subtitle':
+ 'Δημιουργήστε έναν λογαριασμό και ξεκινήστε να σχεδιάζετε τα ταξίδια των ονείρων σας.',
+ 'register.feature1': 'Απεριόριστα σχέδια ταξιδιών',
+ 'register.feature2': 'Διαδραστική προβολή χάρτη',
+ 'register.feature3': 'Διαχείριση τόπων και κατηγοριών',
+ 'register.feature4': 'Παρακολούθηση κρατήσεων',
+ 'register.feature5': 'Δημιουργία λιστών πακεταρίσματος',
+ 'register.feature6': 'Αποθήκευση φωτογραφιών και αρχείων',
+ 'register.createAccount': 'Δημιουργία Λογαριασμού',
+ 'register.startPlanning': 'Ξεκινήστε τον σχεδιασμό του ταξιδιού σας',
+ 'register.minChars': 'Ελάχ. 6 χαρακτήρες',
+ 'register.confirmPassword': 'Επιβεβαίωση Κωδικού',
+ 'register.repeatPassword': 'Επανάληψη κωδικού',
+ 'register.registering': 'Εγγραφή...',
+ 'register.register': 'Εγγραφή',
+ 'register.hasAccount': 'Έχετε ήδη λογαριασμό;',
+ 'register.signIn': 'Σύνδεση',
+};
+export default register;
diff --git a/shared/src/i18n/gr/reservations.ts b/shared/src/i18n/gr/reservations.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2f56a769f1dad3e13d019f384eb97f695159f527
--- /dev/null
+++ b/shared/src/i18n/gr/reservations.ts
@@ -0,0 +1,164 @@
+import type { TranslationStrings } from '../types';
+
+const reservations: TranslationStrings = {
+ 'reservations.title': 'Κρατήσεις',
+ 'reservations.empty': 'Δεν υπάρχουν κρατήσεις ακόμη',
+ 'reservations.emptyHint':
+ 'Προσθέστε κρατήσεις για πτήσεις, ξενοδοχεία και άλλα',
+ 'reservations.add': 'Προσθήκη Κράτησης',
+ 'reservations.addManual': 'Χειροκίνητη Κράτηση',
+ 'reservations.placeHint':
+ 'Συμβουλή: Οι κρατήσεις δημιουργούνται καλύτερα απευθείας από ένα μέρος για να συνδεθούν με το ημερήσιο πλάνο σας.',
+ 'reservations.confirmed': 'Επιβεβαιωμένο',
+ 'reservations.pending': 'Εκκρεμές',
+ 'reservations.summary': '{confirmed} επιβεβαιωμένες, {pending} εκκρεμείς',
+ 'reservations.fromPlan': 'Από το Πλάνο',
+ 'reservations.showFiles': 'Εμφάνιση Αρχείων',
+ 'reservations.editTitle': 'Επεξεργασία Κράτησης',
+ 'reservations.status': 'Κατάσταση',
+ 'reservations.datetime': 'Ημερομηνία & Ώρα',
+ 'reservations.startTime': 'Ώρα έναρξης',
+ 'reservations.endTime': 'Ώρα λήξης',
+ 'reservations.date': 'Ημερομηνία',
+ 'reservations.time': 'Ώρα',
+ 'reservations.timeAlt': 'Ώρα (εναλλακτικά, π.χ. 19:30)',
+ 'reservations.notes': 'Σημειώσεις',
+ 'reservations.notesPlaceholder': 'Πρόσθετες σημειώσεις...',
+ 'reservations.meta.airline': 'Αεροπορική Εταιρεία',
+ 'reservations.meta.flightNumber': 'Αρ. Πτήσης',
+ 'reservations.meta.from': 'Από',
+ 'reservations.meta.to': 'Προς',
+ 'reservations.layover.route': 'Διαδρομή',
+ 'reservations.layover.stop': 'Στάση',
+ 'reservations.layover.addStop': 'Προσθήκη στάσης',
+ 'reservations.layover.connection': 'Ανταπόκριση',
+ 'reservations.layover.layover': 'Ενδιάμεση στάση',
+ 'reservations.needsReview': 'Έλεγχος',
+ 'reservations.needsReviewHint':
+ 'Δεν ήταν δυνατή η αυτόματη αντιστοίχιση του αεροδρομίου — παρακαλώ επιβεβαιώστε την τοποθεσία.',
+ 'reservations.searchLocation': 'Αναζήτηση σταθμού, λιμανιού, διεύθυνσης…',
+ 'reservations.meta.trainNumber': 'Αρ. Τρένου',
+ 'reservations.meta.platform': 'Αποβάθρα',
+ 'reservations.meta.seat': 'Θέση',
+ 'reservations.meta.checkIn': 'Check-in',
+ 'reservations.meta.checkInUntil': 'Check-in έως',
+ 'reservations.meta.checkOut': 'Check-out',
+ 'reservations.meta.linkAccommodation': 'Κατάλυμα',
+ 'reservations.meta.pickAccommodation': 'Σύνδεση με κατάλυμα',
+ 'reservations.meta.noAccommodation': 'Κανένα',
+ 'reservations.meta.hotelPlace': 'Κατάλυμα',
+ 'reservations.meta.pickHotel': 'Επιλογή καταλύματος',
+ 'reservations.meta.fromDay': 'Από',
+ 'reservations.meta.toDay': 'Προς',
+ 'reservations.meta.selectDay': 'Επιλογή ημέρας',
+ 'reservations.type.flight': 'Πτήση',
+ 'reservations.type.hotel': 'Κατάλυμα',
+ 'reservations.type.restaurant': 'Εστιατόριο',
+ 'reservations.type.train': 'Τρένο',
+ 'reservations.type.car': 'Αυτοκίνητο',
+ 'reservations.type.cruise': 'Κρουαζιέρα',
+ 'reservations.type.event': 'Εκδήλωση',
+ 'reservations.type.tour': 'Περιήγηση',
+ 'reservations.type.other': 'Άλλο',
+ 'reservations.type.bus': 'Λεωφορείο',
+ 'reservations.type.ferry': 'Φέρι',
+ 'reservations.type.bicycle': 'Ποδήλατο',
+ 'reservations.type.taxi': 'Ταξί',
+ 'reservations.type.transport_other': 'Άλλο',
+ 'reservations.confirm.delete':
+ 'Είστε σίγουροι ότι θέλετε να διαγράψετε την κράτηση "{name}";',
+ 'reservations.confirm.deleteTitle': 'Διαγραφή κράτησης;',
+ 'reservations.confirm.deleteBody':
+ 'Η κράτηση "{name}" θα διαγραφεί οριστικά.',
+ 'reservations.toast.updated': 'Η κράτηση ενημερώθηκε',
+ 'reservations.toast.removed': 'Η κράτηση διαγράφηκε',
+ 'reservations.toast.fileUploaded': 'Το αρχείο μεταφορτώθηκε',
+ 'reservations.toast.uploadError': 'Η μεταφόρτωση απέτυχε',
+ 'reservations.newTitle': 'Νέα Κράτηση',
+ 'reservations.bookingType': 'Τύπος Κράτησης',
+ 'reservations.titleLabel': 'Τίτλος',
+ 'reservations.titlePlaceholder': 'π.χ. Lufthansa LH123, Hotel Adlon, ...',
+ 'reservations.locationAddress': 'Τοποθεσία / Διεύθυνση',
+ 'reservations.locationPlaceholder': 'Διεύθυνση, Αεροδρόμιο, Ξενοδοχείο...',
+ 'reservations.confirmationCode': 'Κωδικός Κράτησης',
+ 'reservations.confirmationPlaceholder': 'π.χ. ABC12345',
+ 'reservations.day': 'Ημέρα',
+ 'reservations.noDay': 'Καμία Ημέρα',
+ 'reservations.place': 'Μέρος',
+ 'reservations.noPlace': 'Κανένα Μέρος',
+ 'reservations.pendingSave': 'θα αποθηκευτεί…',
+ 'reservations.uploading': 'Μεταφόρτωση...',
+ 'reservations.attachFile': 'Επισύναψη αρχείου',
+ 'reservations.linkExisting': 'Σύνδεση υπάρχοντος αρχείου',
+ 'reservations.toast.saveError': 'Αποτυχία αποθήκευσης',
+ 'reservations.toast.updateError': 'Αποτυχία ενημέρωσης',
+ 'reservations.toast.deleteError': 'Αποτυχία διαγραφής',
+ 'reservations.confirm.remove': 'Αφαίρεση κράτησης για "{name}";',
+ 'reservations.linkAssignment': 'Σύνδεση με ανάθεση ημέρας',
+ 'reservations.pickAssignment': 'Επιλέξτε μια ανάθεση από το πλάνο σας...',
+ 'reservations.noAssignment': 'Χωρίς σύνδεση (ανεξάρτητη)',
+ 'reservations.price': 'Τιμή',
+ 'reservations.budgetCategory': 'Κατηγορία προϋπολογισμού',
+ 'reservations.budgetCategoryPlaceholder': 'π.χ. Μεταφορά, Κατάλυμα',
+ 'reservations.budgetCategoryAuto': 'Αυτόματα (από τον τύπο κράτησης)',
+ 'reservations.budgetHint':
+ 'Μια εγγραφή προϋπολογισμού θα δημιουργηθεί αυτόματα κατά την αποθήκευση.',
+ 'reservations.departureDate': 'Αναχώρηση',
+ 'reservations.arrivalDate': 'Άφιξη',
+ 'reservations.departureTime': 'Ώρα αναχ.',
+ 'reservations.arrivalTime': 'Ώρα άφ.',
+ 'reservations.pickupDate': 'Παραλαβή',
+ 'reservations.returnDate': 'Επιστροφή',
+ 'reservations.pickupTime': 'Ώρα παραλαβής',
+ 'reservations.returnTime': 'Ώρα επιστροφής',
+ 'reservations.endDate': 'Ημερομηνία λήξης',
+ 'reservations.meta.departureTimezone': 'Ζώνη ώρας αναχ.',
+ 'reservations.meta.arrivalTimezone': 'Ζώνη ώρας άφ.',
+ 'reservations.span.departure': 'Αναχώρηση',
+ 'reservations.span.arrival': 'Άφιξη',
+ 'reservations.span.inTransit': 'Σε διέλευση',
+ 'reservations.span.pickup': 'Παραλαβή',
+ 'reservations.span.return': 'Επιστροφή',
+ 'reservations.span.active': 'Ενεργό',
+ 'reservations.span.start': 'Έναρξη',
+ 'reservations.span.end': 'Λήξη',
+ 'reservations.span.ongoing': 'Σε εξέλιξη',
+ 'reservations.validation.endBeforeStart':
+ 'Η ημερομηνία/ώρα λήξης πρέπει να είναι μετά την ημερομηνία/ώρα έναρξης',
+ 'reservations.addBooking': 'Προσθήκη κράτησης',
+ 'reservations.import.title': 'Εισαγωγή επιβεβαιώσεων κράτησης',
+ 'reservations.import.cta': 'Εισαγωγή από αρχείο',
+ 'reservations.import.dropHere': 'Αποθέστε αρχεία επιβεβαίωσης κράτησης εδώ ή κάντε κλικ για επιλογή',
+ 'reservations.import.dropActive': 'Αποθέστε αρχεία για εισαγωγή',
+ 'reservations.import.acceptedFormats': 'Αποδεκτά: EML, PDF, PKPass, HTML, TXT (μέγιστο 10 MB το καθένα, έως 5 αρχεία)',
+ 'reservations.import.parsing': 'Επεξεργασία αρχείων…',
+ 'reservations.import.previewHeading': 'Βρέθηκαν {count} κράτηση/κρατήσεις',
+ 'reservations.import.previewEmpty': 'Δεν ήταν δυνατή η εξαγωγή κρατήσεων από τα μεταφορτωμένα αρχεία.',
+ 'reservations.import.removeItem': 'Αφαίρεση',
+ 'reservations.import.confirm': 'Εισαγωγή {count} κράτησης/κρατήσεων',
+ 'reservations.import.back': 'Πίσω',
+ 'reservations.import.success': '{count} κράτηση/κρατήσεις εισήχθησαν',
+ 'reservations.import.partialFailure': '{created} εισήχθησαν, {failed} απέτυχαν',
+ 'reservations.import.error': 'Η επεξεργασία απέτυχε. Βεβαιωθείτε ότι το αρχείο είναι έγκυρη επιβεβαίωση κράτησης.',
+ 'reservations.import.unavailable': 'Η εισαγωγή κρατήσεων δεν είναι διαθέσιμη σε αυτόν τον διακομιστή.',
+ 'reservations.import.unsupportedFormat': 'Μη υποστηριζόμενη μορφή αρχείου. Χρησιμοποιήστε EML, PDF, PKPass, HTML ή TXT.',
+ 'reservations.import.fileTooLarge': 'Το αρχείο «{name}» υπερβαίνει το όριο των 10 MB.',
+ 'reservations.airtrail.title': 'Εισαγωγή από το AirTrail',
+ 'reservations.airtrail.cta': 'AirTrail',
+ 'reservations.airtrail.synced': 'AirTrail',
+ 'reservations.airtrail.syncedHint': 'Συγχρονισμένο από το AirTrail — οι αλλαγές συγχρονίζονται και προς τις δύο κατευθύνσεις.',
+ 'reservations.airtrail.notSynced': 'Μη συγχρονισμένο',
+ 'reservations.airtrail.notSyncedHint': 'Αυτή η πτήση αφαιρέθηκε στο AirTrail και δεν συγχρονίζεται πλέον.',
+ 'reservations.airtrail.loadError': 'Δεν ήταν δυνατή η φόρτωση των πτήσεών σας από το AirTrail.',
+ 'reservations.airtrail.imported': '{count} πτήση/πτήσεις εισήχθησαν',
+ 'reservations.airtrail.skippedDuplicate': '{count} υπάρχουν ήδη σε αυτό το ταξίδι, παραλείφθηκαν',
+ 'reservations.airtrail.nothingImported': 'Δεν υπάρχει τίποτα για εισαγωγή.',
+ 'reservations.airtrail.importError': 'Η εισαγωγή απέτυχε. Δοκιμάστε ξανά.',
+ 'reservations.airtrail.undo': 'Εισαγωγή από το AirTrail',
+ 'reservations.airtrail.alreadyImported': 'Εισήχθη',
+ 'reservations.airtrail.duringTrip': 'Κατά τη διάρκεια αυτού του ταξιδιού',
+ 'reservations.airtrail.otherFlights': 'Άλλες πτήσεις',
+ 'reservations.airtrail.empty': 'Δεν βρέθηκαν πτήσεις στον λογαριασμό σας στο AirTrail.',
+ 'reservations.airtrail.importCta': 'Εισαγωγή {count}',
+};
+export default reservations;
diff --git a/shared/src/i18n/gr/settings.ts b/shared/src/i18n/gr/settings.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e8674ee2f737fe84cd1502856ee1b954c21b193d
--- /dev/null
+++ b/shared/src/i18n/gr/settings.ts
@@ -0,0 +1,352 @@
+import type { TranslationStrings } from '../types';
+
+const settings: TranslationStrings = {
+ 'settings.title': 'Ρυθμίσεις',
+ 'settings.subtitle': 'Διαμορφώστε τις προσωπικές σας ρυθμίσεις',
+ 'settings.tabs.display': 'Εμφάνιση',
+ 'settings.tabs.map': 'Χάρτης',
+ 'settings.tabs.notifications': 'Ειδοποιήσεις',
+ 'settings.tabs.integrations': 'Ενσωματώσεις',
+ 'settings.tabs.account': 'Λογαριασμός',
+ 'settings.tabs.offline': 'Εκτός σύνδεσης',
+ 'settings.tabs.about': 'Σχετικά',
+ 'settings.map': 'Χάρτης',
+ 'settings.mapTemplate': 'Πρότυπο Χάρτη',
+ 'settings.mapTemplatePlaceholder.select': 'Επιλέξτε πρότυπο...',
+ 'settings.mapDefaultHint': 'Αφήστε κενό για OpenStreetMap (προεπιλογή)',
+ 'settings.mapTemplatePlaceholder':
+ 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
+ 'settings.mapHint': 'Πρότυπο URL για πλακίδια χάρτη',
+ 'settings.mapProvider': 'Πάροχος Χάρτη',
+ 'settings.mapProviderHint':
+ 'Επηρεάζει τους χάρτες του Trip Planner και του Journey. Το Atlas χρησιμοποιεί πάντα Leaflet.',
+ 'settings.mapLeafletSubtitle': 'Κλασικό 2D, οποιαδήποτε raster πλακίδια',
+ 'settings.mapMapboxSubtitle': 'Διανυσματικά πλακίδια, 3D κτίρια & ανάγλυφο',
+ 'settings.mapExperimental': 'Πειραματικό',
+ 'settings.mapMapboxToken': 'Mapbox Access Token',
+ 'settings.mapMapboxTokenHint': 'Δημόσιο token (pk.*) από',
+ 'settings.mapMapboxTokenLink': 'mapbox.com → Access tokens',
+ 'settings.mapStyle': 'Στυλ Χάρτη',
+ 'settings.mapStylePlaceholder': 'Επιλέξτε ένα στυλ Mapbox',
+ 'settings.mapStyleHint':
+ 'Προκαθορισμένο ή δικό σας mapbox://styles/USER/ID URL',
+ 'settings.map3dBuildings': '3D Κτίρια & Ανάγλυφο',
+ 'settings.map3dHint':
+ 'Κλίση + πραγματικές 3D προεξοχές κτιρίων — λειτουργεί σε κάθε στυλ, συμπεριλαμβανομένου του δορυφορικού.',
+ 'settings.mapHighQuality': 'Λειτουργία Υψηλής Ποιότητας',
+ 'settings.mapHighQualityHint':
+ 'Antialiasing + προβολή υδρογείου για ευκρινέστερες ακμές και ρεαλιστική όψη του κόσμου.',
+ 'settings.mapHighQualityWarning':
+ 'Μπορεί να επηρεάσει την απόδοση σε συσκευές χαμηλότερων προδιαγραφών.',
+ 'settings.mapTipLabel': 'Συμβουλή:',
+ 'settings.mapTip':
+ 'κάντε δεξί κλικ και σύρετε για περιστροφή/κλίση του χάρτη. Μεσαίο κλικ για προσθήκη τοποθεσίας (το δεξί κλικ έχει κρατηθεί για περιστροφή).',
+ 'settings.latitude': 'Γεωγραφικό Πλάτος',
+ 'settings.longitude': 'Γεωγραφικό Μήκος',
+ 'settings.saveMap': 'Αποθήκευση Χάρτη',
+ 'settings.apiKeys': 'Κλειδιά API',
+ 'settings.mapsKey': 'Google Maps API Key',
+ 'settings.mapsKeyHint':
+ 'Για αναζήτηση τοποθεσιών. Απαιτείται Places API (New). Λάβετε το από console.cloud.google.com',
+ 'settings.weatherKey': 'OpenWeatherMap API Key',
+ 'settings.weatherKeyHint':
+ 'Για δεδομένα καιρού. Δωρεάν στο openweathermap.org/api',
+ 'settings.keyPlaceholder': 'Εισάγετε κλειδί...',
+ 'settings.configured': 'Διαμορφωμένο',
+ 'settings.saveKeys': 'Αποθήκευση Κλειδιών',
+ 'settings.display': 'Εμφάνιση',
+ 'settings.colorMode': 'Λειτουργία Χρώματος',
+ 'settings.light': 'Φωτεινό',
+ 'settings.dark': 'Σκούρο',
+ 'settings.auto': 'Αυτόματο',
+ 'settings.language': 'Γλώσσα',
+ 'settings.temperature': 'Μονάδα Θερμοκρασίας',
+ 'settings.timeFormat': 'Μορφή Ώρας',
+ 'settings.bookingLabels': 'Ετικέτες διαδρομής κρατήσεων',
+ 'settings.bookingLabelsHint':
+ 'Εμφάνιση ονομάτων σταθμών / αεροδρομίων στον χάρτη. Όταν είναι απενεργοποιημένο, εμφανίζεται μόνο το εικονίδιο.',
+ 'settings.blurBookingCodes': 'Θόλωμα Κωδικών Κρατήσεων',
+ 'settings.optimizeFromAccommodation': 'Βελτιστοποίηση διαδρομής από το κατάλυμα',
+ 'settings.optimizeFromAccommodationHint':
+ 'Κατά τη βελτιστοποίηση μιας ημέρας, ξεκινήστε τη διαδρομή από το ξενοδοχείο στο οποίο ξυπνάτε και τερματίστε την σε αυτό στο οποίο κάνετε check-in το ίδιο βράδυ.',
+ 'settings.notifications': 'Ειδοποιήσεις',
+ 'settings.notifyTripInvite': 'Προσκλήσεις ταξιδιού',
+ 'settings.notifyBookingChange': 'Αλλαγές κρατήσεων',
+ 'settings.notifyTripReminder': 'Υπενθυμίσεις ταξιδιού',
+ 'settings.notifyTodoDue': 'Εκκρεμότητα λήγει σύντομα',
+ 'settings.notifyVacayInvite': 'Προσκλήσεις Vacay fusion',
+ 'settings.notifyPhotosShared': 'Κοινόχρηστες φωτογραφίες (Immich)',
+ 'settings.notifyCollabMessage': 'Μηνύματα συνομιλίας (Collab)',
+ 'settings.notifyPackingTagged': 'Λίστα πακεταρίσματος: αναθέσεις',
+ 'settings.notifyWebhook': 'Ειδοποιήσεις Webhook',
+ 'settings.notifyVersionAvailable': 'Διαθέσιμη νέα έκδοση',
+ 'settings.notificationPreferences.email': 'Email',
+ 'settings.notificationPreferences.webhook': 'Webhook',
+ 'settings.notificationPreferences.inapp': 'Εντός εφαρμογής',
+ 'settings.notificationPreferences.ntfy': 'Ntfy',
+ 'settings.notificationPreferences.noChannels':
+ 'Δεν έχουν διαμορφωθεί κανάλια ειδοποιήσεων. Ζητήστε από έναν διαχειριστή να ρυθμίσει ειδοποιήσεις email ή webhook.',
+ 'settings.webhookUrl.label': 'Webhook URL',
+ 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...',
+ 'settings.webhookUrl.hint':
+ 'Εισάγετε το Discord, Slack ή προσαρμοσμένο webhook URL σας για να λαμβάνετε ειδοποιήσεις.',
+ 'settings.webhookUrl.saved': 'Το Webhook URL αποθηκεύτηκε',
+ 'settings.webhookUrl.test': 'Δοκιμή',
+ 'settings.webhookUrl.testSuccess': 'Η δοκιμή webhook στάλθηκε επιτυχώς',
+ 'settings.webhookUrl.testFailed': 'Η δοκιμή webhook απέτυχε',
+ 'settings.ntfyUrl.topicLabel': 'Θέμα Ntfy',
+ 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts',
+ 'settings.ntfyUrl.serverLabel': 'URL Ntfy Server (προαιρετικό)',
+ 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh',
+ 'settings.ntfyUrl.hint':
+ 'Εισάγετε το ntfy topic σας για να λαμβάνετε push ειδοποιήσεις. Αφήστε τον server κενό για χρήση του προεπιλεγμένου από τον διαχειριστή.',
+ 'settings.ntfyUrl.tokenLabel': 'Access Token (προαιρετικό)',
+ 'settings.ntfyUrl.tokenHint':
+ 'Απαιτείται για θέματα προστατευμένα με κωδικό.',
+ 'settings.ntfyUrl.saved': 'Οι ρυθμίσεις Ntfy αποθηκεύτηκαν',
+ 'settings.ntfyUrl.test': 'Δοκιμή',
+ 'settings.ntfyUrl.testSuccess':
+ 'Η δοκιμαστική ειδοποίηση ntfy στάλθηκε επιτυχώς',
+ 'settings.ntfyUrl.testFailed': 'Η δοκιμαστική ειδοποίηση ntfy απέτυχε',
+ 'settings.ntfyUrl.tokenCleared': 'Το access token εκκαθαρίστηκε',
+ 'settings.notificationsDisabled':
+ 'Οι ειδοποιήσεις δεν έχουν διαμορφωθεί. Ζητήστε από έναν διαχειριστή να ενεργοποιήσει ειδοποιήσεις email ή webhook.',
+ 'settings.notificationsActive': 'Ενεργό κανάλι',
+ 'settings.notificationsManagedByAdmin':
+ 'Τα συμβάντα ειδοποιήσεων διαμορφώνονται από τον διαχειριστή σας.',
+ 'settings.on': 'Ενεργό',
+ 'settings.off': 'Ανενεργό',
+ 'settings.mcp.title': 'Διαμόρφωση MCP',
+ 'settings.mcp.endpoint': 'MCP Endpoint',
+ 'settings.mcp.clientConfig': 'Διαμόρφωση Πελάτη',
+ 'settings.mcp.clientConfigHint':
+ 'Αντικαταστήστε το με ένα API token από την παρακάτω λίστα. Η διαδρομή για το npx ίσως χρειαστεί να προσαρμοστεί για το σύστημά σας (π.χ. C:\\PROGRA~1\\nodejs\\npx.cmd στα Windows).',
+ 'settings.mcp.clientConfigHintOAuth':
+ 'Αντικαταστήστε τα και με τα διαπιστευτήρια που εμφανίζονται στον πελάτη OAuth 2.1 που δημιουργήσατε παραπάνω. Το mcp-remote θα ανοίξει τον browser σας για να ολοκληρώσετε την εξουσιοδότηση την πρώτη φορά που θα συνδεθείτε. Η διαδρομή για το npx ίσως χρειαστεί να προσαρμοστεί για το σύστημά σας (π.χ. C:\\PROGRA~1\\nodejs\\npx.cmd στα Windows).',
+ 'settings.mcp.copy': 'Αντιγραφή',
+ 'settings.mcp.copied': 'Αντιγράφηκε!',
+ 'settings.mcp.apiTokens': 'API Tokens',
+ 'settings.mcp.createToken': 'Δημιουργία νέου Token',
+ 'settings.mcp.noTokens':
+ 'Δεν υπάρχουν tokens ακόμη. Δημιουργήστε ένα για να συνδέσετε πελάτες MCP.',
+ 'settings.mcp.tokenCreatedAt': 'Δημιουργήθηκε',
+ 'settings.mcp.tokenUsedAt': 'Χρησιμοποιήθηκε',
+ 'settings.mcp.deleteTokenTitle': 'Διαγραφή Token',
+ 'settings.mcp.deleteTokenMessage':
+ 'Αυτό το token θα σταματήσει να λειτουργεί αμέσως. Οποιοσδήποτε πελάτης MCP το χρησιμοποιεί θα χάσει την πρόσβαση.',
+ 'settings.mcp.modal.createTitle': 'Δημιουργία API Token',
+ 'settings.mcp.modal.tokenName': 'Όνομα Token',
+ 'settings.mcp.modal.tokenNamePlaceholder':
+ 'π.χ. Claude Desktop, Φορητός εργασίας',
+ 'settings.mcp.modal.creating': 'Δημιουργία…',
+ 'settings.mcp.modal.create': 'Δημιουργία Token',
+ 'settings.mcp.modal.createdTitle': 'Το Token δημιουργήθηκε',
+ 'settings.mcp.modal.createdWarning':
+ 'Αυτό το token θα εμφανιστεί μόνο μία φορά. Αντιγράψτε και αποθηκεύστε το τώρα — δεν μπορεί να ανακτηθεί.',
+ 'settings.mcp.modal.done': 'Ολοκληρώθηκε',
+ 'settings.mcp.toast.created': 'Το token δημιουργήθηκε',
+ 'settings.mcp.toast.createError': 'Αποτυχία δημιουργίας token',
+ 'settings.mcp.toast.deleted': 'Το token διαγράφηκε',
+ 'settings.mcp.toast.deleteError': 'Αποτυχία διαγραφής token',
+ 'settings.mcp.apiTokensDeprecated':
+ 'Τα API Tokens έχουν καταργηθεί και θα αφαιρεθούν σε μελλοντική έκδοση. Παρακαλώ χρησιμοποιήστε OAuth 2.1 Clients αντί αυτών.',
+ 'settings.oauth.clients': 'OAuth 2.1 Clients',
+ 'settings.oauth.clientsHint':
+ 'Καταχωρήστε πελάτες OAuth 2.1 για να επιτρέψετε σε εφαρμογές MCP τρίτων (Claude Web, Cursor, κ.λπ.) να συνδέονται χωρίς στατικά tokens.',
+ 'settings.oauth.createClient': 'Νέος Πελάτης',
+ 'settings.oauth.noClients': 'Δεν έχουν καταχωρηθεί πελάτες OAuth.',
+ 'settings.oauth.clientId': 'Client ID',
+ 'settings.oauth.clientSecret': 'Client Secret',
+ 'settings.oauth.deleteClient': 'Διαγραφή Πελάτη',
+ 'settings.oauth.deleteClientMessage':
+ 'Αυτός ο πελάτης και όλες οι ενεργές συνεδρίες θα αφαιρεθούν μόνιμα. Οποιαδήποτε εφαρμογή τον χρησιμοποιεί θα χάσει την πρόσβαση αμέσως.',
+ 'settings.oauth.rotateSecret': 'Εναλλαγή Secret',
+ 'settings.oauth.rotateSecretMessage':
+ 'Θα δημιουργηθεί ένα νέο client secret και όλες οι υπάρχουσες συνεδρίες θα ακυρωθούν αμέσως. Ενημερώστε την εφαρμογή σας πριν κλείσετε αυτό το παράθυρο.',
+ 'settings.oauth.rotateSecretConfirm': 'Εναλλαγή',
+ 'settings.oauth.rotateSecretConfirming': 'Εναλλαγή…',
+ 'settings.oauth.rotateSecretDoneTitle': 'Δημιουργήθηκε νέο Secret',
+ 'settings.oauth.rotateSecretDoneWarning':
+ 'Αυτό το secret εμφανίζεται μόνο μία φορά. Αντιγράψτε το τώρα και ενημερώστε την εφαρμογή σας — όλες οι προηγούμενες συνεδρίες έχουν ακυρωθεί.',
+ 'settings.oauth.activeSessions': 'Ενεργές Συνεδρίες OAuth',
+ 'settings.oauth.sessionScopes': 'Scopes',
+ 'settings.oauth.sessionExpires': 'Λήγει',
+ 'settings.oauth.revoke': 'Ανάκληση',
+ 'settings.oauth.revokeSession': 'Ανάκληση Συνεδρίας',
+ 'settings.oauth.revokeSessionMessage':
+ 'Αυτό θα ανακαλέσει αμέσως την πρόσβαση για αυτή τη συνεδρία OAuth.',
+ 'settings.oauth.modal.createTitle': 'Καταχώρηση Πελάτη OAuth',
+ 'settings.oauth.modal.presets': 'Γρήγορες προεπιλογές',
+ 'settings.oauth.modal.clientName': 'Όνομα Εφαρμογής',
+ 'settings.oauth.modal.clientNamePlaceholder':
+ 'π.χ. Claude Web, Η εφαρμογή μου MCP',
+ 'settings.oauth.modal.redirectUris': 'Redirect URIs',
+ 'settings.oauth.modal.redirectUrisPlaceholder':
+ 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint':
+ 'Ένα URI ανά γραμμή. Απαιτείται HTTPS (το localhost εξαιρείται). Επιβάλλεται ακριβής αντιστοίχιση.',
+ 'settings.oauth.modal.scopes': 'Επιτρεπόμενα Scopes',
+ 'settings.oauth.modal.scopesHint':
+ 'Τα list_trips και get_trip_summary είναι πάντα διαθέσιμα — δεν απαιτείται scope. Επιτρέπουν στο AI να εντοπίζει τα trip IDs που χρειάζονται για τη χρήση οποιουδήποτε άλλου εργαλείου.',
+ 'settings.oauth.modal.selectAll': 'Επιλογή όλων',
+ 'settings.oauth.modal.deselectAll': 'Απο-επιλογή όλων',
+ 'settings.oauth.modal.creating': 'Καταχώρηση…',
+ 'settings.oauth.modal.create': 'Καταχώρηση Πελάτη',
+ 'settings.oauth.modal.createdTitle': 'Ο Πελάτης Καταχωρήθηκε',
+ 'settings.oauth.modal.createdWarning':
+ 'Το client secret εμφανίζεται μόνο μία φορά. Αντιγράψτε το τώρα — δεν μπορεί να ανακτηθεί.',
+ 'settings.oauth.toast.createError': 'Αποτυχία καταχώρησης πελάτη OAuth',
+ 'settings.oauth.toast.deleted': 'Ο πελάτης OAuth διαγράφηκε',
+ 'settings.oauth.toast.deleteError': 'Αποτυχία διαγραφής πελάτη OAuth',
+ 'settings.oauth.toast.revoked': 'Η συνεδρία ανακλήθηκε',
+ 'settings.oauth.toast.revokeError': 'Αποτυχία ανάκλησης συνεδρίας',
+ 'settings.oauth.toast.rotateError': 'Αποτυχία εναλλαγής client secret',
+ 'settings.oauth.modal.machineClient':
+ 'Πελάτης μηχανής (χωρίς σύνδεση μέσω browser)',
+ 'settings.oauth.modal.machineClientHint':
+ 'Χρησιμοποιήστε grant client_credentials — δεν χρειάζονται redirect URIs. Το token εκδίδεται απευθείας μέσω client_id + client_secret και ενεργεί ως εσείς εντός των επιλεγμένων scopes.',
+ 'settings.oauth.modal.machineClientUsage':
+ 'Λήψη token: POST /oauth/token με grant_type=client_credentials, client_id, και client_secret. Χωρίς browser, χωρίς refresh token.',
+ 'settings.oauth.badge.machine': 'μηχανή',
+ 'settings.account': 'Λογαριασμός',
+ 'settings.about': 'Σχετικά',
+ 'settings.about.reportBug': 'Αναφορά Σφάλματος',
+ 'settings.about.reportBugHint': 'Βρήκατε πρόβλημα; Ενημερώστε μας',
+ 'settings.about.featureRequest': 'Αίτημα Λειτουργίας',
+ 'settings.about.featureRequestHint': 'Προτείνετε ένα νέο χαρακτηριστικό',
+ 'settings.about.wikiHint': 'Τεκμηρίωση & οδηγοί',
+ 'settings.about.supporters.badge': 'Μηνιαίοι Υποστηρικτές',
+ 'settings.about.supporters.title': 'Συνταξιδιώτες για το TREK',
+ 'settings.about.supporters.subtitle':
+ 'Καθώς εσείς σχεδιάζετε την επόμενή σας διαδρομή, αυτοί οι άνθρωποι βοηθούν να σχεδιαστεί το μέλλον του TREK. Η μηνιαία τους συνεισφορά πηγαίνει απευθείας στην ανάπτυξη και σε πραγματικές ώρες εργασίας — ώστε το TREK να παραμένει Open Source.',
+ 'settings.about.supporters.since': 'υποστηρικτής από {date}',
+ 'settings.about.supporters.tierEmpty': 'Γίνετε ο πρώτος',
+ 'settings.about.supporter.tier.noReturnTicket': 'Χωρίς Εισιτήριο Επιστροφής',
+ 'settings.about.supporter.tier.lostLuggageVip': 'VIP Χαμένων Αποσκευών',
+ 'settings.about.supporter.tier.businessClassDreamer':
+ 'Ονειροπόλος Business Class',
+ 'settings.about.supporter.tier.budgetTraveller': 'Οικονομικός Ταξιδιώτης',
+ 'settings.about.supporter.tier.hostelBunkmate': 'Συγκάτοικος Hostel',
+ 'settings.about.description':
+ 'Το TREK είναι ένας self-hosted ταξιδιωτικός σχεδιαστής που σας βοηθά να οργανώσετε τα ταξίδια σας από την πρώτη ιδέα μέχρι την τελευταία ανάμνηση. Σχεδιασμός ημερών, προϋπολογισμός, λίστες πακεταρίσματος, φωτογραφίες και πολλά άλλα — όλα σε ένα μέρος, στον δικό σας server.',
+ 'settings.about.madeWith': 'Φτιαγμένο με',
+ 'settings.about.madeBy':
+ 'από τον Maurice και μια αναπτυσσόμενη κοινότητα open-source.',
+ 'settings.username': 'Όνομα χρήστη',
+ 'settings.email': 'Email',
+ 'settings.role': 'Ρόλος',
+ 'settings.roleAdmin': 'Διαχειριστής',
+ 'settings.oidcLinked': 'Συνδεδεμένο με',
+ 'settings.changePassword': 'Αλλαγή Κωδικού',
+ 'settings.currentPassword': 'Τρέχων κωδικός',
+ 'settings.currentPasswordRequired': 'Ο τρέχων κωδικός είναι υποχρεωτικός',
+ 'settings.newPassword': 'Νέος κωδικός',
+ 'settings.confirmPassword': 'Επιβεβαίωση νέου κωδικού',
+ 'settings.updatePassword': 'Ενημέρωση κωδικού',
+ 'settings.passwordRequired':
+ 'Παρακαλώ εισάγετε τον τρέχοντα και τον νέο κωδικό',
+ 'settings.passwordTooShort':
+ 'Ο κωδικός πρέπει να έχει τουλάχιστον 8 χαρακτήρες',
+ 'settings.passwordMismatch': 'Οι κωδικοί δεν ταιριάζουν',
+ 'settings.passwordWeak':
+ 'Ο κωδικός πρέπει να περιέχει κεφαλαία, πεζά, έναν αριθμό και έναν ειδικό χαρακτήρα',
+ 'settings.passwordChanged': 'Ο κωδικός άλλαξε επιτυχώς',
+ 'settings.mustChangePassword':
+ 'Πρέπει να αλλάξετε τον κωδικό σας πριν συνεχίσετε. Παρακαλώ ορίστε έναν νέο κωδικό παρακάτω.',
+ 'settings.deleteAccount': 'Διαγραφή λογαριασμού',
+ 'settings.deleteAccountTitle': 'Διαγραφή του λογαριασμού σας;',
+ 'settings.deleteAccountWarning':
+ 'Ο λογαριασμός σας και όλα τα ταξίδια, οι τόποι και τα αρχεία σας θα διαγραφούν μόνιμα. Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.',
+ 'settings.deleteAccountConfirm': 'Μόνιμη διαγραφή',
+ 'settings.deleteBlockedTitle': 'Η διαγραφή δεν είναι δυνατή',
+ 'settings.deleteBlockedMessage':
+ 'Είστε ο μόνος διαχειριστής. Προαγάγετε έναν άλλον χρήστη σε διαχειριστή πριν διαγράψετε τον λογαριασμό σας.',
+ 'settings.roleUser': 'Χρήστης',
+ 'settings.saveProfile': 'Αποθήκευση Προφίλ',
+ 'settings.toast.mapSaved': 'Οι ρυθμίσεις χάρτη αποθηκεύτηκαν',
+ 'settings.toast.keysSaved': 'Τα κλειδιά API αποθηκεύτηκαν',
+ 'settings.toast.displaySaved': 'Οι ρυθμίσεις εμφάνισης αποθηκεύτηκαν',
+ 'settings.toast.profileSaved': 'Το προφίλ αποθηκεύτηκε',
+ 'settings.uploadAvatar': 'Μεταφόρτωση Εικόνας Προφίλ',
+ 'settings.removeAvatar': 'Αφαίρεση Εικόνας Προφίλ',
+ 'settings.avatarUploaded': 'Η εικόνα προφίλ ενημερώθηκε',
+ 'settings.avatarRemoved': 'Η εικόνα προφίλ αφαιρέθηκε',
+ 'settings.avatarError': 'Η μεταφόρτωση απέτυχε',
+ 'settings.mfa.title': 'Έλεγχος ταυτότητας δύο παραγόντων (2FA)',
+ 'settings.mfa.description':
+ 'Προσθέτει ένα δεύτερο βήμα όταν συνδέεστε με email και κωδικό. Χρησιμοποιήστε μια εφαρμογή authenticator (Google Authenticator, Authy, κ.λπ.).',
+ 'settings.mfa.requiredByPolicy':
+ 'Ο διαχειριστής σας απαιτεί έλεγχο ταυτότητας δύο παραγόντων. Ρυθμίστε μια εφαρμογή authenticator παρακάτω πριν συνεχίσετε.',
+ 'settings.mfa.backupTitle': 'Εφεδρικοί κωδικοί',
+ 'settings.mfa.backupDescription':
+ 'Χρησιμοποιήστε αυτούς τους εφεδρικούς κωδικούς μίας χρήσης αν χάσετε την πρόσβαση στην εφαρμογή authenticator.',
+ 'settings.mfa.backupWarning':
+ 'Αποθηκεύστε αυτούς τους κωδικούς τώρα. Κάθε κωδικός μπορεί να χρησιμοποιηθεί μόνο μία φορά.',
+ 'settings.mfa.backupCopy': 'Αντιγραφή κωδικών',
+ 'settings.mfa.backupDownload': 'Λήψη TXT',
+ 'settings.mfa.backupPrint': 'Εκτύπωση / PDF',
+ 'settings.mfa.backupCopied': 'Οι εφεδρικοί κωδικοί αντιγράφηκαν',
+ 'settings.mfa.enabled': 'Το 2FA είναι ενεργοποιημένο στον λογαριασμό σας.',
+ 'settings.mfa.disabled': 'Το 2FA δεν είναι ενεργοποιημένο.',
+ 'settings.mfa.setup': 'Ρύθμιση authenticator',
+ 'settings.mfa.scanQr':
+ 'Σαρώστε αυτόν τον κωδικό QR με την εφαρμογή σας ή εισάγετε το secret χειροκίνητα.',
+ 'settings.mfa.secretLabel': 'Κλειδί secret (χειροκίνητη εισαγωγή)',
+ 'settings.mfa.codePlaceholder': 'Κωδικός 6 ψηφίων',
+ 'settings.mfa.enable': 'Ενεργοποίηση 2FA',
+ 'settings.mfa.cancelSetup': 'Ακύρωση',
+ 'settings.mfa.disableTitle': 'Απενεργοποίηση 2FA',
+ 'settings.mfa.disableHint':
+ 'Εισάγετε τον κωδικό του λογαριασμού σας και έναν τρέχοντα κωδικό από τον authenticator σας.',
+ 'settings.mfa.disable': 'Απενεργοποίηση 2FA',
+ 'settings.mfa.toastEnabled':
+ 'Ο έλεγχος ταυτότητας δύο παραγόντων ενεργοποιήθηκε',
+ 'settings.mfa.toastDisabled':
+ 'Ο έλεγχος ταυτότητας δύο παραγόντων απενεργοποιήθηκε',
+ 'settings.mfa.demoBlocked': 'Δεν είναι διαθέσιμο σε λειτουργία demo',
+ "settings.currency": "Currency",
+ "settings.currencyHint": "All amounts in Costs are converted to and shown in this currency.",
+ 'settings.passkey.title': 'Passkeys',
+ 'settings.passkey.description':
+ 'Συνδεθείτε πιο γρήγορα και με προστασία από phishing χρησιμοποιώντας ένα passkey — το δαχτυλικό σας αποτύπωμα, το πρόσωπό σας, ένα PIN ή ένα κλειδί υλικού. Ο κωδικός σας παραμένει ως εφεδρεία.',
+ 'settings.passkey.notConfigured':
+ 'Τα passkeys είναι ενεργοποιημένα αλλά δεν έχουν διαμορφωθεί πλήρως σε αυτόν τον server ακόμη. Ζητήστε από τον διαχειριστή σας να ορίσει τον τομέα WebAuthn.',
+ 'settings.passkey.add': 'Προσθήκη passkey',
+ 'settings.passkey.addTitle': 'Προσθήκη passkey',
+ 'settings.passkey.passwordPrompt':
+ 'Επιβεβαιώστε τον τρέχοντα κωδικό σας και έπειτα ακολουθήστε τις οδηγίες της συσκευής σας.',
+ 'settings.passkey.passwordRequired': 'Ο τρέχων κωδικός σας είναι υποχρεωτικός.',
+ 'settings.passkey.namePlaceholder': 'Όνομα (προαιρετικό, π.χ. "iPhone")',
+ 'settings.passkey.addedToast': 'Το passkey προστέθηκε',
+ 'settings.passkey.added': 'Προστέθηκε',
+ 'settings.passkey.addError': 'Δεν ήταν δυνατή η προσθήκη του passkey',
+ 'settings.passkey.cancelled': 'Η ρύθμιση του passkey ακυρώθηκε',
+ 'settings.passkey.deleted': 'Το passkey αφαιρέθηκε',
+ 'settings.passkey.deleteConfirm':
+ 'Αφαίρεση αυτού του passkey; Επιβεβαιώστε με τον κωδικό σας.',
+ 'settings.passkey.rename': 'Μετονομασία',
+ 'settings.passkey.defaultName': 'Passkey',
+ 'settings.passkey.synced': 'Συγχρονισμένο',
+ 'settings.passkey.deviceBound': 'Αυτή η συσκευή',
+ 'settings.passkey.lastUsed': 'Τελευταία χρήση',
+ 'settings.passkey.neverUsed': 'Δεν χρησιμοποιήθηκε ποτέ',
+ 'settings.mapPoiPill': 'Εξερεύνηση μερών στον χάρτη',
+ 'settings.mapPoiPillHint': 'Εμφάνιση ετικέτας κατηγορίας στον χάρτη του ταξιδιού για εύρεση κοντινών εστιατορίων, ξενοδοχείων και άλλων από το OpenStreetMap.',
+ 'settings.airtrail.title': 'AirTrail',
+ 'settings.airtrail.hint': 'Συνδέστε το αυτο-φιλοξενούμενο AirTrail σας για εισαγωγή και συγχρονισμό πτήσεων. Δημιουργήστε ένα κλειδί API στο AirTrail από Ρυθμίσεις → Ασφάλεια.',
+ 'settings.airtrail.url': 'URL της εγκατάστασης',
+ 'settings.airtrail.apiKey': 'Κλειδί API',
+ 'settings.airtrail.apiKeyPlaceholder': 'Κλειδί API τύπου Bearer',
+ 'settings.airtrail.apiKeyHint': 'Δημιουργείται στο AirTrail από Ρυθμίσεις → Ασφάλεια. Αποθηκεύεται κρυπτογραφημένο.',
+ 'settings.airtrail.allowInsecureTls': 'Να επιτρέπονται αυτο-υπογεγραμμένα πιστοποιητικά',
+ 'settings.airtrail.allowInsecureTlsHint': 'Ενεργοποιήστε το μόνο για μια αξιόπιστη εγκατάσταση στο δικό σας δίκτυο.',
+ 'settings.airtrail.connected': 'Συνδέθηκε',
+ 'settings.airtrail.notConnected': 'Δεν συνδέθηκε',
+ 'settings.airtrail.toast.saved': 'Η σύνδεση με το AirTrail αποθηκεύτηκε',
+ 'settings.airtrail.toast.saveError': 'Δεν ήταν δυνατή η αποθήκευση της σύνδεσης',
+ 'settings.airtrail.test.button': 'Δοκιμή σύνδεσης',
+ 'settings.airtrail.test.success': 'Συνδέθηκε — βρέθηκαν {count} πτήση/πτήσεις',
+ 'settings.airtrail.test.failed': 'Η σύνδεση απέτυχε',
+};
+
+export default settings;
diff --git a/shared/src/i18n/gr/share.ts b/shared/src/i18n/gr/share.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6c2579bc31dc554eb728d78d1c251dd4ae42a482
--- /dev/null
+++ b/shared/src/i18n/gr/share.ts
@@ -0,0 +1,16 @@
+import type { TranslationStrings } from '../types';
+
+const share: TranslationStrings = {
+ 'share.linkTitle': 'Δημόσιος Σύνδεσμος',
+ 'share.linkHint':
+ 'Δημιουργήστε έναν σύνδεσμο που μπορεί να χρησιμοποιήσει οποιοσδήποτε για να δει αυτό το ταξίδι χωρίς σύνδεση. Μόνο για ανάγνωση — χωρίς δυνατότητα επεξεργασίας.',
+ 'share.createLink': 'Δημιουργία συνδέσμου',
+ 'share.deleteLink': 'Διαγραφή συνδέσμου',
+ 'share.createError': 'Αδυναμία δημιουργίας συνδέσμου',
+ 'share.permMap': 'Χάρτης & Σχέδιο',
+ 'share.permBookings': 'Κρατήσεις',
+ 'share.permPacking': 'Πακετάρισμα',
+ 'share.permBudget': 'Προϋπολογισμός',
+ 'share.permCollab': 'Συνομιλία',
+};
+export default share;
diff --git a/shared/src/i18n/gr/shared.ts b/shared/src/i18n/gr/shared.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c269ed723d44e5697354954eb25d4682f9d9fc82
--- /dev/null
+++ b/shared/src/i18n/gr/shared.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const shared: TranslationStrings = {
+ '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': 'Σε εκκρεμότητα',
+};
+export default shared;
diff --git a/shared/src/i18n/gr/stats.ts b/shared/src/i18n/gr/stats.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e68e220a7ccf3df0feadaf0d944ab3a0c1b1ae8f
--- /dev/null
+++ b/shared/src/i18n/gr/stats.ts
@@ -0,0 +1,13 @@
+import type { TranslationStrings } from '../types';
+
+const stats: TranslationStrings = {
+ 'stats.countries': 'Χώρες',
+ 'stats.cities': 'Πόλεις',
+ 'stats.trips': 'Ταξίδια',
+ 'stats.places': 'Τοποθεσίες',
+ 'stats.worldProgress': 'Παγκόσμια Πρόοδος',
+ 'stats.visited': 'επισκέφθηκα',
+ 'stats.remaining': 'απομένουν',
+ 'stats.visitedCountries': 'Χώρες που Επισκέφτηκα',
+};
+export default stats;
diff --git a/shared/src/i18n/gr/system_notice.ts b/shared/src/i18n/gr/system_notice.ts
new file mode 100644
index 0000000000000000000000000000000000000000..93aa81fa6f5c53bd5eaef997a74c6baa3feac350
--- /dev/null
+++ b/shared/src/i18n/gr/system_notice.ts
@@ -0,0 +1,66 @@
+import type { TranslationStrings } from '../types';
+
+const system_notice: TranslationStrings = {
+ 'system_notice.v3_photos.title': 'Οι Φωτογραφίες μετακινήθηκαν στην 3.0',
+ 'system_notice.v3_photos.body':
+ 'Οι **Φωτογραφίες** στον Σχεδιαστή Ταξιδιού έχουν αφαιρεθεί. Οι φωτογραφίες σας είναι ασφαλείς — το TREK δεν τροποποίησε ποτέ τη βιβλιοθήκη σας Immich ή Synology.\n\nΟι φωτογραφίες τώρα βρίσκονται στο πρόσθετο **Journey**. Το Journey είναι προαιρετικό — αν δεν είναι ακόμα διαθέσιμο, ζητήστε από τον διαχειριστή σας να το ενεργοποιήσει από το Διαχειριστής → Πρόσθετα.',
+ 'system_notice.v3_journey.title': 'Γνωρίστε το Journey — ημερολόγιο ταξιδιών',
+ 'system_notice.v3_journey.body':
+ 'Καταγράψτε τα ταξίδια σας ως πλούσιες ταξιδιωτικές ιστορίες με χρονολόγια, συλλογές φωτογραφιών και διαδραστικούς χάρτες.',
+ 'system_notice.v3_journey.cta_label': 'Άνοιγμα Journey',
+ 'system_notice.v3_journey.highlight_timeline':
+ 'Χρονολόγιο ανά ημέρα & συλλογή',
+ 'system_notice.v3_journey.highlight_photos': 'Εισαγωγή από Immich ή Synology',
+ 'system_notice.v3_journey.highlight_share':
+ 'Δημόσια κοινοποίηση — δεν χρειάζεται σύνδεση',
+ 'system_notice.v3_journey.highlight_export':
+ 'Εξαγωγή ως βιβλίο φωτογραφιών PDF',
+ 'system_notice.v3_features.title': 'Περισσότερα αξιοσημείωτα στην 3.0',
+ 'system_notice.v3_features.body':
+ 'Μερικά ακόμα πράγματα που αξίζει να γνωρίζετε για αυτή την έκδοση.',
+ 'system_notice.v3_features.highlight_dashboard':
+ 'Σχεδιασμός πίνακα ελέγχου πρώτα για κινητά',
+ 'system_notice.v3_features.highlight_offline':
+ 'Πλήρης λειτουργία εκτός σύνδεσης ως PWA',
+ 'system_notice.v3_features.highlight_search':
+ 'Αυτόματη συμπλήρωση αναζήτησης τοποθεσιών σε πραγματικό χρόνο',
+ 'system_notice.v3_features.highlight_import':
+ 'Εισαγωγή τοποθεσιών από αρχεία KMZ/KML',
+ 'system_notice.v3_mcp.title': 'MCP: Αναβάθμιση OAuth 2.1',
+ 'system_notice.v3_mcp.body':
+ 'Η ενσωμάτωση MCP ανασχεδιάστηκε πλήρως. Το OAuth 2.1 είναι τώρα η συνιστώμενη μέθοδος αυθεντικοποίησης. Τα παλιά στατικά tokens (trek_…) είναι παρωχημένα και θα αφαιρεθούν σε μελλοντική έκδοση.',
+ 'system_notice.v3_mcp.highlight_oauth': 'Συνιστάται OAuth 2.1 (mcp-remote)',
+ 'system_notice.v3_mcp.highlight_scopes': '24 λεπτομερή εύρη δικαιωμάτων',
+ 'system_notice.v3_mcp.highlight_deprecated':
+ 'Στατικά tokens trek_ παρωχημένα',
+ 'system_notice.v3_mcp.highlight_tools':
+ 'Επεκτεταμένο σύνολο εργαλείων & προτροπών',
+ 'system_notice.v3_thankyou.title': 'Μια προσωπική σημείωση από εμένα',
+ 'system_notice.v3_thankyou.body':
+ 'Πριν φύγετε — θέλω να αφιερώσω μια στιγμή.\n\nΤο TREK ξεκίνησε ως ένα δευτερεύον έργο που έφτιαξα για τα δικά μου ταξίδια. Ποτέ δεν φαντάστηκα ότι θα γινόταν κάτι που 4.000 από εσάς εμπιστεύεστε τώρα για να σχεδιάσετε τις περιπέτειές σας. Κάθε αστέρι, κάθε αναφορά, κάθε αίτημα χαρακτηριστικού — τα διαβάζω όλα, και με κρατούν να συνεχίζω τις ξενύχτιες ανάμεσα σε δουλειά πλήρους απασχόλησης και πανεπιστήμιο.\n\nΘέλω να ξέρετε: το TREK θα είναι πάντα ανοιχτού κώδικα, πάντα self-hosted, πάντα δικό σας. Χωρίς παρακολούθηση, χωρίς συνδρομές, χωρίς δεσμεύσεις. Απλώς ένα εργαλείο φτιαγμένο από κάποιον που λατρεύει τα ταξίδια όσο κι εσείς.\n\nΙδιαίτερες ευχαριστίες στον [jubnl](https://github.com/jubnl) — έγινες ένας απίστευτος συνεργάτης. Πολλά από αυτά που κάνουν την 3.0 σπουδαία φέρουν τα δαχτυλικά σου αποτυπώματα. Σε ευχαριστώ που πίστεψες σε αυτό το έργο όταν ήταν ακόμα ατελές.\n\nΚαι σε κάθε έναν από εσάς που αναφέρατε ένα σφάλμα, μεταφράσατε ένα κείμενο, μοιραστήκατε το TREK με έναν φίλο, ή απλώς το χρησιμοποιήσατε για να σχεδιάσετε ένα ταξίδι — **σας ευχαριστώ**. Είστε ο λόγος που υπάρχει αυτό.\n\nΕις πολλές ακόμα περιπέτειες μαζί.\n\n— Maurice\n\n---\n\n[Γίνετε μέλος της κοινότητας στο Discord](https://discord.gg/7Q6M6jDwzf)\n\nΑν το TREK κάνει τα ταξίδια σας καλύτερα, ένας [μικρός καφές](https://ko-fi.com/mauriceboe) πάντα κρατά τα φώτα αναμμένα.',
+
+ 'system_notice.v3014_whitespace_collision.title':
+ 'Απαιτείται ενέργεια: σύγκρουση λογαριασμού χρήστη',
+ 'system_notice.v3014_whitespace_collision.body':
+ 'Η αναβάθμιση 3.0.14 εντόπισε μία ή περισσότερες συγκρούσεις ονομάτων χρήστη ή email που προκλήθηκαν από κενά στην αρχή/τέλος αποθηκευμένων λογαριασμών. Οι επηρεαζόμενοι λογαριασμοί μετονομάστηκαν αυτόματα. Ελέγξτε τα logs του server για γραμμές που ξεκινούν με **[migration] WHITESPACE COLLISION** για να εντοπίσετε ποιοι λογαριασμοί χρειάζονται έλεγχο.',
+ 'system_notice.welcome_v1.title': 'Καλώς ήρθατε στο TREK',
+ 'system_notice.welcome_v1.body':
+ 'Ο πλήρης ταξιδιωτικός σας σχεδιαστής. Δημιουργήστε δρομολόγια, μοιραστείτε ταξίδια με φίλους και μείνετε οργανωμένοι — συνδεδεμένοι ή εκτός σύνδεσης.',
+ 'system_notice.welcome_v1.cta_label': 'Σχεδιάστε ένα ταξίδι',
+ 'system_notice.welcome_v1.hero_alt':
+ 'Ένας γραφικός ταξιδιωτικός προορισμός με επικάλυψη περιβάλλοντος σχεδιασμού TREK',
+ 'system_notice.welcome_v1.highlight_plan':
+ 'Δρομολόγια ανά ημέρα για κάθε ταξίδι',
+ 'system_notice.welcome_v1.highlight_share': 'Συνεργαστείτε με συνταξιδιώτες',
+ 'system_notice.welcome_v1.highlight_offline':
+ 'Λειτουργεί εκτός σύνδεσης σε κινητά',
+ 'system_notice.dev_test_modal.title': '[Dev] Δοκιμαστική ειδοποίηση',
+ 'system_notice.dev_test_modal.body':
+ 'Αυτή είναι μια δοκιμαστική ειδοποίηση μόνο για ανάπτυξη.',
+ 'system_notice.pager.prev': 'Προηγούμενη ειδοποίηση',
+ 'system_notice.pager.next': 'Επόμενη ειδοποίηση',
+ 'system_notice.pager.counter': '{current} / {total}',
+ 'system_notice.pager.goto': 'Μετάβαση στην ειδοποίηση {n}',
+ 'system_notice.pager.position': 'Ειδοποίηση {current} από {total}',
+};
+export default system_notice;
diff --git a/shared/src/i18n/gr/todo.ts b/shared/src/i18n/gr/todo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3a558d0f47e3891ca2377d4fdb44403118af1a9c
--- /dev/null
+++ b/shared/src/i18n/gr/todo.ts
@@ -0,0 +1,40 @@
+import type { TranslationStrings } from '../types';
+
+const todo: TranslationStrings = {
+ 'todo.subtab.packing': 'Λίστα Αποσκευών',
+ 'todo.subtab.todo': 'Εργασίες',
+ 'todo.completed': 'ολοκληρώθηκαν',
+ 'todo.filter.all': 'Όλα',
+ 'todo.filter.open': 'Ανοιχτά',
+ 'todo.filter.done': 'Ολοκληρωμένα',
+ 'todo.uncategorized': 'Χωρίς κατηγορία',
+ 'todo.namePlaceholder': 'Όνομα εργασίας',
+ 'todo.descriptionPlaceholder': 'Περιγραφή (προαιρετικό)',
+ 'todo.unassigned': 'Χωρίς ανάθεση',
+ 'todo.noCategory': 'Χωρίς κατηγορία',
+ 'todo.hasDescription': 'Έχει περιγραφή',
+ 'todo.addItem': 'Προσθήκη νέας εργασίας',
+ 'todo.sidebar.sortBy': 'Ταξινόμηση κατά',
+ 'todo.priority': 'Προτεραιότητα',
+ 'todo.newCategoryLabel': 'νέο',
+ 'todo.newCategory': 'Όνομα κατηγορίας',
+ 'todo.addCategory': 'Προσθήκη κατηγορίας',
+ 'todo.newItem': 'Νέα εργασία',
+ 'todo.empty': 'Δεν υπάρχουν εργασίες ακόμα. Προσθέστε μία για να ξεκινήσετε!',
+ 'todo.filter.my': 'Οι Εργασίες μου',
+ 'todo.filter.overdue': 'Εκπρόθεσμα',
+ 'todo.sidebar.tasks': 'Εργασίες',
+ 'todo.sidebar.categories': 'Κατηγορίες',
+ 'todo.detail.title': 'Εργασία',
+ 'todo.detail.description': 'Περιγραφή',
+ 'todo.detail.category': 'Κατηγορία',
+ 'todo.detail.dueDate': 'Ημερομηνία λήξης',
+ 'todo.detail.assignedTo': 'Ανατεθειμένο σε',
+ 'todo.detail.delete': 'Διαγραφή',
+ 'todo.detail.save': 'Αποθήκευση αλλαγών',
+ 'todo.sortByPrio': 'Προτεραιότητα',
+ 'todo.detail.priority': 'Προτεραιότητα',
+ 'todo.detail.noPriority': 'Καμία',
+ 'todo.detail.create': 'Δημιουργία εργασίας',
+};
+export default todo;
diff --git a/shared/src/i18n/gr/transport.ts b/shared/src/i18n/gr/transport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c6106a6476191d6e398906aa4218be874aad36e4
--- /dev/null
+++ b/shared/src/i18n/gr/transport.ts
@@ -0,0 +1,10 @@
+import type { TranslationStrings } from '../types';
+
+const transport: TranslationStrings = {
+ 'transport.addTransport': 'Προσθήκη μεταφοράς',
+ 'transport.modalTitle.create': 'Προσθήκη μεταφοράς',
+ 'transport.modalTitle.edit': 'Επεξεργασία μεταφοράς',
+ 'transport.title': 'Μεταφορές',
+ 'transport.addManual': 'Χειροκίνητη Μεταφορά',
+};
+export default transport;
diff --git a/shared/src/i18n/gr/trip.ts b/shared/src/i18n/gr/trip.ts
new file mode 100644
index 0000000000000000000000000000000000000000..96f44bce75d65430b237b5831ae67bdf5a307e68
--- /dev/null
+++ b/shared/src/i18n/gr/trip.ts
@@ -0,0 +1,32 @@
+import type { TranslationStrings } from '../types';
+
+const trip: TranslationStrings = {
+ 'trip.tabs.plan': 'Πλάνο',
+ 'trip.tabs.transports': 'Μεταφορές',
+ 'trip.tabs.reservations': 'Κρατήσεις',
+ 'trip.tabs.reservationsShort': 'Κράτ.',
+ 'trip.tabs.packing': 'Λίστα Αποσκευών',
+ 'trip.tabs.packingShort': 'Αποσκευές',
+ 'trip.tabs.lists': 'Λίστες',
+ 'trip.tabs.listsShort': 'Λίστες',
+ 'trip.tabs.budget': "Costs",
+ 'trip.tabs.files': 'Αρχεία',
+ 'trip.loading': 'Φόρτωση ταξιδιού...',
+ 'trip.loadingPhotos': 'Φόρτωση φωτογραφιών μέρους...',
+ 'trip.mobilePlan': 'Πλάνο',
+ 'trip.mobilePlaces': 'Μέρη',
+ 'trip.toast.placeUpdated': 'Το μέρος ενημερώθηκε',
+ 'trip.toast.placeAdded': 'Το μέρος προστέθηκε',
+ 'trip.toast.placeDeleted': 'Το μέρος διαγράφηκε',
+ 'trip.toast.selectDay': 'Παρακαλώ επιλέξτε πρώτα μια ημέρα',
+ 'trip.toast.assignedToDay': 'Το μέρος ανατέθηκε στην ημέρα',
+ 'trip.toast.reorderError': 'Η αναδιάταξη απέτυχε',
+ 'trip.toast.reservationUpdated': 'Η κράτηση ενημερώθηκε',
+ 'trip.toast.reservationAdded': 'Η κράτηση προστέθηκε',
+ 'trip.toast.deleted': 'Διαγράφηκε',
+ 'trip.confirm.deletePlace':
+ 'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το μέρος;',
+ 'trip.confirm.deletePlaces': 'Διαγραφή {count} μερών;',
+ 'trip.toast.placesDeleted': '{count} μέρη διαγράφηκαν',
+};
+export default trip;
diff --git a/shared/src/i18n/gr/trips.ts b/shared/src/i18n/gr/trips.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8223c63505f4ca7c8bc978c22a23a7ac34920dda
--- /dev/null
+++ b/shared/src/i18n/gr/trips.ts
@@ -0,0 +1,17 @@
+import type { TranslationStrings } from '../types';
+
+const trips: TranslationStrings = {
+ 'trips.memberRemoved': '{username} διαγράφηκε',
+ 'trips.memberRemoveError': 'Αποτυχία διαγραφής',
+ 'trips.memberAdded': '{username} προστέθηκε',
+ 'trips.memberAddError': 'Αποτυχία προσθήκης',
+ 'trips.reminder': 'Ειδοποίηση',
+ 'trips.reminderNone': 'Κανένα',
+ 'trips.reminderDay': 'ημέρα',
+ 'trips.reminderDays': 'ημέρες',
+ 'trips.reminderCustom': 'Προσαρμοσμένη',
+ 'trips.reminderDaysBefore': 'ημέρες πριν από την αναχώση',
+ 'trips.reminderDisabledHint':
+ 'Η ειδοποίηση για τις υπενθυμήσεις είναι απενεργοποιημένη. Ενεργοποιήστε την στο Διαχείριση > Ρυθμίσεις > Ανακοινώσεις.',
+};
+export default trips;
diff --git a/shared/src/i18n/gr/undo.ts b/shared/src/i18n/gr/undo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ba019d3fb2884f52b3c52e7aae8c44b3568b533a
--- /dev/null
+++ b/shared/src/i18n/gr/undo.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const undo: TranslationStrings = {
+ 'undo.button': 'Αναίρεση',
+ 'undo.tooltip': 'Αναίρεση: {action}',
+ 'undo.assignPlace': 'Η τοποθεσία ανατέθηκε στην ημέρα',
+ 'undo.removeAssignment': 'Η τοποθεσία αφαιρέθηκε από την ημέρα',
+ 'undo.reorder': 'Οι τοποθεσίες αναδιατάχθηκαν',
+ 'undo.optimize': 'Η διαδρομή βελτιστοποιήθηκε',
+ 'undo.deletePlace': 'Η τοποθεσία διαγράφηκε',
+ 'undo.deletePlaces': 'Οι τοποθεσίες διαγράφηκαν',
+ 'undo.moveDay': 'Η τοποθεσία μεταφέρθηκε σε άλλη ημέρα',
+ 'undo.lock': 'Το κλείδωμα τοποθεσίας εναλλάχθηκε',
+ 'undo.importGpx': 'Εισαγωγή GPX',
+ 'undo.importKeyholeMarkup': 'Εισαγωγή KMZ/KML',
+ 'undo.importGoogleList': 'Εισαγωγή Google Maps',
+ 'undo.importNaverList': 'Εισαγωγή Naver Maps',
+ 'undo.addPlace': 'Η τοποθεσία προστέθηκε',
+ 'undo.done': 'Αναιρέθηκε: {action}',
+ 'undo.importBooking': 'Εισαγωγή επιβεβαίωσης κράτησης',
+};
+export default undo;
diff --git a/shared/src/i18n/gr/vacay.ts b/shared/src/i18n/gr/vacay.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bebd2aa4fd429afbc9084324412c73e438592920
--- /dev/null
+++ b/shared/src/i18n/gr/vacay.ts
@@ -0,0 +1,109 @@
+import type { TranslationStrings } from '../types';
+
+const vacay: TranslationStrings = {
+ 'vacay.subtitle': 'Σχεδιάστε και διαχειριστείτε ημέρες διακοπών',
+ 'vacay.settings': 'Ρυθμίσεις',
+ 'vacay.year': 'Έτος',
+ 'vacay.addYear': 'Προσθήκη επόμενου έτους',
+ 'vacay.addPrevYear': 'Προσθήκη προηγούμενου έτους',
+ 'vacay.removeYear': 'Αφαίρεση έτους',
+ 'vacay.removeYearConfirm': 'Αφαίρεση {year};',
+ 'vacay.removeYearHint':
+ 'Όλες οι καταχωρήσεις διακοπών και οι αργίες της εταιρείας για αυτό το έτος θα διαγραφούν οριστικά.',
+ 'vacay.remove': 'Αφαίρεση',
+ 'vacay.persons': 'Άτομα',
+ 'vacay.noPersons': 'Δεν έχουν προστεθεί άτομα',
+ 'vacay.addPerson': 'Προσθήκη Ατόμου',
+ 'vacay.editPerson': 'Επεξεργασία Ατόμου',
+ 'vacay.removePerson': 'Αφαίρεση Ατόμου',
+ 'vacay.removePersonConfirm': 'Αφαίρεση {name};',
+ 'vacay.removePersonHint':
+ 'Όλες οι καταχωρήσεις διακοπών για αυτό το άτομο θα διαγραφούν οριστικά.',
+ 'vacay.personName': 'Όνομα',
+ 'vacay.personNamePlaceholder': 'Εισαγάγετε όνομα',
+ 'vacay.color': 'Χρώμα',
+ 'vacay.add': 'Προσθήκη',
+ 'vacay.legend': 'Υπόμνημα',
+ 'vacay.publicHoliday': 'Δημόσια Αργία',
+ 'vacay.companyHoliday': 'Αργία Εταιρείας',
+ 'vacay.weekend': 'Σαββατοκύριακο',
+ 'vacay.modeVacation': 'Διακοπές',
+ 'vacay.modeCompany': 'Αργία Εταιρείας',
+ 'vacay.entitlement': 'Δικαίωμα',
+ 'vacay.entitlementDays': 'Ημέρες',
+ 'vacay.used': 'Χρησιμοποιημένες',
+ 'vacay.remaining': 'Υπόλοιπο',
+ 'vacay.carriedOver': 'από {year}',
+ 'vacay.blockWeekends': 'Αποκλεισμός Σαββατοκύριακων',
+ 'vacay.blockWeekendsHint':
+ 'Αποτρέψτε καταχωρήσεις διακοπών σε ημέρες σαββατοκύριακου',
+ 'vacay.weekendDays': 'Ημέρες σαββατοκύριακου',
+ 'vacay.mon': 'Δευ',
+ 'vacay.tue': 'Τρι',
+ 'vacay.wed': 'Τετ',
+ 'vacay.thu': 'Πεμ',
+ 'vacay.fri': 'Παρ',
+ 'vacay.sat': 'Σαβ',
+ 'vacay.sun': 'Κυρ',
+ 'vacay.publicHolidays': 'Δημόσιες Αργίες',
+ 'vacay.publicHolidaysHint': 'Σήμανση δημόσιων αργιών στο ημερολόγιο',
+ 'vacay.selectCountry': 'Επιλογή χώρας',
+ 'vacay.selectRegion': 'Επιλογή περιοχής (προαιρετικό)',
+ 'vacay.addCalendar': 'Προσθήκη ημερολογίου',
+ 'vacay.calendarLabel': 'Ετικέτα (προαιρετικό)',
+ 'vacay.calendarColor': 'Χρώμα',
+ 'vacay.noCalendars': 'Δεν έχουν προστεθεί ημερολόγια αργιών ακόμη',
+ 'vacay.companyHolidays': 'Αργίες Εταιρείας',
+ 'vacay.companyHolidaysHint':
+ 'Επιτρέψτε τη σήμανση αργιών για όλη την εταιρεία',
+ 'vacay.companyHolidaysNoDeduct':
+ 'Οι αργίες της εταιρείας δεν προσμετρώνται στις ημέρες διακοπών.',
+ 'vacay.weekStart': 'Η εβδομάδα ξεκινά την',
+ 'vacay.weekStartHint':
+ 'Επιλέξτε αν η εβδομάδα του ημερολογίου ξεκινά Δευτέρα ή Κυριακή',
+ 'vacay.carryOver': 'Μεταφορά',
+ 'vacay.carryOverHint':
+ 'Αυτόματη μεταφορά των υπολοίπων ημερών διακοπών στο επόμενο έτος',
+ 'vacay.sharing': 'Κοινή Χρήση',
+ 'vacay.sharingHint':
+ 'Μοιραστείτε το πλάνο διακοπών σας με άλλους χρήστες TREK',
+ 'vacay.owner': 'Κάτοχος',
+ 'vacay.shareEmailPlaceholder': 'Email χρήστη TREK',
+ 'vacay.shareSuccess': 'Το πλάνο μοιράστηκε επιτυχώς',
+ 'vacay.shareError': 'Δεν ήταν δυνατή η κοινοποίηση του πλάνου',
+ 'vacay.dissolve': 'Διάλυση Συγχώνευσης',
+ 'vacay.dissolveHint':
+ 'Διαχωρίστε ξανά τα ημερολόγια. Οι καταχωρήσεις σας θα διατηρηθούν.',
+ 'vacay.dissolveAction': 'Διάλυση',
+ 'vacay.dissolved': 'Το ημερολόγιο διαχωρίστηκε',
+ 'vacay.fusedWith': 'Συγχωνευμένο με',
+ 'vacay.you': 'εσείς',
+ 'vacay.noData': 'Δεν υπάρχουν δεδομένα',
+ 'vacay.changeColor': 'Αλλαγή χρώματος',
+ 'vacay.inviteUser': 'Πρόσκληση Χρήστη',
+ 'vacay.inviteHint':
+ 'Προσκαλέστε έναν άλλο χρήστη TREK για να μοιραστείτε ένα κοινό ημερολόγιο διακοπών.',
+ 'vacay.selectUser': 'Επιλογή χρήστη',
+ 'vacay.sendInvite': 'Αποστολή Πρόσκλησης',
+ 'vacay.inviteSent': 'Η πρόσκληση στάλθηκε',
+ 'vacay.inviteError': 'Δεν ήταν δυνατή η αποστολή της πρόσκλησης',
+ 'vacay.pending': 'εκκρεμεί',
+ 'vacay.noUsersAvailable': 'Δεν υπάρχουν διαθέσιμοι χρήστες',
+ 'vacay.accept': 'Αποδοχή',
+ 'vacay.decline': 'Απόρριψη',
+ 'vacay.acceptFusion': 'Αποδοχή & Συγχώνευση',
+ 'vacay.inviteTitle': 'Αίτημα Συγχώνευσης',
+ 'vacay.inviteWantsToFuse':
+ 'θέλει να μοιραστεί ένα ημερολόγιο διακοπών μαζί σας.',
+ 'vacay.fuseInfo1':
+ 'Και οι δύο θα βλέπετε όλες τις καταχωρήσεις διακοπών σε ένα κοινό ημερολόγιο.',
+ 'vacay.fuseInfo2':
+ 'Και τα δύο μέρη μπορούν να δημιουργούν και να επεξεργάζονται καταχωρήσεις ο ένας για τον άλλο.',
+ 'vacay.fuseInfo3':
+ 'Και τα δύο μέρη μπορούν να διαγράφουν καταχωρήσεις και να αλλάζουν τα δικαιώματα διακοπών.',
+ 'vacay.fuseInfo4':
+ 'Ρυθμίσεις όπως δημόσιες αργίες και αργίες εταιρείας μοιράζονται.',
+ 'vacay.fuseInfo5':
+ 'Η συγχώνευση μπορεί να διαλυθεί ανά πάσα στιγμή από οποιοδήποτε μέρος. Οι καταχωρήσεις σας θα διατηρηθούν.',
+};
+export default vacay;
diff --git a/shared/src/i18n/hu/admin.ts b/shared/src/i18n/hu/admin.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e5e28bc36ab1f64afe2235143696976bc7e1c7af
--- /dev/null
+++ b/shared/src/i18n/hu/admin.ts
@@ -0,0 +1,395 @@
+import type { TranslationStrings } from '../types';
+
+const admin: TranslationStrings = {
+ 'admin.notifications.title': 'Értesítések',
+ 'admin.notifications.hint':
+ 'Válasszon értesítési csatornát. Egyszerre csak egy lehet aktív.',
+ 'admin.notifications.none': 'Kikapcsolva',
+ 'admin.notifications.email': 'E-mail (SMTP)',
+ 'admin.notifications.webhook': 'Webhook',
+ 'admin.notifications.save': 'Értesítési beállítások mentése',
+ 'admin.notifications.saved': 'Értesítési beállítások mentve',
+ 'admin.notifications.testWebhook': 'Teszt webhook küldése',
+ 'admin.notifications.testWebhookSuccess': 'Teszt webhook sikeresen elküldve',
+ 'admin.notifications.testWebhookFailed': 'Teszt webhook küldése sikertelen',
+ 'admin.smtp.title': 'E-mail és értesítések',
+ 'admin.smtp.hint': 'SMTP konfiguráció e-mail értesítések küldéséhez.',
+ 'admin.smtp.testButton': 'Teszt e-mail küldése',
+ 'admin.webhook.hint':
+ 'Értesítések küldése külső webhookra (Discord, Slack stb.).',
+ 'admin.smtp.testSuccess': 'Teszt e-mail sikeresen elküldve',
+ 'admin.smtp.testFailed': 'Teszt e-mail küldése sikertelen',
+ 'admin.title': 'Adminisztráció',
+ 'admin.subtitle': 'Felhasználókezelés és rendszerbeállítások',
+ 'admin.tabs.users': 'Felhasználók',
+ 'admin.tabs.categories': 'Kategóriák',
+ 'admin.tabs.backup': 'Biztonsági mentés',
+ 'admin.stats.users': 'Felhasználók',
+ 'admin.stats.trips': 'Utazások',
+ 'admin.stats.places': 'Helyek',
+ 'admin.stats.photos': 'Fotók',
+ 'admin.stats.files': 'Fájlok',
+ 'admin.table.user': 'Felhasználó',
+ 'admin.table.email': 'E-mail',
+ 'admin.table.role': 'Szerepkör',
+ 'admin.table.created': 'Létrehozva',
+ 'admin.table.lastLogin': 'Utolsó belépés',
+ 'admin.table.actions': 'Műveletek',
+ 'admin.you': '(Te)',
+ 'admin.editUser': 'Felhasználó szerkesztése',
+ 'admin.newPassword': 'Új jelszó',
+ 'admin.newPasswordHint': 'Hagyd üresen a jelenlegi jelszó megtartásához',
+ 'admin.deleteUser':
+ '"{name}" felhasználó törlése? Minden utazás véglegesen törlődik.',
+ 'admin.deleteUserTitle': 'Felhasználó törlése',
+ 'admin.newPasswordPlaceholder': 'Új jelszó megadása…',
+ 'admin.toast.loadError': 'Nem sikerült betölteni az admin adatokat',
+ 'admin.toast.userUpdated': 'Felhasználó frissítve',
+ 'admin.toast.updateError': 'Nem sikerült frissíteni',
+ 'admin.toast.userDeleted': 'Felhasználó törölve',
+ 'admin.toast.deleteError': 'Nem sikerült törölni',
+ 'admin.toast.cannotDeleteSelf': 'Saját fiók nem törölhető',
+ 'admin.toast.userCreated': 'Felhasználó létrehozva',
+ 'admin.toast.createError': 'Nem sikerült létrehozni a felhasználót',
+ 'admin.toast.fieldsRequired':
+ 'Felhasználónév, e-mail és jelszó megadása kötelező',
+ 'admin.createUser': 'Felhasználó létrehozása',
+ 'admin.invite.title': 'Meghívó linkek',
+ 'admin.invite.subtitle':
+ 'Egyszer használatos regisztrációs linkek létrehozása',
+ 'admin.invite.create': 'Link létrehozása',
+ 'admin.invite.createAndCopy': 'Létrehozás és másolás',
+ 'admin.invite.empty': 'Még nincsenek meghívó linkek',
+ 'admin.invite.maxUses': 'Max. használat',
+ 'admin.invite.expiry': 'Lejárat',
+ 'admin.invite.uses': 'felhasználva',
+ 'admin.invite.expiresAt': 'lejár',
+ 'admin.invite.createdBy': 'készítette',
+ 'admin.invite.active': 'Aktív',
+ 'admin.invite.expired': 'Lejárt',
+ 'admin.invite.usedUp': 'Elhasználva',
+ 'admin.invite.copied': 'Meghívó link vágólapra másolva',
+ 'admin.invite.copyLink': 'Link másolása',
+ 'admin.invite.deleted': 'Meghívó link törölve',
+ 'admin.invite.createError': 'Nem sikerült létrehozni a meghívó linket',
+ 'admin.invite.deleteError': 'Nem sikerült törölni a meghívó linket',
+ 'admin.tabs.settings': 'Beállítások',
+ 'admin.allowRegistration': 'Regisztráció engedélyezése',
+ 'admin.allowRegistrationHint': 'Új felhasználók regisztrálhatják magukat',
+ 'admin.authMethods': 'Authentication Methods',
+ 'admin.passwordLogin': 'Password Login',
+ 'admin.passwordLoginHint': 'Allow users to sign in with email and password',
+ 'admin.passwordRegistration': 'Password Registration',
+ 'admin.passwordRegistrationHint':
+ 'Allow new users to register with email and password',
+ 'admin.oidcLogin': 'SSO Login',
+ 'admin.oidcLoginHint': 'Allow users to sign in with SSO',
+ 'admin.oidcRegistration': 'SSO Auto-Provisioning',
+ 'admin.oidcRegistrationHint':
+ 'Automatically create accounts for new SSO users',
+ 'admin.envOverrideHint':
+ 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.',
+ 'admin.lockoutWarning': 'At least one login method must remain enabled',
+ 'admin.requireMfa': 'Kétlépcsős hitelesítés (2FA) kötelezővé tétele',
+ 'admin.requireMfaHint':
+ 'A 2FA nélküli felhasználóknak a Beállításokban kell befejezniük a beállítást az alkalmazás használata előtt.',
+ 'admin.apiKeys': 'API kulcsok',
+ 'admin.apiKeysHint':
+ 'Opcionális. Bővített helyadatokat tesz lehetővé, például fotókat és időjárást.',
+ 'admin.mapsKey': 'Google Maps API kulcs',
+ 'admin.mapsKeyHint':
+ 'Helykereséshez szükséges. Létrehozás: console.cloud.google.com',
+ 'admin.mapsKeyHintLong':
+ 'API kulcs nélkül az OpenStreetMap szolgál helykeresésre. Google API kulccsal képek, értékelések és nyitvatartás is betölthetők. Létrehozás: console.cloud.google.com.',
+ 'admin.recommended': 'Ajánlott',
+ 'admin.weatherKey': 'OpenWeatherMap API kulcs',
+ 'admin.weatherKeyHint': 'Időjárás adatokhoz. Ingyenes: openweathermap.org',
+ 'admin.validateKey': 'Teszt',
+ 'admin.keyValid': 'Csatlakozva',
+ 'admin.keyInvalid': 'Érvénytelen',
+ 'admin.keySaved': 'API kulcsok mentve',
+ 'admin.oidcTitle': 'Egyszeri bejelentkezés (OIDC)',
+ 'admin.oidcSubtitle':
+ 'Bejelentkezés külső szolgáltatókon keresztül, pl. Google, Apple, Authentik vagy Keycloak.',
+ 'admin.oidcDisplayName': 'Megjelenítendő név',
+ 'admin.oidcIssuer': 'Issuer URL',
+ 'admin.oidcIssuerHint':
+ 'A szolgáltató OpenID Connect Issuer URL-je. pl. https://accounts.google.com',
+ 'admin.oidcSaved': 'OIDC konfiguráció mentve',
+ 'admin.oidcOnlyMode': 'Jelszavas hitelesítés letiltása',
+ 'admin.oidcOnlyModeHint':
+ 'Ha engedélyezve van, csak SSO bejelentkezés lehetséges. A jelszavas bejelentkezés és regisztráció le van tiltva.',
+ 'admin.fileTypes': 'Engedélyezett fájltípusok',
+ 'admin.fileTypesHint':
+ 'Állítsd be, milyen fájltípusokat tölthetnek fel a felhasználók.',
+ 'admin.fileTypesFormat':
+ 'Vesszővel elválasztott kiterjesztések (pl. jpg,png,pdf,doc). Használj *-ot az összes típus engedélyezéséhez.',
+ 'admin.fileTypesSaved': 'Fájltípus-beállítások mentve',
+ 'admin.placesPhotos.title': 'Helyfotók',
+ 'admin.placesPhotos.subtitle':
+ 'Fotók lekérése a Google Places API-ból. Tiltsa le az API-kvóta megtakarításához. A Wikimedia-fotók nem érintettek.',
+ 'admin.placesAutocomplete.title': 'Hely automatikus kiegészítése',
+ 'admin.placesAutocomplete.subtitle':
+ 'A Google Places API használata keresési javaslatokhoz. Tiltsa le az API-kvóta megtakarításához.',
+ 'admin.placesDetails.title': 'Hely részletei',
+ 'admin.placesDetails.subtitle':
+ 'Részletes helyinformációk lekérése (nyitvatartás, értékelés, weboldal) a Google Places API-ból. Tiltsa le az API-kvóta megtakarításához.',
+ 'admin.bagTracking.title': 'Poggyászkövetés',
+ 'admin.bagTracking.subtitle':
+ 'Súly- és táskahozzárendelés engedélyezése csomagolási tételeknél',
+ 'admin.collab.chat.title': 'Chat',
+ 'admin.collab.chat.subtitle': 'Valós idejű üzenetküldés az együttműködéshez',
+ 'admin.collab.notes.title': 'Jegyzetek',
+ 'admin.collab.notes.subtitle': 'Megosztott jegyzetek és dokumentumok',
+ 'admin.collab.polls.title': 'Szavazások',
+ 'admin.collab.polls.subtitle': 'Csoportos szavazások',
+ 'admin.collab.whatsnext.title': 'Mi következik',
+ 'admin.collab.whatsnext.subtitle':
+ 'Tevékenységjavaslatok és következő lépések',
+ 'admin.tabs.config': 'Személyre szabás',
+ 'admin.tabs.defaults': 'Alapértelmezett beállítások',
+ 'admin.defaultSettings.title': 'Alapértelmezett felhasználói beállítások',
+ 'admin.defaultSettings.description':
+ 'Állítson be alapértelmezett értékeket az egész példányra. Azok a felhasználók, akik nem módosítottak egy beállítást, ezeket az értékeket fogják látni. A saját módosításaik mindig elsőbbséget élveznek.',
+ 'admin.defaultSettings.saved': 'Alapértelmezett mentve',
+ 'admin.defaultSettings.reset': 'Visszaállítás a beépített alapértelmezésre',
+ 'admin.defaultSettings.resetToBuiltIn': 'visszaállítás',
+ 'admin.tabs.templates': 'Csomagolási sablonok',
+ 'admin.packingTemplates.title': 'Csomagolási sablonok',
+ 'admin.packingTemplates.subtitle':
+ 'Újrafelhasználható csomagolási listák létrehozása utazásaidhoz',
+ 'admin.packingTemplates.create': 'Új sablon',
+ 'admin.packingTemplates.namePlaceholder':
+ 'Sablon neve (pl. Tengerparti nyaralás)',
+ 'admin.packingTemplates.empty': 'Még nincsenek sablonok',
+ 'admin.packingTemplates.items': 'tétel',
+ 'admin.packingTemplates.categories': 'kategória',
+ 'admin.packingTemplates.itemName': 'Tétel neve',
+ 'admin.packingTemplates.itemCategory': 'Kategória',
+ 'admin.packingTemplates.categoryName': 'Kategória neve (pl. Ruházat)',
+ 'admin.packingTemplates.addCategory': 'Kategória hozzáadása',
+ 'admin.packingTemplates.created': 'Sablon létrehozva',
+ 'admin.packingTemplates.deleted': 'Sablon törölve',
+ 'admin.packingTemplates.loadError': 'Nem sikerült betölteni a sablonokat',
+ 'admin.packingTemplates.createError': 'Nem sikerült létrehozni a sablont',
+ 'admin.packingTemplates.deleteError': 'Nem sikerült törölni a sablont',
+ 'admin.packingTemplates.saveError': 'Nem sikerült menteni',
+ 'admin.tabs.addons': 'Bővítmények',
+ 'admin.addons.title': 'Bővítmények',
+ 'admin.addons.subtitle':
+ 'Funkciók engedélyezése vagy letiltása a TREK testreszabásához.',
+ 'admin.addons.catalog.packing.name': 'Listák',
+ 'admin.addons.catalog.packing.description':
+ 'Csomagolási listák és teendők az utazásaidhoz',
+ 'admin.addons.catalog.budget.name': 'Költségvetés',
+ 'admin.addons.catalog.budget.description':
+ 'Kiadások nyomon követése és az utazási költségvetés tervezése',
+ 'admin.addons.catalog.documents.name': 'Dokumentumok',
+ 'admin.addons.catalog.documents.description':
+ 'Úti dokumentumok tárolása és kezelése',
+ 'admin.addons.catalog.vacay.name': 'Vacay',
+ 'admin.addons.catalog.vacay.description':
+ 'Személyes szabadságtervező naptárnézettel',
+ 'admin.addons.catalog.atlas.name': 'Atlasz',
+ 'admin.addons.catalog.atlas.description':
+ 'Világtérkép meglátogatott országokkal és utazási statisztikákkal',
+ 'admin.addons.catalog.collab.name': 'Együttműködés',
+ 'admin.addons.catalog.collab.description':
+ 'Valós idejű jegyzetek, szavazások és csevegés az utazás tervezéséhez',
+ 'admin.addons.catalog.memories.name': 'Fotók (Immich)',
+ 'admin.addons.catalog.memories.description':
+ 'Utazási fotók megosztása az Immich példányon keresztül',
+ 'admin.addons.catalog.mcp.name': 'MCP',
+ 'admin.addons.catalog.mcp.description':
+ 'Model Context Protocol AI asszisztens integrációhoz',
+ 'admin.addons.subtitleBefore': 'Funkciók engedélyezése vagy letiltása a ',
+ 'admin.addons.subtitleAfter': ' testreszabásához.',
+ 'admin.addons.enabled': 'Engedélyezve',
+ 'admin.addons.disabled': 'Letiltva',
+ 'admin.addons.type.trip': 'Utazás',
+ 'admin.addons.type.global': 'Globális',
+ 'admin.addons.type.integration': 'Integráció',
+ 'admin.addons.tripHint': 'Fülként érhető el minden utazáson belül',
+ 'admin.addons.globalHint': 'Önálló szekcióként elérhető a fő navigációban',
+ 'admin.addons.integrationHint':
+ 'Háttérszolgáltatások és API integrációk dedikált oldal nélkül',
+ 'admin.addons.toast.updated': 'Bővítmény frissítve',
+ 'admin.addons.toast.error': 'Nem sikerült frissíteni a bővítményt',
+ 'admin.addons.noAddons': 'Nincsenek elérhető bővítmények',
+ 'admin.weather.title': 'Időjárás adatok',
+ 'admin.weather.badge': '2026. március 24. óta',
+ 'admin.weather.description':
+ 'A TREK az Open-Meteo-t használja időjárás-adatforrásként. Az Open-Meteo egy ingyenes, nyílt forráskódú időjárás-szolgáltatás — nincs szükség API kulcsra.',
+ 'admin.weather.forecast': '16 napos előrejelzés',
+ 'admin.weather.forecastDesc': 'Korábban 5 nap volt (OpenWeatherMap)',
+ 'admin.weather.climate': 'Történelmi klímaadatok',
+ 'admin.weather.climateDesc':
+ 'Az elmúlt 85 év átlagai a 16 napos előrejelzésen túli napokhoz',
+ 'admin.weather.requests': '10 000 kérés / nap',
+ 'admin.weather.requestsDesc': 'Ingyenes, nincs szükség API kulcsra',
+ 'admin.weather.locationHint':
+ 'Az időjárás az adott nap első koordinátákkal rendelkező helye alapján készül. Ha nincs hely hozzárendelve a naphoz, a helylista bármelyik helye szolgál referenciául.',
+ 'admin.tabs.audit': 'Audit',
+ 'admin.audit.subtitle':
+ 'Biztonsági és adminisztrációs események (mentések, felhasználók, 2FA, beállítások).',
+ 'admin.audit.empty': 'Még nincsenek audit bejegyzések.',
+ 'admin.audit.refresh': 'Frissítés',
+ 'admin.audit.loadMore': 'Továbbiak betöltése',
+ 'admin.audit.showing': '{count} betöltve · {total} összesen',
+ 'admin.audit.col.time': 'Időpont',
+ 'admin.audit.col.user': 'Felhasználó',
+ 'admin.audit.col.action': 'Művelet',
+ 'admin.audit.col.resource': 'Erőforrás',
+ 'admin.audit.col.ip': 'IP',
+ 'admin.audit.col.details': 'Részletek',
+ 'admin.tabs.mcpTokens': 'MCP hozzáférés',
+ 'admin.mcpTokens.title': 'MCP hozzáférés',
+ 'admin.mcpTokens.subtitle':
+ 'OAuth munkamenetek és API tokenek kezelése az összes felhasználó számára',
+ 'admin.mcpTokens.sectionTitle': 'API tokenek',
+ 'admin.mcpTokens.owner': 'Tulajdonos',
+ 'admin.mcpTokens.tokenName': 'Token neve',
+ 'admin.mcpTokens.created': 'Létrehozva',
+ 'admin.mcpTokens.lastUsed': 'Utoljára használva',
+ 'admin.mcpTokens.never': 'Soha',
+ 'admin.mcpTokens.empty': 'Még nem hoztak létre MCP tokeneket',
+ 'admin.mcpTokens.deleteTitle': 'Token törlése',
+ 'admin.mcpTokens.deleteMessage':
+ 'Ez a token azonnal érvénytelenítésre kerül. A felhasználó elveszíti az MCP hozzáférést ezen a tokenen keresztül.',
+ 'admin.mcpTokens.deleteSuccess': 'Token törölve',
+ 'admin.mcpTokens.deleteError': 'Nem sikerült törölni a tokent',
+ 'admin.mcpTokens.loadError': 'Nem sikerült betölteni a tokeneket',
+ 'admin.oauthSessions.sectionTitle': 'OAuth munkamenetek',
+ 'admin.oauthSessions.clientName': 'Kliens',
+ 'admin.oauthSessions.owner': 'Tulajdonos',
+ 'admin.oauthSessions.scopes': 'Jogosultságok',
+ 'admin.oauthSessions.created': 'Létrehozva',
+ 'admin.oauthSessions.empty': 'Nincsenek aktív OAuth munkamenetek',
+ 'admin.oauthSessions.revokeTitle': 'Munkamenet visszavonása',
+ 'admin.oauthSessions.revokeMessage':
+ 'Ez az OAuth munkamenet azonnal visszavonásra kerül. A kliens elveszíti az MCP hozzáférést.',
+ 'admin.oauthSessions.revokeSuccess': 'Munkamenet visszavonva',
+ 'admin.oauthSessions.revokeError': 'Nem sikerült visszavonni a munkamenetet',
+ 'admin.oauthSessions.loadError':
+ 'Nem sikerült betölteni az OAuth munkameneteket',
+ 'admin.tabs.github': 'GitHub',
+ 'admin.github.title': 'Frissítési előzmények',
+ 'admin.github.subtitle': 'Legújabb frissítések: {repo}',
+ 'admin.github.latest': 'Legújabb',
+ 'admin.github.prerelease': 'Előzetes kiadás',
+ 'admin.github.showDetails': 'Részletek megjelenítése',
+ 'admin.github.hideDetails': 'Részletek elrejtése',
+ 'admin.github.loadMore': 'Továbbiak betöltése',
+ 'admin.github.loading': 'Betöltés...',
+ 'admin.github.error': 'Nem sikerült betölteni a kiadásokat',
+ 'admin.github.by': 'készítette',
+ 'admin.github.support': 'Segít fenntartani a TREK fejlesztését',
+ 'admin.update.available': 'Frissítés elérhető',
+ 'admin.update.text':
+ 'A TREK {version} elérhető. Jelenleg a {current} verziót használod.',
+ 'admin.update.button': 'Megtekintés a GitHubon',
+ 'admin.update.install': 'Frissítés telepítése',
+ 'admin.update.confirmTitle': 'Frissítés telepítése?',
+ 'admin.update.confirmText':
+ 'A TREK frissítésre kerül {current} verzióról {version} verzióra. A szerver ezután automatikusan újraindul.',
+ 'admin.update.dataInfo':
+ 'Minden adat (utazások, felhasználók, API kulcsok, feltöltések, Vacay, Atlas, költségvetések) megmarad.',
+ 'admin.update.warning':
+ 'Az alkalmazás az újraindítás alatt rövid ideig nem lesz elérhető.',
+ 'admin.update.confirm': 'Frissítés most',
+ 'admin.update.installing': 'Frissítés…',
+ 'admin.update.success': 'Frissítés telepítve! A szerver újraindul…',
+ 'admin.update.failed': 'Frissítés sikertelen',
+ 'admin.update.backupHint':
+ 'Javasoljuk, hogy frissítés előtt készíts biztonsági mentést.',
+ 'admin.update.backupLink': 'Biztonsági mentéshez',
+ 'admin.update.howTo': 'Frissítési útmutató',
+ 'admin.update.dockerText':
+ 'A TREK példányod Dockerben fut. A {version} verzióra frissítéshez futtasd a következő parancsokat a szervereden:',
+ 'admin.update.reloadHint':
+ 'Kérjük, töltsd újra az oldalt néhány másodperc múlva.',
+ 'admin.tabs.permissions': 'Jogosultságok',
+ 'admin.notifications.emailPanel.title': 'Email (SMTP)',
+ 'admin.notifications.webhookPanel.title': 'Webhook',
+ 'admin.notifications.inappPanel.title': 'In-App',
+ 'admin.notifications.inappPanel.hint':
+ 'Az alkalmazáson belüli értesítések mindig aktívak, és globálisan nem kapcsolhatók ki.',
+ 'admin.notifications.adminWebhookPanel.title': 'Admin webhook',
+ 'admin.notifications.adminWebhookPanel.hint':
+ 'Ez a webhook kizárólag admin értesítésekhez használatos (pl. verziófrissítési figyelmeztetések). Független a felhasználói webhookoktól, és automatikusan küld, ha URL van beállítva.',
+ 'admin.notifications.adminWebhookPanel.saved': 'Admin webhook URL mentve',
+ 'admin.notifications.adminWebhookPanel.testSuccess':
+ 'Teszt webhook sikeresen elküldve',
+ 'admin.notifications.adminWebhookPanel.testFailed':
+ 'Teszt webhook sikertelen',
+ 'admin.notifications.adminWebhookPanel.alwaysOnHint':
+ 'Az admin webhook automatikusan küld, ha URL van beállítva',
+ 'admin.notifications.ntfy': 'Ntfy',
+ 'admin.ntfy.hint':
+ 'Lehetővé teszi a felhasználóknak, hogy saját ntfy-témáikat konfigurálják push értesítésekhez. Állítsa be az alapértelmezett szervert alább a felhasználói beállítások előre kitöltéséhez.',
+ 'admin.notifications.testNtfy': 'Teszt Ntfy küldése',
+ 'admin.notifications.testNtfySuccess': 'Teszt Ntfy sikeresen elküldve',
+ 'admin.notifications.testNtfyFailed': 'Teszt Ntfy sikertelen',
+ 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy',
+ 'admin.notifications.adminNtfyPanel.hint':
+ 'Ez az Ntfy téma kizárólag admin értesítésekhez használatos (pl. verziófrissítési figyelmeztetések). Független a felhasználói témáktól, és mindig küld, ha konfigurálva van.',
+ 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy szerver URL',
+ 'admin.notifications.adminNtfyPanel.serverHint':
+ 'Alapértelmezett szerverként is szolgál a felhasználói ntfy értesítésekhez. Üresen hagyva ntfy.sh-t használ. A felhasználók felülírhatják saját beállításaikban.',
+ 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh',
+ 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin téma',
+ 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts',
+ 'admin.notifications.adminNtfyPanel.tokenLabel':
+ 'Hozzáférési token (opcionális)',
+ 'admin.notifications.adminNtfyPanel.tokenCleared':
+ 'Admin hozzáférési token törölve',
+ 'admin.notifications.adminNtfyPanel.saved': 'Admin Ntfy beállítások mentve',
+ 'admin.notifications.adminNtfyPanel.test': 'Teszt Ntfy küldése',
+ 'admin.notifications.adminNtfyPanel.testSuccess':
+ 'Teszt Ntfy sikeresen elküldve',
+ 'admin.notifications.adminNtfyPanel.testFailed': 'Teszt Ntfy sikertelen',
+ 'admin.notifications.adminNtfyPanel.alwaysOnHint':
+ 'Az admin Ntfy mindig küld, ha egy téma konfigurálva van',
+ 'admin.notifications.adminNotificationsHint':
+ 'Állítsa be, hogy mely csatornák szállítsák az admin értesítéseket (pl. verziófrissítési figyelmeztetések). A webhook automatikusan küld, ha admin webhook URL van megadva.',
+ 'admin.notifications.tripReminders.title': 'Utazási emlékeztetők',
+ 'admin.notifications.tripReminders.hint':
+ 'Emlékeztető értesítést küld az utazás kezdete előtt (az utazásnál megadott emlékeztető napok szükségesek).',
+ 'admin.notifications.tripReminders.enabled':
+ 'Utazási emlékeztetők engedélyezve',
+ 'admin.notifications.tripReminders.disabled': 'Utazási emlékeztetők letiltva',
+ 'admin.tabs.notifications': 'Értesítések',
+ 'admin.addons.catalog.journey.name': 'Útinaplók',
+ 'admin.addons.catalog.journey.description':
+ 'Utazáskövetés és útinapló bejelentkezésekkel, fotókkal és napi történetekkel',
+ 'admin.passkey.title': 'Passkey-bejelentkezés',
+ 'admin.passkey.cardHint':
+ 'Engedélyezd a felhasználóknak a bejelentkezést passkey-vel (WebAuthn). Alapból kikapcsolva.',
+ 'admin.passkey.login': 'Passkey-bejelentkezés engedélyezése',
+ 'admin.passkey.loginHint':
+ 'Jeleníts meg egy „Bejelentkezés passkey-jel" lehetőséget, és engedd, hogy a felhasználók passkey-ket regisztráljanak a beállításaikban.',
+ 'admin.passkey.notConfigured':
+ 'Ehhez a telepítéshez még nem oldódik fel WebAuthn-domain. Állítsd be az APP_URL-t vagy a lenti Relying Party ID-t — addig a passkey-k rejtve maradnak.',
+ 'admin.passkey.rpId': 'Relying Party ID (domain)',
+ 'admin.passkey.rpIdHint':
+ 'A csupasz domain, amelyhez a passkey-k kötődnek, pl. trek.example.org. Hagyd üresen, hogy az APP_URL-ből legyen levezetve. Későbbi módosítása érvényteleníti a meglévő passkey-ket.',
+ 'admin.passkey.origins': 'Engedélyezett origók',
+ 'admin.passkey.originsHint':
+ 'Vesszővel elválasztott teljes origók, pl. https://trek.example.org. Hagyd üresen az APP_URL használatához.',
+ 'admin.passkey.reset': 'Passkey-k visszaállítása',
+ 'admin.passkey.resetHint':
+ 'Eltávolítja a felhasználó összes passkey-jét (pl. elveszett eszköz esetén). A jelszavukkal továbbra is be tudnak jelentkezni.',
+ 'admin.passkey.resetConfirm': 'Eltávolítod {name} összes passkey-jét?',
+ 'admin.passkey.resetDone': '{count} passkey eltávolítva',
+ 'admin.defaultSettings.mapProvider': 'Térképmotor',
+ 'admin.defaultSettings.mapProviderHint': 'Az alapértelmezett térkép mindenkinek ezen a példányon. Minden felhasználó felülírhatja a saját beállításaiban.',
+ 'admin.defaultSettings.providerLeaflet': 'Alapértelmezett (ingyenes)',
+ 'admin.defaultSettings.providerMapbox': 'Mapbox (3D)',
+ 'admin.defaultSettings.mapboxToken': 'Megosztott Mapbox-token',
+ 'admin.defaultSettings.mapboxTokenHint': 'Minden olyan felhasználóhoz használatos, aki nem adta meg a saját tokenjét — így az egész példány eléri a Mapboxot anélkül, hogy egyenként kellene megosztani a kulcsot. Titkosítva tárolódik.',
+ 'admin.defaultSettings.mapboxStyle': 'Térképstílus',
+ 'admin.defaultSettings.mapboxStylePlaceholder': 'Válassz stílust…',
+ 'admin.defaultSettings.mapbox3d': '3D épületek & domborzat',
+ 'admin.defaultSettings.mapboxQuality': 'Kiváló minőségű mód',
+};
+export default admin;
diff --git a/shared/src/i18n/hu/airport.ts b/shared/src/i18n/hu/airport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..61d13ac43bc0570d35274d38c49dc411d9f2c48a
--- /dev/null
+++ b/shared/src/i18n/hu/airport.ts
@@ -0,0 +1,6 @@
+import type { TranslationStrings } from '../types';
+
+const airport: TranslationStrings = {
+ 'airport.searchPlaceholder': 'Repülőtér kódja vagy város (pl. FRA)',
+};
+export default airport;
diff --git a/shared/src/i18n/hu/atlas.ts b/shared/src/i18n/hu/atlas.ts
new file mode 100644
index 0000000000000000000000000000000000000000..72996477db661abd66a08fbe5b1d2bb7f8585ff7
--- /dev/null
+++ b/shared/src/i18n/hu/atlas.ts
@@ -0,0 +1,62 @@
+import type { TranslationStrings } from '../types';
+
+const atlas: TranslationStrings = {
+ 'atlas.subtitle': 'Utazási lábnyomod a világban',
+ 'atlas.countries': 'Országok',
+ 'atlas.trips': 'Utazások',
+ 'atlas.places': 'Helyek',
+ 'atlas.unmark': 'Eltávolítás',
+ 'atlas.confirmMark': 'Megjelölöd ezt az országot meglátogatottként?',
+ 'atlas.confirmUnmark':
+ 'Eltávolítod ezt az országot a meglátogatottak listájáról?',
+ 'atlas.confirmUnmarkRegion':
+ 'Eltávolítod ezt a régiót a meglátogatottak listájáról?',
+ 'atlas.markVisited': 'Megjelölés meglátogatottként',
+ 'atlas.markVisitedHint': 'Ország hozzáadása a meglátogatottak listájához',
+ 'atlas.markRegionVisitedHint':
+ 'Régió hozzáadása a meglátogatottak listájához',
+ 'atlas.addToBucket': 'Hozzáadás a bakancslistához',
+ 'atlas.addPoi': 'Hely hozzáadása',
+ 'atlas.searchCountry': 'Ország keresése...',
+ 'atlas.bucketNamePlaceholder': 'Név (ország, város, hely...)',
+ 'atlas.month': 'Hónap',
+ 'atlas.addToBucketHint': 'Mentés meglátogatni kívánt helyként',
+ 'atlas.bucketWhen': 'Mikor tervezed meglátogatni?',
+ 'atlas.statsTab': 'Statisztikák',
+ 'atlas.bucketTab': 'Bakancslista',
+ 'atlas.addBucket': 'Hozzáadás a bakancslistához',
+ 'atlas.bucketNotesPlaceholder': 'Jegyzetek (opcionális)',
+ 'atlas.bucketEmpty': 'A bakancslistád üres',
+ 'atlas.bucketEmptyHint': 'Adj hozzá helyeket, ahová álmodsz eljutni',
+ 'atlas.days': 'Napok',
+ 'atlas.visitedCountries': 'Meglátogatott országok',
+ 'atlas.cities': 'Városok',
+ 'atlas.noData': 'Még nincsenek utazási adatok',
+ 'atlas.noDataHint':
+ 'Hozz létre egy utazást és adj hozzá helyeket a világtérképhez',
+ 'atlas.lastTrip': 'Utolsó utazás',
+ 'atlas.nextTrip': 'Következő utazás',
+ 'atlas.daysLeft': 'nap van hátra',
+ 'atlas.streak': 'Sorozat',
+ 'atlas.year': 'év',
+ 'atlas.years': 'év',
+ 'atlas.yearInRow': 'egymást követő év',
+ 'atlas.yearsInRow': 'egymást követő év',
+ 'atlas.tripIn': 'utazás',
+ 'atlas.tripsIn': 'utazás',
+ 'atlas.since': 'óta',
+ 'atlas.europe': 'Európa',
+ 'atlas.asia': 'Ázsia',
+ 'atlas.northAmerica': 'É-Amerika',
+ 'atlas.southAmerica': 'D-Amerika',
+ 'atlas.africa': 'Afrika',
+ 'atlas.oceania': 'Óceánia',
+ 'atlas.other': 'Egyéb',
+ 'atlas.firstVisit': 'Első utazás',
+ 'atlas.lastVisitLabel': 'Utolsó utazás',
+ 'atlas.tripSingular': 'Utazás',
+ 'atlas.tripPlural': 'Utazások',
+ 'atlas.placeVisited': 'Meglátogatott hely',
+ 'atlas.placesVisited': 'Meglátogatott helyek',
+};
+export default atlas;
diff --git a/shared/src/i18n/hu/backup.ts b/shared/src/i18n/hu/backup.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7720e3bb8739d3aedcd2c2936c88c23c5097ae52
--- /dev/null
+++ b/shared/src/i18n/hu/backup.ts
@@ -0,0 +1,77 @@
+import type { TranslationStrings } from '../types';
+
+const backup: TranslationStrings = {
+ 'backup.title': 'Adatmentés',
+ 'backup.subtitle': 'Adatbázis és minden feltöltött fájl',
+ 'backup.refresh': 'Frissítés',
+ 'backup.upload': 'Mentés feltöltése',
+ 'backup.uploading': 'Feltöltés…',
+ 'backup.create': 'Mentés készítése',
+ 'backup.creating': 'Készítés…',
+ 'backup.empty': 'Még nincsenek mentések',
+ 'backup.createFirst': 'Első mentés készítése',
+ 'backup.download': 'Letöltés',
+ 'backup.restore': 'Visszaállítás',
+ 'backup.confirm.restore':
+ '"{name}" mentés visszaállítása?\n\nMinden jelenlegi adat a mentéssel lesz helyettesítve.',
+ 'backup.confirm.uploadRestore':
+ '"{name}" mentésfájl feltöltése és visszaállítása?\n\nMinden jelenlegi adat felülíródik.',
+ 'backup.confirm.delete': '"{name}" mentés törlése?',
+ 'backup.toast.loadError': 'Nem sikerült betölteni a mentéseket',
+ 'backup.toast.created': 'Mentés sikeresen létrehozva',
+ 'backup.toast.createError': 'Nem sikerült létrehozni a mentést',
+ 'backup.toast.restored': 'Mentés visszaállítva. Az oldal újratöltődik…',
+ 'backup.toast.restoreError': 'Nem sikerült visszaállítani',
+ 'backup.toast.uploadError': 'Nem sikerült feltölteni',
+ 'backup.toast.deleted': 'Mentés törölve',
+ 'backup.toast.deleteError': 'Nem sikerült törölni',
+ 'backup.toast.downloadError': 'Letöltés sikertelen',
+ 'backup.toast.settingsSaved': 'Automatikus mentés beállításai mentve',
+ 'backup.toast.settingsError': 'Nem sikerült menteni a beállításokat',
+ 'backup.auto.title': 'Automatikus mentés',
+ 'backup.auto.subtitle': 'Automatikus mentés ütemezés szerint',
+ 'backup.auto.enable': 'Automatikus mentés engedélyezése',
+ 'backup.auto.enableHint':
+ 'A mentések automatikusan készülnek a választott ütemezés szerint',
+ 'backup.auto.interval': 'Időköz',
+ 'backup.auto.hour': 'Futtatás időpontja',
+ 'backup.auto.hourHint': 'Szerver helyi ideje ({format} formátum)',
+ 'backup.auto.dayOfWeek': 'A hét napja',
+ 'backup.auto.dayOfMonth': 'A hónap napja',
+ 'backup.auto.dayOfMonthHint':
+ '1–28-ra korlátozva az összes hónappal való kompatibilitás érdekében',
+ 'backup.auto.scheduleSummary': 'Ütemezés',
+ 'backup.auto.summaryDaily': 'Minden nap {hour}:00-kor',
+ 'backup.auto.summaryWeekly': 'Minden {day} {hour}:00-kor',
+ 'backup.auto.summaryMonthly': 'Minden hónap {day}. napján {hour}:00-kor',
+ 'backup.auto.envLocked': 'Docker',
+ 'backup.auto.envLockedHint':
+ 'Az automatikus mentés Docker környezeti változókon keresztül van konfigurálva. A beállítások módosításához frissítsd a docker-compose.yml fájlt és indítsd újra a konténert.',
+ 'backup.auto.copyEnv': 'Docker env változók másolása',
+ 'backup.auto.envCopied': 'Docker env változók vágólapra másolva',
+ 'backup.auto.keepLabel': 'Régi mentések törlése ennyi idő után',
+ 'backup.dow.sunday': 'Va',
+ 'backup.dow.monday': 'Hé',
+ 'backup.dow.tuesday': 'Ke',
+ 'backup.dow.wednesday': 'Sze',
+ 'backup.dow.thursday': 'Csü',
+ 'backup.dow.friday': 'Pé',
+ 'backup.dow.saturday': 'Szo',
+ 'backup.interval.hourly': 'Óránként',
+ 'backup.interval.daily': 'Naponta',
+ 'backup.interval.weekly': 'Hetente',
+ 'backup.interval.monthly': 'Havonta',
+ 'backup.keep.1day': '1 nap',
+ 'backup.keep.3days': '3 nap',
+ 'backup.keep.7days': '7 nap',
+ 'backup.keep.14days': '14 nap',
+ 'backup.keep.30days': '30 nap',
+ 'backup.keep.forever': 'Örökre megőrzés',
+ 'backup.restoreConfirmTitle': 'Mentés visszaállítása?',
+ 'backup.restoreWarning':
+ 'Minden jelenlegi adat (utazások, helyek, felhasználók, feltöltések) véglegesen lecserélődik a mentéssel. Ez a művelet nem vonható vissza.',
+ 'backup.restoreTip':
+ 'Tipp: Készíts mentést a jelenlegi állapotról a visszaállítás előtt.',
+ 'backup.restoreConfirm': 'Igen, visszaállítás',
+};
+export default backup;
diff --git a/shared/src/i18n/hu/budget.ts b/shared/src/i18n/hu/budget.ts
new file mode 100644
index 0000000000000000000000000000000000000000..305b96e2e646e1cd1aed32a3c2f0f3d25b16ec63
--- /dev/null
+++ b/shared/src/i18n/hu/budget.ts
@@ -0,0 +1,117 @@
+import type { TranslationStrings } from '../types';
+
+const budget: TranslationStrings = {
+ 'budget.title': 'Költségvetés',
+ 'budget.exportCsv': 'CSV exportálás',
+ 'budget.emptyTitle': 'Még nincs költségvetés létrehozva',
+ 'budget.emptyText':
+ 'Hozz létre kategóriákat és bejegyzéseket az utazási költségvetés tervezéséhez',
+ 'budget.emptyPlaceholder': 'Kategória neve...',
+ 'budget.createCategory': 'Kategória létrehozása',
+ 'budget.category': 'Kategória',
+ 'budget.categoryName': 'Kategória neve',
+ 'budget.table.name': 'Név',
+ 'budget.table.total': 'Összesen',
+ 'budget.table.persons': 'Személyek',
+ 'budget.table.days': 'nap',
+ 'budget.table.perPerson': 'Személyenként',
+ 'budget.table.perDay': 'Naponta',
+ 'budget.table.perPersonDay': 'Fő / Nap',
+ 'budget.table.note': 'Megjegyzés',
+ 'budget.table.date': 'Dátum',
+ 'budget.newEntry': 'Új bejegyzés',
+ 'budget.defaultEntry': 'Új bejegyzés',
+ 'budget.defaultCategory': 'Új kategória',
+ 'budget.total': 'Összesen',
+ 'budget.totalBudget': 'Teljes költségvetés',
+ 'budget.byCategory': 'Kategóriánként',
+ 'budget.editTooltip': 'Kattints a szerkesztéshez',
+ 'budget.linkedToReservation':
+ 'Foglaláshoz kapcsolva — ott szerkessze a nevet',
+ 'budget.confirm.deleteCategory':
+ 'Biztosan törölni szeretnéd a(z) "{name}" kategóriát {count} bejegyzéssel?',
+ 'budget.deleteCategory': 'Kategória törlése',
+ 'budget.perPerson': 'Személyenként',
+ 'budget.paid': 'Fizetve',
+ 'budget.open': 'Nyitott',
+ 'budget.noMembers': 'Nincsenek résztvevők hozzárendelve',
+ 'budget.settlement': 'Elszámolás',
+ 'budget.settlementInfo':
+ 'Kattints egy tag avatárjára egy költségvetési tételen a zöld jelöléshez — ez azt jelenti, hogy fizetett. Az elszámolás ezután mutatja, ki kinek mennyivel tartozik.',
+ 'budget.netBalances': 'Nettó egyenlegek',
+ 'budget.categoriesLabel': 'kategóriák',
+ "costs.you": "Te",
+ "costs.youShort": "T",
+ "costs.youLower": "te",
+ "costs.youOwe": "Tartozol",
+ "costs.youOweSub": "Fizetned kell másoknak",
+ "costs.youreOwed": "Neked tartoznak",
+ "costs.youreOwedSub": "Mások fizetnek neked",
+ "costs.totalSpend": "Teljes utazási költség",
+ "costs.totalSpendSub": "Az összes utazóra vetítve",
+ "costs.to": "Kinek",
+ "costs.from": "Kitől",
+ "costs.allSettled": "Minden el van számolva",
+ "costs.nothingOwed": "Senki sem tartozik neked",
+ "costs.yourShare": "A te részed",
+ "costs.youPaid": "Te fizettél",
+ "costs.expenses": "Költségek",
+ "costs.entries": "{count} bejegyzés",
+ "costs.searchPlaceholder": "Költségek keresése…",
+ "costs.filter.all": "Mind",
+ "costs.filter.mine": "Én fizettem",
+ "costs.filter.owed": "Nekem tartoznak",
+ "costs.addExpense": "Költség hozzáadása",
+ "costs.editExpense": "Költség szerkesztése",
+ "costs.noMatch": "Nincs a keresésnek megfelelő költség.",
+ "costs.emptyText": "Még nincs költség. Add hozzá az elsőt.",
+ "costs.spent": "{amount} elköltve",
+ "costs.noDate": "Nincs dátum",
+ "costs.noOnePaid": "Még senki sem fizetett",
+ "costs.youLent": "{amount} kölcsönadtál",
+ "costs.youBorrowed": "{amount} kölcsönkértél",
+ "costs.settleUp": "Elszámolás",
+ "costs.history": "Előzmények",
+ "costs.everyoneSquare": "Mindenki kvittben van",
+ "costs.nothingOutstanding": "Jelenleg nincs kifizetendő összeg.",
+ "costs.pay": "fizet",
+ "costs.pays": "fizet",
+ "costs.settle": "Elszámol",
+ "costs.balances": "Egyenlegek",
+ "costs.byCategory": "Kategóriánként",
+ "costs.noCategories": "Még nincs költség.",
+ "costs.settleHistory": "Elszámolási előzmények",
+ "costs.noSettlements": "Még nincs elszámolt fizetés.",
+ "costs.paymentsSettled": "{count} fizetés elszámolva",
+ "costs.paid": "fizetve",
+ "costs.undo": "Visszavonás",
+ "costs.whatFor": "Mire volt?",
+ "costs.namePlaceholder": "pl. vacsora, ajándékok, benzin…",
+ "costs.totalAmount": "Teljes összeg",
+ "costs.currency": "Pénznem",
+ "costs.day": "Nap",
+ "costs.rateLabel": "1 {from} ennyi: {to}",
+ "costs.category": "Kategória",
+ "costs.whoPaid": "Ki fizetett?",
+ "costs.splitBetween": "Egyenlően elosztva köztük",
+ "costs.pickSomeone": "Válassz legalább egy személyt a megosztáshoz.",
+ "costs.splitSummary": "{count} fő közt megosztva · egyenként {amount}",
+ "costs.cat.accommodation": "Szállás",
+ "costs.cat.food": "Étel és ital",
+ "costs.cat.groceries": "Élelmiszer",
+ "costs.cat.transport": "Közlekedés",
+ "costs.cat.flights": "Repülőjáratok",
+ "costs.cat.activities": "Programok",
+ "costs.cat.sightseeing": "Városnézés",
+ "costs.cat.shopping": "Vásárlás",
+ "costs.cat.fees": "Díjak és jegyek",
+ "costs.cat.health": "Egészség",
+ "costs.cat.tips": "Borravaló",
+ "costs.cat.other": "Egyéb",
+ "costs.daysCount": "{count} nap",
+ "costs.travelers": "{count} utazó",
+ "costs.liveRate": "élő árfolyam",
+ "costs.settleAll": "Összes elszámolása",
+};
+
+export default budget;
diff --git a/shared/src/i18n/hu/categories.ts b/shared/src/i18n/hu/categories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cc5dafbb984e6c479e08202b02eec8c409dd2681
--- /dev/null
+++ b/shared/src/i18n/hu/categories.ts
@@ -0,0 +1,26 @@
+import type { TranslationStrings } from '../types';
+
+const categories: TranslationStrings = {
+ 'categories.title': 'Kategóriák',
+ 'categories.subtitle': 'Helyek kategóriáinak kezelése',
+ 'categories.new': 'Új kategória',
+ 'categories.empty': 'Még nincsenek kategóriák',
+ 'categories.namePlaceholder': 'Kategória neve',
+ 'categories.icon': 'Ikon',
+ 'categories.color': 'Szín',
+ 'categories.customColor': 'Egyéni szín kiválasztása',
+ 'categories.preview': 'Előnézet',
+ 'categories.defaultName': 'Kategória',
+ 'categories.update': 'Frissítés',
+ 'categories.create': 'Létrehozás',
+ 'categories.confirm.delete':
+ 'Kategória törlése? Az ebben a kategóriában lévő helyek nem törlődnek.',
+ 'categories.toast.loadError': 'Nem sikerült betölteni a kategóriákat',
+ 'categories.toast.nameRequired': 'Kérjük, adj meg egy nevet',
+ 'categories.toast.updated': 'Kategória frissítve',
+ 'categories.toast.created': 'Kategória létrehozva',
+ 'categories.toast.saveError': 'Nem sikerült menteni',
+ 'categories.toast.deleted': 'Kategória törölve',
+ 'categories.toast.deleteError': 'Nem sikerült törölni',
+};
+export default categories;
diff --git a/shared/src/i18n/hu/collab.ts b/shared/src/i18n/hu/collab.ts
new file mode 100644
index 0000000000000000000000000000000000000000..91504e260cff961b19a7c0c1486841151cd2b155
--- /dev/null
+++ b/shared/src/i18n/hu/collab.ts
@@ -0,0 +1,78 @@
+import type { TranslationStrings } from '../types';
+
+const collab: TranslationStrings = {
+ 'collab.tabs.chat': 'Csevegés',
+ 'collab.tabs.notes': 'Jegyzetek',
+ 'collab.tabs.polls': 'Szavazások',
+ 'collab.whatsNext.title': 'Mi következik',
+ 'collab.whatsNext.today': 'Ma',
+ 'collab.whatsNext.tomorrow': 'Holnap',
+ 'collab.whatsNext.empty': 'Nincsenek közelgő tevékenységek',
+ 'collab.whatsNext.until': '-ig',
+ 'collab.whatsNext.emptyHint':
+ 'Az időponttal rendelkező tevékenységek itt jelennek meg',
+ 'collab.chat.send': 'Küldés',
+ 'collab.chat.placeholder': 'Üzenet írása...',
+ 'collab.chat.empty': 'Kezdd el a beszélgetést',
+ 'collab.chat.emptyHint':
+ 'Az üzenetek az utazás minden tagjával meg vannak osztva',
+ 'collab.chat.emptyDesc':
+ 'Oszd meg ötleteidet, terveidet és híreidet az utazócsoportoddal',
+ 'collab.chat.today': 'Ma',
+ 'collab.chat.yesterday': 'Tegnap',
+ 'collab.chat.deletedMessage': 'törölt egy üzenetet',
+ 'collab.chat.reply': 'Válasz',
+ 'collab.chat.loadMore': 'Korábbi üzenetek betöltése',
+ 'collab.chat.justNow': 'éppen most',
+ 'collab.chat.minutesAgo': '{n} perce',
+ 'collab.chat.hoursAgo': '{n} órája',
+ 'collab.notes.title': 'Jegyzetek',
+ 'collab.notes.new': 'Új jegyzet',
+ 'collab.notes.empty': 'Még nincsenek jegyzetek',
+ 'collab.notes.emptyHint': 'Rögzítsd az ötleteidet és terveidet',
+ 'collab.notes.all': 'Összes',
+ 'collab.notes.titlePlaceholder': 'Jegyzet címe',
+ 'collab.notes.contentPlaceholder': 'Írj valamit...',
+ 'collab.notes.categoryPlaceholder': 'Kategória',
+ 'collab.notes.newCategory': 'Új kategória...',
+ 'collab.notes.category': 'Kategória',
+ 'collab.notes.noCategory': 'Nincs kategória',
+ 'collab.notes.color': 'Szín',
+ 'collab.notes.save': 'Mentés',
+ 'collab.notes.cancel': 'Mégse',
+ 'collab.notes.edit': 'Szerkesztés',
+ 'collab.notes.delete': 'Törlés',
+ 'collab.notes.confirmDeleteTitle': 'Törli a jegyzetet?',
+ 'collab.notes.confirmDeleteBody': 'Ez a jegyzet véglegesen törlődik.',
+ 'collab.notes.pin': 'Kitűzés',
+ 'collab.notes.unpin': 'Kitűzés eltávolítása',
+ 'collab.notes.daysAgo': '{n} napja',
+ 'collab.notes.categorySettings': 'Kategóriák kezelése',
+ 'collab.notes.create': 'Létrehozás',
+ 'collab.notes.website': 'Weboldal',
+ 'collab.notes.websitePlaceholder': 'https://...',
+ 'collab.notes.attachFiles': 'Fájlok csatolása',
+ 'collab.notes.noCategoriesYet': 'Még nincsenek kategóriák',
+ 'collab.notes.emptyDesc': 'Hozz létre egy jegyzetet a kezdéshez',
+ 'collab.polls.title': 'Szavazások',
+ 'collab.polls.new': 'Új szavazás',
+ 'collab.polls.empty': 'Még nincsenek szavazások',
+ 'collab.polls.emptyHint': 'Kérdezd meg a csoportot és szavazzatok együtt',
+ 'collab.polls.question': 'Kérdés',
+ 'collab.polls.questionPlaceholder': 'Mit csináljunk?',
+ 'collab.polls.addOption': 'Opció hozzáadása',
+ 'collab.polls.optionPlaceholder': '{n}. opció',
+ 'collab.polls.create': 'Szavazás létrehozása',
+ 'collab.polls.close': 'Lezárás',
+ 'collab.polls.closed': 'Lezárva',
+ 'collab.polls.votes': '{n} szavazat',
+ 'collab.polls.vote': '{n} szavazat',
+ 'collab.polls.multipleChoice': 'Többszörös választás',
+ 'collab.polls.multiChoice': 'Többszörös választás',
+ 'collab.polls.deadline': 'Határidő',
+ 'collab.polls.option': 'Opció',
+ 'collab.polls.options': 'Opciók',
+ 'collab.polls.delete': 'Törlés',
+ 'collab.polls.closedSection': 'Lezárva',
+};
+export default collab;
diff --git a/shared/src/i18n/hu/common.ts b/shared/src/i18n/hu/common.ts
new file mode 100644
index 0000000000000000000000000000000000000000..245cb8b3ba7fb862cc736b8042c8942c8e9afb58
--- /dev/null
+++ b/shared/src/i18n/hu/common.ts
@@ -0,0 +1,55 @@
+import type { TranslationStrings } from '../types';
+
+const common: TranslationStrings = {
+ 'common.save': 'Mentés',
+ 'common.showMore': 'Továbbiak',
+ 'common.showLess': 'Kevesebb',
+ 'common.cancel': 'Mégse',
+ 'common.clear': 'Törlés',
+ 'common.delete': 'Törlés',
+ 'common.edit': 'Szerkesztés',
+ 'common.add': 'Hozzáadás',
+ 'common.loading': 'Betöltés...',
+ 'common.import': 'Importálás',
+ 'common.select': 'Kiválaszt',
+ 'common.selectAll': 'Mindet kiválaszt',
+ 'common.deselectAll': 'Összes kijelölés megszüntetése',
+ 'common.error': 'Hiba',
+ 'common.unknownError': 'Ismeretlen hiba',
+ 'common.tooManyAttempts':
+ 'Túl sok próbálkozás. Kérjük, próbálja újra később.',
+ 'common.back': 'Vissza',
+ 'common.all': 'Összes',
+ 'common.close': 'Bezárás',
+ 'common.open': 'Megnyitás',
+ 'common.upload': 'Feltöltés',
+ 'common.search': 'Keresés',
+ 'common.confirm': 'Megerősítés',
+ 'common.ok': 'OK',
+ 'common.yes': 'Igen',
+ 'common.no': 'Nem',
+ 'common.or': 'vagy',
+ 'common.none': 'Nincs',
+ 'common.date': 'Dátum',
+ 'common.rename': 'Átnevezés',
+ 'common.discardChanges': 'Változtatások elvetése',
+ 'common.discard': 'Elveti',
+ 'common.name': 'Név',
+ 'common.email': 'E-mail',
+ 'common.password': 'Jelszó',
+ 'common.saving': 'Mentés...',
+ 'common.expand': 'Kibontás',
+ 'common.collapse': 'Összecsukás',
+ 'common.saved': 'Mentve',
+ 'common.update': 'Frissítés',
+ 'common.change': 'Módosítás',
+ 'common.uploading': 'Feltöltés…',
+ 'common.backToPlanning': 'Vissza a tervezéshez',
+ 'common.reset': 'Visszaállítás',
+ 'common.copy': 'Másolás',
+ 'common.copied': 'Másolva',
+ 'common.justNow': 'az imént',
+ 'common.hoursAgo': '{count} órája',
+ 'common.daysAgo': '{count} napja',
+};
+export default common;
diff --git a/shared/src/i18n/hu/dashboard.ts b/shared/src/i18n/hu/dashboard.ts
new file mode 100644
index 0000000000000000000000000000000000000000..91d35438418c18824573171acba0164d1d390ad9
--- /dev/null
+++ b/shared/src/i18n/hu/dashboard.ts
@@ -0,0 +1,169 @@
+import type { TranslationStrings } from '../types';
+
+const dashboard: TranslationStrings = {
+ 'dashboard.title': 'Utazásaim',
+ 'dashboard.subtitle.loading': 'Utazások betöltése...',
+ 'dashboard.subtitle.trips': '{count} utazás ({archived} archivált)',
+ 'dashboard.subtitle.empty': 'Indítsd el az első utazásodat',
+ 'dashboard.subtitle.activeOne': '{count} aktív utazás',
+ 'dashboard.subtitle.activeMany': '{count} aktív utazás',
+ 'dashboard.subtitle.archivedSuffix': ' · {count} archivált',
+ 'dashboard.newTrip': 'Új utazás',
+ 'dashboard.newTripSub': 'Tervezz egy új utazást a nulláról',
+ 'dashboard.gridView': 'Rácsnézet',
+ 'dashboard.listView': 'Listanézet',
+ 'dashboard.currency': 'Pénznem',
+ 'dashboard.timezone': 'Időzónák',
+ 'dashboard.localTime': 'Helyi',
+ 'dashboard.timezoneCustomTitle': 'Egyéni időzóna',
+ 'dashboard.timezoneCustomLabelPlaceholder': 'Címke (opcionális)',
+ 'dashboard.timezoneCustomTzPlaceholder': 'pl. America/New_York',
+ 'dashboard.timezoneCustomAdd': 'Hozzáadás',
+ 'dashboard.timezoneCustomErrorEmpty': 'Adj meg egy időzóna-azonosítót',
+ 'dashboard.timezoneCustomErrorInvalid':
+ 'Érvénytelen időzóna. Használj Europe/Berlin formátumot',
+ 'dashboard.timezoneCustomErrorDuplicate': 'Már hozzáadva',
+ 'dashboard.emptyTitle': 'Még nincsenek utazások',
+ 'dashboard.emptyText':
+ 'Hozd létre az első utazásodat, és kezdj el tervezni helyeket, napi programokat és csomagolási listákat.',
+ 'dashboard.emptyButton': 'Első utazás létrehozása',
+ 'dashboard.nextTrip': 'Következő utazás',
+ 'dashboard.shared': 'Megosztott',
+ 'dashboard.sharedBy': 'Megosztotta: {name}',
+ 'dashboard.days': 'nap',
+ 'dashboard.places': 'hely',
+ 'dashboard.members': 'Útitársak',
+ 'dashboard.archive': 'Archiválás',
+ 'dashboard.copyTrip': 'Másolás',
+ 'dashboard.copySuffix': 'másolat',
+ 'dashboard.restore': 'Visszaállítás',
+ 'dashboard.archived': 'Archivált',
+ 'dashboard.status.ongoing': 'Folyamatban',
+ 'dashboard.status.today': 'Ma',
+ 'dashboard.status.tomorrow': 'Holnap',
+ 'dashboard.status.past': 'Múlt',
+ 'dashboard.status.daysLeft': 'Még {count} nap',
+ 'dashboard.toast.loadError': 'Nem sikerült betölteni az utazásokat',
+ 'dashboard.toast.created': 'Utazás sikeresen létrehozva!',
+ 'dashboard.toast.createError': 'Nem sikerült létrehozni',
+ 'dashboard.toast.updated': 'Utazás frissítve!',
+ 'dashboard.toast.updateError': 'Nem sikerült frissíteni',
+ 'dashboard.toast.deleted': 'Utazás törölve',
+ 'dashboard.toast.deleteError': 'Nem sikerült törölni',
+ 'dashboard.toast.archived': 'Utazás archiválva',
+ 'dashboard.toast.archiveError': 'Nem sikerült archiválni',
+ 'dashboard.toast.restored': 'Utazás visszaállítva',
+ 'dashboard.toast.restoreError': 'Nem sikerült visszaállítani',
+ 'dashboard.toast.copied': 'Utazás másolva!',
+ 'dashboard.toast.copyError': 'Nem sikerült másolni az utazást',
+ 'dashboard.confirm.delete':
+ '"{title}" utazás törlése? Minden hely és terv véglegesen törlődik.',
+ 'dashboard.editTrip': 'Utazás szerkesztése',
+ 'dashboard.createTrip': 'Új utazás létrehozása',
+ 'dashboard.tripTitle': 'Cím',
+ 'dashboard.tripTitlePlaceholder': 'pl. Nyár Japánban',
+ 'dashboard.tripDescription': 'Leírás',
+ 'dashboard.tripDescriptionPlaceholder': 'Miről szól ez az utazás?',
+ 'dashboard.startDate': 'Kezdő dátum',
+ 'dashboard.endDate': 'Záró dátum',
+ 'dashboard.dayCount': 'Napok száma',
+ 'dashboard.dayCountHint':
+ 'Hány napot tervezzen, ha nincsenek utazási dátumok megadva.',
+ 'dashboard.noDateHint':
+ 'Nincs dátum megadva — 7 alapértelmezett nap jön létre. Ezt bármikor módosíthatod.',
+ 'dashboard.coverImage': 'Borítókép',
+ 'dashboard.addCoverImage': 'Borítókép hozzáadása',
+ 'dashboard.addMembers': 'Útitársak',
+ 'dashboard.addMember': 'Tag hozzáadása',
+ 'dashboard.coverSaved': 'Borítókép mentve',
+ 'dashboard.coverUploadError': 'Feltöltés sikertelen',
+ 'dashboard.coverRemoveError': 'Eltávolítás sikertelen',
+ 'dashboard.titleRequired': 'A cím megadása kötelező',
+ 'dashboard.endDateError': 'A záró dátumnak a kezdő dátum után kell lennie',
+ 'dashboard.greeting.morning': 'Jó reggelt,',
+ 'dashboard.greeting.afternoon': 'Jó napot,',
+ 'dashboard.greeting.evening': 'Jó estét,',
+ 'dashboard.mobile.liveNow': 'Most élőben',
+ 'dashboard.mobile.tripProgress': 'Út előrehaladása',
+ 'dashboard.mobile.daysLeft': 'még {count} nap',
+ 'dashboard.mobile.places': 'Helyszínek',
+ 'dashboard.mobile.buddies': 'Útitársak',
+ 'dashboard.mobile.newTrip': 'Új út',
+ 'dashboard.mobile.currency': 'Pénznem',
+ 'dashboard.mobile.timezone': 'Időzóna',
+ 'dashboard.mobile.upcomingTrips': 'Közelgő utak',
+ 'dashboard.mobile.yourTrips': 'Utaid',
+ 'dashboard.mobile.trips': 'út',
+ 'dashboard.mobile.starts': 'Kezdés',
+ 'dashboard.mobile.duration': 'Időtartam',
+ 'dashboard.mobile.day': 'nap',
+ 'dashboard.mobile.days': 'nap',
+ 'dashboard.mobile.ongoing': 'Folyamatban',
+ 'dashboard.mobile.startsToday': 'Ma kezdődik',
+ 'dashboard.mobile.tomorrow': 'Holnap',
+ 'dashboard.mobile.inDays': '{count} nap múlva',
+ 'dashboard.mobile.inMonths': '{count} hónap múlva',
+ 'dashboard.mobile.completed': 'Befejezett',
+ 'dashboard.mobile.currencyConverter': 'Pénznemváltó',
+ 'dashboard.filter.planned': 'Tervezett',
+ 'dashboard.hero.badgeLive': 'MOST ÉLŐBEN',
+ 'dashboard.hero.badgeToday': 'MA KEZDŐDIK',
+ 'dashboard.hero.badgeTomorrow': 'HOLNAP',
+ 'dashboard.hero.badgeNext': 'KÖVETKEZŐ',
+ 'dashboard.hero.badgeRecent': 'LEGUTÓBBI',
+ 'dashboard.hero.tripDates': 'Utazás dátumai',
+ 'dashboard.hero.noDates': 'Nincs dátum megadva',
+ 'dashboard.hero.travelerOne': '{count} utazó',
+ 'dashboard.hero.travelerMany': '{count} utazó',
+ 'dashboard.hero.destinationOne': '{count} úti cél',
+ 'dashboard.hero.destinationMany': '{count} úti cél',
+ 'dashboard.hero.dayUnitOne': 'nap',
+ 'dashboard.hero.dayUnitMany': 'nap',
+ 'dashboard.hero.dayLeft': 'nap van hátra',
+ 'dashboard.hero.daysLeft': 'nap van hátra',
+ 'dashboard.hero.lastDay': 'Utolsó nap',
+ 'dashboard.hero.untilStart': 'Indulásig',
+ 'dashboard.hero.startsIn': 'Indulásig',
+ 'dashboard.atlas.countriesVisited': 'Atlas · Meglátogatott országok',
+ 'dashboard.atlas.ofTotal': '/ {total}',
+ 'dashboard.atlas.tripsTotal': 'Utazások összesen',
+ 'dashboard.atlas.placesMapped': '{count} hely a térképen',
+ 'dashboard.atlas.daysTraveled': 'Utazási napok',
+ 'dashboard.atlas.daysUnit': 'nap',
+ 'dashboard.atlas.acrossAllTrips': 'az összes utazásban',
+ 'dashboard.atlas.distanceFlown': 'Megtett távolság',
+ 'dashboard.atlas.kmUnit': 'km',
+ 'dashboard.atlas.aroundEquator': '≈ {count}× az Egyenlítő körül',
+ 'dashboard.card.idea': 'Ötlet',
+ 'dashboard.card.buddyOne': 'Útitárs',
+ 'dashboard.fx.from': 'Erről',
+ 'dashboard.fx.to': 'Erre',
+ 'dashboard.fx.unavailable': 'Árfolyam nem elérhető',
+ 'dashboard.tz.searchPlaceholder': 'Időzóna keresése…',
+ 'dashboard.tz.empty': 'Még nincs több időzóna — adj hozzá egyet a + gombbal',
+ 'dashboard.upcoming.title': 'Közelgő foglalások',
+ 'dashboard.upcoming.empty': 'Még nincs semmi lefoglalva.',
+ 'dashboard.confirm.copy.title': 'Másolja ezt az utazást?',
+ 'dashboard.confirm.copy.willCopy': 'Másolásra kerül',
+ 'dashboard.confirm.copy.will1': 'Napok, helyek és napi hozzárendelések',
+ 'dashboard.confirm.copy.will2': 'Szállások és foglalások',
+ 'dashboard.confirm.copy.will3': 'Költségtételek és kategóriasorrend',
+ 'dashboard.confirm.copy.will4': 'Csomaglisták (kipipálatlan)',
+ 'dashboard.confirm.copy.will5': 'Teendők (hozzárendelés és pipa nélkül)',
+ 'dashboard.confirm.copy.will6': 'Napi jegyzetek',
+ 'dashboard.confirm.copy.wontCopy': 'Nem kerül másolásra',
+ 'dashboard.confirm.copy.wont1': 'Közreműködők és tag-hozzárendelések',
+ 'dashboard.confirm.copy.wont2': 'Közös jegyzetek, szavazások és üzenetek',
+ 'dashboard.confirm.copy.wont3': 'Fájlok és fotók',
+ 'dashboard.confirm.copy.wont4': 'Megosztási tokenek',
+ 'dashboard.confirm.copy.confirm': 'Utazás másolása',
+ 'dashboard.aria.toggleView': 'Nézet váltása',
+ 'dashboard.aria.filter': 'Szűrő',
+ 'dashboard.aria.duplicate': 'Duplikálás',
+ 'dashboard.aria.refreshRates': 'Árfolyamok frissítése',
+ 'dashboard.aria.swapCurrencies': 'Pénznemek cseréje',
+ 'dashboard.aria.addTimezone': 'Időzóna hozzáadása',
+ 'dashboard.aria.removeTimezone': '{city} eltávolítása',
+ 'dashboard.dayCountRequired': 'A napok száma kötelező',
+};
+export default dashboard;
diff --git a/shared/src/i18n/hu/day.ts b/shared/src/i18n/hu/day.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8656592a0655c30ce533679c4c0d4f10badf75de
--- /dev/null
+++ b/shared/src/i18n/hu/day.ts
@@ -0,0 +1,27 @@
+import type { TranslationStrings } from '../types';
+
+const day: TranslationStrings = {
+ 'day.precipProb': 'Csapadékvalószínűség',
+ 'day.precipitation': 'Csapadék',
+ 'day.wind': 'Szél',
+ 'day.sunrise': 'Napkelte',
+ 'day.sunset': 'Napnyugta',
+ 'day.hourlyForecast': 'Óránkénti előrejelzés',
+ 'day.climateHint':
+ 'Történelmi átlagok — valós előrejelzés a dátum előtti 16 napon belül érhető el.',
+ 'day.noWeather':
+ 'Nem állnak rendelkezésre időjárási adatok. Adj hozzá egy helyet koordinátákkal.',
+ 'day.overview': 'Napi áttekintés',
+ 'day.accommodation': 'Szállás',
+ 'day.addAccommodation': 'Szállás hozzáadása',
+ 'day.hotelDayRange': 'Alkalmazás napokra',
+ 'day.noPlacesForHotel': 'Először adj hozzá helyeket az utazásodhoz',
+ 'day.allDays': 'Összes',
+ 'day.checkIn': 'Bejelentkezés',
+ 'day.checkInUntil': 'Eddig',
+ 'day.checkOut': 'Kijelentkezés',
+ 'day.confirmation': 'Visszaigazolás',
+ 'day.editAccommodation': 'Szállás szerkesztése',
+ 'day.reservations': 'Foglalások',
+};
+export default day;
diff --git a/shared/src/i18n/hu/dayplan.ts b/shared/src/i18n/hu/dayplan.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b13e4047bf6d937caab43e2ea8effd3d538f177b
--- /dev/null
+++ b/shared/src/i18n/hu/dayplan.ts
@@ -0,0 +1,61 @@
+import type { TranslationStrings } from '../types';
+
+const dayplan: TranslationStrings = {
+ 'dayplan.icsTooltip': 'Naptár exportálása (ICS)',
+ 'dayplan.emptyDay': 'Nincs tervezett hely erre a napra',
+ 'dayplan.addNote': 'Jegyzet hozzáadása',
+ 'dayplan.editNote': 'Jegyzet szerkesztése',
+ 'dayplan.noteAdd': 'Jegyzet hozzáadása',
+ 'dayplan.noteEdit': 'Jegyzet szerkesztése',
+ 'dayplan.noteTitle': 'Jegyzet',
+ 'dayplan.noteSubtitle': 'Napi jegyzet',
+ 'dayplan.totalCost': 'Összköltség',
+ 'dayplan.days': 'nap',
+ 'dayplan.dayN': '{n}. nap',
+ 'dayplan.calculating': 'Számítás...',
+ 'dayplan.route': 'Útvonal',
+ 'dayplan.optimize': 'Optimalizálás',
+ 'dayplan.optimized': 'Útvonal optimalizálva',
+ 'dayplan.routeError': 'Nem sikerült kiszámítani az útvonalat',
+ 'dayplan.toast.needTwoPlaces':
+ 'Legalább két hely szükséges az útvonal-optimalizáláshoz',
+ 'dayplan.toast.routeOptimized': 'Útvonal optimalizálva',
+ 'dayplan.toast.routeOptimizedFromHotel':
+ 'Útvonal optimalizálva a szállásodtól',
+ 'dayplan.toast.noGeoPlaces':
+ 'Nem találhatók koordinátákkal rendelkező helyek az útvonalszámításhoz',
+ 'dayplan.confirmed': 'Megerősítve',
+ 'dayplan.pendingRes': 'Függőben',
+ 'dayplan.pdf': 'PDF',
+ 'dayplan.pdfTooltip': 'Napi terv exportálása PDF-be',
+ 'dayplan.pdfError': 'Nem sikerült a PDF exportálás',
+ 'dayplan.cannotReorderTransport':
+ 'A rögzített időpontú foglalások nem rendezhetők át',
+ 'dayplan.confirmRemoveTimeTitle': 'Időpont eltávolítása?',
+ 'dayplan.confirmRemoveTimeBody':
+ 'Ennek a helynek rögzített időpontja van ({time}). Az áthelyezéssel az időpont eltávolítódik és szabad rendezés válik lehetővé.',
+ 'dayplan.confirmRemoveTimeAction': 'Időpont eltávolítása és áthelyezés',
+ 'dayplan.confirmDeleteNoteTitle': 'Törli a jegyzetet?',
+ 'dayplan.confirmDeleteNoteBody': 'Ez a jegyzet véglegesen törlődik.',
+ 'dayplan.cannotDropOnTimed':
+ 'Elemek nem helyezhetők rögzített időpontú bejegyzések közé',
+ 'dayplan.cannotBreakChronology':
+ 'Ez megbontaná az időzített elemek és foglalások időrendi sorrendjét',
+ 'dayplan.mobile.addPlace': 'Helyszín hozzáadása',
+ 'dayplan.mobile.searchPlaces': 'Helyszínek keresése...',
+ 'dayplan.mobile.allAssigned': 'Minden helyszín kiosztva',
+ 'dayplan.mobile.noMatch': 'Nincs találat',
+ 'dayplan.mobile.createNew': 'Új helyszín létrehozása',
+ 'dayplan.expandAll': 'Expand all days', // en-fallback
+ 'dayplan.collapseAll': 'Collapse all days', // en-fallback
+ 'dayplan.reorderDays': 'Napok átrendezése',
+ 'dayplan.reorderTitle': 'Napok átrendezése',
+ 'dayplan.reorderHint': 'A nap helyei, jegyzetei és foglalásai együtt mozognak vele.',
+ 'dayplan.addDay': 'Nap hozzáadása',
+ 'dayplan.moveUp': 'Mozgatás felfelé',
+ 'dayplan.moveDown': 'Mozgatás lefelé',
+ 'dayplan.reorderUndo': 'Napok átrendezése',
+ 'dayplan.reorderError': 'Nem sikerült átrendezni a napokat',
+ 'dayplan.addDayError': 'Nem sikerült napot hozzáadni',
+};
+export default dayplan;
diff --git a/shared/src/i18n/hu/externalNotifications.ts b/shared/src/i18n/hu/externalNotifications.ts
new file mode 100644
index 0000000000000000000000000000000000000000..da4c3d9f602f0ae3d27fd76837b16820f02b406f
--- /dev/null
+++ b/shared/src/i18n/hu/externalNotifications.ts
@@ -0,0 +1,64 @@
+import type { NotificationLocale } from '../externalNotifications/types';
+
+const hu: NotificationLocale = {
+ email: {
+ footer:
+ 'Ezt az értesítést azért kaptad, mert engedélyezted az értesítéseket a TREK-ben.',
+ manage: 'Beállítások kezelése',
+ madeWith: 'Made with',
+ openTrek: 'TREK megnyitása',
+ },
+ events: {
+ trip_invite: (p) => ({
+ title: `Meghívó a(z) "${p.trip}" utazásra`,
+ body: `${p.actor} meghívta ${p.invitee || 'egy tagot'} a(z) "${p.trip}" utazásra.`,
+ }),
+ booking_change: (p) => ({
+ title: `Új foglalás: ${p.booking}`,
+ body: `${p.actor} hozzáadott egy "${p.booking}" (${p.type}) foglalást a(z) "${p.trip}" utazáshoz.`,
+ }),
+ trip_reminder: (p) => ({
+ title: `Utazás emlékeztető: ${p.trip}`,
+ body: `A(z) "${p.trip}" utazás hamarosan kezdődik!`,
+ }),
+ todo_due: (p) => ({
+ title: `Teendő esedékes: ${p.todo}`,
+ body: `"${p.todo}" (${p.trip}) határideje: ${p.due}.`,
+ }),
+ vacay_invite: (p) => ({
+ title: 'Vacay Fusion meghívó',
+ body: `${p.actor} meghívott a nyaralási tervek összevonásához. Nyissa meg a TREK-et az elfogadáshoz vagy elutasításhoz.`,
+ }),
+ photos_shared: (p) => ({
+ title: `${p.count} fotó megosztva`,
+ body: `${p.actor} ${p.count} fotót osztott meg a(z) "${p.trip}" utazásban.`,
+ }),
+ collab_message: (p) => ({
+ title: `Új üzenet a(z) "${p.trip}" utazásban`,
+ body: `${p.actor}: ${p.preview}`,
+ }),
+ packing_tagged: (p) => ({
+ title: `Csomagolás: ${p.category}`,
+ body: `${p.actor} hozzárendelte Önt a "${p.category}" csomagolási kategóriához a(z) "${p.trip}" utazásban.`,
+ }),
+ version_available: (p) => ({
+ title: 'Új TREK verzió érhető el',
+ body: `A TREK ${p.version} elérhető. Látogasson el az adminisztrációs panelre a frissítéshez.`,
+ }),
+ synology_session_cleared: () => ({
+ title: 'Synology munkamenet törölve',
+ body: 'A Synology fiókja vagy URL-je megváltozott. Kijelentkeztek a Synology Photos-ból.',
+ }),
+ },
+ passwordReset: {
+ subject: 'Jelszó visszaállítása',
+ greeting: 'Szia',
+ body: 'Kérést kaptunk a TREK-fiókod jelszavának visszaállítására. Kattints az alábbi gombra az új jelszó beállításához.',
+ ctaIntro: 'Jelszó visszaállítása',
+ expiry: 'Ez a link 60 perc után lejár.',
+ ignore:
+ 'Ha nem te kérted ezt, nyugodtan hagyd figyelmen kívül ezt az e-mailt — a jelszavad változatlan marad.',
+ },
+};
+
+export default hu;
diff --git a/shared/src/i18n/hu/files.ts b/shared/src/i18n/hu/files.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c924303e52c5b6f8d4e9fd541c35efafbae09c36
--- /dev/null
+++ b/shared/src/i18n/hu/files.ts
@@ -0,0 +1,62 @@
+import type { TranslationStrings } from '../types';
+
+const files: TranslationStrings = {
+ 'files.title': 'Fájlok',
+ 'files.pageTitle': 'Fájlok és dokumentumok',
+ 'files.subtitle': '{count} fájl a következőhöz: {trip}',
+ 'files.download': 'Letöltés',
+ 'files.openError': 'A fájl megnyitása sikertelen',
+ 'files.downloadPdf': 'PDF letöltése',
+ 'files.count': '{count} fájl',
+ 'files.countSingular': '1 fájl',
+ 'files.uploaded': '{count} feltöltve',
+ 'files.uploadError': 'Feltöltés sikertelen',
+ 'files.dropzone': 'Húzd ide a fájlokat',
+ 'files.dropzoneHint': 'vagy kattints a böngészéshez',
+ 'files.allowedTypes':
+ 'Képek, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Max 50 MB',
+ 'files.uploading': 'Feltöltés...',
+ 'files.filterAll': 'Összes',
+ 'files.filterPdf': 'PDF-ek',
+ 'files.filterImages': 'Képek',
+ 'files.filterDocs': 'Dokumentumok',
+ 'files.filterCollab': 'Közös jegyzetek',
+ 'files.sourceCollab': 'Közös jegyzetekből',
+ 'files.empty': 'Még nincsenek fájlok',
+ 'files.emptyHint': 'Tölts fel fájlokat az utazásodhoz',
+ 'files.openTab': 'Megnyitás új lapon',
+ 'files.confirm.delete': 'Biztosan törölni szeretnéd ezt a fájlt?',
+ 'files.toast.deleted': 'Fájl törölve',
+ 'files.toast.deleteError': 'Nem sikerült törölni a fájlt',
+ 'files.sourcePlan': 'Napi terv',
+ 'files.sourceBooking': 'Foglalás',
+ 'files.sourceTransport': 'Közlekedés',
+ 'files.attach': 'Csatolás',
+ 'files.pasteHint': 'Képeket a vágólapról is beillesztheted (Ctrl+V)',
+ 'files.trash': 'Kuka',
+ 'files.trashEmpty': 'A kuka üres',
+ 'files.emptyTrash': 'Kuka ürítése',
+ 'files.restore': 'Visszaállítás',
+ 'files.star': 'Csillag',
+ 'files.unstar': 'Csillag eltávolítása',
+ 'files.assign': 'Hozzárendelés',
+ 'files.assignTitle': 'Fájl hozzárendelése',
+ 'files.assignPlace': 'Hely',
+ 'files.assignBooking': 'Foglalás',
+ 'files.assignTransport': 'Közlekedés',
+ 'files.unassigned': 'Nincs hozzárendelve',
+ 'files.unlink': 'Kapcsolat eltávolítása',
+ 'files.toast.trashed': 'Kukába helyezve',
+ 'files.toast.restored': 'Fájl visszaállítva',
+ 'files.toast.trashEmptied': 'Kuka kiürítve',
+ 'files.toast.assigned': 'Fájl hozzárendelve',
+ 'files.toast.assignError': 'Hozzárendelés sikertelen',
+ 'files.toast.restoreError': 'Visszaállítás sikertelen',
+ 'files.confirm.permanentDelete':
+ 'Véglegesen törlöd ezt a fájlt? Ez nem vonható vissza.',
+ 'files.confirm.emptyTrash':
+ 'Véglegesen törlöd az összes kukába helyezett fájlt? Ez nem vonható vissza.',
+ 'files.noteLabel': 'Megjegyzés',
+ 'files.notePlaceholder': 'Megjegyzés hozzáadása...',
+};
+export default files;
diff --git a/shared/src/i18n/hu/index.ts b/shared/src/i18n/hu/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..43c637d685ca7fbc5964ab230900908672005c8c
--- /dev/null
+++ b/shared/src/i18n/hu/index.ts
@@ -0,0 +1,86 @@
+import admin from './admin';
+import airport from './airport';
+import atlas from './atlas';
+import backup from './backup';
+import budget from './budget';
+import categories from './categories';
+import collab from './collab';
+import common from './common';
+import dashboard from './dashboard';
+import day from './day';
+import dayplan from './dayplan';
+import files from './files';
+import inspector from './inspector';
+import journey from './journey';
+import login from './login';
+import map from './map';
+import members from './members';
+import memories from './memories';
+import nav from './nav';
+import notif from './notif';
+import notifications from './notifications';
+import oauth from './oauth';
+import packing from './packing';
+import pdf from './pdf';
+import perm from './perm';
+import photos from './photos';
+import places from './places';
+import planner from './planner';
+import register from './register';
+import reservations from './reservations';
+import settings from './settings';
+import share from './share';
+import shared from './shared';
+import stats from './stats';
+import system_notice from './system_notice';
+import todo from './todo';
+import transport from './transport';
+import trip from './trip';
+import trips from './trips';
+import undo from './undo';
+import vacay from './vacay';
+
+const locale = {
+ ...common,
+ ...trips,
+ ...nav,
+ ...dashboard,
+ ...settings,
+ ...admin,
+ ...dayplan,
+ ...share,
+ ...shared,
+ ...login,
+ ...register,
+ ...vacay,
+ ...atlas,
+ ...trip,
+ ...places,
+ ...inspector,
+ ...reservations,
+ ...airport,
+ ...map,
+ ...budget,
+ ...files,
+ ...packing,
+ ...members,
+ ...categories,
+ ...backup,
+ ...photos,
+ ...pdf,
+ ...planner,
+ ...stats,
+ ...day,
+ ...collab,
+ ...memories,
+ ...perm,
+ ...undo,
+ ...notifications,
+ ...todo,
+ ...notif,
+ ...journey,
+ ...oauth,
+ ...system_notice,
+ ...transport,
+};
+export default locale;
diff --git a/shared/src/i18n/hu/inspector.ts b/shared/src/i18n/hu/inspector.ts
new file mode 100644
index 0000000000000000000000000000000000000000..896fb6f9083a4170572c367ffbb25e64185f243c
--- /dev/null
+++ b/shared/src/i18n/hu/inspector.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const inspector: TranslationStrings = {
+ 'inspector.opened': 'Nyitva',
+ 'inspector.closed': 'Zárva',
+ 'inspector.openingHours': 'Nyitvatartás',
+ 'inspector.showHours': 'Nyitvatartás megjelenítése',
+ 'inspector.files': 'Fájlok',
+ 'inspector.filesCount': '{count} fájl',
+ 'inspector.removeFromDay': 'Eltávolítás a napról',
+ 'inspector.remove': 'Eltávolítás',
+ 'inspector.addToDay': 'Hozzáadás a naphoz',
+ 'inspector.confirmedRes': 'Megerősített foglalás',
+ 'inspector.pendingRes': 'Függőben lévő foglalás',
+ 'inspector.google': 'Megnyitás a Google Térképben',
+ 'inspector.website': 'Weboldal megnyitása',
+ 'inspector.addRes': 'Foglalás',
+ 'inspector.editRes': 'Foglalás szerkesztése',
+ 'inspector.participants': 'Résztvevők',
+ 'inspector.trackStats': 'Útvonal adatok',
+};
+export default inspector;
diff --git a/shared/src/i18n/hu/journey.ts b/shared/src/i18n/hu/journey.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7682942343b7c2bc2c2de93a57d0aa533f1db03d
--- /dev/null
+++ b/shared/src/i18n/hu/journey.ts
@@ -0,0 +1,245 @@
+import type { TranslationStrings } from '../types';
+
+const journey: TranslationStrings = {
+ 'journey.search.placeholder': 'Utak keresése…',
+ 'journey.search.noResults': 'Nincs „{query}" kifejezéssel egyező út',
+ 'journey.title': 'Útinaplók',
+ 'journey.subtitle': 'Kövesse nyomon utazásait valós időben',
+ 'journey.new': 'Új útinapló',
+ 'journey.create': 'Létrehozás',
+ 'journey.titlePlaceholder': 'Hová utazol?',
+ 'journey.empty': 'Még nincsenek útinaplók',
+ 'journey.emptyHint': 'Kezdd el dokumentálni a következő utazásod',
+ 'journey.deleted': 'Útinapló törölve',
+ 'journey.createError': 'Nem sikerült létrehozni az útinaplót',
+ 'journey.deleteError': 'Nem sikerült törölni az útinaplót',
+ 'journey.deleteConfirmTitle': 'Törlés',
+ 'journey.deleteConfirmMessage':
+ 'Törlöd a(z) „{title}" útinaplót? Ez nem vonható vissza.',
+ 'journey.deleteConfirmGeneric': 'Biztosan törölni szeretnéd?',
+ 'journey.notFound': 'Útinapló nem található',
+ 'journey.photos': 'Fotók',
+ 'journey.timelineEmpty': 'Még nincsenek megállók',
+ 'journey.timelineEmptyHint':
+ 'Adj hozzá egy bejelentkezést vagy írj naplóbejegyzést a kezdéshez',
+ 'journey.status.draft': 'Vázlat',
+ 'journey.status.active': 'Aktív',
+ 'journey.status.completed': 'Befejezett',
+ 'journey.status.upcoming': 'Közelgő',
+ 'journey.status.archived': 'Archivált',
+ 'journey.checkin.add': 'Bejelentkezés',
+ 'journey.checkin.namePlaceholder': 'Helyszín neve',
+ 'journey.checkin.notesPlaceholder': 'Jegyzetek (opcionális)',
+ 'journey.checkin.save': 'Mentés',
+ 'journey.checkin.error': 'Nem sikerült menteni a bejelentkezést',
+ 'journey.entry.add': 'Napló',
+ 'journey.entry.edit': 'Bejegyzés szerkesztése',
+ 'journey.entry.titlePlaceholder': 'Cím (opcionális)',
+ 'journey.entry.bodyPlaceholder': 'Mi történt ma?',
+ 'journey.entry.save': 'Mentés',
+ 'journey.entry.error': 'Nem sikerült menteni a bejegyzést',
+ 'journey.photo.add': 'Fotó',
+ 'journey.photo.uploadError': 'A feltöltés sikertelen',
+ 'journey.share.share': 'Megosztás',
+ 'journey.share.public': 'Nyilvános',
+ 'journey.share.linkCopied': 'Nyilvános link másolva',
+ 'journey.share.disabled': 'Nyilvános megosztás letiltva',
+ 'journey.editor.titlePlaceholder': 'Adj nevet ennek a pillanatnak...',
+ 'journey.editor.bodyPlaceholder': 'Meséld el ennek a napnak a történetét...',
+ 'journey.editor.placePlaceholder': 'Helyszín (opcionális)',
+ 'journey.editor.tagsPlaceholder':
+ 'Címkék: rejtett kincs, legjobb étel, újra meglátogatandó...',
+ 'journey.visibility.private': 'Privát',
+ 'journey.visibility.shared': 'Megosztott',
+ 'journey.visibility.public': 'Nyilvános',
+ 'journey.emptyState.title': 'Itt kezdődik a történeted',
+ 'journey.emptyState.subtitle':
+ 'Jelentkezz be egy helyszínen vagy írd meg az első naplóbejegyzésed',
+ 'journey.frontpage.subtitle':
+ 'Alakítsd utazásaidat történetekké, amelyeket soha nem felejtesz el',
+ 'journey.frontpage.createJourney': 'Útinapló létrehozása',
+ 'journey.frontpage.activeJourney': 'Aktív útinapló',
+ 'journey.frontpage.allJourneys': 'Összes útinapló',
+ 'journey.frontpage.journeys': 'útinapló',
+ 'journey.frontpage.createNew': 'Új útinapló létrehozása',
+ 'journey.frontpage.createNewSub':
+ 'Válassz utakat, írj történeteket, oszd meg kalandjaidat',
+ 'journey.frontpage.live': 'Élő',
+ 'journey.frontpage.synced': 'Szinkronizálva',
+ 'journey.frontpage.continueWriting': 'Írás folytatása',
+ 'journey.frontpage.updated': 'Frissítve: {time}',
+ 'journey.frontpage.suggestionLabel': 'Az út épp véget ért',
+ 'journey.frontpage.suggestionText':
+ 'Alakítsd a(z) {title} útinaplóvá',
+ 'journey.frontpage.dismiss': 'Elvetés',
+ 'journey.frontpage.journeyName': 'Útinapló neve',
+ 'journey.frontpage.namePlaceholder': 'pl. Délkelet-Ázsia 2026',
+ 'journey.frontpage.selectTrips': 'Utak kiválasztása',
+ 'journey.frontpage.tripsSelected': 'út kiválasztva',
+ 'journey.frontpage.trips': 'út',
+ 'journey.frontpage.placesImported': 'helyszín importálásra kerül',
+ 'journey.frontpage.places': 'helyszín',
+ 'journey.detail.backToJourney': 'Vissza az útinaplóhoz',
+ 'journey.detail.syncedWithTrips': 'Szinkronizálva az utakkal',
+ 'journey.detail.addEntry': 'Bejegyzés hozzáadása',
+ 'journey.detail.newEntry': 'Új bejegyzés',
+ 'journey.detail.editEntry': 'Bejegyzés szerkesztése',
+ 'journey.detail.noEntries': 'Még nincsenek bejegyzések',
+ 'journey.detail.noEntriesHint':
+ 'Adj hozzá egy utat a vázlatos bejegyzések elkészítéséhez',
+ 'journey.detail.noPhotos': 'Még nincsenek fotók',
+ 'journey.detail.noPhotosHint':
+ 'Tölts fel fotókat a bejegyzésekhez vagy böngészd az Immich/Synology könyvtárat',
+ 'journey.detail.journeyStats': 'Útinapló statisztika',
+ 'journey.detail.syncedTrips': 'Szinkronizált utak',
+ 'journey.detail.noTripsLinked': 'Még nincsenek kapcsolt utak',
+ 'journey.detail.contributors': 'Közreműködők',
+ 'journey.detail.readMore': 'Tovább olvasás',
+ 'journey.detail.prosCons': 'Előnyök és hátrányok',
+ 'journey.detail.photos': 'fotók',
+ 'journey.detail.day': '{number}. nap',
+ 'journey.detail.places': 'helyek',
+ 'journey.stats.days': 'Napok',
+ 'journey.stats.cities': 'Városok',
+ 'journey.stats.entries': 'Bejegyzések',
+ 'journey.stats.photos': 'Fotók',
+ 'journey.stats.places': 'Helyszínek',
+ 'journey.skeletons.show': 'Javaslatok megjelenítése',
+ 'journey.skeletons.hide': 'Javaslatok elrejtése',
+ 'journey.verdict.lovedIt': 'Imádtam',
+ 'journey.verdict.couldBeBetter': 'Lehetne jobb',
+ 'journey.synced.places': 'helyszín',
+ 'journey.synced.synced': 'szinkronizálva',
+ 'journey.editor.discardChangesConfirm':
+ 'Mentetlen módosításaid vannak. Elveted?',
+ 'journey.editor.uploadFailed': 'A fotók feltöltése sikertelen',
+ 'journey.editor.uploadPhotos': 'Fotók feltöltése',
+ 'journey.editor.uploading': 'Feltöltés...',
+ 'journey.editor.uploadingProgress': 'Feltöltés {done}/{total}…',
+ 'journey.editor.uploadPartialFailed':
+ '{failed} / {total} fotó sikertelen — mentsd el újra a próbálkozáshoz',
+ 'journey.editor.fromGallery': 'Galériából',
+ 'journey.editor.allPhotosAdded': 'Minden fotó már hozzáadva',
+ 'journey.editor.writeStory': 'Írd meg a történeted...',
+ 'journey.editor.prosCons': 'Előnyök és hátrányok',
+ 'journey.editor.pros': 'Előnyök',
+ 'journey.editor.cons': 'Hátrányok',
+ 'journey.editor.proPlaceholder': 'Valami remek...',
+ 'journey.editor.conPlaceholder': 'Nem annyira jó...',
+ 'journey.editor.addAnother': 'Még egy hozzáadása',
+ 'journey.editor.date': 'Dátum',
+ 'journey.editor.location': 'Helyszín',
+ 'journey.editor.searchLocation': 'Helyszín keresése...',
+ 'journey.editor.mood': 'Hangulat',
+ 'journey.editor.weather': 'Időjárás',
+ 'journey.editor.photoFirst': '1.',
+ 'journey.editor.makeFirst': 'Legyen az 1.',
+ 'journey.editor.searching': 'Keresés...',
+ 'journey.mood.amazing': 'Fantasztikus',
+ 'journey.mood.good': 'Jó',
+ 'journey.mood.neutral': 'Semleges',
+ 'journey.mood.rough': 'Nehéz',
+ 'journey.weather.sunny': 'Napos',
+ 'journey.weather.partly': 'Részben felhős',
+ 'journey.weather.cloudy': 'Felhős',
+ 'journey.weather.rainy': 'Esős',
+ 'journey.weather.stormy': 'Viharos',
+ 'journey.weather.cold': 'Havas',
+ 'journey.trips.linkTrip': 'Út kapcsolása',
+ 'journey.trips.searchTrip': 'Út keresése',
+ 'journey.trips.searchPlaceholder': 'Út neve vagy úti cél...',
+ 'journey.trips.noTripsAvailable': 'Nincsenek elérhető utak',
+ 'journey.trips.link': 'Kapcsolás',
+ 'journey.trips.tripLinked': 'Út kapcsolva',
+ 'journey.trips.linkFailed': 'Nem sikerült az utat kapcsolni',
+ 'journey.trips.addTrip': 'Út hozzáadása',
+ 'journey.trips.unlinkTrip': 'Út leválasztása',
+ 'journey.trips.unlinkMessage':
+ 'Leválasztod a(z) „{title}" utat? Az összes szinkronizált bejegyzés és fotó véglegesen törlődik. Ez nem vonható vissza.',
+ 'journey.trips.unlink': 'Leválasztás',
+ 'journey.trips.tripUnlinked': 'Út leválasztva',
+ 'journey.trips.unlinkFailed': 'Nem sikerült az utat leválasztani',
+ 'journey.trips.noTripsLinkedSettings': 'Nincsenek kapcsolt utak',
+ 'journey.contributors.invite': 'Közreműködő meghívása',
+ 'journey.contributors.searchUser': 'Felhasználó keresése',
+ 'journey.contributors.searchPlaceholder': 'Felhasználónév vagy e-mail...',
+ 'journey.contributors.noUsers': 'Nem található felhasználó',
+ 'journey.contributors.role': 'Szerep',
+ 'journey.contributors.added': 'Közreműködő hozzáadva',
+ 'journey.contributors.addFailed': 'Nem sikerült hozzáadni a közreműködőt',
+ 'journey.share.publicShare': 'Nyilvános megosztás',
+ 'journey.share.createLink': 'Megosztó link létrehozása',
+ 'journey.share.linkCreated': 'Megosztó link létrehozva',
+ 'journey.share.createFailed': 'Nem sikerült létrehozni a linket',
+ 'journey.share.copy': 'Másolás',
+ 'journey.share.copied': 'Másolva!',
+ 'journey.share.timeline': 'Idővonal',
+ 'journey.share.gallery': 'Galéria',
+ 'journey.share.map': 'Térkép',
+ 'journey.share.removeLink': 'Megosztó link eltávolítása',
+ 'journey.share.linkDeleted': 'Megosztó link törölve',
+ 'journey.share.deleteFailed': 'Nem sikerült törölni',
+ 'journey.share.updateFailed': 'Nem sikerült frissíteni',
+ 'journey.invite.role': 'Szerepkör',
+ 'journey.invite.viewer': 'Megtekintő',
+ 'journey.invite.editor': 'Szerkesztő',
+ 'journey.invite.invite': 'Meghívás',
+ 'journey.invite.inviting': 'Meghívás...',
+ 'journey.settings.title': 'Útinapló beállításai',
+ 'journey.settings.coverImage': 'Borítókép',
+ 'journey.settings.changeCover': 'Borító módosítása',
+ 'journey.settings.addCover': 'Borítókép hozzáadása',
+ 'journey.settings.name': 'Név',
+ 'journey.settings.subtitle': 'Alcím',
+ 'journey.settings.subtitlePlaceholder': 'pl. Thaiföld, Vietnam és Kambodzsa',
+ 'journey.settings.endJourney': 'Út archiválása',
+ 'journey.settings.reopenJourney': 'Út visszaállítása',
+ 'journey.settings.archived': 'Út archiválva',
+ 'journey.settings.reopened': 'Út újranyitva',
+ 'journey.settings.endDescription':
+ 'Elrejti az Élő jelzést. Bármikor újranyitható.',
+ 'journey.settings.delete': 'Törlés',
+ 'journey.settings.deleteJourney': 'Útinapló törlése',
+ 'journey.settings.deleteMessage':
+ 'Törlöd a(z) „{title}" útinaplót? Minden bejegyzés és fotó elveszik.',
+ 'journey.settings.saved': 'Beállítások mentve',
+ 'journey.settings.saveFailed': 'Nem sikerült menteni',
+ 'journey.settings.coverUpdated': 'Borítókép frissítve',
+ 'journey.settings.coverFailed': 'A feltöltés sikertelen',
+ 'journey.settings.failedToDelete': 'Törlés sikertelen',
+ 'journey.entries.deleteTitle': 'Bejegyzés törlése',
+ 'journey.photosUploaded': '{count} fotó feltöltve',
+ 'journey.photosUploadFailed': 'Néhány fotót nem sikerült feltölteni',
+ 'journey.photosAdded': '{count} fotó hozzáadva',
+ 'journey.public.notFound': 'Nem található',
+ 'journey.public.notFoundMessage':
+ 'Ez az útinapló nem létezik vagy a link lejárt.',
+ 'journey.public.readOnly': 'Csak olvasható · Nyilvános útinapló',
+ 'journey.public.tagline': 'Utazástervező és felfedező eszköz',
+ 'journey.public.sharedVia': 'Megosztva a következőn keresztül:',
+ 'journey.public.madeWith': 'Készítve a következővel:',
+ 'journey.pdf.journeyBook': 'Útinaplókönyv',
+ 'journey.pdf.madeWith': 'Készítve a TREK segítségével',
+ 'journey.pdf.day': 'Nap',
+ 'journey.pdf.theEnd': 'Vége',
+ 'journey.pdf.saveAsPdf': 'Mentés PDF-ként',
+ 'journey.pdf.pages': 'oldal',
+ 'journey.picker.tripPeriod': 'Utazási időszak',
+ 'journey.picker.dateRange': 'Időszak',
+ 'journey.picker.allPhotos': 'Összes fotó',
+ 'journey.picker.albums': 'Albumok',
+ 'journey.picker.selected': 'kiválasztva',
+ 'journey.picker.addTo': 'Hozzáadás',
+ 'journey.picker.newGallery': 'Új galéria',
+ 'journey.picker.selectAll': 'Összes kijelölése',
+ 'journey.picker.deselectAll': 'Összes kijelölés törlése',
+ 'journey.picker.noAlbums': 'Nem található album',
+ 'journey.picker.selectDate': 'Dátum választása',
+ 'journey.picker.search': 'Keresés',
+ 'journey.detail.journeyTab': 'Journey', // en-fallback
+ 'journey.contributors.remove': 'Remove contributor', // en-fallback
+ 'journey.contributors.removeConfirm': 'Remove {username} from this journey?', // en-fallback
+ 'journey.contributors.removed': 'Contributor removed', // en-fallback
+ 'journey.contributors.removeFailed': 'Failed to remove contributor', // en-fallback
+};
+export default journey;
diff --git a/shared/src/i18n/hu/login.ts b/shared/src/i18n/hu/login.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0cd3e5a42ef44f742d8efd342d48ea32ef382a25
--- /dev/null
+++ b/shared/src/i18n/hu/login.ts
@@ -0,0 +1,106 @@
+import type { TranslationStrings } from '../types';
+
+const login: TranslationStrings = {
+ 'login.error':
+ 'Bejelentkezés sikertelen. Kérjük, ellenőrizd a megadott adatokat.',
+ 'login.tagline': 'Az utazásaid.\nA terved.',
+ 'login.description':
+ 'Tervezz utazásokat közösen interaktív térképekkel, költségvetéssel és valós idejű szinkronizálással.',
+ 'login.features.maps': 'Interaktív térképek',
+ 'login.features.mapsDesc': 'Google Places, útvonalak és csoportosítás',
+ 'login.features.realtime': 'Valós idejű szinkron',
+ 'login.features.realtimeDesc': 'Közös tervezés WebSocket-en keresztül',
+ 'login.features.budget': 'Költségvetés-követés',
+ 'login.features.budgetDesc':
+ 'Kategóriák, diagramok és személyenkénti költségek',
+ 'login.features.collab': 'Együttműködés',
+ 'login.features.collabDesc': 'Többfelhasználós, megosztott utazásokkal',
+ 'login.features.packing': 'Csomagolási listák',
+ 'login.features.packingDesc': 'Kategóriák és haladás',
+ 'login.features.bookings': 'Foglalások',
+ 'login.features.bookingsDesc': 'Repülők, szállodák, éttermek és még több',
+ 'login.features.files': 'Dokumentumok',
+ 'login.features.filesDesc': 'Fájlok feltöltése és kezelése',
+ 'login.features.routes': 'Útvonal-optimalizálás',
+ 'login.features.routesDesc':
+ 'Automatikus optimalizálás és Google Maps export',
+ 'login.selfHosted':
+ 'Saját üzemeltetés · Nyílt forráskód · Az adataid nálad maradnak',
+ 'login.title': 'Bejelentkezés',
+ 'login.subtitle': 'Üdv újra',
+ 'login.signingIn': 'Bejelentkezés…',
+ 'login.signIn': 'Bejelentkezés',
+ 'login.createAdmin': 'Admin fiók létrehozása',
+ 'login.createAdminHint': 'Hozd létre az első admin fiókot a TREK-hez.',
+ 'login.setNewPassword': 'Új jelszó beállítása',
+ 'login.setNewPasswordHint':
+ 'A folytatás előtt meg kell változtatnia a jelszavát.',
+ 'login.createAccount': 'Fiók létrehozása',
+ 'login.createAccountHint': 'Új fiók regisztrálása.',
+ 'login.creating': 'Létrehozás…',
+ 'login.noAccount': 'Nincs még fiókod?',
+ 'login.hasAccount': 'Már van fiókod?',
+ 'login.register': 'Regisztráció',
+ 'login.emailPlaceholder': 'email@cimed.hu',
+ 'login.username': 'Felhasználónév',
+ 'login.oidc.registrationDisabled':
+ 'A regisztráció le van tiltva. Lépj kapcsolatba az adminisztrátorral.',
+ 'login.oidc.noEmail': 'Nem érkezett e-mail a szolgáltatótól.',
+ 'login.oidc.tokenFailed': 'Hitelesítés sikertelen.',
+ 'login.oidc.invalidState': 'Érvénytelen munkamenet. Kérjük, próbáld újra.',
+ 'login.demoFailed': 'Demo bejelentkezés sikertelen',
+ 'login.oidcSignIn': 'Bejelentkezés ezzel: {name}',
+ 'login.oidcOnly':
+ 'A jelszavas hitelesítés le van tiltva. Kérjük, jelentkezz be az SSO szolgáltatódon keresztül.',
+ 'login.oidcLoggedOut':
+ 'Kijelentkeztél. Jelentkezz be újra az SSO szolgáltatódon keresztül.',
+ 'login.demoHint': 'Próbáld ki a demót — regisztráció nélkül',
+ 'login.mfaTitle': 'Kétfaktoros hitelesítés',
+ 'login.mfaSubtitle': 'Add meg a 6 jegyű kódot a hitelesítő alkalmazásból.',
+ 'login.mfaCodeLabel': 'Ellenőrző kód',
+ 'login.mfaCodeRequired': 'Add meg a kódot a hitelesítő alkalmazásból.',
+ 'login.mfaHint':
+ 'Nyisd meg a Google Authenticator, Authy vagy más TOTP alkalmazást.',
+ 'login.mfaBack': '← Vissza a bejelentkezéshez',
+ 'login.mfaVerify': 'Ellenőrzés',
+ 'login.invalidInviteLink': 'Érvénytelen vagy lejárt meghívólink',
+ 'login.oidcFailed': 'OIDC bejelentkezés sikertelen',
+ 'login.usernameRequired': 'A felhasználónév kötelező',
+ 'login.passwordMinLength':
+ 'A jelszónak legalább 8 karakter hosszúnak kell lennie',
+ 'login.forgotPassword': 'Elfelejtetted a jelszavad?',
+ 'login.rememberMe': 'Emlékezz rám',
+ 'login.forgotPasswordTitle': 'Jelszó visszaállítása',
+ 'login.forgotPasswordBody':
+ 'Írd be a regisztrációnál használt e-mail-címet. Ha létezik fiók, küldünk egy visszaállítási linket.',
+ 'login.forgotPasswordSubmit': 'Link küldése',
+ 'login.forgotPasswordSentTitle': 'Nézd meg az e-mailjeidet',
+ 'login.forgotPasswordSentBody':
+ 'Ha létezik fiók ehhez az e-mailhez, a visszaállítási link úton van. 60 perc után lejár.',
+ 'login.forgotPasswordSmtpHintOff':
+ 'Megjegyzés: a rendszergazda nem konfigurálta az SMTP-t, ezért a visszaállítási link e-mail helyett a szerverkonzolba kerül.',
+ 'login.backToLogin': 'Vissza a bejelentkezéshez',
+ 'login.newPassword': 'Új jelszó',
+ 'login.confirmPassword': 'Új jelszó megerősítése',
+ 'login.passwordsDontMatch': 'A jelszavak nem egyeznek',
+ 'login.mfaCode': '2FA-kód',
+ 'login.resetPasswordTitle': 'Új jelszó beállítása',
+ 'login.resetPasswordBody':
+ 'Válassz erős jelszót, amit itt még nem használtál. Minimum 8 karakter.',
+ 'login.resetPasswordMfaBody':
+ 'Add meg a 2FA-kódodat vagy egy tartalék kódot a visszaállítás befejezéséhez.',
+ 'login.resetPasswordSubmit': 'Jelszó visszaállítása',
+ 'login.resetPasswordVerify': 'Ellenőrzés és visszaállítás',
+ 'login.resetPasswordSuccessTitle': 'Jelszó frissítve',
+ 'login.resetPasswordSuccessBody':
+ 'Mostantól bejelentkezhetsz az új jelszavaddal.',
+ 'login.resetPasswordInvalidLink': 'Érvénytelen visszaállítási link',
+ 'login.resetPasswordInvalidLinkBody':
+ 'A link hiányzik vagy sérült. A folytatáshoz kérj egy újat.',
+ 'login.resetPasswordFailed':
+ 'A visszaállítás nem sikerült. A link lehet, hogy lejárt.',
+ 'login.passkey.signIn': 'Bejelentkezés passkey-jel',
+ 'login.passkey.failed':
+ 'A passkey-bejelentkezés sikertelen. Kérjük, próbáld újra.',
+};
+export default login;
diff --git a/shared/src/i18n/hu/map.ts b/shared/src/i18n/hu/map.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8db1166954790cc34008fb8d1f901b0d90e7a8b3
--- /dev/null
+++ b/shared/src/i18n/hu/map.ts
@@ -0,0 +1,17 @@
+import type { TranslationStrings } from '../types';
+
+const map: TranslationStrings = {
+ 'map.connections': 'Kapcsolatok',
+ 'map.showConnections': 'Foglalási útvonalak megjelenítése',
+ 'map.hideConnections': 'Foglalási útvonalak elrejtése',
+ 'poi.searchThisArea': 'Keresés ezen a területen',
+ 'poi.cat.restaurants': 'Éttermek',
+ 'poi.cat.cafes': 'Kávézók',
+ 'poi.cat.bars': 'Bárok és éjszakai élet',
+ 'poi.cat.hotels': 'Szállás',
+ 'poi.cat.sights': 'Látnivalók',
+ 'poi.cat.museums': 'Múzeumok és kultúra',
+ 'poi.cat.nature': 'Természet és parkok',
+ 'poi.cat.activities': 'Programok',
+};
+export default map;
diff --git a/shared/src/i18n/hu/members.ts b/shared/src/i18n/hu/members.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bec59e8bd65dfac9f728f3e13d9c1a1c04e6b01e
--- /dev/null
+++ b/shared/src/i18n/hu/members.ts
@@ -0,0 +1,24 @@
+import type { TranslationStrings } from '../types';
+
+const members: TranslationStrings = {
+ 'members.shareTrip': 'Utazás megosztása',
+ 'members.inviteUser': 'Felhasználó meghívása',
+ 'members.selectUser': 'Felhasználó kiválasztása…',
+ 'members.invite': 'Meghívás',
+ 'members.allHaveAccess': 'Minden felhasználónak már van hozzáférése.',
+ 'members.access': 'Hozzáférés',
+ 'members.person': 'személy',
+ 'members.persons': 'személy',
+ 'members.you': 'te',
+ 'members.owner': 'Tulajdonos',
+ 'members.leaveTrip': 'Utazás elhagyása',
+ 'members.removeAccess': 'Hozzáférés eltávolítása',
+ 'members.confirmLeave': 'Elhagyod az utazást? Elveszíted a hozzáférést.',
+ 'members.confirmRemove': 'Eltávolítod a hozzáférést ettől a felhasználótól?',
+ 'members.loadError': 'Nem sikerült betölteni a tagokat',
+ 'members.added': 'hozzáadva',
+ 'members.addError': 'Nem sikerült hozzáadni',
+ 'members.removed': 'Tag eltávolítva',
+ 'members.removeError': 'Nem sikerült eltávolítani',
+};
+export default members;
diff --git a/shared/src/i18n/hu/memories.ts b/shared/src/i18n/hu/memories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2965eb6633a874c3f576a8fe2d6cc319f06e9e13
--- /dev/null
+++ b/shared/src/i18n/hu/memories.ts
@@ -0,0 +1,82 @@
+import type { TranslationStrings } from '../types';
+
+const memories: TranslationStrings = {
+ 'memories.title': 'Fotók',
+ 'memories.notConnected': 'Immich nincs csatlakoztatva',
+ 'memories.notConnectedHint':
+ 'Csatlakoztasd az Immich példányodat a Beállításokban, hogy itt lásd az utazási fotóidat.',
+ 'memories.notConnectedMultipleHint':
+ 'A fényképek hozzáadásához csatlakoztasson egyet a következő fényképszolgáltatók közül a Beállításokban: {provider_names}.',
+ 'memories.noDates': 'Adj hozzá dátumokat az utazáshoz a fotók betöltéséhez.',
+ 'memories.noPhotos': 'Nem találhatók fotók',
+ 'memories.noPhotosHint':
+ 'Nem találhatók fotók az Immichben erre az utazási időszakra.',
+ 'memories.photosFound': 'fotó',
+ 'memories.fromOthers': 'másoktól',
+ 'memories.sharePhotos': 'Fotók megosztása',
+ 'memories.sharing': 'Megosztás',
+ 'memories.reviewTitle': 'Nézd át a fotóidat',
+ 'memories.reviewHint': 'Kattints a fotókra a megosztásból való kizáráshoz.',
+ 'memories.shareCount': '{count} fotó megosztása',
+ 'memories.providerUrl': 'Szerver URL',
+ 'memories.providerApiKey': 'API kulcs',
+ 'memories.providerUsername': 'Felhasználónév',
+ 'memories.providerPassword': 'Jelszó',
+ 'memories.providerOTP': 'MFA kód (ha engedélyezve van)',
+ 'memories.skipSSLVerification': 'SSL tanúsítvány ellenőrzésének kihagyása',
+ 'memories.immichAutoUpload':
+ 'Journey-fotók feltöltésekor másolat Immich-be is',
+ 'memories.providerUrlHintSynology':
+ 'Adja meg a Photos alkalmazás elérési útját az URL-ben, pl. https://nas:5001/photo',
+ 'memories.testConnection': 'Kapcsolat tesztelése',
+ 'memories.testShort': 'Teszt',
+ 'memories.testFirst': 'Először teszteld a kapcsolatot',
+ 'memories.connected': 'Csatlakoztatva',
+ 'memories.disconnected': 'Nincs csatlakoztatva',
+ 'memories.connectionSuccess': 'Csatlakozva az Immichhez',
+ 'memories.connectionError': 'Nem sikerült csatlakozni az Immichhez',
+ 'memories.saved': '{provider_name} beállítások mentve',
+ 'memories.providerDisconnectedBanner':
+ 'A {provider_name} kapcsolat megszakadt. Csatlakozzon újra a Beállításokban a fényképek megtekintéséhez.',
+ 'memories.saveError':
+ 'Nem sikerült menteni a(z) {provider_name} beállításait',
+ 'memories.addPhotos': 'Fotók hozzáadása',
+ 'memories.linkAlbum': 'Album csatolása',
+ 'memories.selectAlbum': 'Immich album kiválasztása',
+ 'memories.selectAlbumMultiple': 'Album kiválasztása',
+ 'memories.noAlbums': 'Nem található album',
+ 'memories.syncAlbum': 'Album szinkronizálása',
+ 'memories.unlinkAlbum': 'Leválasztás',
+ 'memories.photos': 'fotó',
+ 'memories.selectPhotos': 'Fotók kiválasztása az Immichből',
+ 'memories.selectPhotosMultiple': 'Fényképek kiválasztása',
+ 'memories.selectHint': 'Koppints a fotókra a kijelölésükhöz.',
+ 'memories.selected': 'kijelölve',
+ 'memories.addSelected': '{count} fotó hozzáadása',
+ 'memories.alreadyAdded': 'Hozzáadva',
+ 'memories.private': 'Privát',
+ 'memories.stopSharing': 'Megosztás leállítása',
+ 'memories.oldest': 'Legrégebbi elöl',
+ 'memories.newest': 'Legújabb elöl',
+ 'memories.allLocations': 'Összes helyszín',
+ 'memories.tripDates': 'Utazás dátumai',
+ 'memories.allPhotos': 'Összes fotó',
+ 'memories.confirmShareTitle': 'Megosztás az utazótársakkal?',
+ 'memories.confirmShareHint':
+ '{count} fotó lesz látható az utazás összes tagja számára. Később egyenként is priváttá teheted őket.',
+ 'memories.confirmShareButton': 'Fotók megosztása',
+ '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',
+ 'memories.saveRouteNotConfigured':
+ 'A mentési útvonal nincs konfigurálva ehhez a szolgáltatóhoz',
+ 'memories.testRouteNotConfigured':
+ 'A tesztútvonal nincs konfigurálva ehhez a szolgáltatóhoz',
+ 'memories.fillRequiredFields': 'Kérjük töltse ki az összes kötelező mezőt',
+};
+export default memories;
diff --git a/shared/src/i18n/hu/nav.ts b/shared/src/i18n/hu/nav.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9cc7cc78ac8881eecc64b3c991f4347876c38cb0
--- /dev/null
+++ b/shared/src/i18n/hu/nav.ts
@@ -0,0 +1,20 @@
+import type { TranslationStrings } from '../types';
+
+const nav: TranslationStrings = {
+ 'nav.trip': 'Utazás',
+ 'nav.share': 'Megosztás',
+ 'nav.settings': 'Beállítások',
+ 'nav.admin': 'Admin',
+ 'nav.logout': 'Kijelentkezés',
+ 'nav.lightMode': 'Világos mód',
+ 'nav.darkMode': 'Sötét mód',
+ 'nav.autoMode': 'Automatikus mód',
+ 'nav.administrator': 'Adminisztrátor',
+ 'nav.myTrips': 'Utazásaim',
+ 'nav.profile': 'Profil',
+ 'nav.bottomSettings': 'Beállítások',
+ 'nav.bottomAdmin': 'Adminisztráció',
+ 'nav.bottomLogout': 'Kijelentkezés',
+ 'nav.bottomAdminBadge': 'Admin',
+};
+export default nav;
diff --git a/shared/src/i18n/hu/notif.ts b/shared/src/i18n/hu/notif.ts
new file mode 100644
index 0000000000000000000000000000000000000000..17ba7bb29e75a6b2471808d42e3ddcee3bbd093c
--- /dev/null
+++ b/shared/src/i18n/hu/notif.ts
@@ -0,0 +1,45 @@
+import type { TranslationStrings } from '../types';
+
+const notif: TranslationStrings = {
+ 'notif.test.title': '[Teszt] Értesítés',
+ 'notif.test.simple.text': 'Ez egy egyszerű teszt értesítés.',
+ 'notif.test.boolean.text': 'Elfogadod ezt a teszt értesítést?',
+ 'notif.test.navigate.text': 'Kattints alább az irányítópultra navigáláshoz.',
+ 'notif.trip_invite.title': 'Utazásra meghívó',
+ 'notif.trip_invite.text': '{actor} meghívott a(z) {trip} utazásra',
+ 'notif.booking_change.title': 'Foglalás frissítve',
+ 'notif.booking_change.text':
+ '{actor} frissített egy foglalást a(z) {trip} utazásban',
+ 'notif.trip_reminder.title': 'Utazás emlékeztető',
+ 'notif.trip_reminder.text': 'A(z) {trip} utazás hamarosan kezdődik!',
+ 'notif.todo_due.title': 'Teendő esedékes',
+ 'notif.todo_due.text': '{todo} ({trip}) határideje: {due}',
+ 'notif.vacay_invite.title': 'Vacay Fusion meghívó',
+ 'notif.vacay_invite.text':
+ '{actor} meghívott a nyaralási tervek összevonásához',
+ 'notif.photos_shared.title': 'Fotók megosztva',
+ 'notif.photos_shared.text':
+ '{actor} {count} fotót osztott meg a(z) {trip} utazásban',
+ 'notif.collab_message.title': 'Új üzenet',
+ 'notif.collab_message.text': '{actor} üzenetet küldött a(z) {trip} utazásban',
+ 'notif.packing_tagged.title': 'Csomagolási feladat',
+ 'notif.packing_tagged.text':
+ '{actor} hozzárendelte Önt a {category} kategóriához a(z) {trip} utazásban',
+ 'notif.version_available.title': 'Új verzió elérhető',
+ 'notif.version_available.text': 'A TREK {version} elérhető',
+ 'notif.action.view_trip': 'Utazás megtekintése',
+ 'notif.action.view_collab': 'Üzenetek megtekintése',
+ 'notif.action.view_packing': 'Csomagolás megtekintése',
+ 'notif.action.view_photos': 'Fotók megtekintése',
+ 'notif.action.view_vacay': 'Vacay megtekintése',
+ 'notif.action.view_admin': 'Admin megnyitása',
+ 'notif.action.view': 'Megtekintés',
+ 'notif.action.accept': 'Elfogadás',
+ 'notif.action.decline': 'Elutasítás',
+ 'notif.generic.title': 'Értesítés',
+ 'notif.generic.text': 'Új értesítésed érkezett',
+ 'notif.dev.unknown_event.title': '[DEV] Ismeretlen esemény',
+ 'notif.dev.unknown_event.text':
+ 'A(z) "{event}" eseménytípus nincs regisztrálva az EVENT_NOTIFICATION_CONFIG-ban',
+};
+export default notif;
diff --git a/shared/src/i18n/hu/notifications.ts b/shared/src/i18n/hu/notifications.ts
new file mode 100644
index 0000000000000000000000000000000000000000..71f5df41d6aa6861c7aedecaac26bd7a9044f633
--- /dev/null
+++ b/shared/src/i18n/hu/notifications.ts
@@ -0,0 +1,37 @@
+import type { TranslationStrings } from '../types';
+
+const notifications: TranslationStrings = {
+ 'notifications.title': 'Értesítések',
+ 'notifications.markAllRead': 'Összes olvasottnak jelölése',
+ 'notifications.deleteAll': 'Összes törlése',
+ 'notifications.showAll': 'Összes értesítés megtekintése',
+ 'notifications.empty': 'Nincsenek értesítések',
+ 'notifications.emptyDescription': 'Mindennel naprakész vagy!',
+ 'notifications.all': 'Összes',
+ 'notifications.unreadOnly': 'Olvasatlan',
+ 'notifications.markRead': 'Olvasottnak jelölés',
+ 'notifications.markUnread': 'Olvasatlannak jelölés',
+ 'notifications.delete': 'Törlés',
+ 'notifications.system': 'Rendszer',
+ 'notifications.synologySessionCleared.title': 'Synology Photos leválasztva',
+ 'notifications.synologySessionCleared.text':
+ 'A szerver vagy a fiók megváltozott — lépjen a Beállításokba a kapcsolat újrateszteléséhez.',
+ '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.',
+ 'notifications.versionAvailable.title': 'Elérhető frissítés',
+ 'notifications.versionAvailable.text': 'A TREK {version} már elérhető.',
+ 'notifications.versionAvailable.button': 'Részletek megtekintése',
+};
+export default notifications;
diff --git a/shared/src/i18n/hu/oauth.ts b/shared/src/i18n/hu/oauth.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9218f1f738ef0de926d33d3c3d489d164f326b7f
--- /dev/null
+++ b/shared/src/i18n/hu/oauth.ts
@@ -0,0 +1,123 @@
+import type { TranslationStrings } from '../types';
+
+const oauth: TranslationStrings = {
+ 'oauth.scope.group.trips': 'Utazások',
+ 'oauth.scope.group.places': 'Helyek',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'Csomagolás',
+ 'oauth.scope.group.todos': 'Feladatok',
+ 'oauth.scope.group.budget': 'Költségvetés',
+ 'oauth.scope.group.reservations': 'Foglalások',
+ 'oauth.scope.group.collab': 'Együttműködés',
+ 'oauth.scope.group.notifications': 'Értesítések',
+ 'oauth.scope.group.vacay': 'Szabadság',
+ 'oauth.scope.group.geo': 'Geo',
+ 'oauth.scope.group.weather': 'Időjárás',
+ 'oauth.scope.group.journey': 'Útinaplók',
+ 'oauth.scope.trips:read.label': 'Utazások és útvonalak megtekintése',
+ 'oauth.scope.trips:read.description':
+ 'Utazások, napok, napi feljegyzések és tagok olvasása',
+ 'oauth.scope.trips:write.label': 'Utazások és útvonalak szerkesztése',
+ 'oauth.scope.trips:write.description':
+ 'Utazások, napok és feljegyzések létrehozása, frissítése és tagok kezelése',
+ 'oauth.scope.trips:delete.label': 'Utazások törlése',
+ 'oauth.scope.trips:delete.description':
+ 'Teljes utazások végleges törlése — ez a művelet visszafordíthatatlan',
+ 'oauth.scope.trips:share.label': 'Megosztási linkek kezelése',
+ 'oauth.scope.trips:share.description':
+ 'Nyilvános megosztási linkek létrehozása, frissítése és visszavonása',
+ 'oauth.scope.places:read.label': 'Helyek és térképadatok megtekintése',
+ 'oauth.scope.places:read.description':
+ 'Helyek, napi hozzárendelések, címkék és kategóriák olvasása',
+ 'oauth.scope.places:write.label': 'Helyek kezelése',
+ 'oauth.scope.places:write.description':
+ 'Helyek, hozzárendelések és címkék létrehozása, frissítése és törlése',
+ 'oauth.scope.atlas:read.label': 'Atlas megtekintése',
+ 'oauth.scope.atlas:read.description':
+ 'Meglátogatott országok, régiók és bakancslisták olvasása',
+ 'oauth.scope.atlas:write.label': 'Atlas kezelése',
+ 'oauth.scope.atlas:write.description':
+ 'Országok és régiók meglátogatottként jelölése, bakancslisták kezelése',
+ 'oauth.scope.packing:read.label': 'Csomaglisták megtekintése',
+ 'oauth.scope.packing:read.description':
+ 'Csomagolási tételek, táskák és kategória-hozzárendelések olvasása',
+ 'oauth.scope.packing:write.label': 'Csomaglisták kezelése',
+ 'oauth.scope.packing:write.description':
+ 'Csomagolási tételek és táskák hozzáadása, frissítése, törlése, jelölése és átrendezése',
+ 'oauth.scope.todos:read.label': 'Feladatlisták megtekintése',
+ 'oauth.scope.todos:read.description':
+ 'Utazás feladatai és kategória-hozzárendelések olvasása',
+ 'oauth.scope.todos:write.label': 'Feladatlisták kezelése',
+ 'oauth.scope.todos:write.description':
+ 'Feladatok létrehozása, frissítése, jelölése, törlése és átrendezése',
+ 'oauth.scope.budget:read.label': 'Költségvetés megtekintése',
+ 'oauth.scope.budget:read.description':
+ 'Költségvetési tételek és kiadások részletezésének olvasása',
+ 'oauth.scope.budget:write.label': 'Költségvetés kezelése',
+ 'oauth.scope.budget:write.description':
+ 'Költségvetési tételek létrehozása, frissítése és törlése',
+ 'oauth.scope.reservations:read.label': 'Foglalások megtekintése',
+ 'oauth.scope.reservations:read.description':
+ 'Foglalások és szállásadatok olvasása',
+ 'oauth.scope.reservations:write.label': 'Foglalások kezelése',
+ 'oauth.scope.reservations:write.description':
+ 'Foglalások létrehozása, frissítése, törlése és átrendezése',
+ 'oauth.scope.collab:read.label': 'Együttműködés megtekintése',
+ 'oauth.scope.collab:read.description':
+ 'Együttműködési feljegyzések, szavazások és üzenetek olvasása',
+ 'oauth.scope.collab:write.label': 'Együttműködés kezelése',
+ 'oauth.scope.collab:write.description':
+ 'Együttműködési feljegyzések, szavazások és üzenetek létrehozása, frissítése és törlése',
+ 'oauth.scope.notifications:read.label': 'Értesítések megtekintése',
+ 'oauth.scope.notifications:read.description':
+ 'Alkalmazáson belüli értesítések és olvasatlan számok olvasása',
+ 'oauth.scope.notifications:write.label': 'Értesítések kezelése',
+ 'oauth.scope.notifications:write.description':
+ 'Értesítések olvasottként jelölése és válaszadás rájuk',
+ 'oauth.scope.vacay:read.label': 'Szabadságtervek megtekintése',
+ 'oauth.scope.vacay:read.description':
+ 'Szabadságtervezési adatok, bejegyzések és statisztikák olvasása',
+ 'oauth.scope.vacay:write.label': 'Szabadságtervek kezelése',
+ 'oauth.scope.vacay:write.description':
+ 'Szabadságbejegyzések, ünnepnapok és csapattervek létrehozása és kezelése',
+ 'oauth.scope.geo:read.label': 'Térképek és geokódolás',
+ 'oauth.scope.geo:read.description':
+ 'Helyek keresése, térkép URL-ek feloldása és koordináták fordított geokódolása',
+ 'oauth.scope.weather:read.label': 'Időjárás-előrejelzések',
+ 'oauth.scope.weather:read.description':
+ 'Időjárás-előrejelzések lekérése az utazási helyszínekre és dátumokra',
+ 'oauth.scope.journey:read.label': 'Útinaplók megtekintése',
+ 'oauth.scope.journey:read.description':
+ 'Útinaplók, bejegyzések és közreműködők listájának olvasása',
+ 'oauth.scope.journey:write.label': 'Útinaplók kezelése',
+ 'oauth.scope.journey:write.description':
+ 'Útinaplók és bejegyzéseik létrehozása, frissítése és törlése',
+ 'oauth.scope.journey:share.label': 'Útinapló-linkek kezelése',
+ 'oauth.scope.journey:share.description':
+ 'Nyilvános megosztási linkek létrehozása, frissítése és visszavonása útinaplókhoz',
+ 'oauth.authorize.authorizing': 'Authorizing…', // en-fallback
+ 'oauth.authorize.loading': 'Loading…', // en-fallback
+ 'oauth.authorize.errorTitle': 'Authorization Error', // en-fallback
+ 'oauth.authorize.loginTitle': 'Sign in to continue', // en-fallback
+ 'oauth.authorize.loginDescription':
+ '{client} wants access to your TREK account. Please sign in first.', // en-fallback
+ 'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
+ 'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
+ 'oauth.authorize.requestDescription':
+ 'This application is requesting access to your TREK account.', // en-fallback
+ 'oauth.authorize.trustNote':
+ 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
+ 'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
+ 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
+ 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
+ 'oauth.authorize.deny': 'Deny', // en-fallback
+ 'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
+ 'oauth.authorize.permissionsRequested': 'Permissions requested', // en-fallback
+ 'oauth.authorize.alwaysIncluded': 'Always included', // en-fallback
+ 'oauth.authorize.alwaysTool.listTrips':
+ 'List your trips so the AI can discover trip IDs', // en-fallback
+ 'oauth.authorize.alwaysTool.getTripSummary':
+ 'Read a trip overview needed to use any other tool', // en-fallback
+};
+export default oauth;
diff --git a/shared/src/i18n/hu/packing.ts b/shared/src/i18n/hu/packing.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5dd6ca5d9615342b97d02a06c3d39e5319bf3eae
--- /dev/null
+++ b/shared/src/i18n/hu/packing.ts
@@ -0,0 +1,186 @@
+import type { TranslationStrings } from '../types';
+
+const packing: TranslationStrings = {
+ 'packing.title': 'Csomagolási lista',
+ 'packing.empty': 'A csomagolási lista üres',
+ 'packing.import': 'Importálás',
+ 'packing.importTitle': 'Csomagolási lista importálása',
+ 'packing.importHint':
+ 'Soronként egy tétel. Formátum: Kategória, Név, Súly g-ban (opcionális), Táska (opcionális), checked/unchecked (opcionális)',
+ 'packing.importPlaceholder':
+ 'Tisztálkodás, Fogkefe\nRuházat, Pólók, 200\nDokumentumok, Útlevél, , Kézipoggyász\nElektronika, Töltő, 50, Bőrönd, checked',
+ 'packing.importCsv': 'CSV/TXT betöltése',
+ 'packing.importAction': '{count} importálása',
+ 'packing.importSuccess': '{count} tétel importálva',
+ 'packing.importError': 'Importálás sikertelen',
+ 'packing.importEmpty': 'Nincsenek importálható tételek',
+ 'packing.progress': '{packed} / {total} becsomagolva ({percent}%)',
+ 'packing.clearChecked': '{count} kipipált eltávolítása',
+ 'packing.clearCheckedShort': '{count} eltávolítása',
+ 'packing.suggestions': 'Javaslatok',
+ 'packing.suggestionsTitle': 'Javaslatok hozzáadása',
+ 'packing.allSuggested': 'Minden javaslat hozzáadva',
+ 'packing.allPacked': 'Minden be van csomagolva!',
+ 'packing.addPlaceholder': 'Új tárgy hozzáadása...',
+ 'packing.categoryPlaceholder': 'Kategória...',
+ 'packing.filterAll': 'Összes',
+ 'packing.filterOpen': 'Nyitott',
+ 'packing.filterDone': 'Kész',
+ 'packing.emptyTitle': 'A csomagolási lista üres',
+ 'packing.emptyHint': 'Adj hozzá tárgyakat vagy használd a javaslatokat',
+ 'packing.emptyFiltered': 'Nincs elem ebben a szűrőben',
+ 'packing.menuRename': 'Átnevezés',
+ 'packing.menuCheckAll': 'Összes kipipálása',
+ 'packing.menuUncheckAll': 'Összes jelölés törlése',
+ 'packing.menuDeleteCat': 'Kategória törlése',
+ 'packing.noMembers': 'Nincsenek utazási tagok',
+ 'packing.addItem': 'Tétel hozzáadása',
+ 'packing.addItemPlaceholder': 'Tétel neve...',
+ 'packing.addCategory': 'Kategória hozzáadása',
+ 'packing.newCategoryPlaceholder': 'Kategória neve (pl. Ruházat)',
+ 'packing.applyTemplate': 'Sablon alkalmazása',
+ 'packing.template': 'Sablon',
+ 'packing.templateApplied': '{count} tétel hozzáadva a sablonból',
+ 'packing.templateError': 'Nem sikerült alkalmazni a sablont',
+ 'packing.saveAsTemplate': 'Mentés sablonként',
+ 'packing.templateName': 'Sablon neve',
+ 'packing.templateSaved': 'Csomaglista elmentve sablonként',
+ 'packing.bags': 'Táskák',
+ 'packing.noBag': 'Nincs hozzárendelve',
+ 'packing.totalWeight': 'Összsúly',
+ 'packing.bagName': 'Táska neve...',
+ 'packing.addBag': 'Táska hozzáadása',
+ 'packing.changeCategory': 'Kategória módosítása',
+ 'packing.confirm.clearChecked':
+ 'Biztosan el szeretnéd távolítani a(z) {count} kipipált tárgyat?',
+ 'packing.confirm.deleteCat':
+ 'Biztosan törölni szeretnéd a(z) "{name}" kategóriát {count} tárggyal?',
+ 'packing.defaultCategory': 'Egyéb',
+ 'packing.toast.saveError': 'Nem sikerült menteni',
+ 'packing.toast.deleteError': 'Nem sikerült törölni',
+ 'packing.toast.renameError': 'Nem sikerült átnevezni',
+ 'packing.toast.addError': 'Nem sikerült hozzáadni',
+ 'packing.suggestions.items': [
+ {
+ name: 'Útlevél',
+ category: 'Dokumentumok',
+ },
+ {
+ name: 'Személyi igazolvány',
+ category: 'Dokumentumok',
+ },
+ {
+ name: 'Utazási biztosítás',
+ category: 'Dokumentumok',
+ },
+ {
+ name: 'Repülőjegyek',
+ category: 'Dokumentumok',
+ },
+ {
+ name: 'Bankkártya',
+ category: 'Pénzügyek',
+ },
+ {
+ name: 'Készpénz',
+ category: 'Pénzügyek',
+ },
+ {
+ name: 'Vízum',
+ category: 'Dokumentumok',
+ },
+ {
+ name: 'Pólók',
+ category: 'Ruházat',
+ },
+ {
+ name: 'Nadrágok',
+ category: 'Ruházat',
+ },
+ {
+ name: 'Fehérnemű',
+ category: 'Ruházat',
+ },
+ {
+ name: 'Zoknik',
+ category: 'Ruházat',
+ },
+ {
+ name: 'Kabát',
+ category: 'Ruházat',
+ },
+ {
+ name: 'Hálóruha',
+ category: 'Ruházat',
+ },
+ {
+ name: 'Fürdőruha',
+ category: 'Ruházat',
+ },
+ {
+ name: 'Esőkabát',
+ category: 'Ruházat',
+ },
+ {
+ name: 'Kényelmes cipő',
+ category: 'Ruházat',
+ },
+ {
+ name: 'Fogkefe',
+ category: 'Tisztálkodás',
+ },
+ {
+ name: 'Fogkrém',
+ category: 'Tisztálkodás',
+ },
+ {
+ name: 'Sampon',
+ category: 'Tisztálkodás',
+ },
+ {
+ name: 'Dezodor',
+ category: 'Tisztálkodás',
+ },
+ {
+ name: 'Naptej',
+ category: 'Tisztálkodás',
+ },
+ {
+ name: 'Borotva',
+ category: 'Tisztálkodás',
+ },
+ {
+ name: 'Töltő',
+ category: 'Elektronika',
+ },
+ {
+ name: 'Powerbank',
+ category: 'Elektronika',
+ },
+ {
+ name: 'Fejhallgató',
+ category: 'Elektronika',
+ },
+ {
+ name: 'Úti adapter',
+ category: 'Elektronika',
+ },
+ {
+ name: 'Fényképezőgép',
+ category: 'Elektronika',
+ },
+ {
+ name: 'Fájdalomcsillapító',
+ category: 'Egészség',
+ },
+ {
+ name: 'Ragtapasz',
+ category: 'Egészség',
+ },
+ {
+ name: 'Fertőtlenítőszer',
+ category: 'Egészség',
+ },
+ ],
+};
+export default packing;
diff --git a/shared/src/i18n/hu/pdf.ts b/shared/src/i18n/hu/pdf.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f19283df75dd05fec6066230d2fa705f07a59af8
--- /dev/null
+++ b/shared/src/i18n/hu/pdf.ts
@@ -0,0 +1,10 @@
+import type { TranslationStrings } from '../types';
+
+const pdf: TranslationStrings = {
+ 'pdf.travelPlan': 'Utazási terv',
+ 'pdf.planned': 'Tervezett',
+ 'pdf.costLabel': 'Költség',
+ 'pdf.preview': 'PDF előnézet',
+ 'pdf.saveAsPdf': 'Mentés PDF-ként',
+};
+export default pdf;
diff --git a/shared/src/i18n/hu/perm.ts b/shared/src/i18n/hu/perm.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cf4f89dfbefe54fea76013fe5463eabcc38d97cd
--- /dev/null
+++ b/shared/src/i18n/hu/perm.ts
@@ -0,0 +1,65 @@
+import type { TranslationStrings } from '../types';
+
+const perm: TranslationStrings = {
+ 'perm.title': 'Jogosultsági beállítások',
+ 'perm.subtitle':
+ 'Szabályozd, ki milyen műveleteket végezhet az alkalmazásban',
+ 'perm.saved': 'Jogosultsági beállítások mentve',
+ 'perm.resetDefaults': 'Alapértelmezések visszaállítása',
+ 'perm.customized': 'testreszabott',
+ 'perm.level.admin': 'Csak adminisztrátor',
+ 'perm.level.tripOwner': 'Utazás tulajdonosa',
+ 'perm.level.tripMember': 'Utazás tagjai',
+ 'perm.level.everybody': 'Mindenki',
+ 'perm.cat.trip': 'Utazáskezelés',
+ 'perm.cat.members': 'Tagkezelés',
+ 'perm.cat.files': 'Fájlok',
+ 'perm.cat.content': 'Tartalom és menetrend',
+ 'perm.cat.extras': 'Költségvetés, csomagolás és együttműködés',
+ 'perm.action.trip_create': 'Utazások létrehozása',
+ 'perm.action.trip_edit': 'Utazás részleteinek szerkesztése',
+ 'perm.action.trip_delete': 'Utazások törlése',
+ 'perm.action.trip_archive': 'Utazások archiválása / visszaállítása',
+ 'perm.action.trip_cover_upload': 'Borítókép feltöltése',
+ 'perm.action.member_manage': 'Tagok hozzáadása / eltávolítása',
+ 'perm.action.file_upload': 'Fájlok feltöltése',
+ 'perm.action.file_edit': 'Fájl metaadatok szerkesztése',
+ 'perm.action.file_delete': 'Fájlok törlése',
+ 'perm.action.place_edit': 'Helyek hozzáadása / szerkesztése / törlése',
+ 'perm.action.day_edit': 'Napok, jegyzetek és hozzárendelések szerkesztése',
+ 'perm.action.reservation_edit': 'Foglalások kezelése',
+ 'perm.action.budget_edit': 'Költségvetés kezelése',
+ 'perm.action.packing_edit': 'Csomagolási listák kezelése',
+ 'perm.action.collab_edit': 'Együttműködés (jegyzetek, szavazások, chat)',
+ 'perm.action.share_manage': 'Megosztási linkek kezelése',
+ 'perm.actionHint.trip_create': 'Ki hozhat létre új utazásokat',
+ 'perm.actionHint.trip_edit':
+ 'Ki módosíthatja az utazás nevét, dátumait, leírását és pénznemét',
+ 'perm.actionHint.trip_delete': 'Ki törölhet véglegesen egy utazást',
+ 'perm.actionHint.trip_archive':
+ 'Ki archiválhat vagy állíthat vissza egy utazást',
+ 'perm.actionHint.trip_cover_upload':
+ 'Ki tölthet fel vagy módosíthat borítóképet',
+ 'perm.actionHint.member_manage':
+ 'Ki hívhat meg vagy távolíthat el utazás tagokat',
+ 'perm.actionHint.file_upload': 'Ki tölthet fel fájlokat egy utazáshoz',
+ 'perm.actionHint.file_edit':
+ 'Ki szerkesztheti a fájlok leírásait és linkjeit',
+ 'perm.actionHint.file_delete':
+ 'Ki helyezhet fájlokat a kukába vagy törölheti véglegesen',
+ 'perm.actionHint.place_edit':
+ 'Ki adhat hozzá, szerkeszthet vagy törölhet helyeket',
+ 'perm.actionHint.day_edit':
+ 'Ki szerkesztheti a napokat, napi jegyzeteket és hely-hozzárendeléseket',
+ 'perm.actionHint.reservation_edit':
+ 'Ki hozhat létre, szerkeszthet vagy törölhet foglalásokat',
+ 'perm.actionHint.budget_edit':
+ 'Ki hozhat létre, szerkeszthet vagy törölhet költségvetési tételeket',
+ 'perm.actionHint.packing_edit':
+ 'Ki kezelheti a csomagolási tételeket és táskákat',
+ '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',
+};
+export default perm;
diff --git a/shared/src/i18n/hu/photos.ts b/shared/src/i18n/hu/photos.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c7e15a2e0e7e563a8d72b89caf581d2699fc19ce
--- /dev/null
+++ b/shared/src/i18n/hu/photos.ts
@@ -0,0 +1,25 @@
+import type { TranslationStrings } from '../types';
+
+const photos: TranslationStrings = {
+ 'photos.title': 'Fotók',
+ 'photos.subtitle': '{count} fotó a következőhöz: {trip}',
+ 'photos.dropHere': 'Húzza ide a fényképeket...',
+ 'photos.dropHereActive': 'Húzza ide a fényképeket',
+ 'photos.captionForAll': 'Felirat (mindenkinek)',
+ 'photos.captionPlaceholder': 'Opcionális felirat...',
+ 'photos.addCaption': 'Felirat hozzáadása...',
+ 'photos.allDays': 'Minden nap',
+ 'photos.noPhotos': 'Még nincsenek fotók',
+ 'photos.uploadHint': 'Töltsd fel az úti fotóidat',
+ 'photos.clickToSelect': 'vagy kattints a kiválasztáshoz',
+ 'photos.linkPlace': 'Hely társítása',
+ 'photos.noPlace': 'Nincs hely',
+ 'photos.uploadN': '{n} fotó feltöltése',
+ 'photos.linkDay': 'Nap csatolása',
+ 'photos.noDay': 'Nincs nap',
+ 'photos.dayLabel': '{number}. nap',
+ 'photos.photoSelected': 'Fotó kiválasztva',
+ 'photos.photosSelected': 'Fotók kiválasztva',
+ 'photos.fileTypeHint': 'JPG, PNG, WebP · max. 10 MB · legfeljebb 30 fotó',
+};
+export default photos;
diff --git a/shared/src/i18n/hu/places.ts b/shared/src/i18n/hu/places.ts
new file mode 100644
index 0000000000000000000000000000000000000000..aecdc91faef3f0a1c7fc98462678105850a2eb35
--- /dev/null
+++ b/shared/src/i18n/hu/places.ts
@@ -0,0 +1,98 @@
+import type { TranslationStrings } from '../types';
+
+const places: TranslationStrings = {
+ 'places.addPlace': 'Hely/Tevékenység hozzáadása',
+ 'places.importFile': 'Fájl importálása',
+ 'places.sidebarDrop': 'Ejtse el az importáláshoz',
+ 'places.importFileHint':
+ '.gpx, .kml vagy .kmz fájlok importálása olyan eszközökből, mint a Google My Maps, Google Earth vagy egy GPS tracker.',
+ 'places.importFileDropHere':
+ 'Kattintson egy fájl kiválasztásához, vagy húzza ide',
+ 'places.importFileDropActive': 'Ejtse ide a fájlt a kiválasztáshoz',
+ 'places.importFileUnsupported':
+ 'Nem támogatott fájltípus. Használjon .gpx, .kml vagy .kmz fájlt.',
+ 'places.importFileTooLarge':
+ 'A fájl túl nagy. A maximális feltöltési méret {maxMb} MB.',
+ 'places.importFileError': 'Importálás sikertelen',
+ 'places.importAllSkipped': 'Minden hely már szerepel az utazásban.',
+ 'places.gpxImported': '{count} hely importálva GPX-ből',
+ 'places.gpxImportTypes': 'Mit szeretnél importálni?',
+ 'places.gpxImportWaypoints': 'Útpontok',
+ 'places.gpxImportRoutes': 'Útvonalak',
+ 'places.gpxImportTracks': 'Nyomvonalak (útvonalgeometriával)',
+ 'places.gpxImportNoneSelected':
+ 'Válassz legalább egy típust az importáláshoz.',
+ 'places.kmlImportTypes': 'Mit szeretnél importálni?',
+ 'places.kmlImportPoints': 'Pontok (Placemarks)',
+ 'places.kmlImportPaths': 'Útvonalak (LineStrings)',
+ 'places.kmlImportNoneSelected': 'Válassz legalább egy típust.',
+ 'places.selectionCount': '{count} kiválasztva',
+ 'places.deleteSelected': 'Kijelöltek törlése',
+ 'places.kmlKmzImported': '{count} hely importálva KMZ/KML-ből',
+ 'places.urlResolved': 'Hely importálva URL-ből',
+ 'places.importList': 'Lista importálás',
+ 'places.kmlKmzSummaryValues':
+ 'Placemarks: {total} • Importálva: {created} • Kihagyva: {skipped}',
+ 'places.importGoogleList': 'Google Lista',
+ 'places.importNaverList': 'Naver Lista',
+ 'places.googleListHint':
+ 'Illessz be egy megosztott Google Maps lista linket az osszes hely importalasahoz.',
+ 'places.googleListImported': '{count} hely importalva a(z) "{list}" listabol',
+ 'places.googleListError': 'Google Maps lista importalasa sikertelen',
+ 'places.naverListHint':
+ 'Illessz be egy megosztott Naver Maps lista linket az összes hely importálásához.',
+ 'places.naverListImported': '{count} hely importálva a(z) "{list}" listából',
+ 'places.naverListError': 'Naver Maps lista importálása sikertelen',
+ 'places.viewDetails': 'Részletek megtekintése',
+ 'places.assignToDay': 'Melyik naphoz adod?',
+ 'places.all': 'Összes',
+ 'places.unplanned': 'Nem tervezett',
+ 'places.filterTracks': 'Nyomvonalak',
+ 'places.search': 'Helyek keresése...',
+ 'places.allCategories': 'Összes kategória',
+ 'places.categoriesSelected': 'kategória',
+ 'places.clearFilter': 'Szűrő törlése',
+ 'places.count': '{count} hely',
+ 'places.countSingular': '1 hely',
+ 'places.allPlanned': 'Minden hely be van tervezve',
+ 'places.noneFound': 'Nem találhatók helyek',
+ 'places.editPlace': 'Hely szerkesztése',
+ 'places.formName': 'Név',
+ 'places.formNamePlaceholder': 'pl. Eiffel-torony',
+ 'places.formDescription': 'Leírás',
+ 'places.formDescriptionPlaceholder': 'Rövid leírás...',
+ 'places.formAddress': 'Cím',
+ 'places.formAddressPlaceholder': 'Utca, Város, Ország',
+ 'places.formLat': 'Szélességi fok (pl. 48.8566)',
+ 'places.formLng': 'Hosszúsági fok (pl. 2.3522)',
+ 'places.formCategory': 'Kategória',
+ 'places.noCategory': 'Nincs kategória',
+ 'places.categoryNamePlaceholder': 'Kategória neve',
+ 'places.formTime': 'Időpont',
+ 'places.startTime': 'Kezdés',
+ 'places.endTime': 'Befejezés',
+ 'places.endTimeBeforeStart': 'A befejezési idő a kezdési idő előtt van',
+ 'places.timeCollision': 'Időbeli átfedés:',
+ 'places.formWebsite': 'Weboldal',
+ 'places.formNotes': 'Jegyzetek',
+ 'places.formNotesPlaceholder': 'Személyes jegyzetek...',
+ 'places.formReservation': 'Foglalás',
+ 'places.reservationNotesPlaceholder':
+ 'Foglalási jegyzetek, visszaigazolási szám...',
+ 'places.mapsSearchPlaceholder': 'Helyek keresése...',
+ 'places.mapsSearchError': 'Helykeresés sikertelen.',
+ 'places.loadingDetails': 'Hely adatainak betöltése…',
+ 'places.osmHint':
+ 'OpenStreetMap keresés aktív (képek, nyitvatartás és értékelések nélkül). Bővített adatokhoz add meg a Google API kulcsot a beállításokban.',
+ 'places.osmActive':
+ 'Keresés OpenStreetMap-en keresztül (képek, értékelések és nyitvatartás nélkül). Bővített adatokhoz add meg a Google API kulcsot a beállításokban.',
+ 'places.categoryCreateError': 'Nem sikerült létrehozni a kategóriát',
+ 'places.nameRequired': 'Kérjük, adj meg egy nevet',
+ 'places.saveError': 'Nem sikerült menteni',
+ 'places.duplicateExists': "A(z) '{name}' már szerepel ebben az utazásban.",
+ 'places.addAnyway': 'Hozzáadás mindenképp',
+ 'places.enrichOnImport': 'Helyek gazdagítása a Google-lel',
+ 'places.enrichOnImportHint':
+ 'Minden importált helyet megkeres, hogy fotókat, címet és elérhetőséget adjon hozzá. Google Maps-kulcs szükséges.',
+};
+export default places;
diff --git a/shared/src/i18n/hu/planner.ts b/shared/src/i18n/hu/planner.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a07d248fb8a20e603420e805e74448ccc40b216f
--- /dev/null
+++ b/shared/src/i18n/hu/planner.ts
@@ -0,0 +1,68 @@
+import type { TranslationStrings } from '../types';
+
+const planner: TranslationStrings = {
+ 'planner.places': 'Helyek',
+ 'planner.bookings': 'Foglalások',
+ 'planner.packingList': 'Csomagolási lista',
+ 'planner.documents': 'Dokumentumok',
+ 'planner.dayPlan': 'Napi terv',
+ 'planner.reservations': 'Foglalások',
+ 'planner.minTwoPlaces': 'Legalább 2 koordinátákkal rendelkező hely szükséges',
+ 'planner.noGeoPlaces': 'Nincsenek koordinátákkal rendelkező helyek',
+ 'planner.routeCalculated': 'Útvonal kiszámítva',
+ 'planner.routeCalcFailed': 'Nem sikerült kiszámítani az útvonalat',
+ 'planner.routeError': 'Hiba az útvonalszámítás során',
+ 'planner.icsExportFailed': 'Az ICS-exportálás sikertelen',
+ 'planner.routeOptimized': 'Útvonal optimalizálva',
+ 'planner.reservationUpdated': 'Foglalás frissítve',
+ 'planner.reservationAdded': 'Foglalás hozzáadva',
+ 'planner.confirmDeleteReservation': 'Foglalás törlése?',
+ 'planner.reservationDeleted': 'Foglalás törölve',
+ 'planner.days': 'nap',
+ 'planner.allPlaces': 'Összes hely',
+ 'planner.totalPlaces': 'Összesen {n} hely',
+ 'planner.noDaysPlanned': 'Még nincsenek napok tervezve',
+ 'planner.editTrip': 'Utazás szerkesztése →',
+ 'planner.placeOne': '1 hely',
+ 'planner.placeN': '{n} hely',
+ 'planner.addNote': 'Jegyzet hozzáadása',
+ 'planner.noEntries': 'Nincsenek bejegyzések erre a napra',
+ 'planner.addPlace': 'Hely/tevékenység hozzáadása',
+ 'planner.addPlaceShort': '+ Hely/tevékenység hozzáadása',
+ 'planner.resPending': 'Foglalás függőben · ',
+ 'planner.resConfirmed': 'Foglalás megerősítve · ',
+ 'planner.notePlaceholder': 'Jegyzet…',
+ 'planner.noteTimePlaceholder': 'Időpont (opcionális)',
+ 'planner.noteExamplePlaceholder':
+ 'pl. S3 14:30-kor a főpályaudvarról, komp a 7. mólóról, ebédszünet…',
+ 'planner.totalCost': 'Összköltség',
+ 'planner.searchPlaces': 'Helyek keresése…',
+ 'planner.allCategories': 'Összes kategória',
+ 'planner.noPlacesFound': 'Nem találhatók helyek',
+ 'planner.addFirstPlace': 'Első hely hozzáadása',
+ 'planner.noReservations': 'Nincsenek foglalások',
+ 'planner.addFirstReservation': 'Első foglalás hozzáadása',
+ 'planner.new': 'Új',
+ 'planner.addToDay': '+ Nap',
+ 'planner.calculating': 'Számítás…',
+ 'planner.route': 'Útvonal',
+ 'planner.optimize': 'Optimalizálás',
+ 'planner.openGoogleMaps': 'Megnyitás a Google Térképben',
+ 'planner.selectDayHint':
+ 'Válassz egy napot a bal oldali listából a napi terv megtekintéséhez',
+ 'planner.noPlacesForDay': 'Még nincsenek helyek erre a napra',
+ 'planner.addPlacesLink': 'Helyek hozzáadása →',
+ 'planner.minTotal': 'perc összesen',
+ 'planner.noReservation': 'Nincs foglalás',
+ 'planner.removeFromDay': 'Eltávolítás a napról',
+ 'planner.addToThisDay': 'Hozzáadás a naphoz',
+ 'planner.overview': 'Áttekintés',
+ 'planner.noDays': 'Még nincsenek napok',
+ 'planner.editTripToAddDays': 'Szerkeszd az utazást napok hozzáadásához',
+ 'planner.dayCount': '{n} nap',
+ 'planner.clickToUnlock': 'Kattints a feloldáshoz',
+ 'planner.keepPosition': 'Pozíció megtartása útvonal-optimalizálás során',
+ 'planner.dayDetails': 'Nap részletei',
+ 'planner.dayN': '{n}. nap',
+};
+export default planner;
diff --git a/shared/src/i18n/hu/register.ts b/shared/src/i18n/hu/register.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d0e2e6057718525dd63d5c7a0a8817f541c032e1
--- /dev/null
+++ b/shared/src/i18n/hu/register.ts
@@ -0,0 +1,27 @@
+import type { TranslationStrings } from '../types';
+
+const register: TranslationStrings = {
+ 'register.passwordMismatch': 'A jelszavak nem egyeznek',
+ 'register.passwordTooShort':
+ 'A jelszónak legalább 8 karakter hosszúnak kell lennie',
+ 'register.failed': 'Regisztráció sikertelen',
+ 'register.getStarted': 'Kezdjük',
+ 'register.subtitle':
+ 'Hozz létre egy fiókot, és kezdd el megtervezni álomutazásaidat.',
+ 'register.feature1': 'Korlátlan utazási tervek',
+ 'register.feature2': 'Interaktív térképnézet',
+ 'register.feature3': 'Helyek és kategóriák kezelése',
+ 'register.feature4': 'Foglalások nyomon követése',
+ 'register.feature5': 'Csomagolási listák készítése',
+ 'register.feature6': 'Fényképek és fájlok tárolása',
+ 'register.createAccount': 'Fiók létrehozása',
+ 'register.startPlanning': 'Kezdd el az utazástervezést',
+ 'register.minChars': 'Min. 6 karakter',
+ 'register.confirmPassword': 'Jelszó megerősítése',
+ 'register.repeatPassword': 'Jelszó ismétlése',
+ 'register.registering': 'Regisztráció...',
+ 'register.register': 'Regisztráció',
+ 'register.hasAccount': 'Már van fiókod?',
+ 'register.signIn': 'Bejelentkezés',
+};
+export default register;
diff --git a/shared/src/i18n/hu/reservations.ts b/shared/src/i18n/hu/reservations.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a60eb475281c0a6481389939618b53eaf674ae92
--- /dev/null
+++ b/shared/src/i18n/hu/reservations.ts
@@ -0,0 +1,163 @@
+import type { TranslationStrings } from '../types';
+
+const reservations: TranslationStrings = {
+ 'reservations.title': 'Foglalások',
+ 'reservations.empty': 'Még nincsenek foglalások',
+ 'reservations.emptyHint':
+ 'Adj hozzá foglalásokat repülőkhöz, szállodákhoz és egyebekhez',
+ 'reservations.add': 'Foglalás hozzáadása',
+ 'reservations.addManual': 'Kézi foglalás',
+ 'reservations.placeHint':
+ 'Tipp: A foglalásokat legjobb közvetlenül egy helyről létrehozni, hogy összekapcsolódjon a napi tervvel.',
+ 'reservations.confirmed': 'Megerősítve',
+ 'reservations.pending': 'Függőben',
+ 'reservations.summary': '{confirmed} megerősítve, {pending} függőben',
+ 'reservations.fromPlan': 'Tervből',
+ 'reservations.showFiles': 'Fájlok megjelenítése',
+ 'reservations.editTitle': 'Foglalás szerkesztése',
+ 'reservations.status': 'Állapot',
+ 'reservations.datetime': 'Dátum és idő',
+ 'reservations.startTime': 'Kezdési idő',
+ 'reservations.endTime': 'Befejezési idő',
+ 'reservations.date': 'Dátum',
+ 'reservations.time': 'Időpont',
+ 'reservations.timeAlt': 'Időpont (alternatív, pl. 19:30)',
+ 'reservations.linkExisting': 'Meglévő fájl csatolása',
+ 'reservations.notes': 'Jegyzetek',
+ 'reservations.notesPlaceholder': 'További jegyzetek...',
+ 'reservations.meta.airline': 'Légitársaság',
+ 'reservations.meta.flightNumber': 'Járatszám',
+ 'reservations.meta.from': 'Honnan',
+ 'reservations.meta.to': 'Hová',
+ 'reservations.layover.route': 'Útvonal',
+ 'reservations.layover.stop': 'Megálló',
+ 'reservations.layover.addStop': 'Megálló hozzáadása',
+ 'reservations.layover.connection': 'Csatlakozás',
+ 'reservations.layover.layover': 'Átszállás',
+ 'reservations.needsReview': 'Ellenőrzés',
+ 'reservations.needsReviewHint':
+ 'A repülőteret nem sikerült automatikusan azonosítani — erősítsd meg a helyet.',
+ 'reservations.searchLocation': 'Állomás, kikötő, cím keresése...',
+ 'reservations.meta.trainNumber': 'Vonatszám',
+ 'reservations.meta.platform': 'Vágány',
+ 'reservations.meta.seat': 'Ülés',
+ 'reservations.meta.checkIn': 'Bejelentkezés',
+ 'reservations.meta.checkInUntil': 'Bejelentkezés eddig',
+ 'reservations.meta.checkOut': 'Kijelentkezés',
+ 'reservations.meta.linkAccommodation': 'Szállás',
+ 'reservations.meta.pickAccommodation': 'Szállás hozzárendelése',
+ 'reservations.meta.noAccommodation': 'Nincs',
+ 'reservations.meta.hotelPlace': 'Szálloda',
+ 'reservations.meta.pickHotel': 'Szálloda kiválasztása',
+ 'reservations.meta.fromDay': 'Ettől',
+ 'reservations.meta.toDay': 'Eddig',
+ 'reservations.meta.selectDay': 'Nap kiválasztása',
+ 'reservations.type.flight': 'Repülő',
+ 'reservations.type.hotel': 'Szálloda',
+ 'reservations.type.restaurant': 'Étterem',
+ 'reservations.type.train': 'Vonat',
+ 'reservations.type.car': 'Autó',
+ 'reservations.type.cruise': 'Hajóút',
+ 'reservations.type.event': 'Esemény',
+ 'reservations.type.tour': 'Túra',
+ 'reservations.type.other': 'Egyéb',
+ 'reservations.type.bus': 'Busz',
+ 'reservations.type.ferry': 'Komp',
+ 'reservations.type.bicycle': 'Kerékpár',
+ 'reservations.type.taxi': 'Taxi',
+ 'reservations.type.transport_other': 'Egyéb',
+ 'reservations.confirm.delete':
+ 'Biztosan törölni szeretnéd a(z) "{name}" foglalást?',
+ 'reservations.confirm.deleteTitle': 'Foglalás törlése?',
+ 'reservations.confirm.deleteBody': '"{name}" véglegesen törlődik.',
+ 'reservations.toast.updated': 'Foglalás frissítve',
+ 'reservations.toast.removed': 'Foglalás törölve',
+ 'reservations.toast.fileUploaded': 'Fájl feltöltve',
+ 'reservations.toast.uploadError': 'Feltöltés sikertelen',
+ 'reservations.newTitle': 'Új foglalás',
+ 'reservations.bookingType': 'Foglalás típusa',
+ 'reservations.titleLabel': 'Cím',
+ 'reservations.titlePlaceholder': 'pl. Lufthansa LH123, Hotel Adlon, ...',
+ 'reservations.locationAddress': 'Helyszín / Cím',
+ 'reservations.locationPlaceholder': 'Cím, Repülőtér, Szálloda...',
+ 'reservations.confirmationCode': 'Foglalási kód',
+ 'reservations.confirmationPlaceholder': 'pl. ABC12345',
+ 'reservations.day': 'Nap',
+ 'reservations.noDay': 'Nincs nap',
+ 'reservations.place': 'Hely',
+ 'reservations.noPlace': 'Nincs hely',
+ 'reservations.pendingSave': 'mentés…',
+ 'reservations.uploading': 'Feltöltés...',
+ 'reservations.attachFile': 'Fájl csatolása',
+ 'reservations.toast.saveError': 'Nem sikerült menteni',
+ 'reservations.toast.updateError': 'Nem sikerült frissíteni',
+ 'reservations.toast.deleteError': 'Nem sikerült törölni',
+ 'reservations.confirm.remove': '"{name}" foglalás eltávolítása?',
+ 'reservations.linkAssignment': 'Összekapcsolás napi tervvel',
+ 'reservations.pickAssignment': 'Válassz hozzárendelést a tervedből...',
+ 'reservations.noAssignment': 'Nincs összekapcsolás (önálló)',
+ 'reservations.price': 'Ár',
+ 'reservations.budgetCategory': 'Költségvetési kategória',
+ 'reservations.budgetCategoryPlaceholder': 'pl. Közlekedés, Szállás',
+ 'reservations.budgetCategoryAuto': 'Automatikus (foglalás típusa alapján)',
+ 'reservations.budgetHint':
+ 'Mentéskor automatikusan létrejön egy költségvetési tétel.',
+ 'reservations.departureDate': 'Indulás',
+ 'reservations.arrivalDate': 'Érkezés',
+ 'reservations.departureTime': 'Indulási idő',
+ 'reservations.arrivalTime': 'Érkezési idő',
+ 'reservations.pickupDate': 'Felvétel',
+ 'reservations.returnDate': 'Visszaadás',
+ 'reservations.pickupTime': 'Felvétel ideje',
+ 'reservations.returnTime': 'Visszaadás ideje',
+ 'reservations.endDate': 'Befejezés dátuma',
+ 'reservations.meta.departureTimezone': 'TZ indulás',
+ 'reservations.meta.arrivalTimezone': 'TZ érkezés',
+ 'reservations.span.departure': 'Indulás',
+ 'reservations.span.arrival': 'Érkezés',
+ 'reservations.span.inTransit': 'Úton',
+ 'reservations.span.pickup': 'Felvétel',
+ 'reservations.span.return': 'Visszaadás',
+ 'reservations.span.active': 'Aktív',
+ 'reservations.span.start': 'Kezdés',
+ 'reservations.span.end': 'Vége',
+ 'reservations.span.ongoing': 'Folyamatban',
+ 'reservations.validation.endBeforeStart':
+ 'A befejezés dátuma/időpontja a kezdés utáni kell legyen',
+ 'reservations.addBooking': 'Foglalás hozzáadása',
+ 'reservations.import.title': 'Foglalási visszaigazolások importálása',
+ 'reservations.import.cta': 'Importálás fájlból',
+ 'reservations.import.dropHere': 'Dobja ide a foglalási visszaigazolás fájlokat, vagy kattintson a kiválasztáshoz',
+ 'reservations.import.dropActive': 'Dobja ide a fájlokat az importáláshoz',
+ 'reservations.import.acceptedFormats': 'Elfogadott: EML, PDF, PKPass, HTML, TXT (max. 10 MB darabonként, legfeljebb 5 fájl)',
+ 'reservations.import.parsing': 'Fájlok feldolgozása…',
+ 'reservations.import.previewHeading': '{count} foglalás találva',
+ 'reservations.import.previewEmpty': 'A feltöltött fájlokból nem sikerült foglalásokat kinyerni.',
+ 'reservations.import.removeItem': 'Eltávolítás',
+ 'reservations.import.confirm': '{count} foglalás importálása',
+ 'reservations.import.back': 'Vissza',
+ 'reservations.import.success': '{count} foglalás importálva',
+ 'reservations.import.partialFailure': '{created} importálva, {failed} sikertelen',
+ 'reservations.import.error': 'A feldolgozás sikertelen. Győződjön meg arról, hogy a fájl érvényes foglalási visszaigazolás.',
+ 'reservations.import.unavailable': 'A foglalásimportálás nem érhető el ezen a kiszolgálón.',
+ 'reservations.import.unsupportedFormat': 'Nem támogatott fájlformátum. Használjon EML, PDF, PKPass, HTML vagy TXT formátumot.',
+ 'reservations.import.fileTooLarge': 'A(z) „{name}" fájl meghaladja a 10 MB-os korlátot.',
+ 'reservations.airtrail.title': 'Importálás az AirTrailből',
+ 'reservations.airtrail.cta': 'AirTrail',
+ 'reservations.airtrail.synced': 'AirTrail',
+ 'reservations.airtrail.syncedHint': 'Az AirTrailből szinkronizálva — a módosítások mindkét irányban szinkronban maradnak.',
+ 'reservations.airtrail.notSynced': 'Nincs szinkronizálva',
+ 'reservations.airtrail.notSyncedHint': 'Ezt a járatot eltávolították az AirTrailből, és többé nem szinkronizálódik.',
+ 'reservations.airtrail.loadError': 'Nem sikerült betölteni az AirTrail-járataidat.',
+ 'reservations.airtrail.imported': '{count} járat importálva',
+ 'reservations.airtrail.skippedDuplicate': '{count} már szerepel ebben az utazásban, kihagyva',
+ 'reservations.airtrail.nothingImported': 'Nincs mit importálni.',
+ 'reservations.airtrail.importError': 'Az importálás sikertelen. Kérjük, próbáld újra.',
+ 'reservations.airtrail.undo': 'Importálás az AirTrailből',
+ 'reservations.airtrail.alreadyImported': 'Importálva',
+ 'reservations.airtrail.duringTrip': 'Az utazás ideje alatt',
+ 'reservations.airtrail.otherFlights': 'Egyéb járatok',
+ 'reservations.airtrail.empty': 'Nem található járat az AirTrail-fiókodban.',
+ 'reservations.airtrail.importCta': '{count} importálása',
+};
+export default reservations;
diff --git a/shared/src/i18n/hu/settings.ts b/shared/src/i18n/hu/settings.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2ea67e6ae29846e521922bcd7a7f094ecb003e9d
--- /dev/null
+++ b/shared/src/i18n/hu/settings.ts
@@ -0,0 +1,348 @@
+import type { TranslationStrings } from '../types';
+
+const settings: TranslationStrings = {
+ 'settings.title': 'Beállítások',
+ 'settings.subtitle': 'Személyes beállítások konfigurálása',
+ 'settings.tabs.display': 'Megjelenés',
+ 'settings.tabs.map': 'Térkép',
+ 'settings.tabs.notifications': 'Értesítések',
+ 'settings.tabs.integrations': 'Integrációk',
+ 'settings.tabs.account': 'Fiók',
+ 'settings.tabs.offline': 'Offline',
+ 'settings.tabs.about': 'Névjegy',
+ 'settings.map': 'Térkép',
+ 'settings.mapTemplate': 'Térkép sablon',
+ 'settings.mapTemplatePlaceholder.select': 'Sablon kiválasztása...',
+ 'settings.mapDefaultHint':
+ 'Hagyd üresen az OpenStreetMap használatához (alapértelmezett)',
+ 'settings.mapTemplatePlaceholder':
+ 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
+ 'settings.mapHint': 'URL sablon a térképcsempékhez',
+ 'settings.mapProvider': 'Térkép szolgáltató',
+ 'settings.mapProviderHint':
+ 'A Trip Planner és Journey térképekre érvényes. Az Atlas mindig Leafletet használ.',
+ 'settings.mapLeafletSubtitle': 'Klasszikus 2D, bármilyen raszter csempe',
+ 'settings.mapMapboxSubtitle': 'Vektoros csempék, 3D épületek és terep',
+ 'settings.mapExperimental': 'Kísérleti',
+ 'settings.mapMapboxToken': 'Mapbox hozzáférési token',
+ 'settings.mapMapboxTokenHint': 'Publikus token (pk.*) innen:',
+ 'settings.mapMapboxTokenLink': 'mapbox.com → Hozzáférési tokenek',
+ 'settings.mapStyle': 'Térkép stílus',
+ 'settings.mapStylePlaceholder': 'Válassz Mapbox stílust',
+ 'settings.mapStyleHint': 'Preset vagy saját mapbox://styles/USER/ID URL',
+ 'settings.map3dBuildings': '3D épületek és terep',
+ 'settings.map3dHint':
+ 'Dőlés + valódi 3D épület-kiemelés — minden stílussal működik, beleértve a műholdast.',
+ 'settings.mapHighQuality': 'Magas minőség mód',
+ 'settings.mapHighQualityHint':
+ 'Antialiasing + földgömb-vetítés az élesebb kontúrokért és egy valósághű világnézethez.',
+ 'settings.mapHighQualityWarning':
+ 'Gyengébb eszközökön befolyásolhatja a teljesítményt.',
+ 'settings.mapTipLabel': 'Tipp:',
+ 'settings.mapTip':
+ 'Jobb klikk és húzás a térkép forgatásához/döntéséhez. Középső kattintás hely hozzáadásához (a jobb klikk a forgatáshoz van fenntartva).',
+ 'settings.latitude': 'Szélességi fok',
+ 'settings.longitude': 'Hosszúsági fok',
+ 'settings.saveMap': 'Térkép mentése',
+ 'settings.apiKeys': 'API kulcsok',
+ 'settings.mapsKey': 'Google Maps API kulcs',
+ 'settings.mapsKeyHint':
+ 'Helykereséséhez. Places API (New) szükséges. Létrehozás: console.cloud.google.com',
+ 'settings.weatherKey': 'OpenWeatherMap API kulcs',
+ 'settings.weatherKeyHint':
+ 'Időjárás adatokhoz. Ingyenes: openweathermap.org/api',
+ 'settings.keyPlaceholder': 'Kulcs megadása...',
+ 'settings.configured': 'Konfigurálva',
+ 'settings.saveKeys': 'Kulcsok mentése',
+ 'settings.display': 'Megjelenítés',
+ 'settings.colorMode': 'Színmód',
+ 'settings.light': 'Világos',
+ 'settings.dark': 'Sötét',
+ 'settings.auto': 'Automatikus',
+ 'settings.language': 'Nyelv',
+ 'settings.temperature': 'Hőmérséklet egység',
+ 'settings.timeFormat': 'Időformátum',
+ 'settings.blurBookingCodes': 'Foglalási kódok elrejtése',
+ 'settings.optimizeFromAccommodation': 'Útvonal optimalizálása a szállástól',
+ 'settings.optimizeFromAccommodationHint':
+ 'A nap optimalizálásakor az útvonal annál a szállásnál kezdődjön, ahol felébredsz, és annál érjen véget, ahova este bejelentkezel.',
+ 'settings.notifications': 'Értesítések',
+ 'settings.notifyTripInvite': 'Utazási meghívók',
+ 'settings.notifyBookingChange': 'Foglalási változások',
+ 'settings.notifyTripReminder': 'Utazási emlékeztetők',
+ 'settings.notifyTodoDue': 'Teendő esedékes',
+ 'settings.notifyVacayInvite': 'Vacay összevonási meghívók',
+ 'settings.notifyPhotosShared': 'Megosztott fotók (Immich)',
+ 'settings.notifyCollabMessage': 'Csevegés üzenetek (Collab)',
+ 'settings.notifyPackingTagged': 'Csomagolási lista: hozzárendelések',
+ 'settings.notifyWebhook': 'Webhook értesítések',
+ 'settings.notificationsDisabled':
+ 'Az értesítések nincsenek beállítva. Kérje meg a rendszergazdát, hogy engedélyezze az e-mail vagy webhook értesítéseket.',
+ 'settings.notificationsActive': 'Aktív csatorna',
+ 'settings.notificationsManagedByAdmin':
+ 'Az értesítési eseményeket az adminisztrátor konfigurálja.',
+ 'settings.on': 'Be',
+ 'settings.off': 'Ki',
+ 'settings.mcp.title': 'MCP konfiguráció',
+ 'settings.mcp.endpoint': 'MCP végpont',
+ 'settings.mcp.clientConfig': 'Kliens konfiguráció',
+ 'settings.mcp.clientConfigHint':
+ 'Cserélje ki a részt egy API tokenre az alábbi listából. Az npx elérési útját szükség lehet módosítani a rendszeréhez (pl. C:\\PROGRA~1\\nodejs\\npx.cmd Windows-on).',
+ 'settings.mcp.clientConfigHintOAuth':
+ 'Cserélje ki a és részeket a fent létrehozott OAuth 2.1 kliens adataival. Az mcp-remote megnyitja a böngészőt az első csatlakozáskor az engedélyezés elvégzéséhez. Az npx elérési útját szükség lehet módosítani a rendszeréhez (pl. C:\\PROGRA~1\\nodejs\\npx.cmd Windows-on).',
+ 'settings.mcp.copy': 'Másolás',
+ 'settings.mcp.copied': 'Másolva!',
+ 'settings.mcp.apiTokens': 'API tokenek',
+ 'settings.mcp.createToken': 'Új token létrehozása',
+ 'settings.mcp.noTokens':
+ 'Még nincsenek tokenek. Hozzon létre egyet MCP kliensek csatlakoztatásához.',
+ 'settings.mcp.tokenCreatedAt': 'Létrehozva',
+ 'settings.mcp.tokenUsedAt': 'Használva',
+ 'settings.mcp.deleteTokenTitle': 'Token törlése',
+ 'settings.mcp.deleteTokenMessage':
+ 'Ez a token azonnal érvénytelenné válik. Minden MCP kliens, amely használja, elveszíti a hozzáférést.',
+ 'settings.mcp.modal.createTitle': 'API token létrehozása',
+ 'settings.mcp.modal.tokenName': 'Token neve',
+ 'settings.mcp.modal.tokenNamePlaceholder':
+ 'pl. Claude Desktop, Munkahelyi laptop',
+ 'settings.mcp.modal.creating': 'Létrehozás…',
+ 'settings.mcp.modal.create': 'Token létrehozása',
+ 'settings.mcp.modal.createdTitle': 'Token létrehozva',
+ 'settings.mcp.modal.createdWarning':
+ 'Ez a token csak egyszer jelenik meg. Másolja és mentse el most — nem lehet visszaállítani.',
+ 'settings.mcp.modal.done': 'Kész',
+ 'settings.mcp.toast.created': 'Token létrehozva',
+ 'settings.mcp.toast.createError': 'Nem sikerült létrehozni a tokent',
+ 'settings.mcp.toast.deleted': 'Token törölve',
+ 'settings.mcp.toast.deleteError': 'Nem sikerült törölni a tokent',
+ 'settings.mcp.apiTokensDeprecated':
+ 'Az API tokenek elavultak és egy jövőbeli verzióban eltávolításra kerülnek. Kérjük, használjon helyettük OAuth 2.1 klienseket.',
+ 'settings.oauth.clients': 'OAuth 2.1 kliensek',
+ 'settings.oauth.clientsHint':
+ 'Regisztráljon OAuth 2.1 klienseket, hogy a harmadik féltől származó MCP alkalmazások (Claude Web, Cursor stb.) statikus tokenek nélkül csatlakozhassanak.',
+ 'settings.oauth.createClient': 'Új kliens',
+ 'settings.oauth.noClients': 'Nincs regisztrált OAuth kliens.',
+ 'settings.oauth.clientId': 'Kliens azonosító',
+ 'settings.oauth.clientSecret': 'Kliens titok',
+ 'settings.oauth.deleteClient': 'Kliens törlése',
+ 'settings.oauth.deleteClientMessage':
+ 'Ez a kliens és az összes aktív munkamenet véglegesen törlésre kerül. Minden alkalmazás, amely ezt használja, azonnal elveszíti a hozzáférést.',
+ 'settings.oauth.rotateSecret': 'Titok megújítása',
+ 'settings.oauth.rotateSecretMessage':
+ 'Új kliens titok kerül generálásra és az összes meglévő munkamenet azonnal érvénytelenné válik. Frissítse alkalmazását a párbeszéd bezárása előtt.',
+ 'settings.oauth.rotateSecretConfirm': 'Megújítás',
+ 'settings.oauth.rotateSecretConfirming': 'Megújítás…',
+ 'settings.oauth.rotateSecretDoneTitle': 'Új titok generálva',
+ 'settings.oauth.rotateSecretDoneWarning':
+ 'Ez a titok csak egyszer jelenik meg. Másolja most és frissítse alkalmazását — az összes korábbi munkamenet érvénytelenné vált.',
+ 'settings.oauth.activeSessions': 'Aktív OAuth munkamenetek',
+ 'settings.oauth.sessionScopes': 'Jogosultságok',
+ 'settings.oauth.sessionExpires': 'Lejár',
+ 'settings.oauth.revoke': 'Visszavonás',
+ 'settings.oauth.revokeSession': 'Munkamenet visszavonása',
+ 'settings.oauth.revokeSessionMessage':
+ 'Ez azonnal visszavonja a hozzáférést ehhez az OAuth munkamenethez.',
+ 'settings.oauth.modal.createTitle': 'OAuth kliens regisztrálása',
+ 'settings.oauth.modal.presets': 'Gyors beállítások',
+ 'settings.oauth.modal.clientName': 'Alkalmazás neve',
+ 'settings.oauth.modal.clientNamePlaceholder':
+ 'pl. Claude Web, Az én MCP appom',
+ 'settings.oauth.modal.redirectUris': 'Átirányítási URI-k',
+ 'settings.oauth.modal.redirectUrisPlaceholder':
+ 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint':
+ 'Soronként egy URI. HTTPS szükséges (localhost kivételével). Pontos egyezés szükséges.',
+ 'settings.oauth.modal.scopes': 'Engedélyezett jogosultságok',
+ 'settings.oauth.modal.scopesHint':
+ 'A list_trips és get_trip_summary mindig elérhető — jogosultság nélkül. Segítenek az AI-nak megtalálni az utazás azonosítókat.',
+ 'settings.oauth.modal.selectAll': 'Összes kijelölése',
+ 'settings.oauth.modal.deselectAll': 'Összes kijelölés törlése',
+ 'settings.oauth.modal.creating': 'Regisztrálás…',
+ 'settings.oauth.modal.create': 'Kliens regisztrálása',
+ 'settings.oauth.modal.createdTitle': 'Kliens regisztrálva',
+ 'settings.oauth.modal.createdWarning':
+ 'A kliens titok csak egyszer jelenik meg. Másolja most — nem állítható helyre.',
+ 'settings.oauth.toast.createError':
+ 'Az OAuth kliens regisztrálása sikertelen',
+ 'settings.oauth.toast.deleted': 'OAuth kliens törölve',
+ 'settings.oauth.toast.deleteError': 'Az OAuth kliens törlése sikertelen',
+ 'settings.oauth.toast.revoked': 'Munkamenet visszavonva',
+ 'settings.oauth.toast.revokeError': 'A munkamenet visszavonása sikertelen',
+ 'settings.oauth.toast.rotateError': 'A kliens titok megújítása sikertelen',
+ 'settings.oauth.modal.machineClient':
+ 'Gépi kliens (böngészős bejelentkezés nélkül)',
+ 'settings.oauth.modal.machineClientHint':
+ 'client_credentials grant használata — nincs szükség átirányítási URI-kra. A token közvetlenül client_id + client_secret segítségével kerül kiállításra, és a kiválasztott hatókörökön belül az Ön nevében jár el.',
+ 'settings.oauth.modal.machineClientUsage':
+ 'Token lekérése: POST /oauth/token a grant_type=client_credentials, client_id és client_secret értékekkel. Böngésző és frissítési token nélkül.',
+ 'settings.oauth.badge.machine': 'gépi',
+ 'settings.account': 'Fiók',
+ 'settings.about': 'Névjegy',
+ 'settings.about.reportBug': 'Hiba bejelentése',
+ 'settings.about.reportBugHint': 'Problémát találtál? Jelezd nekünk',
+ 'settings.about.featureRequest': 'Funkció javaslat',
+ 'settings.about.featureRequestHint': 'Javasolj egy új funkciót',
+ 'settings.about.wikiHint': 'Dokumentáció és útmutatók',
+ 'settings.about.supporters.badge': 'Havi támogatók',
+ 'settings.about.supporters.title': 'Útitársak a TREK mellett',
+ 'settings.about.supporters.subtitle':
+ 'Miközben te a következő útvonaladat tervezed, ők a TREK jövőjét tervezik velem együtt. Havi hozzájárulásuk közvetlenül fejlesztésre és valódi órákra fordítódik — hogy a TREK Open Source maradhasson.',
+ 'settings.about.supporters.since': 'támogató {date} óta',
+ 'settings.about.supporters.tierEmpty': 'Légy az első',
+ 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket',
+ 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP',
+ 'settings.about.supporter.tier.businessClassDreamer':
+ 'Business Class Dreamer',
+ 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller',
+ 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate',
+ 'settings.about.description':
+ 'A TREK egy saját szerveren üzemeltetett útitervező, amely segít az utazásaid megszervezésében az első ötlettől az utolsó emlékig. Napi tervezés, költségvetés, csomagolási listák, fotók és még sok más — minden egy helyen, a saját szervereden.',
+ 'settings.about.madeWith': 'Készítve',
+ 'settings.about.madeBy':
+ 'Maurice és egy növekvő nyílt forráskódú közösség által.',
+ 'settings.username': 'Felhasználónév',
+ 'settings.email': 'E-mail',
+ 'settings.role': 'Szerepkör',
+ 'settings.roleAdmin': 'Adminisztrátor',
+ 'settings.oidcLinked': 'Összekapcsolva:',
+ 'settings.changePassword': 'Jelszó módosítása',
+ 'settings.currentPassword': 'Jelenlegi jelszó',
+ 'settings.newPassword': 'Új jelszó',
+ 'settings.confirmPassword': 'Új jelszó megerősítése',
+ 'settings.updatePassword': 'Jelszó frissítése',
+ 'settings.passwordRequired': 'Kérjük, add meg a jelenlegi és az új jelszót',
+ 'settings.currentPasswordRequired': 'A jelenlegi jelszó megadása kötelező',
+ 'settings.passwordTooShort':
+ 'A jelszónak legalább 8 karakter hosszúnak kell lennie',
+ 'settings.passwordWeak':
+ 'A jelszónak tartalmaznia kell nagybetűt, kisbetűt, számot és speciális karaktert',
+ 'settings.passwordMismatch': 'A jelszavak nem egyeznek',
+ 'settings.passwordChanged': 'Jelszó sikeresen módosítva',
+ 'settings.deleteAccount': 'Törlés',
+ 'settings.deleteAccountTitle': 'Biztosan törölni szeretnéd a fiókodat?',
+ 'settings.deleteAccountWarning':
+ 'A fiókod és minden utazásod, helyed és fájlod véglegesen törlődik. Ez a művelet nem vonható vissza.',
+ 'settings.deleteAccountConfirm': 'Végleges törlés',
+ 'settings.deleteBlockedTitle': 'Törlés nem lehetséges',
+ 'settings.deleteBlockedMessage':
+ 'Te vagy az egyetlen adminisztrátor. Nevezz ki egy másik felhasználót adminnak, mielőtt törölnéd a fiókodat.',
+ 'settings.roleUser': 'Felhasználó',
+ 'settings.saveProfile': 'Mentés',
+ 'settings.toast.mapSaved': 'Térképbeállítások mentve',
+ 'settings.toast.keysSaved': 'API kulcsok mentve',
+ 'settings.toast.displaySaved': 'Megjelenítési beállítások mentve',
+ 'settings.toast.profileSaved': 'Profil frissítve',
+ 'settings.uploadAvatar': 'Profilkép feltöltése',
+ 'settings.removeAvatar': 'Profilkép eltávolítása',
+ 'settings.avatarUploaded': 'Profilkép frissítve',
+ 'settings.avatarRemoved': 'Profilkép eltávolítva',
+ 'settings.avatarError': 'Feltöltés sikertelen',
+ 'settings.mfa.title': 'Kétfaktoros hitelesítés (2FA)',
+ 'settings.mfa.description':
+ 'Egy második lépést ad a bejelentkezéshez e-mail és jelszó használatakor. Használj hitelesítő alkalmazást (Google Authenticator, Authy stb.).',
+ 'settings.mfa.requiredByPolicy':
+ 'A rendszergazda kétlépcsős hitelesítést ír elő. Állíts be hitelesítő alkalmazást lent, mielőtt továbblépnél.',
+ 'settings.mfa.backupTitle': 'Tartalék kódok',
+ 'settings.mfa.backupDescription':
+ 'Használd ezeket az egyszer használatos kódokat, ha elveszíted a hozzáférést a hitelesítő alkalmazásodhoz.',
+ 'settings.mfa.backupWarning':
+ 'Mentsd el ezeket most. Minden kód csak egyszer használható.',
+ 'settings.mfa.backupCopy': 'Kódok másolása',
+ 'settings.mfa.backupDownload': 'TXT letöltése',
+ 'settings.mfa.backupPrint': 'Nyomtatás / PDF',
+ 'settings.mfa.backupCopied': 'Tartalék kódok másolva',
+ 'settings.mfa.enabled': '2FA engedélyezve van a fiókodban.',
+ 'settings.mfa.disabled': '2FA nincs engedélyezve.',
+ 'settings.mfa.setup': 'Hitelesítő beállítása',
+ 'settings.mfa.scanQr':
+ 'Olvasd be ezt a QR-kódot az alkalmazásoddal, vagy add meg manuálisan a titkos kulcsot.',
+ 'settings.mfa.secretLabel': 'Titkos kulcs (kézi megadás)',
+ 'settings.mfa.codePlaceholder': '6 jegyű kód',
+ 'settings.mfa.enable': '2FA engedélyezése',
+ 'settings.mfa.cancelSetup': 'Mégse',
+ 'settings.mfa.disableTitle': '2FA kikapcsolása',
+ 'settings.mfa.disableHint':
+ 'Add meg a fiókod jelszavát és a hitelesítő alkalmazás aktuális kódját.',
+ 'settings.mfa.disable': '2FA kikapcsolása',
+ 'settings.mfa.toastEnabled': 'Kétfaktoros hitelesítés engedélyezve',
+ 'settings.mfa.toastDisabled': 'Kétfaktoros hitelesítés kikapcsolva',
+ 'settings.mfa.demoBlocked': 'Demo módban nem érhető el',
+ 'settings.mustChangePassword':
+ 'A folytatás előtt meg kell változtatnod a jelszavad. Kérjük, adj meg egy új jelszót alább.',
+ 'settings.bookingLabels': 'Útvonal-címkék a foglalásokhoz',
+ 'settings.bookingLabelsHint':
+ 'Állomás- / repülőtér-nevek megjelenítése a térképen. Ha ki van kapcsolva, csak az ikon látszik.',
+ 'settings.notifyVersionAvailable': 'Új verzió elérhető',
+ 'settings.notificationPreferences.noChannels':
+ 'Nincsenek értesítési csatornák beállítva. Kérd meg a rendszergazdát, hogy állítson be e-mail vagy webhook értesítéseket.',
+ 'settings.webhookUrl.label': 'Webhook URL',
+ 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...',
+ 'settings.webhookUrl.hint':
+ 'Adja meg a Discord, Slack vagy egyéni webhook URL-jét az értesítések fogadásához.',
+ 'settings.webhookUrl.saved': 'Webhook URL mentve',
+ 'settings.webhookUrl.test': 'Teszt',
+ 'settings.webhookUrl.testSuccess': 'Teszt webhook sikeresen elküldve',
+ 'settings.webhookUrl.testFailed': 'Teszt webhook sikertelen',
+ 'settings.ntfyUrl.topicLabel': 'Ntfy téma',
+ 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts',
+ 'settings.ntfyUrl.serverLabel': 'Ntfy szerver URL (opcionális)',
+ 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh',
+ 'settings.ntfyUrl.hint':
+ 'Add meg az Ntfy témádat push értesítések fogadásához. Hagyd üresen a szervert a rendszergazda által beállított alapértelmezett használatához.',
+ 'settings.ntfyUrl.tokenLabel': 'Hozzáférési token (opcionális)',
+ 'settings.ntfyUrl.tokenHint': 'Jelszóval védett témákhoz szükséges.',
+ 'settings.ntfyUrl.saved': 'Ntfy beállítások mentve',
+ 'settings.ntfyUrl.test': 'Teszt',
+ 'settings.ntfyUrl.testSuccess': 'Teszt Ntfy értesítés sikeresen elküldve',
+ 'settings.ntfyUrl.testFailed': 'Teszt Ntfy értesítés sikertelen',
+ 'settings.ntfyUrl.tokenCleared': 'Hozzáférési token törölve',
+ 'settings.notificationPreferences.inapp': 'In-App',
+ 'settings.notificationPreferences.webhook': 'Webhook',
+ 'settings.notificationPreferences.email': 'Email',
+ 'settings.notificationPreferences.ntfy': 'Ntfy',
+ "settings.currency": "Currency",
+ "settings.currencyHint": "All amounts in Costs are converted to and shown in this currency.",
+ 'settings.passkey.title': 'Passkey-k',
+ 'settings.passkey.description':
+ 'Jelentkezz be gyorsabban és adathalászat-állóan egy passkey-jel — ujjlenyomattal, arccal, PIN-kóddal vagy hardveres kulccsal. A jelszavad tartalékként megmarad.',
+ 'settings.passkey.notConfigured':
+ 'A passkey-k engedélyezve vannak, de ezen a szerveren még nincsenek teljesen beállítva. Kérd meg a rendszergazdát, hogy állítsa be a WebAuthn-domaint.',
+ 'settings.passkey.add': 'Passkey hozzáadása',
+ 'settings.passkey.addTitle': 'Passkey hozzáadása',
+ 'settings.passkey.passwordPrompt':
+ 'Erősítsd meg a jelenlegi jelszavad, majd kövesd az eszközöd útmutatását.',
+ 'settings.passkey.passwordRequired': 'A jelenlegi jelszó megadása kötelező.',
+ 'settings.passkey.namePlaceholder': 'Név (opcionális, pl. "iPhone")',
+ 'settings.passkey.addedToast': 'Passkey hozzáadva',
+ 'settings.passkey.added': 'Hozzáadva',
+ 'settings.passkey.addError': 'Nem sikerült hozzáadni a passkey-t',
+ 'settings.passkey.cancelled': 'Passkey beállítása megszakítva',
+ 'settings.passkey.deleted': 'Passkey eltávolítva',
+ 'settings.passkey.deleteConfirm':
+ 'Eltávolítod ezt a passkey-t? Erősítsd meg a jelszavaddal.',
+ 'settings.passkey.rename': 'Átnevezés',
+ 'settings.passkey.defaultName': 'Passkey',
+ 'settings.passkey.synced': 'Szinkronizálva',
+ 'settings.passkey.deviceBound': 'Ez az eszköz',
+ 'settings.passkey.lastUsed': 'Utoljára használva',
+ 'settings.passkey.neverUsed': 'Még nem használt',
+ 'settings.mapPoiPill': 'Helyek felfedezése a térképen',
+ 'settings.mapPoiPillHint': 'Megjelenít egy kategóriasávot az utazási térképen, hogy az OpenStreetMap segítségével közeli éttermeket, szállásokat és továbbiakat találj.',
+ 'settings.airtrail.title': 'AirTrail',
+ 'settings.airtrail.hint': 'Csatlakoztasd a saját üzemeltetésű AirTrail-példányodat járatok importálásához és szinkronizálásához. Hozz létre egy API-kulcsot az AirTrailben a Beállítások → Biztonság menüpontban.',
+ 'settings.airtrail.url': 'Példány URL-címe',
+ 'settings.airtrail.apiKey': 'API-kulcs',
+ 'settings.airtrail.apiKeyPlaceholder': 'Bearer API-kulcs',
+ 'settings.airtrail.apiKeyHint': 'Az AirTrailben a Beállítások → Biztonság menüpontban generálva. Titkosítva tárolva.',
+ 'settings.airtrail.allowInsecureTls': 'Önaláírt tanúsítványok engedélyezése',
+ 'settings.airtrail.allowInsecureTlsHint': 'Csak megbízható, saját hálózaton futó példány esetén engedélyezd.',
+ 'settings.airtrail.connected': 'Csatlakoztatva',
+ 'settings.airtrail.notConnected': 'Nincs csatlakoztatva',
+ 'settings.airtrail.toast.saved': 'AirTrail-kapcsolat mentve',
+ 'settings.airtrail.toast.saveError': 'Nem sikerült menteni a kapcsolatot',
+ 'settings.airtrail.test.button': 'Kapcsolat tesztelése',
+ 'settings.airtrail.test.success': 'Csatlakoztatva — {count} járat található',
+ 'settings.airtrail.test.failed': 'A kapcsolat sikertelen',
+};
+
+export default settings;
diff --git a/shared/src/i18n/hu/share.ts b/shared/src/i18n/hu/share.ts
new file mode 100644
index 0000000000000000000000000000000000000000..52dbea159083c1af0cb678b57a05fa67345a23cf
--- /dev/null
+++ b/shared/src/i18n/hu/share.ts
@@ -0,0 +1,16 @@
+import type { TranslationStrings } from '../types';
+
+const share: TranslationStrings = {
+ 'share.linkTitle': 'Nyilvános link',
+ 'share.linkHint':
+ 'Hozz létre egy linket, amellyel bárki megtekintheti ezt az utazást bejelentkezés nélkül. Csak olvasható — szerkesztés nem lehetséges.',
+ 'share.createLink': 'Link létrehozása',
+ 'share.deleteLink': 'Link törlése',
+ 'share.createError': 'Nem sikerült létrehozni a linket',
+ 'share.permMap': 'Térkép és terv',
+ 'share.permBookings': 'Foglalások',
+ 'share.permPacking': 'Csomagolás',
+ 'share.permBudget': 'Költségvetés',
+ 'share.permCollab': 'Csevegés',
+};
+export default share;
diff --git a/shared/src/i18n/hu/shared.ts b/shared/src/i18n/hu/shared.ts
new file mode 100644
index 0000000000000000000000000000000000000000..338c1efe12c61f46b6057561009c99e50244ae33
--- /dev/null
+++ b/shared/src/i18n/hu/shared.ts
@@ -0,0 +1,21 @@
+import type { TranslationStrings } from '../types';
+
+const shared: TranslationStrings = {
+ 'shared.expired': 'Link lejárt vagy érvénytelen',
+ 'shared.expiredHint': 'Ez a megosztott utazási link már nem aktív.',
+ 'shared.readOnly': 'Csak olvasható megosztott nézet',
+ 'shared.tabPlan': 'Terv',
+ 'shared.tabBookings': 'Foglalások',
+ 'shared.tabPacking': 'Csomagolás',
+ 'shared.tabBudget': 'Költségvetés',
+ 'shared.tabChat': 'Csevegés',
+ 'shared.days': 'nap',
+ 'shared.places': 'hely',
+ 'shared.other': 'Egyéb',
+ 'shared.totalBudget': 'Teljes költségvetés',
+ 'shared.messages': 'üzenet',
+ 'shared.sharedVia': 'Megosztva:',
+ 'shared.confirmed': 'Megerősítve',
+ 'shared.pending': 'Függőben',
+};
+export default shared;
diff --git a/shared/src/i18n/hu/stats.ts b/shared/src/i18n/hu/stats.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c65e834f8cc766f397ae848068c159346e746b38
--- /dev/null
+++ b/shared/src/i18n/hu/stats.ts
@@ -0,0 +1,13 @@
+import type { TranslationStrings } from '../types';
+
+const stats: TranslationStrings = {
+ 'stats.countries': 'Országok',
+ 'stats.cities': 'Városok',
+ 'stats.trips': 'Utazások',
+ 'stats.places': 'Helyek',
+ 'stats.worldProgress': 'Világ felfedezése',
+ 'stats.visited': 'meglátogatott',
+ 'stats.remaining': 'hátralévő',
+ 'stats.visitedCountries': 'Meglátogatott országok',
+};
+export default stats;
diff --git a/shared/src/i18n/hu/system_notice.ts b/shared/src/i18n/hu/system_notice.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d2a08f06e661b4ff84f32a97dec9db236c4c17aa
--- /dev/null
+++ b/shared/src/i18n/hu/system_notice.ts
@@ -0,0 +1,58 @@
+import type { TranslationStrings } from '../types';
+
+const system_notice: TranslationStrings = {
+ 'system_notice.welcome_v1.title': 'Üdvözöl a TREK',
+ 'system_notice.welcome_v1.body':
+ 'Az összes az egyben utazástervező. Készítsen útvonalakat, ossza meg az utakat barátaival, és maradjon szervezett — online és offline.',
+ 'system_notice.welcome_v1.cta_label': 'Utazás tervezése',
+ 'system_notice.welcome_v1.hero_alt': 'Festői úticél TREK tervező felülettel',
+ 'system_notice.welcome_v1.highlight_plan': 'Napi útvonalak minden utazáshoz',
+ 'system_notice.welcome_v1.highlight_share': 'Együttműködés utazótársakkal',
+ 'system_notice.welcome_v1.highlight_offline': 'Mobilon offline is működik',
+ 'system_notice.dev_test_modal.title': '[Dev] Test notice',
+ 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.',
+ 'system_notice.pager.prev': 'Előző értesítés',
+ 'system_notice.pager.next': 'Következő értesítés',
+ 'system_notice.pager.counter': '{current} / {total}',
+ 'system_notice.pager.goto': '{n}. értesítésre ugrás',
+ 'system_notice.pager.position': '{current}/{total}. értesítés',
+ 'system_notice.v3_photos.title': 'A fotók helye megváltozott 3.0-ban',
+ 'system_notice.v3_photos.body':
+ 'Az útiterv-tervező **Fényképek** lapja eltávolításra került. Fényképeid biztonságban vannak — TREK soha nem módosította Immich vagy Synology könyvtáradat.\n\nA fényképek mostantól a **Journey** bővítményben élnek. A Journey opcionális — ha még nem elérhető, kérd meg a rendszergazdát, hogy engedélyezze Admin → Bővítmények alatt.',
+ 'system_notice.v3_journey.title': 'Ismerje meg a Journey-t — útinnapló',
+ 'system_notice.v3_journey.body':
+ 'Dokumentáld utazazsaid gazdag történetekként idővonalakkal, fotgáriákkal és interaktív térképekkel.',
+ 'system_notice.v3_journey.cta_label': 'Journey megnyitása',
+ 'system_notice.v3_journey.highlight_timeline': 'Napi idővonal és galéria',
+ 'system_notice.v3_journey.highlight_photos':
+ 'Import Immich-ből vagy Synology-ból',
+ 'system_notice.v3_journey.highlight_share':
+ 'Nyilvános megosztás — bejelentkezés nélkül',
+ 'system_notice.v3_journey.highlight_export': 'Exportálás PDF fotkönyvként',
+ 'system_notice.v3_features.title': 'További újdonságok a 3.0-ban',
+ 'system_notice.v3_features.body':
+ 'Néhány további dolog, amit érdemes tudni erről a kiadásról.',
+ 'system_notice.v3_features.highlight_dashboard':
+ 'Mobile-first irmütébla újratervezve',
+ 'system_notice.v3_features.highlight_offline': 'Teljes offline mód PWA-ként',
+ 'system_notice.v3_features.highlight_search':
+ 'Valós idejű helykeresés-kiegészítés',
+ 'system_notice.v3_features.highlight_import':
+ 'Helyek importálása KMZ/KML fájlokból',
+ 'system_notice.v3_mcp.title': 'MCP: OAuth 2.1 frissítés',
+ 'system_notice.v3_mcp.body':
+ 'Az MCP integráció teljesen megújult. Az OAuth 2.1 mostantól az ajánlott hitelesítési módszer. A statikus tokenek (trek_…) elavultak és egy jövőbeli kiadásban eltávolításra kerülnek.',
+ 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 ajánlott (mcp-remote)',
+ 'system_notice.v3_mcp.highlight_scopes': '24 részletes engedélyezési hatókör',
+ 'system_notice.v3_mcp.highlight_deprecated':
+ 'Statikus trek_ tokenek elavultak',
+ 'system_notice.v3_mcp.highlight_tools': 'Bővített eszközkészlet és promptok',
+ 'system_notice.v3_thankyou.title': 'Egy személyes gondolat tőlem',
+ 'system_notice.v3_thankyou.body':
+ 'Mielőtt továbbmennél — szeretnék egy pillanatra megállni.\n\nA TREK egy hobbiprojektként indult, amit a saját utazásaimhoz építettem. Sosem gondoltam volna, hogy valami olyanná nő, amire 4000-en bízzátok a kalandjaitok tervezését. Minden csillagot, minden issue-t, minden funkciókérést — mindet elolvasom, és ezek tartanak életben a késő éjszakákon a teljes állás és az egyetem között.\n\nSzeretnétek, ha tudnátok: a TREK mindig nyílt forráskódú marad, mindig self-hosted, mindig a tiétek. Nincs nyomkövetés, nincs előfizetés, nincsenek rejtett feltételek. Csak egy eszköz, amit valaki épített, aki ugyanúgy szereti az utazást, mint ti.\n\nKülönleges köszönet [jubnl](https://github.com/jubnl)-nek — hihetetlen társsá váltál. A 3.0 nagyszerűségének nagy része a te kézjegyedet viseli. Köszönöm, hogy hittél ebben a projektben, amikor még nyers volt.\n\nÉs mindannyiótoknak, akik hibát jelentettetek, szöveget fordítottatok, megosztottátok a TREK-et egy baráttal, vagy egyszerűen csak egy utazást terveztetek vele — **köszönöm**. Ti vagytok az ok, amiért ez létezik.\n\nSok további közös kalandért.\n\n— Maurice\n\n---\n\n[Csatlakozz a közösséghez a Discordon](https://discord.gg/7Q6M6jDwzf)\n\nHa a TREK jobbá teszi az utazásaidat, egy [kis kávé](https://ko-fi.com/mauriceboe) mindig segít, hogy égve maradjanak a fények.',
+ 'system_notice.v3014_whitespace_collision.title':
+ 'Szükséges beavatkozás: felhasználói fiókütközés',
+ 'system_notice.v3014_whitespace_collision.body':
+ 'A 3.0.14-es frissítés egy vagy több felhasználónév- vagy e-mail-ütközést észlelt, amelyeket a tárolt értékek elején vagy végén lévő szóközök okoztak. Az érintett fiókok automatikusan át lettek nevezve. Ellenőrizze a szervernaplókat a **[migration] WHITESPACE COLLISION** kezdetű soroknál a felülvizsgálatot igénylő fiókok azonosításához.',
+};
+export default system_notice;
diff --git a/shared/src/i18n/hu/todo.ts b/shared/src/i18n/hu/todo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f1a364548c23c3895bbf9558f16cdc8c682a67f9
--- /dev/null
+++ b/shared/src/i18n/hu/todo.ts
@@ -0,0 +1,40 @@
+import type { TranslationStrings } from '../types';
+
+const todo: TranslationStrings = {
+ 'todo.subtab.packing': 'Csomagolási lista',
+ 'todo.subtab.todo': 'Teendők',
+ 'todo.completed': 'kész',
+ 'todo.filter.all': 'Mind',
+ 'todo.filter.open': 'Nyitott',
+ 'todo.filter.done': 'Kész',
+ 'todo.uncategorized': 'Kategória nélküli',
+ 'todo.namePlaceholder': 'Feladat neve',
+ 'todo.descriptionPlaceholder': 'Leírás (opcionális)',
+ 'todo.unassigned': 'Nem hozzárendelt',
+ 'todo.noCategory': 'Nincs kategória',
+ 'todo.hasDescription': 'Van leírás',
+ 'todo.addItem': 'Új feladat',
+ 'todo.sidebar.sortBy': 'Rendezés',
+ 'todo.priority': 'Prioritás',
+ 'todo.newCategoryLabel': 'új',
+ 'todo.newCategory': 'Kategória neve',
+ 'todo.addCategory': 'Kategória hozzáadása',
+ 'todo.newItem': 'Új feladat',
+ 'todo.empty': 'Még nincsenek feladatok. Adj hozzá egyet a kezdéshez!',
+ 'todo.filter.my': 'Saját feladataim',
+ 'todo.filter.overdue': 'Lejárt',
+ 'todo.sidebar.tasks': 'Feladatok',
+ 'todo.sidebar.categories': 'Kategóriák',
+ 'todo.detail.title': 'Feladat',
+ 'todo.detail.description': 'Leírás',
+ 'todo.detail.category': 'Kategória',
+ 'todo.detail.dueDate': 'Határidő',
+ 'todo.detail.assignedTo': 'Hozzárendelve',
+ 'todo.detail.delete': 'Törlés',
+ 'todo.detail.save': 'Módosítások mentése',
+ 'todo.detail.create': 'Feladat létrehozása',
+ 'todo.detail.priority': 'Prioritás',
+ 'todo.detail.noPriority': 'Nincs',
+ 'todo.sortByPrio': 'Prioritás',
+};
+export default todo;
diff --git a/shared/src/i18n/hu/transport.ts b/shared/src/i18n/hu/transport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..caa50cf5192fac8198b19fc2768637d263889da7
--- /dev/null
+++ b/shared/src/i18n/hu/transport.ts
@@ -0,0 +1,10 @@
+import type { TranslationStrings } from '../types';
+
+const transport: TranslationStrings = {
+ 'transport.addTransport': 'Közlekedés hozzáadása',
+ 'transport.modalTitle.create': 'Közlekedés hozzáadása',
+ 'transport.modalTitle.edit': 'Közlekedés szerkesztése',
+ 'transport.title': 'Közlekedés',
+ 'transport.addManual': 'Kézi közlekedés',
+};
+export default transport;
diff --git a/shared/src/i18n/hu/trip.ts b/shared/src/i18n/hu/trip.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d02355711997a541f4297d798e1969951b16d57c
--- /dev/null
+++ b/shared/src/i18n/hu/trip.ts
@@ -0,0 +1,31 @@
+import type { TranslationStrings } from '../types';
+
+const trip: TranslationStrings = {
+ 'trip.tabs.plan': 'Terv',
+ 'trip.tabs.transports': 'Közlekedés',
+ 'trip.tabs.reservations': 'Foglalások',
+ 'trip.tabs.reservationsShort': 'Foglalás',
+ 'trip.tabs.packing': 'Csomagolási lista',
+ 'trip.tabs.packingShort': 'Csomag',
+ 'trip.tabs.lists': 'Listák',
+ 'trip.tabs.listsShort': 'Listák',
+ 'trip.tabs.budget': "Costs",
+ 'trip.tabs.files': 'Fájlok',
+ 'trip.loading': 'Utazás betöltése...',
+ 'trip.mobilePlan': 'Tervezés',
+ 'trip.mobilePlaces': 'Helyek',
+ 'trip.toast.placeUpdated': 'Hely frissítve',
+ 'trip.toast.placeAdded': 'Hely hozzáadva',
+ 'trip.toast.placeDeleted': 'Hely törölve',
+ 'trip.toast.selectDay': 'Kérjük, először válassz egy napot',
+ 'trip.toast.assignedToDay': 'Hely hozzárendelve a naphoz',
+ 'trip.toast.reorderError': 'Nem sikerült átrendezni',
+ 'trip.toast.reservationUpdated': 'Foglalás frissítve',
+ 'trip.toast.reservationAdded': 'Foglalás hozzáadva',
+ 'trip.toast.deleted': 'Törölve',
+ 'trip.confirm.deletePlace': 'Biztosan törölni szeretnéd ezt a helyet?',
+ 'trip.confirm.deletePlaces': '{count} helyet töröl?',
+ 'trip.toast.placesDeleted': '{count} hely törölve',
+ 'trip.loadingPhotos': 'Helyek fotóinak betöltése...',
+};
+export default trip;
diff --git a/shared/src/i18n/hu/trips.ts b/shared/src/i18n/hu/trips.ts
new file mode 100644
index 0000000000000000000000000000000000000000..aafd406b2a79f81b25a7d9a2b33106ee0b50f063
--- /dev/null
+++ b/shared/src/i18n/hu/trips.ts
@@ -0,0 +1,17 @@
+import type { TranslationStrings } from '../types';
+
+const trips: TranslationStrings = {
+ 'trips.memberRemoved': '{username} eltávolítva',
+ 'trips.memberRemoveError': 'Eltávolítás sikertelen',
+ 'trips.memberAdded': '{username} hozzáadva',
+ 'trips.memberAddError': 'Hozzáadás sikertelen',
+ 'trips.reminder': 'Emlékeztető',
+ 'trips.reminderNone': 'Nincs',
+ 'trips.reminderDay': 'nap',
+ 'trips.reminderDays': 'nap',
+ 'trips.reminderCustom': 'Egyéni',
+ 'trips.reminderDaysBefore': 'nappal indulás előtt',
+ 'trips.reminderDisabledHint':
+ 'Az utazási emlékeztetők ki vannak kapcsolva. Kapcsold be az Admin > Beállítások > Értesítések menüben.',
+};
+export default trips;
diff --git a/shared/src/i18n/hu/undo.ts b/shared/src/i18n/hu/undo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..91fecdba549c1b491485c6529aa775a89ce3dffa
--- /dev/null
+++ b/shared/src/i18n/hu/undo.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const undo: TranslationStrings = {
+ '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.deletePlaces': 'Helyek 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.importKeyholeMarkup': 'KMZ/KML importálás',
+ 'undo.importGoogleList': 'Google Maps importálás',
+ 'undo.importNaverList': 'Naver Maps importálás',
+ 'undo.addPlace': 'Hely hozzáadva',
+ 'undo.done': 'Visszavonva: {action}',
+ 'undo.importBooking': 'Foglalási visszaigazolás importálása',
+};
+export default undo;
diff --git a/shared/src/i18n/hu/vacay.ts b/shared/src/i18n/hu/vacay.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c97a405cfa1b66b2379d014770ab550f3e627f26
--- /dev/null
+++ b/shared/src/i18n/hu/vacay.ts
@@ -0,0 +1,107 @@
+import type { TranslationStrings } from '../types';
+
+const vacay: TranslationStrings = {
+ 'vacay.subtitle': 'Szabadságnapok tervezése és kezelése',
+ 'vacay.settings': 'Beállítások',
+ 'vacay.year': 'Év',
+ 'vacay.addYear': 'Következő év hozzáadása',
+ 'vacay.addPrevYear': 'Előző év hozzáadása',
+ 'vacay.removeYear': 'Év eltávolítása',
+ 'vacay.removeYearConfirm': '{year} eltávolítása?',
+ 'vacay.removeYearHint':
+ 'Az adott év összes szabadság-bejegyzése és céges szabadnapja véglegesen törlődik.',
+ 'vacay.remove': 'Eltávolítás',
+ 'vacay.persons': 'Személyek',
+ 'vacay.noPersons': 'Nincsenek személyek hozzáadva',
+ 'vacay.addPerson': 'Személy hozzáadása',
+ 'vacay.editPerson': 'Személy szerkesztése',
+ 'vacay.removePerson': 'Személy eltávolítása',
+ 'vacay.removePersonConfirm': '{name} eltávolítása?',
+ 'vacay.removePersonHint':
+ 'A személy összes szabadság-bejegyzése véglegesen törlődik.',
+ 'vacay.personName': 'Név',
+ 'vacay.personNamePlaceholder': 'Név megadása',
+ 'vacay.color': 'Szín',
+ 'vacay.add': 'Hozzáadás',
+ 'vacay.legend': 'Jelmagyarázat',
+ 'vacay.publicHoliday': 'Ünnepnap',
+ 'vacay.companyHoliday': 'Céges szabadnap',
+ 'vacay.weekend': 'Hétvége',
+ 'vacay.modeVacation': 'Szabadság',
+ 'vacay.modeCompany': 'Céges szabadnap',
+ 'vacay.entitlement': 'Szabadságkeret',
+ 'vacay.entitlementDays': 'nap',
+ 'vacay.used': 'Felhasznált',
+ 'vacay.remaining': 'Maradt',
+ 'vacay.carriedOver': '{year}-ból/ből',
+ 'vacay.blockWeekends': 'Hétvégék zárolása',
+ 'vacay.blockWeekendsHint':
+ 'Szabadság-bejegyzések megakadályozása szombaton és vasárnap',
+ 'vacay.publicHolidays': 'Ünnepnapok',
+ 'vacay.publicHolidaysHint': 'Ünnepnapok megjelölése a naptárban',
+ 'vacay.selectCountry': 'Ország kiválasztása',
+ 'vacay.selectRegion': 'Régió kiválasztása (opcionális)',
+ 'vacay.addCalendar': 'Naptár hozzáadása',
+ 'vacay.calendarLabel': 'Címke (opcionális)',
+ 'vacay.calendarColor': 'Szín',
+ 'vacay.noCalendars': 'Még nincsenek ünnepnap-naptárak hozzáadva',
+ 'vacay.weekendDays': 'Hétvégi napok',
+ 'vacay.mon': 'Hé',
+ 'vacay.tue': 'Ke',
+ 'vacay.wed': 'Sze',
+ 'vacay.thu': 'Csü',
+ 'vacay.fri': 'Pé',
+ 'vacay.sat': 'Szo',
+ 'vacay.sun': 'Va',
+ 'vacay.companyHolidays': 'Céges szabadnapok',
+ 'vacay.companyHolidaysHint':
+ 'Céges szintű szabadnapok megjelölésének engedélyezése',
+ 'vacay.companyHolidaysNoDeduct':
+ 'A céges szabadnapok nem számítanak bele a szabadságkeretbe.',
+ 'vacay.weekStart': 'A hét kezdőnapja',
+ 'vacay.weekStartHint':
+ 'Válaszd ki, hogy a hét hétfőn vagy vasárnap kezdődjön',
+ 'vacay.carryOver': 'Szabadság átvitele',
+ 'vacay.carryOverHint':
+ 'Megmaradt szabadságnapok automatikus átvitele a következő évre',
+ 'vacay.sharing': 'Megosztás',
+ 'vacay.sharingHint': 'Szabadságterved megosztása más TREK felhasználókkal',
+ 'vacay.owner': 'Tulajdonos',
+ 'vacay.shareEmailPlaceholder': 'TREK felhasználó e-mail címe',
+ 'vacay.shareSuccess': 'Terv sikeresen megosztva',
+ 'vacay.shareError': 'Nem sikerült megosztani a tervet',
+ 'vacay.dissolve': 'Összevonás feloldása',
+ 'vacay.dissolveHint':
+ 'Naptárak újbóli szétválasztása. A bejegyzéseid megmaradnak.',
+ 'vacay.dissolveAction': 'Feloldás',
+ 'vacay.dissolved': 'Naptár szétválasztva',
+ 'vacay.fusedWith': 'Összevonva:',
+ 'vacay.you': 'te',
+ 'vacay.noData': 'Nincs adat',
+ 'vacay.changeColor': 'Szín módosítása',
+ 'vacay.inviteUser': 'Felhasználó meghívása',
+ 'vacay.inviteHint':
+ 'Hívj meg egy másik TREK felhasználót közös szabadságnaptár megosztásához.',
+ 'vacay.selectUser': 'Felhasználó kiválasztása',
+ 'vacay.sendInvite': 'Meghívó küldése',
+ 'vacay.inviteSent': 'Meghívó elküldve',
+ 'vacay.inviteError': 'Nem sikerült elküldeni a meghívót',
+ 'vacay.pending': 'függőben',
+ 'vacay.noUsersAvailable': 'Nincsenek elérhető felhasználók',
+ 'vacay.accept': 'Elfogadás',
+ 'vacay.decline': 'Elutasítás',
+ 'vacay.acceptFusion': 'Elfogadás és összevonás',
+ 'vacay.inviteTitle': 'Összevonási kérelem',
+ 'vacay.inviteWantsToFuse': 'szeretne megosztani veled egy szabadságnaptárat.',
+ 'vacay.fuseInfo1':
+ 'Mindketten látjátok az összes szabadság-bejegyzést egy közös naptárban.',
+ 'vacay.fuseInfo2':
+ 'Mindkét fél létrehozhat és szerkeszthet bejegyzéseket a másik számára.',
+ 'vacay.fuseInfo3':
+ 'Mindkét fél törölhet bejegyzéseket és módosíthatja a szabadságkeretet.',
+ 'vacay.fuseInfo4':
+ 'A beállítások, mint ünnepnapok és céges szabadnapok, közösen érvényesek.',
+ 'vacay.fuseInfo5':
+ 'Az összevonás bármikor feloldható bármelyik fél által. A bejegyzések megmaradnak.',
+};
+export default vacay;
diff --git a/shared/src/i18n/i18n-parity.spec.ts b/shared/src/i18n/i18n-parity.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..070fa82ec287190fa5b0c1051c94733a777475bd
--- /dev/null
+++ b/shared/src/i18n/i18n-parity.spec.ts
@@ -0,0 +1,33 @@
+// @ts-expect-error — plain .mjs script with no .d.ts; import as JS module.
+import { checkParity } from '../../scripts/i18n-parity.mjs';
+
+import { describe, it, expect } from 'vitest';
+
+/**
+ * Enforces the file-set contract for the i18n migration: every non-en locale
+ * dir must contain the exact same domain files as en/.
+ *
+ * Key-set drift is intentionally NOT enforced here — translation work happens
+ * gradually and gating CI on every newly-added EN key would block feature
+ * merges. The CLI script still prints the key-drift report so translators can
+ * see what they owe; only file-level drift is a structural bug.
+ */
+describe('i18n parity', () => {
+ it('every locale has the same domain files as en', () => {
+ const report = checkParity();
+ expect(report.fileDrift).toEqual([]);
+ });
+
+ it('reports key drift as data (not enforced, used by the CLI tool)', () => {
+ const report = checkParity();
+ // We do not assert here — translation drift is expected and acceptable.
+ // The shape check just confirms the report contract for tooling consumers.
+ expect(Array.isArray(report.keyDrift)).toBe(true);
+ for (const entry of report.keyDrift) {
+ expect(typeof entry.locale).toBe('string');
+ expect(typeof entry.file).toBe('string');
+ expect(Array.isArray(entry.missing)).toBe(true);
+ expect(Array.isArray(entry.extra)).toBe(true);
+ }
+ });
+});
diff --git a/shared/src/i18n/id/admin.ts b/shared/src/i18n/id/admin.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a1f2c477463564ede587944c94e8be6666f5bf41
--- /dev/null
+++ b/shared/src/i18n/id/admin.ts
@@ -0,0 +1,390 @@
+import type { TranslationStrings } from '../types';
+
+const admin: TranslationStrings = {
+ 'admin.notifications.title': 'Notifikasi',
+ 'admin.notifications.hint':
+ 'Pilih satu saluran notifikasi. Hanya satu yang bisa aktif sekaligus.',
+ 'admin.notifications.none': 'Dinonaktifkan',
+ 'admin.notifications.email': 'Email (SMTP)',
+ 'admin.notifications.webhook': 'Webhook',
+ 'admin.notifications.save': 'Simpan pengaturan notifikasi',
+ 'admin.notifications.saved': 'Pengaturan notifikasi tersimpan',
+ 'admin.notifications.testWebhook': 'Kirim test webhook',
+ 'admin.notifications.testWebhookSuccess': 'Test webhook berhasil dikirim',
+ 'admin.notifications.testWebhookFailed': 'Test webhook gagal',
+ 'admin.notifications.emailPanel.title': 'Email (SMTP)',
+ 'admin.notifications.webhookPanel.title': 'Webhook',
+ 'admin.notifications.inappPanel.title': 'In-App',
+ 'admin.notifications.inappPanel.hint':
+ 'Notifikasi in-app selalu aktif dan tidak bisa dinonaktifkan secara global.',
+ 'admin.notifications.adminWebhookPanel.title': 'Admin Webhook',
+ 'admin.notifications.adminWebhookPanel.hint':
+ 'Webhook ini digunakan khusus untuk notifikasi admin (mis. peringatan versi). Terpisah dari webhook per pengguna dan selalu berjalan jika diatur.',
+ 'admin.notifications.adminWebhookPanel.saved': 'Admin webhook URL tersimpan',
+ 'admin.notifications.adminWebhookPanel.testSuccess':
+ 'Test webhook berhasil dikirim',
+ 'admin.notifications.adminWebhookPanel.testFailed': 'Test webhook gagal',
+ 'admin.notifications.adminWebhookPanel.alwaysOnHint':
+ 'Admin webhook selalu berjalan jika URL dikonfigurasi',
+ 'admin.notifications.ntfy': 'Ntfy',
+ 'admin.ntfy.hint':
+ 'Memungkinkan pengguna mengonfigurasi topik ntfy mereka sendiri untuk notifikasi push. Tetapkan server default di bawah untuk mengisi setelan pengguna secara otomatis.',
+ 'admin.notifications.testNtfy': 'Kirim uji Ntfy',
+ 'admin.notifications.testNtfySuccess': 'Uji Ntfy berhasil dikirim',
+ 'admin.notifications.testNtfyFailed': 'Uji Ntfy gagal',
+ 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy',
+ 'admin.notifications.adminNtfyPanel.hint':
+ 'Topik Ntfy ini digunakan khusus untuk notifikasi admin (mis. peringatan versi). Terpisah dari topik per pengguna dan selalu berjalan jika dikonfigurasi.',
+ 'admin.notifications.adminNtfyPanel.serverLabel': 'URL Server Ntfy',
+ 'admin.notifications.adminNtfyPanel.serverHint':
+ 'Juga digunakan sebagai server default untuk notifikasi ntfy pengguna. Kosongkan untuk menggunakan ntfy.sh. Pengguna dapat menggantinya di pengaturan mereka sendiri.',
+ 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh',
+ 'admin.notifications.adminNtfyPanel.topicLabel': 'Topik Admin',
+ 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts',
+ 'admin.notifications.adminNtfyPanel.tokenLabel': 'Token Akses (opsional)',
+ 'admin.notifications.adminNtfyPanel.tokenCleared':
+ 'Token akses admin dihapus',
+ 'admin.notifications.adminNtfyPanel.saved': 'Pengaturan Ntfy admin tersimpan',
+ 'admin.notifications.adminNtfyPanel.test': 'Kirim uji Ntfy',
+ 'admin.notifications.adminNtfyPanel.testSuccess': 'Uji Ntfy berhasil dikirim',
+ 'admin.notifications.adminNtfyPanel.testFailed': 'Uji Ntfy gagal',
+ 'admin.notifications.adminNtfyPanel.alwaysOnHint':
+ 'Admin Ntfy selalu berjalan jika topik dikonfigurasi',
+ 'admin.notifications.adminNotificationsHint':
+ 'Atur saluran mana yang mengirimkan notifikasi khusus admin (mis. peringatan versi).',
+ 'admin.notifications.tripReminders.title': 'Pengingat Perjalanan',
+ 'admin.notifications.tripReminders.hint':
+ 'Mengirim notifikasi pengingat sebelum perjalanan dimulai (memerlukan hari pengingat yang diatur pada perjalanan).',
+ 'admin.notifications.tripReminders.enabled':
+ 'Pengingat perjalanan diaktifkan',
+ 'admin.notifications.tripReminders.disabled':
+ 'Pengingat perjalanan dinonaktifkan',
+ 'admin.smtp.title': 'Email & Notifikasi',
+ 'admin.smtp.hint': 'Konfigurasi SMTP untuk pengiriman notifikasi email.',
+ 'admin.smtp.testButton': 'Kirim email uji',
+ 'admin.webhook.hint':
+ 'Izinkan pengguna mengatur URL webhook sendiri untuk notifikasi (Discord, Slack, dll.).',
+ 'admin.smtp.testSuccess': 'Email uji berhasil dikirim',
+ 'admin.smtp.testFailed': 'Email uji gagal',
+ 'admin.title': 'Administrasi',
+ 'admin.subtitle': 'Manajemen pengguna dan pengaturan sistem',
+ 'admin.tabs.users': 'Pengguna',
+ 'admin.tabs.categories': 'Kategori',
+ 'admin.tabs.backup': 'Backup',
+ 'admin.tabs.notifications': 'Notifikasi',
+ 'admin.tabs.audit': 'Audit',
+ 'admin.stats.users': 'Pengguna',
+ 'admin.stats.trips': 'Perjalanan',
+ 'admin.stats.places': 'Tempat',
+ 'admin.stats.photos': 'Foto',
+ 'admin.stats.files': 'File',
+ 'admin.table.user': 'Pengguna',
+ 'admin.table.email': 'Email',
+ 'admin.table.role': 'Peran',
+ 'admin.table.created': 'Dibuat',
+ 'admin.table.lastLogin': 'Login Terakhir',
+ 'admin.table.actions': 'Tindakan',
+ 'admin.you': '(Kamu)',
+ 'admin.editUser': 'Edit Pengguna',
+ 'admin.newPassword': 'Kata Sandi Baru',
+ 'admin.newPasswordHint': 'Kosongkan untuk mempertahankan kata sandi saat ini',
+ 'admin.deleteUser':
+ 'Hapus pengguna "{name}"? Semua perjalanan akan dihapus secara permanen.',
+ 'admin.deleteUserTitle': 'Hapus pengguna',
+ 'admin.newPasswordPlaceholder': 'Masukkan kata sandi baru…',
+ 'admin.toast.loadError': 'Gagal memuat data admin',
+ 'admin.toast.userUpdated': 'Pengguna diperbarui',
+ 'admin.toast.updateError': 'Gagal memperbarui',
+ 'admin.toast.userDeleted': 'Pengguna dihapus',
+ 'admin.toast.deleteError': 'Gagal menghapus',
+ 'admin.toast.cannotDeleteSelf': 'Tidak bisa menghapus akun sendiri',
+ 'admin.toast.userCreated': 'Pengguna dibuat',
+ 'admin.toast.createError': 'Gagal membuat pengguna',
+ 'admin.toast.fieldsRequired':
+ 'Nama pengguna, email, dan kata sandi wajib diisi',
+ 'admin.createUser': 'Buat Pengguna',
+ 'admin.invite.title': 'Tautan Undangan',
+ 'admin.invite.subtitle': 'Buat tautan pendaftaran sekali pakai',
+ 'admin.invite.create': 'Buat Tautan',
+ 'admin.invite.createAndCopy': 'Buat & Salin',
+ 'admin.invite.empty': 'Belum ada tautan undangan yang dibuat',
+ 'admin.invite.maxUses': 'Maks. Penggunaan',
+ 'admin.invite.expiry': 'Kedaluwarsa setelah',
+ 'admin.invite.uses': 'digunakan',
+ 'admin.invite.expiresAt': 'kedaluwarsa',
+ 'admin.invite.createdBy': 'oleh',
+ 'admin.invite.active': 'Aktif',
+ 'admin.invite.expired': 'Kedaluwarsa',
+ 'admin.invite.usedUp': 'Habis dipakai',
+ 'admin.invite.copied': 'Tautan undangan disalin ke clipboard',
+ 'admin.invite.copyLink': 'Salin tautan',
+ 'admin.invite.deleted': 'Tautan undangan dihapus',
+ 'admin.invite.createError': 'Gagal membuat tautan undangan',
+ 'admin.invite.deleteError': 'Gagal menghapus tautan undangan',
+ 'admin.tabs.settings': 'Pengaturan',
+ 'admin.allowRegistration': 'Izinkan Pendaftaran',
+ 'admin.allowRegistrationHint': 'Pengguna baru dapat mendaftar sendiri',
+ 'admin.authMethods': 'Metode Autentikasi',
+ 'admin.passwordLogin': 'Login dengan Kata Sandi',
+ 'admin.passwordLoginHint':
+ 'Izinkan pengguna masuk dengan email dan kata sandi',
+ 'admin.passwordRegistration': 'Pendaftaran dengan Kata Sandi',
+ 'admin.passwordRegistrationHint':
+ 'Izinkan pengguna baru mendaftar dengan email dan kata sandi',
+ 'admin.oidcLogin': 'Login SSO',
+ 'admin.oidcLoginHint': 'Izinkan pengguna masuk dengan SSO',
+ 'admin.oidcRegistration': 'Penyediaan Otomatis SSO',
+ 'admin.oidcRegistrationHint': 'Buat akun otomatis untuk pengguna SSO baru',
+ 'admin.envOverrideHint':
+ 'Pengaturan login kata sandi dikendalikan oleh variabel lingkungan OIDC_ONLY dan tidak dapat diubah di sini.',
+ 'admin.lockoutWarning': 'Minimal satu metode login harus tetap aktif',
+ 'admin.requireMfa': 'Wajibkan autentikasi dua faktor (2FA)',
+ 'admin.requireMfaHint':
+ 'Pengguna tanpa 2FA harus menyelesaikan pengaturan di Pengaturan sebelum menggunakan aplikasi.',
+ 'admin.apiKeys': 'Kunci API',
+ 'admin.apiKeysHint':
+ 'Opsional. Mengaktifkan data tempat yang lebih lengkap seperti foto dan cuaca.',
+ 'admin.mapsKey': 'Kunci API Google Maps',
+ 'admin.mapsKeyHint':
+ 'Diperlukan untuk pencarian tempat. Dapatkan di console.cloud.google.com',
+ 'admin.mapsKeyHintLong':
+ 'Tanpa kunci API, OpenStreetMap digunakan untuk pencarian tempat. Dengan kunci API Google, foto, rating, dan jam buka juga bisa dimuat. Dapatkan di console.cloud.google.com.',
+ 'admin.recommended': 'Direkomendasikan',
+ 'admin.weatherKey': 'Kunci API OpenWeatherMap',
+ 'admin.weatherKeyHint': 'Untuk data cuaca. Gratis di openweathermap.org',
+ 'admin.validateKey': 'Uji',
+ 'admin.keyValid': 'Terhubung',
+ 'admin.keyInvalid': 'Tidak valid',
+ 'admin.keySaved': 'Kunci API disimpan',
+ 'admin.oidcTitle': 'Single Sign-On (OIDC)',
+ 'admin.oidcSubtitle':
+ 'Izinkan login melalui penyedia eksternal seperti Google, Apple, Authentik, atau Keycloak.',
+ 'admin.oidcDisplayName': 'Nama Tampilan',
+ 'admin.oidcIssuer': 'Issuer URL',
+ 'admin.oidcIssuerHint':
+ 'Issuer URL OpenID Connect dari penyedia. Contoh: https://accounts.google.com',
+ 'admin.oidcSaved': 'Konfigurasi OIDC disimpan',
+ 'admin.oidcOnlyMode': 'Nonaktifkan autentikasi kata sandi',
+ 'admin.oidcOnlyModeHint':
+ 'Jika diaktifkan, hanya login SSO yang diizinkan. Login dan pendaftaran berbasis kata sandi diblokir.',
+ 'admin.fileTypes': 'Jenis File yang Diizinkan',
+ 'admin.fileTypesHint':
+ 'Atur jenis file apa saja yang boleh diunggah pengguna.',
+ 'admin.fileTypesFormat':
+ 'Ekstensi dipisahkan koma (contoh: jpg,png,pdf,doc). Gunakan * untuk mengizinkan semua jenis.',
+ 'admin.fileTypesSaved': 'Pengaturan jenis file disimpan',
+ 'admin.placesPhotos.title': 'Foto Tempat',
+ 'admin.placesPhotos.subtitle':
+ 'Mengambil foto dari Google Places API. Nonaktifkan untuk menghemat kuota API. Foto Wikimedia tidak terpengaruh.',
+ 'admin.placesAutocomplete.title': 'Pelengkapan Otomatis Tempat',
+ 'admin.placesAutocomplete.subtitle':
+ 'Menggunakan Google Places API untuk saran pencarian. Nonaktifkan untuk menghemat kuota API.',
+ 'admin.placesDetails.title': 'Detail Tempat',
+ 'admin.placesDetails.subtitle':
+ 'Mengambil informasi detail tempat (jam, penilaian, situs web) dari Google Places API. Nonaktifkan untuk menghemat kuota API.',
+ 'admin.bagTracking.title': 'Pelacak Tas',
+ 'admin.bagTracking.subtitle':
+ 'Aktifkan berat dan penugasan tas untuk item packing',
+ 'admin.collab.chat.title': 'Chat',
+ 'admin.collab.chat.subtitle': 'Pesan real-time untuk kolaborasi',
+ 'admin.collab.notes.title': 'Catatan',
+ 'admin.collab.notes.subtitle': 'Catatan dan dokumen bersama',
+ 'admin.collab.polls.title': 'Jajak Pendapat',
+ 'admin.collab.polls.subtitle': 'Jajak pendapat dan voting grup',
+ 'admin.collab.whatsnext.title': 'Selanjutnya',
+ 'admin.collab.whatsnext.subtitle': 'Saran aktivitas dan langkah selanjutnya',
+ 'admin.tabs.config': 'Personalisasi',
+ 'admin.tabs.defaults': 'Pengaturan Default Pengguna',
+ 'admin.defaultSettings.title': 'Pengaturan Default Pengguna',
+ 'admin.defaultSettings.description':
+ 'Tetapkan nilai default untuk seluruh instance. Pengguna yang belum mengubah pengaturan akan melihat nilai-nilai ini. Perubahan mereka sendiri selalu diprioritaskan.',
+ 'admin.defaultSettings.saved': 'Default disimpan',
+ 'admin.defaultSettings.reset': 'Atur ulang ke default bawaan',
+ 'admin.defaultSettings.resetToBuiltIn': 'atur ulang',
+ 'admin.tabs.templates': 'Template Packing',
+ 'admin.packingTemplates.title': 'Template Packing',
+ 'admin.packingTemplates.subtitle':
+ 'Buat daftar packing yang bisa digunakan ulang untuk perjalananmu',
+ 'admin.packingTemplates.create': 'Template Baru',
+ 'admin.packingTemplates.namePlaceholder':
+ 'Nama template (contoh: Liburan Pantai)',
+ 'admin.packingTemplates.empty': 'Belum ada template yang dibuat',
+ 'admin.packingTemplates.items': 'item',
+ 'admin.packingTemplates.categories': 'kategori',
+ 'admin.packingTemplates.itemName': 'Nama item',
+ 'admin.packingTemplates.itemCategory': 'Kategori',
+ 'admin.packingTemplates.categoryName': 'Nama kategori (contoh: Pakaian)',
+ 'admin.packingTemplates.addCategory': 'Tambah kategori',
+ 'admin.packingTemplates.created': 'Template dibuat',
+ 'admin.packingTemplates.deleted': 'Template dihapus',
+ 'admin.packingTemplates.loadError': 'Gagal memuat template',
+ 'admin.packingTemplates.createError': 'Gagal membuat template',
+ 'admin.packingTemplates.deleteError': 'Gagal menghapus template',
+ 'admin.packingTemplates.saveError': 'Gagal menyimpan',
+ 'admin.tabs.addons': 'Addon',
+ 'admin.addons.title': 'Addon',
+ 'admin.addons.subtitle':
+ 'Aktifkan atau nonaktifkan fitur untuk menyesuaikan pengalaman TREK kamu.',
+ 'admin.addons.catalog.packing.name': 'Daftar',
+ 'admin.addons.catalog.packing.description':
+ 'Daftar packing dan tugas to-do untuk perjalananmu',
+ 'admin.addons.catalog.budget.name': 'Anggaran',
+ 'admin.addons.catalog.budget.description':
+ 'Lacak pengeluaran dan rencanakan anggaran perjalananmu',
+ 'admin.addons.catalog.documents.name': 'Dokumen',
+ 'admin.addons.catalog.documents.description':
+ 'Simpan dan kelola dokumen perjalanan',
+ 'admin.addons.catalog.vacay.name': 'Vacay',
+ 'admin.addons.catalog.vacay.description':
+ 'Perencana liburan pribadi dengan tampilan kalender',
+ 'admin.addons.catalog.atlas.name': 'Atlas',
+ 'admin.addons.catalog.atlas.description':
+ 'Peta dunia dengan negara yang pernah dikunjungi dan statistik perjalanan',
+ 'admin.addons.catalog.collab.name': 'Collab',
+ 'admin.addons.catalog.collab.description':
+ 'Catatan real-time, polling, dan chat untuk perencanaan perjalanan',
+ 'admin.addons.catalog.memories.name': 'Foto (Immich)',
+ 'admin.addons.catalog.memories.description':
+ 'Bagikan foto perjalanan melalui instans Immich kamu',
+ 'admin.addons.catalog.mcp.name': 'MCP',
+ 'admin.addons.catalog.mcp.description':
+ 'Model Context Protocol untuk integrasi asisten AI',
+ 'admin.addons.subtitleBefore':
+ 'Aktifkan atau nonaktifkan fitur untuk menyesuaikan ',
+ 'admin.addons.subtitleAfter': ' kamu.',
+ 'admin.addons.enabled': 'Aktif',
+ 'admin.addons.disabled': 'Nonaktif',
+ 'admin.addons.type.trip': 'Perjalanan',
+ 'admin.addons.type.global': 'Global',
+ 'admin.addons.type.integration': 'Integrasi',
+ 'admin.addons.tripHint': 'Tersedia sebagai tab di setiap perjalanan',
+ 'admin.addons.globalHint':
+ 'Tersedia sebagai bagian mandiri di navigasi utama',
+ 'admin.addons.integrationHint':
+ 'Layanan backend dan integrasi API tanpa halaman tersendiri',
+ 'admin.addons.toast.updated': 'Addon diperbarui',
+ 'admin.addons.toast.error': 'Gagal memperbarui addon',
+ 'admin.addons.noAddons': 'Tidak ada addon yang tersedia',
+ 'admin.weather.title': 'Data Cuaca',
+ 'admin.weather.badge': 'Sejak 24 Maret 2026',
+ 'admin.weather.description':
+ 'TREK menggunakan Open-Meteo sebagai sumber data cuaca. Open-Meteo adalah layanan cuaca gratis dan open-source — tidak perlu kunci API.',
+ 'admin.weather.forecast': 'Prakiraan 16 hari',
+ 'admin.weather.forecastDesc': 'Sebelumnya 5 hari (OpenWeatherMap)',
+ 'admin.weather.climate': 'Data iklim historis',
+ 'admin.weather.climateDesc':
+ 'Rata-rata dari 85 tahun terakhir untuk hari di luar prakiraan 16 hari',
+ 'admin.weather.requests': '10.000 permintaan / hari',
+ 'admin.weather.requestsDesc': 'Gratis, tidak perlu kunci API',
+ 'admin.weather.locationHint':
+ 'Cuaca didasarkan pada tempat pertama dengan koordinat di setiap hari. Jika tidak ada tempat yang ditetapkan untuk suatu hari, tempat mana pun dari daftar tempat digunakan sebagai referensi.',
+ 'admin.tabs.mcpTokens': 'Akses MCP',
+ 'admin.mcpTokens.title': 'Akses MCP',
+ 'admin.mcpTokens.subtitle':
+ 'Kelola sesi OAuth dan token API di semua pengguna',
+ 'admin.mcpTokens.sectionTitle': 'Token API',
+ 'admin.mcpTokens.owner': 'Pemilik',
+ 'admin.mcpTokens.tokenName': 'Nama Token',
+ 'admin.mcpTokens.created': 'Dibuat',
+ 'admin.mcpTokens.lastUsed': 'Terakhir Digunakan',
+ 'admin.mcpTokens.never': 'Tidak pernah',
+ 'admin.mcpTokens.empty': 'Belum ada token MCP yang dibuat',
+ 'admin.mcpTokens.deleteTitle': 'Hapus Token',
+ 'admin.mcpTokens.deleteMessage':
+ 'Ini akan mencabut token segera. Pengguna akan kehilangan akses MCP melalui token ini.',
+ 'admin.mcpTokens.deleteSuccess': 'Token dihapus',
+ 'admin.mcpTokens.deleteError': 'Gagal menghapus token',
+ 'admin.mcpTokens.loadError': 'Gagal memuat token',
+ 'admin.oauthSessions.sectionTitle': 'Sesi OAuth',
+ 'admin.oauthSessions.clientName': 'Klien',
+ 'admin.oauthSessions.owner': 'Pemilik',
+ 'admin.oauthSessions.scopes': 'Cakupan',
+ 'admin.oauthSessions.created': 'Dibuat',
+ 'admin.oauthSessions.empty': 'Tidak ada sesi OAuth aktif',
+ 'admin.oauthSessions.revokeTitle': 'Cabut Sesi',
+ 'admin.oauthSessions.revokeMessage':
+ 'Ini akan segera mencabut sesi OAuth. Client akan kehilangan akses MCP.',
+ 'admin.oauthSessions.revokeSuccess': 'Sesi dicabut',
+ 'admin.oauthSessions.revokeError': 'Gagal mencabut sesi',
+ 'admin.oauthSessions.loadError': 'Gagal memuat sesi OAuth',
+ 'admin.tabs.github': 'GitHub',
+ 'admin.audit.subtitle':
+ 'Peristiwa sensitif keamanan dan administrasi (backup, pengguna, MFA, pengaturan).',
+ 'admin.audit.empty': 'Belum ada entri audit.',
+ 'admin.audit.refresh': 'Segarkan',
+ 'admin.audit.loadMore': 'Muat lebih banyak',
+ 'admin.audit.showing': '{count} dimuat · {total} total',
+ 'admin.audit.col.time': 'Waktu',
+ 'admin.audit.col.user': 'Pengguna',
+ 'admin.audit.col.action': 'Tindakan',
+ 'admin.audit.col.resource': 'Sumber Daya',
+ 'admin.audit.col.ip': 'IP',
+ 'admin.audit.col.details': 'Detail',
+ 'admin.github.title': 'Riwayat Rilis',
+ 'admin.github.subtitle': 'Pembaruan terbaru dari {repo}',
+ 'admin.github.latest': 'Terbaru',
+ 'admin.github.prerelease': 'Pra-rilis',
+ 'admin.github.showDetails': 'Tampilkan detail',
+ 'admin.github.hideDetails': 'Sembunyikan detail',
+ 'admin.github.loadMore': 'Muat lebih banyak',
+ 'admin.github.loading': 'Memuat...',
+ 'admin.github.error': 'Gagal memuat rilis',
+ 'admin.github.by': 'oleh',
+ 'admin.github.support': 'Bantu saya terus mengembangkan TREK',
+ 'admin.update.available': 'Pembaruan tersedia',
+ 'admin.update.text': 'TREK {version} tersedia. Kamu menggunakan {current}.',
+ 'admin.update.button': 'Lihat di GitHub',
+ 'admin.update.install': 'Pasang Pembaruan',
+ 'admin.update.confirmTitle': 'Pasang Pembaruan?',
+ 'admin.update.confirmText':
+ 'TREK akan diperbarui dari {current} ke {version}. Server akan restart otomatis setelahnya.',
+ 'admin.update.dataInfo':
+ 'Semua datamu (perjalanan, pengguna, kunci API, unggahan, Vacay, Atlas, anggaran) akan dipertahankan.',
+ 'admin.update.warning':
+ 'Aplikasi akan tidak tersedia sebentar selama restart.',
+ 'admin.update.confirm': 'Perbarui Sekarang',
+ 'admin.update.installing': 'Memperbarui…',
+ 'admin.update.success': 'Pembaruan terpasang! Server sedang restart…',
+ 'admin.update.failed': 'Pembaruan gagal',
+ 'admin.update.backupHint':
+ 'Kami merekomendasikan membuat backup sebelum memperbarui.',
+ 'admin.update.backupLink': 'Pergi ke Backup',
+ 'admin.update.howTo': 'Cara Memperbarui',
+ 'admin.update.dockerText':
+ 'Instans TREK kamu berjalan di Docker. Untuk memperbarui ke {version}, jalankan perintah berikut di servermu:',
+ 'admin.update.reloadHint': 'Muat ulang halaman dalam beberapa detik.',
+ 'admin.tabs.permissions': 'Izin',
+ 'admin.addons.catalog.journey.name': 'Journey',
+ 'admin.addons.catalog.journey.description':
+ 'Pelacakan perjalanan & jurnal dengan check-in, foto, dan cerita harian',
+ 'admin.passkey.title': 'Login dengan passkey',
+ 'admin.passkey.cardHint':
+ 'Izinkan pengguna masuk dengan passkey (WebAuthn). Nonaktif secara default.',
+ 'admin.passkey.login': 'Aktifkan login dengan passkey',
+ 'admin.passkey.loginHint':
+ 'Tampilkan opsi "Masuk dengan passkey" dan izinkan pengguna mendaftarkan passkey di pengaturan mereka.',
+ 'admin.passkey.notConfigured':
+ 'Belum ada domain WebAuthn yang terdeteksi untuk deployment ini. Atur APP_URL atau Relying Party ID di bawah — passkey tetap tersembunyi sampai itu dilakukan.',
+ 'admin.passkey.rpId': 'Relying Party ID (domain)',
+ 'admin.passkey.rpIdHint':
+ 'Domain murni tempat passkey diikat, mis. trek.example.org. Kosongkan untuk mengambilnya dari APP_URL. Mengubahnya nanti akan membatalkan passkey yang sudah ada.',
+ 'admin.passkey.origins': 'Origin yang diizinkan',
+ 'admin.passkey.originsHint':
+ 'Origin lengkap dipisahkan koma, mis. https://trek.example.org. Kosongkan untuk menggunakan APP_URL.',
+ 'admin.passkey.reset': 'Reset passkey',
+ 'admin.passkey.resetHint':
+ 'Hapus semua passkey pengguna ini (mis. saat perangkat hilang). Mereka tetap bisa masuk dengan kata sandi mereka.',
+ 'admin.passkey.resetConfirm': 'Hapus semua passkey untuk {name}?',
+ 'admin.passkey.resetDone': 'Menghapus {count} passkey',
+ 'admin.defaultSettings.mapProvider': 'Mesin peta',
+ 'admin.defaultSettings.mapProviderHint': 'Peta default untuk semua orang di instance ini. Setiap pengguna tetap dapat menggantinya di pengaturan masing-masing.',
+ 'admin.defaultSettings.providerLeaflet': 'Standar (gratis)',
+ 'admin.defaultSettings.providerMapbox': 'Mapbox (3D)',
+ 'admin.defaultSettings.mapboxToken': 'Token Mapbox bersama',
+ 'admin.defaultSettings.mapboxTokenHint': 'Digunakan untuk setiap pengguna yang belum memasukkan token mereka sendiri — sehingga seluruh instance mendapatkan Mapbox tanpa perlu membagikan kunci satu per satu. Disimpan dalam bentuk terenkripsi.',
+ 'admin.defaultSettings.mapboxStyle': 'Gaya peta',
+ 'admin.defaultSettings.mapboxStylePlaceholder': 'Pilih gaya…',
+ 'admin.defaultSettings.mapbox3d': 'Bangunan & medan 3D',
+ 'admin.defaultSettings.mapboxQuality': 'Mode kualitas tinggi',
+};
+export default admin;
diff --git a/shared/src/i18n/id/airport.ts b/shared/src/i18n/id/airport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cd206806e52423da61ef0b80ff8fb8c5963c56ad
--- /dev/null
+++ b/shared/src/i18n/id/airport.ts
@@ -0,0 +1,6 @@
+import type { TranslationStrings } from '../types';
+
+const airport: TranslationStrings = {
+ 'airport.searchPlaceholder': 'Kode bandara atau kota (mis. FRA)',
+};
+export default airport;
diff --git a/shared/src/i18n/id/atlas.ts b/shared/src/i18n/id/atlas.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f7f9c323c0c578253de2c606a28957ded621a82e
--- /dev/null
+++ b/shared/src/i18n/id/atlas.ts
@@ -0,0 +1,59 @@
+import type { TranslationStrings } from '../types';
+
+const atlas: TranslationStrings = {
+ 'atlas.subtitle': 'Jejak perjalananmu di seluruh dunia',
+ 'atlas.countries': 'Negara',
+ 'atlas.trips': 'Perjalanan',
+ 'atlas.places': 'Tempat',
+ 'atlas.unmark': 'Hapus',
+ 'atlas.confirmMark': 'Tandai negara ini sebagai sudah dikunjungi?',
+ 'atlas.confirmUnmark': 'Hapus negara ini dari daftar kunjunganmu?',
+ 'atlas.confirmUnmarkRegion': 'Hapus wilayah ini dari daftar kunjunganmu?',
+ 'atlas.markVisited': 'Tandai sudah dikunjungi',
+ 'atlas.markVisitedHint': 'Tambahkan negara ini ke daftar kunjunganmu',
+ 'atlas.markRegionVisitedHint': 'Tambahkan wilayah ini ke daftar kunjunganmu',
+ 'atlas.addToBucket': 'Tambah ke bucket list',
+ 'atlas.addPoi': 'Tambah tempat',
+ 'atlas.searchCountry': 'Cari negara...',
+ 'atlas.bucketNamePlaceholder': 'Nama (negara, kota, tempat...)',
+ 'atlas.month': 'Bulan',
+ 'atlas.year': 'Tahun',
+ 'atlas.addToBucketHint': 'Simpan sebagai tempat yang ingin dikunjungi',
+ 'atlas.bucketWhen': 'Kapan kamu berencana mengunjunginya?',
+ 'atlas.statsTab': 'Statistik',
+ 'atlas.bucketTab': 'Daftar Impian',
+ 'atlas.addBucket': 'Tambah ke daftar impian',
+ 'atlas.bucketNotesPlaceholder': 'Catatan (opsional)',
+ 'atlas.bucketEmpty': 'Daftar impianmu masih kosong',
+ 'atlas.bucketEmptyHint': 'Tambahkan tempat-tempat yang ingin kamu kunjungi',
+ 'atlas.days': 'Hari',
+ 'atlas.visitedCountries': 'Negara yang Dikunjungi',
+ 'atlas.cities': 'Kota',
+ 'atlas.noData': 'Belum ada data perjalanan',
+ 'atlas.noDataHint':
+ 'Buat perjalanan dan tambahkan tempat untuk melihat peta duniamu',
+ 'atlas.lastTrip': 'Perjalanan terakhir',
+ 'atlas.nextTrip': 'Perjalanan berikutnya',
+ 'atlas.daysLeft': 'hari lagi',
+ 'atlas.streak': 'Rentetan',
+ 'atlas.years': 'tahun',
+ 'atlas.yearInRow': 'tahun berturut-turut',
+ 'atlas.yearsInRow': 'tahun berturut-turut',
+ 'atlas.tripIn': 'perjalanan ke',
+ 'atlas.tripsIn': 'perjalanan ke',
+ 'atlas.since': 'sejak',
+ 'atlas.europe': 'Eropa',
+ 'atlas.asia': 'Asia',
+ 'atlas.northAmerica': 'Amerika Utara',
+ 'atlas.southAmerica': 'Amerika Selatan',
+ 'atlas.africa': 'Afrika',
+ 'atlas.oceania': 'Oseania',
+ 'atlas.other': 'Lainnya',
+ 'atlas.firstVisit': 'Perjalanan pertama',
+ 'atlas.lastVisitLabel': 'Perjalanan terakhir',
+ 'atlas.tripSingular': 'Perjalanan',
+ 'atlas.tripPlural': 'Perjalanan',
+ 'atlas.placeVisited': 'Tempat dikunjungi',
+ 'atlas.placesVisited': 'Tempat dikunjungi',
+};
+export default atlas;
diff --git a/shared/src/i18n/id/backup.ts b/shared/src/i18n/id/backup.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e942c4599b617842fdbadd35e5fdf6d81cc0b953
--- /dev/null
+++ b/shared/src/i18n/id/backup.ts
@@ -0,0 +1,77 @@
+import type { TranslationStrings } from '../types';
+
+const backup: TranslationStrings = {
+ 'backup.title': 'Pencadangan Data',
+ 'backup.subtitle': 'Database dan semua file yang diunggah',
+ 'backup.refresh': 'Segarkan',
+ 'backup.upload': 'Unggah Cadangan',
+ 'backup.uploading': 'Mengunggah…',
+ 'backup.create': 'Buat Cadangan',
+ 'backup.creating': 'Membuat…',
+ 'backup.empty': 'Belum ada cadangan',
+ 'backup.createFirst': 'Buat cadangan pertama',
+ 'backup.download': 'Unduh',
+ 'backup.restore': 'Pulihkan',
+ 'backup.confirm.restore':
+ 'Pulihkan cadangan "{name}"?\n\nSemua data saat ini akan digantikan dengan cadangan.',
+ 'backup.confirm.uploadRestore':
+ 'Unggah dan pulihkan file cadangan "{name}"?\n\nSemua data saat ini akan ditimpa.',
+ 'backup.confirm.delete': 'Hapus cadangan "{name}"?',
+ 'backup.toast.loadError': 'Gagal memuat cadangan',
+ 'backup.toast.created': 'Cadangan berhasil dibuat',
+ 'backup.toast.createError': 'Gagal membuat cadangan',
+ 'backup.toast.restored': 'Cadangan dipulihkan. Halaman akan dimuat ulang…',
+ 'backup.toast.restoreError': 'Gagal memulihkan',
+ 'backup.toast.uploadError': 'Gagal mengunggah',
+ 'backup.toast.deleted': 'Cadangan dihapus',
+ 'backup.toast.deleteError': 'Gagal menghapus',
+ 'backup.toast.downloadError': 'Unduhan gagal',
+ 'backup.toast.settingsSaved': 'Pengaturan pencadangan otomatis disimpan',
+ 'backup.toast.settingsError': 'Gagal menyimpan pengaturan',
+ 'backup.auto.title': 'Cadangan Otomatis',
+ 'backup.auto.subtitle': 'Pencadangan otomatis sesuai jadwal',
+ 'backup.auto.enable': 'Aktifkan cadangan otomatis',
+ 'backup.auto.enableHint':
+ 'Cadangan akan dibuat secara otomatis sesuai jadwal yang dipilih',
+ 'backup.auto.interval': 'Interval',
+ 'backup.auto.hour': 'Jalankan pada jam',
+ 'backup.auto.hourHint': 'Waktu lokal server (format {format})',
+ 'backup.auto.dayOfWeek': 'Hari dalam seminggu',
+ 'backup.auto.dayOfMonth': 'Tanggal dalam sebulan',
+ 'backup.auto.dayOfMonthHint':
+ 'Dibatasi 1–28 agar kompatibel dengan semua bulan',
+ 'backup.auto.scheduleSummary': 'Jadwal',
+ 'backup.auto.summaryDaily': 'Setiap hari pukul {hour}:00',
+ 'backup.auto.summaryWeekly': 'Setiap {day} pukul {hour}:00',
+ 'backup.auto.summaryMonthly': 'Tanggal {day} setiap bulan pukul {hour}:00',
+ 'backup.auto.envLocked': 'Docker',
+ 'backup.auto.envLockedHint':
+ 'Cadangan otomatis dikonfigurasi melalui variabel lingkungan Docker. Untuk mengubah pengaturan ini, perbarui docker-compose.yml kamu dan restart container.',
+ 'backup.auto.copyEnv': 'Salin variabel env Docker',
+ 'backup.auto.envCopied': 'Variabel env Docker disalin ke clipboard',
+ 'backup.auto.keepLabel': 'Hapus cadangan lama setelah',
+ 'backup.dow.sunday': 'Min',
+ 'backup.dow.monday': 'Sen',
+ 'backup.dow.tuesday': 'Sel',
+ 'backup.dow.wednesday': 'Rab',
+ 'backup.dow.thursday': 'Kam',
+ 'backup.dow.friday': 'Jum',
+ 'backup.dow.saturday': 'Sab',
+ 'backup.interval.hourly': 'Per jam',
+ 'backup.interval.daily': 'Harian',
+ 'backup.interval.weekly': 'Mingguan',
+ 'backup.interval.monthly': 'Bulanan',
+ 'backup.keep.1day': '1 hari',
+ 'backup.keep.3days': '3 hari',
+ 'backup.keep.7days': '7 hari',
+ 'backup.keep.14days': '14 hari',
+ 'backup.keep.30days': '30 hari',
+ 'backup.keep.forever': 'Simpan selamanya',
+ 'backup.restoreConfirmTitle': 'Pulihkan Cadangan?',
+ 'backup.restoreWarning':
+ 'Semua data saat ini (perjalanan, tempat, pengguna, unggahan) akan digantikan secara permanen oleh cadangan ini. Tindakan ini tidak dapat dibatalkan.',
+ 'backup.restoreTip':
+ 'Tips: Buat cadangan kondisi saat ini sebelum memulihkan.',
+ 'backup.restoreConfirm': 'Ya, pulihkan',
+};
+export default backup;
diff --git a/shared/src/i18n/id/budget.ts b/shared/src/i18n/id/budget.ts
new file mode 100644
index 0000000000000000000000000000000000000000..abbf8784591babf68aec57f86daa4709c8fcec73
--- /dev/null
+++ b/shared/src/i18n/id/budget.ts
@@ -0,0 +1,116 @@
+import type { TranslationStrings } from '../types';
+
+const budget: TranslationStrings = {
+ 'budget.title': 'Anggaran',
+ 'budget.exportCsv': 'Ekspor CSV',
+ 'budget.emptyTitle': 'Belum ada anggaran',
+ 'budget.emptyText':
+ 'Buat kategori dan entri untuk merencanakan anggaran perjalananmu',
+ 'budget.emptyPlaceholder': 'Masukkan nama kategori...',
+ 'budget.createCategory': 'Buat Kategori',
+ 'budget.category': 'Kategori',
+ 'budget.categoryName': 'Nama Kategori',
+ 'budget.table.name': 'Nama',
+ 'budget.table.total': 'Total',
+ 'budget.table.persons': 'Orang',
+ 'budget.table.days': 'Hari',
+ 'budget.table.perPerson': 'Per Orang',
+ 'budget.table.perDay': 'Per Hari',
+ 'budget.table.perPersonDay': 'P. o / Hari',
+ 'budget.table.note': 'Catatan',
+ 'budget.table.date': 'Tanggal',
+ 'budget.newEntry': 'Entri Baru',
+ 'budget.defaultEntry': 'Entri Baru',
+ 'budget.defaultCategory': 'Kategori Baru',
+ 'budget.total': 'Total',
+ 'budget.totalBudget': 'Total Anggaran',
+ 'budget.byCategory': 'Per Kategori',
+ 'budget.editTooltip': 'Klik untuk edit',
+ 'budget.linkedToReservation': 'Terhubung ke reservasi — edit nama di sana',
+ 'budget.confirm.deleteCategory':
+ 'Yakin ingin menghapus kategori "{name}" dengan {count} entri?',
+ 'budget.deleteCategory': 'Hapus Kategori',
+ 'budget.perPerson': 'Per Orang',
+ 'budget.paid': 'Sudah dibayar',
+ 'budget.open': 'Belum dibayar',
+ 'budget.noMembers': 'Tidak ada anggota yang ditugaskan',
+ 'budget.settlement': 'Penyelesaian',
+ 'budget.settlementInfo':
+ 'Klik foto anggota di item anggaran untuk menandainya hijau — artinya mereka sudah bayar. Penyelesaian lalu menunjukkan siapa berhutang ke siapa dan berapa.',
+ 'budget.netBalances': 'Saldo Bersih',
+ 'budget.categoriesLabel': 'kategori',
+ "costs.you": "Kamu",
+ "costs.youShort": "K",
+ "costs.youLower": "kamu",
+ "costs.youOwe": "Kamu berhutang",
+ "costs.youOweSub": "Kamu harus membayar yang lain",
+ "costs.youreOwed": "Kamu dipinjami",
+ "costs.youreOwedSub": "Yang lain harus membayarmu",
+ "costs.totalSpend": "Total pengeluaran perjalanan",
+ "costs.totalSpendSub": "Untuk semua pelancong",
+ "costs.to": "Ke",
+ "costs.from": "Dari",
+ "costs.allSettled": "Semua sudah lunas",
+ "costs.nothingOwed": "Tidak ada yang berhutang padamu",
+ "costs.yourShare": "Bagianmu",
+ "costs.youPaid": "Kamu membayar",
+ "costs.expenses": "Pengeluaran",
+ "costs.entries": "{count} entri",
+ "costs.searchPlaceholder": "Cari pengeluaran…",
+ "costs.filter.all": "Semua",
+ "costs.filter.mine": "Dibayar olehku",
+ "costs.filter.owed": "Dipinjami padaku",
+ "costs.addExpense": "Tambah pengeluaran",
+ "costs.editExpense": "Edit pengeluaran",
+ "costs.noMatch": "Tidak ada pengeluaran yang cocok dengan pencarianmu.",
+ "costs.emptyText": "Belum ada pengeluaran. Tambahkan yang pertama.",
+ "costs.spent": "{amount} dibelanjakan",
+ "costs.noDate": "Tanpa tanggal",
+ "costs.noOnePaid": "Belum ada yang membayar",
+ "costs.youLent": "kamu meminjamkan {amount}",
+ "costs.youBorrowed": "kamu meminjam {amount}",
+ "costs.settleUp": "Lunasi",
+ "costs.history": "Riwayat",
+ "costs.everyoneSquare": "Semua sudah impas",
+ "costs.nothingOutstanding": "Tidak ada pembayaran tertunggak saat ini.",
+ "costs.pay": "bayar",
+ "costs.pays": "membayar",
+ "costs.settle": "Lunasi",
+ "costs.balances": "Saldo",
+ "costs.byCategory": "Per kategori",
+ "costs.noCategories": "Belum ada pengeluaran.",
+ "costs.settleHistory": "Riwayat pelunasan",
+ "costs.noSettlements": "Belum ada pembayaran yang dilunasi.",
+ "costs.paymentsSettled": "{count} pembayaran dilunasi",
+ "costs.paid": "dibayar",
+ "costs.undo": "Urungkan",
+ "costs.whatFor": "Untuk apa?",
+ "costs.namePlaceholder": "mis. Makan malam, oleh-oleh, bensin…",
+ "costs.totalAmount": "Jumlah total",
+ "costs.currency": "Mata uang",
+ "costs.day": "Hari",
+ "costs.rateLabel": "1 {from} dalam {to}",
+ "costs.category": "Kategori",
+ "costs.whoPaid": "Siapa yang membayar?",
+ "costs.splitBetween": "Bagi rata antara",
+ "costs.pickSomeone": "Pilih setidaknya satu orang untuk berbagi.",
+ "costs.splitSummary": "Dibagi {count} cara · {amount} masing-masing",
+ "costs.cat.accommodation": "Akomodasi",
+ "costs.cat.food": "Makanan & minuman",
+ "costs.cat.groceries": "Belanja kebutuhan",
+ "costs.cat.transport": "Transportasi",
+ "costs.cat.flights": "Penerbangan",
+ "costs.cat.activities": "Aktivitas",
+ "costs.cat.sightseeing": "Wisata",
+ "costs.cat.shopping": "Belanja",
+ "costs.cat.fees": "Biaya & tiket",
+ "costs.cat.health": "Kesehatan",
+ "costs.cat.tips": "Tip",
+ "costs.cat.other": "Lainnya",
+ "costs.daysCount": "{count} hari",
+ "costs.travelers": "{count} pelancong",
+ "costs.liveRate": "kurs langsung",
+ "costs.settleAll": "Lunasi semua",
+};
+
+export default budget;
diff --git a/shared/src/i18n/id/categories.ts b/shared/src/i18n/id/categories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9d08b328f31675642c60a618790e0e62b0f94d74
--- /dev/null
+++ b/shared/src/i18n/id/categories.ts
@@ -0,0 +1,26 @@
+import type { TranslationStrings } from '../types';
+
+const categories: TranslationStrings = {
+ 'categories.title': 'Kategori',
+ 'categories.subtitle': 'Kelola kategori untuk tempat',
+ 'categories.new': 'Kategori Baru',
+ 'categories.empty': 'Belum ada kategori',
+ 'categories.namePlaceholder': 'Nama kategori',
+ 'categories.icon': 'Ikon',
+ 'categories.color': 'Warna',
+ 'categories.customColor': 'Pilih warna kustom',
+ 'categories.preview': 'Pratinjau',
+ 'categories.defaultName': 'Kategori',
+ 'categories.update': 'Perbarui',
+ 'categories.create': 'Buat',
+ 'categories.confirm.delete':
+ 'Hapus kategori? Tempat dalam kategori ini tidak akan dihapus.',
+ 'categories.toast.loadError': 'Gagal memuat kategori',
+ 'categories.toast.nameRequired': 'Harap masukkan nama',
+ 'categories.toast.updated': 'Kategori diperbarui',
+ 'categories.toast.created': 'Kategori dibuat',
+ 'categories.toast.saveError': 'Gagal menyimpan',
+ 'categories.toast.deleted': 'Kategori dihapus',
+ 'categories.toast.deleteError': 'Gagal menghapus',
+};
+export default categories;
diff --git a/shared/src/i18n/id/collab.ts b/shared/src/i18n/id/collab.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cf91c8dd04da620ecb63865ee8d9ea83927e7fd3
--- /dev/null
+++ b/shared/src/i18n/id/collab.ts
@@ -0,0 +1,76 @@
+import type { TranslationStrings } from '../types';
+
+const collab: TranslationStrings = {
+ 'collab.tabs.chat': 'Chat',
+ 'collab.tabs.notes': 'Catatan',
+ 'collab.tabs.polls': 'Polling',
+ 'collab.whatsNext.title': 'Berikutnya',
+ 'collab.whatsNext.today': 'Hari ini',
+ 'collab.whatsNext.tomorrow': 'Besok',
+ 'collab.whatsNext.empty': 'Tidak ada aktivitas mendatang',
+ 'collab.whatsNext.until': 'sampai',
+ 'collab.whatsNext.emptyHint': 'Aktivitas dengan waktu akan muncul di sini',
+ 'collab.chat.send': 'Kirim',
+ 'collab.chat.placeholder': 'Ketik pesan...',
+ 'collab.chat.empty': 'Mulai percakapan',
+ 'collab.chat.emptyHint': 'Pesan dibagikan kepada semua anggota perjalanan',
+ 'collab.chat.emptyDesc':
+ 'Bagikan ide, rencana, dan update dengan grup perjalananmu',
+ 'collab.chat.today': 'Hari ini',
+ 'collab.chat.yesterday': 'Kemarin',
+ 'collab.chat.deletedMessage': 'menghapus pesan',
+ 'collab.chat.reply': 'Balas',
+ 'collab.chat.loadMore': 'Muat pesan lebih lama',
+ 'collab.chat.justNow': 'baru saja',
+ 'collab.chat.minutesAgo': '{n}m lalu',
+ 'collab.chat.hoursAgo': '{n}j lalu',
+ 'collab.notes.title': 'Catatan',
+ 'collab.notes.new': 'Catatan Baru',
+ 'collab.notes.empty': 'Belum ada catatan',
+ 'collab.notes.emptyHint': 'Mulai catat ide dan rencana',
+ 'collab.notes.all': 'Semua',
+ 'collab.notes.titlePlaceholder': 'Judul catatan',
+ 'collab.notes.contentPlaceholder': 'Tulis sesuatu...',
+ 'collab.notes.categoryPlaceholder': 'Kategori',
+ 'collab.notes.newCategory': 'Kategori baru...',
+ 'collab.notes.category': 'Kategori',
+ 'collab.notes.noCategory': 'Tanpa kategori',
+ 'collab.notes.color': 'Warna',
+ 'collab.notes.save': 'Simpan',
+ 'collab.notes.cancel': 'Batal',
+ 'collab.notes.edit': 'Edit',
+ 'collab.notes.delete': 'Hapus',
+ 'collab.notes.confirmDeleteTitle': 'Hapus catatan?',
+ 'collab.notes.confirmDeleteBody': 'Catatan ini akan dihapus secara permanen.',
+ 'collab.notes.pin': 'Sematkan',
+ 'collab.notes.unpin': 'Lepas sematan',
+ 'collab.notes.daysAgo': '{n}h lalu',
+ 'collab.notes.categorySettings': 'Kelola Kategori',
+ 'collab.notes.create': 'Buat',
+ 'collab.notes.website': 'Website',
+ 'collab.notes.websitePlaceholder': 'https://...',
+ 'collab.notes.attachFiles': 'Lampirkan file',
+ 'collab.notes.noCategoriesYet': 'Belum ada kategori',
+ 'collab.notes.emptyDesc': 'Buat catatan untuk memulai',
+ 'collab.polls.title': 'Polling',
+ 'collab.polls.new': 'Polling Baru',
+ 'collab.polls.empty': 'Belum ada polling',
+ 'collab.polls.emptyHint': 'Tanyakan ke grup dan voting bersama',
+ 'collab.polls.question': 'Pertanyaan',
+ 'collab.polls.questionPlaceholder': 'Apa yang harus kita lakukan?',
+ 'collab.polls.addOption': '+ Tambah pilihan',
+ 'collab.polls.optionPlaceholder': 'Pilihan {n}',
+ 'collab.polls.create': 'Buat Polling',
+ 'collab.polls.close': 'Tutup',
+ 'collab.polls.closed': 'Ditutup',
+ 'collab.polls.votes': '{n} suara',
+ 'collab.polls.vote': '{n} suara',
+ 'collab.polls.multipleChoice': 'Pilihan ganda',
+ 'collab.polls.multiChoice': 'Pilihan ganda',
+ 'collab.polls.deadline': 'Tenggat waktu',
+ 'collab.polls.option': 'Pilihan',
+ 'collab.polls.options': 'Pilihan',
+ 'collab.polls.delete': 'Hapus',
+ 'collab.polls.closedSection': 'Ditutup',
+};
+export default collab;
diff --git a/shared/src/i18n/id/common.ts b/shared/src/i18n/id/common.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d21f40849f539c20ed16884be1d5cc4296d2eb0c
--- /dev/null
+++ b/shared/src/i18n/id/common.ts
@@ -0,0 +1,54 @@
+import type { TranslationStrings } from '../types';
+
+const common: TranslationStrings = {
+ 'common.save': 'Simpan',
+ 'common.showMore': 'Tampilkan lebih banyak',
+ 'common.showLess': 'Tampilkan lebih sedikit',
+ 'common.cancel': 'Batal',
+ 'common.clear': 'Hapus',
+ 'common.delete': 'Hapus',
+ 'common.edit': 'Sunting',
+ 'common.add': 'Tambah',
+ 'common.loading': 'Memuat...',
+ 'common.import': 'Impor',
+ 'common.select': 'Pilih',
+ 'common.selectAll': 'Pilih semua',
+ 'common.deselectAll': 'Batalkan semua pilihan',
+ 'common.error': 'Kesalahan',
+ 'common.unknownError': 'Kesalahan tidak diketahui',
+ 'common.tooManyAttempts': 'Terlalu banyak percobaan. Coba lagi nanti.',
+ 'common.back': 'Kembali',
+ 'common.all': 'Semua',
+ 'common.close': 'Tutup',
+ 'common.open': 'Buka',
+ 'common.upload': 'Unggah',
+ 'common.search': 'Cari',
+ 'common.confirm': 'Konfirmasi',
+ 'common.ok': 'OK',
+ 'common.yes': 'Ya',
+ 'common.no': 'Tidak',
+ 'common.or': 'atau',
+ 'common.none': 'Tidak ada',
+ 'common.date': 'Tanggal',
+ 'common.rename': 'Ganti nama',
+ 'common.discardChanges': 'Buang perubahan',
+ 'common.discard': 'Buang',
+ 'common.name': 'Nama',
+ 'common.email': 'Email',
+ 'common.password': 'Kata sandi',
+ 'common.saving': 'Menyimpan...',
+ 'common.justNow': 'baru saja',
+ 'common.hoursAgo': '{count}j lalu',
+ 'common.daysAgo': '{count}h lalu',
+ 'common.saved': 'Tersimpan',
+ 'common.update': 'Perbarui',
+ 'common.change': 'Ubah',
+ 'common.uploading': 'Mengunggah…',
+ 'common.backToPlanning': 'Kembali ke Perencanaan',
+ 'common.reset': 'Atur ulang',
+ 'common.expand': 'Perluas',
+ 'common.collapse': 'Ciutkan',
+ 'common.copy': 'Salin',
+ 'common.copied': 'Disalin',
+};
+export default common;
diff --git a/shared/src/i18n/id/dashboard.ts b/shared/src/i18n/id/dashboard.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6def30a84e9b4faa76d0b8cb9041298690544bc5
--- /dev/null
+++ b/shared/src/i18n/id/dashboard.ts
@@ -0,0 +1,168 @@
+import type { TranslationStrings } from '../types';
+
+const dashboard: TranslationStrings = {
+ 'dashboard.title': 'Perjalananku',
+ 'dashboard.subtitle.loading': 'Memuat perjalanan...',
+ 'dashboard.subtitle.trips': '{count} perjalanan ({archived} diarsipkan)',
+ 'dashboard.subtitle.empty': 'Mulai perjalanan pertamamu',
+ 'dashboard.subtitle.activeOne': '{count} perjalanan aktif',
+ 'dashboard.subtitle.activeMany': '{count} perjalanan aktif',
+ 'dashboard.subtitle.archivedSuffix': ' · {count} diarsipkan',
+ 'dashboard.newTrip': 'Perjalanan Baru',
+ 'dashboard.newTripSub': 'Rencanakan perjalanan baru dari awal',
+ 'dashboard.gridView': 'Tampilan grid',
+ 'dashboard.listView': 'Tampilan daftar',
+ 'dashboard.currency': 'Mata uang',
+ 'dashboard.timezone': 'Zona waktu',
+ 'dashboard.localTime': 'Lokal',
+ 'dashboard.timezoneCustomTitle': 'Zona Waktu Kustom',
+ 'dashboard.timezoneCustomLabelPlaceholder': 'Label (opsional)',
+ 'dashboard.timezoneCustomTzPlaceholder': 'mis. America/New_York',
+ 'dashboard.timezoneCustomAdd': 'Tambah',
+ 'dashboard.timezoneCustomErrorEmpty': 'Masukkan pengenal zona waktu',
+ 'dashboard.timezoneCustomErrorInvalid':
+ 'Zona waktu tidak valid. Gunakan format seperti Europe/Berlin',
+ 'dashboard.timezoneCustomErrorDuplicate': 'Sudah ditambahkan',
+ 'dashboard.emptyTitle': 'Belum ada perjalanan',
+ 'dashboard.emptyText': 'Buat perjalanan pertamamu dan mulai merencanakan!',
+ 'dashboard.emptyButton': 'Buat Perjalanan Pertama',
+ 'dashboard.nextTrip': 'Perjalanan Berikutnya',
+ 'dashboard.shared': 'Dibagikan',
+ 'dashboard.sharedBy': 'Dibagikan oleh {name}',
+ 'dashboard.days': 'Hari',
+ 'dashboard.places': 'Tempat',
+ 'dashboard.members': 'Teman perjalanan',
+ 'dashboard.archive': 'Arsipkan',
+ 'dashboard.copyTrip': 'Salin',
+ 'dashboard.copySuffix': 'salinan',
+ 'dashboard.restore': 'Pulihkan',
+ 'dashboard.archived': 'Diarsipkan',
+ 'dashboard.status.ongoing': 'Sedang berlangsung',
+ 'dashboard.status.today': 'Hari ini',
+ 'dashboard.status.tomorrow': 'Besok',
+ 'dashboard.status.past': 'Sudah lewat',
+ 'dashboard.status.daysLeft': '{count} hari lagi',
+ 'dashboard.toast.loadError': 'Gagal memuat perjalanan',
+ 'dashboard.toast.created': 'Perjalanan berhasil dibuat!',
+ 'dashboard.toast.createError': 'Gagal membuat perjalanan',
+ 'dashboard.toast.updated': 'Perjalanan diperbarui!',
+ 'dashboard.toast.updateError': 'Gagal memperbarui perjalanan',
+ 'dashboard.toast.deleted': 'Perjalanan dihapus',
+ 'dashboard.toast.deleteError': 'Gagal menghapus perjalanan',
+ 'dashboard.toast.archived': 'Perjalanan diarsipkan',
+ 'dashboard.toast.archiveError': 'Gagal mengarsipkan perjalanan',
+ 'dashboard.toast.restored': 'Perjalanan dipulihkan',
+ 'dashboard.toast.restoreError': 'Gagal memulihkan perjalanan',
+ 'dashboard.toast.copied': 'Perjalanan disalin!',
+ 'dashboard.toast.copyError': 'Gagal menyalin perjalanan',
+ 'dashboard.confirm.delete':
+ 'Hapus perjalanan "{title}"? Semua tempat dan rencana akan dihapus permanen.',
+ 'dashboard.editTrip': 'Edit Perjalanan',
+ 'dashboard.createTrip': 'Buat Perjalanan Baru',
+ 'dashboard.tripTitle': 'Judul',
+ 'dashboard.tripTitlePlaceholder': 'mis. Musim Panas di Jepang',
+ 'dashboard.tripDescription': 'Deskripsi',
+ 'dashboard.tripDescriptionPlaceholder': 'Perjalanan ini tentang apa?',
+ 'dashboard.startDate': 'Tanggal Mulai',
+ 'dashboard.endDate': 'Tanggal Selesai',
+ 'dashboard.dayCount': 'Jumlah Hari',
+ 'dashboard.dayCountHint':
+ 'Berapa hari yang ingin direncanakan jika tanggal perjalanan belum diatur.',
+ 'dashboard.noDateHint':
+ 'Belum ada tanggal — 7 hari default akan dibuat. Bisa diubah kapan saja.',
+ 'dashboard.coverImage': 'Gambar Sampul',
+ 'dashboard.addCoverImage': 'Tambah gambar sampul (atau seret & lepas)',
+ 'dashboard.addMembers': 'Teman perjalanan',
+ 'dashboard.addMember': 'Tambah anggota',
+ 'dashboard.coverSaved': 'Gambar sampul tersimpan',
+ 'dashboard.coverUploadError': 'Gagal mengunggah',
+ 'dashboard.coverRemoveError': 'Gagal menghapus',
+ 'dashboard.titleRequired': 'Judul wajib diisi',
+ 'dashboard.endDateError': 'Tanggal selesai harus setelah tanggal mulai',
+ 'dashboard.greeting.morning': 'Selamat pagi,',
+ 'dashboard.greeting.afternoon': 'Selamat siang,',
+ 'dashboard.greeting.evening': 'Selamat malam,',
+ 'dashboard.mobile.liveNow': 'Sedang Berlangsung',
+ 'dashboard.mobile.tripProgress': 'Progres perjalanan',
+ 'dashboard.mobile.daysLeft': '{count} hari lagi',
+ 'dashboard.mobile.places': 'Tempat',
+ 'dashboard.mobile.buddies': 'Teman',
+ 'dashboard.mobile.newTrip': 'Perjalanan Baru',
+ 'dashboard.mobile.currency': 'Mata Uang',
+ 'dashboard.mobile.timezone': 'Zona Waktu',
+ 'dashboard.mobile.upcomingTrips': 'Perjalanan Mendatang',
+ 'dashboard.mobile.yourTrips': 'Perjalananmu',
+ 'dashboard.mobile.trips': 'perjalanan',
+ 'dashboard.mobile.starts': 'Mulai',
+ 'dashboard.mobile.duration': 'Durasi',
+ 'dashboard.mobile.day': 'hari',
+ 'dashboard.mobile.days': 'hari',
+ 'dashboard.mobile.ongoing': 'Sedang berlangsung',
+ 'dashboard.mobile.startsToday': 'Mulai hari ini',
+ 'dashboard.mobile.tomorrow': 'Besok',
+ 'dashboard.mobile.inDays': 'Dalam {count} hari',
+ 'dashboard.mobile.inMonths': 'Dalam {count} bulan',
+ 'dashboard.mobile.completed': 'Selesai',
+ 'dashboard.mobile.currencyConverter': 'Konverter Mata Uang',
+ 'dashboard.filter.planned': 'Direncanakan',
+ 'dashboard.hero.badgeLive': 'SEDANG BERLANGSUNG',
+ 'dashboard.hero.badgeToday': 'MULAI HARI INI',
+ 'dashboard.hero.badgeTomorrow': 'BESOK',
+ 'dashboard.hero.badgeNext': 'BERIKUTNYA',
+ 'dashboard.hero.badgeRecent': 'TERBARU',
+ 'dashboard.hero.tripDates': 'Tanggal perjalanan',
+ 'dashboard.hero.noDates': 'Tanggal belum diatur',
+ 'dashboard.hero.travelerOne': '{count} pelancong',
+ 'dashboard.hero.travelerMany': '{count} pelancong',
+ 'dashboard.hero.destinationOne': '{count} destinasi',
+ 'dashboard.hero.destinationMany': '{count} destinasi',
+ 'dashboard.hero.dayUnitOne': 'hari',
+ 'dashboard.hero.dayUnitMany': 'hari',
+ 'dashboard.hero.dayLeft': 'Hari tersisa',
+ 'dashboard.hero.daysLeft': 'Hari tersisa',
+ 'dashboard.hero.lastDay': 'Hari terakhir',
+ 'dashboard.hero.untilStart': 'Hingga mulai',
+ 'dashboard.hero.startsIn': 'Mulai dalam',
+ 'dashboard.atlas.countriesVisited': 'Atlas · Negara dikunjungi',
+ 'dashboard.atlas.ofTotal': 'dari {total}',
+ 'dashboard.atlas.tripsTotal': 'Total perjalanan',
+ 'dashboard.atlas.placesMapped': '{count} tempat dipetakan',
+ 'dashboard.atlas.daysTraveled': 'Hari perjalanan',
+ 'dashboard.atlas.daysUnit': 'hari',
+ 'dashboard.atlas.acrossAllTrips': 'di semua perjalanan',
+ 'dashboard.atlas.distanceFlown': 'Jarak terbang',
+ 'dashboard.atlas.kmUnit': 'km',
+ 'dashboard.atlas.aroundEquator': '≈ {count}× keliling khatulistiwa',
+ 'dashboard.card.idea': 'Ide',
+ 'dashboard.card.buddyOne': 'Teman',
+ 'dashboard.fx.from': 'Dari',
+ 'dashboard.fx.to': 'Ke',
+ 'dashboard.fx.unavailable': 'Kurs tidak tersedia',
+ 'dashboard.tz.searchPlaceholder': 'Cari zona waktu…',
+ 'dashboard.tz.empty': 'Belum ada zona waktu lain — tambahkan dengan +',
+ 'dashboard.upcoming.title': 'Reservasi mendatang',
+ 'dashboard.upcoming.empty': 'Belum ada yang dipesan.',
+ 'dashboard.confirm.copy.title': 'Salin perjalanan ini?',
+ 'dashboard.confirm.copy.willCopy': 'Akan disalin',
+ 'dashboard.confirm.copy.will1': 'Hari, tempat & penugasan harian',
+ 'dashboard.confirm.copy.will2': 'Akomodasi & reservasi',
+ 'dashboard.confirm.copy.will3': 'Item anggaran & urutan kategori',
+ 'dashboard.confirm.copy.will4': 'Daftar bawaan (belum dicentang)',
+ 'dashboard.confirm.copy.will5': 'Tugas (belum ditugaskan & dicentang)',
+ 'dashboard.confirm.copy.will6': 'Catatan harian',
+ 'dashboard.confirm.copy.wontCopy': 'Tidak akan disalin',
+ 'dashboard.confirm.copy.wont1': 'Kolaborator & penugasan anggota',
+ 'dashboard.confirm.copy.wont2': 'Catatan, jajak pendapat & pesan bersama',
+ 'dashboard.confirm.copy.wont3': 'Berkas & foto',
+ 'dashboard.confirm.copy.wont4': 'Token berbagi',
+ 'dashboard.confirm.copy.confirm': 'Salin perjalanan',
+ 'dashboard.aria.toggleView': 'Ganti tampilan',
+ 'dashboard.aria.filter': 'Filter',
+ 'dashboard.aria.duplicate': 'Duplikat',
+ 'dashboard.aria.refreshRates': 'Segarkan kurs',
+ 'dashboard.aria.swapCurrencies': 'Tukar mata uang',
+ 'dashboard.aria.addTimezone': 'Tambah zona waktu',
+ 'dashboard.aria.removeTimezone': 'Hapus {city}',
+ 'dashboard.dayCountRequired': 'Jumlah hari wajib diisi',
+};
+export default dashboard;
diff --git a/shared/src/i18n/id/day.ts b/shared/src/i18n/id/day.ts
new file mode 100644
index 0000000000000000000000000000000000000000..03e994da8587966fd39bab2d1699db6760ae6a4b
--- /dev/null
+++ b/shared/src/i18n/id/day.ts
@@ -0,0 +1,27 @@
+import type { TranslationStrings } from '../types';
+
+const day: TranslationStrings = {
+ 'day.precipProb': 'Peluang hujan',
+ 'day.precipitation': 'Curah hujan',
+ 'day.wind': 'Angin',
+ 'day.sunrise': 'Matahari terbit',
+ 'day.sunset': 'Matahari terbenam',
+ 'day.hourlyForecast': 'Prakiraan Per Jam',
+ 'day.climateHint':
+ 'Rata-rata historis — prakiraan nyata tersedia dalam 16 hari dari tanggal ini.',
+ 'day.noWeather':
+ 'Data cuaca tidak tersedia. Tambahkan tempat dengan koordinat.',
+ 'day.overview': 'Ikhtisar Harian',
+ 'day.accommodation': 'Akomodasi',
+ 'day.addAccommodation': 'Tambah akomodasi',
+ 'day.hotelDayRange': 'Terapkan ke hari',
+ 'day.noPlacesForHotel': 'Tambahkan tempat ke perjalananmu terlebih dahulu',
+ 'day.allDays': 'Semua',
+ 'day.checkIn': 'Check-in',
+ 'day.checkInUntil': 'Sampai',
+ 'day.checkOut': 'Check-out',
+ 'day.confirmation': 'Konfirmasi',
+ 'day.editAccommodation': 'Edit akomodasi',
+ 'day.reservations': 'Reservasi',
+};
+export default day;
diff --git a/shared/src/i18n/id/dayplan.ts b/shared/src/i18n/id/dayplan.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c4576bb659c05739fcfc1f70b842cb2eeacf22b7
--- /dev/null
+++ b/shared/src/i18n/id/dayplan.ts
@@ -0,0 +1,61 @@
+import type { TranslationStrings } from '../types';
+
+const dayplan: TranslationStrings = {
+ 'dayplan.icsTooltip': 'Ekspor kalender (ICS)',
+ 'dayplan.emptyDay': 'Belum ada tempat yang direncanakan untuk hari ini',
+ 'dayplan.cannotReorderTransport':
+ 'Pemesanan dengan waktu tetap tidak bisa diurutkan ulang',
+ 'dayplan.confirmRemoveTimeTitle': 'Hapus waktu?',
+ 'dayplan.confirmRemoveTimeBody':
+ 'Tempat ini memiliki waktu tetap ({time}). Memindahkannya akan menghapus waktu dan mengizinkan pengurutan bebas.',
+ 'dayplan.confirmRemoveTimeAction': 'Hapus waktu & pindahkan',
+ 'dayplan.confirmDeleteNoteTitle': 'Hapus catatan?',
+ 'dayplan.confirmDeleteNoteBody': 'Catatan ini akan dihapus secara permanen.',
+ 'dayplan.cannotDropOnTimed':
+ 'Item tidak dapat ditempatkan di antara entri yang terikat waktu',
+ 'dayplan.cannotBreakChronology':
+ 'Ini akan merusak urutan kronologis item bertanggal dan pemesanan',
+ 'dayplan.addNote': 'Tambah Catatan',
+ 'dayplan.editNote': 'Edit Catatan',
+ 'dayplan.noteAdd': 'Tambah Catatan',
+ 'dayplan.noteEdit': 'Edit Catatan',
+ 'dayplan.noteTitle': 'Catatan',
+ 'dayplan.noteSubtitle': 'Catatan Harian',
+ 'dayplan.totalCost': 'Total Biaya',
+ 'dayplan.days': 'Hari',
+ 'dayplan.dayN': 'Hari {n}',
+ 'dayplan.calculating': 'Menghitung...',
+ 'dayplan.route': 'Rute',
+ 'dayplan.optimize': 'Optimalkan',
+ 'dayplan.optimized': 'Rute dioptimalkan',
+ 'dayplan.routeError': 'Gagal menghitung rute',
+ 'dayplan.toast.needTwoPlaces':
+ 'Diperlukan minimal dua tempat untuk optimasi rute',
+ 'dayplan.toast.routeOptimized': 'Rute dioptimalkan',
+ 'dayplan.toast.routeOptimizedFromHotel':
+ 'Rute dioptimalkan dari akomodasimu',
+ 'dayplan.toast.noGeoPlaces':
+ 'Tidak ditemukan tempat dengan koordinat untuk kalkulasi rute',
+ 'dayplan.confirmed': 'Dikonfirmasi',
+ 'dayplan.pendingRes': 'Menunggu',
+ 'dayplan.pdf': 'PDF',
+ 'dayplan.pdfTooltip': 'Ekspor rencana hari sebagai PDF',
+ 'dayplan.pdfError': 'Gagal mengekspor PDF',
+ 'dayplan.mobile.addPlace': 'Tambah Tempat',
+ 'dayplan.mobile.searchPlaces': 'Cari tempat...',
+ 'dayplan.mobile.allAssigned': 'Semua tempat sudah ditugaskan',
+ 'dayplan.mobile.noMatch': 'Tidak ditemukan',
+ 'dayplan.mobile.createNew': 'Buat tempat baru',
+ 'dayplan.expandAll': 'Expand all days', // en-fallback
+ 'dayplan.collapseAll': 'Collapse all days', // en-fallback
+ 'dayplan.reorderDays': 'Atur ulang hari',
+ 'dayplan.reorderTitle': 'Atur ulang hari',
+ 'dayplan.reorderHint': 'Tempat, catatan, dan pesanan pada suatu hari ikut berpindah.',
+ 'dayplan.addDay': 'Tambah hari',
+ 'dayplan.moveUp': 'Pindah ke atas',
+ 'dayplan.moveDown': 'Pindah ke bawah',
+ 'dayplan.reorderUndo': 'Atur ulang hari',
+ 'dayplan.reorderError': 'Gagal mengatur ulang hari',
+ 'dayplan.addDayError': 'Gagal menambah hari',
+};
+export default dayplan;
diff --git a/shared/src/i18n/id/externalNotifications.ts b/shared/src/i18n/id/externalNotifications.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3ac14b94f32c80211a2f26edfe33e6d35bdd5669
--- /dev/null
+++ b/shared/src/i18n/id/externalNotifications.ts
@@ -0,0 +1,64 @@
+import type { NotificationLocale } from '../externalNotifications/types';
+
+const id: NotificationLocale = {
+ email: {
+ footer:
+ 'Anda menerima ini karena Anda telah mengaktifkan notifikasi di TREK.',
+ manage: 'Kelola preferensi di Pengaturan',
+ madeWith: 'Dibuat dengan',
+ openTrek: 'Buka TREK',
+ },
+ events: {
+ trip_invite: (p) => ({
+ title: `Undangan perjalanan: "${p.trip}"`,
+ body: `${p.actor} mengundang ${p.invitee || 'seorang anggota'} ke perjalanan "${p.trip}".`,
+ }),
+ booking_change: (p) => ({
+ title: `Pemesanan baru: ${p.booking}`,
+ body: `${p.actor} menambahkan "${p.booking}" (${p.type}) baru ke "${p.trip}".`,
+ }),
+ trip_reminder: (p) => ({
+ title: `Pengingat perjalanan: ${p.trip}`,
+ body: `Perjalanan Anda "${p.trip}" akan segera tiba!`,
+ }),
+ todo_due: (p) => ({
+ title: `Tugas jatuh tempo: ${p.todo}`,
+ body: `"${p.todo}" di "${p.trip}" jatuh tempo pada ${p.due}.`,
+ }),
+ vacay_invite: (p) => ({
+ title: 'Undangan Penggabungan Vacay',
+ body: `${p.actor} mengundang Anda untuk menggabungkan rencana liburan. Buka TREK untuk menerima atau menolak.`,
+ }),
+ photos_shared: (p) => ({
+ title: `${p.count} foto dibagikan`,
+ body: `${p.actor} membagikan ${p.count} foto di "${p.trip}".`,
+ }),
+ collab_message: (p) => ({
+ title: `Pesan baru di "${p.trip}"`,
+ body: `${p.actor}: ${p.preview}`,
+ }),
+ packing_tagged: (p) => ({
+ title: `Pengepakan: ${p.category}`,
+ body: `${p.actor} menugaskan Anda ke kategori "${p.category}" di "${p.trip}".`,
+ }),
+ version_available: (p) => ({
+ title: 'Versi TREK baru tersedia',
+ body: `TREK ${p.version} sekarang tersedia. Kunjungi panel admin untuk memperbarui.`,
+ }),
+ synology_session_cleared: () => ({
+ title: 'Sesi Synology dihapus',
+ body: 'Akun atau URL Synology Anda berubah. Anda telah keluar dari Synology Photos.',
+ }),
+ },
+ passwordReset: {
+ subject: 'Setel ulang kata sandi Anda',
+ greeting: 'Halo',
+ body: 'Kami menerima permintaan untuk menyetel ulang kata sandi akun TREK Anda. Klik tombol di bawah untuk menetapkan kata sandi baru.',
+ ctaIntro: 'Setel ulang kata sandi',
+ expiry: 'Tautan ini kedaluwarsa dalam 60 menit.',
+ ignore:
+ 'Jika Anda tidak meminta ini, Anda dapat mengabaikan email ini — kata sandi Anda tidak akan berubah.',
+ },
+};
+
+export default id;
diff --git a/shared/src/i18n/id/files.ts b/shared/src/i18n/id/files.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e73c981f6635967b58b4801669305a9d2ffdbaf0
--- /dev/null
+++ b/shared/src/i18n/id/files.ts
@@ -0,0 +1,62 @@
+import type { TranslationStrings } from '../types';
+
+const files: TranslationStrings = {
+ 'files.title': 'File',
+ 'files.pageTitle': 'File & Dokumen',
+ 'files.subtitle': '{count} file untuk {trip}',
+ 'files.download': 'Unduh',
+ 'files.openError': 'Tidak dapat membuka file',
+ 'files.downloadPdf': 'Unduh PDF',
+ 'files.count': '{count} file',
+ 'files.countSingular': '1 berkas',
+ 'files.uploaded': '{count} diunggah',
+ 'files.uploadError': 'Gagal mengunggah',
+ 'files.dropzone': 'Jatuhkan file di sini',
+ 'files.dropzoneHint': 'atau klik untuk memilih',
+ 'files.allowedTypes':
+ 'Gambar, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Maks 50 MB',
+ 'files.uploading': 'Mengunggah...',
+ 'files.filterAll': 'Semua',
+ 'files.filterPdf': 'PDFs',
+ 'files.filterImages': 'Gambar',
+ 'files.filterDocs': 'Dokumen',
+ 'files.filterCollab': 'Catatan Collab',
+ 'files.sourceCollab': 'Dari Catatan Collab',
+ 'files.empty': 'Belum ada file',
+ 'files.emptyHint': 'Unggah file untuk melampirkannya ke perjalananmu',
+ 'files.openTab': 'Buka di tab baru',
+ 'files.confirm.delete': 'Yakin ingin menghapus file ini?',
+ 'files.toast.deleted': 'File dihapus',
+ 'files.toast.deleteError': 'Gagal menghapus file',
+ 'files.sourcePlan': 'Rencana Harian',
+ 'files.sourceBooking': 'Pemesanan',
+ 'files.sourceTransport': 'Transportasi',
+ 'files.attach': 'Lampirkan',
+ 'files.pasteHint': 'Kamu juga bisa menempel gambar dari clipboard (Ctrl+V)',
+ 'files.trash': 'Sampah',
+ 'files.trashEmpty': 'Sampah kosong',
+ 'files.emptyTrash': 'Kosongkan Sampah',
+ 'files.restore': 'Pulihkan',
+ 'files.star': 'Bintang',
+ 'files.unstar': 'Hapus bintang',
+ 'files.assign': 'Tugaskan',
+ 'files.assignTitle': 'Tugaskan File',
+ 'files.assignPlace': 'Tempat',
+ 'files.assignBooking': 'Pemesanan',
+ 'files.assignTransport': 'Transportasi',
+ 'files.unassigned': 'Tidak ditugaskan',
+ 'files.unlink': 'Hapus tautan',
+ 'files.toast.trashed': 'Dipindahkan ke sampah',
+ 'files.toast.restored': 'File dipulihkan',
+ 'files.toast.trashEmptied': 'Sampah dikosongkan',
+ 'files.toast.assigned': 'File ditugaskan',
+ 'files.toast.assignError': 'Penugasan gagal',
+ 'files.toast.restoreError': 'Pemulihan gagal',
+ 'files.confirm.permanentDelete':
+ 'Hapus file ini secara permanen? Tindakan ini tidak bisa dibatalkan.',
+ 'files.confirm.emptyTrash':
+ 'Hapus semua file di sampah secara permanen? Tindakan ini tidak bisa dibatalkan.',
+ 'files.noteLabel': 'Catatan',
+ 'files.notePlaceholder': 'Tambahkan catatan...',
+};
+export default files;
diff --git a/shared/src/i18n/id/index.ts b/shared/src/i18n/id/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..77aeb6a09fb62e2522e8b4c04e4169a94c2f8d99
--- /dev/null
+++ b/shared/src/i18n/id/index.ts
@@ -0,0 +1,86 @@
+import admin from './admin';
+import airport from './airport';
+import atlas from './atlas';
+import backup from './backup';
+import budget from './budget';
+import categories from './categories';
+import collab from './collab';
+import common from './common';
+import dashboard from './dashboard';
+import day from './day';
+import dayplan from './dayplan';
+import files from './files';
+import inspector from './inspector';
+import journey from './journey';
+import login from './login';
+import map from './map';
+import members from './members';
+import memories from './memories';
+import nav from './nav';
+import notif from './notif';
+import notifications from './notifications';
+import oauth from './oauth';
+import packing from './packing';
+import pdf from './pdf';
+import perm from './perm';
+import photos from './photos';
+import places from './places';
+import planner from './planner';
+import register from './register';
+import reservations from './reservations';
+import settings from './settings';
+import share from './share';
+import shared from './shared';
+import stats from './stats';
+import system_notice from './system_notice';
+import todo from './todo';
+import transport from './transport';
+import trip from './trip';
+import trips from './trips';
+import undo from './undo';
+import vacay from './vacay';
+
+const locale = {
+ ...common,
+ ...trips,
+ ...nav,
+ ...dashboard,
+ ...settings,
+ ...admin,
+ ...dayplan,
+ ...share,
+ ...shared,
+ ...login,
+ ...register,
+ ...vacay,
+ ...atlas,
+ ...trip,
+ ...places,
+ ...inspector,
+ ...reservations,
+ ...airport,
+ ...map,
+ ...budget,
+ ...files,
+ ...packing,
+ ...members,
+ ...categories,
+ ...backup,
+ ...photos,
+ ...pdf,
+ ...planner,
+ ...stats,
+ ...day,
+ ...memories,
+ ...collab,
+ ...perm,
+ ...undo,
+ ...notifications,
+ ...todo,
+ ...notif,
+ ...journey,
+ ...oauth,
+ ...system_notice,
+ ...transport,
+};
+export default locale;
diff --git a/shared/src/i18n/id/inspector.ts b/shared/src/i18n/id/inspector.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7a4ef4209b54355acec254c25c706323228b84b3
--- /dev/null
+++ b/shared/src/i18n/id/inspector.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const inspector: TranslationStrings = {
+ 'inspector.opened': 'Buka',
+ 'inspector.closed': 'Tutup',
+ 'inspector.openingHours': 'Jam Buka',
+ 'inspector.showHours': 'Tampilkan jam buka',
+ 'inspector.files': 'File',
+ 'inspector.filesCount': '{count} file',
+ 'inspector.remove': 'Hapus',
+ 'inspector.removeFromDay': 'Hapus dari Hari',
+ 'inspector.addToDay': 'Tambah ke Hari',
+ 'inspector.confirmedRes': 'Reservasi Dikonfirmasi',
+ 'inspector.pendingRes': 'Reservasi Menunggu',
+ 'inspector.google': 'Buka di Google Maps',
+ 'inspector.website': 'Buka Situs Web',
+ 'inspector.addRes': 'Reservasi',
+ 'inspector.editRes': 'Edit Reservasi',
+ 'inspector.participants': 'Peserta',
+ 'inspector.trackStats': 'Statistik Jalur',
+};
+export default inspector;
diff --git a/shared/src/i18n/id/journey.ts b/shared/src/i18n/id/journey.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2a262d5c0fa87406c0f37821687278f1ceb2aff7
--- /dev/null
+++ b/shared/src/i18n/id/journey.ts
@@ -0,0 +1,246 @@
+import type { TranslationStrings } from '../types';
+
+const journey: TranslationStrings = {
+ 'journey.search.placeholder': 'Cari perjalanan…',
+ 'journey.search.noResults':
+ 'Tidak ada perjalanan yang cocok dengan "{query}"',
+ 'journey.title': 'Journey',
+ 'journey.subtitle': 'Lacak perjalananmu saat terjadi',
+ 'journey.new': 'Journey Baru',
+ 'journey.create': 'Buat',
+ 'journey.titlePlaceholder': 'Ke mana kamu pergi?',
+ 'journey.empty': 'Belum ada journey',
+ 'journey.emptyHint': 'Mulai mendokumentasikan perjalananmu berikutnya',
+ 'journey.deleted': 'Journey dihapus',
+ 'journey.createError': 'Tidak dapat membuat journey',
+ 'journey.deleteError': 'Tidak dapat menghapus journey',
+ 'journey.deleteConfirmTitle': 'Hapus',
+ 'journey.deleteConfirmMessage':
+ 'Hapus "{title}"? Tindakan ini tidak dapat dibatalkan.',
+ 'journey.deleteConfirmGeneric': 'Apakah kamu yakin ingin menghapus ini?',
+ 'journey.notFound': 'Journey tidak ditemukan',
+ 'journey.photos': 'Foto',
+ 'journey.timelineEmpty': 'Belum ada persinggahan',
+ 'journey.timelineEmptyHint':
+ 'Tambahkan check-in atau tulis entri jurnal untuk memulai',
+ 'journey.status.draft': 'Draf',
+ 'journey.status.active': 'Aktif',
+ 'journey.status.completed': 'Selesai',
+ 'journey.status.upcoming': 'Mendatang',
+ 'journey.status.archived': 'Diarsipkan',
+ 'journey.checkin.add': 'Check in',
+ 'journey.checkin.namePlaceholder': 'Nama lokasi',
+ 'journey.checkin.notesPlaceholder': 'Catatan (opsional)',
+ 'journey.checkin.save': 'Simpan',
+ 'journey.checkin.error': 'Tidak dapat menyimpan check-in',
+ 'journey.entry.add': 'Jurnal',
+ 'journey.entry.edit': 'Edit entri',
+ 'journey.entry.titlePlaceholder': 'Judul (opsional)',
+ 'journey.entry.bodyPlaceholder': 'Apa yang terjadi hari ini?',
+ 'journey.entry.save': 'Simpan',
+ 'journey.entry.error': 'Tidak dapat menyimpan entri',
+ 'journey.photo.add': 'Foto',
+ 'journey.photo.uploadError': 'Unggah gagal',
+ 'journey.share.share': 'Bagikan',
+ 'journey.share.public': 'Publik',
+ 'journey.share.linkCopied': 'Tautan publik disalin',
+ 'journey.share.disabled': 'Berbagi publik dinonaktifkan',
+ 'journey.editor.titlePlaceholder': 'Beri nama momen ini...',
+ 'journey.editor.bodyPlaceholder': 'Ceritakan kisah hari ini...',
+ 'journey.editor.placePlaceholder': 'Lokasi (opsional)',
+ 'journey.editor.tagsPlaceholder':
+ 'Tag: permata tersembunyi, makan terbaik, wajib dikunjungi lagi...',
+ 'journey.visibility.private': 'Pribadi',
+ 'journey.visibility.shared': 'Dibagikan',
+ 'journey.visibility.public': 'Publik',
+ 'journey.emptyState.title': 'Kisahmu dimulai di sini',
+ 'journey.emptyState.subtitle':
+ 'Check in di suatu tempat atau tulis entri jurnal pertamamu',
+ 'journey.frontpage.subtitle':
+ 'Ubah perjalananmu menjadi kisah yang tak terlupakan',
+ 'journey.frontpage.createJourney': 'Buat Journey',
+ 'journey.frontpage.activeJourney': 'Journey Aktif',
+ 'journey.frontpage.allJourneys': 'Semua Journey',
+ 'journey.frontpage.journeys': 'journey',
+ 'journey.frontpage.createNew': 'Buat Journey baru',
+ 'journey.frontpage.createNewSub':
+ 'Pilih perjalanan, tulis cerita, bagikan petualanganmu',
+ 'journey.frontpage.live': 'Langsung',
+ 'journey.frontpage.synced': 'Tersinkron',
+ 'journey.frontpage.continueWriting': 'Lanjutkan menulis',
+ 'journey.frontpage.updated': 'Diperbarui {time}',
+ 'journey.frontpage.suggestionLabel': 'Perjalanan baru saja selesai',
+ 'journey.frontpage.suggestionText':
+ 'Ubah {title} menjadi Journey',
+ 'journey.frontpage.dismiss': 'Tutup',
+ 'journey.frontpage.journeyName': 'Nama Journey',
+ 'journey.frontpage.namePlaceholder': 'mis. Asia Tenggara 2026',
+ 'journey.frontpage.selectTrips': 'Pilih Perjalanan',
+ 'journey.frontpage.tripsSelected': 'perjalanan dipilih',
+ 'journey.frontpage.trips': 'perjalanan',
+ 'journey.frontpage.placesImported': 'tempat akan diimpor',
+ 'journey.frontpage.places': 'tempat',
+ 'journey.detail.backToJourney': 'Kembali ke Journey',
+ 'journey.detail.syncedWithTrips': 'Tersinkron dengan Perjalanan',
+ 'journey.detail.addEntry': 'Tambah Entri',
+ 'journey.detail.newEntry': 'Entri Baru',
+ 'journey.detail.editEntry': 'Edit Entri',
+ 'journey.detail.noEntries': 'Belum ada entri',
+ 'journey.detail.noEntriesHint':
+ 'Tambahkan perjalanan untuk mulai dengan entri kerangka',
+ 'journey.detail.noPhotos': 'Belum ada foto',
+ 'journey.detail.noPhotosHint':
+ 'Unggah foto ke entri atau jelajahi galeri Immich/Synology-mu',
+ 'journey.detail.journeyStats': 'Statistik Journey',
+ 'journey.detail.syncedTrips': 'Perjalanan Tersinkron',
+ 'journey.detail.noTripsLinked': 'Belum ada perjalanan yang ditautkan',
+ 'journey.detail.contributors': 'Kontributor',
+ 'journey.detail.readMore': 'Baca selengkapnya',
+ 'journey.detail.prosCons': 'Pro & Kontra',
+ 'journey.detail.photos': 'foto',
+ 'journey.detail.day': 'Hari {number}',
+ 'journey.detail.places': 'tempat',
+ 'journey.stats.days': 'Hari',
+ 'journey.stats.cities': 'Kota',
+ 'journey.stats.entries': 'Entri',
+ 'journey.stats.photos': 'Foto',
+ 'journey.stats.places': 'Tempat',
+ 'journey.skeletons.show': 'Tampilkan saran',
+ 'journey.skeletons.hide': 'Sembunyikan saran',
+ 'journey.verdict.lovedIt': 'Sangat suka',
+ 'journey.verdict.couldBeBetter': 'Bisa lebih baik',
+ 'journey.synced.places': 'tempat',
+ 'journey.synced.synced': 'tersinkron',
+ 'journey.editor.discardChangesConfirm':
+ 'Anda memiliki perubahan yang belum disimpan. Buang?',
+ 'journey.editor.uploadFailed': 'Gagal mengunggah foto',
+ 'journey.editor.uploadPhotos': 'Unggah foto',
+ 'journey.editor.uploading': 'Mengunggah...',
+ 'journey.editor.uploadingProgress': 'Mengunggah {done}/{total}…',
+ 'journey.editor.uploadPartialFailed':
+ '{failed} dari {total} foto gagal — simpan lagi untuk mencoba ulang',
+ 'journey.editor.fromGallery': 'Dari Galeri',
+ 'journey.editor.allPhotosAdded': 'Semua foto sudah ditambahkan',
+ 'journey.editor.writeStory': 'Tulis kisahmu...',
+ 'journey.editor.prosCons': 'Pro & Kontra',
+ 'journey.editor.pros': 'Pro',
+ 'journey.editor.cons': 'Kontra',
+ 'journey.editor.proPlaceholder': 'Sesuatu yang bagus...',
+ 'journey.editor.conPlaceholder': 'Tidak begitu bagus...',
+ 'journey.editor.addAnother': 'Tambah lagi',
+ 'journey.editor.date': 'Tanggal',
+ 'journey.editor.location': 'Lokasi',
+ 'journey.editor.searchLocation': 'Cari lokasi...',
+ 'journey.editor.mood': 'Suasana Hati',
+ 'journey.editor.weather': 'Cuaca',
+ 'journey.editor.photoFirst': '1.',
+ 'journey.editor.makeFirst': 'Jadikan ke-1',
+ 'journey.editor.searching': 'Mencari...',
+ 'journey.mood.amazing': 'Luar biasa',
+ 'journey.mood.good': 'Baik',
+ 'journey.mood.neutral': 'Biasa',
+ 'journey.mood.rough': 'Berat',
+ 'journey.weather.sunny': 'Cerah',
+ 'journey.weather.partly': 'Berawan sebagian',
+ 'journey.weather.cloudy': 'Mendung',
+ 'journey.weather.rainy': 'Hujan',
+ 'journey.weather.stormy': 'Badai',
+ 'journey.weather.cold': 'Bersalju',
+ 'journey.trips.linkTrip': 'Tautkan Perjalanan',
+ 'journey.trips.searchTrip': 'Cari Perjalanan',
+ 'journey.trips.searchPlaceholder': 'Nama perjalanan atau tujuan...',
+ 'journey.trips.noTripsAvailable': 'Tidak ada perjalanan tersedia',
+ 'journey.trips.link': 'Tautkan',
+ 'journey.trips.tripLinked': 'Perjalanan ditautkan',
+ 'journey.trips.linkFailed': 'Gagal menautkan perjalanan',
+ 'journey.trips.addTrip': 'Tambah Perjalanan',
+ 'journey.trips.unlinkTrip': 'Lepas Tautan Perjalanan',
+ 'journey.trips.unlinkMessage':
+ 'Lepas tautan "{title}"? Semua entri dan foto yang tersinkron dari perjalanan ini akan dihapus permanen. Tindakan ini tidak dapat dibatalkan.',
+ 'journey.trips.unlink': 'Lepas Tautan',
+ 'journey.trips.tripUnlinked': 'Tautan perjalanan dilepas',
+ 'journey.trips.unlinkFailed': 'Gagal melepas tautan perjalanan',
+ 'journey.trips.noTripsLinkedSettings': 'Tidak ada perjalanan yang ditautkan',
+ 'journey.contributors.invite': 'Undang Kontributor',
+ 'journey.contributors.searchUser': 'Cari Pengguna',
+ 'journey.contributors.searchPlaceholder': 'Nama pengguna atau email...',
+ 'journey.contributors.noUsers': 'Tidak ada pengguna ditemukan',
+ 'journey.contributors.role': 'Peran',
+ 'journey.contributors.added': 'Kontributor ditambahkan',
+ 'journey.contributors.addFailed': 'Gagal menambahkan kontributor',
+ 'journey.share.publicShare': 'Berbagi Publik',
+ 'journey.share.createLink': 'Buat tautan berbagi',
+ 'journey.share.linkCreated': 'Tautan berbagi dibuat',
+ 'journey.share.createFailed': 'Gagal membuat tautan',
+ 'journey.share.copy': 'Salin',
+ 'journey.share.copied': 'Disalin!',
+ 'journey.share.timeline': 'Linimasa',
+ 'journey.share.gallery': 'Galeri',
+ 'journey.share.map': 'Peta',
+ 'journey.share.removeLink': 'Hapus tautan berbagi',
+ 'journey.share.linkDeleted': 'Tautan berbagi dihapus',
+ 'journey.share.deleteFailed': 'Gagal menghapus',
+ 'journey.share.updateFailed': 'Gagal memperbarui',
+ 'journey.invite.role': 'Peran',
+ 'journey.invite.viewer': 'Penonton',
+ 'journey.invite.editor': 'Editor',
+ 'journey.invite.invite': 'Undang',
+ 'journey.invite.inviting': 'Mengundang...',
+ 'journey.settings.title': 'Pengaturan Journey',
+ 'journey.settings.coverImage': 'Gambar Sampul',
+ 'journey.settings.changeCover': 'Ubah sampul',
+ 'journey.settings.addCover': 'Tambah gambar sampul',
+ 'journey.settings.name': 'Nama',
+ 'journey.settings.subtitle': 'Subjudul',
+ 'journey.settings.subtitlePlaceholder': 'mis. Thailand, Vietnam & Kamboja',
+ 'journey.settings.endJourney': 'Arsipkan Perjalanan',
+ 'journey.settings.reopenJourney': 'Pulihkan Perjalanan',
+ 'journey.settings.archived': 'Perjalanan diarsipkan',
+ 'journey.settings.reopened': 'Perjalanan dibuka kembali',
+ 'journey.settings.endDescription':
+ 'Menyembunyikan lencana Langsung. Anda dapat membuka kembali kapan saja.',
+ 'journey.settings.delete': 'Hapus',
+ 'journey.settings.deleteJourney': 'Hapus Journey',
+ 'journey.settings.deleteMessage':
+ 'Hapus "{title}"? Semua entri dan foto akan hilang.',
+ 'journey.settings.saved': 'Pengaturan disimpan',
+ 'journey.settings.saveFailed': 'Gagal menyimpan',
+ 'journey.settings.coverUpdated': 'Sampul diperbarui',
+ 'journey.settings.coverFailed': 'Unggah gagal',
+ 'journey.settings.failedToDelete': 'Gagal menghapus',
+ 'journey.entries.deleteTitle': 'Hapus Entri',
+ 'journey.photosUploaded': '{count} foto diunggah',
+ 'journey.photosUploadFailed': 'Beberapa foto gagal diunggah',
+ 'journey.photosAdded': '{count} foto ditambahkan',
+ 'journey.public.notFound': 'Tidak Ditemukan',
+ 'journey.public.notFoundMessage':
+ 'Journey ini tidak ada atau tautan telah kedaluwarsa.',
+ 'journey.public.readOnly': 'Hanya baca · Journey Publik',
+ 'journey.public.tagline': 'Travel Resource & Exploration Kit',
+ 'journey.public.sharedVia': 'Dibagikan melalui',
+ 'journey.public.madeWith': 'Dibuat dengan',
+ 'journey.pdf.journeyBook': 'Buku Journey',
+ 'journey.pdf.madeWith': 'Dibuat dengan TREK',
+ 'journey.pdf.day': 'Hari',
+ 'journey.pdf.theEnd': 'Tamat',
+ 'journey.pdf.saveAsPdf': 'Simpan sebagai PDF',
+ 'journey.pdf.pages': 'halaman',
+ 'journey.picker.tripPeriod': 'Periode Perjalanan',
+ 'journey.picker.dateRange': 'Rentang Tanggal',
+ 'journey.picker.allPhotos': 'Semua Foto',
+ 'journey.picker.albums': 'Album',
+ 'journey.picker.selected': 'dipilih',
+ 'journey.picker.addTo': 'Tambahkan ke',
+ 'journey.picker.newGallery': 'Galeri Baru',
+ 'journey.picker.selectAll': 'Pilih semua',
+ 'journey.picker.deselectAll': 'Batalkan semua',
+ 'journey.picker.noAlbums': 'Tidak ada album ditemukan',
+ 'journey.picker.selectDate': 'Pilih tanggal',
+ 'journey.picker.search': 'Cari',
+ 'journey.detail.journeyTab': 'Journey', // en-fallback
+ 'journey.contributors.remove': 'Remove contributor', // en-fallback
+ 'journey.contributors.removeConfirm': 'Remove {username} from this journey?', // en-fallback
+ 'journey.contributors.removed': 'Contributor removed', // en-fallback
+ 'journey.contributors.removeFailed': 'Failed to remove contributor', // en-fallback
+};
+export default journey;
diff --git a/shared/src/i18n/id/login.ts b/shared/src/i18n/id/login.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3b2e31b4020479cdaa7a13a79d7e9d6ec2df7fd7
--- /dev/null
+++ b/shared/src/i18n/id/login.ts
@@ -0,0 +1,100 @@
+import type { TranslationStrings } from '../types';
+
+const login: TranslationStrings = {
+ 'login.error': 'Login gagal. Periksa kembali kredensial kamu.',
+ 'login.tagline': 'Perjalananmu.\nRencanamu.',
+ 'login.description':
+ 'Rencanakan perjalanan bersama dengan peta interaktif, anggaran, dan sinkronisasi real-time.',
+ 'login.features.maps': 'Peta Interaktif',
+ 'login.features.mapsDesc': 'Google Places, rute & pengelompokan',
+ 'login.features.realtime': 'Sinkronisasi Real-Time',
+ 'login.features.realtimeDesc': 'Rencanakan bersama via WebSocket',
+ 'login.features.budget': 'Pelacak Anggaran',
+ 'login.features.budgetDesc': 'Kategori, grafik & biaya per orang',
+ 'login.features.collab': 'Kolaborasi',
+ 'login.features.collabDesc': 'Multi-pengguna dengan perjalanan bersama',
+ 'login.features.packing': 'Daftar Packing',
+ 'login.features.packingDesc': 'Kategori, progres & saran',
+ 'login.features.bookings': 'Reservasi',
+ 'login.features.bookingsDesc': 'Penerbangan, hotel, restoran & lainnya',
+ 'login.features.files': 'Dokumen',
+ 'login.features.filesDesc': 'Unggah & kelola dokumen',
+ 'login.features.routes': 'Rute Cerdas',
+ 'login.features.routesDesc': 'Optimasi otomatis & ekspor ke Google Maps',
+ 'login.selfHosted': 'Self-hosted · Open Source · Datamu tetap milikmu',
+ 'login.title': 'Masuk',
+ 'login.subtitle': 'Selamat datang kembali',
+ 'login.signingIn': 'Sedang masuk…',
+ 'login.signIn': 'Masuk',
+ 'login.createAdmin': 'Buat Akun Admin',
+ 'login.createAdminHint': 'Siapkan akun admin pertama untuk TREK.',
+ 'login.setNewPassword': 'Atur Kata Sandi Baru',
+ 'login.setNewPasswordHint':
+ 'Kamu harus mengganti kata sandi sebelum melanjutkan.',
+ 'login.createAccount': 'Buat Akun',
+ 'login.createAccountHint': 'Daftarkan akun baru.',
+ 'login.creating': 'Membuat…',
+ 'login.noAccount': 'Belum punya akun?',
+ 'login.hasAccount': 'Sudah punya akun?',
+ 'login.register': 'Daftar',
+ 'login.emailPlaceholder': 'kamu@email.com',
+ 'login.username': 'Nama pengguna',
+ 'login.oidc.registrationDisabled':
+ 'Pendaftaran dinonaktifkan. Hubungi administrator kamu.',
+ 'login.oidc.noEmail': 'Tidak ada email yang diterima dari penyedia.',
+ 'login.oidc.tokenFailed': 'Autentikasi gagal.',
+ 'login.oidc.invalidState': 'Sesi tidak valid. Coba lagi.',
+ 'login.demoFailed': 'Login demo gagal',
+ 'login.oidcSignIn': 'Masuk dengan {name}',
+ 'login.oidcOnly':
+ 'Autentikasi kata sandi dinonaktifkan. Masuk menggunakan penyedia SSO kamu.',
+ 'login.oidcLoggedOut':
+ 'Kamu telah keluar. Masuk kembali menggunakan penyedia SSO kamu.',
+ 'login.demoHint': 'Coba demo — tidak perlu registrasi',
+ 'login.mfaTitle': 'Autentikasi dua faktor',
+ 'login.mfaSubtitle': 'Masukkan kode 6 digit dari aplikasi autentikator kamu.',
+ 'login.mfaCodeLabel': 'Kode verifikasi',
+ 'login.mfaCodeRequired': 'Masukkan kode dari aplikasi autentikator kamu.',
+ 'login.mfaHint':
+ 'Buka Google Authenticator, Authy, atau aplikasi TOTP lainnya.',
+ 'login.mfaBack': '← Kembali ke halaman masuk',
+ 'login.mfaVerify': 'Verifikasi',
+ 'login.invalidInviteLink':
+ 'Tautan undangan tidak valid atau sudah kedaluwarsa',
+ 'login.oidcFailed': 'Login OIDC gagal',
+ 'login.usernameRequired': 'Nama pengguna wajib diisi',
+ 'login.passwordMinLength': 'Kata sandi minimal 8 karakter',
+ 'login.forgotPassword': 'Lupa kata sandi?',
+ 'login.rememberMe': 'Ingat saya',
+ 'login.forgotPasswordTitle': 'Setel ulang kata sandi',
+ 'login.forgotPasswordBody':
+ 'Masukkan alamat email akunmu. Jika akun ada, kami akan mengirim tautan reset.',
+ 'login.forgotPasswordSubmit': 'Kirim tautan',
+ 'login.forgotPasswordSentTitle': 'Periksa email kamu',
+ 'login.forgotPasswordSentBody':
+ 'Jika ada akun dengan email tersebut, tautannya sedang dikirim. Berlaku 60 menit.',
+ 'login.forgotPasswordSmtpHintOff':
+ 'Catatan: administrator belum mengonfigurasi SMTP, jadi tautan reset akan ditulis ke konsol server alih-alih dikirim lewat email.',
+ 'login.backToLogin': 'Kembali ke login',
+ 'login.newPassword': 'Kata sandi baru',
+ 'login.confirmPassword': 'Konfirmasi kata sandi baru',
+ 'login.passwordsDontMatch': 'Kata sandi tidak cocok',
+ 'login.mfaCode': 'Kode 2FA',
+ 'login.resetPasswordTitle': 'Tetapkan kata sandi baru',
+ 'login.resetPasswordBody':
+ 'Pilih kata sandi kuat yang belum pernah kamu pakai di sini. Minimal 8 karakter.',
+ 'login.resetPasswordMfaBody':
+ 'Masukkan kode 2FA atau kode cadangan untuk menyelesaikan reset.',
+ 'login.resetPasswordSubmit': 'Setel ulang kata sandi',
+ 'login.resetPasswordVerify': 'Verifikasi & setel ulang',
+ 'login.resetPasswordSuccessTitle': 'Kata sandi diperbarui',
+ 'login.resetPasswordSuccessBody':
+ 'Sekarang kamu bisa login dengan kata sandi baru.',
+ 'login.resetPasswordInvalidLink': 'Tautan tidak valid',
+ 'login.resetPasswordInvalidLinkBody':
+ 'Tautan hilang atau rusak. Minta tautan baru untuk melanjutkan.',
+ 'login.resetPasswordFailed': 'Reset gagal. Tautan mungkin sudah kedaluwarsa.',
+ 'login.passkey.signIn': 'Masuk dengan passkey',
+ 'login.passkey.failed': 'Masuk dengan passkey gagal. Silakan coba lagi.',
+};
+export default login;
diff --git a/shared/src/i18n/id/map.ts b/shared/src/i18n/id/map.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2bfbc188f0a6bd962528eb7a6e0cd071ff5db4b3
--- /dev/null
+++ b/shared/src/i18n/id/map.ts
@@ -0,0 +1,17 @@
+import type { TranslationStrings } from '../types';
+
+const map: TranslationStrings = {
+ 'map.connections': 'Koneksi',
+ 'map.showConnections': 'Tampilkan rute pemesanan',
+ 'map.hideConnections': 'Sembunyikan rute pemesanan',
+ 'poi.searchThisArea': 'Cari di area ini',
+ 'poi.cat.restaurants': 'Restoran',
+ 'poi.cat.cafes': 'Kafe',
+ 'poi.cat.bars': 'Bar & hiburan malam',
+ 'poi.cat.hotels': 'Penginapan',
+ 'poi.cat.sights': 'Tempat wisata',
+ 'poi.cat.museums': 'Museum & budaya',
+ 'poi.cat.nature': 'Alam & taman',
+ 'poi.cat.activities': 'Aktivitas',
+};
+export default map;
diff --git a/shared/src/i18n/id/members.ts b/shared/src/i18n/id/members.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eebdb9f0a667b4fbc7bb0efe71e740bcb19f0812
--- /dev/null
+++ b/shared/src/i18n/id/members.ts
@@ -0,0 +1,24 @@
+import type { TranslationStrings } from '../types';
+
+const members: TranslationStrings = {
+ 'members.shareTrip': 'Bagikan Perjalanan',
+ 'members.inviteUser': 'Undang Pengguna',
+ 'members.selectUser': 'Pilih pengguna…',
+ 'members.invite': 'Undang',
+ 'members.allHaveAccess': 'Semua pengguna sudah punya akses.',
+ 'members.access': 'Akses',
+ 'members.person': 'orang',
+ 'members.persons': 'orang',
+ 'members.you': 'kamu',
+ 'members.owner': 'Pemilik',
+ 'members.leaveTrip': 'Keluar dari perjalanan',
+ 'members.removeAccess': 'Hapus akses',
+ 'members.confirmLeave': 'Keluar dari perjalanan? Kamu akan kehilangan akses.',
+ 'members.confirmRemove': 'Hapus akses untuk pengguna ini?',
+ 'members.loadError': 'Gagal memuat anggota',
+ 'members.added': 'ditambahkan',
+ 'members.addError': 'Gagal menambahkan',
+ 'members.removed': 'Anggota dihapus',
+ 'members.removeError': 'Gagal menghapus',
+};
+export default members;
diff --git a/shared/src/i18n/id/memories.ts b/shared/src/i18n/id/memories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..986b632c676f495143f3faf66726242ee20a0065
--- /dev/null
+++ b/shared/src/i18n/id/memories.ts
@@ -0,0 +1,80 @@
+import type { TranslationStrings } from '../types';
+
+const memories: TranslationStrings = {
+ 'memories.title': 'Foto',
+ 'memories.notConnected': '{provider_name} belum terhubung',
+ 'memories.notConnectedHint':
+ 'Hubungkan instans {provider_name} kamu di Pengaturan untuk dapat menambahkan foto ke perjalanan ini.',
+ 'memories.notConnectedMultipleHint':
+ 'Hubungkan salah satu penyedia foto berikut: {provider_names} di Pengaturan untuk dapat menambahkan foto ke perjalanan ini.',
+ 'memories.noDates': 'Tambahkan tanggal ke perjalananmu untuk memuat foto.',
+ 'memories.noPhotos': 'Foto tidak ditemukan',
+ 'memories.noPhotosHint':
+ 'Tidak ada foto ditemukan di {provider_name} untuk rentang tanggal perjalanan ini.',
+ 'memories.photosFound': 'foto',
+ 'memories.fromOthers': 'dari orang lain',
+ 'memories.sharePhotos': 'Bagikan foto',
+ 'memories.sharing': 'Berbagi',
+ 'memories.reviewTitle': 'Tinjau foto kamu',
+ 'memories.reviewHint': 'Klik foto untuk mengecualikannya dari berbagi.',
+ 'memories.shareCount': 'Bagikan {count} foto',
+ 'memories.providerUrl': 'URL Server',
+ 'memories.providerApiKey': 'API Key',
+ 'memories.providerUsername': 'Nama pengguna',
+ 'memories.providerPassword': 'Kata sandi',
+ 'memories.providerOTP': 'Kode MFA (jika diaktifkan)',
+ 'memories.skipSSLVerification': 'Lewati verifikasi sertifikat SSL',
+ 'memories.immichAutoUpload': 'Salin foto journey ke Immich saat diunggah',
+ 'memories.providerUrlHintSynology':
+ 'Sertakan path aplikasi Photos di URL, mis. https://nas:5001/photo',
+ 'memories.testConnection': 'Uji koneksi',
+ 'memories.testShort': 'Uji',
+ 'memories.testFirst': 'Uji koneksi terlebih dahulu',
+ 'memories.connected': 'Terhubung',
+ 'memories.disconnected': 'Tidak terhubung',
+ 'memories.connectionSuccess': 'Terhubung ke {provider_name}',
+ 'memories.connectionError': 'Tidak dapat terhubung ke {provider_name}',
+ 'memories.saved': 'Pengaturan {provider_name} disimpan',
+ 'memories.providerDisconnectedBanner':
+ 'Koneksi {provider_name} kamu terputus. Hubungkan ulang di Pengaturan untuk melihat foto.',
+ 'memories.saveError': 'Tidak dapat menyimpan pengaturan {provider_name}',
+ 'memories.addPhotos': 'Tambah foto',
+ 'memories.linkAlbum': 'Tautkan Album',
+ 'memories.selectAlbum': 'Pilih Album {provider_name}',
+ 'memories.selectAlbumMultiple': 'Pilih Album',
+ 'memories.noAlbums': 'Tidak ada album ditemukan',
+ 'memories.syncAlbum': 'Sinkronkan album',
+ 'memories.unlinkAlbum': 'Putuskan tautan album',
+ 'memories.photos': 'foto',
+ 'memories.selectPhotos': 'Pilih foto dari {provider_name}',
+ 'memories.selectPhotosMultiple': 'Pilih Foto',
+ 'memories.selectHint': 'Ketuk foto untuk memilihnya.',
+ 'memories.selected': 'dipilih',
+ 'memories.addSelected': 'Tambah {count} foto',
+ 'memories.alreadyAdded': 'Sudah ditambahkan',
+ 'memories.private': 'Privat',
+ 'memories.stopSharing': 'Berhenti berbagi',
+ 'memories.oldest': 'Terlama dulu',
+ 'memories.newest': 'Terbaru dulu',
+ 'memories.allLocations': 'Semua lokasi',
+ 'memories.tripDates': 'Tanggal perjalanan',
+ 'memories.allPhotos': 'Semua foto',
+ 'memories.confirmShareTitle': 'Bagikan ke anggota perjalanan?',
+ 'memories.confirmShareHint':
+ '{count} foto akan terlihat oleh semua anggota perjalanan ini. Kamu bisa membuat foto tertentu menjadi privat nanti.',
+ 'memories.confirmShareButton': 'Bagikan foto',
+ 'memories.error.loadAlbums': 'Gagal memuat album',
+ 'memories.error.linkAlbum': 'Gagal menautkan album',
+ 'memories.error.unlinkAlbum': 'Gagal memutuskan tautan album',
+ 'memories.error.syncAlbum': 'Gagal menyinkronkan album',
+ 'memories.error.loadPhotos': 'Gagal memuat foto',
+ 'memories.error.addPhotos': 'Gagal menambahkan foto',
+ 'memories.error.removePhoto': 'Gagal menghapus foto',
+ 'memories.error.toggleSharing': 'Gagal memperbarui berbagi',
+ 'memories.saveRouteNotConfigured':
+ 'Rute simpan tidak dikonfigurasi untuk penyedia ini',
+ 'memories.testRouteNotConfigured':
+ 'Rute uji tidak dikonfigurasi untuk penyedia ini',
+ 'memories.fillRequiredFields': 'Harap isi semua kolom yang wajib diisi',
+};
+export default memories;
diff --git a/shared/src/i18n/id/nav.ts b/shared/src/i18n/id/nav.ts
new file mode 100644
index 0000000000000000000000000000000000000000..723fa26c5422c59a9dd7483df3dc50920adef857
--- /dev/null
+++ b/shared/src/i18n/id/nav.ts
@@ -0,0 +1,20 @@
+import type { TranslationStrings } from '../types';
+
+const nav: TranslationStrings = {
+ 'nav.trip': 'Perjalanan',
+ 'nav.share': 'Bagikan',
+ 'nav.settings': 'Pengaturan',
+ 'nav.admin': 'Admin',
+ 'nav.logout': 'Keluar',
+ 'nav.lightMode': 'Mode Terang',
+ 'nav.darkMode': 'Mode Gelap',
+ 'nav.autoMode': 'Mode Otomatis',
+ 'nav.administrator': 'Administrator',
+ 'nav.myTrips': 'Perjalananku',
+ 'nav.profile': 'Profil',
+ 'nav.bottomSettings': 'Pengaturan',
+ 'nav.bottomAdmin': 'Pengaturan Admin',
+ 'nav.bottomLogout': 'Keluar',
+ 'nav.bottomAdminBadge': 'Admin',
+};
+export default nav;
diff --git a/shared/src/i18n/id/notif.ts b/shared/src/i18n/id/notif.ts
new file mode 100644
index 0000000000000000000000000000000000000000..815536373601b0637a830eecc93119bccc1504c3
--- /dev/null
+++ b/shared/src/i18n/id/notif.ts
@@ -0,0 +1,42 @@
+import type { TranslationStrings } from '../types';
+
+const notif: TranslationStrings = {
+ 'notif.test.title': '[Test] Notifikasi',
+ 'notif.test.simple.text': 'Ini adalah notifikasi uji sederhana.',
+ 'notif.test.boolean.text': 'Apakah kamu menerima notifikasi uji ini?',
+ 'notif.test.navigate.text': 'Klik di bawah untuk pergi ke dasbor.',
+ 'notif.trip_invite.title': 'Undangan Perjalanan',
+ 'notif.trip_invite.text': '{actor} mengundangmu ke {trip}',
+ 'notif.booking_change.title': 'Pemesanan Diperbarui',
+ 'notif.booking_change.text': '{actor} memperbarui pemesanan di {trip}',
+ 'notif.trip_reminder.title': 'Pengingat Perjalanan',
+ 'notif.trip_reminder.text': 'Perjalananmu {trip} akan segera dimulai!',
+ 'notif.todo_due.title': 'Tugas jatuh tempo',
+ 'notif.todo_due.text': '{todo} di {trip} jatuh tempo pada {due}',
+ 'notif.vacay_invite.title': 'Undangan Vacay Fusion',
+ 'notif.vacay_invite.text':
+ '{actor} mengundangmu untuk menggabungkan rencana liburan',
+ 'notif.photos_shared.title': 'Foto Dibagikan',
+ 'notif.photos_shared.text': '{actor} membagikan {count} foto di {trip}',
+ 'notif.collab_message.title': 'Pesan Baru',
+ 'notif.collab_message.text': '{actor} mengirim pesan di {trip}',
+ 'notif.packing_tagged.title': 'Penugasan Perlengkapan',
+ 'notif.packing_tagged.text': '{actor} menugaskanmu ke {category} di {trip}',
+ 'notif.version_available.title': 'Versi Baru Tersedia',
+ 'notif.version_available.text': 'TREK {version} kini tersedia',
+ 'notif.action.view_trip': 'Lihat Perjalanan',
+ 'notif.action.view_collab': 'Lihat Pesan',
+ 'notif.action.view_packing': 'Lihat Perlengkapan',
+ 'notif.action.view_photos': 'Lihat Foto',
+ 'notif.action.view_vacay': 'Lihat Vacay',
+ 'notif.action.view_admin': 'Ke Admin',
+ 'notif.action.view': 'Lihat',
+ 'notif.action.accept': 'Terima',
+ 'notif.action.decline': 'Tolak',
+ 'notif.generic.title': 'Notifikasi',
+ 'notif.generic.text': 'Kamu punya notifikasi baru',
+ 'notif.dev.unknown_event.title': '[DEV] Event Tidak Dikenal',
+ 'notif.dev.unknown_event.text':
+ 'Tipe event "{event}" tidak terdaftar di EVENT_NOTIFICATION_CONFIG',
+};
+export default notif;
diff --git a/shared/src/i18n/id/notifications.ts b/shared/src/i18n/id/notifications.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f271a9a9b15eaab7954247a6eb47048f0469af8f
--- /dev/null
+++ b/shared/src/i18n/id/notifications.ts
@@ -0,0 +1,38 @@
+import type { TranslationStrings } from '../types';
+
+const notifications: TranslationStrings = {
+ 'notifications.title': 'Notifikasi',
+ 'notifications.markAllRead': 'Tandai semua dibaca',
+ 'notifications.deleteAll': 'Hapus semua',
+ 'notifications.showAll': 'Tampilkan semua notifikasi',
+ 'notifications.empty': 'Tidak ada notifikasi',
+ 'notifications.emptyDescription': 'Semuanya sudah terbaca!',
+ 'notifications.all': 'Semua',
+ 'notifications.unreadOnly': 'Belum dibaca',
+ 'notifications.markRead': 'Tandai sudah dibaca',
+ 'notifications.markUnread': 'Tandai belum dibaca',
+ 'notifications.delete': 'Hapus',
+ 'notifications.system': 'Sistem',
+ 'notifications.synologySessionCleared.title': 'Synology Photos terputus',
+ 'notifications.synologySessionCleared.text':
+ 'Server atau akun kamu berubah — buka Pengaturan untuk menguji koneksi lagi.',
+ 'notifications.versionAvailable.title': 'Pembaruan Tersedia',
+ 'notifications.versionAvailable.text': 'TREK {version} kini tersedia.',
+ 'notifications.versionAvailable.button': 'Lihat Detail',
+ 'notifications.test.title': 'Notifikasi uji dari {actor}',
+ 'notifications.test.text': 'Ini adalah notifikasi uji sederhana.',
+ 'notifications.test.booleanTitle': '{actor} meminta persetujuanmu',
+ 'notifications.test.booleanText':
+ 'Ini adalah notifikasi uji boolean. Pilih tindakan di bawah.',
+ 'notifications.test.accept': 'Setujui',
+ 'notifications.test.decline': 'Tolak',
+ 'notifications.test.navigateTitle': 'Cek sesuatu',
+ 'notifications.test.navigateText': 'Ini adalah notifikasi uji navigasi.',
+ 'notifications.test.goThere': 'Ke sana',
+ 'notifications.test.adminTitle': 'Siaran Admin',
+ 'notifications.test.adminText':
+ '{actor} mengirim notifikasi uji ke semua admin.',
+ 'notifications.test.tripTitle': '{actor} memposting di perjalananmu',
+ 'notifications.test.tripText': 'Notifikasi uji untuk perjalanan "{trip}".',
+};
+export default notifications;
diff --git a/shared/src/i18n/id/oauth.ts b/shared/src/i18n/id/oauth.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b73af70811fa26878b518a2fe2e4aff17fc68095
--- /dev/null
+++ b/shared/src/i18n/id/oauth.ts
@@ -0,0 +1,123 @@
+import type { TranslationStrings } from '../types';
+
+const oauth: TranslationStrings = {
+ 'oauth.scope.group.trips': 'Perjalanan',
+ 'oauth.scope.group.places': 'Tempat',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'Perlengkapan',
+ 'oauth.scope.group.todos': 'To-do',
+ 'oauth.scope.group.budget': 'Anggaran',
+ 'oauth.scope.group.reservations': 'Reservasi',
+ 'oauth.scope.group.collab': 'Kolaborasi',
+ 'oauth.scope.group.notifications': 'Notifikasi',
+ 'oauth.scope.group.vacay': 'Liburan',
+ 'oauth.scope.group.geo': 'Geo',
+ 'oauth.scope.group.weather': 'Cuaca',
+ 'oauth.scope.group.journey': 'Journey',
+ 'oauth.scope.trips:read.label': 'Lihat perjalanan & itinerari',
+ 'oauth.scope.trips:read.description':
+ 'Baca perjalanan, hari, catatan harian, dan anggota',
+ 'oauth.scope.trips:write.label': 'Edit perjalanan & itinerari',
+ 'oauth.scope.trips:write.description':
+ 'Buat dan perbarui perjalanan, hari, catatan, dan kelola anggota',
+ 'oauth.scope.trips:delete.label': 'Hapus perjalanan',
+ 'oauth.scope.trips:delete.description':
+ 'Hapus permanen seluruh perjalanan — tindakan ini tidak dapat dibatalkan',
+ 'oauth.scope.trips:share.label': 'Kelola tautan berbagi',
+ 'oauth.scope.trips:share.description':
+ 'Buat, perbarui, dan cabut tautan berbagi publik untuk perjalanan',
+ 'oauth.scope.places:read.label': 'Lihat tempat & data peta',
+ 'oauth.scope.places:read.description':
+ 'Baca tempat, penugasan hari, tag, dan kategori',
+ 'oauth.scope.places:write.label': 'Kelola tempat',
+ 'oauth.scope.places:write.description':
+ 'Buat, perbarui, dan hapus tempat, penugasan, dan tag',
+ 'oauth.scope.atlas:read.label': 'Lihat Atlas',
+ 'oauth.scope.atlas:read.description':
+ 'Baca negara yang dikunjungi, wilayah, dan daftar impian',
+ 'oauth.scope.atlas:write.label': 'Kelola Atlas',
+ 'oauth.scope.atlas:write.description':
+ 'Tandai negara dan wilayah yang dikunjungi, kelola daftar impian',
+ 'oauth.scope.packing:read.label': 'Lihat daftar perlengkapan',
+ 'oauth.scope.packing:read.description':
+ 'Baca barang perlengkapan, tas, dan penugasan kategori',
+ 'oauth.scope.packing:write.label': 'Kelola daftar perlengkapan',
+ 'oauth.scope.packing:write.description':
+ 'Tambah, perbarui, hapus, centang, dan urutkan barang dan tas',
+ 'oauth.scope.todos:read.label': 'Lihat daftar to-do',
+ 'oauth.scope.todos:read.description':
+ 'Baca item to-do perjalanan dan penugasan kategori',
+ 'oauth.scope.todos:write.label': 'Kelola daftar to-do',
+ 'oauth.scope.todos:write.description':
+ 'Buat, perbarui, centang, hapus, dan urutkan item to-do',
+ 'oauth.scope.budget:read.label': 'Lihat anggaran',
+ 'oauth.scope.budget:read.description':
+ 'Baca item anggaran dan rincian pengeluaran',
+ 'oauth.scope.budget:write.label': 'Kelola anggaran',
+ 'oauth.scope.budget:write.description':
+ 'Buat, perbarui, dan hapus item anggaran',
+ 'oauth.scope.reservations:read.label': 'Lihat reservasi',
+ 'oauth.scope.reservations:read.description':
+ 'Baca reservasi dan detail akomodasi',
+ 'oauth.scope.reservations:write.label': 'Kelola reservasi',
+ 'oauth.scope.reservations:write.description':
+ 'Buat, perbarui, hapus, dan urutkan reservasi',
+ 'oauth.scope.collab:read.label': 'Lihat kolaborasi',
+ 'oauth.scope.collab:read.description':
+ 'Baca catatan, polling, dan pesan kolaborasi',
+ 'oauth.scope.collab:write.label': 'Kelola kolaborasi',
+ 'oauth.scope.collab:write.description':
+ 'Buat, perbarui, dan hapus catatan, polling, dan pesan kolaborasi',
+ 'oauth.scope.notifications:read.label': 'Lihat notifikasi',
+ 'oauth.scope.notifications:read.description':
+ 'Baca notifikasi dalam aplikasi dan jumlah yang belum dibaca',
+ 'oauth.scope.notifications:write.label': 'Kelola notifikasi',
+ 'oauth.scope.notifications:write.description':
+ 'Tandai notifikasi sebagai telah dibaca dan tanggapi',
+ 'oauth.scope.vacay:read.label': 'Lihat rencana liburan',
+ 'oauth.scope.vacay:read.description':
+ 'Baca data perencanaan liburan, entri, dan statistik',
+ 'oauth.scope.vacay:write.label': 'Kelola rencana liburan',
+ 'oauth.scope.vacay:write.description':
+ 'Buat dan kelola entri liburan, hari libur, dan rencana tim',
+ 'oauth.scope.geo:read.label': 'Peta & geokoding',
+ 'oauth.scope.geo:read.description':
+ 'Cari lokasi, selesaikan URL peta, dan geokode terbalik koordinat',
+ 'oauth.scope.weather:read.label': 'Prakiraan cuaca',
+ 'oauth.scope.weather:read.description':
+ 'Ambil prakiraan cuaca untuk lokasi dan tanggal perjalanan',
+ 'oauth.scope.journey:read.label': 'Lihat Journey',
+ 'oauth.scope.journey:read.description':
+ 'Baca Journey, entri, dan daftar kontributor',
+ 'oauth.scope.journey:write.label': 'Kelola Journey',
+ 'oauth.scope.journey:write.description':
+ 'Buat, perbarui, dan hapus Journey beserta entrinya',
+ 'oauth.scope.journey:share.label': 'Kelola tautan Journey',
+ 'oauth.scope.journey:share.description':
+ 'Buat, perbarui, dan cabut tautan berbagi publik untuk Journey',
+ 'oauth.authorize.authorizing': 'Authorizing…', // en-fallback
+ 'oauth.authorize.loading': 'Loading…', // en-fallback
+ 'oauth.authorize.errorTitle': 'Authorization Error', // en-fallback
+ 'oauth.authorize.loginTitle': 'Sign in to continue', // en-fallback
+ 'oauth.authorize.loginDescription':
+ '{client} wants access to your TREK account. Please sign in first.', // en-fallback
+ 'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
+ 'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
+ 'oauth.authorize.requestDescription':
+ 'This application is requesting access to your TREK account.', // en-fallback
+ 'oauth.authorize.trustNote':
+ 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
+ 'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
+ 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
+ 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
+ 'oauth.authorize.deny': 'Deny', // en-fallback
+ 'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
+ 'oauth.authorize.permissionsRequested': 'Permissions requested', // en-fallback
+ 'oauth.authorize.alwaysIncluded': 'Always included', // en-fallback
+ 'oauth.authorize.alwaysTool.listTrips':
+ 'List your trips so the AI can discover trip IDs', // en-fallback
+ 'oauth.authorize.alwaysTool.getTripSummary':
+ 'Read a trip overview needed to use any other tool', // en-fallback
+};
+export default oauth;
diff --git a/shared/src/i18n/id/packing.ts b/shared/src/i18n/id/packing.ts
new file mode 100644
index 0000000000000000000000000000000000000000..04d73887a58b6b5ff802ec3822ef00a3e66051e7
--- /dev/null
+++ b/shared/src/i18n/id/packing.ts
@@ -0,0 +1,186 @@
+import type { TranslationStrings } from '../types';
+
+const packing: TranslationStrings = {
+ 'packing.title': 'Daftar Bawaan',
+ 'packing.empty': 'Daftar bawaan kosong',
+ 'packing.import': 'Impor',
+ 'packing.importTitle': 'Impor Daftar Bawaan',
+ 'packing.importHint':
+ 'Satu item per baris. Format: Kategori, Nama, Berat dalam g (opsional), Tas (opsional), checked/unchecked (opsional)',
+ 'packing.importPlaceholder':
+ 'Kebersihan, Sikat Gigi\nPakaian, Kaos, 200\nDokumen, Paspor, , Kabin\nElektronik, Charger, 50, Koper, checked',
+ 'packing.importCsv': 'Muat CSV/TXT',
+ 'packing.importAction': 'Impor {count}',
+ 'packing.importSuccess': '{count} item berhasil diimpor',
+ 'packing.importError': 'Impor gagal',
+ 'packing.importEmpty': 'Tidak ada item untuk diimpor',
+ 'packing.progress': '{packed} dari {total} sudah dikemas ({percent}%)',
+ 'packing.clearChecked': 'Hapus {count} yang dicentang',
+ 'packing.clearCheckedShort': 'Hapus {count}',
+ 'packing.suggestions': 'Saran',
+ 'packing.suggestionsTitle': 'Tambah Saran',
+ 'packing.allSuggested': 'Semua saran sudah ditambahkan',
+ 'packing.allPacked': 'Semua sudah dikemas!',
+ 'packing.addPlaceholder': 'Tambah item baru...',
+ 'packing.categoryPlaceholder': 'Kategori...',
+ 'packing.filterAll': 'Semua',
+ 'packing.filterOpen': 'Belum',
+ 'packing.filterDone': 'Selesai',
+ 'packing.emptyTitle': 'Daftar bawaan kosong',
+ 'packing.emptyHint': 'Tambah item atau gunakan saran',
+ 'packing.emptyFiltered': 'Tidak ada item yang cocok dengan filter ini',
+ 'packing.menuRename': 'Ganti Nama',
+ 'packing.menuCheckAll': 'Centang Semua',
+ 'packing.menuUncheckAll': 'Hapus Centang Semua',
+ 'packing.menuDeleteCat': 'Hapus Kategori',
+ 'packing.noMembers': 'Tidak ada anggota perjalanan',
+ 'packing.addItem': 'Tambah item',
+ 'packing.addItemPlaceholder': 'Nama item...',
+ 'packing.addCategory': 'Tambah kategori',
+ 'packing.newCategoryPlaceholder': 'Nama kategori (contoh: Pakaian)',
+ 'packing.applyTemplate': 'Terapkan template',
+ 'packing.template': 'Template',
+ 'packing.templateApplied': '{count} item ditambahkan dari template',
+ 'packing.templateError': 'Gagal menerapkan template',
+ 'packing.saveAsTemplate': 'Simpan sebagai template',
+ 'packing.templateName': 'Nama template',
+ 'packing.templateSaved': 'Daftar bawaan disimpan sebagai template',
+ 'packing.bags': 'Tas',
+ 'packing.noBag': 'Belum ditugaskan',
+ 'packing.totalWeight': 'Total berat',
+ 'packing.bagName': 'Nama tas...',
+ 'packing.addBag': 'Tambah tas',
+ 'packing.changeCategory': 'Ganti Kategori',
+ 'packing.confirm.clearChecked':
+ 'Yakin ingin menghapus {count} item yang dicentang?',
+ 'packing.confirm.deleteCat':
+ 'Yakin ingin menghapus kategori "{name}" beserta {count} item di dalamnya?',
+ 'packing.defaultCategory': 'Lainnya',
+ 'packing.toast.saveError': 'Gagal menyimpan',
+ 'packing.toast.deleteError': 'Gagal menghapus',
+ 'packing.toast.renameError': 'Gagal mengganti nama',
+ 'packing.toast.addError': 'Gagal menambahkan',
+ 'packing.suggestions.items': [
+ {
+ name: 'Paspor',
+ category: 'Dokumen',
+ },
+ {
+ name: 'KTP',
+ category: 'Dokumen',
+ },
+ {
+ name: 'Asuransi Perjalanan',
+ category: 'Dokumen',
+ },
+ {
+ name: 'Tiket Penerbangan',
+ category: 'Dokumen',
+ },
+ {
+ name: 'Kartu Kredit',
+ category: 'Keuangan',
+ },
+ {
+ name: 'Uang Tunai',
+ category: 'Keuangan',
+ },
+ {
+ name: 'Visa',
+ category: 'Dokumen',
+ },
+ {
+ name: 'Kaos',
+ category: 'Pakaian',
+ },
+ {
+ name: 'Celana',
+ category: 'Pakaian',
+ },
+ {
+ name: 'Pakaian Dalam',
+ category: 'Pakaian',
+ },
+ {
+ name: 'Kaos Kaki',
+ category: 'Pakaian',
+ },
+ {
+ name: 'Jaket',
+ category: 'Pakaian',
+ },
+ {
+ name: 'Pakaian Tidur',
+ category: 'Pakaian',
+ },
+ {
+ name: 'Pakaian Renang',
+ category: 'Pakaian',
+ },
+ {
+ name: 'Jas Hujan',
+ category: 'Pakaian',
+ },
+ {
+ name: 'Sepatu Nyaman',
+ category: 'Pakaian',
+ },
+ {
+ name: 'Sikat Gigi',
+ category: 'Perlengkapan Mandi',
+ },
+ {
+ name: 'Pasta Gigi',
+ category: 'Perlengkapan Mandi',
+ },
+ {
+ name: 'Sampo',
+ category: 'Perlengkapan Mandi',
+ },
+ {
+ name: 'Deodoran',
+ category: 'Perlengkapan Mandi',
+ },
+ {
+ name: 'Tabir Surya',
+ category: 'Perlengkapan Mandi',
+ },
+ {
+ name: 'Pisau Cukur',
+ category: 'Perlengkapan Mandi',
+ },
+ {
+ name: 'Charger',
+ category: 'Elektronik',
+ },
+ {
+ name: 'Power Bank',
+ category: 'Elektronik',
+ },
+ {
+ name: 'Headphone',
+ category: 'Elektronik',
+ },
+ {
+ name: 'Adaptor Perjalanan',
+ category: 'Elektronik',
+ },
+ {
+ name: 'Kamera',
+ category: 'Elektronik',
+ },
+ {
+ name: 'Obat Pereda Nyeri',
+ category: 'Kesehatan',
+ },
+ {
+ name: 'Plester',
+ category: 'Kesehatan',
+ },
+ {
+ name: 'Disinfektan',
+ category: 'Kesehatan',
+ },
+ ],
+};
+export default packing;
diff --git a/shared/src/i18n/id/pdf.ts b/shared/src/i18n/id/pdf.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c20334b996c8782261b4791175c0309757d41b8f
--- /dev/null
+++ b/shared/src/i18n/id/pdf.ts
@@ -0,0 +1,10 @@
+import type { TranslationStrings } from '../types';
+
+const pdf: TranslationStrings = {
+ 'pdf.travelPlan': 'Rencana Perjalanan',
+ 'pdf.planned': 'Direncanakan',
+ 'pdf.costLabel': 'Biaya EUR',
+ 'pdf.preview': 'Pratinjau PDF',
+ 'pdf.saveAsPdf': 'Simpan sebagai PDF',
+};
+export default pdf;
diff --git a/shared/src/i18n/id/perm.ts b/shared/src/i18n/id/perm.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b216b5c1005f4e7fdb6f4199e45eaa3b8afb544b
--- /dev/null
+++ b/shared/src/i18n/id/perm.ts
@@ -0,0 +1,66 @@
+import type { TranslationStrings } from '../types';
+
+const perm: TranslationStrings = {
+ 'perm.title': 'Pengaturan Izin',
+ 'perm.subtitle': 'Kendalikan siapa yang bisa melakukan tindakan di aplikasi',
+ 'perm.saved': 'Pengaturan izin disimpan',
+ 'perm.resetDefaults': 'Kembalikan ke default',
+ 'perm.customized': 'dikustomisasi',
+ 'perm.level.admin': 'Hanya Admin',
+ 'perm.level.tripOwner': 'Pemilik perjalanan',
+ 'perm.level.tripMember': 'Anggota perjalanan',
+ 'perm.level.everybody': 'Semua orang',
+ 'perm.cat.trip': 'Manajemen Perjalanan',
+ 'perm.cat.members': 'Manajemen Anggota',
+ 'perm.cat.files': 'File',
+ 'perm.cat.content': 'Konten & Jadwal',
+ 'perm.cat.extras': 'Anggaran, Perlengkapan & Kolaborasi',
+ 'perm.action.trip_create': 'Buat perjalanan',
+ 'perm.action.trip_edit': 'Edit detail perjalanan',
+ 'perm.action.trip_delete': 'Hapus perjalanan',
+ 'perm.action.trip_archive': 'Arsipkan / batalkan arsip perjalanan',
+ 'perm.action.trip_cover_upload': 'Unggah gambar sampul',
+ 'perm.action.member_manage': 'Tambah / hapus anggota',
+ 'perm.action.file_upload': 'Unggah file',
+ 'perm.action.file_edit': 'Edit metadata file',
+ 'perm.action.file_delete': 'Hapus file',
+ 'perm.action.place_edit': 'Tambah / edit / hapus tempat',
+ 'perm.action.day_edit': 'Edit hari, catatan & penugasan',
+ 'perm.action.reservation_edit': 'Kelola reservasi',
+ 'perm.action.budget_edit': 'Kelola anggaran',
+ 'perm.action.packing_edit': 'Kelola daftar perlengkapan',
+ 'perm.action.collab_edit': 'Kolaborasi (catatan, polling, chat)',
+ 'perm.action.share_manage': 'Kelola tautan berbagi',
+ 'perm.actionHint.trip_create': 'Siapa yang bisa membuat perjalanan baru',
+ 'perm.actionHint.trip_edit':
+ 'Siapa yang bisa mengubah nama, tanggal, deskripsi, dan mata uang perjalanan',
+ 'perm.actionHint.trip_delete':
+ 'Siapa yang bisa menghapus perjalanan secara permanen',
+ 'perm.actionHint.trip_archive':
+ 'Siapa yang bisa mengarsipkan atau membatalkan arsip perjalanan',
+ 'perm.actionHint.trip_cover_upload':
+ 'Siapa yang bisa mengunggah atau mengganti gambar sampul',
+ 'perm.actionHint.member_manage':
+ 'Siapa yang bisa mengundang atau menghapus anggota perjalanan',
+ 'perm.actionHint.file_upload':
+ 'Siapa yang bisa mengunggah file ke perjalanan',
+ 'perm.actionHint.file_edit':
+ 'Siapa yang bisa mengedit deskripsi dan tautan file',
+ 'perm.actionHint.file_delete':
+ 'Siapa yang bisa memindahkan file ke sampah atau menghapusnya secara permanen',
+ 'perm.actionHint.place_edit':
+ 'Siapa yang bisa menambah, mengedit, atau menghapus tempat',
+ 'perm.actionHint.day_edit':
+ 'Siapa yang bisa mengedit hari, catatan hari, dan penugasan tempat',
+ 'perm.actionHint.reservation_edit':
+ 'Siapa yang bisa membuat, mengedit, atau menghapus reservasi',
+ 'perm.actionHint.budget_edit':
+ 'Siapa yang bisa membuat, mengedit, atau menghapus item anggaran',
+ 'perm.actionHint.packing_edit':
+ 'Siapa yang bisa mengelola item perlengkapan dan tas',
+ 'perm.actionHint.collab_edit':
+ 'Siapa yang bisa membuat catatan, polling, dan mengirim pesan',
+ 'perm.actionHint.share_manage':
+ 'Siapa yang bisa membuat atau menghapus tautan berbagi publik',
+};
+export default perm;
diff --git a/shared/src/i18n/id/photos.ts b/shared/src/i18n/id/photos.ts
new file mode 100644
index 0000000000000000000000000000000000000000..44882b0b52d57acfdba6ab14ca30cdafac4eb817
--- /dev/null
+++ b/shared/src/i18n/id/photos.ts
@@ -0,0 +1,25 @@
+import type { TranslationStrings } from '../types';
+
+const photos: TranslationStrings = {
+ 'photos.title': 'Foto',
+ 'photos.subtitle': '{count} foto untuk {trip}',
+ 'photos.dropHere': 'Lepas foto di sini...',
+ 'photos.dropHereActive': 'Lepas foto di sini',
+ 'photos.captionForAll': 'Keterangan (untuk semua)',
+ 'photos.captionPlaceholder': 'Keterangan opsional...',
+ 'photos.addCaption': 'Tambah keterangan...',
+ 'photos.allDays': 'Semua Hari',
+ 'photos.noPhotos': 'Belum ada foto',
+ 'photos.uploadHint': 'Unggah foto perjalananmu',
+ 'photos.clickToSelect': 'atau klik untuk memilih',
+ 'photos.linkPlace': 'Tautkan Tempat',
+ 'photos.noPlace': 'Tanpa Tempat',
+ 'photos.uploadN': 'Unggah {n} foto',
+ 'photos.linkDay': 'Tautkan Hari',
+ 'photos.noDay': 'Tanpa Hari',
+ 'photos.dayLabel': 'Hari {number}',
+ 'photos.photoSelected': 'Foto dipilih',
+ 'photos.photosSelected': 'Foto dipilih',
+ 'photos.fileTypeHint': 'JPG, PNG, WebP · maks. 10 MB · hingga 30 foto',
+};
+export default photos;
diff --git a/shared/src/i18n/id/places.ts b/shared/src/i18n/id/places.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6d4a2f3f4c4fa24b7ae305043b4ae8c583642308
--- /dev/null
+++ b/shared/src/i18n/id/places.ts
@@ -0,0 +1,97 @@
+import type { TranslationStrings } from '../types';
+
+const places: TranslationStrings = {
+ 'places.addPlace': 'Tambah Tempat/Aktivitas',
+ 'places.importFile': 'Impor file',
+ 'places.sidebarDrop': 'Lepas untuk mengimpor',
+ 'places.importFileHint':
+ 'Impor file .gpx, .kml, atau .kmz dari Google My Maps, Google Earth, atau pelacak GPS.',
+ 'places.importFileDropHere':
+ 'Klik untuk memilih file atau seret dan lepas di sini',
+ 'places.importFileDropActive': 'Lepas file untuk memilih',
+ 'places.importFileUnsupported':
+ 'Jenis file tidak didukung. Gunakan .gpx, .kml, atau .kmz.',
+ 'places.importFileTooLarge':
+ 'File terlalu besar. Ukuran unggah maksimum adalah {maxMb} MB.',
+ 'places.importFileError': 'Impor gagal',
+ 'places.importAllSkipped': 'Semua tempat sudah ada di perjalanan.',
+ 'places.gpxImported': '{count} tempat diimpor dari GPX',
+ 'places.gpxImportTypes': 'Apa yang ingin diimpor?',
+ 'places.gpxImportWaypoints': 'Titik jalan',
+ 'places.gpxImportRoutes': 'Rute',
+ 'places.gpxImportTracks': 'Trek (dengan geometri jalur)',
+ 'places.gpxImportNoneSelected': 'Pilih setidaknya satu jenis untuk diimpor.',
+ 'places.kmlImportTypes': 'Apa yang ingin diimpor?',
+ 'places.kmlImportPoints': 'Titik (Placemarks)',
+ 'places.kmlImportPaths': 'Jalur (LineStrings)',
+ 'places.kmlImportNoneSelected': 'Pilih setidaknya satu jenis.',
+ 'places.selectionCount': '{count} dipilih',
+ 'places.deleteSelected': 'Hapus yang dipilih',
+ 'places.kmlKmzImported': '{count} tempat diimpor dari KMZ/KML',
+ 'places.urlResolved': 'Tempat diimpor dari URL',
+ 'places.importList': 'Impor Daftar',
+ 'places.kmlKmzSummaryValues':
+ 'Placemark: {total} • Diimpor: {created} • Dilewati: {skipped}',
+ 'places.importGoogleList': 'Daftar Google',
+ 'places.importNaverList': 'Daftar Naver',
+ 'places.googleListHint':
+ 'Tempel tautan daftar Google Maps yang dibagikan untuk mengimpor semua tempat.',
+ 'places.googleListImported': '{count} tempat diimpor dari "{list}"',
+ 'places.googleListError': 'Gagal mengimpor daftar Google Maps',
+ 'places.naverListHint':
+ 'Tempel tautan daftar Naver Maps yang dibagikan untuk mengimpor semua tempat.',
+ 'places.naverListImported': '{count} tempat diimpor dari "{list}"',
+ 'places.naverListError': 'Gagal mengimpor daftar Naver Maps',
+ 'places.viewDetails': 'Lihat Detail',
+ 'places.assignToDay': 'Tambah ke hari mana?',
+ 'places.all': 'Semua',
+ 'places.unplanned': 'Belum direncanakan',
+ 'places.filterTracks': 'Trek',
+ 'places.search': 'Cari tempat...',
+ 'places.allCategories': 'Semua Kategori',
+ 'places.categoriesSelected': 'kategori',
+ 'places.clearFilter': 'Hapus filter',
+ 'places.count': '{count} tempat',
+ 'places.countSingular': '1 tempat',
+ 'places.allPlanned': 'Semua tempat sudah direncanakan',
+ 'places.noneFound': 'Tidak ada tempat ditemukan',
+ 'places.editPlace': 'Edit Tempat',
+ 'places.formName': 'Nama',
+ 'places.formNamePlaceholder': 'mis. Menara Eiffel',
+ 'places.formDescription': 'Deskripsi',
+ 'places.formDescriptionPlaceholder': 'Deskripsi singkat...',
+ 'places.formAddress': 'Alamat',
+ 'places.formAddressPlaceholder': 'Jalan, Kota, Negara',
+ 'places.formLat': 'Lintang (mis. 48.8566)',
+ 'places.formLng': 'Bujur (mis. 2.3522)',
+ 'places.formCategory': 'Kategori',
+ 'places.noCategory': 'Tanpa Kategori',
+ 'places.categoryNamePlaceholder': 'Nama kategori',
+ 'places.formTime': 'Waktu',
+ 'places.startTime': 'Mulai',
+ 'places.endTime': 'Selesai',
+ 'places.endTimeBeforeStart': 'Waktu selesai lebih awal dari waktu mulai',
+ 'places.timeCollision': 'Waktu tumpang tindih dengan:',
+ 'places.formWebsite': 'Situs web',
+ 'places.formNotes': 'Catatan',
+ 'places.formNotesPlaceholder': 'Catatan pribadi...',
+ 'places.formReservation': 'Reservasi',
+ 'places.reservationNotesPlaceholder':
+ 'Catatan reservasi, nomor konfirmasi...',
+ 'places.mapsSearchPlaceholder': 'Cari tempat...',
+ 'places.mapsSearchError': 'Pencarian tempat gagal.',
+ 'places.loadingDetails': 'Memuat detail tempat…',
+ 'places.osmHint':
+ 'Menggunakan pencarian OpenStreetMap (tanpa foto, jam buka, atau penilaian). Tambahkan Google API key di pengaturan untuk detail lengkap.',
+ 'places.osmActive':
+ 'Pencarian via OpenStreetMap (tanpa foto, penilaian, atau jam buka). Tambahkan Google API key di Pengaturan untuk data yang lebih lengkap.',
+ 'places.categoryCreateError': 'Gagal membuat kategori',
+ 'places.nameRequired': 'Harap masukkan nama',
+ 'places.saveError': 'Gagal menyimpan',
+ 'places.duplicateExists': "'{name}' sudah ada di perjalanan ini.",
+ 'places.addAnyway': 'Tetap tambahkan',
+ 'places.enrichOnImport': 'Perkaya tempat via Google',
+ 'places.enrichOnImportHint':
+ 'Mencari setiap tempat yang diimpor untuk menambahkan foto, alamat, dan kontak. Memerlukan kunci Google Maps.',
+};
+export default places;
diff --git a/shared/src/i18n/id/planner.ts b/shared/src/i18n/id/planner.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e5a3738008078ba253e0ae71038ebf36f7412474
--- /dev/null
+++ b/shared/src/i18n/id/planner.ts
@@ -0,0 +1,68 @@
+import type { TranslationStrings } from '../types';
+
+const planner: TranslationStrings = {
+ 'planner.places': 'Tempat',
+ 'planner.bookings': 'Pemesanan',
+ 'planner.packingList': 'Daftar Bawaan',
+ 'planner.documents': 'Dokumen',
+ 'planner.dayPlan': 'Rencana Hari',
+ 'planner.reservations': 'Reservasi',
+ 'planner.minTwoPlaces': 'Dibutuhkan minimal 2 tempat dengan koordinat',
+ 'planner.noGeoPlaces': 'Tidak ada tempat dengan koordinat yang tersedia',
+ 'planner.routeCalculated': 'Rute berhasil dihitung',
+ 'planner.routeCalcFailed': 'Rute tidak dapat dihitung',
+ 'planner.routeError': 'Terjadi kesalahan saat menghitung rute',
+ 'planner.icsExportFailed': 'Ekspor ICS gagal',
+ 'planner.routeOptimized': 'Rute dioptimalkan',
+ 'planner.reservationUpdated': 'Reservasi diperbarui',
+ 'planner.reservationAdded': 'Reservasi ditambahkan',
+ 'planner.confirmDeleteReservation': 'Hapus reservasi?',
+ 'planner.reservationDeleted': 'Reservasi dihapus',
+ 'planner.days': 'Hari',
+ 'planner.allPlaces': 'Semua Tempat',
+ 'planner.totalPlaces': '{n} tempat total',
+ 'planner.noDaysPlanned': 'Belum ada hari yang direncanakan',
+ 'planner.editTrip': 'Edit perjalanan →',
+ 'planner.placeOne': '1 tempat',
+ 'planner.placeN': '{n} tempat',
+ 'planner.addNote': 'Tambah catatan',
+ 'planner.noEntries': 'Tidak ada entri untuk hari ini',
+ 'planner.addPlace': 'Tambah tempat/aktivitas',
+ 'planner.addPlaceShort': '+ Tambah tempat/aktivitas',
+ 'planner.resPending': 'Reservasi tertunda · ',
+ 'planner.resConfirmed': 'Reservasi dikonfirmasi · ',
+ 'planner.notePlaceholder': 'Catatan…',
+ 'planner.noteTimePlaceholder': 'Waktu (opsional)',
+ 'planner.noteExamplePlaceholder':
+ 'mis. S3 pukul 14:30 dari stasiun pusat, feri dari dermaga 7, istirahat makan siang…',
+ 'planner.totalCost': 'Total biaya',
+ 'planner.searchPlaces': 'Cari tempat…',
+ 'planner.allCategories': 'Semua Kategori',
+ 'planner.noPlacesFound': 'Tempat tidak ditemukan',
+ 'planner.addFirstPlace': 'Tambah tempat pertama',
+ 'planner.noReservations': 'Tidak ada reservasi',
+ 'planner.addFirstReservation': 'Tambah reservasi pertama',
+ 'planner.new': 'Baru',
+ 'planner.addToDay': '+ Hari',
+ 'planner.calculating': 'Menghitung…',
+ 'planner.route': 'Rute',
+ 'planner.optimize': 'Optimalkan',
+ 'planner.openGoogleMaps': 'Buka di Google Maps',
+ 'planner.selectDayHint':
+ 'Pilih hari dari daftar kiri untuk melihat rencana hari',
+ 'planner.noPlacesForDay': 'Belum ada tempat untuk hari ini',
+ 'planner.addPlacesLink': 'Tambah tempat →',
+ 'planner.minTotal': 'total min.',
+ 'planner.noReservation': 'Tidak ada reservasi',
+ 'planner.removeFromDay': 'Hapus dari hari',
+ 'planner.addToThisDay': 'Tambah ke hari ini',
+ 'planner.overview': 'Ikhtisar',
+ 'planner.noDays': 'Belum ada hari',
+ 'planner.editTripToAddDays': 'Edit perjalanan untuk menambah hari',
+ 'planner.dayCount': '{n} Hari',
+ 'planner.clickToUnlock': 'Klik untuk membuka kunci',
+ 'planner.keepPosition': 'Pertahankan posisi saat mengoptimalkan rute',
+ 'planner.dayDetails': 'Detail hari',
+ 'planner.dayN': 'Hari {n}',
+};
+export default planner;
diff --git a/shared/src/i18n/id/register.ts b/shared/src/i18n/id/register.ts
new file mode 100644
index 0000000000000000000000000000000000000000..130d1febfd59b3acf03ad0d8a701694e0acc6295
--- /dev/null
+++ b/shared/src/i18n/id/register.ts
@@ -0,0 +1,25 @@
+import type { TranslationStrings } from '../types';
+
+const register: TranslationStrings = {
+ 'register.passwordMismatch': 'Kata sandi tidak cocok',
+ 'register.passwordTooShort': 'Kata sandi minimal 8 karakter',
+ 'register.failed': 'Pendaftaran gagal',
+ 'register.getStarted': 'Mulai Sekarang',
+ 'register.subtitle': 'Buat akun dan mulai rencanakan perjalanan impianmu.',
+ 'register.feature1': 'Rencana perjalanan tak terbatas',
+ 'register.feature2': 'Tampilan peta interaktif',
+ 'register.feature3': 'Kelola tempat dan kategori',
+ 'register.feature4': 'Lacak reservasi',
+ 'register.feature5': 'Buat daftar packing',
+ 'register.feature6': 'Simpan foto dan file',
+ 'register.createAccount': 'Buat Akun',
+ 'register.startPlanning': 'Mulai rencanakan perjalananmu',
+ 'register.minChars': 'Min. 6 karakter',
+ 'register.confirmPassword': 'Konfirmasi Kata Sandi',
+ 'register.repeatPassword': 'Ulangi kata sandi',
+ 'register.registering': 'Mendaftar...',
+ 'register.register': 'Daftar',
+ 'register.hasAccount': 'Sudah punya akun?',
+ 'register.signIn': 'Masuk',
+};
+export default register;
diff --git a/shared/src/i18n/id/reservations.ts b/shared/src/i18n/id/reservations.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5a3ca2fd8957008dd53f335bee6b69999bc250a2
--- /dev/null
+++ b/shared/src/i18n/id/reservations.ts
@@ -0,0 +1,162 @@
+import type { TranslationStrings } from '../types';
+
+const reservations: TranslationStrings = {
+ 'reservations.title': 'Pemesanan',
+ 'reservations.empty': 'Belum ada reservasi',
+ 'reservations.emptyHint':
+ 'Tambahkan reservasi untuk penerbangan, hotel, dan lainnya',
+ 'reservations.add': 'Tambah Reservasi',
+ 'reservations.addManual': 'Pemesanan Manual',
+ 'reservations.placeHint':
+ 'Tips: Reservasi paling baik dibuat langsung dari sebuah tempat agar terhubung dengan rencana harianmu.',
+ 'reservations.confirmed': 'Dikonfirmasi',
+ 'reservations.pending': 'Tertunda',
+ 'reservations.summary': '{confirmed} dikonfirmasi, {pending} tertunda',
+ 'reservations.fromPlan': 'Dari Rencana',
+ 'reservations.showFiles': 'Tampilkan File',
+ 'reservations.editTitle': 'Edit Reservasi',
+ 'reservations.status': 'Status',
+ 'reservations.datetime': 'Tanggal & Waktu',
+ 'reservations.startTime': 'Waktu mulai',
+ 'reservations.endTime': 'Waktu selesai',
+ 'reservations.date': 'Tanggal',
+ 'reservations.time': 'Waktu',
+ 'reservations.timeAlt': 'Waktu (alternatif, mis. 19:30)',
+ 'reservations.notes': 'Catatan',
+ 'reservations.notesPlaceholder': 'Catatan tambahan...',
+ 'reservations.meta.airline': 'Maskapai',
+ 'reservations.meta.flightNumber': 'No. Penerbangan',
+ 'reservations.meta.from': 'Dari',
+ 'reservations.meta.to': 'Ke',
+ 'reservations.layover.route': 'Rute',
+ 'reservations.layover.stop': 'Persinggahan',
+ 'reservations.layover.addStop': 'Tambah persinggahan',
+ 'reservations.layover.connection': 'Sambungan',
+ 'reservations.layover.layover': 'Transit',
+ 'reservations.needsReview': 'Tinjau',
+ 'reservations.needsReviewHint':
+ 'Bandara tidak dapat dicocokkan otomatis — konfirmasi lokasi.',
+ 'reservations.searchLocation': 'Cari stasiun, pelabuhan, alamat...',
+ 'reservations.meta.trainNumber': 'No. Kereta',
+ 'reservations.meta.platform': 'Peron',
+ 'reservations.meta.seat': 'Kursi',
+ 'reservations.meta.checkIn': 'Check-in',
+ 'reservations.meta.checkInUntil': 'Check-in sampai',
+ 'reservations.meta.checkOut': 'Check-out',
+ 'reservations.meta.linkAccommodation': 'Akomodasi',
+ 'reservations.meta.pickAccommodation': 'Hubungkan ke akomodasi',
+ 'reservations.meta.noAccommodation': 'Tidak ada',
+ 'reservations.meta.hotelPlace': 'Akomodasi',
+ 'reservations.meta.pickHotel': 'Pilih akomodasi',
+ 'reservations.meta.fromDay': 'Dari',
+ 'reservations.meta.toDay': 'Sampai',
+ 'reservations.meta.selectDay': 'Pilih hari',
+ 'reservations.type.flight': 'Penerbangan',
+ 'reservations.type.hotel': 'Akomodasi',
+ 'reservations.type.restaurant': 'Restoran',
+ 'reservations.type.train': 'Kereta',
+ 'reservations.type.car': 'Mobil',
+ 'reservations.type.cruise': 'Kapal Pesiar',
+ 'reservations.type.event': 'Acara',
+ 'reservations.type.tour': 'Tur',
+ 'reservations.type.other': 'Lainnya',
+ 'reservations.type.bus': 'Bus',
+ 'reservations.type.ferry': 'Feri',
+ 'reservations.type.bicycle': 'Sepeda',
+ 'reservations.type.taxi': 'Taksi',
+ 'reservations.type.transport_other': 'Lainnya',
+ 'reservations.confirm.delete': 'Yakin ingin menghapus reservasi "{name}"?',
+ 'reservations.confirm.deleteTitle': 'Hapus pemesanan?',
+ 'reservations.confirm.deleteBody': '"{name}" akan dihapus permanen.',
+ 'reservations.toast.updated': 'Reservasi diperbarui',
+ 'reservations.toast.removed': 'Reservasi dihapus',
+ 'reservations.toast.fileUploaded': 'File diunggah',
+ 'reservations.toast.uploadError': 'Gagal mengunggah',
+ 'reservations.newTitle': 'Reservasi Baru',
+ 'reservations.bookingType': 'Jenis Pemesanan',
+ 'reservations.titleLabel': 'Judul',
+ 'reservations.titlePlaceholder': 'mis. Lufthansa LH123, Hotel Adlon, ...',
+ 'reservations.locationAddress': 'Lokasi / Alamat',
+ 'reservations.locationPlaceholder': 'Alamat, Bandara, Hotel...',
+ 'reservations.confirmationCode': 'Kode Pemesanan',
+ 'reservations.confirmationPlaceholder': 'mis. ABC12345',
+ 'reservations.day': 'Hari',
+ 'reservations.noDay': 'Tanpa Hari',
+ 'reservations.place': 'Tempat',
+ 'reservations.noPlace': 'Tanpa Tempat',
+ 'reservations.pendingSave': 'akan disimpan…',
+ 'reservations.uploading': 'Mengunggah...',
+ 'reservations.attachFile': 'Lampirkan file',
+ 'reservations.linkExisting': 'Hubungkan file yang ada',
+ 'reservations.toast.saveError': 'Gagal menyimpan',
+ 'reservations.toast.updateError': 'Gagal memperbarui',
+ 'reservations.toast.deleteError': 'Gagal menghapus',
+ 'reservations.confirm.remove': 'Hapus reservasi untuk "{name}"?',
+ 'reservations.linkAssignment': 'Hubungkan ke jadwal harian',
+ 'reservations.pickAssignment': 'Pilih jadwal dari rencanamu...',
+ 'reservations.noAssignment': 'Tanpa tautan (mandiri)',
+ 'reservations.price': 'Harga',
+ 'reservations.budgetCategory': 'Kategori anggaran',
+ 'reservations.budgetCategoryPlaceholder': 'mis. Transportasi, Akomodasi',
+ 'reservations.budgetCategoryAuto': 'Otomatis (dari jenis pemesanan)',
+ 'reservations.budgetHint':
+ 'Entri anggaran akan dibuat otomatis saat menyimpan.',
+ 'reservations.departureDate': 'Keberangkatan',
+ 'reservations.arrivalDate': 'Kedatangan',
+ 'reservations.departureTime': 'Waktu berangkat',
+ 'reservations.arrivalTime': 'Waktu tiba',
+ 'reservations.pickupDate': 'Penjemputan',
+ 'reservations.returnDate': 'Pengembalian',
+ 'reservations.pickupTime': 'Waktu jemput',
+ 'reservations.returnTime': 'Waktu kembali',
+ 'reservations.endDate': 'Tanggal selesai',
+ 'reservations.meta.departureTimezone': 'TZ Berangkat',
+ 'reservations.meta.arrivalTimezone': 'TZ Tiba',
+ 'reservations.span.departure': 'Keberangkatan',
+ 'reservations.span.arrival': 'Kedatangan',
+ 'reservations.span.inTransit': 'Dalam perjalanan',
+ 'reservations.span.pickup': 'Penjemputan',
+ 'reservations.span.return': 'Pengembalian',
+ 'reservations.span.active': 'Aktif',
+ 'reservations.span.start': 'Mulai',
+ 'reservations.span.end': 'Selesai',
+ 'reservations.span.ongoing': 'Berlangsung',
+ 'reservations.validation.endBeforeStart':
+ 'Tanggal/waktu selesai harus setelah tanggal/waktu mulai',
+ 'reservations.addBooking': 'Tambah pemesanan',
+ 'reservations.import.title': 'Impor konfirmasi pemesanan',
+ 'reservations.import.cta': 'Impor dari file',
+ 'reservations.import.dropHere': 'Seret file konfirmasi pemesanan ke sini atau klik untuk memilih',
+ 'reservations.import.dropActive': 'Lepaskan file untuk mengimpor',
+ 'reservations.import.acceptedFormats': 'Diterima: EML, PDF, PKPass, HTML, TXT (maks. 10 MB per file, hingga 5 file)',
+ 'reservations.import.parsing': 'Memproses file…',
+ 'reservations.import.previewHeading': '{count} pemesanan ditemukan',
+ 'reservations.import.previewEmpty': 'Tidak ada pemesanan yang dapat diekstrak dari file yang diunggah.',
+ 'reservations.import.removeItem': 'Hapus',
+ 'reservations.import.confirm': 'Impor {count} pemesanan',
+ 'reservations.import.back': 'Kembali',
+ 'reservations.import.success': '{count} pemesanan berhasil diimpor',
+ 'reservations.import.partialFailure': '{created} berhasil diimpor, {failed} gagal',
+ 'reservations.import.error': 'Pemrosesan gagal. Pastikan file adalah konfirmasi pemesanan yang valid.',
+ 'reservations.import.unavailable': 'Impor pemesanan tidak tersedia di server ini.',
+ 'reservations.import.unsupportedFormat': 'Format file tidak didukung. Gunakan EML, PDF, PKPass, HTML, atau TXT.',
+ 'reservations.import.fileTooLarge': 'File "{name}" melebihi batas 10 MB.',
+ 'reservations.airtrail.title': 'Impor dari AirTrail',
+ 'reservations.airtrail.cta': 'AirTrail',
+ 'reservations.airtrail.synced': 'AirTrail',
+ 'reservations.airtrail.syncedHint': 'Tersinkron dari AirTrail — perubahan tetap sinkron di kedua arah.',
+ 'reservations.airtrail.notSynced': 'Tidak tersinkron',
+ 'reservations.airtrail.notSyncedHint': 'Penerbangan ini telah dihapus di AirTrail dan tidak lagi tersinkron.',
+ 'reservations.airtrail.loadError': 'Tidak dapat memuat penerbangan AirTrail-mu.',
+ 'reservations.airtrail.imported': '{count} penerbangan diimpor',
+ 'reservations.airtrail.skippedDuplicate': '{count} sudah ada di perjalanan ini, dilewati',
+ 'reservations.airtrail.nothingImported': 'Tidak ada yang dapat diimpor.',
+ 'reservations.airtrail.importError': 'Impor gagal. Silakan coba lagi.',
+ 'reservations.airtrail.undo': 'Impor dari AirTrail',
+ 'reservations.airtrail.alreadyImported': 'Diimpor',
+ 'reservations.airtrail.duringTrip': 'Selama perjalanan ini',
+ 'reservations.airtrail.otherFlights': 'Penerbangan lain',
+ 'reservations.airtrail.empty': 'Tidak ada penerbangan ditemukan di akun AirTrail-mu.',
+ 'reservations.airtrail.importCta': 'Impor {count}',
+};
+export default reservations;
diff --git a/shared/src/i18n/id/settings.ts b/shared/src/i18n/id/settings.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dac5c26979f7e21cb1967de12c2b0ae9232dd8f8
--- /dev/null
+++ b/shared/src/i18n/id/settings.ts
@@ -0,0 +1,346 @@
+import type { TranslationStrings } from '../types';
+
+const settings: TranslationStrings = {
+ 'settings.title': 'Pengaturan',
+ 'settings.subtitle': 'Atur pengaturan pribadimu',
+ 'settings.tabs.display': 'Tampilan',
+ 'settings.tabs.map': 'Peta',
+ 'settings.tabs.notifications': 'Notifikasi',
+ 'settings.tabs.integrations': 'Integrasi',
+ 'settings.tabs.account': 'Akun',
+ 'settings.tabs.offline': 'Offline',
+ 'settings.tabs.about': 'Tentang',
+ 'settings.map': 'Peta',
+ 'settings.mapTemplate': 'Template Peta',
+ 'settings.mapTemplatePlaceholder.select': 'Pilih template...',
+ 'settings.mapDefaultHint': 'Kosongkan untuk OpenStreetMap (default)',
+ 'settings.mapTemplatePlaceholder':
+ 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
+ 'settings.mapHint': 'Template URL untuk tile peta',
+ 'settings.mapProvider': 'Penyedia peta',
+ 'settings.mapProviderHint':
+ 'Berlaku untuk peta Trip Planner dan Journey. Atlas selalu menggunakan Leaflet.',
+ 'settings.mapLeafletSubtitle': 'Klasik 2D, tile raster apa pun',
+ 'settings.mapMapboxSubtitle': 'Tile vektor, bangunan 3D & medan',
+ 'settings.mapExperimental': 'Eksperimental',
+ 'settings.mapMapboxToken': 'Token akses Mapbox',
+ 'settings.mapMapboxTokenHint': 'Token publik (pk.*) dari',
+ 'settings.mapMapboxTokenLink': 'mapbox.com → Token akses',
+ 'settings.mapStyle': 'Gaya peta',
+ 'settings.mapStylePlaceholder': 'Pilih gaya Mapbox',
+ 'settings.mapStyleHint': 'Preset atau URL mapbox://styles/USER/ID milikmu',
+ 'settings.map3dBuildings': 'Bangunan 3D & medan',
+ 'settings.map3dHint':
+ 'Kemiringan + ekstrusi bangunan 3D nyata — bekerja di semua gaya, termasuk satelit.',
+ 'settings.mapHighQuality': 'Mode kualitas tinggi',
+ 'settings.mapHighQualityHint':
+ 'Antialiasing + proyeksi globe untuk tepi yang lebih tajam dan tampilan dunia realistis.',
+ 'settings.mapHighQualityWarning':
+ 'Dapat memengaruhi performa pada perangkat kelas bawah.',
+ 'settings.mapTipLabel': 'Tip:',
+ 'settings.mapTip':
+ 'Klik kanan dan seret untuk memutar/memiringkan peta. Klik tengah untuk menambah tempat (klik kanan untuk rotasi).',
+ 'settings.latitude': 'Lintang',
+ 'settings.longitude': 'Bujur',
+ 'settings.saveMap': 'Simpan Peta',
+ 'settings.apiKeys': 'API Keys',
+ 'settings.mapsKey': 'Google Maps API Key',
+ 'settings.mapsKeyHint':
+ 'Untuk pencarian tempat. Memerlukan Places API (New). Dapatkan di console.cloud.google.com',
+ 'settings.weatherKey': 'OpenWeatherMap API Key',
+ 'settings.weatherKeyHint':
+ 'Untuk data cuaca. Gratis di openweathermap.org/api',
+ 'settings.keyPlaceholder': 'Masukkan key...',
+ 'settings.configured': 'Sudah dikonfigurasi',
+ 'settings.saveKeys': 'Simpan Keys',
+ 'settings.display': 'Tampilan',
+ 'settings.colorMode': 'Mode Warna',
+ 'settings.light': 'Terang',
+ 'settings.dark': 'Gelap',
+ 'settings.auto': 'Otomatis',
+ 'settings.language': 'Bahasa',
+ 'settings.temperature': 'Satuan Suhu',
+ 'settings.timeFormat': 'Format Waktu',
+ 'settings.blurBookingCodes': 'Sembunyikan Kode Pemesanan',
+ 'settings.optimizeFromAccommodation': 'Optimalkan rute dari akomodasi',
+ 'settings.optimizeFromAccommodationHint':
+ 'Saat mengoptimalkan suatu hari, mulai rute dari hotel tempatmu bangun pagi dan akhiri di hotel tempatmu check-in malam itu.',
+ 'settings.notifications': 'Notifikasi',
+ 'settings.notifyTripInvite': 'Undangan perjalanan',
+ 'settings.notifyBookingChange': 'Perubahan pemesanan',
+ 'settings.notifyTripReminder': 'Pengingat perjalanan',
+ 'settings.notifyTodoDue': 'Tugas jatuh tempo',
+ 'settings.notifyVacayInvite': 'Undangan Vacay fusion',
+ 'settings.notifyPhotosShared': 'Foto dibagikan (Immich)',
+ 'settings.notifyCollabMessage': 'Pesan chat (Collab)',
+ 'settings.notifyPackingTagged': 'Daftar bawaan: penugasan',
+ 'settings.notifyWebhook': 'Notifikasi webhook',
+ 'settings.notifyVersionAvailable': 'Versi baru tersedia',
+ 'settings.notificationPreferences.email': 'Email',
+ 'settings.notificationPreferences.webhook': 'Webhook',
+ 'settings.notificationPreferences.inapp': 'In-App',
+ 'settings.notificationPreferences.ntfy': 'Ntfy',
+ 'settings.notificationPreferences.noChannels':
+ 'Belum ada saluran notifikasi yang dikonfigurasi. Minta admin untuk mengatur notifikasi email atau webhook.',
+ 'settings.webhookUrl.label': 'Webhook URL',
+ 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...',
+ 'settings.webhookUrl.hint':
+ 'Masukkan URL webhook Discord, Slack, atau kustom untuk menerima notifikasi.',
+ 'settings.webhookUrl.saved': 'Webhook URL tersimpan',
+ 'settings.webhookUrl.test': 'Uji',
+ 'settings.webhookUrl.testSuccess': 'Test webhook berhasil dikirim',
+ 'settings.webhookUrl.testFailed': 'Test webhook gagal',
+ 'settings.ntfyUrl.topicLabel': 'Topik Ntfy',
+ 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts',
+ 'settings.ntfyUrl.serverLabel': 'URL Server Ntfy (opsional)',
+ 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh',
+ 'settings.ntfyUrl.hint':
+ 'Masukkan topik Ntfy Anda untuk menerima notifikasi push. Kosongkan bidang server untuk menggunakan default yang dikonfigurasi oleh admin Anda.',
+ 'settings.ntfyUrl.tokenLabel': 'Token Akses (opsional)',
+ 'settings.ntfyUrl.tokenHint':
+ 'Diperlukan untuk topik yang dilindungi kata sandi.',
+ 'settings.ntfyUrl.saved': 'Pengaturan Ntfy tersimpan',
+ 'settings.ntfyUrl.test': 'Uji',
+ 'settings.ntfyUrl.testSuccess': 'Notifikasi uji Ntfy berhasil dikirim',
+ 'settings.ntfyUrl.testFailed': 'Notifikasi uji Ntfy gagal',
+ 'settings.ntfyUrl.tokenCleared': 'Token akses dihapus',
+ 'settings.notificationsDisabled':
+ 'Notifikasi belum dikonfigurasi. Minta admin untuk mengaktifkan notifikasi email atau webhook.',
+ 'settings.notificationsActive': 'Saluran aktif',
+ 'settings.notificationsManagedByAdmin':
+ 'Acara notifikasi dikonfigurasi oleh administrator.',
+ 'settings.on': 'Aktif',
+ 'settings.off': 'Nonaktif',
+ 'settings.mcp.title': 'Konfigurasi MCP',
+ 'settings.mcp.endpoint': 'MCP Endpoint',
+ 'settings.mcp.clientConfig': 'Konfigurasi Client',
+ 'settings.mcp.clientConfigHint':
+ 'Ganti dengan API token dari daftar di bawah. Path ke npx mungkin perlu disesuaikan untuk sistemmu (mis. C:\\PROGRA~1\\nodejs\\npx.cmd di Windows).',
+ 'settings.mcp.clientConfigHintOAuth':
+ 'Ganti dan dengan kredensial dari klien OAuth 2.1 yang kamu buat di atas. mcp-remote akan membuka browser untuk menyelesaikan otorisasi pertama kali kamu terhubung. Path ke npx mungkin perlu disesuaikan untuk sistemmu (misalnya C:\\PROGRA~1\\nodejs\\npx.cmd di Windows).',
+ 'settings.mcp.copy': 'Salin',
+ 'settings.mcp.copied': 'Disalin!',
+ 'settings.mcp.apiTokens': 'API Tokens',
+ 'settings.mcp.createToken': 'Buat Token Baru',
+ 'settings.mcp.noTokens':
+ 'Belum ada token. Buat satu untuk menghubungkan MCP client.',
+ 'settings.mcp.tokenCreatedAt': 'Dibuat',
+ 'settings.mcp.tokenUsedAt': 'Digunakan',
+ 'settings.mcp.deleteTokenTitle': 'Hapus Token',
+ 'settings.mcp.deleteTokenMessage':
+ 'Token ini akan langsung berhenti bekerja. MCP client yang menggunakannya akan kehilangan akses.',
+ 'settings.mcp.modal.createTitle': 'Buat API Token',
+ 'settings.mcp.modal.tokenName': 'Nama Token',
+ 'settings.mcp.modal.tokenNamePlaceholder':
+ 'mis. Claude Desktop, Laptop kerja',
+ 'settings.mcp.modal.creating': 'Membuat…',
+ 'settings.mcp.modal.create': 'Buat Token',
+ 'settings.mcp.modal.createdTitle': 'Token Dibuat',
+ 'settings.mcp.modal.createdWarning':
+ 'Token ini hanya ditampilkan sekali. Salin dan simpan sekarang — tidak bisa dipulihkan.',
+ 'settings.mcp.modal.done': 'Selesai',
+ 'settings.mcp.toast.created': 'Token dibuat',
+ 'settings.mcp.toast.createError': 'Gagal membuat token',
+ 'settings.mcp.toast.deleted': 'Token dihapus',
+ 'settings.mcp.toast.deleteError': 'Gagal menghapus token',
+ 'settings.mcp.apiTokensDeprecated':
+ 'API Token sudah tidak digunakan dan akan dihapus di rilis mendatang. Gunakan OAuth 2.1 Client sebagai gantinya.',
+ 'settings.oauth.clients': 'Klien OAuth 2.1',
+ 'settings.oauth.clientsHint':
+ 'Daftarkan klien OAuth 2.1 agar aplikasi MCP pihak ketiga (Claude Web, Cursor, dll.) dapat terhubung tanpa token statis.',
+ 'settings.oauth.createClient': 'Klien Baru',
+ 'settings.oauth.noClients': 'Belum ada klien OAuth yang terdaftar.',
+ 'settings.oauth.clientId': 'ID Klien',
+ 'settings.oauth.clientSecret': 'Rahasia Klien',
+ 'settings.oauth.deleteClient': 'Hapus Klien',
+ 'settings.oauth.deleteClientMessage':
+ 'Klien ini dan semua sesi aktif akan dihapus permanen. Aplikasi yang menggunakannya akan langsung kehilangan akses.',
+ 'settings.oauth.rotateSecret': 'Putar Ulang Secret',
+ 'settings.oauth.rotateSecretMessage':
+ 'Secret klien baru akan dibuat dan semua sesi yang ada langsung dibatalkan. Perbarui aplikasimu sebelum menutup dialog ini.',
+ 'settings.oauth.rotateSecretConfirm': 'Putar Ulang',
+ 'settings.oauth.rotateSecretConfirming': 'Memutar ulang…',
+ 'settings.oauth.rotateSecretDoneTitle': 'Secret Baru Dibuat',
+ 'settings.oauth.rotateSecretDoneWarning':
+ 'Secret ini hanya ditampilkan sekali. Salin sekarang dan perbarui aplikasimu — semua sesi sebelumnya telah dibatalkan.',
+ 'settings.oauth.activeSessions': 'Sesi OAuth Aktif',
+ 'settings.oauth.sessionScopes': 'Cakupan',
+ 'settings.oauth.sessionExpires': 'Kedaluwarsa',
+ 'settings.oauth.revoke': 'Cabut',
+ 'settings.oauth.revokeSession': 'Cabut Sesi',
+ 'settings.oauth.revokeSessionMessage':
+ 'Ini akan segera mencabut akses untuk sesi OAuth ini.',
+ 'settings.oauth.modal.createTitle': 'Daftarkan OAuth Client',
+ 'settings.oauth.modal.presets': 'Preset cepat',
+ 'settings.oauth.modal.clientName': 'Nama Aplikasi',
+ 'settings.oauth.modal.clientNamePlaceholder':
+ 'mis. Claude Web, Aplikasi MCP Saya',
+ 'settings.oauth.modal.redirectUris': 'Redirect URI',
+ 'settings.oauth.modal.redirectUrisPlaceholder':
+ 'https://aplikasiku.com/callback\nhttps://aplikasiku.com/auth',
+ 'settings.oauth.modal.redirectUrisHint':
+ 'Satu URI per baris. HTTPS wajib (localhost dikecualikan). Kecocokan tepat diberlakukan.',
+ 'settings.oauth.modal.scopes': 'Cakupan yang Diizinkan',
+ 'settings.oauth.modal.scopesHint':
+ 'list_trips dan get_trip_summary selalu tersedia — tidak perlu cakupan. Keduanya memungkinkan AI menemukan ID perjalanan yang diperlukan untuk menggunakan alat lainnya.',
+ 'settings.oauth.modal.selectAll': 'Pilih semua',
+ 'settings.oauth.modal.deselectAll': 'Batalkan semua',
+ 'settings.oauth.modal.creating': 'Mendaftarkan…',
+ 'settings.oauth.modal.create': 'Daftarkan Client',
+ 'settings.oauth.modal.createdTitle': 'Client Terdaftar',
+ 'settings.oauth.modal.createdWarning':
+ 'Client secret hanya ditampilkan sekali. Salin sekarang — tidak dapat dipulihkan.',
+ 'settings.oauth.toast.createError': 'Gagal mendaftarkan klien OAuth',
+ 'settings.oauth.toast.deleted': 'Klien OAuth dihapus',
+ 'settings.oauth.toast.deleteError': 'Gagal menghapus klien OAuth',
+ 'settings.oauth.toast.revoked': 'Sesi dicabut',
+ 'settings.oauth.toast.revokeError': 'Gagal mencabut sesi',
+ 'settings.oauth.toast.rotateError': 'Gagal memutar ulang client secret',
+ 'settings.oauth.modal.machineClient': 'Klien mesin (tanpa login browser)',
+ 'settings.oauth.modal.machineClientHint':
+ 'Menggunakan grant client_credentials — tidak perlu URI pengalihan. Token diterbitkan langsung melalui client_id + client_secret dan bertindak sebagai Anda dalam cakupan yang dipilih.',
+ 'settings.oauth.modal.machineClientUsage':
+ 'Dapatkan token: POST /oauth/token dengan grant_type=client_credentials, client_id, dan client_secret. Tanpa browser, tanpa refresh token.',
+ 'settings.oauth.badge.machine': 'mesin',
+ 'settings.account': 'Akun',
+ 'settings.about': 'Tentang',
+ 'settings.about.reportBug': 'Laporkan Bug',
+ 'settings.about.reportBugHint': 'Menemukan masalah? Beri tahu kami',
+ 'settings.about.featureRequest': 'Permintaan Fitur',
+ 'settings.about.featureRequestHint': 'Sarankan fitur baru',
+ 'settings.about.wikiHint': 'Dokumentasi & panduan',
+ 'settings.about.supporters.badge': 'Pendukung Bulanan',
+ 'settings.about.supporters.title': 'Rekan perjalanan untuk TREK',
+ 'settings.about.supporters.subtitle':
+ 'Saat kamu merencanakan rute berikutnya, orang-orang ini ikut merencanakan masa depan TREK. Kontribusi bulanan mereka langsung masuk ke pengembangan dan jam kerja nyata — supaya TREK tetap Open Source.',
+ 'settings.about.supporters.since': 'pendukung sejak {date}',
+ 'settings.about.supporters.tierEmpty': 'Jadilah yang pertama',
+ 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket',
+ 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP',
+ 'settings.about.supporter.tier.businessClassDreamer':
+ 'Business Class Dreamer',
+ 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller',
+ 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate',
+ 'settings.about.description':
+ 'TREK adalah perencana perjalanan self-hosted yang membantu kamu mengatur perjalanan dari ide pertama hingga kenangan terakhir. Perencanaan harian, anggaran, daftar bawaan, foto dan masih banyak lagi — semua di satu tempat, di servermu sendiri.',
+ 'settings.about.madeWith': 'Dibuat dengan',
+ 'settings.about.madeBy':
+ 'oleh Maurice dan komunitas open-source yang terus berkembang.',
+ 'settings.username': 'Nama pengguna',
+ 'settings.email': 'Email',
+ 'settings.role': 'Peran',
+ 'settings.roleAdmin': 'Administrator',
+ 'settings.oidcLinked': 'Terhubung dengan',
+ 'settings.changePassword': 'Ganti Kata Sandi',
+ 'settings.currentPassword': 'Kata sandi saat ini',
+ 'settings.currentPasswordRequired': 'Kata sandi saat ini wajib diisi',
+ 'settings.newPassword': 'Kata sandi baru',
+ 'settings.confirmPassword': 'Konfirmasi kata sandi baru',
+ 'settings.updatePassword': 'Perbarui kata sandi',
+ 'settings.passwordRequired':
+ 'Masukkan kata sandi saat ini dan kata sandi baru',
+ 'settings.passwordTooShort': 'Kata sandi minimal 8 karakter',
+ 'settings.passwordMismatch': 'Kata sandi tidak cocok',
+ 'settings.passwordWeak':
+ 'Kata sandi harus mengandung huruf besar, huruf kecil, angka, dan karakter khusus',
+ 'settings.passwordChanged': 'Kata sandi berhasil diubah',
+ 'settings.mustChangePassword':
+ 'Kamu harus mengubah kata sandi sebelum melanjutkan. Atur kata sandi baru di bawah ini.',
+ 'settings.deleteAccount': 'Hapus akun',
+ 'settings.deleteAccountTitle': 'Hapus akunmu?',
+ 'settings.deleteAccountWarning':
+ 'Akunmu beserta semua perjalanan, tempat, dan file akan dihapus permanen. Tindakan ini tidak bisa dibatalkan.',
+ 'settings.deleteAccountConfirm': 'Hapus permanen',
+ 'settings.deleteBlockedTitle': 'Penghapusan tidak memungkinkan',
+ 'settings.deleteBlockedMessage':
+ 'Kamu satu-satunya administrator. Promosikan pengguna lain menjadi admin sebelum menghapus akunmu.',
+ 'settings.roleUser': 'Pengguna',
+ 'settings.saveProfile': 'Simpan Profil',
+ 'settings.toast.mapSaved': 'Pengaturan peta tersimpan',
+ 'settings.toast.keysSaved': 'API keys tersimpan',
+ 'settings.toast.displaySaved': 'Pengaturan tampilan tersimpan',
+ 'settings.toast.profileSaved': 'Profil tersimpan',
+ 'settings.uploadAvatar': 'Unggah Foto Profil',
+ 'settings.removeAvatar': 'Hapus Foto Profil',
+ 'settings.avatarUploaded': 'Foto profil diperbarui',
+ 'settings.avatarRemoved': 'Foto profil dihapus',
+ 'settings.avatarError': 'Gagal mengunggah',
+ 'settings.mfa.title': 'Autentikasi dua faktor (2FA)',
+ 'settings.mfa.description':
+ 'Menambahkan langkah kedua saat masuk dengan email dan kata sandi. Gunakan aplikasi autentikator (Google Authenticator, Authy, dll.).',
+ 'settings.mfa.requiredByPolicy':
+ 'Administrator mengharuskan autentikasi dua faktor. Atur aplikasi autentikator di bawah ini sebelum melanjutkan.',
+ 'settings.mfa.backupTitle': 'Kode cadangan',
+ 'settings.mfa.backupDescription':
+ 'Gunakan kode cadangan sekali pakai ini jika kamu kehilangan akses ke aplikasi autentikator.',
+ 'settings.mfa.backupWarning':
+ 'Simpan kode ini sekarang. Setiap kode hanya bisa digunakan sekali.',
+ 'settings.mfa.backupCopy': 'Salin kode',
+ 'settings.mfa.backupDownload': 'Unduh TXT',
+ 'settings.mfa.backupPrint': 'Cetak / PDF',
+ 'settings.mfa.backupCopied': 'Kode cadangan disalin',
+ 'settings.mfa.enabled': '2FA aktif di akunmu.',
+ 'settings.mfa.disabled': '2FA belum diaktifkan.',
+ 'settings.mfa.setup': 'Atur autentikator',
+ 'settings.mfa.scanQr':
+ 'Pindai kode QR ini dengan aplikasimu, atau masukkan secret secara manual.',
+ 'settings.mfa.secretLabel': 'Kunci secret (entri manual)',
+ 'settings.mfa.codePlaceholder': 'Kode 6 digit',
+ 'settings.mfa.enable': 'Aktifkan 2FA',
+ 'settings.mfa.cancelSetup': 'Batal',
+ 'settings.mfa.disableTitle': 'Nonaktifkan 2FA',
+ 'settings.mfa.disableHint':
+ 'Masukkan kata sandi akun dan kode terkini dari aplikasi autentikatormu.',
+ 'settings.mfa.disable': 'Nonaktifkan 2FA',
+ 'settings.mfa.toastEnabled': 'Autentikasi dua faktor diaktifkan',
+ 'settings.mfa.toastDisabled': 'Autentikasi dua faktor dinonaktifkan',
+ 'settings.mfa.demoBlocked': 'Tidak tersedia dalam mode demo',
+ 'settings.bookingLabels': 'Label rute pemesanan',
+ 'settings.bookingLabelsHint':
+ 'Menampilkan nama stasiun / bandara di peta. Jika mati, hanya ikon ditampilkan.',
+ "settings.currency": "Currency",
+ "settings.currencyHint": "All amounts in Costs are converted to and shown in this currency.",
+ 'settings.passkey.title': 'Passkey',
+ 'settings.passkey.description':
+ 'Masuk lebih cepat dan tahan terhadap phishing dengan passkey — sidik jari, wajah, PIN, atau kunci keamanan fisik kamu. Kata sandimu tetap tersedia sebagai cadangan.',
+ 'settings.passkey.notConfigured':
+ 'Passkey diaktifkan tetapi belum sepenuhnya dikonfigurasi di server ini. Minta administratormu untuk mengatur domain WebAuthn.',
+ 'settings.passkey.add': 'Tambah passkey',
+ 'settings.passkey.addTitle': 'Tambah passkey',
+ 'settings.passkey.passwordPrompt':
+ 'Konfirmasi kata sandimu saat ini, lalu ikuti petunjuk di perangkatmu.',
+ 'settings.passkey.passwordRequired': 'Kata sandimu saat ini wajib diisi.',
+ 'settings.passkey.namePlaceholder': 'Nama (opsional, mis. "iPhone")',
+ 'settings.passkey.addedToast': 'Passkey ditambahkan',
+ 'settings.passkey.added': 'Ditambahkan',
+ 'settings.passkey.addError': 'Gagal menambahkan passkey',
+ 'settings.passkey.cancelled': 'Penyiapan passkey dibatalkan',
+ 'settings.passkey.deleted': 'Passkey dihapus',
+ 'settings.passkey.deleteConfirm':
+ 'Hapus passkey ini? Konfirmasi dengan kata sandimu.',
+ 'settings.passkey.rename': 'Ganti nama',
+ 'settings.passkey.defaultName': 'Passkey',
+ 'settings.passkey.synced': 'Tersinkron',
+ 'settings.passkey.deviceBound': 'Perangkat ini',
+ 'settings.passkey.lastUsed': 'Terakhir digunakan',
+ 'settings.passkey.neverUsed': 'Belum pernah digunakan',
+ 'settings.mapPoiPill': 'Jelajahi tempat di peta',
+ 'settings.mapPoiPillHint': 'Tampilkan pil kategori di peta perjalanan untuk menemukan restoran, hotel, dan lainnya di sekitar dari OpenStreetMap.',
+ 'settings.airtrail.title': 'AirTrail',
+ 'settings.airtrail.hint': 'Hubungkan AirTrail yang kamu host sendiri untuk mengimpor dan menyinkronkan penerbangan. Buat kunci API di AirTrail pada Pengaturan → Keamanan.',
+ 'settings.airtrail.url': 'URL Instans',
+ 'settings.airtrail.apiKey': 'Kunci API',
+ 'settings.airtrail.apiKeyPlaceholder': 'Kunci API Bearer',
+ 'settings.airtrail.apiKeyHint': 'Dibuat di AirTrail pada Pengaturan → Keamanan. Disimpan terenkripsi.',
+ 'settings.airtrail.allowInsecureTls': 'Izinkan sertifikat yang ditandatangani sendiri',
+ 'settings.airtrail.allowInsecureTlsHint': 'Aktifkan hanya untuk instans tepercaya di jaringanmu sendiri.',
+ 'settings.airtrail.connected': 'Terhubung',
+ 'settings.airtrail.notConnected': 'Tidak terhubung',
+ 'settings.airtrail.toast.saved': 'Koneksi AirTrail disimpan',
+ 'settings.airtrail.toast.saveError': 'Tidak dapat menyimpan koneksi',
+ 'settings.airtrail.test.button': 'Uji koneksi',
+ 'settings.airtrail.test.success': 'Terhubung — {count} penerbangan ditemukan',
+ 'settings.airtrail.test.failed': 'Koneksi gagal',
+};
+
+export default settings;
diff --git a/shared/src/i18n/id/share.ts b/shared/src/i18n/id/share.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7a8cae6d8b1a5f01184f103025c6329a5465c7a4
--- /dev/null
+++ b/shared/src/i18n/id/share.ts
@@ -0,0 +1,16 @@
+import type { TranslationStrings } from '../types';
+
+const share: TranslationStrings = {
+ 'share.linkTitle': 'Tautan Publik',
+ 'share.linkHint':
+ 'Buat tautan yang bisa digunakan siapa saja untuk melihat perjalanan ini tanpa masuk. Hanya baca — tidak bisa diedit.',
+ 'share.createLink': 'Buat tautan',
+ 'share.deleteLink': 'Hapus tautan',
+ 'share.createError': 'Gagal membuat tautan',
+ 'share.permMap': 'Peta & Rencana',
+ 'share.permBookings': 'Pemesanan',
+ 'share.permPacking': 'Bawaan',
+ 'share.permBudget': 'Anggaran',
+ 'share.permCollab': 'Chat',
+};
+export default share;
diff --git a/shared/src/i18n/id/shared.ts b/shared/src/i18n/id/shared.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9762ba7b5125fe9cb29a6cb0f3ca1ee8bd443567
--- /dev/null
+++ b/shared/src/i18n/id/shared.ts
@@ -0,0 +1,21 @@
+import type { TranslationStrings } from '../types';
+
+const shared: TranslationStrings = {
+ 'shared.expired': 'Tautan kedaluwarsa atau tidak valid',
+ 'shared.expiredHint': 'Tautan perjalanan bersama ini tidak lagi aktif.',
+ 'shared.readOnly': 'Tampilan bersama — hanya baca',
+ 'shared.tabPlan': 'Rencana',
+ 'shared.tabBookings': 'Pemesanan',
+ 'shared.tabPacking': 'Bawaan',
+ 'shared.tabBudget': 'Anggaran',
+ 'shared.tabChat': 'Chat',
+ 'shared.days': 'hari',
+ 'shared.places': 'tempat',
+ 'shared.other': 'Lainnya',
+ 'shared.totalBudget': 'Total Anggaran',
+ 'shared.messages': 'pesan',
+ 'shared.sharedVia': 'Dibagikan via',
+ 'shared.confirmed': 'Dikonfirmasi',
+ 'shared.pending': 'Menunggu',
+};
+export default shared;
diff --git a/shared/src/i18n/id/stats.ts b/shared/src/i18n/id/stats.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e5368e901be86bcada994e127d331549b2299b27
--- /dev/null
+++ b/shared/src/i18n/id/stats.ts
@@ -0,0 +1,13 @@
+import type { TranslationStrings } from '../types';
+
+const stats: TranslationStrings = {
+ 'stats.countries': 'Negara',
+ 'stats.cities': 'Kota',
+ 'stats.trips': 'Perjalanan',
+ 'stats.places': 'Tempat',
+ 'stats.worldProgress': 'Progres Dunia',
+ 'stats.visited': 'dikunjungi',
+ 'stats.remaining': 'tersisa',
+ 'stats.visitedCountries': 'Negara yang Dikunjungi',
+};
+export default stats;
diff --git a/shared/src/i18n/id/system_notice.ts b/shared/src/i18n/id/system_notice.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b0d7e56475aeaf51cb72218f7951fd79f1520ac2
--- /dev/null
+++ b/shared/src/i18n/id/system_notice.ts
@@ -0,0 +1,61 @@
+import type { TranslationStrings } from '../types';
+
+const system_notice: TranslationStrings = {
+ 'system_notice.welcome_v1.title': 'Selamat datang di TREK',
+ 'system_notice.welcome_v1.body':
+ 'Perencana perjalanan lengkap Anda. Buat itinerari, bagikan perjalanan dengan teman, dan tetap terorganisir — online maupun offline.',
+ 'system_notice.welcome_v1.cta_label': 'Rencanakan perjalanan',
+ 'system_notice.welcome_v1.hero_alt':
+ 'Destinasi wisata indah dengan antarmuka TREK',
+ 'system_notice.welcome_v1.highlight_plan':
+ 'Itinerari harian untuk setiap perjalanan',
+ 'system_notice.welcome_v1.highlight_share':
+ 'Berkolaborasi dengan teman perjalanan',
+ 'system_notice.welcome_v1.highlight_offline': 'Bekerja offline di ponsel',
+ 'system_notice.dev_test_modal.title': '[Dev] Test notice',
+ 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.',
+ 'system_notice.pager.prev': 'Pemberitahuan sebelumnya',
+ 'system_notice.pager.next': 'Pemberitahuan berikutnya',
+ 'system_notice.pager.counter': '{current} / {total}',
+ 'system_notice.pager.goto': 'Pergi ke pemberitahuan {n}',
+ 'system_notice.pager.position': 'Pemberitahuan {current} dari {total}',
+ 'system_notice.v3_photos.title': 'Foto dipindahkan di 3.0',
+ 'system_notice.v3_photos.body':
+ '**Foto** di Perencana Perjalanan telah dihapus. Foto Anda aman — TREK tidak pernah mengubah perpustakaan Immich atau Synology Anda.\n\nFoto kini ada di addon **Journey**. Journey bersifat opsional — jika belum tersedia, minta admin untuk mengaktifkannya di Admin → Addon.',
+ 'system_notice.v3_journey.title': 'Kenali Journey — jurnal perjalanan',
+ 'system_notice.v3_journey.body':
+ 'Dokumentasikan perjalanan Anda sebagai cerita hidup dengan linimasa, galeri foto, dan peta interaktif.',
+ 'system_notice.v3_journey.cta_label': 'Buka Journey',
+ 'system_notice.v3_journey.highlight_timeline': 'Linimasa & galeri',
+ 'system_notice.v3_journey.highlight_photos':
+ 'Impor dari Immich atau Synology',
+ 'system_notice.v3_journey.highlight_share':
+ 'Bagikan secara publik — tanpa login',
+ 'system_notice.v3_journey.highlight_export': 'Ekspor sebagai buku foto PDF',
+ 'system_notice.v3_features.title': 'Sorotan lain di 3.0',
+ 'system_notice.v3_features.body': 'Beberapa pembaruan lain dalam rilis ini.',
+ 'system_notice.v3_features.highlight_dashboard':
+ 'Desain ulang dashboard mobile-first',
+ 'system_notice.v3_features.highlight_offline':
+ 'Mode offline penuh sebagai PWA',
+ 'system_notice.v3_features.highlight_search':
+ 'Pelengkapan otomatis tempat secara real-time',
+ 'system_notice.v3_features.highlight_import':
+ 'Impor tempat dari file KMZ/KML',
+ 'system_notice.v3_mcp.title': 'MCP: pembaruan OAuth 2.1',
+ 'system_notice.v3_mcp.body':
+ 'Integrasi MCP telah sepenuhnya diperbarui. OAuth 2.1 kini menjadi metode autentikasi yang direkomendasikan. Token statis (trek_…) sudah usang dan akan dihapus pada versi mendatang.',
+ 'system_notice.v3_mcp.highlight_oauth':
+ 'OAuth 2.1 direkomendasikan (mcp-remote)',
+ 'system_notice.v3_mcp.highlight_scopes': '24 cakupan izin yang terperinci',
+ 'system_notice.v3_mcp.highlight_deprecated': 'Token statis trek_ sudah usang',
+ 'system_notice.v3_mcp.highlight_tools': 'Perangkat dan prompt yang diperluas',
+ 'system_notice.v3_thankyou.title': 'Catatan pribadi dari saya',
+ 'system_notice.v3_thankyou.body':
+ 'Sebelum kamu lanjut — saya ingin berhenti sejenak.\n\nTREK dimulai sebagai proyek sampingan yang saya buat untuk perjalanan saya sendiri. Saya tidak pernah membayangkan ia akan tumbuh menjadi sesuatu yang dipercaya oleh 4.000 dari kalian untuk merencanakan petualangan. Setiap bintang, setiap issue, setiap permintaan fitur — saya membaca semuanya, dan itulah yang membuat saya terus bertahan di malam-malam larut antara pekerjaan penuh waktu dan kuliah.\n\nSaya ingin kalian tahu: TREK akan selalu open source, selalu self-hosted, selalu milik kalian. Tanpa pelacakan, tanpa langganan, tanpa syarat tersembunyi. Hanya sebuah alat yang dibuat oleh seseorang yang mencintai traveling sama seperti kalian.\n\nTerima kasih khusus untuk [jubnl](https://github.com/jubnl) — kamu telah menjadi kolaborator yang luar biasa. Begitu banyak hal yang membuat versi 3.0 hebat memiliki jejakmu. Terima kasih telah percaya pada proyek ini ketika masih kasar.\n\nDan untuk setiap dari kalian yang melaporkan bug, menerjemahkan string, membagikan TREK kepada teman, atau sekadar menggunakannya untuk merencanakan perjalanan — **terima kasih**. Kalianlah alasan semua ini ada.\n\nUntuk lebih banyak petualangan bersama.\n\n— Maurice\n\n---\n\n[Bergabunglah dengan komunitas di Discord](https://discord.gg/7Q6M6jDwzf)\n\nJika TREK membuat perjalananmu lebih baik, [secangkir kopi kecil](https://ko-fi.com/mauriceboe) selalu membantu menjaga lampu tetap menyala.',
+ 'system_notice.v3014_whitespace_collision.title':
+ 'Tindakan diperlukan: konflik akun pengguna',
+ 'system_notice.v3014_whitespace_collision.body':
+ 'Pembaruan 3.0.14 mendeteksi satu atau lebih konflik nama pengguna atau email yang disebabkan oleh spasi di awal atau akhir nilai yang tersimpan. Akun yang terpengaruh telah diganti nama secara otomatis. Periksa log server untuk baris yang dimulai dengan **[migration] WHITESPACE COLLISION** guna mengidentifikasi akun mana yang perlu ditinjau.',
+};
+export default system_notice;
diff --git a/shared/src/i18n/id/todo.ts b/shared/src/i18n/id/todo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0304d741a31679b4b81f3aba35ad32bf687e237a
--- /dev/null
+++ b/shared/src/i18n/id/todo.ts
@@ -0,0 +1,40 @@
+import type { TranslationStrings } from '../types';
+
+const todo: TranslationStrings = {
+ 'todo.subtab.packing': 'Daftar Perlengkapan',
+ 'todo.subtab.todo': 'Tugas',
+ 'todo.completed': 'selesai',
+ 'todo.filter.all': 'Semua',
+ 'todo.filter.open': 'Terbuka',
+ 'todo.filter.done': 'Selesai',
+ 'todo.uncategorized': 'Tanpa kategori',
+ 'todo.namePlaceholder': 'Nama tugas',
+ 'todo.descriptionPlaceholder': 'Deskripsi (opsional)',
+ 'todo.unassigned': 'Belum ditugaskan',
+ 'todo.noCategory': 'Tanpa kategori',
+ 'todo.hasDescription': 'Ada deskripsi',
+ 'todo.addItem': 'Tugas baru',
+ 'todo.sidebar.sortBy': 'Urutkan',
+ 'todo.priority': 'Prioritas',
+ 'todo.newCategoryLabel': 'baru',
+ 'todo.newCategory': 'Nama kategori',
+ 'todo.addCategory': 'Tambah kategori',
+ 'todo.newItem': 'Tugas baru',
+ 'todo.empty': 'Belum ada tugas. Tambah tugas untuk memulai!',
+ 'todo.filter.my': 'Tugasku',
+ 'todo.filter.overdue': 'Terlambat',
+ 'todo.sidebar.tasks': 'Tugas',
+ 'todo.sidebar.categories': 'Kategori',
+ 'todo.detail.title': 'Tugas',
+ 'todo.detail.description': 'Deskripsi',
+ 'todo.detail.category': 'Kategori',
+ 'todo.detail.dueDate': 'Tenggat waktu',
+ 'todo.detail.assignedTo': 'Ditugaskan ke',
+ 'todo.detail.delete': 'Hapus',
+ 'todo.detail.save': 'Simpan perubahan',
+ 'todo.sortByPrio': 'Prioritas',
+ 'todo.detail.priority': 'Prioritas',
+ 'todo.detail.noPriority': 'Tidak ada',
+ 'todo.detail.create': 'Buat tugas',
+};
+export default todo;
diff --git a/shared/src/i18n/id/transport.ts b/shared/src/i18n/id/transport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..55eb314d62e01131590f6ff9922c32e43c931a16
--- /dev/null
+++ b/shared/src/i18n/id/transport.ts
@@ -0,0 +1,10 @@
+import type { TranslationStrings } from '../types';
+
+const transport: TranslationStrings = {
+ 'transport.addTransport': 'Tambah transportasi',
+ 'transport.modalTitle.create': 'Tambah transportasi',
+ 'transport.modalTitle.edit': 'Edit transportasi',
+ 'transport.title': 'Transportasi',
+ 'transport.addManual': 'Transportasi Manual',
+};
+export default transport;
diff --git a/shared/src/i18n/id/trip.ts b/shared/src/i18n/id/trip.ts
new file mode 100644
index 0000000000000000000000000000000000000000..07378d195c13b1e10ac7b1ea3a050a571215989d
--- /dev/null
+++ b/shared/src/i18n/id/trip.ts
@@ -0,0 +1,31 @@
+import type { TranslationStrings } from '../types';
+
+const trip: TranslationStrings = {
+ 'trip.tabs.plan': 'Rencana',
+ 'trip.tabs.transports': 'Transportasi',
+ 'trip.tabs.reservations': 'Pemesanan',
+ 'trip.tabs.reservationsShort': 'Pesan',
+ 'trip.tabs.packing': 'Daftar Perlengkapan',
+ 'trip.tabs.packingShort': 'Perlengkapan',
+ 'trip.tabs.lists': 'Daftar',
+ 'trip.tabs.listsShort': 'Daftar',
+ 'trip.tabs.budget': "Costs",
+ 'trip.tabs.files': 'File',
+ 'trip.loading': 'Memuat perjalanan...',
+ 'trip.loadingPhotos': 'Memuat foto tempat...',
+ 'trip.mobilePlan': 'Rencana',
+ 'trip.mobilePlaces': 'Tempat',
+ 'trip.toast.placeUpdated': 'Tempat diperbarui',
+ 'trip.toast.placeAdded': 'Tempat ditambahkan',
+ 'trip.toast.placeDeleted': 'Tempat dihapus',
+ 'trip.toast.selectDay': 'Pilih hari terlebih dahulu',
+ 'trip.toast.assignedToDay': 'Tempat ditambahkan ke hari',
+ 'trip.toast.reorderError': 'Gagal mengurutkan ulang',
+ 'trip.toast.reservationUpdated': 'Reservasi diperbarui',
+ 'trip.toast.reservationAdded': 'Reservasi ditambahkan',
+ 'trip.toast.deleted': 'Dihapus',
+ 'trip.confirm.deletePlace': 'Apakah kamu yakin ingin menghapus tempat ini?',
+ 'trip.confirm.deletePlaces': 'Hapus {count} tempat?',
+ 'trip.toast.placesDeleted': '{count} tempat dihapus',
+};
+export default trip;
diff --git a/shared/src/i18n/id/trips.ts b/shared/src/i18n/id/trips.ts
new file mode 100644
index 0000000000000000000000000000000000000000..760d8870838350453efb41b7320d1897501368b4
--- /dev/null
+++ b/shared/src/i18n/id/trips.ts
@@ -0,0 +1,17 @@
+import type { TranslationStrings } from '../types';
+
+const trips: TranslationStrings = {
+ 'trips.memberRemoved': '{username} dihapus',
+ 'trips.memberRemoveError': 'Gagal menghapus',
+ 'trips.memberAdded': '{username} ditambahkan',
+ 'trips.memberAddError': 'Gagal menambahkan',
+ 'trips.reminder': 'Pengingat',
+ 'trips.reminderNone': 'Tidak ada',
+ 'trips.reminderDay': 'hari',
+ 'trips.reminderDays': 'hari',
+ 'trips.reminderCustom': 'Kustom',
+ 'trips.reminderDaysBefore': 'hari sebelum keberangkatan',
+ 'trips.reminderDisabledHint':
+ 'Pengingat perjalanan dinonaktifkan. Aktifkan di Admin > Pengaturan > Notifikasi.',
+};
+export default trips;
diff --git a/shared/src/i18n/id/undo.ts b/shared/src/i18n/id/undo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b6de356c49303f7544780b46bff494fc1a262d73
--- /dev/null
+++ b/shared/src/i18n/id/undo.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const undo: TranslationStrings = {
+ 'undo.button': 'Batalkan',
+ 'undo.tooltip': 'Batalkan: {action}',
+ 'undo.assignPlace': 'Tempat ditambahkan ke hari',
+ 'undo.removeAssignment': 'Tempat dihapus dari hari',
+ 'undo.reorder': 'Tempat diurutkan ulang',
+ 'undo.optimize': 'Rute dioptimalkan',
+ 'undo.deletePlace': 'Tempat dihapus',
+ 'undo.deletePlaces': 'Tempat dihapus',
+ 'undo.moveDay': 'Tempat dipindah ke hari lain',
+ 'undo.lock': 'Kunci tempat diubah',
+ 'undo.importGpx': 'Impor GPX',
+ 'undo.importKeyholeMarkup': 'Impor KMZ/KML',
+ 'undo.importGoogleList': 'Impor Google Maps',
+ 'undo.importNaverList': 'Impor Naver Maps',
+ 'undo.addPlace': 'Tempat ditambahkan',
+ 'undo.done': 'Dibatalkan: {action}',
+ 'undo.importBooking': 'Impor konfirmasi pemesanan',
+};
+export default undo;
diff --git a/shared/src/i18n/id/vacay.ts b/shared/src/i18n/id/vacay.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a189efdb6193f3c8fab5abfb91ffd637b0dd2d3e
--- /dev/null
+++ b/shared/src/i18n/id/vacay.ts
@@ -0,0 +1,106 @@
+import type { TranslationStrings } from '../types';
+
+const vacay: TranslationStrings = {
+ 'vacay.subtitle': 'Rencanakan dan kelola hari cuti',
+ 'vacay.settings': 'Pengaturan',
+ 'vacay.year': 'Tahun',
+ 'vacay.addYear': 'Tambah tahun berikutnya',
+ 'vacay.addPrevYear': 'Tambah tahun sebelumnya',
+ 'vacay.removeYear': 'Hapus tahun',
+ 'vacay.removeYearConfirm': 'Hapus {year}?',
+ 'vacay.removeYearHint':
+ 'Semua entri cuti dan hari libur perusahaan untuk tahun ini akan dihapus secara permanen.',
+ 'vacay.remove': 'Hapus',
+ 'vacay.persons': 'Orang',
+ 'vacay.noPersons': 'Belum ada orang ditambahkan',
+ 'vacay.addPerson': 'Tambah Orang',
+ 'vacay.editPerson': 'Edit Orang',
+ 'vacay.removePerson': 'Hapus Orang',
+ 'vacay.removePersonConfirm': 'Hapus {name}?',
+ 'vacay.removePersonHint':
+ 'Semua entri cuti untuk orang ini akan dihapus secara permanen.',
+ 'vacay.personName': 'Nama',
+ 'vacay.personNamePlaceholder': 'Masukkan nama',
+ 'vacay.color': 'Warna',
+ 'vacay.add': 'Tambah',
+ 'vacay.legend': 'Keterangan',
+ 'vacay.publicHoliday': 'Hari Libur Nasional',
+ 'vacay.companyHoliday': 'Hari Libur Perusahaan',
+ 'vacay.weekend': 'Akhir Pekan',
+ 'vacay.modeVacation': 'Cuti',
+ 'vacay.modeCompany': 'Hari Libur Perusahaan',
+ 'vacay.entitlement': 'Jatah Cuti',
+ 'vacay.entitlementDays': 'Hari',
+ 'vacay.used': 'Terpakai',
+ 'vacay.remaining': 'Sisa',
+ 'vacay.carriedOver': 'dari {year}',
+ 'vacay.blockWeekends': 'Blokir Akhir Pekan',
+ 'vacay.blockWeekendsHint': 'Cegah entri cuti pada hari akhir pekan',
+ 'vacay.weekendDays': 'Hari akhir pekan',
+ 'vacay.mon': 'Sen',
+ 'vacay.tue': 'Sel',
+ 'vacay.wed': 'Rab',
+ 'vacay.thu': 'Kam',
+ 'vacay.fri': 'Jum',
+ 'vacay.sat': 'Sab',
+ 'vacay.sun': 'Min',
+ 'vacay.publicHolidays': 'Hari Libur Nasional',
+ 'vacay.publicHolidaysHint': 'Tandai hari libur nasional di kalender',
+ 'vacay.selectCountry': 'Pilih negara',
+ 'vacay.selectRegion': 'Pilih wilayah (opsional)',
+ 'vacay.addCalendar': 'Tambah kalender',
+ 'vacay.calendarLabel': 'Label (opsional)',
+ 'vacay.calendarColor': 'Warna',
+ 'vacay.noCalendars': 'Belum ada kalender hari libur ditambahkan',
+ 'vacay.companyHolidays': 'Hari Libur Perusahaan',
+ 'vacay.companyHolidaysHint':
+ 'Izinkan penandaan hari libur seluruh perusahaan',
+ 'vacay.companyHolidaysNoDeduct':
+ 'Hari libur perusahaan tidak dipotong dari jatah cuti.',
+ 'vacay.weekStart': 'Awal minggu',
+ 'vacay.weekStartHint':
+ 'Pilih apakah minggu kalender dimulai pada hari Senin atau Minggu',
+ 'vacay.carryOver': 'Carry Over Cuti',
+ 'vacay.carryOverHint':
+ 'Otomatis pindahkan sisa hari cuti ke tahun berikutnya',
+ 'vacay.sharing': 'Berbagi',
+ 'vacay.sharingHint': 'Bagikan rencana cuti kamu dengan pengguna TREK lainnya',
+ 'vacay.owner': 'Pemilik',
+ 'vacay.shareEmailPlaceholder': 'Email pengguna TREK',
+ 'vacay.shareSuccess': 'Rencana berhasil dibagikan',
+ 'vacay.shareError': 'Gagal membagikan rencana',
+ 'vacay.dissolve': 'Pisahkan Gabungan',
+ 'vacay.dissolveHint':
+ 'Pisahkan kalender kembali. Entri kamu akan tetap disimpan.',
+ 'vacay.dissolveAction': 'Pisahkan',
+ 'vacay.dissolved': 'Kalender dipisahkan',
+ 'vacay.fusedWith': 'Digabung dengan',
+ 'vacay.you': 'kamu',
+ 'vacay.noData': 'Tidak ada data',
+ 'vacay.changeColor': 'Ganti warna',
+ 'vacay.inviteUser': 'Undang Pengguna',
+ 'vacay.inviteHint':
+ 'Undang pengguna TREK lain untuk berbagi kalender cuti bersama.',
+ 'vacay.selectUser': 'Pilih pengguna',
+ 'vacay.sendInvite': 'Kirim Undangan',
+ 'vacay.inviteSent': 'Undangan terkirim',
+ 'vacay.inviteError': 'Gagal mengirim undangan',
+ 'vacay.pending': 'menunggu',
+ 'vacay.noUsersAvailable': 'Tidak ada pengguna tersedia',
+ 'vacay.accept': 'Terima',
+ 'vacay.decline': 'Tolak',
+ 'vacay.acceptFusion': 'Terima & Gabung',
+ 'vacay.inviteTitle': 'Permintaan Penggabungan',
+ 'vacay.inviteWantsToFuse': 'ingin berbagi kalender cuti bersamamu.',
+ 'vacay.fuseInfo1':
+ 'Kalian berdua akan melihat semua entri cuti dalam satu kalender bersama.',
+ 'vacay.fuseInfo2':
+ 'Kedua pihak dapat membuat dan mengedit entri satu sama lain.',
+ 'vacay.fuseInfo3':
+ 'Kedua pihak dapat menghapus entri dan mengubah jatah cuti.',
+ 'vacay.fuseInfo4':
+ 'Pengaturan seperti hari libur nasional dan hari libur perusahaan dibagikan bersama.',
+ 'vacay.fuseInfo5':
+ 'Penggabungan dapat dipisahkan kapan saja oleh salah satu pihak. Entri kamu akan tetap disimpan.',
+};
+export default vacay;
diff --git a/shared/src/i18n/index.ts b/shared/src/i18n/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1a3d2f238fdd2d3199efc410bc5ef7d96e6b537f
--- /dev/null
+++ b/shared/src/i18n/index.ts
@@ -0,0 +1,2 @@
+export * from './types';
+export * from './languages';
diff --git a/shared/src/i18n/it/admin.ts b/shared/src/i18n/it/admin.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9add04a9b666c852f7319a1eed3d56449d55d64d
--- /dev/null
+++ b/shared/src/i18n/it/admin.ts
@@ -0,0 +1,398 @@
+import type { TranslationStrings } from '../types';
+
+const admin: TranslationStrings = {
+ 'admin.notifications.title': 'Notifiche',
+ 'admin.notifications.hint':
+ 'Scegli un canale di notifica. Solo uno può essere attivo alla volta.',
+ 'admin.notifications.none': 'Disattivato',
+ 'admin.notifications.email': 'E-mail (SMTP)',
+ 'admin.notifications.webhook': 'Webhook',
+ 'admin.notifications.save': 'Salva impostazioni notifiche',
+ 'admin.notifications.saved': 'Impostazioni notifiche salvate',
+ 'admin.notifications.testWebhook': 'Invia webhook di test',
+ 'admin.notifications.testWebhookSuccess':
+ 'Webhook di test inviato con successo',
+ 'admin.notifications.testWebhookFailed': 'Invio webhook di test fallito',
+ 'admin.smtp.title': 'Email e notifiche',
+ 'admin.smtp.hint':
+ "Configurazione SMTP per l'invio delle notifiche via e-mail.",
+ 'admin.smtp.testButton': 'Invia email di prova',
+ 'admin.webhook.hint':
+ 'Invia notifiche a un webhook esterno (Discord, Slack, ecc.).',
+ 'admin.smtp.testSuccess': 'Email di prova inviata con successo',
+ 'admin.smtp.testFailed': 'Invio email di prova fallito',
+ 'admin.title': 'Amministrazione',
+ 'admin.subtitle': 'Gestione utenti e impostazioni di sistema',
+ 'admin.tabs.users': 'Utenti',
+ 'admin.tabs.categories': 'Categorie',
+ 'admin.tabs.backup': 'Backup',
+ 'admin.stats.users': 'Utenti',
+ 'admin.stats.trips': 'Viaggi',
+ 'admin.stats.places': 'Luoghi',
+ 'admin.stats.photos': 'Foto',
+ 'admin.stats.files': 'File',
+ 'admin.table.user': 'Utente',
+ 'admin.table.email': 'Email',
+ 'admin.table.role': 'Ruolo',
+ 'admin.table.created': 'Creato',
+ 'admin.table.lastLogin': 'Ultimo Accesso',
+ 'admin.table.actions': 'Azioni',
+ 'admin.you': '(Tu)',
+ 'admin.editUser': 'Modifica Utente',
+ 'admin.newPassword': 'Nuova Password',
+ 'admin.newPasswordHint': 'Lascia vuoto per mantenere la password attuale',
+ 'admin.deleteUser':
+ 'Eliminare l\'utente "{name}"? Tutti i viaggi verranno eliminati in modo permanente.',
+ 'admin.deleteUserTitle': 'Elimina utente',
+ 'admin.newPasswordPlaceholder': 'Inserisci nuova password…',
+ 'admin.toast.loadError': 'Impossibile caricare i dati di amministrazione',
+ 'admin.toast.userUpdated': 'Utente aggiornato',
+ 'admin.toast.updateError': 'Impossibile aggiornare',
+ 'admin.toast.userDeleted': 'Utente eliminato',
+ 'admin.toast.deleteError': 'Impossibile eliminare',
+ 'admin.toast.cannotDeleteSelf': 'Impossibile eliminare il proprio account',
+ 'admin.toast.userCreated': 'Utente creato',
+ 'admin.toast.createError': "Impossibile creare l'utente",
+ 'admin.toast.fieldsRequired': 'Username, email e password sono obbligatori',
+ 'admin.createUser': 'Crea Utente',
+ 'admin.invite.title': 'Link di Invito',
+ 'admin.invite.subtitle': 'Crea link di registrazione monouso',
+ 'admin.invite.create': 'Crea Link',
+ 'admin.invite.createAndCopy': 'Crea & Copia',
+ 'admin.invite.empty': 'Nessun link di invito ancora creato',
+ 'admin.invite.maxUses': 'Usi Max.',
+ 'admin.invite.expiry': 'Scade tra',
+ 'admin.invite.uses': 'usato',
+ 'admin.invite.expiresAt': 'scade',
+ 'admin.invite.createdBy': 'da',
+ 'admin.invite.active': 'Attivo',
+ 'admin.invite.expired': 'Scaduto',
+ 'admin.invite.usedUp': 'Esaurito',
+ 'admin.invite.copied': 'Link di invito copiato negli appunti',
+ 'admin.invite.copyLink': 'Copia link',
+ 'admin.invite.deleted': 'Link di invito eliminato',
+ 'admin.invite.createError': 'Impossibile creare il link di invito',
+ 'admin.invite.deleteError': 'Impossibile eliminare il link di invito',
+ 'admin.tabs.settings': 'Impostazioni',
+ 'admin.allowRegistration': 'Consenti Registrazione',
+ 'admin.allowRegistrationHint':
+ 'I nuovi utenti possono registrarsi autonomamente',
+ 'admin.authMethods': 'Authentication Methods',
+ 'admin.passwordLogin': 'Password Login',
+ 'admin.passwordLoginHint': 'Allow users to sign in with email and password',
+ 'admin.passwordRegistration': 'Password Registration',
+ 'admin.passwordRegistrationHint':
+ 'Allow new users to register with email and password',
+ 'admin.oidcLogin': 'SSO Login',
+ 'admin.oidcLoginHint': 'Allow users to sign in with SSO',
+ 'admin.oidcRegistration': 'SSO Auto-Provisioning',
+ 'admin.oidcRegistrationHint':
+ 'Automatically create accounts for new SSO users',
+ 'admin.envOverrideHint':
+ 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.',
+ 'admin.lockoutWarning': 'At least one login method must remain enabled',
+ 'admin.requireMfa': 'Richiedi autenticazione a due fattori (2FA)',
+ 'admin.requireMfaHint':
+ "Gli utenti senza 2FA devono completare la configurazione in Impostazioni prima di usare l'app.",
+ 'admin.apiKeys': 'Chiavi API',
+ 'admin.apiKeysHint':
+ 'Opzionale. Abilita dati estesi per i luoghi come foto e meteo.',
+ 'admin.mapsKey': 'Chiave API Google Maps',
+ 'admin.mapsKeyHint':
+ 'Richiesta per la ricerca dei luoghi. Ottienila su console.cloud.google.com',
+ 'admin.mapsKeyHintLong':
+ 'Senza una chiave API, OpenStreetMap viene utilizzato per la ricerca dei luoghi. Con una chiave API di Google, è possibile caricare anche foto, valutazioni e orari di apertura. Ottienine una su console.cloud.google.com.',
+ 'admin.recommended': 'Consigliato',
+ 'admin.weatherKey': 'Chiave API OpenWeatherMap',
+ 'admin.weatherKeyHint': 'Per i dati meteo. Gratuita su openweathermap.org',
+ 'admin.validateKey': 'Testa',
+ 'admin.keyValid': 'Connessa',
+ 'admin.keyInvalid': 'Non valida',
+ 'admin.keySaved': 'Chiavi API salvate',
+ 'admin.oidcTitle': 'Single Sign-On (OIDC)',
+ 'admin.oidcSubtitle':
+ "Consenti l'accesso tramite provider esterni come Google, Apple, Authentik o Keycloak.",
+ 'admin.oidcDisplayName': 'Nome Visualizzato',
+ 'admin.oidcIssuer': 'URL Emittente',
+ 'admin.oidcIssuerHint':
+ "L'URL dell'Emittente OpenID Connect del provider. es. https://accounts.google.com",
+ 'admin.oidcSaved': 'Configurazione OIDC salvata',
+ 'admin.oidcOnlyMode': 'Disabilita autenticazione con password',
+ 'admin.oidcOnlyModeHint':
+ "Se abilitato, è consentito solo l'accesso SSO. L'accesso basato su password e la registrazione sono bloccati.",
+ 'admin.fileTypes': 'Tipi di File Consentiti',
+ 'admin.fileTypesHint':
+ 'Configura quali tipi di file gli utenti possono caricare.',
+ 'admin.fileTypesFormat':
+ 'Estensioni separate da virgola (es. jpg,png,pdf,doc). Usa * per consentire tutti i tipi.',
+ 'admin.fileTypesSaved': 'Impostazioni dei tipi di file salvate',
+ 'admin.placesPhotos.title': 'Foto dei luoghi',
+ 'admin.placesPhotos.subtitle':
+ "Recupera le foto dall'API Google Places. Disabilita per risparmiare la quota API. Le foto di Wikimedia non sono interessate.",
+ 'admin.placesAutocomplete.title': 'Completamento automatico dei luoghi',
+ 'admin.placesAutocomplete.subtitle':
+ "Utilizza l'API Google Places per i suggerimenti di ricerca. Disabilita per risparmiare la quota API.",
+ 'admin.placesDetails.title': 'Dettagli del luogo',
+ 'admin.placesDetails.subtitle':
+ "Recupera informazioni dettagliate sul luogo (orari, valutazione, sito web) dall'API Google Places. Disabilita per risparmiare la quota API.",
+ 'admin.bagTracking.title': 'Tracciamento valigia',
+ 'admin.bagTracking.subtitle':
+ "Abilita il peso e l'assegnazione della valigia per gli elementi della lista valigia",
+ 'admin.collab.chat.title': 'Chat',
+ 'admin.collab.chat.subtitle':
+ 'Messaggistica in tempo reale per la collaborazione',
+ 'admin.collab.notes.title': 'Note',
+ 'admin.collab.notes.subtitle': 'Note e documenti condivisi',
+ 'admin.collab.polls.title': 'Sondaggi',
+ 'admin.collab.polls.subtitle': 'Sondaggi e votazioni di gruppo',
+ 'admin.collab.whatsnext.title': 'Prossimi passi',
+ 'admin.collab.whatsnext.subtitle': 'Suggerimenti attività e prossimi passi',
+ 'admin.tabs.config': 'Personalizzazione',
+ 'admin.tabs.defaults': 'Impostazioni predefinite',
+ 'admin.defaultSettings.title': 'Impostazioni predefinite utente',
+ 'admin.defaultSettings.description':
+ "Imposta i valori predefiniti per l'intera istanza. Gli utenti che non hanno modificato un'impostazione vedranno questi valori. Le loro modifiche hanno sempre la priorità.",
+ 'admin.defaultSettings.saved': 'Predefinito salvato',
+ 'admin.defaultSettings.reset': 'Ripristina il predefinito integrato',
+ 'admin.defaultSettings.resetToBuiltIn': 'ripristina',
+ 'admin.tabs.templates': 'Modelli lista valigia',
+ 'admin.packingTemplates.title': 'Modelli lista valigia',
+ 'admin.packingTemplates.subtitle':
+ 'Crea liste valigia riutilizzabili per i tuoi viaggi',
+ 'admin.packingTemplates.create': 'Nuovo modello',
+ 'admin.packingTemplates.namePlaceholder':
+ 'Nome modello (es. Vacanza al mare)',
+ 'admin.packingTemplates.empty': 'Ancora nessun modello creato',
+ 'admin.packingTemplates.items': 'elementi',
+ 'admin.packingTemplates.categories': 'categorie',
+ 'admin.packingTemplates.itemName': 'Nome elemento',
+ 'admin.packingTemplates.itemCategory': 'Categoria',
+ 'admin.packingTemplates.categoryName': 'Nome categoria (es. Abbigliamento)',
+ 'admin.packingTemplates.addCategory': 'Aggiungi categoria',
+ 'admin.packingTemplates.created': 'Modello creato',
+ 'admin.packingTemplates.deleted': 'Modello eliminato',
+ 'admin.packingTemplates.loadError': 'Impossibile caricare i modelli',
+ 'admin.packingTemplates.createError': 'Impossibile creare il modello',
+ 'admin.packingTemplates.deleteError': 'Impossibile eliminare il modello',
+ 'admin.packingTemplates.saveError': 'Impossibile salvare',
+ 'admin.tabs.addons': 'Moduli',
+ 'admin.addons.title': 'Moduli',
+ 'admin.addons.subtitle':
+ 'Abilita o disabilita le funzionalità per personalizzare la tua esperienza TREK.',
+ 'admin.addons.catalog.packing.name': 'Liste',
+ 'admin.addons.catalog.packing.description':
+ 'Liste di imballaggio e attività da svolgere per i tuoi viaggi',
+ 'admin.addons.catalog.budget.name': 'Budget',
+ 'admin.addons.catalog.budget.description':
+ 'Tieni traccia delle spese e pianifica il budget del tuo viaggio',
+ 'admin.addons.catalog.documents.name': 'Documenti',
+ 'admin.addons.catalog.documents.description':
+ 'Archivia e gestisci i documenti di viaggio',
+ 'admin.addons.catalog.vacay.name': 'Ferie',
+ 'admin.addons.catalog.vacay.description':
+ 'Pianificatore personale delle ferie con vista calendario',
+ 'admin.addons.catalog.atlas.name': 'Atlante',
+ 'admin.addons.catalog.atlas.description':
+ 'Mappa del mondo con paesi visitati e statistiche di viaggio',
+ 'admin.addons.catalog.collab.name': 'Collaborazione',
+ 'admin.addons.catalog.collab.description':
+ 'Note, sondaggi e chat in tempo reale per la pianificazione del viaggio',
+ 'admin.addons.catalog.memories.name': 'Foto (Immich)',
+ 'admin.addons.catalog.memories.description':
+ 'Condividi le foto del viaggio tramite la tua istanza Immich',
+ 'admin.addons.catalog.mcp.name': 'MCP',
+ 'admin.addons.catalog.mcp.description':
+ "Model Context Protocol per l'integrazione di assistenti AI",
+ 'admin.addons.subtitleBefore':
+ 'Abilita o disabilita le funzionalità per personalizzare la tua ',
+ 'admin.addons.subtitleAfter': ' esperienza.',
+ 'admin.addons.enabled': 'Abilitato',
+ 'admin.addons.disabled': 'Disabilitato',
+ 'admin.addons.type.trip': 'Viaggio',
+ 'admin.addons.type.global': 'Globale',
+ 'admin.addons.type.integration': 'Integrazione',
+ 'admin.addons.tripHint':
+ "Disponibile come scheda all'interno di ciascun viaggio",
+ 'admin.addons.globalHint':
+ 'Disponibile come sezione autonoma nella navigazione principale',
+ 'admin.addons.integrationHint':
+ 'Servizi backend e integrazioni API senza pagina dedicata',
+ 'admin.addons.toast.updated': 'Modulo aggiornato',
+ 'admin.addons.toast.error': 'Impossibile aggiornare il modulo',
+ 'admin.addons.noAddons': 'Nessun modulo disponibile',
+ 'admin.weather.title': 'Dati meteo',
+ 'admin.weather.badge': 'Dal 24 marzo 2026',
+ 'admin.weather.description':
+ 'TREK utilizza Open-Meteo come fonte dei dati meteo. Open-Meteo è un servizio meteo gratuito e open-source — non è richiesta alcuna chiave API.',
+ 'admin.weather.forecast': 'Previsioni a 16 giorni',
+ 'admin.weather.forecastDesc': 'In precedenza 5 giorni (OpenWeatherMap)',
+ 'admin.weather.climate': 'Dati climatici storici',
+ 'admin.weather.climateDesc':
+ 'Medie degli ultimi 85 anni per i giorni oltre le previsioni a 16 giorni',
+ 'admin.weather.requests': '10.000 richieste / giorno',
+ 'admin.weather.requestsDesc': 'Gratis, nessuna chiave API richiesta',
+ 'admin.weather.locationHint':
+ "Il meteo si basa sul primo luogo con coordinate di ogni giorno. Se a un giorno non è assegnato alcun luogo, viene utilizzato come riferimento un qualsiasi luogo dell'elenco.",
+ 'admin.tabs.audit': 'Audit',
+ 'admin.audit.subtitle':
+ 'Eventi sensibili di sicurezza e amministrazione (backup, utenti, 2FA, impostazioni).',
+ 'admin.audit.empty': 'Nessuna voce di audit.',
+ 'admin.audit.refresh': 'Aggiorna',
+ 'admin.audit.loadMore': 'Carica altro',
+ 'admin.audit.showing': '{count} caricati · {total} totali',
+ 'admin.audit.col.time': 'Ora',
+ 'admin.audit.col.user': 'Utente',
+ 'admin.audit.col.action': 'Azione',
+ 'admin.audit.col.resource': 'Risorsa',
+ 'admin.audit.col.ip': 'IP',
+ 'admin.audit.col.details': 'Dettagli',
+ 'admin.tabs.mcpTokens': 'Accesso MCP',
+ 'admin.mcpTokens.title': 'Accesso MCP',
+ 'admin.mcpTokens.subtitle':
+ 'Gestisci le sessioni OAuth e i token API di tutti gli utenti',
+ 'admin.mcpTokens.sectionTitle': 'Token API',
+ 'admin.mcpTokens.owner': 'Proprietario',
+ 'admin.mcpTokens.tokenName': 'Nome token',
+ 'admin.mcpTokens.created': 'Creato',
+ 'admin.mcpTokens.lastUsed': 'Ultimo utilizzo',
+ 'admin.mcpTokens.never': 'Mai',
+ 'admin.mcpTokens.empty': 'Non sono ancora stati creati token MCP',
+ 'admin.mcpTokens.deleteTitle': 'Elimina token',
+ 'admin.mcpTokens.deleteMessage':
+ "Questo token verrà revocato immediatamente. L'utente perderà l'accesso MCP tramite questo token.",
+ 'admin.mcpTokens.deleteSuccess': 'Token eliminato',
+ 'admin.mcpTokens.deleteError': 'Impossibile eliminare il token',
+ 'admin.mcpTokens.loadError': 'Impossibile caricare i token',
+ 'admin.oauthSessions.sectionTitle': 'Sessioni OAuth',
+ 'admin.oauthSessions.clientName': 'Client',
+ 'admin.oauthSessions.owner': 'Proprietario',
+ 'admin.oauthSessions.scopes': 'Ambiti',
+ 'admin.oauthSessions.created': 'Creato',
+ 'admin.oauthSessions.empty': 'Nessuna sessione OAuth attiva',
+ 'admin.oauthSessions.revokeTitle': 'Revoca sessione',
+ 'admin.oauthSessions.revokeMessage':
+ "Questa sessione OAuth verrà revocata immediatamente. Il client perderà l'accesso MCP.",
+ 'admin.oauthSessions.revokeSuccess': 'Sessione revocata',
+ 'admin.oauthSessions.revokeError': 'Impossibile revocare la sessione',
+ 'admin.oauthSessions.loadError': 'Impossibile caricare le sessioni OAuth',
+ 'admin.tabs.github': 'GitHub',
+ 'admin.github.title': 'Cronologia rilasci',
+ 'admin.github.subtitle': 'Ultimi aggiornamenti da {repo}',
+ 'admin.github.latest': 'Ultimo',
+ 'admin.github.prerelease': 'Pre-release',
+ 'admin.github.showDetails': 'Mostra dettagli',
+ 'admin.github.hideDetails': 'Nascondi dettagli',
+ 'admin.github.loadMore': 'Carica altro',
+ 'admin.github.loading': 'Caricamento...',
+ 'admin.github.error': 'Impossibile caricare i rilasci',
+ 'admin.github.by': 'da',
+ 'admin.github.support': 'Mi aiuta a continuare a sviluppare TREK',
+ 'admin.update.available': 'Aggiornamento disponibile',
+ 'admin.update.text':
+ 'TREK {version} è disponibile. Stai eseguendo {current}.',
+ 'admin.update.button': 'Vedi su GitHub',
+ 'admin.update.install': 'Installa aggiornamento',
+ 'admin.update.confirmTitle': "Installare l'aggiornamento?",
+ 'admin.update.confirmText':
+ 'TREK verrà aggiornato da {current} a {version}. Il server si riavvierà automaticamente in seguito.',
+ 'admin.update.dataInfo':
+ 'Tutti i tuoi dati (viaggi, utenti, chiavi API, caricamenti, Ferie, Atlante, budget) saranno preservati.',
+ 'admin.update.warning':
+ "L'app sarà temporaneamente non disponibile durante il riavvio.",
+ 'admin.update.confirm': 'Aggiorna ora',
+ 'admin.update.installing': 'Aggiornamento in corso…',
+ 'admin.update.success':
+ 'Aggiornamento installato! Il server si sta riavviando…',
+ 'admin.update.failed': 'Aggiornamento non riuscito',
+ 'admin.update.backupHint':
+ 'Ti consigliamo di creare un backup prima di aggiornare.',
+ 'admin.update.backupLink': 'Vai a Backup',
+ 'admin.update.howTo': 'Come aggiornare',
+ 'admin.update.dockerText':
+ 'La tua istanza TREK è in esecuzione in Docker. Per aggiornare alla versione {version}, esegui i seguenti comandi sul tuo server:',
+ 'admin.update.reloadHint': 'Ricarica la pagina tra qualche secondo.',
+ 'admin.tabs.permissions': 'Permessi',
+ 'admin.notifications.emailPanel.title': 'Email (SMTP)',
+ 'admin.notifications.webhookPanel.title': 'Webhook',
+ 'admin.notifications.inappPanel.title': 'In-App',
+ 'admin.notifications.inappPanel.hint':
+ 'Le notifiche in-app sono sempre attive e non possono essere disabilitate globalmente.',
+ 'admin.notifications.adminWebhookPanel.title': 'Webhook admin',
+ 'admin.notifications.adminWebhookPanel.hint':
+ 'Questo webhook viene usato esclusivamente per le notifiche admin (es. avvisi di versione). È separato dai webhook utente e si attiva automaticamente quando è configurato un URL.',
+ 'admin.notifications.adminWebhookPanel.saved': 'URL webhook admin salvato',
+ 'admin.notifications.adminWebhookPanel.testSuccess':
+ 'Webhook di test inviato con successo',
+ 'admin.notifications.adminWebhookPanel.testFailed':
+ 'Invio webhook di test fallito',
+ 'admin.notifications.adminWebhookPanel.alwaysOnHint':
+ 'Il webhook admin si attiva automaticamente quando è configurato un URL',
+ 'admin.notifications.ntfy': 'Ntfy',
+ 'admin.ntfy.hint':
+ 'Consente agli utenti di configurare i propri argomenti ntfy per le notifiche push. Imposta il server predefinito di seguito per precompilare le impostazioni utente.',
+ 'admin.notifications.testNtfy': 'Invia Ntfy di test',
+ 'admin.notifications.testNtfySuccess': 'Ntfy di test inviato con successo',
+ 'admin.notifications.testNtfyFailed': 'Invio Ntfy di test fallito',
+ 'admin.notifications.adminNtfyPanel.title': 'Ntfy admin',
+ 'admin.notifications.adminNtfyPanel.hint':
+ 'Questo argomento Ntfy viene usato esclusivamente per le notifiche admin (es. avvisi di versione). È separato dagli argomenti per utente e si attiva sempre quando è configurato.',
+ 'admin.notifications.adminNtfyPanel.serverLabel': 'URL server Ntfy',
+ 'admin.notifications.adminNtfyPanel.serverHint':
+ 'Usato anche come server predefinito per le notifiche ntfy degli utenti. Lasciare vuoto per usare ntfy.sh. Gli utenti possono sovrascriverlo nelle proprie impostazioni.',
+ 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh',
+ 'admin.notifications.adminNtfyPanel.topicLabel': 'Argomento admin',
+ 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts',
+ 'admin.notifications.adminNtfyPanel.tokenLabel':
+ 'Token di accesso (opzionale)',
+ 'admin.notifications.adminNtfyPanel.tokenCleared':
+ 'Token di accesso admin rimosso',
+ 'admin.notifications.adminNtfyPanel.saved': 'Impostazioni Ntfy admin salvate',
+ 'admin.notifications.adminNtfyPanel.test': 'Invia Ntfy di test',
+ 'admin.notifications.adminNtfyPanel.testSuccess':
+ 'Ntfy di test inviato con successo',
+ 'admin.notifications.adminNtfyPanel.testFailed': 'Invio Ntfy di test fallito',
+ 'admin.notifications.adminNtfyPanel.alwaysOnHint':
+ 'Il Ntfy admin si attiva sempre quando un argomento è configurato',
+ 'admin.notifications.adminNotificationsHint':
+ 'Configura quali canali consegnano le notifiche admin (es. avvisi di versione). Il webhook si attiva automaticamente se è impostato un URL webhook admin.',
+ 'admin.notifications.tripReminders.title': 'Promemoria viaggio',
+ 'admin.notifications.tripReminders.hint':
+ "Invia una notifica promemoria prima dell'inizio di un viaggio (richiede giorni di promemoria impostati sul viaggio).",
+ 'admin.notifications.tripReminders.enabled': 'Promemoria viaggio attivati',
+ 'admin.notifications.tripReminders.disabled':
+ 'Promemoria viaggio disattivati',
+ 'admin.tabs.notifications': 'Notifiche',
+ 'admin.addons.catalog.journey.name': 'Diario di viaggio',
+ 'admin.addons.catalog.journey.description':
+ 'Tracciamento viaggi e diario con check-in, foto e storie quotidiane',
+ 'admin.passkey.title': 'Accesso con passkey',
+ 'admin.passkey.cardHint':
+ 'Consenti agli utenti di accedere con le passkey (WebAuthn). Disattivato per impostazione predefinita.',
+ 'admin.passkey.login': 'Abilita accesso con passkey',
+ 'admin.passkey.loginHint':
+ 'Mostra un\'opzione "Accedi con una passkey" e consenti agli utenti di registrare le passkey nelle loro impostazioni.',
+ 'admin.passkey.notConfigured':
+ 'Nessun dominio WebAuthn è ancora risolto per questa installazione. Imposta APP_URL o il Relying Party ID qui sotto — le passkey restano nascoste fino ad allora.',
+ 'admin.passkey.rpId': 'Relying Party ID (dominio)',
+ 'admin.passkey.rpIdHint':
+ 'Il dominio puro a cui le passkey sono associate, es. trek.example.org. Lascia vuoto per derivarlo da APP_URL. Modificarlo in seguito invalida le passkey esistenti.',
+ 'admin.passkey.origins': 'Origini consentite',
+ 'admin.passkey.originsHint':
+ 'Origini complete separate da virgola, es. https://trek.example.org. Lascia vuoto per usare APP_URL.',
+ 'admin.passkey.reset': 'Reimposta passkey',
+ 'admin.passkey.resetHint':
+ "Rimuovi tutte le passkey di questo utente (es. in caso di dispositivo smarrito). Potrà comunque accedere con la sua password.",
+ 'admin.passkey.resetConfirm': 'Rimuovere tutte le passkey di {name}?',
+ 'admin.passkey.resetDone': 'Rimosse {count} passkey',
+ 'admin.defaultSettings.mapProvider': 'Motore mappe',
+ 'admin.defaultSettings.mapProviderHint': 'La mappa predefinita per tutti gli utenti di questa istanza. Ogni utente può comunque sostituirla nelle proprie impostazioni.',
+ 'admin.defaultSettings.providerLeaflet': 'Standard (gratuito)',
+ 'admin.defaultSettings.providerMapbox': 'Mapbox (3D)',
+ 'admin.defaultSettings.mapboxToken': 'Token Mapbox condiviso',
+ 'admin.defaultSettings.mapboxTokenHint': 'Usato per ogni utente che non ha inserito un proprio token — così tutta l\'istanza ottiene Mapbox senza dover condividere la chiave individualmente. Archiviato in forma crittografata.',
+ 'admin.defaultSettings.mapboxStyle': 'Stile mappa',
+ 'admin.defaultSettings.mapboxStylePlaceholder': 'Scegli uno stile…',
+ 'admin.defaultSettings.mapbox3d': 'Edifici & terreno in 3D',
+ 'admin.defaultSettings.mapboxQuality': 'Modalità alta qualità',
+};
+export default admin;
diff --git a/shared/src/i18n/it/airport.ts b/shared/src/i18n/it/airport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c05ae376abe5fa25ffb70dd7b2cfa1cc9efe2c64
--- /dev/null
+++ b/shared/src/i18n/it/airport.ts
@@ -0,0 +1,6 @@
+import type { TranslationStrings } from '../types';
+
+const airport: TranslationStrings = {
+ 'airport.searchPlaceholder': "Codice o città dell'aeroporto (es. FRA)",
+};
+export default airport;
diff --git a/shared/src/i18n/it/atlas.ts b/shared/src/i18n/it/atlas.ts
new file mode 100644
index 0000000000000000000000000000000000000000..36d91a1b07897bc902188ff4a5c02b9c91addd0a
--- /dev/null
+++ b/shared/src/i18n/it/atlas.ts
@@ -0,0 +1,61 @@
+import type { TranslationStrings } from '../types';
+
+const atlas: TranslationStrings = {
+ 'atlas.subtitle': 'La tua impronta di viaggio nel mondo',
+ 'atlas.countries': 'Paesi',
+ 'atlas.trips': 'Viaggi',
+ 'atlas.places': 'Luoghi',
+ 'atlas.unmark': 'Rimuovi',
+ 'atlas.confirmMark': 'Segnare questo paese come visitato?',
+ 'atlas.confirmUnmark': 'Rimuovere questo paese dalla tua lista dei visitati?',
+ 'atlas.confirmUnmarkRegion':
+ 'Rimuovere questa regione dalla tua lista dei visitati?',
+ 'atlas.markVisited': 'Segna come visitato',
+ 'atlas.markVisitedHint': 'Aggiungi questo paese alla tua lista dei visitati',
+ 'atlas.markRegionVisitedHint':
+ 'Aggiungi questa regione alla tua lista dei visitati',
+ 'atlas.addToBucket': 'Aggiungi alla lista desideri',
+ 'atlas.addPoi': 'Aggiungi luogo',
+ 'atlas.bucketNamePlaceholder': 'Nome (paese, città, luogo...)',
+ 'atlas.month': 'Mese',
+ 'atlas.addToBucketHint': 'Salvalo come luogo che vuoi visitare',
+ 'atlas.bucketWhen': 'Quando pensi di visitarlo?',
+ 'atlas.statsTab': 'Statistiche',
+ 'atlas.bucketTab': 'Lista desideri',
+ 'atlas.addBucket': 'Aggiungi alla lista desideri',
+ 'atlas.bucketNotesPlaceholder': 'Note (opzionale)',
+ 'atlas.bucketEmpty': 'La tua lista desideri è vuota',
+ 'atlas.bucketEmptyHint': 'Aggiungi luoghi che sogni di visitare',
+ 'atlas.days': 'Giorni',
+ 'atlas.visitedCountries': 'Paesi visitati',
+ 'atlas.cities': 'Città',
+ 'atlas.noData': 'Ancora nessun dato di viaggio',
+ 'atlas.noDataHint':
+ 'Crea un viaggio e aggiungi luoghi per vedere la tua mappa del mondo',
+ 'atlas.lastTrip': 'Ultimo viaggio',
+ 'atlas.nextTrip': 'Prossimo viaggio',
+ 'atlas.daysLeft': 'giorni rimasti',
+ 'atlas.streak': 'Serie',
+ 'atlas.year': 'anno',
+ 'atlas.years': 'anni',
+ 'atlas.yearInRow': 'anno consecutivo',
+ 'atlas.yearsInRow': 'anni consecutivi',
+ 'atlas.tripIn': 'viaggio in',
+ 'atlas.tripsIn': 'viaggi in',
+ 'atlas.since': 'dal',
+ 'atlas.europe': 'Europa',
+ 'atlas.asia': 'Asia',
+ 'atlas.northAmerica': 'Nord America',
+ 'atlas.southAmerica': 'Sud America',
+ 'atlas.africa': 'Africa',
+ 'atlas.oceania': 'Oceania',
+ 'atlas.other': 'Altro',
+ 'atlas.firstVisit': 'Primo viaggio',
+ 'atlas.lastVisitLabel': 'Ultimo viaggio',
+ 'atlas.tripSingular': 'Viaggio',
+ 'atlas.tripPlural': 'Viaggi',
+ 'atlas.placeVisited': 'Luogo visitato',
+ 'atlas.placesVisited': 'Luoghi visitati',
+ 'atlas.searchCountry': 'Cerca un paese...',
+};
+export default atlas;
diff --git a/shared/src/i18n/it/backup.ts b/shared/src/i18n/it/backup.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1ea227fd785d02016c0ea778330525299ec70c4e
--- /dev/null
+++ b/shared/src/i18n/it/backup.ts
@@ -0,0 +1,77 @@
+import type { TranslationStrings } from '../types';
+
+const backup: TranslationStrings = {
+ 'backup.title': 'Backup dati',
+ 'backup.subtitle': 'Database e tutti i file caricati',
+ 'backup.refresh': 'Aggiorna',
+ 'backup.upload': 'Carica backup',
+ 'backup.uploading': 'Caricamento...',
+ 'backup.create': 'Crea backup',
+ 'backup.creating': 'Creazione...',
+ 'backup.empty': 'Ancora nessun backup',
+ 'backup.createFirst': 'Crea primo backup',
+ 'backup.download': 'Scarica',
+ 'backup.restore': 'Ripristina',
+ 'backup.confirm.restore':
+ 'Ripristinare il backup "{name}"?\n\nTutti i dati attuali verranno sostituiti con il backup.',
+ 'backup.confirm.uploadRestore':
+ 'Scaricare e ripristinare il file di backup "{name}"?\n\nTutti i dati attuali verranno sovrascritti.',
+ 'backup.confirm.delete': 'Eliminare il backup "{name}"?',
+ 'backup.toast.loadError': 'Impossibile caricare i backup',
+ 'backup.toast.created': 'Backup creato con successo',
+ 'backup.toast.createError': 'Impossibile creare il backup',
+ 'backup.toast.restored': 'Backup ripristinato. La pagina verrà ricaricata...',
+ 'backup.toast.restoreError': 'Impossibile ripristinare',
+ 'backup.toast.uploadError': 'Impossibile caricare',
+ 'backup.toast.deleted': 'Backup eliminato',
+ 'backup.toast.deleteError': 'Impossibile eliminare',
+ 'backup.toast.downloadError': 'Download non riuscito',
+ 'backup.toast.settingsSaved': 'Impostazioni auto-backup salvate',
+ 'backup.toast.settingsError': 'Impossibile salvare le impostazioni',
+ 'backup.auto.title': 'Auto-Backup',
+ 'backup.auto.subtitle': 'Backup automatico pianificato',
+ 'backup.auto.enable': 'Abilita auto-backup',
+ 'backup.auto.enableHint':
+ 'I backup verranno creati automaticamente in base alla pianificazione scelta',
+ 'backup.auto.interval': 'Intervallo',
+ 'backup.auto.hour': "Esegui all'ora",
+ 'backup.auto.hourHint': 'Ora locale del server (formato {format})',
+ 'backup.auto.dayOfWeek': 'Giorno della settimana',
+ 'backup.auto.dayOfMonth': 'Giorno del mese',
+ 'backup.auto.dayOfMonthHint':
+ 'Limitato a 1–28 per compatibilità con tutti i mesi',
+ 'backup.auto.scheduleSummary': 'Pianificazione',
+ 'backup.auto.summaryDaily': 'Ogni giorno alle {hour}:00',
+ 'backup.auto.summaryWeekly': 'Ogni {day} alle {hour}:00',
+ 'backup.auto.summaryMonthly': 'Giorno {day} di ogni mese alle {hour}:00',
+ 'backup.auto.envLocked': 'Docker',
+ 'backup.auto.envLockedHint':
+ "L'auto-backup è configurato tramite variabili d'ambiente Docker. Per modificare queste impostazioni, aggiorna il tuo docker-compose.yml e riavvia il container.",
+ 'backup.auto.copyEnv': 'Copia variabili env Docker',
+ 'backup.auto.envCopied': 'Variabili env Docker copiate negli appunti',
+ 'backup.auto.keepLabel': 'Elimina i vecchi backup dopo',
+ 'backup.dow.sunday': 'Dom',
+ 'backup.dow.monday': 'Lun',
+ 'backup.dow.tuesday': 'Mar',
+ 'backup.dow.wednesday': 'Mer',
+ 'backup.dow.thursday': 'Gio',
+ 'backup.dow.friday': 'Ven',
+ 'backup.dow.saturday': 'Sab',
+ 'backup.interval.hourly': 'Ogni ora',
+ 'backup.interval.daily': 'Giornaliero',
+ 'backup.interval.weekly': 'Settimanale',
+ 'backup.interval.monthly': 'Mensile',
+ 'backup.keep.1day': '1 giorno',
+ 'backup.keep.3days': '3 giorni',
+ 'backup.keep.7days': '7 giorni',
+ 'backup.keep.14days': '14 giorni',
+ 'backup.keep.30days': '30 giorni',
+ 'backup.keep.forever': 'Conserva per sempre',
+ 'backup.restoreConfirmTitle': 'Ripristinare il backup?',
+ 'backup.restoreWarning':
+ 'Tutti i dati attuali (viaggi, luoghi, utenti, caricamenti) verranno sostituiti in modo permanente dal backup. Questa azione non può essere annullata.',
+ 'backup.restoreTip':
+ 'Suggerimento: Crea un backup dello stato attuale prima di ripristinare.',
+ 'backup.restoreConfirm': 'Sì, ripristina',
+};
+export default backup;
diff --git a/shared/src/i18n/it/budget.ts b/shared/src/i18n/it/budget.ts
new file mode 100644
index 0000000000000000000000000000000000000000..af357871f9285ef17e0806dbca0925ebbb79b810
--- /dev/null
+++ b/shared/src/i18n/it/budget.ts
@@ -0,0 +1,117 @@
+import type { TranslationStrings } from '../types';
+
+const budget: TranslationStrings = {
+ 'budget.title': 'Budget',
+ 'budget.exportCsv': 'Esporta CSV',
+ 'budget.emptyTitle': 'Ancora nessun budget creato',
+ 'budget.emptyText':
+ 'Crea categorie e voci per pianificare il budget del tuo viaggio',
+ 'budget.emptyPlaceholder': 'Inserisci nome categoria...',
+ 'budget.createCategory': 'Crea categoria',
+ 'budget.category': 'Categoria',
+ 'budget.categoryName': 'Nome categoria',
+ 'budget.table.name': 'Nome',
+ 'budget.table.total': 'Totale',
+ 'budget.table.persons': 'Persone',
+ 'budget.table.days': 'Giorni',
+ 'budget.table.perPerson': 'Per persona',
+ 'budget.table.perDay': 'Per giorno',
+ 'budget.table.perPersonDay': 'P. p / gio.',
+ 'budget.table.note': 'Nota',
+ 'budget.table.date': 'Data',
+ 'budget.newEntry': 'Nuova voce',
+ 'budget.defaultEntry': 'Nuova voce',
+ 'budget.defaultCategory': 'Nuova categoria',
+ 'budget.total': 'Totale',
+ 'budget.totalBudget': 'Budget totale',
+ 'budget.byCategory': 'Per categoria',
+ 'budget.editTooltip': 'Clicca per modificare',
+ 'budget.linkedToReservation':
+ 'Collegato a una prenotazione — modifica il nome lì',
+ 'budget.confirm.deleteCategory':
+ 'Sei sicuro di voler eliminare la categoria "{name}" con {count} voci?',
+ 'budget.deleteCategory': 'Elimina categoria',
+ 'budget.perPerson': 'Per persona',
+ 'budget.paid': 'Pagato',
+ 'budget.open': 'Aperto',
+ 'budget.noMembers': 'Nessun membro assegnato',
+ 'budget.settlement': 'Regolamento',
+ 'budget.settlementInfo':
+ "Clicca sull'avatar di un membro su una voce di budget per contrassegnarlo in verde — significa che ha pagato. Il regolamento mostra poi chi deve quanto a chi.",
+ 'budget.netBalances': 'Saldi netti',
+ 'budget.categoriesLabel': 'categorie',
+ "costs.you": "Tu",
+ "costs.youShort": "T",
+ "costs.youLower": "tu",
+ "costs.youOwe": "Devi",
+ "costs.youOweSub": "Dovresti pagare gli altri",
+ "costs.youreOwed": "Ti devono",
+ "costs.youreOwedSub": "Gli altri dovrebbero pagarti",
+ "costs.totalSpend": "Spesa totale del viaggio",
+ "costs.totalSpendSub": "Tra tutti i viaggiatori",
+ "costs.to": "A",
+ "costs.from": "Da",
+ "costs.allSettled": "Hai saldato tutto",
+ "costs.nothingOwed": "Nessuno ti deve nulla",
+ "costs.yourShare": "La tua quota",
+ "costs.youPaid": "Hai pagato",
+ "costs.expenses": "Spese",
+ "costs.entries": "{count} voci",
+ "costs.searchPlaceholder": "Cerca spese…",
+ "costs.filter.all": "Tutte",
+ "costs.filter.mine": "Pagate da me",
+ "costs.filter.owed": "Mi devono",
+ "costs.addExpense": "Aggiungi spesa",
+ "costs.editExpense": "Modifica spesa",
+ "costs.noMatch": "Nessuna spesa corrisponde alla ricerca.",
+ "costs.emptyText": "Ancora nessuna spesa. Aggiungi la prima.",
+ "costs.spent": "{amount} spesi",
+ "costs.noDate": "Nessuna data",
+ "costs.noOnePaid": "Nessuno ha ancora pagato",
+ "costs.youLent": "hai prestato {amount}",
+ "costs.youBorrowed": "hai preso in prestito {amount}",
+ "costs.settleUp": "Salda",
+ "costs.history": "Cronologia",
+ "costs.everyoneSquare": "Sono tutti in pari",
+ "costs.nothingOutstanding": "Nessun pagamento in sospeso al momento.",
+ "costs.pay": "paga",
+ "costs.pays": "paga",
+ "costs.settle": "Salda",
+ "costs.balances": "Saldi",
+ "costs.byCategory": "Per categoria",
+ "costs.noCategories": "Ancora nessuna spesa.",
+ "costs.settleHistory": "Cronologia saldi",
+ "costs.noSettlements": "Ancora nessun pagamento saldato.",
+ "costs.paymentsSettled": "{count} pagamenti saldati",
+ "costs.paid": "pagato",
+ "costs.undo": "Annulla",
+ "costs.whatFor": "Per cosa era?",
+ "costs.namePlaceholder": "es. Cena, souvenir, benzina…",
+ "costs.totalAmount": "Importo totale",
+ "costs.currency": "Valuta",
+ "costs.day": "Giorno",
+ "costs.rateLabel": "1 {from} in {to}",
+ "costs.category": "Categoria",
+ "costs.whoPaid": "Chi ha pagato?",
+ "costs.splitBetween": "Dividi equamente tra",
+ "costs.pickSomeone": "Scegli almeno una persona con cui dividere.",
+ "costs.splitSummary": "Diviso in {count} · {amount} ciascuno",
+ "costs.cat.accommodation": "Alloggio",
+ "costs.cat.food": "Cibo e bevande",
+ "costs.cat.groceries": "Spesa alimentare",
+ "costs.cat.transport": "Trasporti",
+ "costs.cat.flights": "Voli",
+ "costs.cat.activities": "Attività",
+ "costs.cat.sightseeing": "Visite turistiche",
+ "costs.cat.shopping": "Shopping",
+ "costs.cat.fees": "Tariffe e biglietti",
+ "costs.cat.health": "Salute",
+ "costs.cat.tips": "Mance",
+ "costs.cat.other": "Altro",
+ "costs.daysCount": "{count} giorni",
+ "costs.travelers": "{count} viaggiatori",
+ "costs.liveRate": "tasso in tempo reale",
+ "costs.settleAll": "Salda tutto",
+};
+
+export default budget;
diff --git a/shared/src/i18n/it/categories.ts b/shared/src/i18n/it/categories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d6d639e787f791faa34a61188ddf00064a5d8e7e
--- /dev/null
+++ b/shared/src/i18n/it/categories.ts
@@ -0,0 +1,26 @@
+import type { TranslationStrings } from '../types';
+
+const categories: TranslationStrings = {
+ 'categories.title': 'Categorie',
+ 'categories.subtitle': 'Gestisci le categorie per i luoghi',
+ 'categories.new': 'Nuova categoria',
+ 'categories.empty': 'Ancora nessuna categoria',
+ 'categories.namePlaceholder': 'Nome categoria',
+ 'categories.icon': 'Icona',
+ 'categories.color': 'Colore',
+ 'categories.customColor': 'Scegli colore personalizzato',
+ 'categories.preview': 'Anteprima',
+ 'categories.defaultName': 'Categoria',
+ 'categories.update': 'Aggiorna',
+ 'categories.create': 'Crea',
+ 'categories.confirm.delete':
+ 'Eliminare la categoria? I luoghi in questa categoria non verranno eliminati.',
+ 'categories.toast.loadError': 'Impossibile caricare le categorie',
+ 'categories.toast.nameRequired': 'Inserisci un nome',
+ 'categories.toast.updated': 'Categoria aggiornata',
+ 'categories.toast.created': 'Categoria creata',
+ 'categories.toast.saveError': 'Impossibile salvare',
+ 'categories.toast.deleted': 'Categoria eliminata',
+ 'categories.toast.deleteError': 'Impossibile eliminare',
+};
+export default categories;
diff --git a/shared/src/i18n/it/collab.ts b/shared/src/i18n/it/collab.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0bf522b9019edd69a080e6ea4a26f9f18377a666
--- /dev/null
+++ b/shared/src/i18n/it/collab.ts
@@ -0,0 +1,77 @@
+import type { TranslationStrings } from '../types';
+
+const collab: TranslationStrings = {
+ 'collab.tabs.chat': 'Chat',
+ 'collab.tabs.notes': 'Note',
+ 'collab.tabs.polls': 'Sondaggi',
+ 'collab.whatsNext.title': "Cosa c'è dopo",
+ 'collab.whatsNext.today': 'Oggi',
+ 'collab.whatsNext.tomorrow': 'Domani',
+ 'collab.whatsNext.empty': 'Nessuna attività imminente',
+ 'collab.whatsNext.until': 'a',
+ 'collab.whatsNext.emptyHint': 'Le attività con orari appariranno qui',
+ 'collab.chat.send': 'Invia',
+ 'collab.chat.placeholder': 'Scrivi un messaggio...',
+ 'collab.chat.empty': 'Inizia la conversazione',
+ 'collab.chat.emptyHint':
+ 'I messaggi sono condivisi con tutti i membri del viaggio',
+ 'collab.chat.emptyDesc':
+ 'Condividi idee, programmi e aggiornamenti con il tuo gruppo di viaggio',
+ 'collab.chat.today': 'Oggi',
+ 'collab.chat.yesterday': 'Ieri',
+ 'collab.chat.deletedMessage': 'ha eliminato un messaggio',
+ 'collab.chat.reply': 'Rispondi',
+ 'collab.chat.loadMore': 'Carica messaggi precedenti',
+ 'collab.chat.justNow': 'ora',
+ 'collab.chat.minutesAgo': '{n}m fa',
+ 'collab.chat.hoursAgo': '{n}h fa',
+ 'collab.notes.title': 'Note',
+ 'collab.notes.new': 'Nuova nota',
+ 'collab.notes.empty': 'Ancora nessuna nota',
+ 'collab.notes.emptyHint': 'Inizia a raccogliere idee e programmi',
+ 'collab.notes.all': 'Tutte',
+ 'collab.notes.titlePlaceholder': 'Titolo della nota',
+ 'collab.notes.contentPlaceholder': 'Scrivi qualcosa...',
+ 'collab.notes.categoryPlaceholder': 'Categoria',
+ 'collab.notes.newCategory': 'Nuova categoria...',
+ 'collab.notes.category': 'Categoria',
+ 'collab.notes.noCategory': 'Nessuna categoria',
+ 'collab.notes.color': 'Colore',
+ 'collab.notes.save': 'Salva',
+ 'collab.notes.cancel': 'Annulla',
+ 'collab.notes.edit': 'Modifica',
+ 'collab.notes.delete': 'Elimina',
+ 'collab.notes.confirmDeleteTitle': 'Eliminare la nota?',
+ 'collab.notes.confirmDeleteBody': 'Questa nota verrà eliminata definitivamente.',
+ 'collab.notes.pin': 'Fissa',
+ 'collab.notes.unpin': 'Rimuovi',
+ 'collab.notes.daysAgo': '{n}g fa',
+ 'collab.notes.categorySettings': 'Gestisci categorie',
+ 'collab.notes.create': 'Crea',
+ 'collab.notes.website': 'Sito web',
+ 'collab.notes.websitePlaceholder': 'https://...',
+ 'collab.notes.attachFiles': 'Allega file',
+ 'collab.notes.noCategoriesYet': 'Ancora nessuna categoria',
+ 'collab.notes.emptyDesc': 'Crea una nota per iniziare',
+ 'collab.polls.title': 'Sondaggi',
+ 'collab.polls.new': 'Nuovo sondaggio',
+ 'collab.polls.empty': 'Ancora nessun sondaggio',
+ 'collab.polls.emptyHint': 'Chiedi al gruppo e votate insieme',
+ 'collab.polls.question': 'Domanda',
+ 'collab.polls.questionPlaceholder': 'Cosa dovremmo fare?',
+ 'collab.polls.addOption': '+ Aggiungi opzione',
+ 'collab.polls.optionPlaceholder': 'Opzione {n}',
+ 'collab.polls.create': 'Crea sondaggio',
+ 'collab.polls.close': 'Chiudi',
+ 'collab.polls.closed': 'Chiuso',
+ 'collab.polls.votes': '{n} voti',
+ 'collab.polls.vote': '{n} voto',
+ 'collab.polls.multipleChoice': 'Scelta multipla',
+ 'collab.polls.multiChoice': 'Scelta multipla',
+ 'collab.polls.deadline': 'Scadenza',
+ 'collab.polls.option': 'Opzione',
+ 'collab.polls.options': 'Opzioni',
+ 'collab.polls.delete': 'Elimina',
+ 'collab.polls.closedSection': 'Chiusi',
+};
+export default collab;
diff --git a/shared/src/i18n/it/common.ts b/shared/src/i18n/it/common.ts
new file mode 100644
index 0000000000000000000000000000000000000000..de55313919113c6ca97b4330a52669a10f9e1c1d
--- /dev/null
+++ b/shared/src/i18n/it/common.ts
@@ -0,0 +1,54 @@
+import type { TranslationStrings } from '../types';
+
+const common: TranslationStrings = {
+ 'common.save': 'Salva',
+ 'common.showMore': 'Mostra di più',
+ 'common.showLess': 'Mostra meno',
+ 'common.cancel': 'Annulla',
+ 'common.clear': 'Cancella',
+ 'common.delete': 'Elimina',
+ 'common.edit': 'Modifica',
+ 'common.add': 'Aggiungi',
+ 'common.loading': 'Caricamento...',
+ 'common.import': 'Importa',
+ 'common.select': 'Seleziona',
+ 'common.selectAll': 'Seleziona tutto',
+ 'common.deselectAll': 'Deseleziona tutto',
+ 'common.error': 'Errore',
+ 'common.unknownError': 'Errore sconosciuto',
+ 'common.tooManyAttempts': 'Troppi tentativi. Riprova più tardi.',
+ 'common.back': 'Indietro',
+ 'common.all': 'Tutti',
+ 'common.close': 'Chiudi',
+ 'common.open': 'Apri',
+ 'common.upload': 'Carica',
+ 'common.search': 'Cerca',
+ 'common.confirm': 'Conferma',
+ 'common.ok': 'OK',
+ 'common.yes': 'Sì',
+ 'common.no': 'No',
+ 'common.or': 'o',
+ 'common.none': 'Nessuno',
+ 'common.date': 'Data',
+ 'common.rename': 'Rinomina',
+ 'common.discardChanges': 'Scarta modifiche',
+ 'common.discard': 'Scarta',
+ 'common.name': 'Nome',
+ 'common.email': 'Email',
+ 'common.password': 'Password',
+ 'common.saving': 'Salvataggio...',
+ 'common.saved': 'Salvato',
+ 'common.expand': 'Espandi',
+ 'common.collapse': 'Comprimi',
+ 'common.update': 'Aggiorna',
+ 'common.change': 'Cambia',
+ 'common.uploading': 'Caricamento…',
+ 'common.backToPlanning': 'Torna al Programma',
+ 'common.reset': 'Reimposta',
+ 'common.copy': 'Copia',
+ 'common.copied': 'Copiato',
+ 'common.justNow': 'proprio ora',
+ 'common.hoursAgo': '{count}h fa',
+ 'common.daysAgo': '{count}g fa',
+};
+export default common;
diff --git a/shared/src/i18n/it/dashboard.ts b/shared/src/i18n/it/dashboard.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b6c73a8012fefe85a623e7dbc509087e0994a77a
--- /dev/null
+++ b/shared/src/i18n/it/dashboard.ts
@@ -0,0 +1,172 @@
+import type { TranslationStrings } from '../types';
+
+const dashboard: TranslationStrings = {
+ 'dashboard.title': 'I miei Viaggi',
+ 'dashboard.subtitle.loading': 'Caricamento viaggi...',
+ 'dashboard.subtitle.trips': '{count} viaggi ({archived} archiviati)',
+ 'dashboard.subtitle.empty': 'Inizia il tuo primo viaggio',
+ 'dashboard.subtitle.activeOne': '{count} viaggio attivo',
+ 'dashboard.subtitle.activeMany': '{count} viaggi attivi',
+ 'dashboard.subtitle.archivedSuffix': ' · {count} archiviati',
+ 'dashboard.newTrip': 'Nuovo Viaggio',
+ 'dashboard.newTripSub': 'Pianifica un nuovo viaggio da zero',
+ 'dashboard.gridView': 'Vista a griglia',
+ 'dashboard.listView': 'Vista a lista',
+ 'dashboard.currency': 'Valuta',
+ 'dashboard.timezone': 'Fusi orari',
+ 'dashboard.localTime': 'Locale',
+ 'dashboard.timezoneCustomTitle': 'Fuso orario personalizzato',
+ 'dashboard.timezoneCustomLabelPlaceholder': 'Etichetta (opzionale)',
+ 'dashboard.timezoneCustomTzPlaceholder': 'es. Europe/Rome',
+ 'dashboard.timezoneCustomAdd': 'Aggiungi',
+ 'dashboard.timezoneCustomErrorEmpty':
+ 'Inserisci un identificatore di fuso orario',
+ 'dashboard.timezoneCustomErrorInvalid':
+ 'Fuso orario non valido. Usa formati come Europe/Rome',
+ 'dashboard.timezoneCustomErrorDuplicate': 'Già aggiunto',
+ 'dashboard.emptyTitle': 'Ancora nessun viaggio',
+ 'dashboard.emptyText': 'Crea il tuo primo viaggio e inizia a programmare!',
+ 'dashboard.emptyButton': 'Crea il primo viaggio',
+ 'dashboard.nextTrip': 'Prossimo Viaggio',
+ 'dashboard.shared': 'Condiviso',
+ 'dashboard.sharedBy': 'Condiviso da {name}',
+ 'dashboard.days': 'Giorni',
+ 'dashboard.places': 'Luoghi',
+ 'dashboard.members': 'Compagni di viaggio',
+ 'dashboard.archive': 'Archivia',
+ 'dashboard.copyTrip': 'Copia',
+ 'dashboard.copySuffix': 'copia',
+ 'dashboard.restore': 'Ripristina',
+ 'dashboard.archived': 'Archiviati',
+ 'dashboard.status.ongoing': 'In corso',
+ 'dashboard.status.today': 'Oggi',
+ 'dashboard.status.tomorrow': 'Domani',
+ 'dashboard.status.past': 'Passato',
+ 'dashboard.status.daysLeft': '-{count} giorni',
+ 'dashboard.toast.loadError': 'Impossibile caricare i viaggi',
+ 'dashboard.toast.created': 'Viaggio creato con successo!',
+ 'dashboard.toast.createError': 'Impossibile creare il viaggio',
+ 'dashboard.toast.updated': 'Viaggio aggiornato!',
+ 'dashboard.toast.updateError': 'Impossibile aggiornare il viaggio',
+ 'dashboard.toast.deleted': 'Viaggio eliminato',
+ 'dashboard.toast.deleteError': 'Impossibile eliminare il viaggio',
+ 'dashboard.toast.archived': 'Viaggio archiviato',
+ 'dashboard.toast.archiveError': 'Impossibile archiviare il viaggio',
+ 'dashboard.toast.restored': 'Viaggio ripristinato',
+ 'dashboard.toast.restoreError': 'Impossibile ripristinare il viaggio',
+ 'dashboard.toast.copied': 'Viaggio copiato!',
+ 'dashboard.toast.copyError': 'Impossibile copiare il viaggio',
+ 'dashboard.confirm.delete':
+ 'Eliminare il viaggio "{title}"? Tutti i luoghi e i programmi verranno eliminati in modo permanente.',
+ 'dashboard.editTrip': 'Modifica Viaggio',
+ 'dashboard.createTrip': 'Crea Nuovo Viaggio',
+ 'dashboard.tripTitle': 'Titolo',
+ 'dashboard.tripTitlePlaceholder': 'es. Estate in Giappone',
+ 'dashboard.tripDescription': 'Descrizione',
+ 'dashboard.tripDescriptionPlaceholder': 'Di cosa tratta questo viaggio?',
+ 'dashboard.startDate': 'Data di inizio',
+ 'dashboard.endDate': 'Data di fine',
+ 'dashboard.dayCount': 'Numero di giorni',
+ 'dashboard.dayCountHint':
+ 'Quanti giorni pianificare quando non sono impostate date di viaggio.',
+ 'dashboard.noDateHint':
+ 'Nessuna data impostata — verranno creati 7 giorni predefiniti. Puoi cambiarlo in qualsiasi momento.',
+ 'dashboard.coverImage': 'Immagine di copertina',
+ 'dashboard.addCoverImage':
+ 'Aggiungi immagine di copertina (o trascinala qui)',
+ 'dashboard.addMembers': 'Compagni di viaggio',
+ 'dashboard.addMember': 'Aggiungi membro',
+ 'dashboard.coverSaved': 'Immagine di copertina salvata',
+ 'dashboard.coverUploadError': 'Impossibile caricare',
+ 'dashboard.coverRemoveError': 'Impossibile rimuovere',
+ 'dashboard.titleRequired': 'Il titolo è obbligatorio',
+ 'dashboard.endDateError':
+ 'La data di fine deve essere successiva alla data di inizio',
+ 'dashboard.greeting.morning': 'Buongiorno,',
+ 'dashboard.greeting.afternoon': 'Buon pomeriggio,',
+ 'dashboard.greeting.evening': 'Buonasera,',
+ 'dashboard.mobile.liveNow': 'In diretta',
+ 'dashboard.mobile.tripProgress': 'Progresso del viaggio',
+ 'dashboard.mobile.daysLeft': '{count} giorni rimanenti',
+ 'dashboard.mobile.places': 'Luoghi',
+ 'dashboard.mobile.buddies': 'Compagni',
+ 'dashboard.mobile.newTrip': 'Nuovo viaggio',
+ 'dashboard.mobile.currency': 'Valuta',
+ 'dashboard.mobile.timezone': 'Fuso orario',
+ 'dashboard.mobile.upcomingTrips': 'Viaggi in arrivo',
+ 'dashboard.mobile.yourTrips': 'I tuoi viaggi',
+ 'dashboard.mobile.trips': 'viaggi',
+ 'dashboard.mobile.starts': 'Inizio',
+ 'dashboard.mobile.duration': 'Durata',
+ 'dashboard.mobile.day': 'giorno',
+ 'dashboard.mobile.days': 'giorni',
+ 'dashboard.mobile.ongoing': 'In corso',
+ 'dashboard.mobile.startsToday': 'Inizia oggi',
+ 'dashboard.mobile.tomorrow': 'Domani',
+ 'dashboard.mobile.inDays': 'Tra {count} giorni',
+ 'dashboard.mobile.inMonths': 'Tra {count} mesi',
+ 'dashboard.mobile.completed': 'Completato',
+ 'dashboard.mobile.currencyConverter': 'Convertitore di valuta',
+ 'dashboard.filter.planned': 'Pianificati',
+ 'dashboard.hero.badgeLive': 'IN DIRETTA',
+ 'dashboard.hero.badgeToday': 'INIZIA OGGI',
+ 'dashboard.hero.badgeTomorrow': 'DOMANI',
+ 'dashboard.hero.badgeNext': 'PROSSIMO',
+ 'dashboard.hero.badgeRecent': 'RECENTE',
+ 'dashboard.hero.tripDates': 'Date del viaggio',
+ 'dashboard.hero.noDates': 'Nessuna data impostata',
+ 'dashboard.hero.travelerOne': '{count} viaggiatore',
+ 'dashboard.hero.travelerMany': '{count} viaggiatori',
+ 'dashboard.hero.destinationOne': '{count} destinazione',
+ 'dashboard.hero.destinationMany': '{count} destinazioni',
+ 'dashboard.hero.dayUnitOne': 'giorno',
+ 'dashboard.hero.dayUnitMany': 'giorni',
+ 'dashboard.hero.dayLeft': 'Giorno rimasto',
+ 'dashboard.hero.daysLeft': 'Giorni rimasti',
+ 'dashboard.hero.lastDay': 'Ultimo giorno',
+ 'dashboard.hero.untilStart': "All'inizio",
+ 'dashboard.hero.startsIn': 'Si parte tra',
+ 'dashboard.atlas.countriesVisited': 'Atlas · Paesi visitati',
+ 'dashboard.atlas.ofTotal': 'di {total}',
+ 'dashboard.atlas.tripsTotal': 'Viaggi totali',
+ 'dashboard.atlas.placesMapped': '{count} luoghi mappati',
+ 'dashboard.atlas.daysTraveled': 'Giorni in viaggio',
+ 'dashboard.atlas.daysUnit': 'giorni',
+ 'dashboard.atlas.acrossAllTrips': 'su tutti i viaggi',
+ 'dashboard.atlas.distanceFlown': 'Distanza in volo',
+ 'dashboard.atlas.kmUnit': 'km',
+ 'dashboard.atlas.aroundEquator': "≈ {count}× intorno all'equatore",
+ 'dashboard.card.idea': 'Idea',
+ 'dashboard.card.buddyOne': 'Compagno',
+ 'dashboard.fx.from': 'Da',
+ 'dashboard.fx.to': 'A',
+ 'dashboard.fx.unavailable': 'Tasso non disponibile',
+ 'dashboard.tz.searchPlaceholder': 'Cerca fuso orario…',
+ 'dashboard.tz.empty':
+ 'Ancora nessun altro fuso orario — aggiungine uno con +',
+ 'dashboard.upcoming.title': 'Prossime prenotazioni',
+ 'dashboard.upcoming.empty': 'Niente ancora prenotato.',
+ 'dashboard.confirm.copy.title': 'Copiare questo viaggio?',
+ 'dashboard.confirm.copy.willCopy': 'Verrà copiato',
+ 'dashboard.confirm.copy.will1': 'Giorni, luoghi e assegnazioni giornaliere',
+ 'dashboard.confirm.copy.will2': 'Alloggi e prenotazioni',
+ 'dashboard.confirm.copy.will3': 'Voci di budget e ordine delle categorie',
+ 'dashboard.confirm.copy.will4': 'Liste bagagli (non spuntate)',
+ 'dashboard.confirm.copy.will5': 'Attività (non assegnate e non spuntate)',
+ 'dashboard.confirm.copy.will6': 'Note del giorno',
+ 'dashboard.confirm.copy.wontCopy': 'Non verrà copiato',
+ 'dashboard.confirm.copy.wont1': 'Collaboratori e assegnazioni dei membri',
+ 'dashboard.confirm.copy.wont2': 'Note, sondaggi e messaggi condivisi',
+ 'dashboard.confirm.copy.wont3': 'File e foto',
+ 'dashboard.confirm.copy.wont4': 'Token di condivisione',
+ 'dashboard.confirm.copy.confirm': 'Copia viaggio',
+ 'dashboard.aria.toggleView': 'Cambia vista',
+ 'dashboard.aria.filter': 'Filtra',
+ 'dashboard.aria.duplicate': 'Duplica',
+ 'dashboard.aria.refreshRates': 'Aggiorna i tassi',
+ 'dashboard.aria.swapCurrencies': 'Inverti valute',
+ 'dashboard.aria.addTimezone': 'Aggiungi fuso orario',
+ 'dashboard.aria.removeTimezone': 'Rimuovi {city}',
+ 'dashboard.dayCountRequired': 'Il numero di giorni è obbligatorio',
+};
+export default dashboard;
diff --git a/shared/src/i18n/it/day.ts b/shared/src/i18n/it/day.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ea08b3c3dbc2b9e5dd270fede081e35d4e23b9c0
--- /dev/null
+++ b/shared/src/i18n/it/day.ts
@@ -0,0 +1,27 @@
+import type { TranslationStrings } from '../types';
+
+const day: TranslationStrings = {
+ 'day.precipProb': 'Probabilità di pioggia',
+ 'day.precipitation': 'Precipitazioni',
+ 'day.wind': 'Vento',
+ 'day.sunrise': 'Alba',
+ 'day.sunset': 'Tramonto',
+ 'day.hourlyForecast': 'Previsione oraria',
+ 'day.climateHint':
+ 'Medie storiche — previsioni reali disponibili entro 16 giorni da questa data.',
+ 'day.noWeather':
+ 'Nessun dato meteo disponibile. Aggiungi un luogo con coordinate.',
+ 'day.overview': 'Panoramica giornaliera',
+ 'day.accommodation': 'Alloggio',
+ 'day.addAccommodation': 'Aggiungi alloggio',
+ 'day.hotelDayRange': 'Applica ai giorni',
+ 'day.noPlacesForHotel': 'Aggiungi prima i luoghi al tuo viaggio',
+ 'day.allDays': 'Tutti',
+ 'day.checkIn': 'Check-in',
+ 'day.checkInUntil': 'Fino a',
+ 'day.checkOut': 'Check-out',
+ 'day.confirmation': 'Conferma',
+ 'day.editAccommodation': 'Modifica alloggio',
+ 'day.reservations': 'Prenotazioni',
+};
+export default day;
diff --git a/shared/src/i18n/it/dayplan.ts b/shared/src/i18n/it/dayplan.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4e95fe7932c674330ebeeffaaa18610e6fae057d
--- /dev/null
+++ b/shared/src/i18n/it/dayplan.ts
@@ -0,0 +1,62 @@
+import type { TranslationStrings } from '../types';
+
+const dayplan: TranslationStrings = {
+ 'dayplan.icsTooltip': 'Esporta calendario (ICS)',
+ 'dayplan.emptyDay': 'Nessun luogo programmato per questo giorno',
+ 'dayplan.addNote': 'Aggiungi nota',
+ 'dayplan.editNote': 'Modifica nota',
+ 'dayplan.noteAdd': 'Aggiungi nota',
+ 'dayplan.noteEdit': 'Modifica nota',
+ 'dayplan.noteTitle': 'Nota',
+ 'dayplan.noteSubtitle': 'Nota giornaliera',
+ 'dayplan.totalCost': 'Costo totale',
+ 'dayplan.days': 'Giorni',
+ 'dayplan.dayN': 'Giorno {n}',
+ 'dayplan.calculating': 'Calcolo in corso...',
+ 'dayplan.route': 'Percorso',
+ 'dayplan.optimize': 'Ottimizza',
+ 'dayplan.optimized': 'Percorso ottimizzato',
+ 'dayplan.routeError': 'Impossibile calcolare il percorso',
+ 'dayplan.toast.needTwoPlaces':
+ "Servono almeno due luoghi per l'ottimizzazione del percorso",
+ 'dayplan.toast.routeOptimized': 'Percorso ottimizzato',
+ 'dayplan.toast.routeOptimizedFromHotel':
+ 'Percorso ottimizzato a partire dal tuo alloggio',
+ 'dayplan.toast.noGeoPlaces':
+ 'Nessun luogo con coordinate trovato per il calcolo del percorso',
+ 'dayplan.confirmed': 'Confermata',
+ 'dayplan.pendingRes': 'In attesa',
+ 'dayplan.pdf': 'PDF',
+ 'dayplan.pdfTooltip': 'Esporta il programma del giorno come PDF',
+ 'dayplan.pdfError': 'Impossibile esportare il PDF',
+ 'dayplan.cannotReorderTransport':
+ 'Le prenotazioni con un orario fisso non possono essere riordinate',
+ 'dayplan.confirmRemoveTimeTitle': "Rimuovere l'orario?",
+ 'dayplan.confirmRemoveTimeBody':
+ "Questo luogo ha un orario fisso ({time}). Spostarlo rimuoverà l'orario e consentirà l'ordinamento libero.",
+ 'dayplan.confirmRemoveTimeAction': 'Rimuovi orario e sposta',
+ 'dayplan.confirmDeleteNoteTitle': 'Eliminare la nota?',
+ 'dayplan.confirmDeleteNoteBody':
+ 'Questa nota verrà eliminata definitivamente.',
+ 'dayplan.cannotDropOnTimed':
+ 'Gli elementi non possono essere posizionati tra voci con orario fisso',
+ 'dayplan.cannotBreakChronology':
+ "Ciò interromperebbe l'ordine cronologico degli elementi e delle prenotazioni pianificati",
+ 'dayplan.mobile.addPlace': 'Aggiungi luogo',
+ 'dayplan.mobile.searchPlaces': 'Cerca luoghi...',
+ 'dayplan.mobile.allAssigned': 'Tutti i luoghi assegnati',
+ 'dayplan.mobile.noMatch': 'Nessun risultato',
+ 'dayplan.mobile.createNew': 'Crea nuovo luogo',
+ 'dayplan.expandAll': 'Expand all days', // en-fallback
+ 'dayplan.collapseAll': 'Collapse all days', // en-fallback
+ 'dayplan.reorderDays': 'Riordina i giorni',
+ 'dayplan.reorderTitle': 'Riordina i giorni',
+ 'dayplan.reorderHint': 'I luoghi, le note e le prenotazioni di un giorno si spostano insieme a esso.',
+ 'dayplan.addDay': 'Aggiungi giorno',
+ 'dayplan.moveUp': 'Sposta su',
+ 'dayplan.moveDown': 'Sposta giù',
+ 'dayplan.reorderUndo': 'Riordina i giorni',
+ 'dayplan.reorderError': 'Riordino dei giorni non riuscito',
+ 'dayplan.addDayError': 'Aggiunta del giorno non riuscita',
+};
+export default dayplan;
diff --git a/shared/src/i18n/it/externalNotifications.ts b/shared/src/i18n/it/externalNotifications.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1895def70390c8fc46614abae04620f2b3268ddd
--- /dev/null
+++ b/shared/src/i18n/it/externalNotifications.ts
@@ -0,0 +1,64 @@
+import type { NotificationLocale } from '../externalNotifications/types';
+
+const it: NotificationLocale = {
+ email: {
+ footer:
+ 'Hai ricevuto questa email perché hai le notifiche abilitate in TREK.',
+ manage: 'Gestisci le preferenze nelle impostazioni',
+ madeWith: 'Made with',
+ openTrek: 'Apri TREK',
+ },
+ events: {
+ trip_invite: (p) => ({
+ title: `Invito a "${p.trip}"`,
+ body: `${p.actor} ha invitato ${p.invitee || 'un membro'} al viaggio "${p.trip}".`,
+ }),
+ booking_change: (p) => ({
+ title: `Nuova prenotazione: ${p.booking}`,
+ body: `${p.actor} ha aggiunto una prenotazione "${p.booking}" (${p.type}) a "${p.trip}".`,
+ }),
+ trip_reminder: (p) => ({
+ title: `Promemoria viaggio: ${p.trip}`,
+ body: `Il tuo viaggio "${p.trip}" si avvicina!`,
+ }),
+ todo_due: (p) => ({
+ title: `Attività in scadenza: ${p.todo}`,
+ body: `"${p.todo}" in "${p.trip}" scade il ${p.due}.`,
+ }),
+ vacay_invite: (p) => ({
+ title: 'Invito Vacay Fusion',
+ body: `${p.actor} ti ha invitato a fondere i piani vacanza. Apri TREK per accettare o rifiutare.`,
+ }),
+ photos_shared: (p) => ({
+ title: `${p.count} foto condivise`,
+ body: `${p.actor} ha condiviso ${p.count} foto in "${p.trip}".`,
+ }),
+ collab_message: (p) => ({
+ title: `Nuovo messaggio in "${p.trip}"`,
+ body: `${p.actor}: ${p.preview}`,
+ }),
+ packing_tagged: (p) => ({
+ title: `Bagagli: ${p.category}`,
+ body: `${p.actor} ti ha assegnato alla categoria "${p.category}" in "${p.trip}".`,
+ }),
+ version_available: (p) => ({
+ title: 'Nuova versione TREK disponibile',
+ body: `TREK ${p.version} è ora disponibile. Visita il pannello di amministrazione per aggiornare.`,
+ }),
+ synology_session_cleared: () => ({
+ title: 'Sessione Synology rimossa',
+ body: 'Il tuo account o URL Synology è cambiato. Sei stato disconnesso da Synology Photos.',
+ }),
+ },
+ passwordReset: {
+ subject: 'Reimposta la tua password',
+ greeting: 'Ciao',
+ body: 'Abbiamo ricevuto una richiesta di reimpostazione della password per il tuo account TREK. Clicca il pulsante qui sotto per impostare una nuova password.',
+ ctaIntro: 'Reimposta password',
+ expiry: 'Questo link scade tra 60 minuti.',
+ ignore:
+ 'Se non hai richiesto questa operazione, ignora questa email — la tua password non cambierà.',
+ },
+};
+
+export default it;
diff --git a/shared/src/i18n/it/files.ts b/shared/src/i18n/it/files.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2f388c275b1ec5179fd9764eda8c16d8900df02b
--- /dev/null
+++ b/shared/src/i18n/it/files.ts
@@ -0,0 +1,62 @@
+import type { TranslationStrings } from '../types';
+
+const files: TranslationStrings = {
+ 'files.title': 'File',
+ 'files.pageTitle': 'File e documenti',
+ 'files.subtitle': '{count} file per {trip}',
+ 'files.download': 'Scarica',
+ 'files.openError': 'Impossibile aprire il file',
+ 'files.downloadPdf': 'Scarica PDF',
+ 'files.count': '{count} file',
+ 'files.countSingular': '1 documento',
+ 'files.uploaded': '{count} caricati',
+ 'files.uploadError': 'Caricamento non riuscito',
+ 'files.dropzone': 'Trascina qui i file',
+ 'files.dropzoneHint': 'oppure clicca per sfogliare',
+ 'files.allowedTypes':
+ 'Immagini, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Max 50 MB',
+ 'files.uploading': 'Caricamento...',
+ 'files.filterAll': 'Tutti',
+ 'files.filterPdf': 'PDF',
+ 'files.filterImages': 'Immagini',
+ 'files.filterDocs': 'Documenti',
+ 'files.filterCollab': 'Note Collaborazione',
+ 'files.sourceCollab': 'Da Note Collaborazione',
+ 'files.empty': 'Ancora nessun file',
+ 'files.emptyHint': 'Carica file per allegarli al tuo viaggio',
+ 'files.openTab': 'Apri in una nuova scheda',
+ 'files.confirm.delete': 'Sei sicuro di voler eliminare questo file?',
+ 'files.toast.deleted': 'File eliminato',
+ 'files.toast.deleteError': 'Impossibile eliminare il file',
+ 'files.sourcePlan': 'Programma giornaliero',
+ 'files.sourceBooking': 'Prenotazione',
+ 'files.sourceTransport': 'Trasporto',
+ 'files.attach': 'Allega',
+ 'files.pasteHint': 'Puoi anche incollare immagini dagli appunti (Ctrl+V)',
+ 'files.trash': 'Cestino',
+ 'files.trashEmpty': 'Il cestino è vuoto',
+ 'files.emptyTrash': 'Svuota cestino',
+ 'files.restore': 'Ripristina',
+ 'files.star': 'Aggiungi ai preferiti',
+ 'files.unstar': 'Rimuovi dai preferiti',
+ 'files.assign': 'Assegna',
+ 'files.assignTitle': 'Assegna file',
+ 'files.assignPlace': 'Luogo',
+ 'files.assignBooking': 'Prenotazione',
+ 'files.assignTransport': 'Trasporto',
+ 'files.unassigned': 'Non assegnato',
+ 'files.unlink': 'Rimuovi collegamento',
+ 'files.toast.trashed': 'Spostato nel cestino',
+ 'files.toast.restored': 'File ripristinato',
+ 'files.toast.trashEmptied': 'Cestino svuotato',
+ 'files.toast.assigned': 'File assegnato',
+ 'files.toast.assignError': 'Assegnazione fallita',
+ 'files.toast.restoreError': 'Ripristino fallito',
+ 'files.confirm.permanentDelete':
+ 'Eliminare questo file in modo permanente? Questa operazione non può essere annullata.',
+ 'files.confirm.emptyTrash':
+ 'Eliminare in modo permanente tutti i file nel cestino? Questa operazione non può essere annullata.',
+ 'files.noteLabel': 'Nota',
+ 'files.notePlaceholder': 'Aggiungi una nota...',
+};
+export default files;
diff --git a/shared/src/i18n/it/index.ts b/shared/src/i18n/it/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..77aeb6a09fb62e2522e8b4c04e4169a94c2f8d99
--- /dev/null
+++ b/shared/src/i18n/it/index.ts
@@ -0,0 +1,86 @@
+import admin from './admin';
+import airport from './airport';
+import atlas from './atlas';
+import backup from './backup';
+import budget from './budget';
+import categories from './categories';
+import collab from './collab';
+import common from './common';
+import dashboard from './dashboard';
+import day from './day';
+import dayplan from './dayplan';
+import files from './files';
+import inspector from './inspector';
+import journey from './journey';
+import login from './login';
+import map from './map';
+import members from './members';
+import memories from './memories';
+import nav from './nav';
+import notif from './notif';
+import notifications from './notifications';
+import oauth from './oauth';
+import packing from './packing';
+import pdf from './pdf';
+import perm from './perm';
+import photos from './photos';
+import places from './places';
+import planner from './planner';
+import register from './register';
+import reservations from './reservations';
+import settings from './settings';
+import share from './share';
+import shared from './shared';
+import stats from './stats';
+import system_notice from './system_notice';
+import todo from './todo';
+import transport from './transport';
+import trip from './trip';
+import trips from './trips';
+import undo from './undo';
+import vacay from './vacay';
+
+const locale = {
+ ...common,
+ ...trips,
+ ...nav,
+ ...dashboard,
+ ...settings,
+ ...admin,
+ ...dayplan,
+ ...share,
+ ...shared,
+ ...login,
+ ...register,
+ ...vacay,
+ ...atlas,
+ ...trip,
+ ...places,
+ ...inspector,
+ ...reservations,
+ ...airport,
+ ...map,
+ ...budget,
+ ...files,
+ ...packing,
+ ...members,
+ ...categories,
+ ...backup,
+ ...photos,
+ ...pdf,
+ ...planner,
+ ...stats,
+ ...day,
+ ...memories,
+ ...collab,
+ ...perm,
+ ...undo,
+ ...notifications,
+ ...todo,
+ ...notif,
+ ...journey,
+ ...oauth,
+ ...system_notice,
+ ...transport,
+};
+export default locale;
diff --git a/shared/src/i18n/it/inspector.ts b/shared/src/i18n/it/inspector.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7927eb87806a6435f476549af296418de55ef6bc
--- /dev/null
+++ b/shared/src/i18n/it/inspector.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const inspector: TranslationStrings = {
+ 'inspector.opened': 'Aperto',
+ 'inspector.closed': 'Chiuso',
+ 'inspector.openingHours': 'Orari di apertura',
+ 'inspector.showHours': 'Mostra orari di apertura',
+ 'inspector.files': 'File',
+ 'inspector.filesCount': '{count} file',
+ 'inspector.removeFromDay': 'Rimuovi dal giorno',
+ 'inspector.remove': 'Rimuovi',
+ 'inspector.addToDay': 'Aggiungi al giorno',
+ 'inspector.confirmedRes': 'Prenotazione confermata',
+ 'inspector.pendingRes': 'Prenotazione in attesa',
+ 'inspector.google': 'Apri in Google Maps',
+ 'inspector.website': 'Apri sito web',
+ 'inspector.addRes': 'Prenotazione',
+ 'inspector.editRes': 'Modifica prenotazione',
+ 'inspector.participants': 'Partecipanti',
+ 'inspector.trackStats': 'Dati del percorso',
+};
+export default inspector;
diff --git a/shared/src/i18n/it/journey.ts b/shared/src/i18n/it/journey.ts
new file mode 100644
index 0000000000000000000000000000000000000000..94921c4e214684f7ab907ebdcce9549f57664661
--- /dev/null
+++ b/shared/src/i18n/it/journey.ts
@@ -0,0 +1,245 @@
+import type { TranslationStrings } from '../types';
+
+const journey: TranslationStrings = {
+ 'journey.search.placeholder': 'Cerca viaggi…',
+ 'journey.search.noResults': 'Nessun viaggio corrisponde a "{query}"',
+ 'journey.title': 'Diario di viaggio',
+ 'journey.subtitle': 'Segui i tuoi viaggi in tempo reale',
+ 'journey.new': 'Nuovo diario',
+ 'journey.create': 'Crea',
+ 'journey.titlePlaceholder': 'Dove stai andando?',
+ 'journey.empty': 'Nessun diario ancora',
+ 'journey.emptyHint': 'Inizia a documentare il tuo prossimo viaggio',
+ 'journey.deleted': 'Diario eliminato',
+ 'journey.createError': 'Impossibile creare il diario',
+ 'journey.deleteError': 'Impossibile eliminare il diario',
+ 'journey.deleteConfirmTitle': 'Elimina',
+ 'journey.deleteConfirmMessage':
+ 'Eliminare "{title}"? Questa azione non può essere annullata.',
+ 'journey.deleteConfirmGeneric': 'Sei sicuro di voler eliminare questo?',
+ 'journey.notFound': 'Diario non trovato',
+ 'journey.photos': 'Foto',
+ 'journey.timelineEmpty': 'Nessuna tappa ancora',
+ 'journey.timelineEmptyHint':
+ 'Aggiungi un check-in o scrivi una voce di diario per iniziare',
+ 'journey.status.draft': 'Bozza',
+ 'journey.status.active': 'Attivo',
+ 'journey.status.completed': 'Completato',
+ 'journey.status.upcoming': 'In arrivo',
+ 'journey.status.archived': 'Archiviato',
+ 'journey.checkin.add': 'Check-in',
+ 'journey.checkin.namePlaceholder': 'Nome del luogo',
+ 'journey.checkin.notesPlaceholder': 'Note (facoltativo)',
+ 'journey.checkin.save': 'Salva',
+ 'journey.checkin.error': 'Impossibile salvare il check-in',
+ 'journey.entry.add': 'Diario',
+ 'journey.entry.edit': 'Modifica voce',
+ 'journey.entry.titlePlaceholder': 'Titolo (facoltativo)',
+ 'journey.entry.bodyPlaceholder': 'Cosa è successo oggi?',
+ 'journey.entry.save': 'Salva',
+ 'journey.entry.error': 'Impossibile salvare la voce',
+ 'journey.photo.add': 'Foto',
+ 'journey.photo.uploadError': 'Caricamento fallito',
+ 'journey.share.share': 'Condividi',
+ 'journey.share.public': 'Pubblico',
+ 'journey.share.linkCopied': 'Link pubblico copiato',
+ 'journey.share.disabled': 'Condivisione pubblica disattivata',
+ 'journey.editor.titlePlaceholder': 'Dai un nome a questo momento...',
+ 'journey.editor.bodyPlaceholder': 'Racconta la storia di questa giornata...',
+ 'journey.editor.placePlaceholder': 'Luogo (facoltativo)',
+ 'journey.editor.tagsPlaceholder':
+ 'Tag: gioiello nascosto, miglior pasto, da rivisitare...',
+ 'journey.visibility.private': 'Privato',
+ 'journey.visibility.shared': 'Condiviso',
+ 'journey.visibility.public': 'Pubblico',
+ 'journey.emptyState.title': 'La tua storia inizia qui',
+ 'journey.emptyState.subtitle':
+ 'Fai un check-in o scrivi la tua prima voce di diario',
+ 'journey.frontpage.subtitle':
+ 'Trasforma i tuoi viaggi in storie indimenticabili',
+ 'journey.frontpage.createJourney': 'Crea diario',
+ 'journey.frontpage.activeJourney': 'Diario attivo',
+ 'journey.frontpage.allJourneys': 'Tutti i diari',
+ 'journey.frontpage.journeys': 'diari',
+ 'journey.frontpage.createNew': 'Crea un nuovo diario',
+ 'journey.frontpage.createNewSub':
+ 'Scegli viaggi, scrivi storie, condividi le tue avventure',
+ 'journey.frontpage.live': 'In diretta',
+ 'journey.frontpage.synced': 'Sincronizzato',
+ 'journey.frontpage.continueWriting': 'Continua a scrivere',
+ 'journey.frontpage.updated': 'Aggiornato {time}',
+ 'journey.frontpage.suggestionLabel': 'Viaggio appena terminato',
+ 'journey.frontpage.suggestionText':
+ 'Trasforma {title} in un diario di viaggio',
+ 'journey.frontpage.dismiss': 'Ignora',
+ 'journey.frontpage.journeyName': 'Nome del diario',
+ 'journey.frontpage.namePlaceholder': 'es. Sud-est asiatico 2026',
+ 'journey.frontpage.selectTrips': 'Seleziona viaggi',
+ 'journey.frontpage.tripsSelected': 'viaggi selezionati',
+ 'journey.frontpage.trips': 'viaggi',
+ 'journey.frontpage.placesImported': 'luoghi saranno importati',
+ 'journey.frontpage.places': 'luoghi',
+ 'journey.detail.backToJourney': 'Torna al diario',
+ 'journey.detail.syncedWithTrips': 'Sincronizzato con i viaggi',
+ 'journey.detail.addEntry': 'Aggiungi voce',
+ 'journey.detail.newEntry': 'Nuova voce',
+ 'journey.detail.editEntry': 'Modifica voce',
+ 'journey.detail.noEntries': 'Nessuna voce ancora',
+ 'journey.detail.noEntriesHint':
+ 'Aggiungi un viaggio per iniziare con voci precompilate',
+ 'journey.detail.noPhotos': 'Nessuna foto ancora',
+ 'journey.detail.noPhotosHint':
+ 'Carica foto nelle voci o sfoglia la tua libreria Immich/Synology',
+ 'journey.detail.journeyStats': 'Statistiche del diario',
+ 'journey.detail.syncedTrips': 'Viaggi sincronizzati',
+ 'journey.detail.noTripsLinked': 'Nessun viaggio collegato ancora',
+ 'journey.detail.contributors': 'Contributori',
+ 'journey.detail.readMore': 'Leggi di più',
+ 'journey.detail.prosCons': 'Pro e contro',
+ 'journey.detail.photos': 'foto',
+ 'journey.detail.day': 'Giorno {number}',
+ 'journey.detail.places': 'luoghi',
+ 'journey.stats.days': 'Giorni',
+ 'journey.stats.cities': 'Città',
+ 'journey.stats.entries': 'Voci',
+ 'journey.stats.photos': 'Foto',
+ 'journey.stats.places': 'Luoghi',
+ 'journey.skeletons.show': 'Mostra suggerimenti',
+ 'journey.skeletons.hide': 'Nascondi suggerimenti',
+ 'journey.verdict.lovedIt': 'Adorato',
+ 'journey.verdict.couldBeBetter': 'Potrebbe essere meglio',
+ 'journey.synced.places': 'luoghi',
+ 'journey.synced.synced': 'sincronizzato',
+ 'journey.editor.discardChangesConfirm':
+ 'Hai modifiche non salvate. Vuoi scartarle?',
+ 'journey.editor.uploadFailed': 'Caricamento foto non riuscito',
+ 'journey.editor.uploadPhotos': 'Carica foto',
+ 'journey.editor.uploading': 'Caricamento...',
+ 'journey.editor.uploadingProgress': 'Caricamento {done}/{total}…',
+ 'journey.editor.uploadPartialFailed':
+ '{failed} di {total} foto non riuscite — salva di nuovo per riprovare',
+ 'journey.editor.fromGallery': 'Dalla galleria',
+ 'journey.editor.allPhotosAdded': 'Tutte le foto sono già state aggiunte',
+ 'journey.editor.writeStory': 'Scrivi la tua storia...',
+ 'journey.editor.prosCons': 'Pro e contro',
+ 'journey.editor.pros': 'Pro',
+ 'journey.editor.cons': 'Contro',
+ 'journey.editor.proPlaceholder': 'Qualcosa di fantastico...',
+ 'journey.editor.conPlaceholder': 'Non così fantastico...',
+ 'journey.editor.addAnother': 'Aggiungi un altro',
+ 'journey.editor.date': 'Data',
+ 'journey.editor.location': 'Luogo',
+ 'journey.editor.searchLocation': 'Cerca luogo...',
+ 'journey.editor.mood': 'Umore',
+ 'journey.editor.weather': 'Meteo',
+ 'journey.editor.photoFirst': '1°',
+ 'journey.editor.makeFirst': 'Metti 1°',
+ 'journey.editor.searching': 'Ricerca...',
+ 'journey.mood.amazing': 'Fantastico',
+ 'journey.mood.good': 'Buono',
+ 'journey.mood.neutral': 'Neutro',
+ 'journey.mood.rough': 'Difficile',
+ 'journey.weather.sunny': 'Soleggiato',
+ 'journey.weather.partly': 'Parzialmente nuvoloso',
+ 'journey.weather.cloudy': 'Nuvoloso',
+ 'journey.weather.rainy': 'Piovoso',
+ 'journey.weather.stormy': 'Temporalesco',
+ 'journey.weather.cold': 'Nevoso',
+ 'journey.trips.linkTrip': 'Collega viaggio',
+ 'journey.trips.searchTrip': 'Cerca viaggio',
+ 'journey.trips.searchPlaceholder': 'Nome del viaggio o destinazione...',
+ 'journey.trips.noTripsAvailable': 'Nessun viaggio disponibile',
+ 'journey.trips.link': 'Collega',
+ 'journey.trips.tripLinked': 'Viaggio collegato',
+ 'journey.trips.linkFailed': 'Collegamento del viaggio fallito',
+ 'journey.trips.addTrip': 'Aggiungi viaggio',
+ 'journey.trips.unlinkTrip': 'Scollega viaggio',
+ 'journey.trips.unlinkMessage':
+ 'Scollegare "{title}"? Tutte le voci e le foto sincronizzate da questo viaggio saranno eliminate definitivamente. Questa azione non può essere annullata.',
+ 'journey.trips.unlink': 'Scollega',
+ 'journey.trips.tripUnlinked': 'Viaggio scollegato',
+ 'journey.trips.unlinkFailed': 'Scollegamento del viaggio fallito',
+ 'journey.trips.noTripsLinkedSettings': 'Nessun viaggio collegato',
+ 'journey.contributors.invite': 'Invita contributore',
+ 'journey.contributors.searchUser': 'Cerca utente',
+ 'journey.contributors.searchPlaceholder': 'Nome utente o e-mail...',
+ 'journey.contributors.noUsers': 'Nessun utente trovato',
+ 'journey.contributors.role': 'Ruolo',
+ 'journey.contributors.added': 'Contributore aggiunto',
+ 'journey.contributors.addFailed': 'Impossibile aggiungere il contributore',
+ 'journey.share.publicShare': 'Condivisione pubblica',
+ 'journey.share.createLink': 'Crea link di condivisione',
+ 'journey.share.linkCreated': 'Link di condivisione creato',
+ 'journey.share.createFailed': 'Creazione del link fallita',
+ 'journey.share.copy': 'Copia',
+ 'journey.share.copied': 'Copiato!',
+ 'journey.share.timeline': 'Cronologia',
+ 'journey.share.gallery': 'Galleria',
+ 'journey.share.map': 'Mappa',
+ 'journey.share.removeLink': 'Rimuovi link di condivisione',
+ 'journey.share.linkDeleted': 'Link di condivisione eliminato',
+ 'journey.share.deleteFailed': 'Eliminazione fallita',
+ 'journey.share.updateFailed': 'Aggiornamento fallito',
+ 'journey.invite.role': 'Ruolo',
+ 'journey.invite.viewer': 'Visualizzatore',
+ 'journey.invite.editor': 'Editore',
+ 'journey.invite.invite': 'Invita',
+ 'journey.invite.inviting': 'Invito in corso...',
+ 'journey.settings.title': 'Impostazioni del diario',
+ 'journey.settings.coverImage': 'Immagine di copertina',
+ 'journey.settings.changeCover': 'Cambia copertina',
+ 'journey.settings.addCover': 'Aggiungi immagine di copertina',
+ 'journey.settings.name': 'Nome',
+ 'journey.settings.subtitle': 'Sottotitolo',
+ 'journey.settings.subtitlePlaceholder': 'es. Thailandia, Vietnam e Cambogia',
+ 'journey.settings.endJourney': 'Archivia il viaggio',
+ 'journey.settings.reopenJourney': 'Ripristina il viaggio',
+ 'journey.settings.archived': 'Viaggio archiviato',
+ 'journey.settings.reopened': 'Viaggio riaperto',
+ 'journey.settings.endDescription':
+ 'Nasconde il badge In diretta. Puoi riaprire in qualsiasi momento.',
+ 'journey.settings.delete': 'Elimina',
+ 'journey.settings.deleteJourney': 'Elimina diario',
+ 'journey.settings.deleteMessage':
+ 'Eliminare "{title}"? Tutte le voci e le foto andranno perse.',
+ 'journey.settings.saved': 'Impostazioni salvate',
+ 'journey.settings.saveFailed': 'Salvataggio fallito',
+ 'journey.settings.coverUpdated': 'Copertina aggiornata',
+ 'journey.settings.coverFailed': 'Caricamento fallito',
+ 'journey.settings.failedToDelete': 'Eliminazione non riuscita',
+ 'journey.entries.deleteTitle': 'Elimina voce',
+ 'journey.photosUploaded': '{count} foto caricate',
+ 'journey.photosUploadFailed': 'Alcune foto non sono state caricate',
+ 'journey.photosAdded': '{count} foto aggiunte',
+ 'journey.public.notFound': 'Non trovato',
+ 'journey.public.notFoundMessage':
+ 'Questo diario non esiste o il link è scaduto.',
+ 'journey.public.readOnly': 'Sola lettura · Diario pubblico',
+ 'journey.public.tagline': 'Travel Resource & Exploration Kit',
+ 'journey.public.sharedVia': 'Condiviso tramite',
+ 'journey.public.madeWith': 'Creato con',
+ 'journey.pdf.journeyBook': 'Diario di viaggio',
+ 'journey.pdf.madeWith': 'Creato con TREK',
+ 'journey.pdf.day': 'Giorno',
+ 'journey.pdf.theEnd': 'Fine',
+ 'journey.pdf.saveAsPdf': 'Salva come PDF',
+ 'journey.pdf.pages': 'pagine',
+ 'journey.picker.tripPeriod': 'Periodo del viaggio',
+ 'journey.picker.dateRange': 'Intervallo di date',
+ 'journey.picker.allPhotos': 'Tutte le foto',
+ 'journey.picker.albums': 'Album',
+ 'journey.picker.selected': 'selezionati',
+ 'journey.picker.addTo': 'Aggiungi a',
+ 'journey.picker.newGallery': 'Nuova galleria',
+ 'journey.picker.selectAll': 'Seleziona tutto',
+ 'journey.picker.deselectAll': 'Deseleziona tutto',
+ 'journey.picker.noAlbums': 'Nessun album trovato',
+ 'journey.picker.selectDate': 'Seleziona data',
+ 'journey.picker.search': 'Cerca',
+ 'journey.detail.journeyTab': 'Journey', // en-fallback
+ 'journey.contributors.remove': 'Remove contributor', // en-fallback
+ 'journey.contributors.removeConfirm': 'Remove {username} from this journey?', // en-fallback
+ 'journey.contributors.removed': 'Contributor removed', // en-fallback
+ 'journey.contributors.removeFailed': 'Failed to remove contributor', // en-fallback
+};
+export default journey;
diff --git a/shared/src/i18n/it/login.ts b/shared/src/i18n/it/login.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7a50c85a8d0a1fd0bdefb2c0b024ec7d7bab2c69
--- /dev/null
+++ b/shared/src/i18n/it/login.ts
@@ -0,0 +1,99 @@
+import type { TranslationStrings } from '../types';
+
+const login: TranslationStrings = {
+ 'login.error': 'Accesso fallito. Controlla le tue credenziali.',
+ 'login.tagline': 'I tuoi viaggi.\nIl tuo programma.',
+ 'login.description':
+ 'Programma viaggi in collaborazione con mappe interattive, budget e sincronizzazione in tempo reale.',
+ 'login.features.maps': 'Mappe Interattive',
+ 'login.features.mapsDesc': 'Google Places, percorsi e clustering',
+ 'login.features.realtime': 'Sincronizzazione in tempo reale',
+ 'login.features.realtimeDesc': 'Programmate insieme tramite WebSocket',
+ 'login.features.budget': 'Tracciamento Budget',
+ 'login.features.budgetDesc': 'Categorie, grafici e costi per persona',
+ 'login.features.collab': 'Collaborazione',
+ 'login.features.collabDesc': 'Multi-utente con viaggi condivisi',
+ 'login.features.packing': 'Lista Valigia',
+ 'login.features.packingDesc': 'Categorie, progressi e suggerimenti',
+ 'login.features.bookings': 'Prenotazioni',
+ 'login.features.bookingsDesc': 'Voli, alloggi, ristoranti e altro',
+ 'login.features.files': 'Documenti',
+ 'login.features.filesDesc': 'Carica e gestisci i documenti',
+ 'login.features.routes': 'Percorsi Intelligenti',
+ 'login.features.routesDesc':
+ 'Ottimizzazione automatica ed esportazione su Google Maps',
+ 'login.selfHosted': 'Self-hosted · Open Source · Your data stays yours',
+ 'login.title': 'Accedi',
+ 'login.subtitle': 'Bentornato',
+ 'login.signingIn': 'Accesso in corso…',
+ 'login.signIn': 'Accedi',
+ 'login.createAdmin': 'Crea Account Amministratore',
+ 'login.createAdminHint': 'Imposta il primo account amministratore per TREK.',
+ 'login.setNewPassword': 'Imposta nuova password',
+ 'login.setNewPasswordHint': 'Devi cambiare la password prima di continuare.',
+ 'login.createAccount': 'Crea Account',
+ 'login.createAccountHint': 'Registra un nuovo account.',
+ 'login.creating': 'Creazione in corso…',
+ 'login.noAccount': 'Non hai un account?',
+ 'login.hasAccount': 'Hai già un account?',
+ 'login.register': 'Registrati',
+ 'login.emailPlaceholder': 'tua@email.com',
+ 'login.username': 'Nome utente',
+ 'login.oidc.registrationDisabled':
+ 'La registrazione è disabilitata. Contatta il tuo amministratore.',
+ 'login.oidc.noEmail': 'Nessuna email ricevuta dal provider.',
+ 'login.oidc.tokenFailed': 'Autenticazione fallita.',
+ 'login.oidc.invalidState': 'Sessione non valida. Riprova.',
+ 'login.demoFailed': 'Accesso demo fallito',
+ 'login.oidcSignIn': 'Accedi con {name}',
+ 'login.oidcOnly':
+ "L'autenticazione tramite password è disabilitata. Accedi utilizzando il tuo provider SSO.",
+ 'login.oidcLoggedOut':
+ 'Sei stato disconnesso. Accedi nuovamente tramite il tuo provider SSO.',
+ 'login.demoHint': 'Prova la demo — nessuna registrazione necessaria',
+ 'login.mfaTitle': 'Autenticazione a due fattori',
+ 'login.mfaSubtitle':
+ 'Inserisci il codice a 6 cifre dalla tua app authenticator.',
+ 'login.mfaCodeLabel': 'Codice di verifica',
+ 'login.mfaCodeRequired': 'Inserisci il codice dalla tua app authenticator.',
+ 'login.mfaHint': "Apri Google Authenticator, Authy o un'altra app TOTP.",
+ 'login.mfaBack': "← Torna all'accesso",
+ 'login.mfaVerify': 'Verifica',
+ 'login.invalidInviteLink': 'Link di invito non valido o scaduto',
+ 'login.oidcFailed': 'Accesso OIDC non riuscito',
+ 'login.usernameRequired': 'Il nome utente è obbligatorio',
+ 'login.passwordMinLength': 'La password deve contenere almeno 8 caratteri',
+ 'login.forgotPassword': 'Password dimenticata?',
+ 'login.rememberMe': 'Ricordami',
+ 'login.forgotPasswordTitle': 'Reimposta la password',
+ 'login.forgotPasswordBody':
+ 'Inserisci l’indirizzo email del tuo account. Se esiste un account, invieremo un link per reimpostarla.',
+ 'login.forgotPasswordSubmit': 'Invia link',
+ 'login.forgotPasswordSentTitle': 'Controlla la tua email',
+ 'login.forgotPasswordSentBody':
+ 'Se esiste un account con questa email, il link è in arrivo. Scade tra 60 minuti.',
+ 'login.forgotPasswordSmtpHintOff':
+ 'Nota: il tuo amministratore non ha configurato SMTP, quindi il link di reset verrà scritto nella console del server invece di essere inviato via email.',
+ 'login.backToLogin': 'Torna all’accesso',
+ 'login.newPassword': 'Nuova password',
+ 'login.confirmPassword': 'Conferma nuova password',
+ 'login.passwordsDontMatch': 'Le password non corrispondono',
+ 'login.mfaCode': 'Codice 2FA',
+ 'login.resetPasswordTitle': 'Imposta una nuova password',
+ 'login.resetPasswordBody':
+ 'Scegli una password robusta che non hai già usato qui. Minimo 8 caratteri.',
+ 'login.resetPasswordMfaBody':
+ 'Inserisci il codice 2FA o un codice di backup per completare il reset.',
+ 'login.resetPasswordSubmit': 'Reimposta password',
+ 'login.resetPasswordVerify': 'Verifica e reimposta',
+ 'login.resetPasswordSuccessTitle': 'Password aggiornata',
+ 'login.resetPasswordSuccessBody': 'Ora puoi accedere con la nuova password.',
+ 'login.resetPasswordInvalidLink': 'Link di reset non valido',
+ 'login.resetPasswordInvalidLinkBody':
+ 'Il link è mancante o danneggiato. Richiedine uno nuovo per continuare.',
+ 'login.resetPasswordFailed':
+ 'Reset non riuscito. Il link potrebbe essere scaduto.',
+ 'login.passkey.signIn': 'Accedi con una passkey',
+ 'login.passkey.failed': 'Accesso con passkey non riuscito. Riprova.',
+};
+export default login;
diff --git a/shared/src/i18n/it/map.ts b/shared/src/i18n/it/map.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e308c102409a750c129156a43e7a8d652d0bc6db
--- /dev/null
+++ b/shared/src/i18n/it/map.ts
@@ -0,0 +1,17 @@
+import type { TranslationStrings } from '../types';
+
+const map: TranslationStrings = {
+ 'map.connections': 'Connessioni',
+ 'map.showConnections': 'Mostra percorsi prenotati',
+ 'map.hideConnections': 'Nascondi percorsi prenotati',
+ 'poi.searchThisArea': 'Cerca in questa zona',
+ 'poi.cat.restaurants': 'Ristoranti',
+ 'poi.cat.cafes': 'Caffè',
+ 'poi.cat.bars': 'Bar e vita notturna',
+ 'poi.cat.hotels': 'Alloggi',
+ 'poi.cat.sights': 'Attrazioni',
+ 'poi.cat.museums': 'Musei e cultura',
+ 'poi.cat.nature': 'Natura e parchi',
+ 'poi.cat.activities': 'Attività',
+};
+export default map;
diff --git a/shared/src/i18n/it/members.ts b/shared/src/i18n/it/members.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1a05a734943e43b13b938623a86ba967b8dd484b
--- /dev/null
+++ b/shared/src/i18n/it/members.ts
@@ -0,0 +1,24 @@
+import type { TranslationStrings } from '../types';
+
+const members: TranslationStrings = {
+ 'members.shareTrip': 'Condividi viaggio',
+ 'members.inviteUser': 'Invita utente',
+ 'members.selectUser': 'Seleziona utente...',
+ 'members.invite': 'Invita',
+ 'members.allHaveAccess': 'Tutti gli utenti hanno già accesso.',
+ 'members.access': 'Accesso',
+ 'members.person': 'persona',
+ 'members.persons': 'persone',
+ 'members.you': 'tu',
+ 'members.owner': 'Proprietario',
+ 'members.leaveTrip': 'Abbandona viaggio',
+ 'members.removeAccess': 'Rimuovi accesso',
+ 'members.confirmLeave': "Abbandonare il viaggio? Perderai l'accesso.",
+ 'members.confirmRemove': "Rimuovere l'accesso per questo utente?",
+ 'members.loadError': 'Impossibile caricare i membri',
+ 'members.added': 'aggiunto',
+ 'members.addError': 'Impossibile aggiungere',
+ 'members.removed': 'Membro rimosso',
+ 'members.removeError': 'Impossibile rimuovere',
+};
+export default members;
diff --git a/shared/src/i18n/it/memories.ts b/shared/src/i18n/it/memories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ea92bd303007287ce3c85fec1b7777620cdc89d5
--- /dev/null
+++ b/shared/src/i18n/it/memories.ts
@@ -0,0 +1,82 @@
+import type { TranslationStrings } from '../types';
+
+const memories: TranslationStrings = {
+ 'memories.title': 'Foto',
+ 'memories.notConnected': 'Immich non connesso',
+ 'memories.notConnectedHint':
+ 'Connetti la tua istanza Immich nelle Impostazioni per vedere qui le foto del tuo viaggio.',
+ 'memories.notConnectedMultipleHint':
+ 'Collega uno di questi provider di foto: {provider_names} nelle Impostazioni per poter aggiungere foto a questo viaggio.',
+ 'memories.noDates': 'Aggiungi le date al tuo viaggio per caricare le foto.',
+ 'memories.noPhotos': 'Nessuna foto trovata',
+ 'memories.noPhotosHint':
+ "Nessuna foto trovata in Immich per l'intervallo di date di questo viaggio.",
+ 'memories.photosFound': 'foto',
+ 'memories.fromOthers': 'da altri',
+ 'memories.sharePhotos': 'Condividi foto',
+ 'memories.sharing': 'Condivisione',
+ 'memories.reviewTitle': 'Rivedi le tue foto',
+ 'memories.reviewHint': 'Clicca sulle foto per escluderle dalla condivisione.',
+ 'memories.shareCount': 'Condividi {count} foto',
+ 'memories.providerUrl': 'URL del server',
+ 'memories.providerApiKey': 'Chiave API',
+ 'memories.providerUsername': 'Nome utente',
+ 'memories.providerPassword': 'Password',
+ 'memories.providerOTP': 'Codice MFA (se abilitato)',
+ 'memories.skipSSLVerification': 'Ignora la verifica del certificato SSL',
+ 'memories.immichAutoUpload':
+ 'Rispecchia le foto del journey su Immich al caricamento',
+ 'memories.providerUrlHintSynology':
+ "Includi il percorso dell'app Foto nell'URL, es. https://nas:5001/photo",
+ 'memories.testConnection': 'Test connessione',
+ 'memories.testShort': 'Prova',
+ 'memories.testFirst': 'Testa prima la connessione',
+ 'memories.connected': 'Connesso',
+ 'memories.disconnected': 'Non connesso',
+ 'memories.connectionSuccess': 'Connesso a Immich',
+ 'memories.connectionError': 'Impossibile connettersi a Immich',
+ 'memories.saved': 'Impostazioni {provider_name} salvate',
+ 'memories.providerDisconnectedBanner':
+ 'La connessione a {provider_name} è persa. Riconnetti nelle Impostazioni per visualizzare le foto.',
+ 'memories.saveError':
+ 'Impossibile salvare le impostazioni di {provider_name}',
+ 'memories.saveRouteNotConfigured':
+ 'La route di salvataggio non è configurata per questo provider',
+ 'memories.testRouteNotConfigured':
+ 'La route di test non è configurata per questo provider',
+ 'memories.fillRequiredFields': 'Per favore compila tutti i campi obbligatori',
+ 'memories.addPhotos': 'Aggiungi foto',
+ 'memories.linkAlbum': 'Collega album',
+ 'memories.selectAlbum': 'Seleziona album Immich',
+ 'memories.selectAlbumMultiple': 'Seleziona album',
+ 'memories.noAlbums': 'Nessun album trovato',
+ 'memories.syncAlbum': 'Sincronizza album',
+ 'memories.unlinkAlbum': 'Scollega',
+ 'memories.photos': 'foto',
+ 'memories.selectPhotos': 'Seleziona foto da Immich',
+ 'memories.selectPhotosMultiple': 'Seleziona foto',
+ 'memories.selectHint': 'Tocca le foto per selezionarle.',
+ 'memories.selected': 'selezionate',
+ 'memories.addSelected': 'Aggiungi {count} foto',
+ 'memories.alreadyAdded': 'Aggiunta',
+ 'memories.private': 'Privato',
+ 'memories.stopSharing': 'Interrompi condivisione',
+ 'memories.oldest': 'Prima le più vecchie',
+ 'memories.newest': 'Prima le più recenti',
+ 'memories.allLocations': 'Tutte le posizioni',
+ 'memories.tripDates': 'Date del viaggio',
+ 'memories.allPhotos': 'Tutte le foto',
+ 'memories.confirmShareTitle': 'Condividere con i membri del viaggio?',
+ 'memories.confirmShareHint':
+ '{count} foto saranno visibili a tutti i membri di questo viaggio. Potrai rendere private le singole foto in seguito.',
+ 'memories.confirmShareButton': 'Condividi foto',
+ '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',
+};
+export default memories;
diff --git a/shared/src/i18n/it/nav.ts b/shared/src/i18n/it/nav.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0d4b09c02397eba5d49700f03afc1bf9046fac3e
--- /dev/null
+++ b/shared/src/i18n/it/nav.ts
@@ -0,0 +1,20 @@
+import type { TranslationStrings } from '../types';
+
+const nav: TranslationStrings = {
+ 'nav.trip': 'Viaggio',
+ 'nav.share': 'Condividi',
+ 'nav.settings': 'Impostazioni',
+ 'nav.admin': 'Amministrazione',
+ 'nav.logout': 'Esci',
+ 'nav.lightMode': 'Modalità chiara',
+ 'nav.darkMode': 'Modalità scura',
+ 'nav.autoMode': 'Modalità automatica',
+ 'nav.administrator': 'Amministratore',
+ 'nav.myTrips': 'I miei viaggi',
+ 'nav.profile': 'Profilo',
+ 'nav.bottomSettings': 'Impostazioni',
+ 'nav.bottomAdmin': 'Amministrazione',
+ 'nav.bottomLogout': 'Disconnetti',
+ 'nav.bottomAdminBadge': 'Admin',
+};
+export default nav;
diff --git a/shared/src/i18n/it/notif.ts b/shared/src/i18n/it/notif.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5f4cdeb53c47597264079ad653a570e49fe69a12
--- /dev/null
+++ b/shared/src/i18n/it/notif.ts
@@ -0,0 +1,42 @@
+import type { TranslationStrings } from '../types';
+
+const notif: TranslationStrings = {
+ 'notif.test.title': '[Test] Notifica',
+ 'notif.test.simple.text': 'Questa è una semplice notifica di test.',
+ 'notif.test.boolean.text': 'Accetti questa notifica di test?',
+ 'notif.test.navigate.text': 'Clicca qui sotto per accedere alla dashboard.',
+ 'notif.trip_invite.title': 'Invito al viaggio',
+ 'notif.trip_invite.text': '{actor} ti ha invitato a {trip}',
+ 'notif.booking_change.title': 'Prenotazione aggiornata',
+ 'notif.booking_change.text':
+ '{actor} ha aggiornato una prenotazione in {trip}',
+ 'notif.trip_reminder.title': 'Promemoria viaggio',
+ 'notif.trip_reminder.text': 'Il tuo viaggio {trip} si avvicina!',
+ 'notif.todo_due.title': 'Attività in scadenza',
+ 'notif.todo_due.text': '{todo} in {trip} scade il {due}',
+ 'notif.vacay_invite.title': 'Invito Vacay Fusion',
+ 'notif.vacay_invite.text': '{actor} ti ha invitato a fondere i piani vacanza',
+ 'notif.photos_shared.title': 'Foto condivise',
+ 'notif.photos_shared.text': '{actor} ha condiviso {count} foto in {trip}',
+ 'notif.collab_message.title': 'Nuovo messaggio',
+ 'notif.collab_message.text': '{actor} ha inviato un messaggio in {trip}',
+ 'notif.packing_tagged.title': 'Assegnazione bagagli',
+ 'notif.packing_tagged.text': '{actor} ti ha assegnato a {category} in {trip}',
+ 'notif.version_available.title': 'Nuova versione disponibile',
+ 'notif.version_available.text': 'TREK {version} è ora disponibile',
+ 'notif.action.view_trip': 'Vedi viaggio',
+ 'notif.action.view_collab': 'Vedi messaggi',
+ 'notif.action.view_packing': 'Vedi bagagli',
+ 'notif.action.view_photos': 'Vedi foto',
+ 'notif.action.view_vacay': 'Vedi Vacay',
+ 'notif.action.view_admin': "Vai all'admin",
+ 'notif.action.view': 'Vedi',
+ 'notif.action.accept': 'Accetta',
+ 'notif.action.decline': 'Rifiuta',
+ 'notif.generic.title': 'Notifica',
+ 'notif.generic.text': 'Hai una nuova notifica',
+ 'notif.dev.unknown_event.title': '[DEV] Evento sconosciuto',
+ 'notif.dev.unknown_event.text':
+ 'Il tipo di evento "{event}" non è registrato in EVENT_NOTIFICATION_CONFIG',
+};
+export default notif;
diff --git a/shared/src/i18n/it/notifications.ts b/shared/src/i18n/it/notifications.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d5ebace8d640462b10428572c86c272f715c6d7f
--- /dev/null
+++ b/shared/src/i18n/it/notifications.ts
@@ -0,0 +1,37 @@
+import type { TranslationStrings } from '../types';
+
+const notifications: TranslationStrings = {
+ 'notifications.title': 'Notifiche',
+ 'notifications.markAllRead': 'Segna tutto come letto',
+ 'notifications.deleteAll': 'Elimina tutto',
+ 'notifications.showAll': 'Vedi tutte le notifiche',
+ 'notifications.empty': 'Nessuna notifica',
+ 'notifications.emptyDescription': 'Sei aggiornato!',
+ 'notifications.all': 'Tutte',
+ 'notifications.unreadOnly': 'Non lette',
+ 'notifications.markRead': 'Segna come letto',
+ 'notifications.markUnread': 'Segna come non letto',
+ 'notifications.delete': 'Elimina',
+ 'notifications.system': 'Sistema',
+ 'notifications.synologySessionCleared.title': 'Synology Photos disconnesso',
+ 'notifications.synologySessionCleared.text':
+ "Il server o l'account è cambiato — vai alle Impostazioni per testare nuovamente la connessione.",
+ '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}".',
+ 'notifications.versionAvailable.title': 'Aggiornamento disponibile',
+ 'notifications.versionAvailable.text': 'TREK {version} è ora disponibile.',
+ 'notifications.versionAvailable.button': 'Visualizza dettagli',
+};
+export default notifications;
diff --git a/shared/src/i18n/it/oauth.ts b/shared/src/i18n/it/oauth.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c6736a46f8c83d7ecc898735d271c8ace4be9cd3
--- /dev/null
+++ b/shared/src/i18n/it/oauth.ts
@@ -0,0 +1,123 @@
+import type { TranslationStrings } from '../types';
+
+const oauth: TranslationStrings = {
+ 'oauth.scope.group.trips': 'Viaggi',
+ 'oauth.scope.group.places': 'Luoghi',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'Bagagli',
+ 'oauth.scope.group.todos': 'Attività',
+ 'oauth.scope.group.budget': 'Budget',
+ 'oauth.scope.group.reservations': 'Prenotazioni',
+ 'oauth.scope.group.collab': 'Collaborazione',
+ 'oauth.scope.group.notifications': 'Notifiche',
+ 'oauth.scope.group.vacay': 'Ferie',
+ 'oauth.scope.group.geo': 'Geo',
+ 'oauth.scope.group.weather': 'Meteo',
+ 'oauth.scope.group.journey': 'Diario di viaggio',
+ 'oauth.scope.trips:read.label': 'Visualizza viaggi e itinerari',
+ 'oauth.scope.trips:read.description':
+ 'Leggi viaggi, giorni, note giornaliere e membri',
+ 'oauth.scope.trips:write.label': 'Modifica viaggi e itinerari',
+ 'oauth.scope.trips:write.description':
+ 'Crea e aggiorna viaggi, giorni, note e gestisci membri',
+ 'oauth.scope.trips:delete.label': 'Elimina viaggi',
+ 'oauth.scope.trips:delete.description':
+ 'Elimina definitivamente interi viaggi — questa azione è irreversibile',
+ 'oauth.scope.trips:share.label': 'Gestisci link di condivisione',
+ 'oauth.scope.trips:share.description':
+ 'Crea, aggiorna e revoca link di condivisione pubblici per i viaggi',
+ 'oauth.scope.places:read.label': 'Visualizza luoghi e dati mappa',
+ 'oauth.scope.places:read.description':
+ 'Leggi luoghi, assegnazioni giornaliere, tag e categorie',
+ 'oauth.scope.places:write.label': 'Gestisci luoghi',
+ 'oauth.scope.places:write.description':
+ 'Crea, aggiorna ed elimina luoghi, assegnazioni e tag',
+ 'oauth.scope.atlas:read.label': 'Visualizza Atlas',
+ 'oauth.scope.atlas:read.description':
+ 'Leggi paesi visitati, regioni e lista dei desideri',
+ 'oauth.scope.atlas:write.label': 'Gestisci Atlas',
+ 'oauth.scope.atlas:write.description':
+ 'Segna paesi e regioni come visitati, gestisci la lista dei desideri',
+ 'oauth.scope.packing:read.label': 'Visualizza liste bagagli',
+ 'oauth.scope.packing:read.description':
+ 'Leggi articoli, borse e assegnatari di categoria',
+ 'oauth.scope.packing:write.label': 'Gestisci liste bagagli',
+ 'oauth.scope.packing:write.description':
+ 'Aggiungi, aggiorna, elimina, spunta e riordina articoli e borse',
+ 'oauth.scope.todos:read.label': 'Visualizza liste attività',
+ 'oauth.scope.todos:read.description':
+ 'Leggi attività del viaggio e assegnatari di categoria',
+ 'oauth.scope.todos:write.label': 'Gestisci liste attività',
+ 'oauth.scope.todos:write.description':
+ 'Crea, aggiorna, spunta, elimina e riordina attività',
+ 'oauth.scope.budget:read.label': 'Visualizza budget',
+ 'oauth.scope.budget:read.description':
+ 'Leggi voci di budget e ripartizione delle spese',
+ 'oauth.scope.budget:write.label': 'Gestisci budget',
+ 'oauth.scope.budget:write.description':
+ 'Crea, aggiorna ed elimina voci di budget',
+ 'oauth.scope.reservations:read.label': 'Visualizza prenotazioni',
+ 'oauth.scope.reservations:read.description':
+ 'Leggi prenotazioni e dettagli alloggio',
+ 'oauth.scope.reservations:write.label': 'Gestisci prenotazioni',
+ 'oauth.scope.reservations:write.description':
+ 'Crea, aggiorna, elimina e riordina prenotazioni',
+ 'oauth.scope.collab:read.label': 'Visualizza collaborazione',
+ 'oauth.scope.collab:read.description':
+ 'Leggi note collaborative, sondaggi e messaggi',
+ 'oauth.scope.collab:write.label': 'Gestisci collaborazione',
+ 'oauth.scope.collab:write.description':
+ 'Crea, aggiorna ed elimina note collaborative, sondaggi e messaggi',
+ 'oauth.scope.notifications:read.label': 'Visualizza notifiche',
+ 'oauth.scope.notifications:read.description':
+ 'Leggi notifiche in-app e conteggi non letti',
+ 'oauth.scope.notifications:write.label': 'Gestisci notifiche',
+ 'oauth.scope.notifications:write.description':
+ 'Segna notifiche come lette e rispondi',
+ 'oauth.scope.vacay:read.label': 'Visualizza piani ferie',
+ 'oauth.scope.vacay:read.description':
+ 'Leggi dati di pianificazione ferie, voci e statistiche',
+ 'oauth.scope.vacay:write.label': 'Gestisci piani ferie',
+ 'oauth.scope.vacay:write.description':
+ 'Crea e gestisci voci ferie, festività e piani del team',
+ 'oauth.scope.geo:read.label': 'Mappe e geocodifica',
+ 'oauth.scope.geo:read.description':
+ 'Cerca luoghi, risolvi URL mappa e geocodifica inversa coordinate',
+ 'oauth.scope.weather:read.label': 'Previsioni meteo',
+ 'oauth.scope.weather:read.description':
+ 'Ottieni previsioni meteo per luoghi e date del viaggio',
+ 'oauth.scope.journey:read.label': 'Visualizza diari di viaggio',
+ 'oauth.scope.journey:read.description':
+ 'Leggi diari di viaggio, voci e lista dei collaboratori',
+ 'oauth.scope.journey:write.label': 'Gestisci diari di viaggio',
+ 'oauth.scope.journey:write.description':
+ 'Crea, aggiorna ed elimina diari di viaggio e le loro voci',
+ 'oauth.scope.journey:share.label': 'Gestisci link diari di viaggio',
+ 'oauth.scope.journey:share.description':
+ 'Crea, aggiorna e revoca link di condivisione pubblici per i diari di viaggio',
+ 'oauth.authorize.authorizing': 'Authorizing…', // en-fallback
+ 'oauth.authorize.loading': 'Loading…', // en-fallback
+ 'oauth.authorize.errorTitle': 'Authorization Error', // en-fallback
+ 'oauth.authorize.loginTitle': 'Sign in to continue', // en-fallback
+ 'oauth.authorize.loginDescription':
+ '{client} wants access to your TREK account. Please sign in first.', // en-fallback
+ 'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
+ 'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
+ 'oauth.authorize.requestDescription':
+ 'This application is requesting access to your TREK account.', // en-fallback
+ 'oauth.authorize.trustNote':
+ 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
+ 'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
+ 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
+ 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
+ 'oauth.authorize.deny': 'Deny', // en-fallback
+ 'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
+ 'oauth.authorize.permissionsRequested': 'Permissions requested', // en-fallback
+ 'oauth.authorize.alwaysIncluded': 'Always included', // en-fallback
+ 'oauth.authorize.alwaysTool.listTrips':
+ 'List your trips so the AI can discover trip IDs', // en-fallback
+ 'oauth.authorize.alwaysTool.getTripSummary':
+ 'Read a trip overview needed to use any other tool', // en-fallback
+};
+export default oauth;
diff --git a/shared/src/i18n/it/packing.ts b/shared/src/i18n/it/packing.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b1111efdd64deb04a273ee93c523ddce0bb25886
--- /dev/null
+++ b/shared/src/i18n/it/packing.ts
@@ -0,0 +1,186 @@
+import type { TranslationStrings } from '../types';
+
+const packing: TranslationStrings = {
+ 'packing.title': 'Lista valigia',
+ 'packing.empty': 'La lista valigia è vuota',
+ 'packing.import': 'Importa',
+ 'packing.importTitle': 'Importa lista valigia',
+ 'packing.importHint':
+ 'Un elemento per riga. Formato: Categoria, Nome, Peso in g (opzionale), Borsa (opzionale), checked/unchecked (opzionale)',
+ 'packing.importPlaceholder':
+ 'Igiene, Spazzolino\nAbbigliamento, Magliette, 200\nDocumenti, Passaporto, , Bagaglio a mano\nElettronica, Caricabatterie, 50, Valigia, checked',
+ 'packing.importCsv': 'Carica CSV/TXT',
+ 'packing.importAction': 'Importa {count}',
+ 'packing.importSuccess': '{count} elementi importati',
+ 'packing.importError': 'Importazione non riuscita',
+ 'packing.importEmpty': 'Nessun elemento da importare',
+ 'packing.progress': '{packed} di {total} in valigia ({percent}%)',
+ 'packing.clearChecked': 'Rimuovi {count} spuntati',
+ 'packing.clearCheckedShort': 'Rimuovi {count}',
+ 'packing.suggestions': 'Suggerimenti',
+ 'packing.suggestionsTitle': 'Aggiungi suggerimenti',
+ 'packing.allSuggested': 'Tutti i suggerimenti aggiunti',
+ 'packing.allPacked': 'Tutto in valigia!',
+ 'packing.addPlaceholder': 'Aggiungi nuovo elemento...',
+ 'packing.categoryPlaceholder': 'Categoria...',
+ 'packing.filterAll': 'Tutti',
+ 'packing.filterOpen': 'Da fare',
+ 'packing.filterDone': 'Fatto',
+ 'packing.emptyTitle': 'La lista valigia è vuota',
+ 'packing.emptyHint': 'Aggiungi elementi o usa i suggerimenti',
+ 'packing.emptyFiltered': 'Nessun elemento corrisponde a questo filtro',
+ 'packing.menuRename': 'Rinomina',
+ 'packing.menuCheckAll': 'Seleziona tutti',
+ 'packing.menuUncheckAll': 'Deseleziona tutti',
+ 'packing.menuDeleteCat': 'Elimina categoria',
+ 'packing.noMembers': 'Nessun membro del viaggio',
+ 'packing.addItem': 'Aggiungi elemento',
+ 'packing.addItemPlaceholder': 'Nome elemento...',
+ 'packing.addCategory': 'Aggiungi categoria',
+ 'packing.newCategoryPlaceholder': 'Nome categoria (es. Abbigliamento)',
+ 'packing.applyTemplate': 'Applica modello',
+ 'packing.template': 'Modello',
+ 'packing.templateApplied': '{count} elementi aggiunti dal modello',
+ 'packing.templateError': 'Impossibile applicare il modello',
+ 'packing.saveAsTemplate': 'Salva come modello',
+ 'packing.templateName': 'Nome modello',
+ 'packing.templateSaved': 'Lista bagagli salvata come modello',
+ 'packing.bags': 'Valigie',
+ 'packing.noBag': 'Non assegnato',
+ 'packing.totalWeight': 'Peso totale',
+ 'packing.bagName': 'Nome valigia...',
+ 'packing.addBag': 'Aggiungi valigia',
+ 'packing.changeCategory': 'Cambia categoria',
+ 'packing.confirm.clearChecked':
+ 'Sei sicuro di voler rimuovere {count} elementi spuntati?',
+ 'packing.confirm.deleteCat':
+ 'Sei sicuro di voler eliminare la categoria "{name}" con {count} elementi?',
+ 'packing.defaultCategory': 'Altro',
+ 'packing.toast.saveError': 'Impossibile salvare',
+ 'packing.toast.deleteError': 'Impossibile eliminare',
+ 'packing.toast.renameError': 'Impossibile rinominare',
+ 'packing.toast.addError': 'Impossibile aggiungere',
+ 'packing.suggestions.items': [
+ {
+ name: 'Passaporto',
+ category: 'Documenti',
+ },
+ {
+ name: "Carta d'identità",
+ category: 'Documenti',
+ },
+ {
+ name: 'Assicurazione di viaggio',
+ category: 'Documenti',
+ },
+ {
+ name: 'Biglietti aerei',
+ category: 'Documenti',
+ },
+ {
+ name: 'Carta di credito',
+ category: 'Finanze',
+ },
+ {
+ name: 'Contanti',
+ category: 'Finanze',
+ },
+ {
+ name: 'Visto',
+ category: 'Documenti',
+ },
+ {
+ name: 'Magliette',
+ category: 'Abbigliamento',
+ },
+ {
+ name: 'Pantaloni',
+ category: 'Abbigliamento',
+ },
+ {
+ name: 'Intimo',
+ category: 'Abbigliamento',
+ },
+ {
+ name: 'Calzini',
+ category: 'Abbigliamento',
+ },
+ {
+ name: 'Giacca',
+ category: 'Abbigliamento',
+ },
+ {
+ name: 'Pigiama',
+ category: 'Abbigliamento',
+ },
+ {
+ name: 'Costume da bagno',
+ category: 'Abbigliamento',
+ },
+ {
+ name: 'Giacca a vento',
+ category: 'Abbigliamento',
+ },
+ {
+ name: 'Scarpe comode',
+ category: 'Abbigliamento',
+ },
+ {
+ name: 'Spazzolino da denti',
+ category: 'Igiene personale',
+ },
+ {
+ name: 'Dentifricio',
+ category: 'Igiene personale',
+ },
+ {
+ name: 'Shampoo',
+ category: 'Igiene personale',
+ },
+ {
+ name: 'Deodorante',
+ category: 'Igiene personale',
+ },
+ {
+ name: 'Crema solare',
+ category: 'Igiene personale',
+ },
+ {
+ name: 'Rasoio',
+ category: 'Igiene personale',
+ },
+ {
+ name: 'Caricabatterie',
+ category: 'Elettronica',
+ },
+ {
+ name: 'Power bank',
+ category: 'Elettronica',
+ },
+ {
+ name: 'Cuffie',
+ category: 'Elettronica',
+ },
+ {
+ name: 'Adattatore da viaggio',
+ category: 'Elettronica',
+ },
+ {
+ name: 'Macchina fotografica',
+ category: 'Elettronica',
+ },
+ {
+ name: 'Antidolorifici',
+ category: 'Salute',
+ },
+ {
+ name: 'Cerotti',
+ category: 'Salute',
+ },
+ {
+ name: 'Disinfettante',
+ category: 'Salute',
+ },
+ ],
+};
+export default packing;
diff --git a/shared/src/i18n/it/pdf.ts b/shared/src/i18n/it/pdf.ts
new file mode 100644
index 0000000000000000000000000000000000000000..57c07fd70cc9042b1b39df31f80678a492058328
--- /dev/null
+++ b/shared/src/i18n/it/pdf.ts
@@ -0,0 +1,10 @@
+import type { TranslationStrings } from '../types';
+
+const pdf: TranslationStrings = {
+ 'pdf.travelPlan': 'Programma di viaggio',
+ 'pdf.planned': 'Programmato',
+ 'pdf.costLabel': 'Costo EUR',
+ 'pdf.preview': 'Anteprima PDF',
+ 'pdf.saveAsPdf': 'Salva come PDF',
+};
+export default pdf;
diff --git a/shared/src/i18n/it/perm.ts b/shared/src/i18n/it/perm.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e7232dbba212a499aaa4c9d50ae00ef8ce438ac9
--- /dev/null
+++ b/shared/src/i18n/it/perm.ts
@@ -0,0 +1,63 @@
+import type { TranslationStrings } from '../types';
+
+const perm: TranslationStrings = {
+ 'perm.title': 'Impostazioni dei permessi',
+ 'perm.subtitle': "Controlla chi può eseguire azioni nell'applicazione",
+ 'perm.saved': 'Impostazioni dei permessi salvate',
+ 'perm.resetDefaults': 'Ripristina predefiniti',
+ 'perm.customized': 'personalizzato',
+ 'perm.level.admin': 'Solo amministratore',
+ 'perm.level.tripOwner': 'Proprietario del viaggio',
+ 'perm.level.tripMember': 'Membri del viaggio',
+ 'perm.level.everybody': 'Tutti',
+ 'perm.cat.trip': 'Gestione viaggi',
+ 'perm.cat.members': 'Gestione membri',
+ 'perm.cat.files': 'File',
+ 'perm.cat.content': 'Contenuti e programma',
+ 'perm.cat.extras': 'Budget, bagagli e collaborazione',
+ 'perm.action.trip_create': 'Creare viaggi',
+ 'perm.action.trip_edit': 'Modificare dettagli del viaggio',
+ 'perm.action.trip_delete': 'Eliminare viaggi',
+ 'perm.action.trip_archive': 'Archiviare / dearchiviare viaggi',
+ 'perm.action.trip_cover_upload': 'Caricare immagine di copertina',
+ 'perm.action.member_manage': 'Aggiungere / rimuovere membri',
+ 'perm.action.file_upload': 'Caricare file',
+ 'perm.action.file_edit': 'Modificare metadati dei file',
+ 'perm.action.file_delete': 'Eliminare file',
+ 'perm.action.place_edit': 'Aggiungere / modificare / eliminare luoghi',
+ 'perm.action.day_edit': 'Modificare giorni, note e assegnazioni',
+ 'perm.action.reservation_edit': 'Gestire prenotazioni',
+ 'perm.action.budget_edit': 'Gestire budget',
+ 'perm.action.packing_edit': 'Gestire liste bagagli',
+ 'perm.action.collab_edit': 'Collaborazione (note, sondaggi, chat)',
+ 'perm.action.share_manage': 'Gestire link di condivisione',
+ 'perm.actionHint.trip_create': 'Chi può creare nuovi viaggi',
+ 'perm.actionHint.trip_edit':
+ 'Chi può modificare nome, date, descrizione e valuta del viaggio',
+ 'perm.actionHint.trip_delete': 'Chi può eliminare definitivamente un viaggio',
+ 'perm.actionHint.trip_archive':
+ 'Chi può archiviare o dearchiviare un viaggio',
+ 'perm.actionHint.trip_cover_upload':
+ "Chi può caricare o modificare l'immagine di copertina",
+ 'perm.actionHint.member_manage':
+ 'Chi può invitare o rimuovere membri del viaggio',
+ 'perm.actionHint.file_upload': 'Chi può caricare file in un viaggio',
+ 'perm.actionHint.file_edit': 'Chi può modificare descrizioni e link dei file',
+ 'perm.actionHint.file_delete':
+ 'Chi può spostare file nel cestino o eliminarli definitivamente',
+ 'perm.actionHint.place_edit':
+ 'Chi può aggiungere, modificare o eliminare luoghi',
+ 'perm.actionHint.day_edit':
+ 'Chi può modificare giorni, note dei giorni e assegnazioni dei luoghi',
+ 'perm.actionHint.reservation_edit':
+ 'Chi può creare, modificare o eliminare prenotazioni',
+ 'perm.actionHint.budget_edit':
+ 'Chi può creare, modificare o eliminare voci di budget',
+ 'perm.actionHint.packing_edit':
+ 'Chi può gestire articoli da bagaglio e borse',
+ 'perm.actionHint.collab_edit':
+ 'Chi può creare note, sondaggi e inviare messaggi',
+ 'perm.actionHint.share_manage':
+ 'Chi può creare o eliminare link di condivisione pubblici',
+};
+export default perm;
diff --git a/shared/src/i18n/it/photos.ts b/shared/src/i18n/it/photos.ts
new file mode 100644
index 0000000000000000000000000000000000000000..55ed2ac246a33b21f71859785b231705bbeed410
--- /dev/null
+++ b/shared/src/i18n/it/photos.ts
@@ -0,0 +1,25 @@
+import type { TranslationStrings } from '../types';
+
+const photos: TranslationStrings = {
+ 'photos.title': 'Foto',
+ 'photos.subtitle': '{count} foto per {trip}',
+ 'photos.dropHere': 'Trascina le foto qui...',
+ 'photos.dropHereActive': 'Trascina le foto qui',
+ 'photos.captionForAll': 'Didascalia (per tutti)',
+ 'photos.captionPlaceholder': 'Didascalia opzionale...',
+ 'photos.addCaption': 'Aggiungi didascalia...',
+ 'photos.allDays': 'Tutti i giorni',
+ 'photos.noPhotos': 'Ancora nessuna foto',
+ 'photos.uploadHint': 'Carica le foto del tuo viaggio',
+ 'photos.clickToSelect': 'o clicca per selezionare',
+ 'photos.linkPlace': 'Collega luogo',
+ 'photos.noPlace': 'Nessun luogo',
+ 'photos.uploadN': 'Caricamento di {n} foto',
+ 'photos.linkDay': 'Collega giorno',
+ 'photos.noDay': 'Nessun giorno',
+ 'photos.dayLabel': 'Giorno {number}',
+ 'photos.photoSelected': 'Foto selezionata',
+ 'photos.photosSelected': 'Foto selezionate',
+ 'photos.fileTypeHint': 'JPG, PNG, WebP · max. 10 MB · fino a 30 foto',
+};
+export default photos;
diff --git a/shared/src/i18n/it/places.ts b/shared/src/i18n/it/places.ts
new file mode 100644
index 0000000000000000000000000000000000000000..34980397ee040e4fb6b31bb06a68f731c4d1b023
--- /dev/null
+++ b/shared/src/i18n/it/places.ts
@@ -0,0 +1,97 @@
+import type { TranslationStrings } from '../types';
+
+const places: TranslationStrings = {
+ 'places.addPlace': 'Aggiungi Luogo/Attività',
+ 'places.importFile': 'Importa file',
+ 'places.sidebarDrop': 'Rilascia per importare',
+ 'places.importFileHint':
+ 'Importa file .gpx, .kml o .kmz da strumenti come Google My Maps, Google Earth o un tracker GPS.',
+ 'places.importFileDropHere':
+ 'Clicca per selezionare un file o trascina e rilascia qui',
+ 'places.importFileDropActive': 'Rilascia il file per selezionarlo',
+ 'places.importFileUnsupported':
+ 'Tipo di file non supportato. Usa .gpx, .kml o .kmz.',
+ 'places.importFileTooLarge':
+ 'Il file è troppo grande. La dimensione massima di caricamento è {maxMb} MB.',
+ 'places.importFileError': 'Importazione non riuscita',
+ 'places.importAllSkipped': 'Tutti i luoghi erano già nel viaggio.',
+ 'places.gpxImported': '{count} luoghi importati da GPX',
+ 'places.gpxImportTypes': 'Cosa vuoi importare?',
+ 'places.gpxImportWaypoints': 'Waypoint',
+ 'places.gpxImportRoutes': 'Percorsi',
+ 'places.gpxImportTracks': 'Tracce (con geometria percorso)',
+ 'places.gpxImportNoneSelected': 'Seleziona almeno un tipo da importare.',
+ 'places.kmlImportTypes': 'Cosa vuoi importare?',
+ 'places.kmlImportPoints': 'Punti (Placemarks)',
+ 'places.kmlImportPaths': 'Percorsi (LineStrings)',
+ 'places.kmlImportNoneSelected': 'Seleziona almeno un tipo.',
+ 'places.selectionCount': '{count} selezionato/i',
+ 'places.deleteSelected': 'Elimina selezionati',
+ 'places.kmlKmzImported': '{count} luoghi importati da KMZ/KML',
+ 'places.urlResolved': "Luogo importato dall'URL",
+ 'places.importList': 'Importa lista',
+ 'places.kmlKmzSummaryValues':
+ 'Placemarks: {total} • Importati: {created} • Saltati: {skipped}',
+ 'places.importGoogleList': 'Lista Google',
+ 'places.importNaverList': 'Lista Naver',
+ 'places.googleListHint':
+ 'Incolla un link condiviso di una lista Google Maps per importare tutti i luoghi.',
+ 'places.googleListImported': '{count} luoghi importati da "{list}"',
+ 'places.googleListError': 'Importazione lista Google Maps non riuscita',
+ 'places.naverListHint':
+ 'Incolla un link condiviso di una lista Naver Maps per importare tutti i luoghi.',
+ 'places.naverListImported': '{count} luoghi importati da "{list}"',
+ 'places.naverListError': 'Importazione lista Naver Maps non riuscita',
+ 'places.viewDetails': 'Visualizza dettagli',
+ 'places.assignToDay': 'A quale giorno aggiungere?',
+ 'places.all': 'Tutti',
+ 'places.unplanned': 'Non pianificati',
+ 'places.filterTracks': 'Tracce',
+ 'places.search': 'Cerca luoghi...',
+ 'places.allCategories': 'Tutte le categorie',
+ 'places.categoriesSelected': 'categorie',
+ 'places.clearFilter': 'Cancella filtro',
+ 'places.count': '{count} luoghi',
+ 'places.countSingular': '1 luogo',
+ 'places.allPlanned': 'Tutti i luoghi sono programmati',
+ 'places.noneFound': 'Nessun luogo trovato',
+ 'places.editPlace': 'Modifica luogo',
+ 'places.formName': 'Nome',
+ 'places.formNamePlaceholder': 'es. Torre Eiffel',
+ 'places.formDescription': 'Descrizione',
+ 'places.formDescriptionPlaceholder': 'Breve descrizione...',
+ 'places.formAddress': 'Indirizzo',
+ 'places.formAddressPlaceholder': 'Via, Città, Paese',
+ 'places.formLat': 'Latitudine (es. 48.8566)',
+ 'places.formLng': 'Longitudine (es. 2.3522)',
+ 'places.formCategory': 'Categoria',
+ 'places.noCategory': 'Nessuna categoria',
+ 'places.categoryNamePlaceholder': 'Nome categoria',
+ 'places.formTime': 'Ora',
+ 'places.startTime': 'Inizio',
+ 'places.endTime': 'Fine',
+ 'places.endTimeBeforeStart': "L'ora di fine è precedente all'ora di inizio",
+ 'places.timeCollision': 'Sovrapposizione di orario con:',
+ 'places.formWebsite': 'Sito web',
+ 'places.formNotes': 'Note',
+ 'places.formNotesPlaceholder': 'Note personali...',
+ 'places.formReservation': 'Prenotazione',
+ 'places.reservationNotesPlaceholder':
+ 'Note della prenotazione, numero di conferma...',
+ 'places.mapsSearchPlaceholder': 'Cerca luoghi...',
+ 'places.mapsSearchError': 'Impossibile cercare i luoghi.',
+ 'places.loadingDetails': 'Caricamento dettagli del luogo…',
+ 'places.osmHint':
+ 'Uso della ricerca OpenStreetMap (senza foto, orari di apertura o valutazioni). Aggiungi una chiave API Google nelle impostazioni per i dettagli completi.',
+ 'places.osmActive':
+ 'Ricerca tramite OpenStreetMap (senza foto, valutazioni o orari di apertura). Aggiungi una chiave API Google nelle Impostazioni per dati avanzati.',
+ 'places.categoryCreateError': 'Impossibile creare la categoria',
+ 'places.nameRequired': 'Inserisci un nome',
+ 'places.saveError': 'Impossibile salvare',
+ 'places.duplicateExists': "'{name}' è già in questo viaggio.",
+ 'places.addAnyway': 'Aggiungi comunque',
+ 'places.enrichOnImport': 'Arricchisci i luoghi con Google',
+ 'places.enrichOnImportHint':
+ 'Cerca ogni luogo importato per aggiungere foto, indirizzo e contatti. Usa la tua chiave Google Maps.',
+};
+export default places;
diff --git a/shared/src/i18n/it/planner.ts b/shared/src/i18n/it/planner.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a588205ba573252f2378f5234bcc9cab9c18964b
--- /dev/null
+++ b/shared/src/i18n/it/planner.ts
@@ -0,0 +1,69 @@
+import type { TranslationStrings } from '../types';
+
+const planner: TranslationStrings = {
+ 'planner.places': 'Luoghi',
+ 'planner.bookings': 'Prenotazioni',
+ 'planner.packingList': 'Lista valigia',
+ 'planner.documents': 'Documenti',
+ 'planner.dayPlan': 'Programma giornaliero',
+ 'planner.reservations': 'Prenotazioni',
+ 'planner.minTwoPlaces': 'Servono almeno 2 luoghi con coordinate',
+ 'planner.noGeoPlaces': 'Nessun luogo con coordinate disponibile',
+ 'planner.routeCalculated': 'Percorso calcolato',
+ 'planner.routeCalcFailed': 'Il percorso non è stato calcolato',
+ 'planner.routeError': 'Errore nel calcolo del percorso',
+ 'planner.icsExportFailed': 'Esportazione ICS non riuscita',
+ 'planner.routeOptimized': 'Percorso ottimizzato',
+ 'planner.reservationUpdated': 'Prenotazione aggiornata',
+ 'planner.reservationAdded': 'Prenotazione aggiunta',
+ 'planner.confirmDeleteReservation': 'Eliminare la prenotazione?',
+ 'planner.reservationDeleted': 'Prenotazione eliminata',
+ 'planner.days': 'Giorni',
+ 'planner.allPlaces': 'Tutti i luoghi',
+ 'planner.totalPlaces': '{n} luoghi in totale',
+ 'planner.noDaysPlanned': 'Nessun giorno ancora programmato',
+ 'planner.editTrip': 'Modifica viaggio →',
+ 'planner.placeOne': '1 luogo',
+ 'planner.placeN': '{n} luoghi',
+ 'planner.addNote': 'Aggiungi nota',
+ 'planner.noEntries': 'Nessuna voce per questo giorno',
+ 'planner.addPlace': 'Aggiungi luogo/attività',
+ 'planner.addPlaceShort': '+ Aggiungi luogo/attività',
+ 'planner.resPending': 'Prenotazione in attesa · ',
+ 'planner.resConfirmed': 'Prenotazione confermata · ',
+ 'planner.notePlaceholder': 'Nota…',
+ 'planner.noteTimePlaceholder': 'Ora (opzionale)',
+ 'planner.noteExamplePlaceholder':
+ 'es. S3 alle 14:30 dalla stazione centrale, traghetto dal molo 7, pausa pranzo…',
+ 'planner.totalCost': 'Costo totale',
+ 'planner.searchPlaces': 'Cerca luoghi…',
+ 'planner.allCategories': 'Tutte le categorie',
+ 'planner.noPlacesFound': 'Nessun luogo trovato',
+ 'planner.addFirstPlace': 'Aggiungi primo luogo',
+ 'planner.noReservations': 'Nessuna prenotazione',
+ 'planner.addFirstReservation': 'Aggiungi prima prenotazione',
+ 'planner.new': 'Nuovo',
+ 'planner.addToDay': '+ Giorno',
+ 'planner.calculating': 'Calcolo in corso…',
+ 'planner.route': 'Percorso',
+ 'planner.optimize': 'Ottimizza',
+ 'planner.openGoogleMaps': 'Apri in Google Maps',
+ 'planner.selectDayHint':
+ "Seleziona un giorno dall'elenco a sinistra per vedere il programma",
+ 'planner.noPlacesForDay': 'Ancora nessun luogo per questo giorno',
+ 'planner.addPlacesLink': 'Aggiungi luoghi →',
+ 'planner.minTotal': 'min. totali',
+ 'planner.noReservation': 'Nessuna prenotazione',
+ 'planner.removeFromDay': 'Rimuovi dal giorno',
+ 'planner.addToThisDay': 'Aggiungi al giorno',
+ 'planner.overview': 'Panoramica',
+ 'planner.noDays': 'Ancora nessun giorno',
+ 'planner.editTripToAddDays': 'Modifica viaggio per aggiungere giorni',
+ 'planner.dayCount': '{n} Giorni',
+ 'planner.clickToUnlock': 'Clicca per sbloccare',
+ 'planner.keepPosition':
+ "Mantieni la posizione durante l'ottimizzazione del percorso",
+ 'planner.dayDetails': 'Dettagli del giorno',
+ 'planner.dayN': 'Giorno {n}',
+};
+export default planner;
diff --git a/shared/src/i18n/it/register.ts b/shared/src/i18n/it/register.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9b3a66c91362cd11f72be02c4c61d065b49c31a1
--- /dev/null
+++ b/shared/src/i18n/it/register.ts
@@ -0,0 +1,26 @@
+import type { TranslationStrings } from '../types';
+
+const register: TranslationStrings = {
+ 'register.passwordMismatch': 'Le password non corrispondono',
+ 'register.passwordTooShort': 'La password deve contenere almeno 8 caratteri',
+ 'register.failed': 'Registrazione fallita',
+ 'register.getStarted': 'Inizia',
+ 'register.subtitle':
+ 'Crea un account e inizia a programmare i viaggi dei tuoi sogni.',
+ 'register.feature1': 'Piani di viaggio illimitati',
+ 'register.feature2': 'Vista mappa interattiva',
+ 'register.feature3': 'Gestisci luoghi e categorie',
+ 'register.feature4': 'Traccia le prenotazioni',
+ 'register.feature5': 'Crea liste per la valigia',
+ 'register.feature6': 'Archivia foto e file',
+ 'register.createAccount': 'Crea Account',
+ 'register.startPlanning': 'Inizia a programmare il tuo viaggio',
+ 'register.minChars': 'Min. 6 caratteri',
+ 'register.confirmPassword': 'Conferma Password',
+ 'register.repeatPassword': 'Ripeti password',
+ 'register.registering': 'Registrazione in corso...',
+ 'register.register': 'Registrati',
+ 'register.hasAccount': 'Hai già un account?',
+ 'register.signIn': 'Accedi',
+};
+export default register;
diff --git a/shared/src/i18n/it/reservations.ts b/shared/src/i18n/it/reservations.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b38efd37aac437c164f0a97f808b03b59cfb431c
--- /dev/null
+++ b/shared/src/i18n/it/reservations.ts
@@ -0,0 +1,164 @@
+import type { TranslationStrings } from '../types';
+
+const reservations: TranslationStrings = {
+ 'reservations.title': 'Prenotazioni',
+ 'reservations.empty': 'Ancora nessuna prenotazione',
+ 'reservations.emptyHint': 'Aggiungi prenotazioni per voli, alloggi e altro',
+ 'reservations.add': 'Aggiungi prenotazione',
+ 'reservations.addManual': 'Prenotazione manuale',
+ 'reservations.placeHint':
+ 'Suggerimento: è meglio creare le prenotazioni direttamente da un luogo per collegarle al tuo programma del giorno.',
+ 'reservations.confirmed': 'Confermata',
+ 'reservations.pending': 'In attesa',
+ 'reservations.summary': '{confirmed} confermate, {pending} in attesa',
+ 'reservations.fromPlan': 'Dal programma',
+ 'reservations.showFiles': 'Mostra file',
+ 'reservations.editTitle': 'Modifica prenotazione',
+ 'reservations.status': 'Stato',
+ 'reservations.datetime': 'Data e ora',
+ 'reservations.startTime': 'Ora di inizio',
+ 'reservations.endTime': 'Ora di fine',
+ 'reservations.date': 'Data',
+ 'reservations.time': 'Ora',
+ 'reservations.timeAlt': 'Ora (alternativa, es. 19:30)',
+ 'reservations.notes': 'Note',
+ 'reservations.notesPlaceholder': 'Note aggiuntive...',
+ 'reservations.meta.airline': 'Compagnia aerea',
+ 'reservations.meta.flightNumber': 'N. volo',
+ 'reservations.meta.from': 'Da',
+ 'reservations.meta.to': 'A',
+ 'reservations.layover.route': 'Itinerario',
+ 'reservations.layover.stop': 'Scalo',
+ 'reservations.layover.addStop': 'Aggiungi scalo',
+ 'reservations.layover.connection': 'Coincidenza',
+ 'reservations.layover.layover': 'Sosta',
+ 'reservations.needsReview': 'Verifica',
+ 'reservations.needsReviewHint':
+ "L'aeroporto non è stato riconosciuto automaticamente — conferma la posizione.",
+ 'reservations.searchLocation': 'Cerca stazione, porto, indirizzo...',
+ 'reservations.meta.trainNumber': 'N. treno',
+ 'reservations.meta.platform': 'Binario',
+ 'reservations.meta.seat': 'Posto',
+ 'reservations.meta.checkIn': 'Check-in',
+ 'reservations.meta.checkInUntil': 'Check-in fino a',
+ 'reservations.meta.checkOut': 'Check-out',
+ 'reservations.meta.linkAccommodation': 'Alloggio',
+ 'reservations.meta.pickAccommodation': 'Collega a un alloggio',
+ 'reservations.meta.noAccommodation': 'Nessuno',
+ 'reservations.meta.hotelPlace': 'Alloggio',
+ 'reservations.meta.pickHotel': 'Seleziona alloggio',
+ 'reservations.meta.fromDay': 'Da',
+ 'reservations.meta.toDay': 'A',
+ 'reservations.meta.selectDay': 'Seleziona giorno',
+ 'reservations.type.flight': 'Volo',
+ 'reservations.type.hotel': 'Alloggio',
+ 'reservations.type.restaurant': 'Ristorante',
+ 'reservations.type.train': 'Treno',
+ 'reservations.type.car': 'Auto',
+ 'reservations.type.cruise': 'Crociera',
+ 'reservations.type.event': 'Evento',
+ 'reservations.type.tour': 'Tour',
+ 'reservations.type.other': 'Altro',
+ 'reservations.type.bus': 'Autobus',
+ 'reservations.type.ferry': 'Traghetto',
+ 'reservations.type.bicycle': 'Bicicletta',
+ 'reservations.type.taxi': 'Taxi',
+ 'reservations.type.transport_other': 'Altro',
+ 'reservations.confirm.delete':
+ 'Sei sicuro di voler eliminare la prenotazione "{name}"?',
+ 'reservations.confirm.deleteTitle': 'Eliminare la prenotazione?',
+ 'reservations.confirm.deleteBody':
+ '"{name}" verrà eliminato in modo permanente.',
+ 'reservations.toast.updated': 'Prenotazione aggiornata',
+ 'reservations.toast.removed': 'Prenotazione eliminata',
+ 'reservations.toast.fileUploaded': 'File caricato',
+ 'reservations.toast.uploadError': 'Impossibile caricare',
+ 'reservations.newTitle': 'Nuova prenotazione',
+ 'reservations.bookingType': 'Tipo di prenotazione',
+ 'reservations.titleLabel': 'Titolo',
+ 'reservations.titlePlaceholder': 'es. Lufthansa LH123, Hotel Adlon, ...',
+ 'reservations.locationAddress': 'Posizione / Indirizzo',
+ 'reservations.locationPlaceholder': 'Indirizzo, aeroporto, hotel...',
+ 'reservations.confirmationCode': 'Codice prenotazione',
+ 'reservations.confirmationPlaceholder': 'es. ABC12345',
+ 'reservations.day': 'Giorno',
+ 'reservations.noDay': 'Nessun giorno',
+ 'reservations.place': 'Luogo',
+ 'reservations.noPlace': 'Nessun luogo',
+ 'reservations.pendingSave': 'verrà salvato…',
+ 'reservations.uploading': 'Caricamento...',
+ 'reservations.attachFile': 'Allega file',
+ 'reservations.linkExisting': 'Collega file esistente',
+ 'reservations.toast.saveError': 'Impossibile salvare',
+ 'reservations.toast.updateError': 'Impossibile aggiornare',
+ 'reservations.toast.deleteError': 'Impossibile eliminare',
+ 'reservations.confirm.remove': 'Rimuovere la prenotazione per "{name}"?',
+ 'reservations.linkAssignment': "Collega all'assegnazione del giorno",
+ 'reservations.pickAssignment':
+ "Seleziona un'assegnazione dal tuo programma...",
+ 'reservations.noAssignment': 'Nessun collegamento (autonomo)',
+ 'reservations.price': 'Prezzo',
+ 'reservations.budgetCategory': 'Categoria budget',
+ 'reservations.budgetCategoryPlaceholder': 'es. Trasporto, Alloggio',
+ 'reservations.budgetCategoryAuto': 'Auto (dal tipo di prenotazione)',
+ 'reservations.budgetHint':
+ 'Una voce di budget verrà creata automaticamente al salvataggio.',
+ 'reservations.departureDate': 'Partenza',
+ 'reservations.arrivalDate': 'Arrivo',
+ 'reservations.departureTime': 'Ora part.',
+ 'reservations.arrivalTime': 'Ora arr.',
+ 'reservations.pickupDate': 'Ritiro',
+ 'reservations.returnDate': 'Riconsegna',
+ 'reservations.pickupTime': 'Ora ritiro',
+ 'reservations.returnTime': 'Ora riconsegna',
+ 'reservations.endDate': 'Data fine',
+ 'reservations.meta.departureTimezone': 'TZ part.',
+ 'reservations.meta.arrivalTimezone': 'TZ arr.',
+ 'reservations.span.departure': 'Partenza',
+ 'reservations.span.arrival': 'Arrivo',
+ 'reservations.span.inTransit': 'In transito',
+ 'reservations.span.pickup': 'Ritiro',
+ 'reservations.span.return': 'Riconsegna',
+ 'reservations.span.active': 'Attivo',
+ 'reservations.span.start': 'Inizio',
+ 'reservations.span.end': 'Fine',
+ 'reservations.span.ongoing': 'In corso',
+ 'reservations.validation.endBeforeStart':
+ 'La data/ora di fine deve essere successiva alla data/ora di inizio',
+ 'reservations.addBooking': 'Aggiungi prenotazione',
+ 'reservations.import.title': 'Importa conferme di prenotazione',
+ 'reservations.import.cta': 'Importa da file',
+ 'reservations.import.dropHere': 'Trascina i file di conferma prenotazione qui o clicca per selezionare',
+ 'reservations.import.dropActive': 'Rilascia i file per importare',
+ 'reservations.import.acceptedFormats': 'Accettati: EML, PDF, PKPass, HTML, TXT (max 10 MB ciascuno, fino a 5 file)',
+ 'reservations.import.parsing': 'Analisi dei file in corso…',
+ 'reservations.import.previewHeading': '{count} prenotazione/i trovata/e',
+ 'reservations.import.previewEmpty': 'Nessuna prenotazione è stata estratta dai file caricati.',
+ 'reservations.import.removeItem': 'Rimuovi',
+ 'reservations.import.confirm': 'Importa {count} prenotazione/i',
+ 'reservations.import.back': 'Indietro',
+ 'reservations.import.success': '{count} prenotazione/i importata/e',
+ 'reservations.import.partialFailure': '{created} importata/e, {failed} fallita/e',
+ 'reservations.import.error': "Analisi fallita. Assicurati che il file sia una conferma di prenotazione valida.",
+ 'reservations.import.unavailable': "L'importazione di prenotazioni non è disponibile su questo server.",
+ 'reservations.import.unsupportedFormat': 'Formato file non supportato. Usa EML, PDF, PKPass, HTML o TXT.',
+ 'reservations.import.fileTooLarge': 'Il file "{name}" supera il limite di 10 MB.',
+ 'reservations.airtrail.title': 'Importa da AirTrail',
+ 'reservations.airtrail.cta': 'AirTrail',
+ 'reservations.airtrail.synced': 'AirTrail',
+ 'reservations.airtrail.syncedHint': 'Sincronizzato da AirTrail — le modifiche restano sincronizzate in entrambe le direzioni.',
+ 'reservations.airtrail.notSynced': 'Non sincronizzato',
+ 'reservations.airtrail.notSyncedHint': 'Questo volo è stato rimosso in AirTrail e non si sincronizza più.',
+ 'reservations.airtrail.loadError': 'Impossibile caricare i tuoi voli AirTrail.',
+ 'reservations.airtrail.imported': '{count} volo/i importato/i',
+ 'reservations.airtrail.skippedDuplicate': '{count} già presente/i in questo viaggio, ignorato/i',
+ 'reservations.airtrail.nothingImported': 'Niente da importare.',
+ 'reservations.airtrail.importError': 'Importazione fallita. Riprova.',
+ 'reservations.airtrail.undo': 'Importa da AirTrail',
+ 'reservations.airtrail.alreadyImported': 'Importato',
+ 'reservations.airtrail.duringTrip': 'Durante questo viaggio',
+ 'reservations.airtrail.otherFlights': 'Altri voli',
+ 'reservations.airtrail.empty': 'Nessun volo trovato nel tuo account AirTrail.',
+ 'reservations.airtrail.importCta': 'Importa {count}',
+};
+export default reservations;
diff --git a/shared/src/i18n/it/settings.ts b/shared/src/i18n/it/settings.ts
new file mode 100644
index 0000000000000000000000000000000000000000..acc504b8baa8dbcb441842aea348f7a10680334b
--- /dev/null
+++ b/shared/src/i18n/it/settings.ts
@@ -0,0 +1,345 @@
+import type { TranslationStrings } from '../types';
+
+const settings: TranslationStrings = {
+ 'settings.title': 'Impostazioni',
+ 'settings.subtitle': 'Configura le tue impostazioni personali',
+ 'settings.tabs.display': 'Visualizzazione',
+ 'settings.tabs.map': 'Mappa',
+ 'settings.tabs.notifications': 'Notifiche',
+ 'settings.tabs.integrations': 'Integrazioni',
+ 'settings.tabs.account': 'Account',
+ 'settings.tabs.offline': 'Offline',
+ 'settings.tabs.about': 'Informazioni',
+ 'settings.map': 'Mappa',
+ 'settings.mapTemplate': 'Modello Mappa',
+ 'settings.mapTemplatePlaceholder.select': 'Seleziona modello...',
+ 'settings.mapDefaultHint': 'Lascia vuoto per OpenStreetMap (predefinito)',
+ 'settings.mapTemplatePlaceholder':
+ 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
+ 'settings.mapHint': 'Modello URL per i tile della mappa',
+ 'settings.mapProvider': 'Provider mappa',
+ 'settings.mapProviderHint':
+ 'Influisce sulle mappe Trip Planner e Journey. Atlas usa sempre Leaflet.',
+ 'settings.mapLeafletSubtitle': 'Classica 2D, qualsiasi tile raster',
+ 'settings.mapMapboxSubtitle': 'Tile vettoriali, edifici 3D e terreno',
+ 'settings.mapExperimental': 'Sperimentale',
+ 'settings.mapMapboxToken': 'Token di accesso Mapbox',
+ 'settings.mapMapboxTokenHint': 'Token pubblico (pk.*) da',
+ 'settings.mapMapboxTokenLink': 'mapbox.com → Token di accesso',
+ 'settings.mapStyle': 'Stile mappa',
+ 'settings.mapStylePlaceholder': 'Seleziona uno stile Mapbox',
+ 'settings.mapStyleHint': 'Preset o il tuo URL mapbox://styles/USER/ID',
+ 'settings.map3dBuildings': 'Edifici 3D e terreno',
+ 'settings.map3dHint':
+ 'Inclinazione + estrusioni 3D reali degli edifici — funziona con ogni stile, incluso satellite.',
+ 'settings.mapHighQuality': 'Modalità alta qualità',
+ 'settings.mapHighQualityHint':
+ 'Antialiasing + proiezione globo per bordi più nitidi e una vista realistica del mondo.',
+ 'settings.mapHighQualityWarning':
+ 'Può influire sulle prestazioni su dispositivi meno potenti.',
+ 'settings.mapTipLabel': 'Suggerimento:',
+ 'settings.mapTip':
+ 'Click destro e trascina per ruotare/inclinare la mappa. Click centrale per aggiungere un luogo (il click destro è riservato alla rotazione).',
+ 'settings.latitude': 'Latitudine',
+ 'settings.longitude': 'Longitudine',
+ 'settings.saveMap': 'Salva Mappa',
+ 'settings.apiKeys': 'Chiavi API',
+ 'settings.mapsKey': 'Chiave API Google Maps',
+ 'settings.mapsKeyHint':
+ 'Per la ricerca dei luoghi. Richiede Places API (New). Ottienila su console.cloud.google.com',
+ 'settings.weatherKey': 'Chiave API OpenWeatherMap',
+ 'settings.weatherKeyHint':
+ 'Per i dati meteo. Gratuita su openweathermap.org/api',
+ 'settings.keyPlaceholder': 'Inserisci la chiave...',
+ 'settings.configured': 'Configurata',
+ 'settings.saveKeys': 'Salva Chiavi',
+ 'settings.display': 'Visualizzazione',
+ 'settings.colorMode': 'Modalità Colore',
+ 'settings.light': 'Chiara',
+ 'settings.dark': 'Scura',
+ 'settings.auto': 'Automatica',
+ 'settings.language': 'Lingua',
+ 'settings.temperature': 'Unità di Temperatura',
+ 'settings.timeFormat': 'Formato Ora',
+ 'settings.blurBookingCodes': 'Nascondi codici di prenotazione',
+ 'settings.optimizeFromAccommodation': "Ottimizza il percorso dall'alloggio",
+ 'settings.optimizeFromAccommodationHint':
+ "Quando ottimizzi un giorno, fa iniziare il percorso dall'hotel in cui ti svegli e terminarlo in quello in cui fai il check-in quella sera.",
+ 'settings.notifications': 'Notifiche',
+ 'settings.notifyTripInvite': 'Inviti di viaggio',
+ 'settings.notifyBookingChange': 'Modifiche alle prenotazioni',
+ 'settings.notifyTripReminder': 'Promemoria di viaggio',
+ 'settings.notifyTodoDue': 'Attività in scadenza',
+ 'settings.notifyVacayInvite': 'Inviti fusione Vacay',
+ 'settings.notifyPhotosShared': 'Foto condivise (Immich)',
+ 'settings.notifyCollabMessage': 'Messaggi chat (Collab)',
+ 'settings.notifyPackingTagged': 'Lista valigia: assegnazioni',
+ 'settings.notifyWebhook': 'Notifiche webhook',
+ 'settings.notificationsDisabled':
+ 'Le notifiche non sono configurate. Chiedi a un amministratore di abilitare le notifiche e-mail o webhook.',
+ 'settings.notificationsActive': 'Canale attivo',
+ 'settings.notificationsManagedByAdmin':
+ "Gli eventi di notifica sono configurati dall'amministratore.",
+ 'settings.on': 'On',
+ 'settings.off': 'Off',
+ 'settings.mcp.title': 'Configurazione MCP',
+ 'settings.mcp.endpoint': 'Endpoint MCP',
+ 'settings.mcp.clientConfig': 'Configurazione client',
+ 'settings.mcp.clientConfigHint':
+ 'Sostituisci con un token API dalla lista sottostante. Il percorso di npx potrebbe dover essere adattato per il tuo sistema (es. C:\\PROGRA~1\\nodejs\\npx.cmd su Windows).',
+ 'settings.mcp.clientConfigHintOAuth':
+ 'Replace and with the credentials shown in the OAuth 2.1 client you created above. mcp-remote will open your browser to complete the authorization the first time you connect. The path to npx may need to be adjusted for your system (e.g. C:PROGRA~1\nodejs\npx.cmd on Windows).',
+ 'settings.mcp.copy': 'Copia',
+ 'settings.mcp.copied': 'Copiato!',
+ 'settings.mcp.apiTokens': 'Token API',
+ 'settings.mcp.createToken': 'Crea nuovo token',
+ 'settings.mcp.noTokens':
+ 'Nessun token ancora. Creane uno per connettere i client MCP.',
+ 'settings.mcp.tokenCreatedAt': 'Creato',
+ 'settings.mcp.tokenUsedAt': 'Utilizzato',
+ 'settings.mcp.deleteTokenTitle': 'Elimina token',
+ 'settings.mcp.deleteTokenMessage':
+ "Questo token smetterà di funzionare immediatamente. Qualsiasi client MCP che lo utilizza perderà l'accesso.",
+ 'settings.mcp.modal.createTitle': 'Crea token API',
+ 'settings.mcp.modal.tokenName': 'Nome del token',
+ 'settings.mcp.modal.tokenNamePlaceholder':
+ 'es. Claude Desktop, Laptop di lavoro',
+ 'settings.mcp.modal.creating': 'Creazione…',
+ 'settings.mcp.modal.create': 'Crea token',
+ 'settings.mcp.modal.createdTitle': 'Token creato',
+ 'settings.mcp.modal.createdWarning':
+ 'Questo token verrà mostrato solo una volta. Copialo e salvalo ora — non può essere recuperato.',
+ 'settings.mcp.modal.done': 'Fatto',
+ 'settings.mcp.toast.created': 'Token creato',
+ 'settings.mcp.toast.createError': 'Impossibile creare il token',
+ 'settings.mcp.toast.deleted': 'Token eliminato',
+ 'settings.mcp.toast.deleteError': 'Impossibile eliminare il token',
+ 'settings.mcp.apiTokensDeprecated':
+ 'I token API sono deprecati e verranno rimossi in una versione futura. Utilizza invece i client OAuth 2.1.',
+ 'settings.oauth.clients': 'Client OAuth 2.1',
+ 'settings.oauth.clientsHint':
+ 'Registra client OAuth 2.1 per consentire alle applicazioni MCP di terze parti (Claude Web, Cursor, ecc.) di connettersi senza token statici.',
+ 'settings.oauth.createClient': 'Nuovo client',
+ 'settings.oauth.noClients': 'Nessun client OAuth registrato.',
+ 'settings.oauth.clientId': 'ID client',
+ 'settings.oauth.clientSecret': 'Segreto client',
+ 'settings.oauth.deleteClient': 'Elimina client',
+ 'settings.oauth.deleteClientMessage':
+ "Questo client e tutte le sessioni attive verranno eliminati definitivamente. Qualsiasi applicazione che lo utilizza perderà immediatamente l'accesso.",
+ 'settings.oauth.rotateSecret': 'Rinnova segreto',
+ 'settings.oauth.rotateSecretMessage':
+ 'Verrà generato un nuovo segreto client e tutte le sessioni esistenti verranno invalidate immediatamente. Aggiorna la tua applicazione prima di chiudere questa finestra.',
+ 'settings.oauth.rotateSecretConfirm': 'Rinnova',
+ 'settings.oauth.rotateSecretConfirming': 'Rinnovo in corso…',
+ 'settings.oauth.rotateSecretDoneTitle': 'Nuovo segreto generato',
+ 'settings.oauth.rotateSecretDoneWarning':
+ 'Questo segreto viene mostrato una sola volta. Copialo ora e aggiorna la tua applicazione — tutte le sessioni precedenti sono state invalidate.',
+ 'settings.oauth.activeSessions': 'Sessioni OAuth attive',
+ 'settings.oauth.sessionScopes': 'Ambiti',
+ 'settings.oauth.sessionExpires': 'Scade',
+ 'settings.oauth.revoke': 'Revoca',
+ 'settings.oauth.revokeSession': 'Revoca sessione',
+ 'settings.oauth.revokeSessionMessage':
+ "Questo revocherà immediatamente l'accesso per questa sessione OAuth.",
+ 'settings.oauth.modal.createTitle': 'Registra client OAuth',
+ 'settings.oauth.modal.presets': 'Preimpostazioni rapide',
+ 'settings.oauth.modal.clientName': 'Nome applicazione',
+ 'settings.oauth.modal.clientNamePlaceholder':
+ 'es. Claude Web, La mia app MCP',
+ 'settings.oauth.modal.redirectUris': 'URI di reindirizzamento',
+ 'settings.oauth.modal.redirectUrisPlaceholder':
+ 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint':
+ 'Un URI per riga. HTTPS richiesto (localhost esente). Corrispondenza esatta richiesta.',
+ 'settings.oauth.modal.scopes': 'Ambiti consentiti',
+ 'settings.oauth.modal.scopesHint':
+ "list_trips e get_trip_summary sono sempre disponibili — nessun ambito richiesto. Permettono all'IA di scoprire gli ID viaggio necessari.",
+ 'settings.oauth.modal.selectAll': 'Seleziona tutto',
+ 'settings.oauth.modal.deselectAll': 'Deseleziona tutto',
+ 'settings.oauth.modal.creating': 'Registrazione…',
+ 'settings.oauth.modal.create': 'Registra client',
+ 'settings.oauth.modal.createdTitle': 'Client registrato',
+ 'settings.oauth.modal.createdWarning':
+ 'Il segreto client viene mostrato una sola volta. Copialo ora — non può essere recuperato.',
+ 'settings.oauth.toast.createError': 'Impossibile registrare il client OAuth',
+ 'settings.oauth.toast.deleted': 'Client OAuth eliminato',
+ 'settings.oauth.toast.deleteError': 'Impossibile eliminare il client OAuth',
+ 'settings.oauth.toast.revoked': 'Sessione revocata',
+ 'settings.oauth.toast.revokeError': 'Impossibile revocare la sessione',
+ 'settings.oauth.toast.rotateError': 'Impossibile rinnovare il segreto client',
+ 'settings.oauth.modal.machineClient':
+ 'Client macchina (senza login nel browser)',
+ 'settings.oauth.modal.machineClientHint':
+ 'Usa il grant client_credentials — nessun URI di reindirizzamento necessario. Il token viene emesso direttamente tramite client_id + client_secret e agisce come te negli ambiti selezionati.',
+ 'settings.oauth.modal.machineClientUsage':
+ 'Ottieni token: POST /oauth/token con grant_type=client_credentials, client_id e client_secret. Senza browser, senza token di aggiornamento.',
+ 'settings.oauth.badge.machine': 'macchina',
+ 'settings.account': 'Account',
+ 'settings.about': 'Informazioni',
+ 'settings.about.reportBug': 'Segnala un bug',
+ 'settings.about.reportBugHint': 'Hai trovato un problema? Faccelo sapere',
+ 'settings.about.featureRequest': 'Richiedi funzionalità',
+ 'settings.about.featureRequestHint': 'Suggerisci una nuova funzionalità',
+ 'settings.about.wikiHint': 'Documentazione e guide',
+ 'settings.about.supporters.badge': 'Sostenitori Mensili',
+ 'settings.about.supporters.title': 'Compagni di viaggio per TREK',
+ 'settings.about.supporters.subtitle':
+ 'Mentre pianifichi il tuo prossimo itinerario, queste persone aiutano a pianificare il futuro di TREK. Il loro contributo mensile va direttamente allo sviluppo e alle ore realmente investite — per mantenere TREK Open Source.',
+ 'settings.about.supporters.since': 'sostenitore da {date}',
+ 'settings.about.supporters.tierEmpty': 'Sii il primo',
+ 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket',
+ 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP',
+ 'settings.about.supporter.tier.businessClassDreamer':
+ 'Business Class Dreamer',
+ 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller',
+ 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate',
+ 'settings.about.description':
+ "TREK è un pianificatore di viaggi self-hosted che ti aiuta a organizzare i tuoi viaggi dalla prima idea all'ultimo ricordo. Pianificazione giornaliera, budget, liste bagagli, foto e molto altro — tutto in un unico posto, sul tuo server.",
+ 'settings.about.madeWith': 'Fatto con',
+ 'settings.about.madeBy': 'da Maurice e una crescente comunità open-source.',
+ 'settings.username': 'Nome utente',
+ 'settings.email': 'Email',
+ 'settings.role': 'Ruolo',
+ 'settings.roleAdmin': 'Amministratore',
+ 'settings.oidcLinked': 'Collegato con',
+ 'settings.changePassword': 'Cambia Password',
+ 'settings.currentPassword': 'Password attuale',
+ 'settings.currentPasswordRequired': 'La password attuale è obbligatoria',
+ 'settings.newPassword': 'Nuova password',
+ 'settings.confirmPassword': 'Conferma nuova password',
+ 'settings.updatePassword': 'Aggiorna password',
+ 'settings.passwordRequired': 'Inserisci la password attuale e quella nuova',
+ 'settings.passwordTooShort': 'La password deve contenere almeno 8 caratteri',
+ 'settings.passwordMismatch': 'Le password non corrispondono',
+ 'settings.passwordWeak':
+ 'La password deve contenere lettere maiuscole, minuscole, un numero e un carattere speciale',
+ 'settings.passwordChanged': 'Password cambiata con successo',
+ 'settings.deleteAccount': 'Elimina account',
+ 'settings.deleteAccountTitle': 'Eliminare il tuo account?',
+ 'settings.deleteAccountWarning':
+ 'Il tuo account e tutti i tuoi viaggi, luoghi e file verranno eliminati in modo permanente. Questa azione non può essere annullata.',
+ 'settings.deleteAccountConfirm': 'Elimina in modo permanente',
+ 'settings.deleteBlockedTitle': 'Eliminazione impossibile',
+ 'settings.deleteBlockedMessage':
+ "Sei l'unico amministratore. Promuovi un altro utente ad amministratore prima di eliminare il tuo account.",
+ 'settings.roleUser': 'Utente',
+ 'settings.saveProfile': 'Salva Profillo',
+ 'settings.toast.mapSaved': 'Impostazioni mappa salvate',
+ 'settings.toast.keysSaved': 'Chiavi API salvate',
+ 'settings.toast.displaySaved': 'Impostazioni di visualizzazione salvate',
+ 'settings.toast.profileSaved': 'Profilo salvato',
+ 'settings.uploadAvatar': 'Carica Immagine del Profilo',
+ 'settings.removeAvatar': 'Rimuovi Immagine del Profilo',
+ 'settings.avatarUploaded': 'Immagine del profilo aggiornata',
+ 'settings.avatarRemoved': 'Immagine del profilo rimossa',
+ 'settings.avatarError': 'Impossibile caricare',
+ 'settings.mfa.title': 'Autenticazione a due fattori (2FA)',
+ 'settings.mfa.description':
+ "Aggiunge un secondo passaggio quando accedi con email e password. Usa un'app authenticator (Google Authenticator, Authy, ecc.).",
+ 'settings.mfa.requiredByPolicy':
+ "L'amministratore richiede l'autenticazione a due fattori. Configura un'app authenticator qui sotto prima di continuare.",
+ 'settings.mfa.backupTitle': 'Codici di backup',
+ 'settings.mfa.backupDescription':
+ "Usa questi codici monouso se perdi l'accesso alla tua app authenticator.",
+ 'settings.mfa.backupWarning':
+ 'Salvali adesso. Ogni codice può essere usato una sola volta.',
+ 'settings.mfa.backupCopy': 'Copia codici',
+ 'settings.mfa.backupDownload': 'Scarica TXT',
+ 'settings.mfa.backupPrint': 'Stampa / PDF',
+ 'settings.mfa.backupCopied': 'Codici di backup copiati',
+ 'settings.mfa.enabled': 'La 2FA è abilitata sul tuo account.',
+ 'settings.mfa.disabled': 'La 2FA non è abilitata.',
+ 'settings.mfa.setup': 'Configura authenticator',
+ 'settings.mfa.scanQr':
+ 'Scansiona questo codice QR con la tua app, o inserisci il segreto manualmente.',
+ 'settings.mfa.secretLabel': 'Chiave segreta (inserimento manuale)',
+ 'settings.mfa.codePlaceholder': 'Codice a 6 cifre',
+ 'settings.mfa.enable': 'Abilita 2FA',
+ 'settings.mfa.cancelSetup': 'Annulla',
+ 'settings.mfa.disableTitle': 'Disabilita 2FA',
+ 'settings.mfa.disableHint':
+ 'Inserisci la password del tuo account e un codice attuale dal tuo authenticator.',
+ 'settings.mfa.disable': 'Disabilita 2FA',
+ 'settings.mfa.toastEnabled': 'Autenticazione a due fattori abilitata',
+ 'settings.mfa.toastDisabled': 'Autenticazione a due fattori disabilitata',
+ 'settings.mfa.demoBlocked': 'Non disponibile in modalità demo',
+ 'settings.mustChangePassword':
+ 'Devi cambiare la password prima di continuare. Imposta una nuova password qui sotto.',
+ 'settings.bookingLabels': 'Etichette percorsi prenotati',
+ 'settings.bookingLabelsHint':
+ "Mostra i nomi di stazioni / aeroporti sulla mappa. Se disattivato, viene mostrata solo l'icona.",
+ 'settings.notifyVersionAvailable': 'Nuova versione disponibile',
+ 'settings.notificationPreferences.noChannels':
+ 'Nessun canale di notifica configurato. Chiedi a un amministratore di configurare notifiche via e-mail o webhook.',
+ 'settings.webhookUrl.label': 'URL webhook',
+ 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...',
+ 'settings.webhookUrl.hint':
+ 'Inserisci il tuo URL webhook Discord, Slack o personalizzato per ricevere notifiche.',
+ 'settings.webhookUrl.saved': 'URL webhook salvato',
+ 'settings.webhookUrl.test': 'Test',
+ 'settings.webhookUrl.testSuccess': 'Webhook di test inviato con successo',
+ 'settings.webhookUrl.testFailed': 'Invio webhook di test fallito',
+ 'settings.ntfyUrl.topicLabel': 'Argomento Ntfy',
+ 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts',
+ 'settings.ntfyUrl.serverLabel': 'URL server Ntfy (opzionale)',
+ 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh',
+ 'settings.ntfyUrl.hint':
+ "Inserisci il tuo argomento Ntfy per ricevere notifiche push. Lascia il server vuoto per usare il valore predefinito configurato dall'amministratore.",
+ 'settings.ntfyUrl.tokenLabel': 'Token di accesso (opzionale)',
+ 'settings.ntfyUrl.tokenHint':
+ 'Richiesto per gli argomenti protetti da password.',
+ 'settings.ntfyUrl.saved': 'Impostazioni Ntfy salvate',
+ 'settings.ntfyUrl.test': 'Testa',
+ 'settings.ntfyUrl.testSuccess': 'Notifica di test Ntfy inviata con successo',
+ 'settings.ntfyUrl.testFailed': 'Notifica di test Ntfy fallita',
+ 'settings.ntfyUrl.tokenCleared': 'Token di accesso rimosso',
+ 'settings.notificationPreferences.inapp': 'In-App',
+ 'settings.notificationPreferences.webhook': 'Webhook',
+ 'settings.notificationPreferences.email': 'Email',
+ 'settings.notificationPreferences.ntfy': 'Ntfy',
+ "settings.currency": "Currency",
+ "settings.currencyHint": "All amounts in Costs are converted to and shown in this currency.",
+ 'settings.passkey.title': 'Passkey',
+ 'settings.passkey.description':
+ 'Accedi più velocemente e in modo resistente al phishing con una passkey — la tua impronta digitale, il volto, il PIN o una chiave hardware. La tua password resta come riserva.',
+ 'settings.passkey.notConfigured':
+ 'Le passkey sono abilitate ma non ancora completamente configurate su questo server. Chiedi al tuo amministratore di impostare il dominio WebAuthn.',
+ 'settings.passkey.add': 'Aggiungi una passkey',
+ 'settings.passkey.addTitle': 'Aggiungi una passkey',
+ 'settings.passkey.passwordPrompt':
+ 'Conferma la tua password attuale, poi segui le istruzioni del tuo dispositivo.',
+ 'settings.passkey.passwordRequired': 'La tua password attuale è obbligatoria.',
+ 'settings.passkey.namePlaceholder': 'Nome (facoltativo, es. "iPhone")',
+ 'settings.passkey.addedToast': 'Passkey aggiunta',
+ 'settings.passkey.added': 'Aggiunta',
+ 'settings.passkey.addError': 'Impossibile aggiungere la passkey',
+ 'settings.passkey.cancelled': 'Configurazione della passkey annullata',
+ 'settings.passkey.deleted': 'Passkey rimossa',
+ 'settings.passkey.deleteConfirm':
+ 'Rimuovere questa passkey? Conferma con la tua password.',
+ 'settings.passkey.rename': 'Rinomina',
+ 'settings.passkey.defaultName': 'Passkey',
+ 'settings.passkey.synced': 'Sincronizzata',
+ 'settings.passkey.deviceBound': 'Questo dispositivo',
+ 'settings.passkey.lastUsed': 'Ultimo utilizzo',
+ 'settings.passkey.neverUsed': 'Mai usata',
+ 'settings.mapPoiPill': 'Esplora luoghi sulla mappa',
+ 'settings.mapPoiPillHint': 'Mostra un selettore di categorie sulla mappa del viaggio per trovare ristoranti, hotel e altro nelle vicinanze da OpenStreetMap.',
+ 'settings.airtrail.title': 'AirTrail',
+ 'settings.airtrail.hint': 'Collega il tuo AirTrail self-hosted per importare e sincronizzare i voli. Crea una chiave API in AirTrail in Impostazioni → Sicurezza.',
+ 'settings.airtrail.url': 'URL istanza',
+ 'settings.airtrail.apiKey': 'Chiave API',
+ 'settings.airtrail.apiKeyPlaceholder': 'Chiave API Bearer',
+ 'settings.airtrail.apiKeyHint': 'Generata in AirTrail in Impostazioni → Sicurezza. Memorizzata crittografata.',
+ 'settings.airtrail.allowInsecureTls': 'Consenti certificati autofirmati',
+ 'settings.airtrail.allowInsecureTlsHint': 'Abilita solo per un\'istanza attendibile sulla tua rete.',
+ 'settings.airtrail.connected': 'Connesso',
+ 'settings.airtrail.notConnected': 'Non connesso',
+ 'settings.airtrail.toast.saved': 'Connessione AirTrail salvata',
+ 'settings.airtrail.toast.saveError': 'Impossibile salvare la connessione',
+ 'settings.airtrail.test.button': 'Prova connessione',
+ 'settings.airtrail.test.success': 'Connesso — {count} volo/i trovato/i',
+ 'settings.airtrail.test.failed': 'Connessione fallita',
+};
+
+export default settings;
diff --git a/shared/src/i18n/it/share.ts b/shared/src/i18n/it/share.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7889ae7c162ec7d7809de0177e36fea90c4097be
--- /dev/null
+++ b/shared/src/i18n/it/share.ts
@@ -0,0 +1,16 @@
+import type { TranslationStrings } from '../types';
+
+const share: TranslationStrings = {
+ 'share.linkTitle': 'Link pubblico',
+ 'share.linkHint':
+ 'Crea un link che chiunque può usare per visualizzare questo viaggio senza accedere. Solo lettura — nessuna modifica possibile.',
+ 'share.createLink': 'Crea link',
+ 'share.deleteLink': 'Elimina link',
+ 'share.createError': 'Impossibile creare il link',
+ 'share.permMap': 'Mappa e programma',
+ 'share.permBookings': 'Prenotazioni',
+ 'share.permPacking': 'Valigia',
+ 'share.permBudget': 'Budget',
+ 'share.permCollab': 'Chat',
+};
+export default share;
diff --git a/shared/src/i18n/it/shared.ts b/shared/src/i18n/it/shared.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a052c63da89b393b0012e0ac7b28f3dce91c4925
--- /dev/null
+++ b/shared/src/i18n/it/shared.ts
@@ -0,0 +1,21 @@
+import type { TranslationStrings } from '../types';
+
+const shared: TranslationStrings = {
+ 'shared.expired': 'Link scaduto o non valido',
+ 'shared.expiredHint': 'Questo link di viaggio condiviso non è più attivo.',
+ 'shared.readOnly': 'Vista in sola lettura',
+ 'shared.tabPlan': 'Programma',
+ 'shared.tabBookings': 'Prenotazioni',
+ 'shared.tabPacking': 'Valigia',
+ 'shared.tabBudget': 'Budget',
+ 'shared.tabChat': 'Chat',
+ 'shared.days': 'giorni',
+ 'shared.places': 'luoghi',
+ 'shared.other': 'Altro',
+ 'shared.totalBudget': 'Budget totale',
+ 'shared.messages': 'messaggi',
+ 'shared.sharedVia': 'Condiviso tramite',
+ 'shared.confirmed': 'Confermato',
+ 'shared.pending': 'In attesa',
+};
+export default shared;
diff --git a/shared/src/i18n/it/stats.ts b/shared/src/i18n/it/stats.ts
new file mode 100644
index 0000000000000000000000000000000000000000..35b45a98e488fa841b6085d1a8b4182413566297
--- /dev/null
+++ b/shared/src/i18n/it/stats.ts
@@ -0,0 +1,13 @@
+import type { TranslationStrings } from '../types';
+
+const stats: TranslationStrings = {
+ 'stats.countries': 'Paesi',
+ 'stats.cities': 'Città',
+ 'stats.trips': 'Viaggi',
+ 'stats.places': 'Luoghi',
+ 'stats.worldProgress': 'Progresso nel mondo',
+ 'stats.visited': 'visitati',
+ 'stats.remaining': 'rimanenti',
+ 'stats.visitedCountries': 'Paesi visitati',
+};
+export default stats;
diff --git a/shared/src/i18n/it/system_notice.ts b/shared/src/i18n/it/system_notice.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f8c3b322639ba2e01bf764eb5cb67ee38ca19b99
--- /dev/null
+++ b/shared/src/i18n/it/system_notice.ts
@@ -0,0 +1,62 @@
+import type { TranslationStrings } from '../types';
+
+const system_notice: TranslationStrings = {
+ 'system_notice.welcome_v1.title': 'Benvenuto su TREK',
+ 'system_notice.welcome_v1.body':
+ 'Il tuo pianificatore di viaggi tutto in uno. Crea itinerari, condividi viaggi con gli amici e rimani organizzato — online e offline.',
+ 'system_notice.welcome_v1.cta_label': 'Pianifica un viaggio',
+ 'system_notice.welcome_v1.hero_alt':
+ "Destinazione di viaggio panoramica con l'interfaccia TREK",
+ 'system_notice.welcome_v1.highlight_plan': 'Itinerari giorno per giorno',
+ 'system_notice.welcome_v1.highlight_share':
+ 'Collabora con i tuoi compagni di viaggio',
+ 'system_notice.welcome_v1.highlight_offline': 'Funziona offline su mobile',
+ 'system_notice.dev_test_modal.title': '[Dev] Test notice',
+ 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.',
+ 'system_notice.pager.prev': 'Avviso precedente',
+ 'system_notice.pager.next': 'Avviso successivo',
+ 'system_notice.pager.counter': '{current} / {total}',
+ 'system_notice.pager.goto': "Vai all'avviso {n}",
+ 'system_notice.pager.position': 'Avviso {current} di {total}',
+ 'system_notice.v3_photos.title': 'Le foto sono spostate nella 3.0',
+ 'system_notice.v3_photos.body':
+ '**Foto** nel Pianificatore di Viaggio sono state rimosse. Le tue foto sono al sicuro — TREK non ha mai modificato la tua libreria Immich o Synology.\n\nLe foto ora si trovano nel componente aggiuntivo **Journey**. Journey è opzionale — se non è ancora disponibile, chiedi al tuo admin di abilitarlo in Admin → Addon.',
+ 'system_notice.v3_journey.title': 'Scopri Journey — diario di viaggio',
+ 'system_notice.v3_journey.body':
+ 'Documenta i tuoi viaggi come storie ricche con cronologie, gallerie fotografiche e mappe interattive.',
+ 'system_notice.v3_journey.cta_label': 'Apri Journey',
+ 'system_notice.v3_journey.highlight_timeline':
+ 'Cronologia e galleria giornaliera',
+ 'system_notice.v3_journey.highlight_photos': 'Importa da Immich o Synology',
+ 'system_notice.v3_journey.highlight_share':
+ 'Condividi pubblicamente — senza accesso',
+ 'system_notice.v3_journey.highlight_export':
+ 'Esporta come libro fotografico PDF',
+ 'system_notice.v3_features.title': 'Altri punti salienti nel 3.0',
+ 'system_notice.v3_features.body':
+ 'Altre novità da conoscere in questa versione.',
+ 'system_notice.v3_features.highlight_dashboard':
+ 'Dashboard ridisegnata mobile-first',
+ 'system_notice.v3_features.highlight_offline':
+ 'Modalità offline completa come PWA',
+ 'system_notice.v3_features.highlight_search':
+ 'Completamento automatico luoghi in tempo reale',
+ 'system_notice.v3_features.highlight_import':
+ 'Importa luoghi da file KMZ/KML',
+ 'system_notice.v3_mcp.title': 'MCP: aggiornamento OAuth 2.1',
+ 'system_notice.v3_mcp.body':
+ "L'integrazione MCP è stata completamente rinnovata. OAuth 2.1 è ora il metodo di autenticazione consigliato. I token statici (trek_…) sono deprecati e verranno rimossi in una versione futura.",
+ 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 consigliato (mcp-remote)',
+ 'system_notice.v3_mcp.highlight_scopes':
+ '24 scope di autorizzazione granulari',
+ 'system_notice.v3_mcp.highlight_deprecated': 'Token statici trek_ deprecati',
+ 'system_notice.v3_mcp.highlight_tools': 'Strumenti e prompt estesi',
+ 'system_notice.v3_thankyou.title': 'Una nota personale da parte mia',
+ 'system_notice.v3_thankyou.body':
+ "Prima di andare avanti — voglio prendermi un momento.\n\nTREK è nato come un progetto secondario che ho costruito per i miei viaggi. Non avrei mai immaginato che sarebbe cresciuto fino a diventare qualcosa di cui 4.000 di voi si fidano per pianificare le proprie avventure. Ogni stella, ogni issue, ogni richiesta di funzionalità — le leggo tutte, e sono loro a tenermi in piedi nelle notti tarde tra un lavoro a tempo pieno e l'università.\n\nVoglio che sappiate: TREK sarà sempre open source, sempre self-hosted, sempre vostro. Nessun tracciamento, nessun abbonamento, nessuna fregatura. Solo uno strumento creato da qualcuno che ama viaggiare tanto quanto voi.\n\nUn ringraziamento speciale a [jubnl](https://github.com/jubnl) — sei diventato un collaboratore incredibile. Molto di ciò che rende la 3.0 fantastica porta la tua impronta. Grazie per aver creduto in questo progetto quando era ancora acerbo.\n\nE a ognuno di voi che ha segnalato un bug, tradotto una stringa, condiviso TREK con un amico o semplicemente lo ha usato per pianificare un viaggio — **grazie**. Voi siete il motivo per cui tutto questo esiste.\n\nA molte altre avventure insieme.\n\n— Maurice\n\n---\n\n[Unisciti alla community su Discord](https://discord.gg/7Q6M6jDwzf)\n\nSe TREK rende i tuoi viaggi migliori, un [piccolo caffè](https://ko-fi.com/mauriceboe) aiuta sempre a tenere le luci accese.",
+ 'system_notice.v3014_whitespace_collision.title':
+ 'Azione richiesta: conflitto di account utente',
+ 'system_notice.v3014_whitespace_collision.body':
+ "L'aggiornamento 3.0.14 ha rilevato uno o più conflitti di nome utente o e-mail causati da spazi iniziali o finali nei valori memorizzati. Gli account interessati sono stati rinominati automaticamente. Controlla i log del server per le righe che iniziano con **[migration] WHITESPACE COLLISION** per identificare quali account richiedono revisione.",
+};
+export default system_notice;
diff --git a/shared/src/i18n/it/todo.ts b/shared/src/i18n/it/todo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..87149400b0d97c0a8d629b8147a8b7cf1c990f6d
--- /dev/null
+++ b/shared/src/i18n/it/todo.ts
@@ -0,0 +1,40 @@
+import type { TranslationStrings } from '../types';
+
+const todo: TranslationStrings = {
+ 'todo.subtab.packing': 'Lista di imballaggio',
+ 'todo.subtab.todo': 'Da fare',
+ 'todo.completed': 'completato/i',
+ 'todo.filter.all': 'Tutti',
+ 'todo.filter.open': 'Aperto',
+ 'todo.filter.done': 'Fatto',
+ 'todo.uncategorized': 'Senza categoria',
+ 'todo.namePlaceholder': 'Nome attività',
+ 'todo.descriptionPlaceholder': 'Descrizione (facoltativa)',
+ 'todo.unassigned': 'Non assegnato',
+ 'todo.noCategory': 'Nessuna categoria',
+ 'todo.hasDescription': 'Ha descrizione',
+ 'todo.addItem': 'Nuova attività',
+ 'todo.sidebar.sortBy': 'Ordina per',
+ 'todo.priority': 'Priorità',
+ 'todo.newCategoryLabel': 'nuova',
+ 'todo.newCategory': 'Nome categoria',
+ 'todo.addCategory': 'Aggiungi categoria',
+ 'todo.newItem': 'Nuova attività',
+ 'todo.empty': "Nessuna attività ancora. Aggiungi un'attività per iniziare!",
+ 'todo.filter.my': 'Le mie attività',
+ 'todo.filter.overdue': 'Scaduta',
+ 'todo.sidebar.tasks': 'Attività',
+ 'todo.sidebar.categories': 'Categorie',
+ 'todo.detail.title': 'Attività',
+ 'todo.detail.description': 'Descrizione',
+ 'todo.detail.category': 'Categoria',
+ 'todo.detail.dueDate': 'Scadenza',
+ 'todo.detail.assignedTo': 'Assegnato a',
+ 'todo.detail.delete': 'Elimina',
+ 'todo.detail.save': 'Salva modifiche',
+ 'todo.detail.create': 'Crea attività',
+ 'todo.detail.priority': 'Priorità',
+ 'todo.detail.noPriority': 'Nessuna',
+ 'todo.sortByPrio': 'Priorità',
+};
+export default todo;
diff --git a/shared/src/i18n/it/transport.ts b/shared/src/i18n/it/transport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8fbb56442f66493e940eb182ef36471fc02f3361
--- /dev/null
+++ b/shared/src/i18n/it/transport.ts
@@ -0,0 +1,10 @@
+import type { TranslationStrings } from '../types';
+
+const transport: TranslationStrings = {
+ 'transport.addTransport': 'Aggiungi trasporto',
+ 'transport.modalTitle.create': 'Aggiungi trasporto',
+ 'transport.modalTitle.edit': 'Modifica trasporto',
+ 'transport.title': 'Trasporti',
+ 'transport.addManual': 'Trasporto manuale',
+};
+export default transport;
diff --git a/shared/src/i18n/it/trip.ts b/shared/src/i18n/it/trip.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eb2024480d1aba819ec8f0632f3f3c78e088d43d
--- /dev/null
+++ b/shared/src/i18n/it/trip.ts
@@ -0,0 +1,31 @@
+import type { TranslationStrings } from '../types';
+
+const trip: TranslationStrings = {
+ 'trip.tabs.plan': 'Programma',
+ 'trip.tabs.transports': 'Trasporti',
+ 'trip.tabs.reservations': 'Prenotazioni',
+ 'trip.tabs.reservationsShort': 'Pren.',
+ 'trip.tabs.packing': 'Lista valigia',
+ 'trip.tabs.packingShort': 'Valigia',
+ 'trip.tabs.lists': 'Liste',
+ 'trip.tabs.listsShort': 'Liste',
+ 'trip.tabs.budget': "Costs",
+ 'trip.tabs.files': 'File',
+ 'trip.loading': 'Caricamento viaggio...',
+ 'trip.mobilePlan': 'Programma',
+ 'trip.mobilePlaces': 'Luoghi',
+ 'trip.toast.placeUpdated': 'Luogo aggiornato',
+ 'trip.toast.placeAdded': 'Luogo aggiunto',
+ 'trip.toast.placeDeleted': 'Luogo eliminato',
+ 'trip.toast.selectDay': 'Seleziona prima un giorno',
+ 'trip.toast.assignedToDay': 'Luogo assegnato al giorno',
+ 'trip.toast.reorderError': 'Impossibile riordinare',
+ 'trip.toast.reservationUpdated': 'Prenotazione aggiornata',
+ 'trip.toast.reservationAdded': 'Prenotazione aggiunta',
+ 'trip.toast.deleted': 'Eliminato',
+ 'trip.confirm.deletePlace': 'Sei sicuro di voler eliminare questo luogo?',
+ 'trip.confirm.deletePlaces': 'Eliminare {count} luoghi?',
+ 'trip.toast.placesDeleted': '{count} luoghi eliminati',
+ 'trip.loadingPhotos': 'Caricamento foto dei luoghi...',
+};
+export default trip;
diff --git a/shared/src/i18n/it/trips.ts b/shared/src/i18n/it/trips.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4b099465a04cc6007e5f59b9aad1793b8a604683
--- /dev/null
+++ b/shared/src/i18n/it/trips.ts
@@ -0,0 +1,17 @@
+import type { TranslationStrings } from '../types';
+
+const trips: TranslationStrings = {
+ 'trips.memberRemoved': '{username} rimosso',
+ 'trips.memberRemoveError': 'Rimozione non riuscita',
+ 'trips.memberAdded': '{username} aggiunto',
+ 'trips.memberAddError': 'Aggiunta non riuscita',
+ 'trips.reminder': 'Promemoria',
+ 'trips.reminderNone': 'Nessuno',
+ 'trips.reminderDay': 'giorno',
+ 'trips.reminderDays': 'giorni',
+ 'trips.reminderCustom': 'Personalizzato',
+ 'trips.reminderDaysBefore': 'giorni prima della partenza',
+ 'trips.reminderDisabledHint':
+ 'I promemoria dei viaggi sono disabilitati. Abilitali in Admin > Impostazioni > Notifiche.',
+};
+export default trips;
diff --git a/shared/src/i18n/it/undo.ts b/shared/src/i18n/it/undo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a037f0ee5a930361c2dd5ce9322e56e3f767d70d
--- /dev/null
+++ b/shared/src/i18n/it/undo.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const undo: TranslationStrings = {
+ 'undo.button': 'Annulla',
+ 'undo.tooltip': 'Annulla: {action}',
+ 'undo.assignPlace': 'Luogo assegnato al giorno',
+ 'undo.removeAssignment': 'Luogo rimosso dal giorno',
+ 'undo.reorder': 'Luoghi riordinati',
+ 'undo.optimize': 'Percorso ottimizzato',
+ 'undo.deletePlace': 'Luogo eliminato',
+ 'undo.deletePlaces': 'Luoghi eliminati',
+ 'undo.moveDay': 'Luogo spostato in altro giorno',
+ 'undo.lock': 'Blocco luogo modificato',
+ 'undo.importGpx': 'Importazione GPX',
+ 'undo.importKeyholeMarkup': 'Importazione KMZ/KML',
+ 'undo.importGoogleList': 'Importazione Google Maps',
+ 'undo.importNaverList': 'Importazione Naver Maps',
+ 'undo.addPlace': 'Luogo aggiunto',
+ 'undo.done': 'Annullato: {action}',
+ 'undo.importBooking': 'Importazione conferma prenotazione',
+};
+export default undo;
diff --git a/shared/src/i18n/it/vacay.ts b/shared/src/i18n/it/vacay.ts
new file mode 100644
index 0000000000000000000000000000000000000000..acca33b094441dd57e45f75aba8e52a7b76557d2
--- /dev/null
+++ b/shared/src/i18n/it/vacay.ts
@@ -0,0 +1,105 @@
+import type { TranslationStrings } from '../types';
+
+const vacay: TranslationStrings = {
+ 'vacay.subtitle': 'Pianifica e gestisci i giorni di ferie',
+ 'vacay.settings': 'Impostazioni',
+ 'vacay.year': 'Anno',
+ 'vacay.addYear': 'Aggiungi anno successivo',
+ 'vacay.addPrevYear': 'Aggiungi anno precedente',
+ 'vacay.removeYear': 'Rimuovi anno',
+ 'vacay.removeYearConfirm': 'Rimuovere {year}?',
+ 'vacay.removeYearHint':
+ 'Tutte le voci delle ferie e le ferie aziendali di questo anno verranno eliminate in modo permanente.',
+ 'vacay.remove': 'Rimuovi',
+ 'vacay.persons': 'Persone',
+ 'vacay.noPersons': 'Nessuna persona aggiunta',
+ 'vacay.addPerson': 'Aggiungi persona',
+ 'vacay.editPerson': 'Modifica persona',
+ 'vacay.removePerson': 'Rimuovi persona',
+ 'vacay.removePersonConfirm': 'Rimuovere {name}?',
+ 'vacay.removePersonHint':
+ 'Tutte le voci delle ferie per questa persona verranno eliminate in modo permanente.',
+ 'vacay.personName': 'Nome',
+ 'vacay.personNamePlaceholder': 'Inserisci nome',
+ 'vacay.color': 'Colore',
+ 'vacay.add': 'Aggiungi',
+ 'vacay.legend': 'Legenda',
+ 'vacay.publicHoliday': 'Festività pubblica',
+ 'vacay.companyHoliday': 'Ferie aziendali',
+ 'vacay.weekend': 'Weekend',
+ 'vacay.modeVacation': 'Ferie',
+ 'vacay.modeCompany': 'Ferie aziendali',
+ 'vacay.entitlement': 'Disponibilità',
+ 'vacay.entitlementDays': 'Giorni',
+ 'vacay.used': 'Usati',
+ 'vacay.remaining': 'Rimanenti',
+ 'vacay.carriedOver': 'dal {year}',
+ 'vacay.blockWeekends': 'Blocca weekend',
+ 'vacay.blockWeekendsHint': 'Impedisci le voci ferie nei giorni del weekend',
+ 'vacay.weekendDays': 'Giorni del weekend',
+ 'vacay.mon': 'Lun',
+ 'vacay.tue': 'Mar',
+ 'vacay.wed': 'Mer',
+ 'vacay.thu': 'Gio',
+ 'vacay.fri': 'Ven',
+ 'vacay.sat': 'Sab',
+ 'vacay.sun': 'Dom',
+ 'vacay.publicHolidays': 'Festività pubbliche',
+ 'vacay.publicHolidaysHint': 'Segna le festività pubbliche nel calendario',
+ 'vacay.selectCountry': 'Seleziona paese',
+ 'vacay.selectRegion': 'Seleziona regione (opzionale)',
+ 'vacay.addCalendar': 'Aggiungi calendario',
+ 'vacay.calendarLabel': 'Etichetta (opzionale)',
+ 'vacay.calendarColor': 'Colore',
+ 'vacay.noCalendars': 'Ancora nessun calendario delle festività aggiunto',
+ 'vacay.companyHolidays': 'Ferie aziendali',
+ 'vacay.companyHolidaysHint': 'Consenti di segnare giorni di ferie aziendali',
+ 'vacay.companyHolidaysNoDeduct':
+ 'Le ferie aziendali non vengono conteggiate nei giorni di ferie.',
+ 'vacay.weekStart': 'La settimana inizia il',
+ 'vacay.weekStartHint':
+ 'Scegli se la settimana inizia il lunedì o la domenica',
+ 'vacay.carryOver': 'Riporto',
+ 'vacay.carryOverHint':
+ "Riporta automaticamente i giorni di ferie rimanenti all'anno successivo",
+ 'vacay.sharing': 'Condivisione',
+ 'vacay.sharingHint': 'Condividi il tuo piano ferie con altri utenti TREK',
+ 'vacay.owner': 'Proprietario',
+ 'vacay.shareEmailPlaceholder': "Email dell'utente TREK",
+ 'vacay.shareSuccess': 'Piano condiviso con successo',
+ 'vacay.shareError': 'Impossibile condividere il piano',
+ 'vacay.dissolve': 'Sciogli unione',
+ 'vacay.dissolveHint':
+ 'Separa di nuovo i calendari. Le tue voci verranno mantenute.',
+ 'vacay.dissolveAction': 'Sciogli',
+ 'vacay.dissolved': 'Calendario separato',
+ 'vacay.fusedWith': 'Unito con',
+ 'vacay.you': 'tu',
+ 'vacay.noData': 'Nessun dato',
+ 'vacay.changeColor': 'Cambia colore',
+ 'vacay.inviteUser': 'Invita utente',
+ 'vacay.inviteHint':
+ 'Invita un altro utente TREK a condividere un calendario ferie combinato.',
+ 'vacay.selectUser': 'Seleziona utente',
+ 'vacay.sendInvite': 'Invia invito',
+ 'vacay.inviteSent': 'Invito inviato',
+ 'vacay.inviteError': "Impossibile inviare l'invito",
+ 'vacay.pending': 'in attesa',
+ 'vacay.noUsersAvailable': 'Nessun utente disponibile',
+ 'vacay.accept': 'Accetta',
+ 'vacay.decline': 'Rifiuta',
+ 'vacay.acceptFusion': 'Accetta e unisci',
+ 'vacay.inviteTitle': 'Richiesta di unione',
+ 'vacay.inviteWantsToFuse': 'vuole condividere con te un calendario ferie.',
+ 'vacay.fuseInfo1':
+ 'Entrambi vedrete tutte le voci ferie in un unico calendario condiviso.',
+ 'vacay.fuseInfo2':
+ 'Entrambe le parti possono creare e modificare le voci reciproche.',
+ 'vacay.fuseInfo3':
+ 'Entrambe le parti possono eliminare le voci e modificare le disponibilità ferie.',
+ 'vacay.fuseInfo4':
+ 'Le impostazioni come festività pubbliche e ferie aziendali sono condivise.',
+ 'vacay.fuseInfo5':
+ "L'unione può essere sciolta in qualsiasi momento da una delle due parti. Le tue voci verranno preservate.",
+};
+export default vacay;
diff --git a/shared/src/i18n/ja/admin.ts b/shared/src/i18n/ja/admin.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a1345f423fd3c11c774de224de28415241eb984c
--- /dev/null
+++ b/shared/src/i18n/ja/admin.ts
@@ -0,0 +1,369 @@
+import type { TranslationStrings } from '../types';
+
+const admin: TranslationStrings = {
+ 'admin.notifications.title': '通知',
+ 'admin.notifications.hint':
+ '通知チャネルを1つ選択してください。同時に有効にできるのは1つだけです。',
+ 'admin.notifications.none': '無効',
+ 'admin.notifications.email': 'メール(SMTP)',
+ 'admin.notifications.webhook': 'Webhook',
+ 'admin.notifications.ntfy': 'Ntfy',
+ 'admin.ntfy.hint':
+ 'ユーザーが独自のntfyトピックを設定できるようにします。下で既定サーバーを設定してください。',
+ 'admin.notifications.save': '通知設定を保存',
+ 'admin.notifications.saved': '通知設定を保存しました',
+ 'admin.notifications.testWebhook': 'Webhookテスト送信',
+ 'admin.notifications.testWebhookSuccess': 'テストWebhookを送信しました',
+ 'admin.notifications.testWebhookFailed': 'テストWebhookに失敗しました',
+ 'admin.notifications.testNtfy': 'ntfyテスト送信',
+ 'admin.notifications.testNtfySuccess': 'テストntfyを送信しました',
+ 'admin.notifications.testNtfyFailed': 'テストntfyに失敗しました',
+ 'admin.notifications.emailPanel.title': 'メール(SMTP)',
+ 'admin.notifications.webhookPanel.title': 'Webhook',
+ 'admin.notifications.inappPanel.title': 'アプリ内',
+ 'admin.notifications.inappPanel.hint':
+ 'アプリ内通知は常に有効で、全体では無効にできません。',
+ 'admin.notifications.adminWebhookPanel.title': '管理者Webhook',
+ 'admin.notifications.adminWebhookPanel.hint':
+ '管理者通知専用のWebhookです(例:バージョン通知)。常に送信されます。',
+ 'admin.notifications.adminWebhookPanel.saved':
+ '管理者Webhook URLを保存しました',
+ 'admin.notifications.adminWebhookPanel.testSuccess':
+ 'テストWebhookを送信しました',
+ 'admin.notifications.adminWebhookPanel.testFailed':
+ 'テストWebhookに失敗しました',
+ 'admin.notifications.adminWebhookPanel.alwaysOnHint':
+ 'URLが設定されていると常に送信されます',
+ 'admin.notifications.adminNtfyPanel.title': '管理者Ntfy',
+ 'admin.notifications.adminNtfyPanel.hint':
+ '管理者通知専用のntfyトピックです。',
+ 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy サーバーURL',
+ 'admin.notifications.adminNtfyPanel.serverHint':
+ 'ユーザー通知の既定サーバーとしても使用されます。',
+ 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh',
+ 'admin.notifications.adminNtfyPanel.topicLabel': '管理者トピック',
+ 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts',
+ 'admin.notifications.adminNtfyPanel.tokenLabel': 'アクセストークン(任意)',
+ 'admin.notifications.adminNtfyPanel.tokenCleared':
+ '管理者アクセストークンを削除しました',
+ 'admin.notifications.adminNtfyPanel.saved': '管理者ntfy設定を保存しました',
+ 'admin.notifications.adminNtfyPanel.test': 'ntfyテスト送信',
+ 'admin.notifications.adminNtfyPanel.testSuccess': 'テストntfyを送信しました',
+ 'admin.notifications.adminNtfyPanel.testFailed': 'テストntfyに失敗しました',
+ 'admin.notifications.adminNtfyPanel.alwaysOnHint':
+ 'トピック設定時は常に送信されます',
+ 'admin.notifications.adminNotificationsHint':
+ '管理者専用通知の配信先を設定します。',
+ 'admin.notifications.tripReminders.title': '旅行リマインダー',
+ 'admin.notifications.tripReminders.hint':
+ '旅行開始前に通知を送信します(旅行側の設定が必要)。',
+ 'admin.notifications.tripReminders.enabled': '旅行リマインダー有効',
+ 'admin.notifications.tripReminders.disabled': '旅行リマインダー無効',
+ 'admin.smtp.title': 'メール・通知',
+ 'admin.smtp.hint': 'メール通知送信用のSMTP設定。',
+ 'admin.smtp.testButton': 'テストメール送信',
+ 'admin.webhook.hint': 'ユーザーが独自のWebhook URLを設定できるようにします。',
+ 'admin.smtp.testSuccess': 'テストメールを送信しました',
+ 'admin.smtp.testFailed': 'テストメールに失敗しました',
+ 'admin.title': '管理',
+ 'admin.subtitle': 'ユーザー管理とシステム設定',
+ 'admin.tabs.users': 'ユーザー',
+ 'admin.tabs.categories': 'カテゴリ',
+ 'admin.tabs.backup': 'バックアップ',
+ 'admin.tabs.notifications': '通知',
+ 'admin.tabs.audit': '監査',
+ 'admin.stats.users': 'ユーザー',
+ 'admin.stats.trips': '旅行',
+ 'admin.stats.places': '場所',
+ 'admin.stats.photos': '写真',
+ 'admin.stats.files': 'ファイル',
+ 'admin.table.user': 'ユーザー',
+ 'admin.table.email': 'メール',
+ 'admin.table.role': '権限',
+ 'admin.table.created': '作成日',
+ 'admin.table.lastLogin': '最終ログイン',
+ 'admin.table.actions': '操作',
+ 'admin.you': '(あなた)',
+ 'admin.editUser': 'ユーザーを編集',
+ 'admin.newPassword': '新しいパスワード',
+ 'admin.newPasswordHint': '空欄の場合は現在のパスワードを維持',
+ 'admin.deleteUser':
+ 'ユーザー「{name}」を削除しますか?すべての旅行が完全に削除されます。',
+ 'admin.deleteUserTitle': 'ユーザー削除',
+ 'admin.newPasswordPlaceholder': '新しいパスワードを入力…',
+ 'admin.toast.loadError': '管理データの読み込みに失敗しました',
+ 'admin.toast.userUpdated': 'ユーザーを更新しました',
+ 'admin.toast.updateError': '更新に失敗しました',
+ 'admin.toast.userDeleted': 'ユーザーを削除しました',
+ 'admin.toast.deleteError': '削除に失敗しました',
+ 'admin.toast.cannotDeleteSelf': '自分のアカウントは削除できません',
+ 'admin.toast.userCreated': 'ユーザーを作成しました',
+ 'admin.toast.createError': 'ユーザー作成に失敗しました',
+ 'admin.toast.fieldsRequired': 'ユーザー名、メール、パスワードは必須です',
+ 'admin.createUser': 'ユーザーを作成',
+ 'admin.invite.title': '招待リンク',
+ 'admin.invite.subtitle': '使い切り登録リンクを作成',
+ 'admin.invite.create': 'リンク作成',
+ 'admin.invite.createAndCopy': '作成してコピー',
+ 'admin.invite.empty': '招待リンクがまだありません',
+ 'admin.invite.maxUses': '最大使用回数',
+ 'admin.invite.expiry': '有効期限',
+ 'admin.invite.uses': '使用済み',
+ 'admin.invite.expiresAt': '期限',
+ 'admin.invite.createdBy': '作成者',
+ 'admin.invite.active': '有効',
+ 'admin.invite.expired': '期限切れ',
+ 'admin.invite.usedUp': '使用済み',
+ 'admin.invite.copied': '招待リンクをコピーしました',
+ 'admin.invite.copyLink': 'リンクをコピー',
+ 'admin.invite.deleted': '招待リンクを削除しました',
+ 'admin.invite.createError': '招待リンクの作成に失敗しました',
+ 'admin.invite.deleteError': '招待リンクの削除に失敗しました',
+ 'admin.tabs.settings': '設定',
+ 'admin.allowRegistration': '登録を許可',
+ 'admin.allowRegistrationHint': '新規ユーザーの自己登録を許可します',
+ 'admin.authMethods': '認証方法',
+ 'admin.passwordLogin': 'パスワードログイン',
+ 'admin.passwordLoginHint': 'メールとパスワードでのログインを許可',
+ 'admin.passwordRegistration': 'パスワード登録',
+ 'admin.passwordRegistrationHint': 'メールとパスワードでの新規登録を許可',
+ 'admin.oidcLogin': 'SSOログイン',
+ 'admin.oidcLoginHint': 'SSOでのログインを許可',
+ 'admin.oidcRegistration': 'SSO自動登録',
+ 'admin.oidcRegistrationHint': '新しいSSOユーザーを自動作成',
+ 'admin.envOverrideHint':
+ 'パスワードログイン設定は OIDC_ONLY 環境変数で制御されています。',
+ 'admin.lockoutWarning': '少なくとも1つのログイン方法を有効にしてください',
+ 'admin.requireMfa': '二要素認証(2FA)を必須にする',
+ 'admin.requireMfaHint': '2FA未設定のユーザーは、利用前に設定が必要です。',
+ 'admin.apiKeys': 'APIキー',
+ 'admin.apiKeysHint': '任意。写真や天気などの拡張データを有効化します。',
+ 'admin.mapsKey': 'Google Maps APIキー',
+ 'admin.mapsKeyHint': '場所検索に必要。console.cloud.google.com で取得',
+ 'admin.mapsKeyHintLong':
+ 'APIキーなしではOpenStreetMapを使用します。Google APIキーがあれば写真、評価、営業時間も表示できます。',
+ 'admin.recommended': '推奨',
+ 'admin.weatherKey': 'OpenWeatherMap APIキー',
+ 'admin.weatherKeyHint': '天気データ用。openweathermap.org で無料',
+ 'admin.validateKey': 'テスト',
+ 'admin.keyValid': '接続済み',
+ 'admin.keyInvalid': '無効',
+ 'admin.keySaved': 'APIキーを保存しました',
+ 'admin.oidcTitle': 'シングルサインオン(OIDC)',
+ 'admin.oidcSubtitle':
+ 'Google、Apple、Authentik、Keycloak などでログインを許可します。',
+ 'admin.oidcDisplayName': '表示名',
+ 'admin.oidcIssuer': 'Issuer URL',
+ 'admin.oidcIssuerHint':
+ 'OpenID ConnectのIssuer URL(例:https://accounts.google.com)',
+ 'admin.oidcSaved': 'OIDC設定を保存しました',
+ 'admin.oidcOnlyMode': 'パスワード認証を無効化',
+ 'admin.oidcOnlyModeHint':
+ '有効にするとSSOのみ使用可能になり、パスワードログインと登録は無効になります。',
+ 'admin.fileTypes': '許可するファイル形式',
+ 'admin.fileTypesHint':
+ 'ユーザーがアップロードできるファイル形式を設定します。',
+ 'admin.fileTypesFormat':
+ '拡張子をカンマ区切り(例:jpg,png,pdf,doc)。すべて許可する場合は *。',
+ 'admin.fileTypesSaved': 'ファイル形式の設定を保存しました',
+ 'admin.placesPhotos.title': '場所の写真',
+ 'admin.placesPhotos.subtitle':
+ 'Google Places APIから写真を取得します。APIクォータ節約のため無効にできます。Wikimediaの写真には影響しません。',
+ 'admin.placesAutocomplete.title': '場所のオートコンプリート',
+ 'admin.placesAutocomplete.subtitle':
+ '検索候補にGoogle Places APIを使用します。APIクォータ節約のため無効にできます。',
+ 'admin.placesDetails.title': '場所の詳細',
+ 'admin.placesDetails.subtitle':
+ 'Google Places APIから営業時間、評価、ウェブサイトなどの詳細情報を取得します。APIクォータ節約のため無効にできます。',
+ 'admin.bagTracking.title': 'バッグ管理',
+ 'admin.bagTracking.subtitle': '持ち物の重量とバッグ割り当てを有効化',
+ 'admin.collab.chat.title': 'チャット',
+ 'admin.collab.chat.subtitle': '旅行の共同作業用リアルタイムメッセージ',
+ 'admin.collab.notes.title': 'ノート',
+ 'admin.collab.notes.subtitle': '共有ノートとドキュメント',
+ 'admin.collab.polls.title': '投票',
+ 'admin.collab.polls.subtitle': 'グループ投票・アンケート',
+ 'admin.collab.whatsnext.title': '次にすること',
+ 'admin.collab.whatsnext.subtitle': 'アクティビティ提案と次のステップ',
+ 'admin.tabs.config': 'カスタマイズ',
+ 'admin.tabs.defaults': 'ユーザーのデフォルト',
+ 'admin.defaultSettings.title': '既定のユーザー設定',
+ 'admin.defaultSettings.description':
+ 'インスタンス全体の既定値を設定します。設定を変更していないユーザーにはこれらの値が適用されます。各ユーザーの設定変更は常に優先されます。',
+ 'admin.defaultSettings.saved': '既定値を保存しました',
+ 'admin.defaultSettings.reset': '組み込みの既定値に戻す',
+ 'admin.defaultSettings.resetToBuiltIn': 'リセット',
+ 'admin.tabs.templates': '持ち物テンプレート',
+ 'admin.packingTemplates.title': '持ち物テンプレート',
+ 'admin.packingTemplates.subtitle': '旅行で使い回せる持ち物リストを作成',
+ 'admin.packingTemplates.create': '新規テンプレート',
+ 'admin.packingTemplates.namePlaceholder': 'テンプレート名(例:ビーチ旅行)',
+ 'admin.packingTemplates.empty': 'テンプレートはまだありません',
+ 'admin.packingTemplates.items': 'アイテム',
+ 'admin.packingTemplates.categories': 'カテゴリ',
+ 'admin.packingTemplates.itemName': 'アイテム名',
+ 'admin.packingTemplates.itemCategory': 'カテゴリ',
+ 'admin.packingTemplates.categoryName': 'カテゴリ名(例:衣類)',
+ 'admin.packingTemplates.addCategory': 'カテゴリを追加',
+ 'admin.packingTemplates.created': 'テンプレートを作成しました',
+ 'admin.packingTemplates.deleted': 'テンプレートを削除しました',
+ 'admin.packingTemplates.loadError': 'テンプレートの読み込みに失敗しました',
+ 'admin.packingTemplates.createError': 'テンプレートの作成に失敗しました',
+ 'admin.packingTemplates.deleteError': 'テンプレートの削除に失敗しました',
+ 'admin.packingTemplates.saveError': '保存に失敗しました',
+ 'admin.tabs.addons': 'アドオン',
+ 'admin.addons.title': 'アドオン',
+ 'admin.addons.subtitle': '機能を有効/無効にしてTREKをカスタマイズします。',
+ 'admin.addons.catalog.packing.name': 'リスト',
+ 'admin.addons.catalog.packing.description': '旅行用の持ち物リストとToDo',
+ 'admin.addons.catalog.budget.name': '予算',
+ 'admin.addons.catalog.budget.description': '支出の管理と予算計画',
+ 'admin.addons.catalog.documents.name': 'ドキュメント',
+ 'admin.addons.catalog.documents.description': '旅行書類の保存・管理',
+ 'admin.addons.catalog.vacay.name': 'Vacay',
+ 'admin.addons.catalog.vacay.description':
+ 'カレンダー表示の個人休暇プランナー',
+ 'admin.addons.catalog.atlas.name': 'Atlas',
+ 'admin.addons.catalog.atlas.description': '訪問国と旅行統計の世界地図',
+ 'admin.addons.catalog.collab.name': 'Collab',
+ 'admin.addons.catalog.collab.description':
+ 'リアルタイムのメモ、投票、チャット',
+ 'admin.addons.catalog.memories.name': '写真(Immich)',
+ 'admin.addons.catalog.memories.description': 'Immichで旅行写真を共有',
+ 'admin.addons.catalog.mcp.name': 'MCP',
+ 'admin.addons.catalog.mcp.description':
+ 'AI連携のためのModel Context Protocol',
+ 'admin.addons.subtitleBefore': '機能を有効/無効にして ',
+ 'admin.addons.subtitleAfter': ' をカスタマイズします。',
+ 'admin.addons.enabled': '有効',
+ 'admin.addons.disabled': '無効',
+ 'admin.addons.type.trip': '旅行',
+ 'admin.addons.type.global': '全体',
+ 'admin.addons.type.integration': '連携',
+ 'admin.addons.tripHint': '各旅行内のタブとして利用可能',
+ 'admin.addons.globalHint': 'メインナビの独立セクションとして利用可能',
+ 'admin.addons.integrationHint': '専用ページのないバックエンド/API連携',
+ 'admin.addons.toast.updated': 'アドオンを更新しました',
+ 'admin.addons.toast.error': 'アドオンの更新に失敗しました',
+ 'admin.addons.noAddons': '利用可能なアドオンはありません',
+ 'admin.weather.title': '天気データ',
+ 'admin.weather.badge': '2026年3月24日以降',
+ 'admin.weather.description':
+ 'TREKは天気データにOpen‑Meteoを使用しています。無料でオープンソース、APIキーは不要です。',
+ 'admin.weather.forecast': '16日間予報',
+ 'admin.weather.forecastDesc': '以前は5日(OpenWeatherMap)',
+ 'admin.weather.climate': '過去の気候データ',
+ 'admin.weather.climateDesc': '16日以降は過去85年の平均値',
+ 'admin.weather.requests': '1日10,000リクエスト',
+ 'admin.weather.requestsDesc': '無料、APIキー不要',
+ 'admin.weather.locationHint':
+ '各日の座標付き最初の場所を基準にします。場所が未割り当ての場合は、場所一覧の任意の場所を参照します。',
+ 'admin.tabs.mcpTokens': 'MCPトークン',
+ 'admin.mcpTokens.title': 'MCPトークン',
+ 'admin.mcpTokens.subtitle': 'すべてのユーザーのAPIトークンを管理',
+ 'admin.mcpTokens.sectionTitle': 'API トークン',
+ 'admin.mcpTokens.owner': '所有者',
+ 'admin.mcpTokens.tokenName': 'トークン名',
+ 'admin.mcpTokens.created': '作成日',
+ 'admin.mcpTokens.lastUsed': '最終使用',
+ 'admin.mcpTokens.never': '未使用',
+ 'admin.mcpTokens.empty': 'MCPトークンはまだ作成されていません',
+ 'admin.mcpTokens.deleteTitle': 'トークンを削除',
+ 'admin.mcpTokens.deleteMessage':
+ 'このトークンは即座に失効します。ユーザーはこのトークン経由のMCPアクセスを失います。',
+ 'admin.mcpTokens.deleteSuccess': 'トークンを削除しました',
+ 'admin.mcpTokens.deleteError': 'トークンの削除に失敗しました',
+ 'admin.mcpTokens.loadError': 'トークンの読み込みに失敗しました',
+ 'admin.oauthSessions.sectionTitle': 'OAuthセッション',
+ 'admin.oauthSessions.clientName': 'クライアント',
+ 'admin.oauthSessions.owner': '所有者',
+ 'admin.oauthSessions.scopes': 'スコープ',
+ 'admin.oauthSessions.created': '作成日時',
+ 'admin.oauthSessions.empty': '有効なOAuthセッションはありません',
+ 'admin.oauthSessions.revokeTitle': 'セッションを無効化',
+ 'admin.oauthSessions.revokeMessage':
+ 'このOAuthセッションは即時無効化され、クライアントはMCPへのアクセスを失います。',
+ 'admin.oauthSessions.revokeSuccess': 'セッションを無効化しました',
+ 'admin.oauthSessions.revokeError': 'セッションの無効化に失敗しました',
+ 'admin.oauthSessions.loadError': 'OAuthセッションの読み込みに失敗しました',
+ 'admin.tabs.github': 'GitHub',
+ 'admin.audit.subtitle':
+ 'セキュリティおよび管理イベント(バックアップ、ユーザー、MFA、設定)。',
+ 'admin.audit.empty': '監査ログはまだありません。',
+ 'admin.audit.refresh': '更新',
+ 'admin.audit.loadMore': 'さらに読み込む',
+ 'admin.audit.showing': '{count}件表示 · 全{total}件',
+ 'admin.audit.col.time': '時刻',
+ 'admin.audit.col.user': 'ユーザー',
+ 'admin.audit.col.action': '操作',
+ 'admin.audit.col.resource': '対象',
+ 'admin.audit.col.ip': 'IP',
+ 'admin.audit.col.details': '詳細',
+ 'admin.github.title': 'リリース履歴',
+ 'admin.github.subtitle': '{repo} の最新アップデート',
+ 'admin.github.latest': '最新',
+ 'admin.github.prerelease': 'プレリリース',
+ 'admin.github.showDetails': '詳細を表示',
+ 'admin.github.hideDetails': '詳細を非表示',
+ 'admin.github.loadMore': 'さらに読み込む',
+ 'admin.github.loading': '読み込み中...',
+ 'admin.github.error': 'リリースの読み込みに失敗しました',
+ 'admin.github.by': '作成者',
+ 'admin.github.support': 'TREKの開発を支援',
+ 'admin.update.available': '更新があります',
+ 'admin.update.text':
+ 'TREK {version} が利用可能です。現在は {current} を使用しています。',
+ 'admin.update.button': 'GitHubで見る',
+ 'admin.update.install': '更新をインストール',
+ 'admin.update.confirmTitle': '更新をインストールしますか?',
+ 'admin.update.confirmText':
+ 'TREKを {current} から {version} に更新します。更新後、サーバーは自動的に再起動します。',
+ 'admin.update.dataInfo':
+ 'すべてのデータ(旅行、ユーザー、APIキー、アップロード、Vacay、Atlas、予算)は保持されます。',
+ 'admin.update.warning': '再起動中、アプリは短時間利用できません。',
+ 'admin.update.confirm': '今すぐ更新',
+ 'admin.update.installing': '更新中…',
+ 'admin.update.success': '更新が完了しました!サーバーを再起動しています…',
+ 'admin.update.failed': '更新に失敗しました',
+ 'admin.update.backupHint':
+ '更新前にバックアップを作成することをおすすめします。',
+ 'admin.update.backupLink': 'バックアップへ',
+ 'admin.update.howTo': '更新方法',
+ 'admin.update.dockerText':
+ 'TREKはDockerで実行されています。{version} に更新するには、サーバーで次のコマンドを実行してください:',
+ 'admin.update.reloadHint': '数秒後にページを再読み込みしてください。',
+ 'admin.tabs.permissions': '権限',
+ 'admin.addons.catalog.journey.name': '日記',
+ 'admin.addons.catalog.journey.description':
+ 'チェックイン、写真、日ごとのストーリーで旅を記録',
+ 'admin.passkey.title': 'パスキーログイン',
+ 'admin.passkey.cardHint':
+ 'ユーザーがパスキー(WebAuthn)でサインインできるようにします。既定では無効です。',
+ 'admin.passkey.login': 'パスキーログインを有効化',
+ 'admin.passkey.loginHint':
+ '「パスキーでサインイン」オプションを表示し、ユーザーが設定でパスキーを登録できるようにします。',
+ 'admin.passkey.notConfigured':
+ 'このデプロイにはまだ有効な WebAuthn ドメインがありません。下の APP_URL または Relying Party ID を設定してください。それまでパスキーは表示されません。',
+ 'admin.passkey.rpId': 'Relying Party ID(ドメイン)',
+ 'admin.passkey.rpIdHint':
+ 'パスキーが紐づくドメイン名のみ(例:trek.example.org)。空欄の場合は APP_URL から導出されます。後で変更すると既存のパスキーは無効になります。',
+ 'admin.passkey.origins': '許可するオリジン',
+ 'admin.passkey.originsHint':
+ 'カンマ区切りの完全なオリジン(例:https://trek.example.org)。空欄の場合は APP_URL を使用します。',
+ 'admin.passkey.reset': 'パスキーをリセット',
+ 'admin.passkey.resetHint':
+ 'このユーザーのパスキーをすべて削除します(例:デバイスを紛失した場合)。パスワードでのサインインは引き続き可能です。',
+ 'admin.passkey.resetConfirm': '{name} のパスキーをすべて削除しますか?',
+ 'admin.passkey.resetDone': '{count} 件のパスキーを削除しました',
+ 'admin.defaultSettings.mapProvider': '地図エンジン',
+ 'admin.defaultSettings.mapProviderHint': 'このインスタンスの全員に適用される既定の地図です。各ユーザーは自分の設定でこれを上書きできます。',
+ 'admin.defaultSettings.providerLeaflet': '標準(無料)',
+ 'admin.defaultSettings.providerMapbox': 'Mapbox(3D)',
+ 'admin.defaultSettings.mapboxToken': '共有 Mapbox トークン',
+ 'admin.defaultSettings.mapboxTokenHint': '自分のトークンを入力していないすべてのユーザーに使用されます。これにより、キーを個別に共有しなくてもインスタンス全体で Mapbox を利用できます。暗号化して保存されます。',
+ 'admin.defaultSettings.mapboxStyle': '地図スタイル',
+ 'admin.defaultSettings.mapboxStylePlaceholder': 'スタイルを選択…',
+ 'admin.defaultSettings.mapbox3d': '3D の建物と地形',
+ 'admin.defaultSettings.mapboxQuality': '高品質モード',
+};
+export default admin;
diff --git a/shared/src/i18n/ja/airport.ts b/shared/src/i18n/ja/airport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ac37c3e8d3189be3060dcf09433a6da3d837dfef
--- /dev/null
+++ b/shared/src/i18n/ja/airport.ts
@@ -0,0 +1,6 @@
+import type { TranslationStrings } from '../types';
+
+const airport: TranslationStrings = {
+ 'airport.searchPlaceholder': '空港コードまたは都市名(例:FRA)',
+};
+export default airport;
diff --git a/shared/src/i18n/ja/atlas.ts b/shared/src/i18n/ja/atlas.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4599557c2e75d01cfcf7f7f7cbde2f6d9e56ee51
--- /dev/null
+++ b/shared/src/i18n/ja/atlas.ts
@@ -0,0 +1,58 @@
+import type { TranslationStrings } from '../types';
+
+const atlas: TranslationStrings = {
+ 'atlas.subtitle': '世界に広がるあなたの旅の足跡',
+ 'atlas.countries': '国',
+ 'atlas.trips': '旅行',
+ 'atlas.places': '場所',
+ 'atlas.unmark': '削除',
+ 'atlas.confirmMark': 'この国を訪問済みにしますか?',
+ 'atlas.confirmUnmark': 'この国を訪問済みリストから削除しますか?',
+ 'atlas.confirmUnmarkRegion': 'この地域を訪問済みリストから削除しますか?',
+ 'atlas.markVisited': '訪問済みにする',
+ 'atlas.markVisitedHint': 'この国を訪問済みリストに追加',
+ 'atlas.markRegionVisitedHint': 'この地域を訪問済みリストに追加',
+ 'atlas.addToBucket': '行きたいリストに追加',
+ 'atlas.addPoi': '場所を追加',
+ 'atlas.searchCountry': '国を検索...',
+ 'atlas.bucketNamePlaceholder': '名前(国・都市・場所など)',
+ 'atlas.month': '月',
+ 'atlas.year': '年',
+ 'atlas.addToBucketHint': '行きたい場所として保存',
+ 'atlas.bucketWhen': '訪問予定はいつですか?',
+ 'atlas.statsTab': '統計',
+ 'atlas.bucketTab': '行きたいリスト',
+ 'atlas.addBucket': '行きたいリストに追加',
+ 'atlas.bucketNotesPlaceholder': 'メモ(任意)',
+ 'atlas.bucketEmpty': '行きたいリストは空です',
+ 'atlas.bucketEmptyHint': '行ってみたい場所を追加しましょう',
+ 'atlas.days': '日',
+ 'atlas.visitedCountries': '訪問国',
+ 'atlas.cities': '都市',
+ 'atlas.noData': '旅行データがありません',
+ 'atlas.noDataHint': '旅行を作成して場所を追加すると、マップに表示されます',
+ 'atlas.lastTrip': '前回の旅行',
+ 'atlas.nextTrip': '次の旅行',
+ 'atlas.daysLeft': '残り日数',
+ 'atlas.streak': '連続',
+ 'atlas.years': '年',
+ 'atlas.yearInRow': '年連続',
+ 'atlas.yearsInRow': '年連続',
+ 'atlas.tripIn': '旅行',
+ 'atlas.tripsIn': '旅行',
+ 'atlas.since': '開始',
+ 'atlas.europe': 'ヨーロッパ',
+ 'atlas.asia': 'アジア',
+ 'atlas.northAmerica': '北アメリカ',
+ 'atlas.southAmerica': '南アメリカ',
+ 'atlas.africa': 'アフリカ',
+ 'atlas.oceania': 'オセアニア',
+ 'atlas.other': 'その他',
+ 'atlas.firstVisit': '最初の旅行',
+ 'atlas.lastVisitLabel': '最後の旅行',
+ 'atlas.tripSingular': '旅行',
+ 'atlas.tripPlural': '旅行',
+ 'atlas.placeVisited': '訪問した場所',
+ 'atlas.placesVisited': '訪問した場所',
+};
+export default atlas;
diff --git a/shared/src/i18n/ja/backup.ts b/shared/src/i18n/ja/backup.ts
new file mode 100644
index 0000000000000000000000000000000000000000..148436bd748ea127718637387f70262725ae9765
--- /dev/null
+++ b/shared/src/i18n/ja/backup.ts
@@ -0,0 +1,77 @@
+import type { TranslationStrings } from '../types';
+
+const backup: TranslationStrings = {
+ 'backup.title': 'データバックアップ',
+ 'backup.subtitle': 'データベースとアップロードされたすべてのファイル',
+ 'backup.refresh': '更新',
+ 'backup.upload': 'バックアップをアップロード',
+ 'backup.uploading': 'アップロード中…',
+ 'backup.create': 'バックアップを作成',
+ 'backup.creating': '作成中…',
+ 'backup.empty': 'バックアップはまだありません',
+ 'backup.createFirst': '最初のバックアップを作成',
+ 'backup.download': 'ダウンロード',
+ 'backup.restore': '復元',
+ 'backup.confirm.restore':
+ 'バックアップ「{name}」を復元しますか?\n\n現在のすべてのデータはバックアップで置き換えられます。',
+ 'backup.confirm.uploadRestore':
+ 'バックアップファイル「{name}」をアップロードして復元しますか?\n\n現在のすべてのデータは上書きされます。',
+ 'backup.confirm.delete': 'バックアップ「{name}」を削除しますか?',
+ 'backup.toast.loadError': 'バックアップの読み込みに失敗しました',
+ 'backup.toast.created': 'バックアップを作成しました',
+ 'backup.toast.createError': 'バックアップの作成に失敗しました',
+ 'backup.toast.restored':
+ 'バックアップを復元しました。ページを再読み込みします…',
+ 'backup.toast.restoreError': '復元に失敗しました',
+ 'backup.toast.uploadError': 'アップロードに失敗しました',
+ 'backup.toast.deleted': 'バックアップを削除しました',
+ 'backup.toast.deleteError': '削除に失敗しました',
+ 'backup.toast.downloadError': 'ダウンロードに失敗しました',
+ 'backup.toast.settingsSaved': '自動バックアップ設定を保存しました',
+ 'backup.toast.settingsError': '設定の保存に失敗しました',
+ 'backup.auto.title': '自動バックアップ',
+ 'backup.auto.subtitle': 'スケジュールに基づいて自動実行',
+ 'backup.auto.enable': '自動バックアップを有効化',
+ 'backup.auto.enableHint': '選択したスケジュールで自動的に作成されます',
+ 'backup.auto.interval': '間隔',
+ 'backup.auto.hour': '実行時刻',
+ 'backup.auto.hourHint': 'サーバーのローカル時刻({format}形式)',
+ 'backup.auto.dayOfWeek': '曜日',
+ 'backup.auto.dayOfMonth': '月の日',
+ 'backup.auto.dayOfMonthHint':
+ 'すべての月に対応するため1~28に制限されています',
+ 'backup.auto.scheduleSummary': 'スケジュール',
+ 'backup.auto.summaryDaily': '毎日 {hour}:00',
+ 'backup.auto.summaryWeekly': '毎週{day} {hour}:00',
+ 'backup.auto.summaryMonthly': '毎月{day}日 {hour}:00',
+ 'backup.auto.envLocked': 'Docker',
+ 'backup.auto.envLockedHint':
+ '自動バックアップはDockerの環境変数で設定されています。変更するにはdocker-compose.ymlを更新し、コンテナを再起動してください。',
+ 'backup.auto.copyEnv': 'Docker環境変数をコピー',
+ 'backup.auto.envCopied': 'Docker環境変数をコピーしました',
+ 'backup.auto.keepLabel': '古いバックアップを削除',
+ 'backup.dow.sunday': '日',
+ 'backup.dow.monday': '月',
+ 'backup.dow.tuesday': '火',
+ 'backup.dow.wednesday': '水',
+ 'backup.dow.thursday': '木',
+ 'backup.dow.friday': '金',
+ 'backup.dow.saturday': '土',
+ 'backup.interval.hourly': '毎時間',
+ 'backup.interval.daily': '毎日',
+ 'backup.interval.weekly': '毎週',
+ 'backup.interval.monthly': '毎月',
+ 'backup.keep.1day': '1日',
+ 'backup.keep.3days': '3日',
+ 'backup.keep.7days': '7日',
+ 'backup.keep.14days': '14日',
+ 'backup.keep.30days': '30日',
+ 'backup.keep.forever': '無期限',
+ 'backup.restoreConfirmTitle': 'バックアップを復元しますか?',
+ 'backup.restoreWarning':
+ '現在のすべてのデータ(旅行、場所、ユーザー、アップロード)はバックアップで完全に置き換えられます。この操作は元に戻せません。',
+ 'backup.restoreTip':
+ 'ヒント:復元前に現在の状態をバックアップすることをおすすめします。',
+ 'backup.restoreConfirm': 'はい、復元します',
+};
+export default backup;
diff --git a/shared/src/i18n/ja/budget.ts b/shared/src/i18n/ja/budget.ts
new file mode 100644
index 0000000000000000000000000000000000000000..77e39b49b5bc76b6e9b7b438027d0135e802399a
--- /dev/null
+++ b/shared/src/i18n/ja/budget.ts
@@ -0,0 +1,115 @@
+import type { TranslationStrings } from '../types';
+
+const budget: TranslationStrings = {
+ 'budget.title': '予算',
+ 'budget.exportCsv': 'CSVを書き出し',
+ 'budget.emptyTitle': '予算はまだありません',
+ 'budget.emptyText': 'カテゴリと項目を作成して旅行予算を計画しましょう',
+ 'budget.emptyPlaceholder': 'カテゴリ名を入力...',
+ 'budget.createCategory': 'カテゴリを作成',
+ 'budget.category': 'カテゴリ',
+ 'budget.categoryName': 'カテゴリ名',
+ 'budget.table.name': '名前',
+ 'budget.table.total': '合計',
+ 'budget.table.persons': '人数',
+ 'budget.table.days': '日数',
+ 'budget.table.perPerson': '1人あたり',
+ 'budget.table.perDay': '1日あたり',
+ 'budget.table.perPersonDay': '人/日',
+ 'budget.table.note': 'メモ',
+ 'budget.table.date': '日付',
+ 'budget.newEntry': '新しい項目',
+ 'budget.defaultEntry': '新しい項目',
+ 'budget.defaultCategory': '新しいカテゴリ',
+ 'budget.total': '合計',
+ 'budget.totalBudget': '総予算',
+ 'budget.byCategory': 'カテゴリ別',
+ 'budget.editTooltip': 'クリックして編集',
+ 'budget.linkedToReservation': '予約に連携中 — 名前はそちらで編集してください',
+ 'budget.confirm.deleteCategory':
+ '{count}件の項目があるカテゴリ「{name}」を削除しますか?',
+ 'budget.deleteCategory': 'カテゴリを削除',
+ 'budget.perPerson': '1人あたり',
+ 'budget.paid': '支払済み',
+ 'budget.open': '未精算',
+ 'budget.noMembers': 'メンバー未割り当て',
+ 'budget.settlement': '精算',
+ 'budget.settlementInfo':
+ '予算項目のメンバーアイコンをクリックして緑にすると、支払い済みを示します。精算では、誰が誰にいくら支払うべきかを表示します。',
+ 'budget.netBalances': '差引残高',
+ 'budget.categoriesLabel': 'カテゴリ',
+ "costs.you": "自分",
+ "costs.youShort": "Y",
+ "costs.youLower": "you",
+ "costs.youOwe": "支払う額",
+ "costs.youOweSub": "他の人に支払うべき額",
+ "costs.youreOwed": "受け取る額",
+ "costs.youreOwedSub": "他の人があなたに支払うべき額",
+ "costs.totalSpend": "旅行の合計支出",
+ "costs.totalSpendSub": "全員の合計",
+ "costs.to": "支払先",
+ "costs.from": "支払元",
+ "costs.allSettled": "すべて精算済みです",
+ "costs.nothingOwed": "受け取る額はありません",
+ "costs.yourShare": "あなたの負担分",
+ "costs.youPaid": "あなたが支払った額",
+ "costs.expenses": "支出",
+ "costs.entries": "{count}件",
+ "costs.searchPlaceholder": "支出を検索…",
+ "costs.filter.all": "すべて",
+ "costs.filter.mine": "自分が支払った分",
+ "costs.filter.owed": "受け取る分",
+ "costs.addExpense": "支出を追加",
+ "costs.editExpense": "支出を編集",
+ "costs.noMatch": "検索条件に一致する支出はありません。",
+ "costs.emptyText": "支出はまだありません。最初の支出を追加しましょう。",
+ "costs.spent": "{amount}を支出",
+ "costs.noDate": "日付なし",
+ "costs.noOnePaid": "まだ誰も支払っていません",
+ "costs.youLent": "{amount}を立て替えました",
+ "costs.youBorrowed": "{amount}を借りています",
+ "costs.settleUp": "精算する",
+ "costs.history": "履歴",
+ "costs.everyoneSquare": "全員が清算済みです",
+ "costs.nothingOutstanding": "現在、未払いの支払いはありません。",
+ "costs.pay": "支払う",
+ "costs.pays": "支払う",
+ "costs.settle": "精算",
+ "costs.balances": "残高",
+ "costs.byCategory": "カテゴリ別",
+ "costs.noCategories": "支出はまだありません。",
+ "costs.settleHistory": "精算履歴",
+ "costs.noSettlements": "精算済みの支払いはまだありません。",
+ "costs.paymentsSettled": "{count}件の支払いを精算済み",
+ "costs.paid": "支払済み",
+ "costs.undo": "元に戻す",
+ "costs.whatFor": "何の支出ですか?",
+ "costs.namePlaceholder": "例:夕食、お土産、ガソリン…",
+ "costs.totalAmount": "合計金額",
+ "costs.currency": "通貨",
+ "costs.day": "日",
+ "costs.rateLabel": "1 {from} = {to}",
+ "costs.category": "カテゴリ",
+ "costs.whoPaid": "誰が支払いましたか?",
+ "costs.splitBetween": "均等に分割する相手",
+ "costs.pickSomeone": "分割する相手を少なくとも1人選んでください。",
+ "costs.splitSummary": "{count}人で分割 · 各{amount}",
+ "costs.cat.accommodation": "宿泊",
+ "costs.cat.food": "飲食",
+ "costs.cat.groceries": "食料品",
+ "costs.cat.transport": "交通",
+ "costs.cat.flights": "航空券",
+ "costs.cat.activities": "アクティビティ",
+ "costs.cat.sightseeing": "観光",
+ "costs.cat.shopping": "買い物",
+ "costs.cat.fees": "手数料・チケット",
+ "costs.cat.health": "健康",
+ "costs.cat.tips": "チップ",
+ "costs.cat.other": "その他",
+ "costs.daysCount": "{count}日間",
+ "costs.travelers": "{count}人の旅行者",
+ "costs.liveRate": "リアルタイムレート",
+ "costs.settleAll": "すべて精算",
+};
+
+export default budget;
diff --git a/shared/src/i18n/ja/categories.ts b/shared/src/i18n/ja/categories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5d644e982751f0b76e7a193cec2a00a60e030a9f
--- /dev/null
+++ b/shared/src/i18n/ja/categories.ts
@@ -0,0 +1,26 @@
+import type { TranslationStrings } from '../types';
+
+const categories: TranslationStrings = {
+ 'categories.title': 'カテゴリ',
+ 'categories.subtitle': '場所のカテゴリを管理',
+ 'categories.new': '新しいカテゴリ',
+ 'categories.empty': 'カテゴリはまだありません',
+ 'categories.namePlaceholder': 'カテゴリ名',
+ 'categories.icon': 'アイコン',
+ 'categories.color': '色',
+ 'categories.customColor': 'カスタムカラーを選択',
+ 'categories.preview': 'プレビュー',
+ 'categories.defaultName': 'カテゴリ',
+ 'categories.update': '更新',
+ 'categories.create': '作成',
+ 'categories.confirm.delete':
+ 'カテゴリを削除しますか?このカテゴリの場所は削除されません。',
+ 'categories.toast.loadError': 'カテゴリの読み込みに失敗しました',
+ 'categories.toast.nameRequired': '名前を入力してください',
+ 'categories.toast.updated': 'カテゴリを更新しました',
+ 'categories.toast.created': 'カテゴリを作成しました',
+ 'categories.toast.saveError': '保存に失敗しました',
+ 'categories.toast.deleted': 'カテゴリを削除しました',
+ 'categories.toast.deleteError': '削除に失敗しました',
+};
+export default categories;
diff --git a/shared/src/i18n/ja/collab.ts b/shared/src/i18n/ja/collab.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4de8e5eff53c85ffbc33db9022433788e5470fc5
--- /dev/null
+++ b/shared/src/i18n/ja/collab.ts
@@ -0,0 +1,76 @@
+import type { TranslationStrings } from '../types';
+
+const collab: TranslationStrings = {
+ 'collab.tabs.chat': 'チャット',
+ 'collab.tabs.notes': 'ノート',
+ 'collab.tabs.polls': '投票',
+ 'collab.whatsNext.title': '次にすること',
+ 'collab.whatsNext.today': '今日',
+ 'collab.whatsNext.tomorrow': '明日',
+ 'collab.whatsNext.empty': '予定されたアクティビティはありません',
+ 'collab.whatsNext.until': '〜',
+ 'collab.whatsNext.emptyHint':
+ '時間が設定されたアクティビティがここに表示されます',
+ 'collab.chat.send': '送信',
+ 'collab.chat.placeholder': 'メッセージを入力…',
+ 'collab.chat.empty': '会話を始めましょう',
+ 'collab.chat.emptyHint': 'メッセージは旅行メンバー全員と共有されます',
+ 'collab.chat.emptyDesc': 'アイデアや計画、最新情報を共有しましょう',
+ 'collab.chat.today': '今日',
+ 'collab.chat.yesterday': '昨日',
+ 'collab.chat.deletedMessage': 'メッセージを削除しました',
+ 'collab.chat.reply': '返信',
+ 'collab.chat.loadMore': '以前のメッセージを読み込む',
+ 'collab.chat.justNow': 'たった今',
+ 'collab.chat.minutesAgo': '{n}分前',
+ 'collab.chat.hoursAgo': '{n}時間前',
+ 'collab.notes.title': 'ノート',
+ 'collab.notes.new': '新規ノート',
+ 'collab.notes.empty': 'まだノートがありません',
+ 'collab.notes.emptyHint': 'アイデアや計画を書き留めましょう',
+ 'collab.notes.all': 'すべて',
+ 'collab.notes.titlePlaceholder': 'ノートのタイトル',
+ 'collab.notes.contentPlaceholder': '内容を入力…',
+ 'collab.notes.categoryPlaceholder': 'カテゴリ',
+ 'collab.notes.newCategory': '新しいカテゴリ…',
+ 'collab.notes.category': 'カテゴリ',
+ 'collab.notes.noCategory': 'カテゴリなし',
+ 'collab.notes.color': '色',
+ 'collab.notes.save': '保存',
+ 'collab.notes.cancel': 'キャンセル',
+ 'collab.notes.edit': '編集',
+ 'collab.notes.delete': '削除',
+ 'collab.notes.confirmDeleteTitle': 'メモを削除しますか?',
+ 'collab.notes.confirmDeleteBody': 'このメモは完全に削除されます。',
+ 'collab.notes.pin': '固定',
+ 'collab.notes.unpin': '固定を解除',
+ 'collab.notes.daysAgo': '{n}日前',
+ 'collab.notes.categorySettings': 'カテゴリ管理',
+ 'collab.notes.create': '作成',
+ 'collab.notes.website': 'ウェブサイト',
+ 'collab.notes.websitePlaceholder': 'https://...',
+ 'collab.notes.attachFiles': 'ファイルを添付',
+ 'collab.notes.noCategoriesYet': 'カテゴリがまだありません',
+ 'collab.notes.emptyDesc': 'ノートを作成して始めましょう',
+ 'collab.polls.title': '投票',
+ 'collab.polls.new': '新しい投票',
+ 'collab.polls.empty': 'まだ投票がありません',
+ 'collab.polls.emptyHint': '質問してみんなで投票しましょう',
+ 'collab.polls.question': '質問',
+ 'collab.polls.questionPlaceholder': '何をしますか?',
+ 'collab.polls.addOption': '+ 選択肢を追加',
+ 'collab.polls.optionPlaceholder': '選択肢 {n}',
+ 'collab.polls.create': '投票を作成',
+ 'collab.polls.close': '閉じる',
+ 'collab.polls.closed': '終了',
+ 'collab.polls.votes': '{n}票',
+ 'collab.polls.vote': '{n}票',
+ 'collab.polls.multipleChoice': '複数選択',
+ 'collab.polls.multiChoice': '複数選択',
+ 'collab.polls.deadline': '締切',
+ 'collab.polls.option': '選択肢',
+ 'collab.polls.options': '選択肢',
+ 'collab.polls.delete': '削除',
+ 'collab.polls.closedSection': '終了',
+};
+export default collab;
diff --git a/shared/src/i18n/ja/common.ts b/shared/src/i18n/ja/common.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d6ef7f514f093b6ae0599a3bd1fa38a315340e60
--- /dev/null
+++ b/shared/src/i18n/ja/common.ts
@@ -0,0 +1,55 @@
+import type { TranslationStrings } from '../types';
+
+const common: TranslationStrings = {
+ 'common.save': '保存',
+ 'common.showMore': 'もっと見る',
+ 'common.showLess': '閉じる',
+ 'common.cancel': 'キャンセル',
+ 'common.clear': 'クリア',
+ 'common.delete': '削除',
+ 'common.edit': '編集',
+ 'common.add': '追加',
+ 'common.loading': '読み込み中…',
+ 'common.import': 'インポート',
+ 'common.select': '選択',
+ 'common.selectAll': 'すべて選択',
+ 'common.deselectAll': 'すべて解除',
+ 'common.error': 'エラー',
+ 'common.unknownError': '不明なエラー',
+ 'common.tooManyAttempts':
+ '試行回数が多すぎます。時間をおいて再度お試しください。',
+ 'common.back': '戻る',
+ 'common.all': 'すべて',
+ 'common.close': '閉じる',
+ 'common.open': '開く',
+ 'common.upload': 'アップロード',
+ 'common.search': '検索',
+ 'common.confirm': '確認',
+ 'common.ok': 'OK',
+ 'common.yes': 'はい',
+ 'common.no': 'いいえ',
+ 'common.or': 'または',
+ 'common.none': 'なし',
+ 'common.date': '日付',
+ 'common.rename': '名前を変更',
+ 'common.discardChanges': '変更をキャンセル',
+ 'common.discard': 'キャンセル',
+ 'common.name': '名前',
+ 'common.email': 'メールアドレス',
+ 'common.password': 'パスワード',
+ 'common.saving': '保存中…',
+ 'common.justNow': 'たった今',
+ 'common.hoursAgo': '{count}時間前',
+ 'common.daysAgo': '{count}日前',
+ 'common.saved': '保存しました',
+ 'common.update': '更新',
+ 'common.change': '変更',
+ 'common.uploading': 'アップロード中…',
+ 'common.backToPlanning': 'プランに戻る',
+ 'common.reset': 'リセット',
+ 'common.expand': '展開',
+ 'common.collapse': '折りたたむ',
+ 'common.copy': 'コピー',
+ 'common.copied': 'コピーしました',
+};
+export default common;
diff --git a/shared/src/i18n/ja/dashboard.ts b/shared/src/i18n/ja/dashboard.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c49f10946c0b748306eef9f3985091135fe0b65e
--- /dev/null
+++ b/shared/src/i18n/ja/dashboard.ts
@@ -0,0 +1,167 @@
+import type { TranslationStrings } from '../types';
+
+const dashboard: TranslationStrings = {
+ 'dashboard.title': 'マイ旅行',
+ 'dashboard.subtitle.loading': '旅行を読み込み中...',
+ 'dashboard.subtitle.trips': '{count}件の旅行({archived}件アーカイブ)',
+ 'dashboard.subtitle.empty': '最初の旅行を始めましょう',
+ 'dashboard.subtitle.activeOne': '進行中の旅行 {count}件',
+ 'dashboard.subtitle.activeMany': '進行中の旅行 {count}件',
+ 'dashboard.subtitle.archivedSuffix': ' · アーカイブ {count}件',
+ 'dashboard.newTrip': '新しい旅行',
+ 'dashboard.newTripSub': '新しい旅行をゼロから計画',
+ 'dashboard.gridView': 'グリッド表示',
+ 'dashboard.listView': 'リスト表示',
+ 'dashboard.currency': '通貨',
+ 'dashboard.timezone': 'タイムゾーン',
+ 'dashboard.localTime': '現地',
+ 'dashboard.timezoneCustomTitle': 'カスタムタイムゾーン',
+ 'dashboard.timezoneCustomLabelPlaceholder': 'ラベル(任意)',
+ 'dashboard.timezoneCustomTzPlaceholder': '例:America/New_York',
+ 'dashboard.timezoneCustomAdd': '追加',
+ 'dashboard.timezoneCustomErrorEmpty': 'タイムゾーンIDを入力してください',
+ 'dashboard.timezoneCustomErrorInvalid':
+ '無効なタイムゾーンです(例:Europe/Berlin)',
+ 'dashboard.timezoneCustomErrorDuplicate': 'すでに追加されています',
+ 'dashboard.emptyTitle': '旅行はまだありません',
+ 'dashboard.emptyText': '最初の旅行を作成して計画を始めましょう!',
+ 'dashboard.emptyButton': '最初の旅行を作成',
+ 'dashboard.nextTrip': '次の旅行',
+ 'dashboard.shared': '共有済み',
+ 'dashboard.sharedBy': '{name}さんが共有',
+ 'dashboard.days': '日数',
+ 'dashboard.places': '場所',
+ 'dashboard.members': '同行者',
+ 'dashboard.archive': 'アーカイブ',
+ 'dashboard.copyTrip': 'コピー',
+ 'dashboard.copySuffix': 'コピー',
+ 'dashboard.restore': '復元',
+ 'dashboard.archived': 'アーカイブ済み',
+ 'dashboard.status.ongoing': '進行中',
+ 'dashboard.status.today': '今日',
+ 'dashboard.status.tomorrow': '明日',
+ 'dashboard.status.past': '過去',
+ 'dashboard.status.daysLeft': '残り{count}日',
+ 'dashboard.toast.loadError': '旅行の読み込みに失敗しました',
+ 'dashboard.toast.created': '旅行を作成しました!',
+ 'dashboard.toast.createError': '旅行の作成に失敗しました',
+ 'dashboard.toast.updated': '旅行を更新しました!',
+ 'dashboard.toast.updateError': '旅行の更新に失敗しました',
+ 'dashboard.toast.deleted': '旅行を削除しました',
+ 'dashboard.toast.deleteError': '旅行の削除に失敗しました',
+ 'dashboard.toast.archived': '旅行をアーカイブしました',
+ 'dashboard.toast.archiveError': '旅行のアーカイブに失敗しました',
+ 'dashboard.toast.restored': '旅行を復元しました',
+ 'dashboard.toast.restoreError': '旅行の復元に失敗しました',
+ 'dashboard.toast.copied': '旅行をコピーしました!',
+ 'dashboard.toast.copyError': '旅行のコピーに失敗しました',
+ 'dashboard.confirm.delete':
+ '旅行「{title}」を削除しますか?すべての場所と計画は完全に削除されます。',
+ 'dashboard.confirm.copy.title': 'この旅行をコピーしますか?',
+ 'dashboard.confirm.copy.willCopy': 'コピーされる内容',
+ 'dashboard.confirm.copy.will1': '日数、訪問先 & 日ごとの割り当て',
+ 'dashboard.confirm.copy.will2': '宿泊施設 & 予約',
+ 'dashboard.confirm.copy.will3': '予算項目 & カテゴリの順序',
+ 'dashboard.confirm.copy.will4': '持ち物リスト(未チェックのみ)',
+ 'dashboard.confirm.copy.will5': 'TODO(未割り当て & 未チェック)',
+ 'dashboard.confirm.copy.will6': '日ごとのメモ',
+ 'dashboard.confirm.copy.wontCopy': 'コピーされない内容',
+ 'dashboard.confirm.copy.wont1': '共同編集者 & メンバー割り当て',
+ 'dashboard.confirm.copy.wont2': '共同ノート、投票 & メッセージ',
+ 'dashboard.confirm.copy.wont3': 'ファイル & 写真',
+ 'dashboard.confirm.copy.wont4': '共有トークン',
+ 'dashboard.confirm.copy.confirm': '旅行をコピー',
+ 'dashboard.editTrip': '旅行を編集',
+ 'dashboard.createTrip': '新しい旅行を作成',
+ 'dashboard.tripTitle': 'タイトル',
+ 'dashboard.tripTitlePlaceholder': '例:日本の夏',
+ 'dashboard.tripDescription': '説明',
+ 'dashboard.tripDescriptionPlaceholder': 'この旅行について',
+ 'dashboard.startDate': '開始日',
+ 'dashboard.endDate': '終了日',
+ 'dashboard.dayCount': '日数',
+ 'dashboard.dayCountHint': '日付が未設定の場合に作成する日数です。',
+ 'dashboard.noDateHint':
+ '日付未設定 — 既定で7日分が作成されます。後で変更できます。',
+ 'dashboard.coverImage': 'カバー画像',
+ 'dashboard.addCoverImage': 'カバー画像を追加(またはドラッグ&ドロップ)',
+ 'dashboard.addMembers': '同行者',
+ 'dashboard.addMember': 'メンバーを追加',
+ 'dashboard.coverSaved': 'カバー画像を保存しました',
+ 'dashboard.coverUploadError': 'アップロードに失敗しました',
+ 'dashboard.coverRemoveError': '削除に失敗しました',
+ 'dashboard.titleRequired': 'タイトルは必須です',
+ 'dashboard.endDateError': '終了日は開始日より後にしてください',
+ 'dashboard.greeting.morning': 'おはようございます、',
+ 'dashboard.greeting.afternoon': 'こんにちは、',
+ 'dashboard.greeting.evening': 'こんばんは、',
+ 'dashboard.mobile.liveNow': 'ライブ中',
+ 'dashboard.mobile.tripProgress': '旅行の進行状況',
+ 'dashboard.mobile.daysLeft': '残り{count}日',
+ 'dashboard.mobile.places': '場所',
+ 'dashboard.mobile.buddies': '仲間',
+ 'dashboard.mobile.newTrip': '新しい旅行',
+ 'dashboard.mobile.currency': '通貨',
+ 'dashboard.mobile.timezone': 'タイムゾーン',
+ 'dashboard.mobile.upcomingTrips': '今後の旅行',
+ 'dashboard.mobile.yourTrips': 'あなたの旅行',
+ 'dashboard.mobile.trips': '旅行',
+ 'dashboard.mobile.starts': '開始',
+ 'dashboard.mobile.duration': '期間',
+ 'dashboard.mobile.day': '日',
+ 'dashboard.mobile.days': '日',
+ 'dashboard.mobile.ongoing': '進行中',
+ 'dashboard.mobile.startsToday': '今日開始',
+ 'dashboard.mobile.tomorrow': '明日',
+ 'dashboard.mobile.inDays': '{count}日後',
+ 'dashboard.mobile.inMonths': '{count}か月後',
+ 'dashboard.mobile.completed': '完了',
+ 'dashboard.mobile.currencyConverter': '通貨換算',
+ 'dashboard.filter.planned': '予定',
+ 'dashboard.hero.badgeLive': '進行中',
+ 'dashboard.hero.badgeToday': '本日開始',
+ 'dashboard.hero.badgeTomorrow': '明日',
+ 'dashboard.hero.badgeNext': '次の旅行',
+ 'dashboard.hero.badgeRecent': '最近',
+ 'dashboard.hero.tripDates': '旅行の日程',
+ 'dashboard.hero.noDates': '日程未設定',
+ 'dashboard.hero.travelerOne': '{count}人の旅行者',
+ 'dashboard.hero.travelerMany': '{count}人の旅行者',
+ 'dashboard.hero.destinationOne': '{count}件の目的地',
+ 'dashboard.hero.destinationMany': '{count}件の目的地',
+ 'dashboard.hero.dayUnitOne': '日',
+ 'dashboard.hero.dayUnitMany': '日',
+ 'dashboard.hero.dayLeft': '残り1日',
+ 'dashboard.hero.daysLeft': '残り日数',
+ 'dashboard.hero.lastDay': '最終日',
+ 'dashboard.hero.untilStart': '開始まで',
+ 'dashboard.hero.startsIn': '出発まで',
+ 'dashboard.atlas.countriesVisited': 'アトラス · 訪問した国',
+ 'dashboard.atlas.ofTotal': '/ {total}',
+ 'dashboard.atlas.tripsTotal': '旅行の総数',
+ 'dashboard.atlas.placesMapped': '{count}件の場所を記録',
+ 'dashboard.atlas.daysTraveled': '旅行日数',
+ 'dashboard.atlas.daysUnit': '日',
+ 'dashboard.atlas.acrossAllTrips': 'すべての旅行を通じて',
+ 'dashboard.atlas.distanceFlown': '飛行距離',
+ 'dashboard.atlas.kmUnit': 'km',
+ 'dashboard.atlas.aroundEquator': '≈ 赤道{count}周',
+ 'dashboard.card.idea': 'アイデア',
+ 'dashboard.card.buddyOne': '同行者',
+ 'dashboard.fx.from': '変換元',
+ 'dashboard.fx.to': '変換先',
+ 'dashboard.fx.unavailable': 'レートを取得できません',
+ 'dashboard.tz.searchPlaceholder': 'タイムゾーンを検索…',
+ 'dashboard.tz.empty': '他のタイムゾーンはまだありません — + で追加',
+ 'dashboard.upcoming.title': '今後の予約',
+ 'dashboard.upcoming.empty': 'まだ予約はありません。',
+ 'dashboard.aria.toggleView': '表示を切り替え',
+ 'dashboard.aria.filter': 'フィルター',
+ 'dashboard.aria.duplicate': '複製',
+ 'dashboard.aria.refreshRates': 'レートを更新',
+ 'dashboard.aria.swapCurrencies': '通貨を入れ替え',
+ 'dashboard.aria.addTimezone': 'タイムゾーンを追加',
+ 'dashboard.aria.removeTimezone': '{city}を削除',
+ 'dashboard.dayCountRequired': '日数は必須です',
+};
+export default dashboard;
diff --git a/shared/src/i18n/ja/day.ts b/shared/src/i18n/ja/day.ts
new file mode 100644
index 0000000000000000000000000000000000000000..da111db30525bca73eeed5de29d1ec8dfff66885
--- /dev/null
+++ b/shared/src/i18n/ja/day.ts
@@ -0,0 +1,26 @@
+import type { TranslationStrings } from '../types';
+
+const day: TranslationStrings = {
+ 'day.precipProb': '降水確率',
+ 'day.precipitation': '降水量',
+ 'day.wind': '風',
+ 'day.sunrise': '日の出',
+ 'day.sunset': '日の入り',
+ 'day.hourlyForecast': '時間別予報',
+ 'day.climateHint':
+ '過去の平均値 — 実際の予報はこの日付の16日前から表示されます。',
+ 'day.noWeather': '天気データがありません。座標付きの場所を追加してください。',
+ 'day.overview': '1日の概要',
+ 'day.accommodation': '宿泊先',
+ 'day.addAccommodation': '宿泊先を追加',
+ 'day.hotelDayRange': '適用日',
+ 'day.noPlacesForHotel': '先に旅行に場所を追加してください',
+ 'day.allDays': 'すべて',
+ 'day.checkIn': 'チェックイン',
+ 'day.checkInUntil': 'チェックイン期限',
+ 'day.checkOut': 'チェックアウト',
+ 'day.confirmation': '確認',
+ 'day.editAccommodation': '宿泊先を編集',
+ 'day.reservations': '予約',
+};
+export default day;
diff --git a/shared/src/i18n/ja/dayplan.ts b/shared/src/i18n/ja/dayplan.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1cb8d3f04a2b4c79649bd77bb62e3766ad415989
--- /dev/null
+++ b/shared/src/i18n/ja/dayplan.ts
@@ -0,0 +1,55 @@
+import type { TranslationStrings } from '../types';
+
+const dayplan: TranslationStrings = {
+ 'dayplan.icsTooltip': 'カレンダーを書き出し(ICS)',
+ 'dayplan.emptyDay': 'この日の予定はありません',
+ 'dayplan.cannotReorderTransport': '時刻が固定された予約は並び替えできません',
+ 'dayplan.confirmRemoveTimeTitle': '時刻を削除しますか?',
+ 'dayplan.confirmRemoveTimeBody':
+ 'この場所には固定時刻({time})があります。移動すると時刻が削除され、自由に並び替えできます。',
+ 'dayplan.confirmRemoveTimeAction': '時刻を削除して移動',
+ 'dayplan.confirmDeleteNoteTitle': 'メモを削除しますか?',
+ 'dayplan.confirmDeleteNoteBody': 'このメモは完全に削除されます。',
+ 'dayplan.cannotDropOnTimed': '時刻指定の項目の間には配置できません',
+ 'dayplan.cannotBreakChronology': '時刻指定の項目や予約の時系列が崩れます',
+ 'dayplan.addNote': 'メモを追加',
+ 'dayplan.expandAll': 'すべての日を展開',
+ 'dayplan.collapseAll': 'すべての日を折りたたむ',
+ 'dayplan.editNote': 'メモを編集',
+ 'dayplan.noteAdd': 'メモを追加',
+ 'dayplan.noteEdit': 'メモを編集',
+ 'dayplan.noteTitle': 'メモ',
+ 'dayplan.noteSubtitle': '日別メモ',
+ 'dayplan.totalCost': '合計費用',
+ 'dayplan.days': '日',
+ 'dayplan.dayN': '{n}日目',
+ 'dayplan.calculating': '計算中...',
+ 'dayplan.route': 'ルート',
+ 'dayplan.optimize': '最適化',
+ 'dayplan.optimized': 'ルートを最適化しました',
+ 'dayplan.routeError': 'ルートの計算に失敗しました',
+ 'dayplan.toast.needTwoPlaces': 'ルート最適化には2つ以上の場所が必要です',
+ 'dayplan.toast.routeOptimized': 'ルートを最適化しました',
+ 'dayplan.toast.routeOptimizedFromHotel': '宿泊先を起点にルートを最適化しました',
+ 'dayplan.toast.noGeoPlaces': '座標付きの場所がありません',
+ 'dayplan.confirmed': '確定',
+ 'dayplan.pendingRes': '保留',
+ 'dayplan.pdf': 'PDF',
+ 'dayplan.pdfTooltip': '日別計画をPDFで書き出し',
+ 'dayplan.pdfError': 'PDFの書き出しに失敗しました',
+ 'dayplan.mobile.addPlace': '場所を追加',
+ 'dayplan.mobile.searchPlaces': '場所を検索…',
+ 'dayplan.mobile.allAssigned': 'すべて割り当て済み',
+ 'dayplan.mobile.noMatch': '一致なし',
+ 'dayplan.mobile.createNew': '新しい場所を作成',
+ 'dayplan.reorderDays': '日付を並べ替え',
+ 'dayplan.reorderTitle': '日付を並べ替え',
+ 'dayplan.reorderHint': 'その日のスポット、メモ、予約も一緒に移動します。',
+ 'dayplan.addDay': '日付を追加',
+ 'dayplan.moveUp': '上へ移動',
+ 'dayplan.moveDown': '下へ移動',
+ 'dayplan.reorderUndo': '日付を並べ替え',
+ 'dayplan.reorderError': '日付の並べ替えに失敗しました',
+ 'dayplan.addDayError': '日付の追加に失敗しました',
+};
+export default dayplan;
diff --git a/shared/src/i18n/ja/externalNotifications.ts b/shared/src/i18n/ja/externalNotifications.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3ae1bd0281c2aa484206cfd6158c7ea05e206071
--- /dev/null
+++ b/shared/src/i18n/ja/externalNotifications.ts
@@ -0,0 +1,63 @@
+import type { NotificationLocale } from '../externalNotifications/types';
+
+const ja: NotificationLocale = {
+ email: {
+ footer: 'TREKで通知を有効にしているため、このメールが届きました。',
+ manage: '設定で通知設定を管理',
+ madeWith: 'Made with',
+ openTrek: 'TREKを開く',
+ },
+ events: {
+ trip_invite: (p) => ({
+ title: `「${p.trip}」への旅行招待`,
+ body: `${p.actor}が${p.invitee || 'メンバー'}を「${p.trip}」の旅行に招待しました。`,
+ }),
+ booking_change: (p) => ({
+ title: `新しい予約:${p.booking}`,
+ body: `${p.actor}が「${p.trip}」に「${p.booking}」(${p.type})を追加しました。`,
+ }),
+ trip_reminder: (p) => ({
+ title: `旅行リマインダー:${p.trip}`,
+ body: `「${p.trip}」の旅行が近づいています!`,
+ }),
+ todo_due: (p) => ({
+ title: `期限のタスク:${p.todo}`,
+ body: `「${p.trip}」の「${p.todo}」は${p.due}が期限です。`,
+ }),
+ vacay_invite: (p) => ({
+ title: 'Vacay Fusion招待',
+ body: `${p.actor}が休暇プランの統合に招待しています。TREKを開いて承認または拒否してください。`,
+ }),
+ photos_shared: (p) => ({
+ title: `${p.count}枚の写真が共有されました`,
+ body: `${p.actor}が「${p.trip}」で${p.count}枚の写真を共有しました。`,
+ }),
+ collab_message: (p) => ({
+ title: `「${p.trip}」の新しいメッセージ`,
+ body: `${p.actor}:${p.preview}`,
+ }),
+ packing_tagged: (p) => ({
+ title: `パッキング:${p.category}`,
+ body: `${p.actor}が「${p.trip}」の「${p.category}」カテゴリにあなたを割り当てました。`,
+ }),
+ version_available: (p) => ({
+ title: '新しいTREKバージョンが利用可能',
+ body: `TREK ${p.version}が利用可能になりました。管理パネルからアップデートしてください。`,
+ }),
+ synology_session_cleared: () => ({
+ title: 'Synologyセッションがクリアされました',
+ body: 'SynologyアカウントまたはURLが変更されました。Synology Photosからログアウトされました。',
+ }),
+ },
+ passwordReset: {
+ subject: 'パスワードをリセット',
+ greeting: 'こんにちは',
+ body: 'TREKアカウントのパスワードリセットリクエストを受け付けました。以下のボタンをクリックして新しいパスワードを設定してください。',
+ ctaIntro: 'パスワードをリセット',
+ expiry: 'このリンクは60分後に期限切れになります。',
+ ignore:
+ 'このリクエストをご自身でしていない場合は、このメールを無視してください — パスワードは変更されません。',
+ },
+};
+
+export default ja;
diff --git a/shared/src/i18n/ja/files.ts b/shared/src/i18n/ja/files.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0c359ad1500adfdf94dc0cdd89913ffb3d683935
--- /dev/null
+++ b/shared/src/i18n/ja/files.ts
@@ -0,0 +1,62 @@
+import type { TranslationStrings } from '../types';
+
+const files: TranslationStrings = {
+ 'files.title': 'ファイル',
+ 'files.pageTitle': 'ファイル・ドキュメント',
+ 'files.subtitle': '{trip} のファイル {count} 件',
+ 'files.download': 'ダウンロード',
+ 'files.openError': 'ファイルを開けませんでした',
+ 'files.downloadPdf': 'PDFをダウンロード',
+ 'files.count': '{count}件のファイル',
+ 'files.countSingular': '1件のファイル',
+ 'files.uploaded': '{count}件アップロード',
+ 'files.uploadError': 'アップロードに失敗しました',
+ 'files.dropzone': 'ここにファイルをドロップ',
+ 'files.dropzoneHint': 'またはクリックして参照',
+ 'files.allowedTypes': '画像、PDF、DOC、DOCX、XLS、XLSX、TXT、CSV · 最大50MB',
+ 'files.uploading': 'アップロード中...',
+ 'files.filterAll': 'すべて',
+ 'files.filterPdf': 'PDF',
+ 'files.filterImages': '画像',
+ 'files.filterDocs': 'ドキュメント',
+ 'files.filterCollab': 'Collabメモ',
+ 'files.sourceCollab': 'Collabメモより',
+ 'files.empty': 'ファイルはまだありません',
+ 'files.emptyHint': 'ファイルをアップロードして旅行に添付しましょう',
+ 'files.openTab': '新しいタブで開く',
+ 'files.confirm.delete': 'このファイルを削除しますか?',
+ 'files.toast.deleted': 'ファイルを削除しました',
+ 'files.toast.deleteError': 'ファイルの削除に失敗しました',
+ 'files.sourcePlan': '日別計画',
+ 'files.sourceBooking': '予約',
+ 'files.sourceTransport': '移動',
+ 'files.attach': '添付',
+ 'files.pasteHint':
+ 'クリップボードから画像を貼り付けることもできます(Ctrl+V)',
+ 'files.trash': 'ゴミ箱',
+ 'files.trashEmpty': 'ゴミ箱は空です',
+ 'files.emptyTrash': 'ゴミ箱を空にする',
+ 'files.restore': '復元',
+ 'files.star': 'スター',
+ 'files.unstar': 'スター解除',
+ 'files.assign': '割り当て',
+ 'files.assignTitle': 'ファイルを割り当て',
+ 'files.assignPlace': '場所',
+ 'files.assignBooking': '予約',
+ 'files.assignTransport': '移動',
+ 'files.unassigned': '未割り当て',
+ 'files.unlink': 'リンクを解除',
+ 'files.toast.trashed': 'ゴミ箱に移動しました',
+ 'files.toast.restored': 'ファイルを復元しました',
+ 'files.toast.trashEmptied': 'ゴミ箱を空にしました',
+ 'files.toast.assigned': 'ファイルを割り当てました',
+ 'files.toast.assignError': '割り当てに失敗しました',
+ 'files.toast.restoreError': '復元に失敗しました',
+ 'files.confirm.permanentDelete':
+ 'このファイルを完全に削除しますか?元に戻せません。',
+ 'files.confirm.emptyTrash':
+ 'ゴミ箱内のファイルをすべて完全に削除しますか?元に戻せません。',
+ 'files.noteLabel': 'メモ',
+ 'files.notePlaceholder': 'メモを追加...',
+};
+export default files;
diff --git a/shared/src/i18n/ja/index.ts b/shared/src/i18n/ja/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..77aeb6a09fb62e2522e8b4c04e4169a94c2f8d99
--- /dev/null
+++ b/shared/src/i18n/ja/index.ts
@@ -0,0 +1,86 @@
+import admin from './admin';
+import airport from './airport';
+import atlas from './atlas';
+import backup from './backup';
+import budget from './budget';
+import categories from './categories';
+import collab from './collab';
+import common from './common';
+import dashboard from './dashboard';
+import day from './day';
+import dayplan from './dayplan';
+import files from './files';
+import inspector from './inspector';
+import journey from './journey';
+import login from './login';
+import map from './map';
+import members from './members';
+import memories from './memories';
+import nav from './nav';
+import notif from './notif';
+import notifications from './notifications';
+import oauth from './oauth';
+import packing from './packing';
+import pdf from './pdf';
+import perm from './perm';
+import photos from './photos';
+import places from './places';
+import planner from './planner';
+import register from './register';
+import reservations from './reservations';
+import settings from './settings';
+import share from './share';
+import shared from './shared';
+import stats from './stats';
+import system_notice from './system_notice';
+import todo from './todo';
+import transport from './transport';
+import trip from './trip';
+import trips from './trips';
+import undo from './undo';
+import vacay from './vacay';
+
+const locale = {
+ ...common,
+ ...trips,
+ ...nav,
+ ...dashboard,
+ ...settings,
+ ...admin,
+ ...dayplan,
+ ...share,
+ ...shared,
+ ...login,
+ ...register,
+ ...vacay,
+ ...atlas,
+ ...trip,
+ ...places,
+ ...inspector,
+ ...reservations,
+ ...airport,
+ ...map,
+ ...budget,
+ ...files,
+ ...packing,
+ ...members,
+ ...categories,
+ ...backup,
+ ...photos,
+ ...pdf,
+ ...planner,
+ ...stats,
+ ...day,
+ ...memories,
+ ...collab,
+ ...perm,
+ ...undo,
+ ...notifications,
+ ...todo,
+ ...notif,
+ ...journey,
+ ...oauth,
+ ...system_notice,
+ ...transport,
+};
+export default locale;
diff --git a/shared/src/i18n/ja/inspector.ts b/shared/src/i18n/ja/inspector.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ed9ad9a7f5332d41cc0ffa6fec49f5b9520b047e
--- /dev/null
+++ b/shared/src/i18n/ja/inspector.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const inspector: TranslationStrings = {
+ 'inspector.opened': '営業中',
+ 'inspector.closed': '営業時間外',
+ 'inspector.openingHours': '営業時間',
+ 'inspector.showHours': '営業時間を表示',
+ 'inspector.files': 'ファイル',
+ 'inspector.remove': '削除',
+ 'inspector.filesCount': '{count}件のファイル',
+ 'inspector.removeFromDay': 'この日から削除',
+ 'inspector.addToDay': '日に追加',
+ 'inspector.confirmedRes': '確定済み予約',
+ 'inspector.pendingRes': '保留中の予約',
+ 'inspector.google': 'Googleマップで開く',
+ 'inspector.website': 'Webサイトを開く',
+ 'inspector.addRes': '予約',
+ 'inspector.editRes': '予約を編集',
+ 'inspector.participants': '参加者',
+ 'inspector.trackStats': '統計を記録',
+};
+export default inspector;
diff --git a/shared/src/i18n/ja/journey.ts b/shared/src/i18n/ja/journey.ts
new file mode 100644
index 0000000000000000000000000000000000000000..62108f912d022c469074970f886cbab5e8780520
--- /dev/null
+++ b/shared/src/i18n/ja/journey.ts
@@ -0,0 +1,240 @@
+import type { TranslationStrings } from '../types';
+
+const journey: TranslationStrings = {
+ 'journey.search.placeholder': '日記を検索…',
+ 'journey.search.noResults': '「{query}」に一致する日記はありません',
+ 'journey.title': '日記',
+ 'journey.subtitle': '旅の記録をリアルタイムで残そう',
+ 'journey.new': '新しい日記',
+ 'journey.create': '作成',
+ 'journey.titlePlaceholder': 'どこへ行きますか?',
+ 'journey.empty': '日記はまだありません',
+ 'journey.emptyHint': '次の旅を記録してみましょう',
+ 'journey.deleted': '日記を削除しました',
+ 'journey.createError': '日記を作成できませんでした',
+ 'journey.deleteError': '日記を削除できませんでした',
+ 'journey.deleteConfirmTitle': '削除',
+ 'journey.deleteConfirmMessage': '「{title}」を削除しますか?元に戻せません。',
+ 'journey.deleteConfirmGeneric': '本当に削除しますか?',
+ 'journey.notFound': '日記が見つかりません',
+ 'journey.photos': '写真',
+ 'journey.timelineEmpty': 'まだ立ち寄りがありません',
+ 'journey.timelineEmptyHint': 'チェックインするか、日記を書いて始めましょう',
+ 'journey.status.draft': '下書き',
+ 'journey.status.active': '進行中',
+ 'journey.status.completed': '完了',
+ 'journey.status.upcoming': '予定',
+ 'journey.status.archived': 'アーカイブ',
+ 'journey.checkin.add': 'チェックイン',
+ 'journey.checkin.namePlaceholder': '場所名',
+ 'journey.checkin.notesPlaceholder': 'メモ(任意)',
+ 'journey.checkin.save': '保存',
+ 'journey.checkin.error': 'チェックインを保存できませんでした',
+ 'journey.entry.add': '日記',
+ 'journey.entry.edit': '編集',
+ 'journey.entry.titlePlaceholder': 'タイトル(任意)',
+ 'journey.entry.bodyPlaceholder': '今日は何がありましたか?',
+ 'journey.entry.save': '保存',
+ 'journey.entry.error': '日記を保存できませんでした',
+ 'journey.photo.add': '写真',
+ 'journey.photo.uploadError': 'アップロードに失敗しました',
+ 'journey.share.share': '共有',
+ 'journey.share.public': '公開',
+ 'journey.share.linkCopied': '公開リンクをコピーしました',
+ 'journey.share.disabled': '公開共有は無効です',
+ 'journey.editor.titlePlaceholder': 'この瞬間に名前をつけて…',
+ 'journey.editor.bodyPlaceholder': 'この日のストーリーを書いてみよう…',
+ 'journey.editor.placePlaceholder': '場所(任意)',
+ 'journey.editor.tagsPlaceholder': 'タグ:穴場、最高の食事、また行きたい…',
+ 'journey.visibility.private': '非公開',
+ 'journey.visibility.shared': '共有',
+ 'journey.visibility.public': '公開',
+ 'journey.emptyState.title': 'ここから物語が始まります',
+ 'journey.emptyState.subtitle':
+ '場所にチェックインするか、最初の日記を書いてみましょう',
+ 'journey.frontpage.subtitle': '旅を、忘れられない物語に',
+ 'journey.frontpage.createJourney': '日記を作成',
+ 'journey.frontpage.activeJourney': '進行中の日記',
+ 'journey.frontpage.allJourneys': 'すべての日記',
+ 'journey.frontpage.journeys': '日記',
+ 'journey.frontpage.createNew': '新しい日記を作成',
+ 'journey.frontpage.createNewSub': '旅を選んで、物語を書き、共有しよう',
+ 'journey.frontpage.live': 'ライブ',
+ 'journey.frontpage.synced': '同期済み',
+ 'journey.frontpage.continueWriting': '続けて書く',
+ 'journey.frontpage.updated': '{time}に更新',
+ 'journey.frontpage.suggestionLabel': '旅行が終了しました',
+ 'journey.frontpage.suggestionText': '{title}を日記にしよう',
+ 'journey.frontpage.dismiss': '閉じる',
+ 'journey.frontpage.journeyName': '日記名',
+ 'journey.frontpage.namePlaceholder': '例:東南アジア 2026',
+ 'journey.frontpage.selectTrips': '旅行を選択',
+ 'journey.frontpage.tripsSelected': '件選択',
+ 'journey.frontpage.trips': '旅行',
+ 'journey.frontpage.placesImported': '場所がインポートされます',
+ 'journey.frontpage.places': '場所',
+ 'journey.detail.backToJourney': '日記に戻る',
+ 'journey.detail.syncedWithTrips': '旅行と同期済み',
+ 'journey.detail.addEntry': 'エントリーを追加',
+ 'journey.detail.newEntry': '新しいエントリー',
+ 'journey.detail.editEntry': 'エントリーを編集',
+ 'journey.detail.noEntries': 'エントリーはまだありません',
+ 'journey.detail.noEntriesHint':
+ '旅行を追加して下書きエントリーを作成しましょう',
+ 'journey.detail.noPhotos': '写真はまだありません',
+ 'journey.detail.noPhotosHint':
+ 'エントリーに写真を追加するか、Immich/Synologyライブラリを表示',
+ 'journey.detail.journeyTab': '日記',
+ 'journey.detail.journeyStats': '統計',
+ 'journey.detail.syncedTrips': '同期中の旅行',
+ 'journey.detail.noTripsLinked': 'リンクされた旅行はありません',
+ 'journey.detail.contributors': '参加者',
+ 'journey.detail.readMore': 'もっと見る',
+ 'journey.detail.prosCons': '良かった点・気になった点',
+ 'journey.detail.photos': '写真',
+ 'journey.detail.day': '{number}日目',
+ 'journey.detail.places': '場所',
+ 'journey.stats.days': '日数',
+ 'journey.stats.cities': '都市',
+ 'journey.stats.entries': 'エントリー',
+ 'journey.stats.photos': '写真',
+ 'journey.stats.places': '場所',
+ 'journey.skeletons.show': '提案を表示',
+ 'journey.skeletons.hide': '提案を非表示',
+ 'journey.verdict.lovedIt': '最高だった',
+ 'journey.verdict.couldBeBetter': '改善の余地あり',
+ 'journey.synced.places': '場所',
+ 'journey.synced.synced': '同期済み',
+ 'journey.editor.discardChangesConfirm':
+ '未保存の変更があります。破棄しますか?',
+ 'journey.editor.uploadPhotos': '写真をアップロード',
+ 'journey.editor.uploading': 'アップロード中…',
+ 'journey.editor.fromGallery': 'ギャラリーから',
+ 'journey.editor.allPhotosAdded': 'すべての写真は追加済みです',
+ 'journey.editor.writeStory': 'ストーリーを書く…',
+ 'journey.editor.prosCons': '良かった点・気になった点',
+ 'journey.editor.pros': '良かった点',
+ 'journey.editor.cons': '気になった点',
+ 'journey.editor.proPlaceholder': '良かったこと…',
+ 'journey.editor.conPlaceholder': 'いまいちだったこと…',
+ 'journey.editor.addAnother': '追加',
+ 'journey.editor.date': '日付',
+ 'journey.editor.location': '場所',
+ 'journey.editor.searchLocation': '場所を検索…',
+ 'journey.editor.mood': '気分',
+ 'journey.editor.weather': '天気',
+ 'journey.editor.photoFirst': '1番目',
+ 'journey.editor.makeFirst': '1番目にする',
+ 'journey.editor.searching': '検索中…',
+ 'journey.mood.amazing': '最高',
+ 'journey.mood.good': '良い',
+ 'journey.mood.neutral': '普通',
+ 'journey.mood.rough': '大変',
+ 'journey.weather.sunny': '晴れ',
+ 'journey.weather.partly': '晴れ時々くもり',
+ 'journey.weather.cloudy': 'くもり',
+ 'journey.weather.rainy': '雨',
+ 'journey.weather.stormy': '嵐',
+ 'journey.weather.cold': '雪',
+ 'journey.trips.linkTrip': '旅行をリンク',
+ 'journey.trips.searchTrip': '旅行を検索',
+ 'journey.trips.searchPlaceholder': '旅行名または目的地…',
+ 'journey.trips.noTripsAvailable': '利用できる旅行がありません',
+ 'journey.trips.link': 'リンク',
+ 'journey.trips.tripLinked': '旅行をリンクしました',
+ 'journey.trips.linkFailed': 'リンクに失敗しました',
+ 'journey.trips.addTrip': '旅行を追加',
+ 'journey.trips.unlinkTrip': 'リンク解除',
+ 'journey.trips.unlinkMessage':
+ '「{title}」のリンクを解除しますか?この旅行から同期されたエントリーと写真はすべて完全に削除されます。元に戻せません。',
+ 'journey.trips.unlink': '解除',
+ 'journey.trips.tripUnlinked': 'リンクを解除しました',
+ 'journey.trips.unlinkFailed': '解除に失敗しました',
+ 'journey.trips.noTripsLinkedSettings': 'リンクされた旅行はありません',
+ 'journey.contributors.invite': '参加者を招待',
+ 'journey.contributors.searchUser': 'ユーザーを検索',
+ 'journey.contributors.searchPlaceholder': 'ユーザー名またはメール…',
+ 'journey.contributors.noUsers': 'ユーザーが見つかりません',
+ 'journey.contributors.role': '役割',
+ 'journey.contributors.added': '参加者を追加しました',
+ 'journey.contributors.addFailed': '追加に失敗しました',
+ 'journey.contributors.remove': '参加者を削除',
+ 'journey.contributors.removeConfirm':
+ '{username}をこの日記から削除しますか?',
+ 'journey.contributors.removed': '参加者を削除しました',
+ 'journey.contributors.removeFailed': '削除に失敗しました',
+ 'journey.share.publicShare': '公開共有',
+ 'journey.share.createLink': '共有リンクを作成',
+ 'journey.share.linkCreated': '共有リンクを作成しました',
+ 'journey.share.createFailed': 'リンク作成に失敗しました',
+ 'journey.share.copy': 'コピー',
+ 'journey.share.copied': 'コピーしました!',
+ 'journey.share.timeline': 'タイムライン',
+ 'journey.share.gallery': 'ギャラリー',
+ 'journey.share.map': 'マップ',
+ 'journey.share.removeLink': '共有リンクを削除',
+ 'journey.share.linkDeleted': '共有リンクを削除しました',
+ 'journey.share.deleteFailed': '削除に失敗しました',
+ 'journey.share.updateFailed': '更新に失敗しました',
+ 'journey.invite.role': '役割',
+ 'journey.invite.viewer': '閲覧者',
+ 'journey.invite.editor': '編集者',
+ 'journey.invite.invite': '招待',
+ 'journey.invite.inviting': '招待中…',
+ 'journey.settings.title': '日記設定',
+ 'journey.settings.coverImage': 'カバー画像',
+ 'journey.settings.changeCover': 'カバーを変更',
+ 'journey.settings.addCover': 'カバー画像を追加',
+ 'journey.settings.name': '名前',
+ 'journey.settings.subtitle': 'サブタイトル',
+ 'journey.settings.subtitlePlaceholder': '例:タイ・ベトナム・カンボジア',
+ 'journey.settings.endJourney': '日記をアーカイブ',
+ 'journey.settings.reopenJourney': '日記を復元',
+ 'journey.settings.archived': '日記をアーカイブしました',
+ 'journey.settings.reopened': '日記を復元しました',
+ 'journey.settings.endDescription':
+ 'Liveバッジを非表示にします。いつでも再開できます。',
+ 'journey.settings.delete': '削除',
+ 'journey.settings.deleteJourney': '日記を削除',
+ 'journey.settings.deleteMessage':
+ '「{title}」を削除しますか?すべてのエントリーと写真が失われます。',
+ 'journey.settings.saved': '設定を保存しました',
+ 'journey.settings.saveFailed': '保存に失敗しました',
+ 'journey.settings.coverUpdated': 'カバーを更新しました',
+ 'journey.settings.coverFailed': 'アップロードに失敗しました',
+ 'journey.settings.failedToDelete': '削除に失敗しました',
+ 'journey.entries.deleteTitle': 'エントリーを削除',
+ 'journey.photosUploaded': '{count}枚の写真をアップロード',
+ 'journey.photosAdded': '{count}枚の写真を追加',
+ 'journey.public.notFound': '見つかりません',
+ 'journey.public.notFoundMessage':
+ 'この日記は存在しないか、リンクの有効期限が切れています。',
+ 'journey.public.readOnly': '閲覧のみ · 公開日記',
+ 'journey.public.tagline': '旅の記録&探索キット',
+ 'journey.public.sharedVia': '共有元',
+ 'journey.public.madeWith': '作成:',
+ 'journey.pdf.journeyBook': '日記ブック',
+ 'journey.pdf.madeWith': 'Made with TREK',
+ 'journey.pdf.day': '日目',
+ 'journey.pdf.theEnd': 'おわり',
+ 'journey.pdf.saveAsPdf': 'PDFとして保存',
+ 'journey.pdf.pages': 'ページ',
+ 'journey.picker.tripPeriod': '旅行期間',
+ 'journey.picker.dateRange': '日付範囲',
+ 'journey.picker.allPhotos': 'すべての写真',
+ 'journey.picker.albums': 'アルバム',
+ 'journey.picker.selected': '選択中',
+ 'journey.picker.addTo': '追加先',
+ 'journey.picker.newGallery': '新しいギャラリー',
+ 'journey.picker.selectAll': 'すべて選択',
+ 'journey.picker.deselectAll': '選択解除',
+ 'journey.picker.noAlbums': 'アルバムがありません',
+ 'journey.picker.selectDate': '日付を選択',
+ 'journey.picker.search': '検索',
+ 'journey.editor.uploadingProgress': 'アップロード中 {done}/{total}…',
+ 'journey.editor.uploadFailed': '写真のアップロードに失敗しました',
+ 'journey.editor.uploadPartialFailed':
+ '{total}枚中{failed}枚の写真がアップロードに失敗しました — もう一度保存して再試行してください',
+ 'journey.photosUploadFailed': '一部の写真をアップロードできませんでした',
+};
+export default journey;
diff --git a/shared/src/i18n/ja/login.ts b/shared/src/i18n/ja/login.ts
new file mode 100644
index 0000000000000000000000000000000000000000..811bff2a6b0327ea0b3649fcd02063f3f702a4d0
--- /dev/null
+++ b/shared/src/i18n/ja/login.ts
@@ -0,0 +1,99 @@
+import type { TranslationStrings } from '../types';
+
+const login: TranslationStrings = {
+ 'login.error': 'ログインに失敗しました。認証情報を確認してください。',
+ 'login.tagline': 'あなたの旅行。\nあなたの計画。',
+ 'login.description':
+ 'インタラクティブなマップ、予算、リアルタイム同期で、みんなで旅行を計画。',
+ 'login.features.maps': 'インタラクティブマップ',
+ 'login.features.mapsDesc': 'Google Places、経路、クラスタリング',
+ 'login.features.realtime': 'リアルタイム同期',
+ 'login.features.realtimeDesc': 'WebSocketで共同計画',
+ 'login.features.budget': '予算管理',
+ 'login.features.budgetDesc': 'カテゴリ、グラフ、人数別費用',
+ 'login.features.collab': 'コラボレーション',
+ 'login.features.collabDesc': '複数ユーザーで旅行を共有',
+ 'login.features.packing': '持ち物リスト',
+ 'login.features.packingDesc': 'カテゴリ、進捗、提案',
+ 'login.features.bookings': '予約',
+ 'login.features.bookingsDesc': '航空券、ホテル、レストランなど',
+ 'login.features.files': 'ドキュメント',
+ 'login.features.filesDesc': '書類のアップロードと管理',
+ 'login.features.routes': 'スマート経路',
+ 'login.features.routesDesc': '自動最適化&Google Maps書き出し',
+ 'login.selfHosted': 'セルフホスト · オープンソース · データはあなたのもの',
+ 'login.title': 'サインイン',
+ 'login.subtitle': 'おかえりなさい',
+ 'login.signingIn': 'サインイン中…',
+ 'login.signIn': 'サインイン',
+ 'login.createAdmin': '管理者アカウントを作成',
+ 'login.createAdminHint': 'TREKの最初の管理者アカウントを設定します。',
+ 'login.setNewPassword': '新しいパスワードを設定',
+ 'login.setNewPasswordHint': '続行する前にパスワードを変更してください。',
+ 'login.createAccount': 'アカウントを作成',
+ 'login.createAccountHint': '新しいアカウントを登録。',
+ 'login.creating': '作成中…',
+ 'login.noAccount': 'アカウントをお持ちでないですか?',
+ 'login.hasAccount': 'すでにアカウントをお持ちですか?',
+ 'login.register': '登録',
+ 'login.emailPlaceholder': 'your@email.com',
+ 'login.username': 'ユーザー名',
+ 'login.oidc.registrationDisabled':
+ '登録は無効です。管理者に連絡してください。',
+ 'login.oidc.noEmail': 'プロバイダーからメールが取得できませんでした。',
+ 'login.oidc.tokenFailed': '認証に失敗しました。',
+ 'login.oidc.invalidState': 'セッションが無効です。もう一度お試しください。',
+ 'login.demoFailed': 'デモログインに失敗しました',
+ 'login.oidcSignIn': '{name}でサインイン',
+ 'login.oidcOnly':
+ 'パスワード認証は無効です。SSOプロバイダーでサインインしてください。',
+ 'login.oidcLoggedOut':
+ 'ログアウトしました。SSOプロバイダーで再度サインインしてください。',
+ 'login.demoHint': 'デモを試す — 登録不要',
+ 'login.mfaTitle': '二要素認証',
+ 'login.mfaSubtitle': '認証アプリの6桁コードを入力してください。',
+ 'login.mfaCodeLabel': '確認コード',
+ 'login.mfaCodeRequired': '認証アプリのコードを入力してください。',
+ 'login.mfaHint':
+ 'Google Authenticator、Authy などのTOTPアプリを開いてください。',
+ 'login.mfaBack': '← サインインに戻る',
+ 'login.mfaVerify': '確認',
+ 'login.invalidInviteLink': '無効または期限切れの招待リンクです',
+ 'login.oidcFailed': 'OIDCログインに失敗しました',
+ 'login.usernameRequired': 'ユーザー名を入力してください',
+ 'login.passwordMinLength': 'パスワードは8文字以上である必要があります',
+ 'login.forgotPassword': 'パスワードを忘れた場合',
+ 'login.rememberMe': 'ログイン状態を保持する',
+ 'login.forgotPasswordTitle': 'パスワードをリセット',
+ 'login.forgotPasswordBody':
+ '登録時のメールアドレスを入力してください。アカウントが存在する場合、リセット用リンクを送信します。',
+ 'login.forgotPasswordSubmit': 'リセットリンクを送信',
+ 'login.forgotPasswordSentTitle': 'メールを確認してください',
+ 'login.forgotPasswordSentBody':
+ '該当するアカウントがある場合、リセットリンクを送信しました。リンクの有効期限は60分です。',
+ 'login.forgotPasswordSmtpHintOff':
+ '注意:管理者がSMTPを設定していないため、リセットリンクはメールではなくサーバーコンソールに出力されます。',
+ 'login.backToLogin': 'ログインに戻る',
+ 'login.newPassword': '新しいパスワード',
+ 'login.confirmPassword': '新しいパスワード(確認)',
+ 'login.passwordsDontMatch': 'パスワードが一致しません',
+ 'login.mfaCode': '2FAコード',
+ 'login.resetPasswordTitle': '新しいパスワードを設定',
+ 'login.resetPasswordBody':
+ '以前使用していない強力なパスワードを設定してください(8文字以上)。',
+ 'login.resetPasswordMfaBody':
+ '2FAコードまたはバックアップコードを入力してリセットを完了してください。',
+ 'login.resetPasswordSubmit': 'パスワードをリセット',
+ 'login.resetPasswordVerify': '確認してリセット',
+ 'login.resetPasswordSuccessTitle': 'パスワードを更新しました',
+ 'login.resetPasswordSuccessBody': '新しいパスワードでログインできます。',
+ 'login.resetPasswordInvalidLink': '無効なリセットリンク',
+ 'login.resetPasswordInvalidLinkBody':
+ 'リンクが無効または破損しています。新しいリンクをリクエストしてください。',
+ 'login.resetPasswordFailed':
+ 'リセットに失敗しました。リンクの有効期限が切れている可能性があります。',
+ 'login.passkey.signIn': 'パスキーでサインイン',
+ 'login.passkey.failed':
+ 'パスキーでのサインインに失敗しました。もう一度お試しください。',
+};
+export default login;
diff --git a/shared/src/i18n/ja/map.ts b/shared/src/i18n/ja/map.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b7eb503e3d888d532e99f7d4b6f37f5d36118282
--- /dev/null
+++ b/shared/src/i18n/ja/map.ts
@@ -0,0 +1,17 @@
+import type { TranslationStrings } from '../types';
+
+const map: TranslationStrings = {
+ 'map.connections': '接続',
+ 'map.showConnections': '予約ルートを表示',
+ 'map.hideConnections': '予約ルートを非表示',
+ 'poi.searchThisArea': 'このエリアを検索',
+ 'poi.cat.restaurants': 'レストラン',
+ 'poi.cat.cafes': 'カフェ',
+ 'poi.cat.bars': 'バー・ナイトライフ',
+ 'poi.cat.hotels': '宿泊施設',
+ 'poi.cat.sights': '観光スポット',
+ 'poi.cat.museums': '美術館・文化施設',
+ 'poi.cat.nature': '自然・公園',
+ 'poi.cat.activities': 'アクティビティ',
+};
+export default map;
diff --git a/shared/src/i18n/ja/members.ts b/shared/src/i18n/ja/members.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3d749039d9bbd31cbe9811c5c1c6f5b23eef3060
--- /dev/null
+++ b/shared/src/i18n/ja/members.ts
@@ -0,0 +1,24 @@
+import type { TranslationStrings } from '../types';
+
+const members: TranslationStrings = {
+ 'members.shareTrip': '旅行を共有',
+ 'members.inviteUser': 'ユーザーを招待',
+ 'members.selectUser': 'ユーザーを選択…',
+ 'members.invite': '招待',
+ 'members.allHaveAccess': 'すでに全員がアクセスできます。',
+ 'members.access': 'アクセス',
+ 'members.person': '人',
+ 'members.persons': '人',
+ 'members.you': 'あなた',
+ 'members.owner': 'オーナー',
+ 'members.leaveTrip': '旅行を退出',
+ 'members.removeAccess': 'アクセスを削除',
+ 'members.confirmLeave': '旅行を退出しますか?アクセスできなくなります。',
+ 'members.confirmRemove': 'このユーザーのアクセスを削除しますか?',
+ 'members.loadError': 'メンバーの読み込みに失敗しました',
+ 'members.added': '追加しました',
+ 'members.addError': '追加に失敗しました',
+ 'members.removed': 'メンバーを削除しました',
+ 'members.removeError': '削除に失敗しました',
+};
+export default members;
diff --git a/shared/src/i18n/ja/memories.ts b/shared/src/i18n/ja/memories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3103125c59454e530a4bf95cdbf135d6ed95ebbe
--- /dev/null
+++ b/shared/src/i18n/ja/memories.ts
@@ -0,0 +1,79 @@
+import type { TranslationStrings } from '../types';
+
+const memories: TranslationStrings = {
+ 'memories.title': '写真',
+ 'memories.notConnected': '{provider_name} が接続されていません',
+ 'memories.notConnectedHint':
+ '設定で {provider_name} インスタンスを接続すると、この旅行に写真を追加できます。',
+ 'memories.notConnectedMultipleHint':
+ '設定で次の写真プロバイダーのいずれかを接続してください:{provider_names}',
+ 'memories.noDates': '写真を読み込むには旅行の日付を追加してください。',
+ 'memories.noPhotos': '写真が見つかりません',
+ 'memories.noPhotosHint': '{provider_name} にこの旅行期間の写真がありません。',
+ 'memories.photosFound': '枚の写真',
+ 'memories.fromOthers': '他のユーザーから',
+ 'memories.sharePhotos': '写真を共有',
+ 'memories.sharing': '共有',
+ 'memories.reviewTitle': '写真を確認',
+ 'memories.reviewHint': 'クリックして共有から除外できます。',
+ 'memories.shareCount': '{count}枚の写真を共有',
+ 'memories.providerUrl': 'サーバーURL',
+ 'memories.providerApiKey': 'APIキー',
+ 'memories.providerUsername': 'ユーザー名',
+ 'memories.providerPassword': 'パスワード',
+ 'memories.providerOTP': 'MFAコード(有効な場合)',
+ 'memories.skipSSLVerification': 'SSL証明書の検証をスキップ',
+ 'memories.immichAutoUpload': 'アップロード時に旅程の写真をImmichにミラー',
+ 'memories.providerUrlHintSynology':
+ 'URLにPhotosアプリのパスを含めてください(例:https://nas:5001/photo)',
+ 'memories.testConnection': '接続をテスト',
+ 'memories.testFirst': '先に接続をテストしてください',
+ 'memories.testShort': 'テスト',
+ 'memories.connected': '接続済み',
+ 'memories.disconnected': '未接続',
+ 'memories.connectionSuccess': '{provider_name} に接続しました',
+ 'memories.connectionError': '{provider_name} に接続できませんでした',
+ 'memories.saved': '{provider_name} の設定を保存しました',
+ 'memories.providerDisconnectedBanner':
+ '{provider_name} との接続が切れています。写真を見るには設定で再接続してください。',
+ 'memories.saveError': '{provider_name} の設定を保存できませんでした',
+ 'memories.addPhotos': '写真を追加',
+ 'memories.linkAlbum': 'アルバムをリンク',
+ 'memories.selectAlbum': '{provider_name} のアルバムを選択',
+ 'memories.selectAlbumMultiple': 'アルバムを選択',
+ 'memories.noAlbums': 'アルバムが見つかりません',
+ 'memories.syncAlbum': 'アルバムを同期',
+ 'memories.unlinkAlbum': 'アルバムのリンクを解除',
+ 'memories.photos': '写真',
+ 'memories.selectPhotos': '{provider_name} から写真を選択',
+ 'memories.selectPhotosMultiple': '写真を選択',
+ 'memories.selectHint': '写真をタップして選択してください。',
+ 'memories.selected': '選択済み',
+ 'memories.addSelected': '{count} 枚の写真を追加',
+ 'memories.alreadyAdded': '追加済み',
+ 'memories.private': '非公開',
+ 'memories.stopSharing': '共有を停止',
+ 'memories.oldest': '古い順',
+ 'memories.newest': '新しい順',
+ 'memories.allLocations': 'すべての場所',
+ 'memories.tripDates': '旅行期間',
+ 'memories.allPhotos': 'すべての写真',
+ 'memories.confirmShareTitle': '旅行メンバーと共有しますか?',
+ 'memories.confirmShareHint':
+ '{count} 枚の写真がこの旅行の全メンバーに表示されます。後から個別に非公開にできます。',
+ 'memories.confirmShareButton': '写真を共有',
+ 'memories.error.loadAlbums': 'アルバムの読み込みに失敗しました',
+ 'memories.error.linkAlbum': 'アルバムのリンクに失敗しました',
+ 'memories.error.unlinkAlbum': 'アルバムのリンク解除に失敗しました',
+ 'memories.error.syncAlbum': 'アルバムの同期に失敗しました',
+ 'memories.error.loadPhotos': '写真の読み込みに失敗しました',
+ 'memories.error.addPhotos': '写真の追加に失敗しました',
+ 'memories.error.removePhoto': '写真の削除に失敗しました',
+ 'memories.error.toggleSharing': '共有設定の更新に失敗しました',
+ 'memories.saveRouteNotConfigured':
+ 'このプロバイダーでは保存先が設定されていません',
+ 'memories.testRouteNotConfigured':
+ 'このプロバイダーではテスト用の保存先が設定されていません',
+ 'memories.fillRequiredFields': '必須項目をすべて入力してください',
+};
+export default memories;
diff --git a/shared/src/i18n/ja/nav.ts b/shared/src/i18n/ja/nav.ts
new file mode 100644
index 0000000000000000000000000000000000000000..67ee82b992fbb71aaede9bf0fe162063d04f0cb8
--- /dev/null
+++ b/shared/src/i18n/ja/nav.ts
@@ -0,0 +1,20 @@
+import type { TranslationStrings } from '../types';
+
+const nav: TranslationStrings = {
+ 'nav.trip': '旅行',
+ 'nav.share': '共有',
+ 'nav.settings': '設定',
+ 'nav.admin': '管理',
+ 'nav.logout': 'ログアウト',
+ 'nav.lightMode': 'ライトモード',
+ 'nav.darkMode': 'ダークモード',
+ 'nav.autoMode': '自動',
+ 'nav.administrator': '管理者',
+ 'nav.myTrips': 'マイ旅行',
+ 'nav.profile': 'プロフィール',
+ 'nav.bottomSettings': '設定',
+ 'nav.bottomAdmin': '管理者設定',
+ 'nav.bottomLogout': 'ログアウト',
+ 'nav.bottomAdminBadge': '管理者',
+};
+export default nav;
diff --git a/shared/src/i18n/ja/notif.ts b/shared/src/i18n/ja/notif.ts
new file mode 100644
index 0000000000000000000000000000000000000000..44dc6a93e9efeaed735a8f47cc654554df58d26f
--- /dev/null
+++ b/shared/src/i18n/ja/notif.ts
@@ -0,0 +1,44 @@
+import type { TranslationStrings } from '../types';
+
+const notif: TranslationStrings = {
+ 'notif.test.title': '[テスト] 通知',
+ 'notif.test.simple.text': 'これはシンプルなテスト通知です。',
+ 'notif.test.boolean.text': 'このテスト通知を承認しますか?',
+ 'notif.test.navigate.text':
+ '下をクリックしてダッシュボードに移動してください。',
+ 'notif.trip_invite.title': '旅行への招待',
+ 'notif.trip_invite.text': '{actor}が「{trip}」に招待しました',
+ 'notif.booking_change.title': '予約が更新されました',
+ 'notif.booking_change.text': '{actor}が「{trip}」の予約を更新しました',
+ 'notif.trip_reminder.title': '旅行リマインド',
+ 'notif.trip_reminder.text': '旅行「{trip}」がまもなく始まります!',
+ 'notif.todo_due.title': 'ToDoの期限',
+ 'notif.todo_due.text': '「{trip}」の{todo}は{due}が期限です',
+ 'notif.vacay_invite.title': 'Vacay Fusionへの招待',
+ 'notif.vacay_invite.text': '{actor}から旅行プランの統合に招待されました',
+ 'notif.photos_shared.title': '写真が共有されました',
+ 'notif.photos_shared.text':
+ '{actor}が「{trip}」で{count}枚の写真を共有しました',
+ 'notif.collab_message.title': '新しいメッセージ',
+ 'notif.collab_message.text': '{actor}が「{trip}」でメッセージを送りました',
+ 'notif.packing_tagged.title': '持ち物の割り当て',
+ 'notif.packing_tagged.text':
+ '{actor}が「{trip}」の{category}をあなたに割り当てました',
+ 'notif.version_available.title': '新しいバージョンがあります',
+ 'notif.version_available.text': 'TREK {version}が利用可能です',
+ 'notif.action.view_trip': '旅行を見る',
+ 'notif.action.view_collab': 'メッセージを見る',
+ 'notif.action.view_packing': '持ち物を見る',
+ 'notif.action.view_photos': '写真を見る',
+ 'notif.action.view_vacay': 'Vacayを見る',
+ 'notif.action.view_admin': '管理画面へ',
+ 'notif.action.view': '表示',
+ 'notif.action.accept': '承認',
+ 'notif.action.decline': '拒否',
+ 'notif.generic.title': '通知',
+ 'notif.generic.text': '新しい通知があります',
+ 'notif.dev.unknown_event.title': '[DEV] 不明なイベント',
+ 'notif.dev.unknown_event.text':
+ 'イベントタイプ「{event}」はEVENT_NOTIFICATION_CONFIGに登録されていません',
+};
+export default notif;
diff --git a/shared/src/i18n/ja/notifications.ts b/shared/src/i18n/ja/notifications.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5157c3c047659dea52842f0cdc5fd5ee8e6bebd9
--- /dev/null
+++ b/shared/src/i18n/ja/notifications.ts
@@ -0,0 +1,37 @@
+import type { TranslationStrings } from '../types';
+
+const notifications: TranslationStrings = {
+ 'notifications.title': '通知',
+ 'notifications.markAllRead': 'すべて既読',
+ 'notifications.deleteAll': 'すべて削除',
+ 'notifications.showAll': 'すべて表示',
+ 'notifications.empty': '通知はありません',
+ 'notifications.emptyDescription': 'すべて確認済みです!',
+ 'notifications.all': 'すべて',
+ 'notifications.unreadOnly': '未読',
+ 'notifications.markRead': '既読にする',
+ 'notifications.markUnread': '未読にする',
+ 'notifications.delete': '削除',
+ 'notifications.system': 'システム',
+ 'notifications.synologySessionCleared.title':
+ 'Synology Photosが切断されました',
+ 'notifications.synologySessionCleared.text':
+ 'サーバーまたはアカウントが変更されました。設定で接続を再テストしてください。',
+ 'notifications.versionAvailable.title': '更新があります',
+ 'notifications.versionAvailable.text': 'TREK {version} が利用可能です。',
+ 'notifications.versionAvailable.button': '詳細を見る',
+ '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 notifications;
diff --git a/shared/src/i18n/ja/oauth.ts b/shared/src/i18n/ja/oauth.ts
new file mode 100644
index 0000000000000000000000000000000000000000..afcb6ad929f182ccbce75959e66b50097cff21a3
--- /dev/null
+++ b/shared/src/i18n/ja/oauth.ts
@@ -0,0 +1,107 @@
+import type { TranslationStrings } from '../types';
+
+const oauth: TranslationStrings = {
+ 'oauth.scope.group.trips': '旅行',
+ 'oauth.scope.group.places': '場所',
+ 'oauth.scope.group.atlas': '地図',
+ 'oauth.scope.group.packing': '持ち物',
+ 'oauth.scope.group.todos': 'ToDo',
+ 'oauth.scope.group.budget': '予算',
+ 'oauth.scope.group.reservations': '予約',
+ 'oauth.scope.group.collab': 'コラボ',
+ 'oauth.scope.group.notifications': '通知',
+ 'oauth.scope.group.vacay': '休暇',
+ 'oauth.scope.group.geo': '地図',
+ 'oauth.scope.group.weather': '天気',
+ 'oauth.scope.group.journey': '日記',
+ 'oauth.scope.trips:read.label': '旅行・旅程を表示',
+ 'oauth.scope.trips:read.description': '旅行、日程、メモ、メンバーを閲覧',
+ 'oauth.scope.trips:write.label': '旅行・旅程を編集',
+ 'oauth.scope.trips:write.description':
+ '旅行や日程、メモの作成・更新、メンバー管理',
+ 'oauth.scope.trips:delete.label': '旅行を削除',
+ 'oauth.scope.trips:delete.description':
+ '旅行全体を完全に削除(元に戻せません)',
+ 'oauth.scope.trips:share.label': '共有リンクを管理',
+ 'oauth.scope.trips:share.description':
+ '旅行の公開共有リンクを作成・更新・無効化',
+ 'oauth.scope.places:read.label': '場所・地図データを表示',
+ 'oauth.scope.places:read.description':
+ '場所、日への割り当て、タグ、カテゴリを閲覧',
+ 'oauth.scope.places:write.label': '場所を管理',
+ 'oauth.scope.places:write.description':
+ '場所、割り当て、タグの作成・更新・削除',
+ 'oauth.scope.atlas:read.label': '地図を表示',
+ 'oauth.scope.atlas:read.description':
+ '訪問した国・地域、バケットリストを閲覧',
+ 'oauth.scope.atlas:write.label': '地図を管理',
+ 'oauth.scope.atlas:write.description':
+ '訪問済みの国・地域を管理、バケットリスト編集',
+ 'oauth.scope.packing:read.label': '持ち物リストを表示',
+ 'oauth.scope.packing:read.description': '持ち物、バッグ、担当者を閲覧',
+ 'oauth.scope.packing:write.label': '持ち物リストを管理',
+ 'oauth.scope.packing:write.description':
+ '持ち物やバッグの追加・編集・削除・並び替え',
+ 'oauth.scope.todos:read.label': 'ToDoリストを表示',
+ 'oauth.scope.todos:read.description': '旅行のToDoと担当者を閲覧',
+ 'oauth.scope.todos:write.label': 'ToDoリストを管理',
+ 'oauth.scope.todos:write.description':
+ 'ToDoの作成・編集・完了・削除・並び替え',
+ 'oauth.scope.budget:read.label': '予算を表示',
+ 'oauth.scope.budget:read.description': '予算項目や内訳を閲覧',
+ 'oauth.scope.budget:write.label': '予算を管理',
+ 'oauth.scope.budget:write.description': '予算項目の作成・編集・削除',
+ 'oauth.scope.reservations:read.label': '予約を表示',
+ 'oauth.scope.reservations:read.description': '予約や宿泊情報を閲覧',
+ 'oauth.scope.reservations:write.label': '予約を管理',
+ 'oauth.scope.reservations:write.description':
+ '予約の作成・編集・削除・並び替え',
+ 'oauth.scope.collab:read.label': 'コラボを表示',
+ 'oauth.scope.collab:read.description': '共同メモ、投票、メッセージを閲覧',
+ 'oauth.scope.collab:write.label': 'コラボを管理',
+ 'oauth.scope.collab:write.description': '共同メモ、投票、メッセージを管理',
+ 'oauth.scope.notifications:read.label': '通知を表示',
+ 'oauth.scope.notifications:read.description': 'アプリ内通知と未読数を閲覧',
+ 'oauth.scope.notifications:write.label': '通知を管理',
+ 'oauth.scope.notifications:write.description': '通知を既読にする・対応する',
+ 'oauth.scope.vacay:read.label': '休暇プランを表示',
+ 'oauth.scope.vacay:read.description': '休暇プランのデータや統計を閲覧',
+ 'oauth.scope.vacay:write.label': '休暇プランを管理',
+ 'oauth.scope.vacay:write.description': '休暇エントリーや予定を管理',
+ 'oauth.scope.geo:read.label': '地図・ジオコーディング',
+ 'oauth.scope.geo:read.description':
+ '場所検索、地図URL解析、逆ジオコーディング',
+ 'oauth.scope.weather:read.label': '天気予報',
+ 'oauth.scope.weather:read.description': '旅行先・日程の天気予報を取得',
+ 'oauth.scope.journey:read.label': '日記を表示',
+ 'oauth.scope.journey:read.description': '日記、エントリー、参加者を閲覧',
+ 'oauth.scope.journey:write.label': '日記を管理',
+ 'oauth.scope.journey:write.description': '日記やエントリーの作成・編集・削除',
+ 'oauth.scope.journey:share.label': '日記共有を管理',
+ 'oauth.scope.journey:share.description': '公開共有リンクの作成・更新・無効化',
+ 'oauth.authorize.authorizing': 'Authorizing…', // en-fallback
+ 'oauth.authorize.loading': 'Loading…', // en-fallback
+ 'oauth.authorize.errorTitle': 'Authorization Error', // en-fallback
+ 'oauth.authorize.loginTitle': 'Sign in to continue', // en-fallback
+ 'oauth.authorize.loginDescription':
+ '{client} wants access to your TREK account. Please sign in first.', // en-fallback
+ 'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
+ 'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
+ 'oauth.authorize.requestDescription':
+ 'This application is requesting access to your TREK account.', // en-fallback
+ 'oauth.authorize.trustNote':
+ 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
+ 'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
+ 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
+ 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
+ 'oauth.authorize.deny': 'Deny', // en-fallback
+ 'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
+ 'oauth.authorize.permissionsRequested': 'Permissions requested', // en-fallback
+ 'oauth.authorize.alwaysIncluded': 'Always included', // en-fallback
+ 'oauth.authorize.alwaysTool.listTrips':
+ 'List your trips so the AI can discover trip IDs', // en-fallback
+ 'oauth.authorize.alwaysTool.getTripSummary':
+ 'Read a trip overview needed to use any other tool', // en-fallback
+};
+export default oauth;
diff --git a/shared/src/i18n/ja/packing.ts b/shared/src/i18n/ja/packing.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2ef3fd08a3cb68e088fa514926e7fa0b2d12549d
--- /dev/null
+++ b/shared/src/i18n/ja/packing.ts
@@ -0,0 +1,185 @@
+import type { TranslationStrings } from '../types';
+
+const packing: TranslationStrings = {
+ 'packing.title': '持ち物リスト',
+ 'packing.empty': '持ち物リストは空です',
+ 'packing.import': 'インポート',
+ 'packing.importTitle': '持ち物リストをインポート',
+ 'packing.importHint':
+ '1行につき1項目。形式:カテゴリ, 名前, 重量(g・任意), バッグ(任意), checked/unchecked(任意)',
+ 'packing.importPlaceholder':
+ '衛生用品, 歯ブラシ\n衣類, Tシャツ, 200\n書類, パスポート, , 機内持ち込み\n電子機器, 充電器, 50, スーツケース, checked',
+ 'packing.importCsv': 'CSV/TXTを読み込む',
+ 'packing.importAction': '{count}件をインポート',
+ 'packing.importSuccess': '{count}件インポートしました',
+ 'packing.importError': 'インポートに失敗しました',
+ 'packing.importEmpty': 'インポートする項目がありません',
+ 'packing.progress': '{packed}/{total} 梱包済み({percent}%)',
+ 'packing.clearChecked': 'チェック済み{count}件を削除',
+ 'packing.clearCheckedShort': '{count}件を削除',
+ 'packing.suggestions': 'おすすめ',
+ 'packing.suggestionsTitle': 'おすすめを追加',
+ 'packing.allSuggested': 'おすすめはすべて追加済み',
+ 'packing.allPacked': 'すべて梱包済み!',
+ 'packing.addPlaceholder': '新しい項目を追加...',
+ 'packing.categoryPlaceholder': 'カテゴリ...',
+ 'packing.filterAll': 'すべて',
+ 'packing.filterOpen': '未完了',
+ 'packing.filterDone': '完了',
+ 'packing.emptyTitle': '持ち物リストは空です',
+ 'packing.emptyHint': '項目を追加するか、おすすめを使いましょう',
+ 'packing.emptyFiltered': 'このフィルターに一致する項目はありません',
+ 'packing.menuRename': '名前を変更',
+ 'packing.menuCheckAll': 'すべてチェック',
+ 'packing.menuUncheckAll': 'すべて解除',
+ 'packing.menuDeleteCat': 'カテゴリを削除',
+ 'packing.noMembers': '旅行メンバーがいません',
+ 'packing.addItem': '項目を追加',
+ 'packing.addItemPlaceholder': '項目名...',
+ 'packing.addCategory': 'カテゴリを追加',
+ 'packing.newCategoryPlaceholder': 'カテゴリ名(例:衣類)',
+ 'packing.applyTemplate': 'テンプレートを適用',
+ 'packing.template': 'テンプレート',
+ 'packing.templateApplied': 'テンプレートから{count}件追加しました',
+ 'packing.templateError': 'テンプレートの適用に失敗しました',
+ 'packing.saveAsTemplate': 'テンプレートとして保存',
+ 'packing.templateName': 'テンプレート名',
+ 'packing.templateSaved': '持ち物リストをテンプレートとして保存しました',
+ 'packing.bags': 'バッグ',
+ 'packing.noBag': '未割り当て',
+ 'packing.totalWeight': '総重量',
+ 'packing.bagName': 'バッグ名...',
+ 'packing.addBag': 'バッグを追加',
+ 'packing.changeCategory': 'カテゴリを変更',
+ 'packing.confirm.clearChecked': 'チェック済み{count}件を削除しますか?',
+ 'packing.confirm.deleteCat':
+ '{count}件の項目があるカテゴリ「{name}」を削除しますか?',
+ 'packing.defaultCategory': 'その他',
+ 'packing.toast.saveError': '保存に失敗しました',
+ 'packing.toast.deleteError': '削除に失敗しました',
+ 'packing.toast.renameError': '名前の変更に失敗しました',
+ 'packing.toast.addError': '追加に失敗しました',
+ 'packing.suggestions.items': [
+ {
+ name: 'パスポート',
+ category: '書類',
+ },
+ {
+ name: '身分証明書',
+ category: '書類',
+ },
+ {
+ name: '海外旅行保険',
+ category: '書類',
+ },
+ {
+ name: '航空券',
+ category: '書類',
+ },
+ {
+ name: 'クレジットカード',
+ category: '金融',
+ },
+ {
+ name: '現金',
+ category: '金融',
+ },
+ {
+ name: 'ビザ',
+ category: '書類',
+ },
+ {
+ name: 'Tシャツ',
+ category: '衣類',
+ },
+ {
+ name: 'ズボン',
+ category: '衣類',
+ },
+ {
+ name: '下着',
+ category: '衣類',
+ },
+ {
+ name: '靴下',
+ category: '衣類',
+ },
+ {
+ name: '上着',
+ category: '衣類',
+ },
+ {
+ name: '寝間着',
+ category: '衣類',
+ },
+ {
+ name: '水着',
+ category: '衣類',
+ },
+ {
+ name: 'レインジャケット',
+ category: '衣類',
+ },
+ {
+ name: '歩きやすい靴',
+ category: '衣類',
+ },
+ {
+ name: '歯ブラシ',
+ category: '洗面用具',
+ },
+ {
+ name: '歯磨き粉',
+ category: '洗面用具',
+ },
+ {
+ name: 'シャンプー',
+ category: '洗面用具',
+ },
+ {
+ name: 'デオドラント',
+ category: '洗面用具',
+ },
+ {
+ name: '日焼け止め',
+ category: '洗面用具',
+ },
+ {
+ name: 'カミソリ',
+ category: '洗面用具',
+ },
+ {
+ name: '充電器',
+ category: '電子機器',
+ },
+ {
+ name: 'モバイルバッテリー',
+ category: '電子機器',
+ },
+ {
+ name: 'ヘッドホン',
+ category: '電子機器',
+ },
+ {
+ name: '変換プラグ',
+ category: '電子機器',
+ },
+ {
+ name: 'カメラ',
+ category: '電子機器',
+ },
+ {
+ name: '鎮痛薬',
+ category: '健康',
+ },
+ {
+ name: '絆創膏',
+ category: '健康',
+ },
+ {
+ name: '消毒液',
+ category: '健康',
+ },
+ ],
+};
+export default packing;
diff --git a/shared/src/i18n/ja/pdf.ts b/shared/src/i18n/ja/pdf.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5dd39fda56dc99e434428bbe46ddefecdac7eef3
--- /dev/null
+++ b/shared/src/i18n/ja/pdf.ts
@@ -0,0 +1,10 @@
+import type { TranslationStrings } from '../types';
+
+const pdf: TranslationStrings = {
+ 'pdf.travelPlan': '旅行計画',
+ 'pdf.planned': '予定',
+ 'pdf.costLabel': '費用(EUR)',
+ 'pdf.preview': 'PDFプレビュー',
+ 'pdf.saveAsPdf': 'PDFとして保存',
+};
+export default pdf;
diff --git a/shared/src/i18n/ja/perm.ts b/shared/src/i18n/ja/perm.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eb652c782b6e1628fcd99c4cdf5120c51b42cbab
--- /dev/null
+++ b/shared/src/i18n/ja/perm.ts
@@ -0,0 +1,51 @@
+import type { TranslationStrings } from '../types';
+
+const perm: TranslationStrings = {
+ 'perm.title': '権限設定',
+ 'perm.subtitle': 'アプリ全体の操作権限を管理',
+ 'perm.saved': '権限設定を保存しました',
+ 'perm.resetDefaults': '既定に戻す',
+ 'perm.customized': 'カスタマイズ済み',
+ 'perm.level.admin': '管理者のみ',
+ 'perm.level.tripOwner': '旅行オーナー',
+ 'perm.level.tripMember': '旅行メンバー',
+ 'perm.level.everybody': '全員',
+ 'perm.cat.trip': '旅行管理',
+ 'perm.cat.members': 'メンバー管理',
+ 'perm.cat.files': 'ファイル',
+ 'perm.cat.content': 'コンテンツと予定',
+ 'perm.cat.extras': '予算・持ち物・コラボ',
+ 'perm.action.trip_create': '旅行を作成',
+ 'perm.action.trip_edit': '旅行詳細を編集',
+ 'perm.action.trip_delete': '旅行を削除',
+ 'perm.action.trip_archive': '旅行をアーカイブ/復元',
+ 'perm.action.trip_cover_upload': 'カバー画像をアップロード',
+ 'perm.action.member_manage': 'メンバーを追加/削除',
+ 'perm.action.file_upload': 'ファイルをアップロード',
+ 'perm.action.file_edit': 'ファイル情報を編集',
+ 'perm.action.file_delete': 'ファイルを削除',
+ 'perm.action.place_edit': '場所を追加/編集/削除',
+ 'perm.action.day_edit': '日・メモ・割り当てを編集',
+ 'perm.action.reservation_edit': '予約を管理',
+ 'perm.action.budget_edit': '予算を管理',
+ 'perm.action.packing_edit': '持ち物を管理',
+ 'perm.action.collab_edit': 'コラボ(メモ・投票・チャット)',
+ 'perm.action.share_manage': '共有リンクを管理',
+ 'perm.actionHint.trip_create': '新しい旅行を作成できる人',
+ 'perm.actionHint.trip_edit': '旅行名や日付などを変更できる人',
+ 'perm.actionHint.trip_delete': '旅行を完全に削除できる人',
+ 'perm.actionHint.trip_archive': '旅行をアーカイブできる人',
+ 'perm.actionHint.trip_cover_upload': 'カバー画像を変更できる人',
+ 'perm.actionHint.member_manage': 'メンバーを招待/削除できる人',
+ 'perm.actionHint.file_upload': 'ファイルをアップロードできる人',
+ 'perm.actionHint.file_edit': 'ファイル説明やリンクを編集できる人',
+ 'perm.actionHint.file_delete': 'ファイルをゴミ箱へ移動/完全削除できる人',
+ 'perm.actionHint.place_edit': '場所を追加・編集・削除できる人',
+ 'perm.actionHint.day_edit': '日やメモ、割り当てを編集できる人',
+ 'perm.actionHint.reservation_edit': '予約を作成・編集・削除できる人',
+ 'perm.actionHint.budget_edit': '予算項目を管理できる人',
+ 'perm.actionHint.packing_edit': '持ち物やバッグを管理できる人',
+ 'perm.actionHint.collab_edit': 'メモや投票、メッセージを作成できる人',
+ 'perm.actionHint.share_manage': '公開共有リンクを管理できる人',
+};
+export default perm;
diff --git a/shared/src/i18n/ja/photos.ts b/shared/src/i18n/ja/photos.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a0524650cf539568549356856cf6f0ebe6fa636f
--- /dev/null
+++ b/shared/src/i18n/ja/photos.ts
@@ -0,0 +1,25 @@
+import type { TranslationStrings } from '../types';
+
+const photos: TranslationStrings = {
+ 'photos.title': '写真',
+ 'photos.subtitle': '{trip} の写真 {count} 枚',
+ 'photos.dropHere': 'ここに写真をドロップ…',
+ 'photos.dropHereActive': 'ここに写真をドロップ',
+ 'photos.captionForAll': 'キャプション(全体)',
+ 'photos.captionPlaceholder': '任意のキャプション…',
+ 'photos.addCaption': 'キャプションを追加…',
+ 'photos.allDays': 'すべての日',
+ 'photos.noPhotos': 'まだ写真はありません',
+ 'photos.uploadHint': '旅行の写真をアップロード',
+ 'photos.clickToSelect': 'またはクリックして選択',
+ 'photos.linkPlace': '場所を紐づけ',
+ 'photos.noPlace': '場所なし',
+ 'photos.uploadN': '{n} 枚の写真をアップロード',
+ 'photos.linkDay': '日を紐づけ',
+ 'photos.noDay': '日付なし',
+ 'photos.dayLabel': '{number}日目',
+ 'photos.photoSelected': '写真を選択しました',
+ 'photos.photosSelected': '写真を選択しました',
+ 'photos.fileTypeHint': 'JPG、PNG、WebP · 最大 10 MB · 最大 30 枚',
+};
+export default photos;
diff --git a/shared/src/i18n/ja/places.ts b/shared/src/i18n/ja/places.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6e7e81c715f3816633ed3f75082b212af77fbdb7
--- /dev/null
+++ b/shared/src/i18n/ja/places.ts
@@ -0,0 +1,98 @@
+import type { TranslationStrings } from '../types';
+
+const places: TranslationStrings = {
+ 'places.addPlace': '場所/アクティビティを追加',
+ 'places.importFile': 'ファイルをインポート',
+ 'places.sidebarDrop': 'ドロップしてインポート',
+ 'places.importFileHint':
+ 'Google My Maps、Google Earth、GPSトラッカーなどの .gpx、.kml、.kmz ファイルをインポートできます。',
+ 'places.importFileDropHere':
+ 'クリックしてファイルを選択、またはここにドラッグ&ドロップ',
+ 'places.importFileDropActive': 'ドロップして選択',
+ 'places.importFileUnsupported':
+ '対応していないファイル形式です。.gpx、.kml、.kmz を使用してください。',
+ 'places.importFileTooLarge':
+ 'ファイルが大きすぎます。最大 {maxMb} MB までです。',
+ 'places.importFileError': 'インポートに失敗しました',
+ 'places.importAllSkipped': 'すべての場所は既に旅行に含まれています。',
+ 'places.gpxImported': 'GPXから {count} 件の場所をインポートしました',
+ 'places.gpxImportTypes': '何をインポートしますか?',
+ 'places.gpxImportWaypoints': 'ウェイポイント',
+ 'places.gpxImportRoutes': 'ルート',
+ 'places.gpxImportTracks': 'トラック(経路付き)',
+ 'places.gpxImportNoneSelected': '少なくとも1つ選択してください。',
+ 'places.kmlImportTypes': '何をインポートしますか?',
+ 'places.kmlImportPoints': 'ポイント(プレースマーク)',
+ 'places.kmlImportPaths': 'パス(ライン)',
+ 'places.kmlImportNoneSelected': '少なくとも1つ選択してください。',
+ 'places.selectionCount': '{count} 件選択中',
+ 'places.deleteSelected': '選択を削除',
+ 'places.kmlKmzImported': 'KMZ/KMLから {count} 件の場所をインポートしました',
+ 'places.urlResolved': 'URLから場所をインポートしました',
+ 'places.importList': 'リストをインポート',
+ 'places.kmlKmzSummaryValues':
+ 'プレースマーク: {total} • 追加: {created} • スキップ: {skipped}',
+ 'places.importGoogleList': 'Google リスト',
+ 'places.importNaverList': 'Naver リスト',
+ 'places.googleListHint':
+ '共有されたGoogleマップのリストリンクを貼り付けてください。',
+ 'places.googleListImported':
+ '「{list}」から {count} 件の場所をインポートしました',
+ 'places.googleListError': 'Googleマップのリストをインポートできませんでした',
+ 'places.naverListHint':
+ '共有されたNaverマップのリストリンクを貼り付けてください。',
+ 'places.naverListImported':
+ '「{list}」から {count} 件の場所をインポートしました',
+ 'places.naverListError': 'Naverマップのリストをインポートできませんでした',
+ 'places.viewDetails': '詳細を見る',
+ 'places.assignToDay': 'どの日に追加しますか?',
+ 'places.all': 'すべて',
+ 'places.unplanned': '未計画',
+ 'places.filterTracks': 'トラック',
+ 'places.search': '場所を検索…',
+ 'places.allCategories': 'すべてのカテゴリ',
+ 'places.categoriesSelected': 'カテゴリ',
+ 'places.clearFilter': 'フィルター解除',
+ 'places.count': '{count} 件の場所',
+ 'places.countSingular': '1 件の場所',
+ 'places.allPlanned': 'すべての場所が計画済みです',
+ 'places.noneFound': '場所が見つかりません',
+ 'places.editPlace': '場所を編集',
+ 'places.formName': '名前',
+ 'places.formNamePlaceholder': '例:エッフェル塔',
+ 'places.formDescription': '説明',
+ 'places.formDescriptionPlaceholder': '短い説明…',
+ 'places.formAddress': '住所',
+ 'places.formAddressPlaceholder': '通り、都市、国',
+ 'places.formLat': '緯度(例:48.8566)',
+ 'places.formLng': '経度(例:2.3522)',
+ 'places.formCategory': 'カテゴリ',
+ 'places.noCategory': 'カテゴリなし',
+ 'places.categoryNamePlaceholder': 'カテゴリ名',
+ 'places.formTime': '時間',
+ 'places.startTime': '開始',
+ 'places.endTime': '終了',
+ 'places.endTimeBeforeStart': '終了時間が開始時間より前です',
+ 'places.timeCollision': '時間が重複しています:',
+ 'places.formWebsite': 'ウェブサイト',
+ 'places.formNotes': 'メモ',
+ 'places.formNotesPlaceholder': '個人的なメモ…',
+ 'places.formReservation': '予約',
+ 'places.reservationNotesPlaceholder': '予約メモ、確認番号など…',
+ 'places.mapsSearchPlaceholder': '場所を検索…',
+ 'places.mapsSearchError': '場所の検索に失敗しました。',
+ 'places.loadingDetails': '詳細を読み込み中…',
+ 'places.osmHint':
+ 'OpenStreetMapで検索しています(写真・営業時間・評価なし)。設定でGoogle APIキーを追加すると詳細が表示されます。',
+ 'places.osmActive':
+ 'OpenStreetMapで検索中(写真・評価・営業時間なし)。設定でGoogle APIキーを追加してください。',
+ 'places.categoryCreateError': 'カテゴリの作成に失敗しました',
+ 'places.nameRequired': '名前を入力してください',
+ 'places.saveError': '保存に失敗しました',
+ 'places.duplicateExists': '「{name}」はすでにこの旅程に含まれています。',
+ 'places.addAnyway': 'それでも追加',
+ 'places.enrichOnImport': 'Googleで場所を補完',
+ 'places.enrichOnImportHint':
+ 'インポートした各場所を検索して、写真・住所・連絡先を追加します。Google Maps キーが必要です。',
+};
+export default places;
diff --git a/shared/src/i18n/ja/planner.ts b/shared/src/i18n/ja/planner.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8645789714951cde3896854b900c8a2dfb7e415c
--- /dev/null
+++ b/shared/src/i18n/ja/planner.ts
@@ -0,0 +1,67 @@
+import type { TranslationStrings } from '../types';
+
+const planner: TranslationStrings = {
+ 'planner.places': '場所',
+ 'planner.bookings': '予約',
+ 'planner.packingList': '持ち物リスト',
+ 'planner.documents': 'ドキュメント',
+ 'planner.dayPlan': '日別計画',
+ 'planner.reservations': '予約',
+ 'planner.minTwoPlaces': '座標付きの場所が少なくとも2つ必要です',
+ 'planner.noGeoPlaces': '座標付きの場所がありません',
+ 'planner.routeCalculated': 'ルートを計算しました',
+ 'planner.routeCalcFailed': 'ルートを計算できませんでした',
+ 'planner.routeError': 'ルート計算中にエラーが発生しました',
+ 'planner.icsExportFailed': 'ICSの書き出しに失敗しました',
+ 'planner.routeOptimized': 'ルートを最適化しました',
+ 'planner.reservationUpdated': '予約を更新しました',
+ 'planner.reservationAdded': '予約を追加しました',
+ 'planner.confirmDeleteReservation': '予約を削除しますか?',
+ 'planner.reservationDeleted': '予約を削除しました',
+ 'planner.days': '日',
+ 'planner.allPlaces': 'すべての場所',
+ 'planner.totalPlaces': '合計{n}件の場所',
+ 'planner.noDaysPlanned': '計画された日がありません',
+ 'planner.editTrip': '旅行を編集 →',
+ 'planner.placeOne': '1件の場所',
+ 'planner.placeN': '{n}件の場所',
+ 'planner.addNote': 'メモを追加',
+ 'planner.noEntries': 'この日の予定はありません',
+ 'planner.addPlace': '場所/アクティビティを追加',
+ 'planner.addPlaceShort': '+ 場所/アクティビティ',
+ 'planner.resPending': '予約保留 · ',
+ 'planner.resConfirmed': '予約確定 · ',
+ 'planner.notePlaceholder': 'メモ…',
+ 'planner.noteTimePlaceholder': '時刻(任意)',
+ 'planner.noteExamplePlaceholder':
+ '例:中央駅から14:30発のS3、7番桟橋からフェリー、昼食休憩…',
+ 'planner.totalCost': '合計費用',
+ 'planner.searchPlaces': '場所を検索…',
+ 'planner.allCategories': 'すべてのカテゴリ',
+ 'planner.noPlacesFound': '場所が見つかりません',
+ 'planner.addFirstPlace': '最初の場所を追加',
+ 'planner.noReservations': '予約はありません',
+ 'planner.addFirstReservation': '最初の予約を追加',
+ 'planner.new': '新規',
+ 'planner.addToDay': '+ 日',
+ 'planner.calculating': '計算中…',
+ 'planner.route': 'ルート',
+ 'planner.optimize': '最適化',
+ 'planner.openGoogleMaps': 'Googleマップで開く',
+ 'planner.selectDayHint': '左の一覧から日を選択すると、日別計画が表示されます',
+ 'planner.noPlacesForDay': 'この日の場所はまだありません',
+ 'planner.addPlacesLink': '場所を追加 →',
+ 'planner.minTotal': '最短合計',
+ 'planner.noReservation': '予約なし',
+ 'planner.removeFromDay': 'この日から削除',
+ 'planner.addToThisDay': 'この日に追加',
+ 'planner.overview': '概要',
+ 'planner.noDays': '日がありません',
+ 'planner.editTripToAddDays': '旅行を編集して日を追加',
+ 'planner.dayCount': '{n}日間',
+ 'planner.clickToUnlock': 'クリックして解除',
+ 'planner.keepPosition': '最適化中も位置を保持',
+ 'planner.dayDetails': '日詳細',
+ 'planner.dayN': '{n}日目',
+};
+export default planner;
diff --git a/shared/src/i18n/ja/register.ts b/shared/src/i18n/ja/register.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a06743fc40f1f77c5c788a2871c6facb4626d750
--- /dev/null
+++ b/shared/src/i18n/ja/register.ts
@@ -0,0 +1,25 @@
+import type { TranslationStrings } from '../types';
+
+const register: TranslationStrings = {
+ 'register.passwordMismatch': 'パスワードが一致しません',
+ 'register.passwordTooShort': 'パスワードは8文字以上必要です',
+ 'register.failed': '登録に失敗しました',
+ 'register.getStarted': '始める',
+ 'register.subtitle': 'アカウントを作成して、理想の旅行計画を始めましょう。',
+ 'register.feature1': '無制限の旅行プラン',
+ 'register.feature2': 'インタラクティブなマップ表示',
+ 'register.feature3': '場所とカテゴリを管理',
+ 'register.feature4': '予約を管理',
+ 'register.feature5': '持ち物リストを作成',
+ 'register.feature6': '写真・ファイルを保存',
+ 'register.createAccount': 'アカウントを作成',
+ 'register.startPlanning': '旅行計画を始める',
+ 'register.minChars': '最小6文字',
+ 'register.confirmPassword': 'パスワード確認',
+ 'register.repeatPassword': 'パスワードを再入力',
+ 'register.registering': '登録中...',
+ 'register.register': '登録',
+ 'register.hasAccount': 'すでにアカウントをお持ちですか?',
+ 'register.signIn': 'サインイン',
+};
+export default register;
diff --git a/shared/src/i18n/ja/reservations.ts b/shared/src/i18n/ja/reservations.ts
new file mode 100644
index 0000000000000000000000000000000000000000..73e630f0d5c2181fd0e54d150e8a94410e981722
--- /dev/null
+++ b/shared/src/i18n/ja/reservations.ts
@@ -0,0 +1,160 @@
+import type { TranslationStrings } from '../types';
+
+const reservations: TranslationStrings = {
+ 'reservations.title': '予約',
+ 'reservations.empty': '予約はまだありません',
+ 'reservations.emptyHint': '航空券、ホテルなどの予約を追加しましょう',
+ 'reservations.add': '予約を追加',
+ 'reservations.addManual': '手動予約',
+ 'reservations.placeHint':
+ 'ヒント:予約は場所から直接作成すると、日別計画に紐づけやすくなります。',
+ 'reservations.confirmed': '確定',
+ 'reservations.pending': '保留',
+ 'reservations.summary': '確定 {confirmed}件、保留 {pending}件',
+ 'reservations.fromPlan': '計画から',
+ 'reservations.showFiles': 'ファイルを表示',
+ 'reservations.editTitle': '予約を編集',
+ 'reservations.status': 'ステータス',
+ 'reservations.datetime': '日時',
+ 'reservations.startTime': '開始時刻',
+ 'reservations.endTime': '終了時刻',
+ 'reservations.date': '日付',
+ 'reservations.time': '時間',
+ 'reservations.timeAlt': '時間(代替、例:19:30)',
+ 'reservations.notes': 'メモ',
+ 'reservations.notesPlaceholder': '追加のメモ...',
+ 'reservations.meta.airline': '航空会社',
+ 'reservations.meta.flightNumber': '便名',
+ 'reservations.meta.from': '出発地',
+ 'reservations.meta.to': '到着地',
+ 'reservations.layover.route': '経路',
+ 'reservations.layover.stop': '経由地',
+ 'reservations.layover.addStop': '経由地を追加',
+ 'reservations.layover.connection': '乗り継ぎ便',
+ 'reservations.layover.layover': '乗り継ぎ',
+ 'reservations.needsReview': '要確認',
+ 'reservations.needsReviewHint':
+ '空港を自動で特定できませんでした。場所を確認してください。',
+ 'reservations.searchLocation': '駅・港・住所を検索…',
+ 'reservations.meta.trainNumber': '列車番号',
+ 'reservations.meta.platform': 'ホーム',
+ 'reservations.meta.seat': '座席',
+ 'reservations.meta.checkIn': 'チェックイン',
+ 'reservations.meta.checkOut': 'チェックアウト',
+ 'reservations.meta.linkAccommodation': '宿泊先',
+ 'reservations.meta.checkInUntil': 'チェックイン期限',
+ 'reservations.meta.pickAccommodation': '宿泊先にリンク',
+ 'reservations.meta.noAccommodation': 'なし',
+ 'reservations.meta.hotelPlace': '宿泊先',
+ 'reservations.meta.pickHotel': '宿泊先を選択',
+ 'reservations.meta.fromDay': '開始',
+ 'reservations.meta.toDay': '終了',
+ 'reservations.meta.selectDay': '日を選択',
+ 'reservations.type.flight': '航空便',
+ 'reservations.type.hotel': '宿泊',
+ 'reservations.type.restaurant': 'レストラン',
+ 'reservations.type.train': '列車',
+ 'reservations.type.car': 'レンタカー',
+ 'reservations.type.cruise': 'クルーズ',
+ 'reservations.type.event': 'イベント',
+ 'reservations.type.tour': 'ツアー',
+ 'reservations.type.other': 'その他',
+ 'reservations.type.bus': 'バス',
+ 'reservations.type.ferry': 'フェリー',
+ 'reservations.type.bicycle': '自転車',
+ 'reservations.type.taxi': 'タクシー',
+ 'reservations.type.transport_other': 'その他',
+ 'reservations.confirm.delete': '予約「{name}」を削除しますか?',
+ 'reservations.confirm.deleteTitle': '予約を削除しますか?',
+ 'reservations.confirm.deleteBody': '「{name}」は完全に削除されます。',
+ 'reservations.toast.updated': '予約を更新しました',
+ 'reservations.toast.removed': '予約を削除しました',
+ 'reservations.toast.fileUploaded': 'ファイルをアップロードしました',
+ 'reservations.toast.uploadError': 'アップロードに失敗しました',
+ 'reservations.newTitle': '新しい予約',
+ 'reservations.bookingType': '予約タイプ',
+ 'reservations.titleLabel': 'タイトル',
+ 'reservations.titlePlaceholder': '例:Lufthansa LH123、Hotel Adlon',
+ 'reservations.locationAddress': '場所/住所',
+ 'reservations.locationPlaceholder': '住所、空港、ホテル...',
+ 'reservations.confirmationCode': '予約コード',
+ 'reservations.confirmationPlaceholder': '例:ABC12345',
+ 'reservations.day': '日',
+ 'reservations.noDay': '日なし',
+ 'reservations.place': '場所',
+ 'reservations.noPlace': '場所なし',
+ 'reservations.pendingSave': '保存されます…',
+ 'reservations.uploading': 'アップロード中...',
+ 'reservations.attachFile': 'ファイルを添付',
+ 'reservations.linkExisting': '既存ファイルをリンク',
+ 'reservations.toast.saveError': '保存に失敗しました',
+ 'reservations.toast.updateError': '更新に失敗しました',
+ 'reservations.toast.deleteError': '削除に失敗しました',
+ 'reservations.confirm.remove': '「{name}」の予約を削除しますか?',
+ 'reservations.linkAssignment': '日への割り当てにリンク',
+ 'reservations.pickAssignment': '計画から割り当てを選択...',
+ 'reservations.noAssignment': 'リンクなし(単独)',
+ 'reservations.price': '価格',
+ 'reservations.budgetCategory': '予算カテゴリ',
+ 'reservations.budgetCategoryPlaceholder': '例:交通、宿泊',
+ 'reservations.budgetCategoryAuto': '自動(予約タイプから)',
+ 'reservations.budgetHint': '保存時に予算エントリが自動で作成されます。',
+ 'reservations.departureDate': '出発',
+ 'reservations.arrivalDate': '到着',
+ 'reservations.departureTime': '出発時刻',
+ 'reservations.arrivalTime': '到着時刻',
+ 'reservations.pickupDate': '受取',
+ 'reservations.returnDate': '返却',
+ 'reservations.pickupTime': '受取時刻',
+ 'reservations.returnTime': '返却時刻',
+ 'reservations.endDate': '終了日',
+ 'reservations.meta.departureTimezone': '出発TZ',
+ 'reservations.meta.arrivalTimezone': '到着TZ',
+ 'reservations.span.departure': '出発',
+ 'reservations.span.arrival': '到着',
+ 'reservations.span.inTransit': '移動中',
+ 'reservations.span.pickup': '受取',
+ 'reservations.span.return': '返却',
+ 'reservations.span.active': '有効',
+ 'reservations.span.start': '開始',
+ 'reservations.span.end': '終了',
+ 'reservations.span.ongoing': '進行中',
+ 'reservations.validation.endBeforeStart':
+ '終了日時は開始日時より後である必要があります',
+ 'reservations.addBooking': '予約を追加',
+ 'reservations.import.title': '予約確認書のインポート',
+ 'reservations.import.cta': 'ファイルからインポート',
+ 'reservations.import.dropHere': '予約確認ファイルをここにドロップするか、クリックして選択',
+ 'reservations.import.dropActive': 'ファイルをドロップしてインポート',
+ 'reservations.import.acceptedFormats': '対応形式:EML、PDF、PKPass、HTML、TXT(各最大 10 MB、最大 5 ファイル)',
+ 'reservations.import.parsing': 'ファイルを解析中…',
+ 'reservations.import.previewHeading': '{count} 件の予約が見つかりました',
+ 'reservations.import.previewEmpty': 'アップロードされたファイルから予約を抽出できませんでした。',
+ 'reservations.import.removeItem': '削除',
+ 'reservations.import.confirm': '{count} 件の予約をインポート',
+ 'reservations.import.back': '戻る',
+ 'reservations.import.success': '{count} 件の予約をインポートしました',
+ 'reservations.import.partialFailure': '{created} 件インポート済み、{failed} 件失敗',
+ 'reservations.import.error': '解析に失敗しました。ファイルが有効な予約確認書であることを確認してください。',
+ 'reservations.import.unavailable': 'このサーバーでは予約インポート機能が利用できません。',
+ 'reservations.import.unsupportedFormat': '対応していないファイル形式です。EML、PDF、PKPass、HTML、または TXT を使用してください。',
+ 'reservations.import.fileTooLarge': 'ファイル「{name}」は 10 MB の制限を超えています。',
+ 'reservations.airtrail.title': 'AirTrail からインポート',
+ 'reservations.airtrail.cta': 'AirTrail',
+ 'reservations.airtrail.synced': 'AirTrail',
+ 'reservations.airtrail.syncedHint': 'AirTrail と同期済み — 編集は双方向で同期されます。',
+ 'reservations.airtrail.notSynced': '未同期',
+ 'reservations.airtrail.notSyncedHint': 'このフライトは AirTrail で削除されたため、同期されなくなりました。',
+ 'reservations.airtrail.loadError': 'AirTrail のフライトを読み込めませんでした。',
+ 'reservations.airtrail.imported': '{count} 件のフライトをインポートしました',
+ 'reservations.airtrail.skippedDuplicate': '{count} 件はこの旅行に既に存在するためスキップしました',
+ 'reservations.airtrail.nothingImported': 'インポートする項目がありません。',
+ 'reservations.airtrail.importError': 'インポートに失敗しました。もう一度お試しください。',
+ 'reservations.airtrail.undo': 'AirTrail からインポート',
+ 'reservations.airtrail.alreadyImported': 'インポート済み',
+ 'reservations.airtrail.duringTrip': 'この旅行の期間中',
+ 'reservations.airtrail.otherFlights': 'その他のフライト',
+ 'reservations.airtrail.empty': 'AirTrail アカウントにフライトが見つかりませんでした。',
+ 'reservations.airtrail.importCta': '{count} 件をインポート',
+};
+export default reservations;
diff --git a/shared/src/i18n/ja/settings.ts b/shared/src/i18n/ja/settings.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6646077dfa8a815a3c4b6794d525ad5f7814000b
--- /dev/null
+++ b/shared/src/i18n/ja/settings.ts
@@ -0,0 +1,325 @@
+import type { TranslationStrings } from '../types';
+
+const settings: TranslationStrings = {
+ 'settings.title': '設定',
+ 'settings.subtitle': '個人設定を管理',
+ 'settings.tabs.display': '表示',
+ 'settings.tabs.map': '地図',
+ 'settings.tabs.notifications': '通知',
+ 'settings.tabs.integrations': '連携',
+ 'settings.tabs.account': 'アカウント',
+ 'settings.tabs.offline': 'オフライン',
+ 'settings.tabs.about': '情報',
+ 'settings.map': '地図',
+ 'settings.mapTemplate': '地図テンプレート',
+ 'settings.mapTemplatePlaceholder.select': 'テンプレートを選択…',
+ 'settings.mapDefaultHint': '空欄の場合は OpenStreetMap(既定)を使用',
+ 'settings.mapTemplatePlaceholder':
+ 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
+ 'settings.mapHint': '地図タイルのURLテンプレート',
+ 'settings.mapProvider': '地図プロバイダー',
+ 'settings.mapProviderHint':
+ '旅程プランナーと日記地図に影響します。Atlas は常に Leaflet を使用します。',
+ 'settings.mapLeafletSubtitle': 'クラシックな2D、任意のラスタータイル',
+ 'settings.mapMapboxSubtitle': 'ベクタータイル、3D建物・地形',
+ 'settings.mapExperimental': '実験的',
+ 'settings.mapMapboxToken': 'Mapbox アクセストークン',
+ 'settings.mapMapboxTokenHint': 'mapbox.com の公開トークン(pk.*)',
+ 'settings.mapMapboxTokenLink': 'mapbox.com → Access tokens',
+ 'settings.mapStyle': '地図スタイル',
+ 'settings.mapStylePlaceholder': 'Mapboxスタイルを選択',
+ 'settings.mapStyleHint': 'プリセットまたは mapbox://styles/USER/ID のURL',
+ 'settings.map3dBuildings': '3D建物・地形',
+ 'settings.map3dHint':
+ 'ピッチ+実際の3D押し出し表示。衛星含む全スタイルで動作。',
+ 'settings.mapHighQuality': '高品質モード',
+ 'settings.mapHighQualityHint':
+ 'アンチエイリアス+地球投影で、より鮮明でリアルに表示。',
+ 'settings.mapHighQualityWarning':
+ '低性能デバイスではパフォーマンスに影響する場合があります。',
+ 'settings.mapTipLabel': 'ヒント:',
+ 'settings.mapTip':
+ '右クリック+ドラッグで回転/傾き。中クリックで場所を追加(右クリックは回転用)。',
+ 'settings.latitude': '緯度',
+ 'settings.longitude': '経度',
+ 'settings.saveMap': '地図を保存',
+ 'settings.apiKeys': 'APIキー',
+ 'settings.mapsKey': 'Google Maps APIキー',
+ 'settings.mapsKeyHint':
+ '場所検索用。Places API(新)が必要。console.cloud.google.com で取得',
+ 'settings.weatherKey': 'OpenWeatherMap APIキー',
+ 'settings.weatherKeyHint': '天気情報用。openweathermap.org/api で無料取得',
+ 'settings.keyPlaceholder': 'キーを入力…',
+ 'settings.configured': '設定済み',
+ 'settings.saveKeys': 'キーを保存',
+ 'settings.display': '表示',
+ 'settings.colorMode': 'カラーモード',
+ 'settings.light': 'ライト',
+ 'settings.dark': 'ダーク',
+ 'settings.auto': '自動',
+ 'settings.language': '言語',
+ 'settings.temperature': '温度単位',
+ 'settings.timeFormat': '時刻形式',
+ 'settings.bookingLabels': '予約ルートのラベル',
+ 'settings.bookingLabelsHint':
+ '地図に駅・空港名を表示。オフ時はアイコンのみ。',
+ 'settings.blurBookingCodes': '予約コードをぼかす',
+ 'settings.optimizeFromAccommodation': '宿泊先を起点にルートを最適化',
+ 'settings.optimizeFromAccommodationHint':
+ 'その日を最適化する際、朝に目覚める宿泊先を起点にし、その晩にチェックインする宿泊先を終点としてルートを組みます。',
+ 'settings.notifications': '通知',
+ 'settings.notifyTripInvite': '旅行の招待',
+ 'settings.notifyBookingChange': '予約の変更',
+ 'settings.notifyTripReminder': '旅行リマインダー',
+ 'settings.notifyTodoDue': 'ToDoの期限',
+ 'settings.notifyVacayInvite': 'Vacay fusion の招待',
+ 'settings.notifyPhotosShared': '共有写真(Immich)',
+ 'settings.notifyCollabMessage': 'チャットメッセージ(Collab)',
+ 'settings.notifyPackingTagged': '持ち物リスト:割り当て',
+ 'settings.notifyWebhook': 'Webhook通知',
+ 'settings.notifyVersionAvailable': '新しいバージョン',
+ 'settings.notificationPreferences.email': 'メール',
+ 'settings.notificationPreferences.webhook': 'Webhook',
+ 'settings.notificationPreferences.inapp': 'アプリ内',
+ 'settings.notificationPreferences.ntfy': 'Ntfy',
+ 'settings.notificationPreferences.noChannels':
+ '通知チャネルが未設定です。管理者に設定を依頼してください。',
+ 'settings.webhookUrl.label': 'Webhook URL',
+ 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...',
+ 'settings.webhookUrl.hint':
+ 'Discord、Slack、または独自のWebhook URLを入力してください。',
+ 'settings.webhookUrl.saved': 'Webhook URLを保存しました',
+ 'settings.webhookUrl.test': 'テスト',
+ 'settings.webhookUrl.testSuccess': 'テストWebhookを送信しました',
+ 'settings.webhookUrl.testFailed': 'テストWebhookに失敗しました',
+ 'settings.ntfyUrl.topicLabel': 'Ntfy トピック',
+ 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts',
+ 'settings.ntfyUrl.serverLabel': 'Ntfy サーバーURL(任意)',
+ 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh',
+ 'settings.ntfyUrl.hint':
+ 'ntfyトピックを入力してください。サーバー未入力時は管理者設定の既定値を使用します。',
+ 'settings.ntfyUrl.tokenLabel': 'アクセストークン(任意)',
+ 'settings.ntfyUrl.tokenHint': 'パスワード保護トピックに必要です。',
+ 'settings.ntfyUrl.saved': 'Ntfy設定を保存しました',
+ 'settings.ntfyUrl.test': 'テスト',
+ 'settings.ntfyUrl.testSuccess': 'テスト通知を送信しました',
+ 'settings.ntfyUrl.testFailed': 'テスト通知に失敗しました',
+ 'settings.ntfyUrl.tokenCleared': 'アクセストークンを削除しました',
+ 'settings.notificationsDisabled':
+ '通知が未設定です。管理者に有効化を依頼してください。',
+ 'settings.notificationsActive': '有効なチャネル',
+ 'settings.notificationsManagedByAdmin': '通知イベントは管理者が設定します。',
+ 'settings.on': 'オン',
+ 'settings.off': 'オフ',
+ 'settings.mcp.title': 'MCP設定',
+ 'settings.mcp.endpoint': 'MCPエンドポイント',
+ 'settings.mcp.clientConfig': 'クライアント設定',
+ 'settings.mcp.clientConfigHint':
+ ' を下のAPIトークンに置き換えてください。',
+ 'settings.mcp.clientConfigHintOAuth':
+ ' と をOAuth 2.1の認証情報に置き換えてください。',
+ 'settings.mcp.copy': 'コピー',
+ 'settings.mcp.copied': 'コピーしました!',
+ 'settings.mcp.apiTokens': 'APIトークン',
+ 'settings.mcp.createToken': '新しいトークン',
+ 'settings.mcp.noTokens': 'トークンがありません。作成してください。',
+ 'settings.mcp.tokenCreatedAt': '作成日',
+ 'settings.mcp.tokenUsedAt': '最終使用',
+ 'settings.mcp.deleteTokenTitle': 'トークン削除',
+ 'settings.mcp.deleteTokenMessage': 'このトークンは即時無効になります。',
+ 'settings.mcp.modal.createTitle': 'APIトークン作成',
+ 'settings.mcp.modal.tokenName': 'トークン名',
+ 'settings.mcp.modal.tokenNamePlaceholder': '例:Claude Desktop',
+ 'settings.mcp.modal.creating': '作成中…',
+ 'settings.mcp.modal.create': '作成',
+ 'settings.mcp.modal.createdTitle': 'トークン作成完了',
+ 'settings.mcp.modal.createdWarning':
+ '表示は一度きりです。今すぐ保存してください。',
+ 'settings.mcp.modal.done': '完了',
+ 'settings.mcp.toast.created': 'トークンを作成しました',
+ 'settings.mcp.toast.createError': 'トークン作成に失敗しました',
+ 'settings.mcp.toast.deleted': 'トークンを削除しました',
+ 'settings.mcp.toast.deleteError': 'トークン削除に失敗しました',
+ 'settings.mcp.apiTokensDeprecated':
+ 'APIトークンは非推奨です。OAuth 2.1 クライアントを使用してください。',
+ 'settings.oauth.clients': 'OAuth 2.1 クライアント',
+ 'settings.oauth.clientsHint': '第三者アプリが接続できるよう登録します。',
+ 'settings.oauth.createClient': '新規クライアント',
+ 'settings.oauth.noClients': '登録されたクライアントはありません。',
+ 'settings.oauth.clientId': 'クライアントID',
+ 'settings.oauth.clientSecret': 'クライアントシークレット',
+ 'settings.oauth.deleteClient': 'クライアント削除',
+ 'settings.oauth.deleteClientMessage':
+ 'このクライアントは完全に削除されます。',
+ 'settings.oauth.rotateSecret': 'シークレット更新',
+ 'settings.oauth.rotateSecretMessage': '新しいシークレットを生成します。',
+ 'settings.oauth.rotateSecretConfirm': '更新',
+ 'settings.oauth.rotateSecretConfirming': '更新中…',
+ 'settings.oauth.rotateSecretDoneTitle': '新しいシークレット',
+ 'settings.oauth.rotateSecretDoneWarning':
+ '表示は一度きりです。今すぐ保存してください。',
+ 'settings.oauth.activeSessions': '有効なセッション',
+ 'settings.oauth.sessionScopes': 'スコープ',
+ 'settings.oauth.sessionExpires': '有効期限',
+ 'settings.oauth.revoke': '取り消し',
+ 'settings.oauth.revokeSession': 'セッション取り消し',
+ 'settings.oauth.revokeSessionMessage':
+ 'このセッションのアクセスを即時無効にします。',
+ 'settings.oauth.modal.createTitle': 'OAuthクライアント登録',
+ 'settings.oauth.modal.presets': '簡単設定',
+ 'settings.oauth.modal.clientName': 'アプリ名',
+ 'settings.oauth.modal.clientNamePlaceholder': '例:Claude Web',
+ 'settings.oauth.modal.redirectUris': 'リダイレクトURI',
+ 'settings.oauth.modal.redirectUrisPlaceholder':
+ 'https://your-app.com/callback',
+ 'settings.oauth.modal.redirectUrisHint': '1行につき1つ。HTTPS必須。',
+ 'settings.oauth.modal.scopes': '許可スコープ',
+ 'settings.oauth.modal.scopesHint':
+ 'list_trips と get_trip_summary は常に利用可能です。',
+ 'settings.oauth.modal.selectAll': 'すべて選択',
+ 'settings.oauth.modal.deselectAll': 'すべて解除',
+ 'settings.oauth.modal.creating': '登録中…',
+ 'settings.oauth.modal.create': '登録',
+ 'settings.oauth.modal.createdTitle': '登録完了',
+ 'settings.oauth.modal.createdWarning':
+ 'シークレットは一度しか表示されません。',
+ 'settings.oauth.toast.createError': '登録に失敗しました',
+ 'settings.oauth.toast.deleted': 'クライアントを削除しました',
+ 'settings.oauth.toast.deleteError': '削除に失敗しました',
+ 'settings.oauth.toast.revoked': 'セッションを取り消しました',
+ 'settings.oauth.toast.revokeError': '取り消しに失敗しました',
+ 'settings.oauth.toast.rotateError': '更新に失敗しました',
+ 'settings.account': 'アカウント',
+ 'settings.about': '情報',
+ 'settings.about.reportBug': '不具合報告',
+ 'settings.about.reportBugHint': '問題を見つけたらお知らせください',
+ 'settings.about.featureRequest': '機能リクエスト',
+ 'settings.about.featureRequestHint': '新機能を提案',
+ 'settings.about.wikiHint': 'ドキュメント・ガイド',
+ 'settings.about.supporters.badge': '月額サポーター',
+ 'settings.about.supporters.title': 'TREKの旅仲間',
+ 'settings.about.supporters.subtitle':
+ '皆さんの支援がTREKの未来を支えています。',
+ 'settings.about.supporters.since': '{date}からサポート',
+ 'settings.about.supporters.tierEmpty': '最初の一人に',
+ 'settings.about.supporter.tier.noReturnTicket': '片道切符',
+ 'settings.about.supporter.tier.lostLuggageVip': 'ロストラゲージVIP',
+ 'settings.about.supporter.tier.businessClassDreamer': 'ビジネスクラスの夢',
+ 'settings.about.supporter.tier.budgetTraveller': '節約トラベラー',
+ 'settings.about.supporter.tier.hostelBunkmate': 'ホステル仲間',
+ 'settings.about.description': 'TREKはセルフホスト型の旅行プランナーです。',
+ 'settings.about.madeWith': 'Made with',
+ 'settings.about.madeBy': 'by Maurice とオープンソースコミュニティ。',
+ 'settings.username': 'ユーザー名',
+ 'settings.email': 'メール',
+ 'settings.role': '役割',
+ 'settings.roleAdmin': '管理者',
+ 'settings.oidcLinked': '連携先',
+ 'settings.changePassword': 'パスワード変更',
+ 'settings.currentPassword': '現在のパスワード',
+ 'settings.currentPasswordRequired': '現在のパスワードが必要です',
+ 'settings.newPassword': '新しいパスワード',
+ 'settings.confirmPassword': '新しいパスワード(確認)',
+ 'settings.updatePassword': 'パスワード更新',
+ 'settings.passwordRequired': '現在と新しいパスワードを入力してください',
+ 'settings.passwordTooShort': '8文字以上必要です',
+ 'settings.passwordMismatch': 'パスワードが一致しません',
+ 'settings.passwordWeak': '大文字・小文字・数字・記号を含めてください',
+ 'settings.passwordChanged': 'パスワードを変更しました',
+ 'settings.mustChangePassword': '続行するにはパスワード変更が必要です。',
+ 'settings.deleteAccount': 'アカウント削除',
+ 'settings.deleteAccountTitle': 'アカウントを削除しますか?',
+ 'settings.deleteAccountWarning': 'すべてのデータが完全に削除されます。',
+ 'settings.deleteAccountConfirm': '完全に削除',
+ 'settings.deleteBlockedTitle': '削除できません',
+ 'settings.deleteBlockedMessage':
+ '唯一の管理者です。別のユーザーを管理者にしてください。',
+ 'settings.roleUser': 'ユーザー',
+ 'settings.saveProfile': 'プロフィールを保存',
+ 'settings.toast.mapSaved': '地図設定を保存しました',
+ 'settings.toast.keysSaved': 'APIキーを保存しました',
+ 'settings.toast.displaySaved': '表示設定を保存しました',
+ 'settings.toast.profileSaved': 'プロフィールを保存しました',
+ 'settings.uploadAvatar': 'プロフィール画像をアップロード',
+ 'settings.removeAvatar': 'プロフィール画像を削除',
+ 'settings.avatarUploaded': 'プロフィール画像を更新しました',
+ 'settings.avatarRemoved': 'プロフィール画像を削除しました',
+ 'settings.avatarError': 'アップロードに失敗しました',
+ 'settings.mfa.title': '二要素認証(2FA)',
+ 'settings.mfa.description': 'サインイン時に追加の認証を行います。',
+ 'settings.mfa.requiredByPolicy': '管理者により2FAが必須です。',
+ 'settings.mfa.backupTitle': 'バックアップコード',
+ 'settings.mfa.backupDescription': '認証アプリが使えない場合に使用します。',
+ 'settings.mfa.backupWarning':
+ '今すぐ保存してください。各コードは1回限りです。',
+ 'settings.mfa.backupCopy': 'コードをコピー',
+ 'settings.mfa.backupDownload': 'TXTでダウンロード',
+ 'settings.mfa.backupPrint': '印刷 / PDF',
+ 'settings.mfa.backupCopied': 'バックアップコードをコピーしました',
+ 'settings.mfa.enabled': '2FAは有効です。',
+ 'settings.mfa.disabled': '2FAは無効です。',
+ 'settings.mfa.setup': '認証アプリを設定',
+ 'settings.mfa.scanQr': 'QRコードをスキャンするか、手動で入力してください。',
+ 'settings.mfa.secretLabel': 'シークレットキー(手動入力)',
+ 'settings.mfa.codePlaceholder': '6桁コード',
+ 'settings.mfa.enable': '2FAを有効化',
+ 'settings.mfa.cancelSetup': 'キャンセル',
+ 'settings.mfa.disableTitle': '2FAを無効化',
+ 'settings.mfa.disableHint': 'パスワードと現在のコードを入力してください。',
+ 'settings.mfa.disable': '2FAを無効化',
+ 'settings.mfa.toastEnabled': '2FAを有効にしました',
+ 'settings.mfa.toastDisabled': '2FAを無効にしました',
+ 'settings.mfa.demoBlocked': 'デモモードでは利用できません',
+ 'settings.oauth.modal.machineClient':
+ 'マシンクライアント(ブラウザログインなし)',
+ 'settings.oauth.modal.machineClientHint':
+ 'client_credentials グラントを使用します — リダイレクト URI は不要です。トークンは client_id + client_secret を介して直接発行され、選択したスコープ内であなたとして動作します。',
+ 'settings.oauth.modal.machineClientUsage':
+ 'トークンを取得するには、grant_type=client_credentials、client_id、client_secret を指定して POST /oauth/token を呼び出します。ブラウザもリフレッシュトークンも不要です。',
+ 'settings.oauth.badge.machine': 'マシン',
+ "settings.currency": "Currency",
+ "settings.currencyHint": "All amounts in Costs are converted to and shown in this currency.",
+ 'settings.passkey.title': 'パスキー',
+ 'settings.passkey.description':
+ '指紋、顔認証、PIN、またはハードウェアキーを使うパスキーで、より速く、フィッシングに強いサインインができます。パスワードはバックアップとして残ります。',
+ 'settings.passkey.notConfigured':
+ 'パスキーは有効ですが、このサーバーではまだ完全には設定されていません。管理者に WebAuthn ドメインの設定を依頼してください。',
+ 'settings.passkey.add': 'パスキーを追加',
+ 'settings.passkey.addTitle': 'パスキーを追加',
+ 'settings.passkey.passwordPrompt':
+ '現在のパスワードを確認し、デバイスの指示に従ってください。',
+ 'settings.passkey.passwordRequired': '現在のパスワードが必要です。',
+ 'settings.passkey.namePlaceholder': '名前(任意、例:"iPhone")',
+ 'settings.passkey.addedToast': 'パスキーを追加しました',
+ 'settings.passkey.added': '追加済み',
+ 'settings.passkey.addError': 'パスキーを追加できませんでした',
+ 'settings.passkey.cancelled': 'パスキーの設定をキャンセルしました',
+ 'settings.passkey.deleted': 'パスキーを削除しました',
+ 'settings.passkey.deleteConfirm':
+ 'このパスキーを削除しますか?パスワードで確認してください。',
+ 'settings.passkey.rename': '名前を変更',
+ 'settings.passkey.defaultName': 'パスキー',
+ 'settings.passkey.synced': '同期済み',
+ 'settings.passkey.deviceBound': 'このデバイス',
+ 'settings.passkey.lastUsed': '最終使用',
+ 'settings.passkey.neverUsed': '未使用',
+ 'settings.mapPoiPill': '地図でスポットを探す',
+ 'settings.mapPoiPillHint': '旅行の地図にカテゴリピルを表示して、OpenStreetMapから近くのレストランや宿泊施設などを見つけられます。',
+ 'settings.airtrail.title': 'AirTrail',
+ 'settings.airtrail.hint': 'セルフホストの AirTrail を接続して、フライトをインポート・同期します。AirTrail の「設定 → セキュリティ」で API キーを作成してください。',
+ 'settings.airtrail.url': 'インスタンス URL',
+ 'settings.airtrail.apiKey': 'API キー',
+ 'settings.airtrail.apiKeyPlaceholder': 'Bearer API キー',
+ 'settings.airtrail.apiKeyHint': 'AirTrail の「設定 → セキュリティ」で生成します。暗号化して保存されます。',
+ 'settings.airtrail.allowInsecureTls': '自己署名証明書を許可する',
+ 'settings.airtrail.allowInsecureTlsHint': '自分のネットワーク内の信頼できるインスタンスの場合にのみ有効にしてください。',
+ 'settings.airtrail.connected': '接続済み',
+ 'settings.airtrail.notConnected': '未接続',
+ 'settings.airtrail.toast.saved': 'AirTrail の接続を保存しました',
+ 'settings.airtrail.toast.saveError': '接続を保存できませんでした',
+ 'settings.airtrail.test.button': '接続をテスト',
+ 'settings.airtrail.test.success': '接続成功 — {count} 件のフライトが見つかりました',
+ 'settings.airtrail.test.failed': '接続に失敗しました',
+};
+
+export default settings;
diff --git a/shared/src/i18n/ja/share.ts b/shared/src/i18n/ja/share.ts
new file mode 100644
index 0000000000000000000000000000000000000000..abdebbd2853f1a28bf6765e735b66d81ef2c3928
--- /dev/null
+++ b/shared/src/i18n/ja/share.ts
@@ -0,0 +1,15 @@
+import type { TranslationStrings } from '../types';
+
+const share: TranslationStrings = {
+ 'share.linkTitle': '公開リンク',
+ 'share.linkHint': 'ログイン不要で閲覧できるリンクを作成します(閲覧のみ)。',
+ 'share.createLink': 'リンク作成',
+ 'share.deleteLink': 'リンク削除',
+ 'share.createError': 'リンクを作成できませんでした',
+ 'share.permMap': '地図・プラン',
+ 'share.permBookings': '予約',
+ 'share.permPacking': '持ち物',
+ 'share.permBudget': '予算',
+ 'share.permCollab': 'チャット',
+};
+export default share;
diff --git a/shared/src/i18n/ja/shared.ts b/shared/src/i18n/ja/shared.ts
new file mode 100644
index 0000000000000000000000000000000000000000..611efab1098e1804fbdcf818de625c37aa7076b4
--- /dev/null
+++ b/shared/src/i18n/ja/shared.ts
@@ -0,0 +1,21 @@
+import type { TranslationStrings } from '../types';
+
+const shared: TranslationStrings = {
+ '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': '保留',
+};
+export default shared;
diff --git a/shared/src/i18n/ja/stats.ts b/shared/src/i18n/ja/stats.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e4903f9b303e734bb41ae1b077324f495bc84ef0
--- /dev/null
+++ b/shared/src/i18n/ja/stats.ts
@@ -0,0 +1,13 @@
+import type { TranslationStrings } from '../types';
+
+const stats: TranslationStrings = {
+ 'stats.countries': '国',
+ 'stats.cities': '都市',
+ 'stats.trips': '旅行',
+ 'stats.places': '場所',
+ 'stats.worldProgress': '世界進捗',
+ 'stats.visited': '訪問済み',
+ 'stats.remaining': '未訪問',
+ 'stats.visitedCountries': '訪問国',
+};
+export default stats;
diff --git a/shared/src/i18n/ja/system_notice.ts b/shared/src/i18n/ja/system_notice.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2a908ceb6bd82dba874a25f575b8e59f58e64099
--- /dev/null
+++ b/shared/src/i18n/ja/system_notice.ts
@@ -0,0 +1,53 @@
+import type { TranslationStrings } from '../types';
+
+const system_notice: TranslationStrings = {
+ 'system_notice.v3_photos.title': '写真の場所が3.0で変更されました',
+ 'system_notice.v3_photos.body':
+ '旅行プランナー内の写真は削除されましたが、写真データは安全です。TREKがImmichやSynologyのライブラリを変更することはありません。\n\n写真は現在日記アドオンにあります。日記は任意機能です。未有効の場合は、管理画面 → アドオンで有効にしてください。',
+ 'system_notice.v3_journey.title': '日記登場 — 旅の日記',
+ 'system_notice.v3_journey.body':
+ 'タイムライン、写真ギャラリー、インタラクティブな地図で旅を物語に。',
+ 'system_notice.v3_journey.cta_label': '日記を開く',
+ 'system_notice.v3_journey.highlight_timeline':
+ '日ごとのタイムラインとギャラリー',
+ 'system_notice.v3_journey.highlight_photos': 'ImmichやSynologyからインポート',
+ 'system_notice.v3_journey.highlight_share': 'ログイン不要で公開共有',
+ 'system_notice.v3_journey.highlight_export': 'PDFフォトブックとして書き出し',
+ 'system_notice.v3_features.title': '3.0のその他の注目点',
+ 'system_notice.v3_features.body': '今回のリリースで知っておきたいポイント。',
+ 'system_notice.v3_features.highlight_dashboard':
+ 'モバイル重視のダッシュボード刷新',
+ 'system_notice.v3_features.highlight_offline': 'PWAとして完全オフライン対応',
+ 'system_notice.v3_features.highlight_search': 'リアルタイム場所検索',
+ 'system_notice.v3_features.highlight_import': 'KMZ/KMLから場所をインポート',
+ 'system_notice.v3_mcp.title': 'MCP:OAuth 2.1に更新',
+ 'system_notice.v3_mcp.body':
+ 'MCP連携が全面的に刷新されました。OAuth 2.1が推奨認証方式です。従来の静的トークン(trek_…)は非推奨となり、将来削除されます。',
+ 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1推奨(mcp-remote)',
+ 'system_notice.v3_mcp.highlight_scopes': '24の詳細な権限スコープ',
+ 'system_notice.v3_mcp.highlight_deprecated': '静的trek_トークンは非推奨',
+ 'system_notice.v3_mcp.highlight_tools': 'ツールとプロンプトを拡張',
+ 'system_notice.v3_thankyou.title': '開発者より一言',
+ 'system_notice.v3_thankyou.body':
+ '少しだけお時間をください。\n\nTREKは、自分の旅のために作った小さな個人プロジェクトでした。それが今では4,000人以上に使ってもらえるとは思ってもいませんでした。スターも、Issueも、機能要望も、すべて目を通しています。\n\nTREKはこれからもオープンソース、自分でホストでき、あなたのものです。トラッキングなし、サブスクなし。旅が好きな人が作ったツールです。\n\nhttps://github.com/jubnlにも感謝を。3.0の多くはあなたのおかげです。\n\nバグ報告、翻訳、共有、利用してくれたすべての方へ—本当にありがとうございます。\n\nこれからも一緒に旅を。\n\n— Maurice',
+ 'system_notice.v3014_whitespace_collision.title':
+ '対応が必要:ユーザーアカウントの競合',
+ 'system_notice.v3014_whitespace_collision.body':
+ '3.0.14 へのアップグレードにより、保存されているアカウントの先頭または末尾の空白が原因で、ユーザー名またはメールアドレスの競合が1件以上検出されました。影響を受けたアカウントは自動的にリネームされています。対象となるアカウントを特定するには、サーバーログで **[migration] WHITESPACE COLLISION** で始まる行を確認してください。',
+ 'system_notice.welcome_v1.title': 'TREKへようこそ',
+ 'system_notice.welcome_v1.body':
+ 'オールインワンの旅行プランナー。旅程作成、共有、整理をオンライン・オフラインで。',
+ 'system_notice.welcome_v1.cta_label': '旅行を計画',
+ 'system_notice.welcome_v1.hero_alt': 'TREKのUIが重なった風景写真',
+ 'system_notice.welcome_v1.highlight_plan': '日ごとの旅程作成',
+ 'system_notice.welcome_v1.highlight_share': '仲間と共同編集',
+ 'system_notice.welcome_v1.highlight_offline': 'モバイルでオフライン対応',
+ 'system_notice.dev_test_modal.title': '[Dev] テスト通知',
+ 'system_notice.dev_test_modal.body': 'これは開発用テスト通知です。',
+ 'system_notice.pager.prev': '前へ',
+ 'system_notice.pager.next': '次へ',
+ 'system_notice.pager.counter': '{current} / {total}',
+ 'system_notice.pager.goto': '通知{n}へ',
+ 'system_notice.pager.position': '{total}件中{current}件目',
+};
+export default system_notice;
diff --git a/shared/src/i18n/ja/todo.ts b/shared/src/i18n/ja/todo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..875720536a7c946226fdc30320b8fb667523ff86
--- /dev/null
+++ b/shared/src/i18n/ja/todo.ts
@@ -0,0 +1,40 @@
+import type { TranslationStrings } from '../types';
+
+const todo: TranslationStrings = {
+ 'todo.subtab.packing': '持ち物リスト',
+ 'todo.subtab.todo': 'ToDo',
+ 'todo.completed': '完了',
+ 'todo.filter.all': 'すべて',
+ 'todo.filter.open': '未完了',
+ 'todo.filter.done': '完了',
+ 'todo.uncategorized': '未分類',
+ 'todo.namePlaceholder': 'タスク名',
+ 'todo.descriptionPlaceholder': '説明(任意)',
+ 'todo.unassigned': '未割り当て',
+ 'todo.noCategory': 'カテゴリなし',
+ 'todo.hasDescription': '説明あり',
+ 'todo.addItem': '新しいタスクを追加...',
+ 'todo.sidebar.sortBy': '並び替え',
+ 'todo.priority': '優先度',
+ 'todo.newCategoryLabel': '新規',
+ 'todo.newCategory': 'カテゴリ名',
+ 'todo.addCategory': 'カテゴリを追加',
+ 'todo.newItem': '新しいタスク',
+ 'todo.empty': 'タスクはまだありません。追加して始めましょう!',
+ 'todo.filter.my': '自分のタスク',
+ 'todo.filter.overdue': '期限切れ',
+ 'todo.sidebar.tasks': 'タスク',
+ 'todo.sidebar.categories': 'カテゴリ',
+ 'todo.detail.title': 'タスク',
+ 'todo.detail.description': '説明',
+ 'todo.detail.category': 'カテゴリ',
+ 'todo.detail.dueDate': '期限',
+ 'todo.detail.assignedTo': '担当者',
+ 'todo.detail.delete': '削除',
+ 'todo.detail.save': '変更を保存',
+ 'todo.sortByPrio': '優先度',
+ 'todo.detail.priority': '優先度',
+ 'todo.detail.noPriority': 'なし',
+ 'todo.detail.create': 'タスクを作成',
+};
+export default todo;
diff --git a/shared/src/i18n/ja/transport.ts b/shared/src/i18n/ja/transport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ca58fa24355337f7c56f1716756d5f9ac02eb080
--- /dev/null
+++ b/shared/src/i18n/ja/transport.ts
@@ -0,0 +1,10 @@
+import type { TranslationStrings } from '../types';
+
+const transport: TranslationStrings = {
+ 'transport.addTransport': '移動手段を追加',
+ 'transport.modalTitle.create': '移動手段を追加',
+ 'transport.modalTitle.edit': '移動手段を編集',
+ 'transport.title': '移動手段',
+ 'transport.addManual': '手動で追加',
+};
+export default transport;
diff --git a/shared/src/i18n/ja/trip.ts b/shared/src/i18n/ja/trip.ts
new file mode 100644
index 0000000000000000000000000000000000000000..066f6624e8e3290cca49510f326f4c2aa9e7b319
--- /dev/null
+++ b/shared/src/i18n/ja/trip.ts
@@ -0,0 +1,31 @@
+import type { TranslationStrings } from '../types';
+
+const trip: TranslationStrings = {
+ 'trip.tabs.plan': '計画',
+ 'trip.tabs.transports': '移動',
+ 'trip.tabs.reservations': '予約',
+ 'trip.tabs.reservationsShort': '予約',
+ 'trip.tabs.packing': '持ち物リスト',
+ 'trip.tabs.packingShort': '持ち物',
+ 'trip.tabs.lists': 'リスト',
+ 'trip.tabs.listsShort': 'リスト',
+ 'trip.tabs.budget': "Costs",
+ 'trip.tabs.files': 'ファイル',
+ 'trip.loading': '旅行を読み込み中...',
+ 'trip.loadingPhotos': '場所の写真を読み込み中...',
+ 'trip.mobilePlan': '計画',
+ 'trip.mobilePlaces': '場所',
+ 'trip.toast.placeUpdated': '場所を更新しました',
+ 'trip.toast.placeAdded': '場所を追加しました',
+ 'trip.toast.placeDeleted': '場所を削除しました',
+ 'trip.toast.selectDay': 'まず日を選択してください',
+ 'trip.toast.assignedToDay': '場所を日に割り当てました',
+ 'trip.toast.reorderError': '並び替えに失敗しました',
+ 'trip.toast.reservationUpdated': '予約を更新しました',
+ 'trip.toast.reservationAdded': '予約を追加しました',
+ 'trip.toast.deleted': '削除しました',
+ 'trip.confirm.deletePlace': 'この場所を削除してもよろしいですか?',
+ 'trip.confirm.deletePlaces': '{count}件の場所を削除してもよろしいですか?',
+ 'trip.toast.placesDeleted': '{count}件の場所を削除しました',
+};
+export default trip;
diff --git a/shared/src/i18n/ja/trips.ts b/shared/src/i18n/ja/trips.ts
new file mode 100644
index 0000000000000000000000000000000000000000..04b537114d492e11749ac8c7840fe226a75b569e
--- /dev/null
+++ b/shared/src/i18n/ja/trips.ts
@@ -0,0 +1,17 @@
+import type { TranslationStrings } from '../types';
+
+const trips: TranslationStrings = {
+ 'trips.memberRemoved': '{username} を削除しました',
+ 'trips.memberRemoveError': '削除に失敗しました',
+ 'trips.memberAdded': '{username} を追加しました',
+ 'trips.memberAddError': '追加に失敗しました',
+ 'trips.reminder': 'リマインダー',
+ 'trips.reminderNone': 'なし',
+ 'trips.reminderDay': '日',
+ 'trips.reminderDays': '日',
+ 'trips.reminderCustom': 'カスタム',
+ 'trips.reminderDaysBefore': '出発前',
+ 'trips.reminderDisabledHint':
+ '旅行のリマインダーは無効です。管理 > 設定 > 通知から有効にしてください。',
+};
+export default trips;
diff --git a/shared/src/i18n/ja/undo.ts b/shared/src/i18n/ja/undo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1b5c942ac68583b5f027e5d2491f18c22b8c1d00
--- /dev/null
+++ b/shared/src/i18n/ja/undo.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const undo: TranslationStrings = {
+ 'undo.button': '元に戻す',
+ 'undo.tooltip': '元に戻す: {action}',
+ 'undo.assignPlace': '場所を日に割り当て',
+ 'undo.removeAssignment': '日の割り当てを解除',
+ 'undo.reorder': '場所を並び替え',
+ 'undo.optimize': 'ルートを最適化',
+ 'undo.deletePlace': '場所を削除',
+ 'undo.deletePlaces': '場所を削除',
+ 'undo.moveDay': '場所を別の日に移動',
+ 'undo.lock': '場所のロックを切り替え',
+ 'undo.importGpx': 'GPXをインポート',
+ 'undo.importKeyholeMarkup': 'KMZ/KMLをインポート',
+ 'undo.importGoogleList': 'Googleマップをインポート',
+ 'undo.importNaverList': 'Naverマップをインポート',
+ 'undo.addPlace': '場所を追加',
+ 'undo.done': '元に戻しました: {action}',
+ 'undo.importBooking': '予約確認書インポート',
+};
+export default undo;
diff --git a/shared/src/i18n/ja/vacay.ts b/shared/src/i18n/ja/vacay.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a0631e117b7eedf3ef7604b33b2c4b613e00d503
--- /dev/null
+++ b/shared/src/i18n/ja/vacay.ts
@@ -0,0 +1,97 @@
+import type { TranslationStrings } from '../types';
+
+const vacay: TranslationStrings = {
+ 'vacay.subtitle': '休暇日数の計画と管理',
+ 'vacay.settings': '設定',
+ 'vacay.year': '年',
+ 'vacay.addYear': '翌年を追加',
+ 'vacay.addPrevYear': '前年を追加',
+ 'vacay.removeYear': '年を削除',
+ 'vacay.removeYearConfirm': '{year}年を削除しますか?',
+ 'vacay.removeYearHint':
+ 'この年の休暇データと会社休日はすべて完全に削除されます。',
+ 'vacay.remove': '削除',
+ 'vacay.persons': '人物',
+ 'vacay.noPersons': '人物が追加されていません',
+ 'vacay.addPerson': '人物を追加',
+ 'vacay.editPerson': '人物を編集',
+ 'vacay.removePerson': '人物を削除',
+ 'vacay.removePersonConfirm': '{name}を削除しますか?',
+ 'vacay.removePersonHint':
+ 'この人物のすべての休暇データが完全に削除されます。',
+ 'vacay.personName': '名前',
+ 'vacay.personNamePlaceholder': '名前を入力',
+ 'vacay.color': '色',
+ 'vacay.add': '追加',
+ 'vacay.legend': '凡例',
+ 'vacay.publicHoliday': '祝日',
+ 'vacay.companyHoliday': '会社休日',
+ 'vacay.weekend': '週末',
+ 'vacay.modeVacation': '休暇',
+ 'vacay.modeCompany': '会社休日',
+ 'vacay.entitlement': '付与日数',
+ 'vacay.entitlementDays': '日',
+ 'vacay.used': '使用済み',
+ 'vacay.remaining': '残り',
+ 'vacay.carriedOver': '{year}年から繰越',
+ 'vacay.blockWeekends': '週末を除外',
+ 'vacay.blockWeekendsHint': '週末に休暇を登録できないようにします',
+ 'vacay.weekendDays': '週末',
+ 'vacay.mon': '月',
+ 'vacay.tue': '火',
+ 'vacay.wed': '水',
+ 'vacay.thu': '木',
+ 'vacay.fri': '金',
+ 'vacay.sat': '土',
+ 'vacay.sun': '日',
+ 'vacay.publicHolidays': '祝日',
+ 'vacay.publicHolidaysHint': 'カレンダーに祝日を表示',
+ 'vacay.selectCountry': '国を選択',
+ 'vacay.selectRegion': '地域を選択(任意)',
+ 'vacay.addCalendar': 'カレンダーを追加',
+ 'vacay.calendarLabel': 'ラベル(任意)',
+ 'vacay.calendarColor': '色',
+ 'vacay.noCalendars': '祝日カレンダーはまだありません',
+ 'vacay.companyHolidays': '会社休日',
+ 'vacay.companyHolidaysHint': '会社全体の休日を設定できます',
+ 'vacay.companyHolidaysNoDeduct': '会社休日は休暇日数に含まれません。',
+ 'vacay.weekStart': '週の開始',
+ 'vacay.weekStartHint':
+ 'カレンダーの週を月曜始まりにするか日曜始まりにするかを選択します',
+ 'vacay.carryOver': '繰越',
+ 'vacay.carryOverHint': '残りの休暇日数を翌年に自動で繰り越します',
+ 'vacay.sharing': '共有',
+ 'vacay.sharingHint': '他のTREKユーザーと休暇計画を共有',
+ 'vacay.owner': '所有者',
+ 'vacay.shareEmailPlaceholder': 'TREKユーザーのメール',
+ 'vacay.shareSuccess': '計画を共有しました',
+ 'vacay.shareError': '共有できませんでした',
+ 'vacay.dissolve': '統合を解除',
+ 'vacay.dissolveHint': 'カレンダーを分離します。データは保持されます。',
+ 'vacay.dissolveAction': '解除',
+ 'vacay.dissolved': 'カレンダーを分離しました',
+ 'vacay.fusedWith': '統合相手',
+ 'vacay.you': 'あなた',
+ 'vacay.noData': 'データなし',
+ 'vacay.changeColor': '色を変更',
+ 'vacay.inviteUser': 'ユーザーを招待',
+ 'vacay.inviteHint':
+ '別のTREKユーザーを招待して、休暇カレンダーを共有します。',
+ 'vacay.selectUser': 'ユーザーを選択',
+ 'vacay.sendInvite': '招待を送信',
+ 'vacay.inviteSent': '招待を送信しました',
+ 'vacay.inviteError': '招待を送信できませんでした',
+ 'vacay.pending': '保留中',
+ 'vacay.noUsersAvailable': '利用可能なユーザーがいません',
+ 'vacay.accept': '承認',
+ 'vacay.decline': '拒否',
+ 'vacay.acceptFusion': '承認して統合',
+ 'vacay.inviteTitle': '統合の招待',
+ 'vacay.inviteWantsToFuse': 'が休暇カレンダーの共有を希望しています。',
+ 'vacay.fuseInfo1': '双方が1つの共有カレンダーですべての休暇を確認できます。',
+ 'vacay.fuseInfo2': '双方が互いの予定を作成・編集できます。',
+ 'vacay.fuseInfo3': '双方が予定の削除や付与日数の変更を行えます。',
+ 'vacay.fuseInfo4': '祝日や会社休日などの設定は共有されます。',
+ 'vacay.fuseInfo5': '統合はいつでも解除できます。データは保持されます。',
+};
+export default vacay;
diff --git a/shared/src/i18n/ko/admin.ts b/shared/src/i18n/ko/admin.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7583b5d962a61d6e6a49ee1f4cf08a3d8c1496c2
--- /dev/null
+++ b/shared/src/i18n/ko/admin.ts
@@ -0,0 +1,382 @@
+import type { TranslationStrings } from '../types';
+
+const admin: TranslationStrings = {
+ 'admin.notifications.title': '알림',
+ 'admin.notifications.hint':
+ '알림 채널을 하나 선택하세요. 한 번에 하나만 활성화할 수 있습니다.',
+ 'admin.notifications.none': '비활성화',
+ 'admin.notifications.email': '이메일 (SMTP)',
+ 'admin.notifications.webhook': '웹훅',
+ 'admin.notifications.ntfy': 'Ntfy',
+ 'admin.ntfy.hint':
+ '사용자가 자신의 ntfy 토픽을 설정하여 푸시 알림을 받을 수 있습니다. 아래에 기본 서버를 설정하면 사용자 설정에 미리 채워집니다.',
+ 'admin.notifications.save': '알림 설정 저장',
+ 'admin.notifications.saved': '알림 설정이 저장되었습니다',
+ 'admin.notifications.testWebhook': '테스트 웹훅 전송',
+ 'admin.notifications.testWebhookSuccess':
+ '테스트 웹훅이 성공적으로 전송되었습니다',
+ 'admin.notifications.testWebhookFailed': '테스트 웹훅 실패',
+ 'admin.notifications.testNtfy': '테스트 ntfy 전송',
+ 'admin.notifications.testNtfySuccess':
+ '테스트 ntfy가 성공적으로 전송되었습니다',
+ 'admin.notifications.testNtfyFailed': '테스트 ntfy 실패',
+ 'admin.notifications.emailPanel.title': '이메일 (SMTP)',
+ 'admin.notifications.webhookPanel.title': '웹훅',
+ 'admin.notifications.inappPanel.title': '앱 내',
+ 'admin.notifications.inappPanel.hint':
+ '앱 내 알림은 항상 활성화되어 있으며 전역으로 비활성화할 수 없습니다.',
+ 'admin.notifications.adminWebhookPanel.title': '관리자 웹훅',
+ 'admin.notifications.adminWebhookPanel.hint':
+ '이 웹훅은 관리자 알림 전용입니다 (예: 버전 알림). 사용자별 웹훅과 별개이며 설정 시 항상 실행됩니다.',
+ 'admin.notifications.adminWebhookPanel.saved':
+ '관리자 웹훅 URL이 저장되었습니다',
+ 'admin.notifications.adminWebhookPanel.testSuccess':
+ '테스트 웹훅이 성공적으로 전송되었습니다',
+ 'admin.notifications.adminWebhookPanel.testFailed': '테스트 웹훅 실패',
+ 'admin.notifications.adminWebhookPanel.alwaysOnHint':
+ 'URL이 설정되면 관리자 웹훅은 항상 실행됩니다',
+ 'admin.notifications.adminNtfyPanel.title': '관리자 Ntfy',
+ 'admin.notifications.adminNtfyPanel.hint':
+ '이 ntfy 토픽은 관리자 알림 전용입니다 (예: 버전 알림). 사용자별 토픽과 별개이며 설정 시 항상 실행됩니다.',
+ 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy 서버 URL',
+ 'admin.notifications.adminNtfyPanel.serverHint':
+ '사용자 ntfy 알림의 기본 서버로도 사용됩니다. 비워두면 ntfy.sh가 기본값입니다. 사용자는 자신의 설정에서 변경할 수 있습니다.',
+ 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh',
+ 'admin.notifications.adminNtfyPanel.topicLabel': '관리자 토픽',
+ 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts',
+ 'admin.notifications.adminNtfyPanel.tokenLabel': '액세스 토큰 (선택)',
+ 'admin.notifications.adminNtfyPanel.tokenCleared':
+ '관리자 액세스 토큰이 삭제되었습니다',
+ 'admin.notifications.adminNtfyPanel.saved':
+ '관리자 ntfy 설정이 저장되었습니다',
+ 'admin.notifications.adminNtfyPanel.test': '테스트 ntfy 전송',
+ 'admin.notifications.adminNtfyPanel.testSuccess':
+ '테스트 ntfy가 성공적으로 전송되었습니다',
+ 'admin.notifications.adminNtfyPanel.testFailed': '테스트 ntfy 실패',
+ 'admin.notifications.adminNtfyPanel.alwaysOnHint':
+ '토픽이 설정되면 관리자 ntfy는 항상 실행됩니다',
+ 'admin.notifications.adminNotificationsHint':
+ '관리자 전용 알림 (예: 버전 알림)을 전달할 채널을 설정하세요.',
+ 'admin.notifications.tripReminders.title': '여행 리마인더',
+ 'admin.notifications.tripReminders.hint':
+ '여행 시작 전에 리마인더 알림을 전송합니다 (여행에 리마인더 일수가 설정되어 있어야 합니다).',
+ 'admin.notifications.tripReminders.enabled': '여행 리마인더 활성화됨',
+ 'admin.notifications.tripReminders.disabled': '여행 리마인더 비활성화됨',
+ 'admin.smtp.title': '이메일 및 알림',
+ 'admin.smtp.hint': '이메일 알림 전송을 위한 SMTP 설정입니다.',
+ 'admin.smtp.testButton': '테스트 이메일 전송',
+ 'admin.webhook.hint':
+ '사용자가 알림용 웹훅 URL을 설정할 수 있습니다 (Discord, Slack 등).',
+ 'admin.smtp.testSuccess': '테스트 이메일이 성공적으로 전송되었습니다',
+ 'admin.smtp.testFailed': '테스트 이메일 실패',
+ 'admin.title': '관리자',
+ 'admin.subtitle': '사용자 관리 및 시스템 설정',
+ 'admin.tabs.users': '사용자',
+ 'admin.tabs.categories': '카테고리',
+ 'admin.tabs.backup': '백업',
+ 'admin.tabs.notifications': '알림',
+ 'admin.tabs.audit': '감사',
+ 'admin.stats.users': '사용자',
+ 'admin.stats.trips': '여행',
+ 'admin.stats.places': '장소',
+ 'admin.stats.photos': '사진',
+ 'admin.stats.files': '파일',
+ 'admin.table.user': '사용자',
+ 'admin.table.email': '이메일',
+ 'admin.table.role': '역할',
+ 'admin.table.created': '생성일',
+ 'admin.table.lastLogin': '마지막 로그인',
+ 'admin.table.actions': '작업',
+ 'admin.you': '(나)',
+ 'admin.editUser': '사용자 편집',
+ 'admin.newPassword': '새 비밀번호',
+ 'admin.newPasswordHint': '비워두면 현재 비밀번호 유지',
+ 'admin.deleteUser':
+ '사용자 "{name}"을(를) 삭제할까요? 모든 여행이 영구 삭제됩니다.',
+ 'admin.deleteUserTitle': '사용자 삭제',
+ 'admin.newPasswordPlaceholder': '새 비밀번호 입력…',
+ 'admin.toast.loadError': '관리자 데이터 불러오기 실패',
+ 'admin.toast.userUpdated': '사용자가 업데이트되었습니다',
+ 'admin.toast.updateError': '업데이트 실패',
+ 'admin.toast.userDeleted': '사용자가 삭제되었습니다',
+ 'admin.toast.deleteError': '삭제 실패',
+ 'admin.toast.cannotDeleteSelf': '자신의 계정은 삭제할 수 없습니다',
+ 'admin.toast.userCreated': '사용자가 생성되었습니다',
+ 'admin.toast.createError': '사용자 생성 실패',
+ 'admin.toast.fieldsRequired': '사용자 이름, 이메일, 비밀번호가 필요합니다',
+ 'admin.createUser': '사용자 만들기',
+ 'admin.invite.title': '초대 링크',
+ 'admin.invite.subtitle': '일회용 회원가입 링크 만들기',
+ 'admin.invite.create': '링크 만들기',
+ 'admin.invite.createAndCopy': '만들기 및 복사',
+ 'admin.invite.empty': '아직 초대 링크가 없습니다',
+ 'admin.invite.maxUses': '최대 사용 횟수',
+ 'admin.invite.expiry': '만료 기간',
+ 'admin.invite.uses': '사용됨',
+ 'admin.invite.expiresAt': '만료',
+ 'admin.invite.createdBy': '작성자',
+ 'admin.invite.active': '활성',
+ 'admin.invite.expired': '만료됨',
+ 'admin.invite.usedUp': '사용 완료',
+ 'admin.invite.copied': '초대 링크가 클립보드에 복사되었습니다',
+ 'admin.invite.copyLink': '링크 복사',
+ 'admin.invite.deleted': '초대 링크가 삭제되었습니다',
+ 'admin.invite.createError': '초대 링크 생성 실패',
+ 'admin.invite.deleteError': '초대 링크 삭제 실패',
+ 'admin.tabs.settings': '설정',
+ 'admin.allowRegistration': '회원가입 허용',
+ 'admin.allowRegistrationHint': '새 사용자가 직접 회원가입할 수 있습니다',
+ 'admin.authMethods': '인증 방법',
+ 'admin.passwordLogin': '비밀번호 로그인',
+ 'admin.passwordLoginHint':
+ '사용자가 이메일과 비밀번호로 로그인할 수 있습니다',
+ 'admin.passwordRegistration': '비밀번호 회원가입',
+ 'admin.passwordRegistrationHint':
+ '새 사용자가 이메일과 비밀번호로 가입할 수 있습니다',
+ 'admin.oidcLogin': 'SSO 로그인',
+ 'admin.oidcLoginHint': '사용자가 SSO로 로그인할 수 있습니다',
+ 'admin.oidcRegistration': 'SSO 자동 프로비저닝',
+ 'admin.oidcRegistrationHint': '새 SSO 사용자의 계정을 자동으로 생성합니다',
+ 'admin.envOverrideHint':
+ '비밀번호 로그인 설정은 OIDC_ONLY 환경 변수로 제어되며 여기서 변경할 수 없습니다.',
+ 'admin.lockoutWarning': '최소 하나의 로그인 방법이 활성화되어 있어야 합니다',
+ 'admin.requireMfa': '2단계 인증 (2FA) 요구',
+ 'admin.requireMfaHint':
+ '2FA가 없는 사용자는 앱을 사용하기 전에 설정에서 설정을 완료해야 합니다.',
+ 'admin.apiKeys': 'API 키',
+ 'admin.apiKeysHint':
+ '선택 사항. 사진 및 날씨 등 확장된 장소 데이터를 활성화합니다.',
+ 'admin.mapsKey': 'Google Maps API 키',
+ 'admin.mapsKeyHint':
+ '장소 검색에 필요합니다. console.cloud.google.com에서 발급',
+ 'admin.mapsKeyHintLong':
+ 'API 키 없이는 장소 검색에 OpenStreetMap이 사용됩니다. Google API 키가 있으면 사진, 평점, 영업 시간도 불러올 수 있습니다. console.cloud.google.com에서 발급하세요.',
+ 'admin.recommended': '권장',
+ 'admin.weatherKey': 'OpenWeatherMap API 키',
+ 'admin.weatherKeyHint': '날씨 데이터용. openweathermap.org에서 무료 발급',
+ 'admin.validateKey': '테스트',
+ 'admin.keyValid': '연결됨',
+ 'admin.keyInvalid': '유효하지 않음',
+ 'admin.keySaved': 'API 키가 저장되었습니다',
+ 'admin.oidcTitle': 'Single Sign-On (OIDC)',
+ 'admin.oidcSubtitle':
+ 'Google, Apple, Authentik 또는 Keycloak 등 외부 공급자를 통한 로그인을 허용합니다.',
+ 'admin.oidcDisplayName': '표시 이름',
+ 'admin.oidcIssuer': '발급자 URL',
+ 'admin.oidcIssuerHint':
+ '공급자의 OpenID Connect 발급자 URL. 예: https://accounts.google.com',
+ 'admin.oidcSaved': 'OIDC 설정이 저장되었습니다',
+ 'admin.oidcOnlyMode': '비밀번호 인증 비활성화',
+ 'admin.oidcOnlyModeHint':
+ '활성화하면 SSO 로그인만 허용됩니다. 비밀번호 기반 로그인 및 회원가입이 차단됩니다.',
+ 'admin.fileTypes': '허용된 파일 형식',
+ 'admin.fileTypesHint': '사용자가 업로드할 수 있는 파일 형식을 설정합니다.',
+ 'admin.fileTypesFormat':
+ '쉼표로 구분된 확장자 (예: jpg,png,pdf,doc). 모든 형식을 허용하려면 *를 사용하세요.',
+ 'admin.fileTypesSaved': '파일 형식 설정이 저장되었습니다',
+ 'admin.placesPhotos.title': '장소 사진',
+ 'admin.placesPhotos.subtitle':
+ 'Google Places API에서 사진을 가져옵니다. API 할당량 절약을 위해 비활성화할 수 있습니다. Wikimedia 사진은 영향을 받지 않습니다.',
+ 'admin.placesAutocomplete.title': '장소 자동완성',
+ 'admin.placesAutocomplete.subtitle':
+ '검색 제안에 Google Places API를 사용합니다. API 할당량 절약을 위해 비활성화할 수 있습니다.',
+ 'admin.placesDetails.title': '장소 상세 정보',
+ 'admin.placesDetails.subtitle':
+ 'Google Places API에서 상세 장소 정보 (영업 시간, 평점, 웹사이트)를 가져옵니다. API 할당량 절약을 위해 비활성화할 수 있습니다.',
+ 'admin.bagTracking.title': '가방 추적',
+ 'admin.bagTracking.subtitle': '짐 항목에 무게 및 가방 배정을 활성화합니다',
+ 'admin.collab.chat.title': '채팅',
+ 'admin.collab.chat.subtitle': '여행 협업을 위한 실시간 메시지',
+ 'admin.collab.notes.title': '메모',
+ 'admin.collab.notes.subtitle': '공유 메모 및 문서',
+ 'admin.collab.polls.title': '투표',
+ 'admin.collab.polls.subtitle': '그룹 투표',
+ 'admin.collab.whatsnext.title': '다음 할 일',
+ 'admin.collab.whatsnext.subtitle': '활동 제안 및 다음 단계',
+ 'admin.tabs.config': '개인 설정',
+ 'admin.tabs.defaults': '기본값',
+ 'admin.defaultSettings.title': '기본 사용자 설정',
+ 'admin.defaultSettings.description':
+ '인스턴스 전체 기본값을 설정합니다. 설정을 변경하지 않은 사용자에게 이 값이 표시됩니다. 사용자의 변경 사항이 항상 우선합니다.',
+ 'admin.defaultSettings.saved': '기본값이 저장되었습니다',
+ 'admin.defaultSettings.reset': '기본 내장값으로 초기화',
+ 'admin.defaultSettings.resetToBuiltIn': '초기화',
+ 'admin.tabs.templates': '짐 목록 템플릿',
+ 'admin.packingTemplates.title': '짐 목록 템플릿',
+ 'admin.packingTemplates.subtitle':
+ '여행을 위한 재사용 가능한 짐 목록을 만드세요',
+ 'admin.packingTemplates.create': '새 템플릿',
+ 'admin.packingTemplates.namePlaceholder': '템플릿 이름 (예: 해변 휴가)',
+ 'admin.packingTemplates.empty': '아직 템플릿이 없습니다',
+ 'admin.packingTemplates.items': '항목',
+ 'admin.packingTemplates.categories': '카테고리',
+ 'admin.packingTemplates.itemName': '항목 이름',
+ 'admin.packingTemplates.itemCategory': '카테고리',
+ 'admin.packingTemplates.categoryName': '카테고리 이름 (예: 의류)',
+ 'admin.packingTemplates.addCategory': '카테고리 추가',
+ 'admin.packingTemplates.created': '템플릿이 생성되었습니다',
+ 'admin.packingTemplates.deleted': '템플릿이 삭제되었습니다',
+ 'admin.packingTemplates.loadError': '템플릿 불러오기 실패',
+ 'admin.packingTemplates.createError': '템플릿 생성 실패',
+ 'admin.packingTemplates.deleteError': '템플릿 삭제 실패',
+ 'admin.packingTemplates.saveError': '저장 실패',
+ 'admin.tabs.addons': '애드온',
+ 'admin.addons.title': '애드온',
+ 'admin.addons.subtitle':
+ '기능을 활성화 또는 비활성화하여 TREK 경험을 맞춤 설정하세요.',
+ 'admin.addons.catalog.packing.name': '목록',
+ 'admin.addons.catalog.packing.description':
+ '여행을 위한 짐 목록 및 할 일 작업',
+ 'admin.addons.catalog.budget.name': '예산',
+ 'admin.addons.catalog.budget.description': '지출 추적 및 여행 예산 계획',
+ 'admin.addons.catalog.documents.name': '문서',
+ 'admin.addons.catalog.documents.description': '여행 서류 저장 및 관리',
+ 'admin.addons.catalog.vacay.name': 'Vacay',
+ 'admin.addons.catalog.vacay.description':
+ '캘린더 보기가 있는 개인 휴가 플래너',
+ 'admin.addons.catalog.atlas.name': 'Atlas',
+ 'admin.addons.catalog.atlas.description':
+ '방문한 나라와 여행 통계가 있는 세계 지도',
+ 'admin.addons.catalog.collab.name': 'Collab',
+ 'admin.addons.catalog.collab.description':
+ '여행 계획을 위한 실시간 메모, 투표, 채팅',
+ 'admin.addons.catalog.memories.name': '사진 (Immich)',
+ 'admin.addons.catalog.memories.description':
+ 'Immich 인스턴스를 통해 여행 사진 공유',
+ 'admin.addons.catalog.mcp.name': 'MCP',
+ 'admin.addons.catalog.mcp.description':
+ 'AI 어시스턴트 통합을 위한 모델 컨텍스트 프로토콜',
+ 'admin.addons.subtitleBefore': '기능을 활성화 또는 비활성화하여 ',
+ 'admin.addons.subtitleAfter': ' 경험을 맞춤 설정하세요.',
+ 'admin.addons.enabled': '활성화됨',
+ 'admin.addons.disabled': '비활성화됨',
+ 'admin.addons.type.trip': '여행',
+ 'admin.addons.type.global': '전역',
+ 'admin.addons.type.integration': '통합',
+ 'admin.addons.tripHint': '각 여행 내 탭으로 사용 가능',
+ 'admin.addons.globalHint': '메인 내비게이션의 독립 섹션으로 사용 가능',
+ 'admin.addons.integrationHint':
+ '전용 페이지가 없는 백엔드 서비스 및 API 통합',
+ 'admin.addons.toast.updated': '애드온이 업데이트되었습니다',
+ 'admin.addons.toast.error': '애드온 업데이트 실패',
+ 'admin.addons.noAddons': '사용 가능한 애드온이 없습니다',
+ 'admin.weather.title': '날씨 데이터',
+ 'admin.weather.badge': '2026년 3월 24일부터',
+ 'admin.weather.description':
+ 'TREK은 날씨 데이터 소스로 Open-Meteo를 사용합니다. Open-Meteo는 무료 오픈 소스 날씨 서비스로 API 키가 필요 없습니다.',
+ 'admin.weather.forecast': '16일 예보',
+ 'admin.weather.forecastDesc': '이전: 5일 (OpenWeatherMap)',
+ 'admin.weather.climate': '과거 기후 데이터',
+ 'admin.weather.climateDesc': '16일 예보 이후 날짜의 85년 평균값',
+ 'admin.weather.requests': '하루 10,000 요청',
+ 'admin.weather.requestsDesc': '무료, API 키 불필요',
+ 'admin.weather.locationHint':
+ '날씨는 각 날의 좌표가 있는 첫 번째 장소를 기준으로 합니다. 날에 배정된 장소가 없으면 장소 목록의 임의 장소가 참조로 사용됩니다.',
+ 'admin.tabs.mcpTokens': 'MCP 접근',
+ 'admin.mcpTokens.title': 'MCP 접근',
+ 'admin.mcpTokens.subtitle':
+ '모든 사용자의 OAuth 세션 및 API 토큰을 관리합니다',
+ 'admin.mcpTokens.sectionTitle': 'API 토큰',
+ 'admin.mcpTokens.owner': '소유자',
+ 'admin.mcpTokens.tokenName': '토큰 이름',
+ 'admin.mcpTokens.created': '생성일',
+ 'admin.mcpTokens.lastUsed': '마지막 사용',
+ 'admin.mcpTokens.never': '없음',
+ 'admin.mcpTokens.empty': '생성된 MCP 토큰이 없습니다',
+ 'admin.mcpTokens.deleteTitle': '토큰 삭제',
+ 'admin.mcpTokens.deleteMessage':
+ '토큰이 즉시 취소됩니다. 사용자는 이 토큰을 통한 MCP 접근 권한을 잃게 됩니다.',
+ 'admin.mcpTokens.deleteSuccess': '토큰이 삭제되었습니다',
+ 'admin.mcpTokens.deleteError': '토큰 삭제 실패',
+ 'admin.mcpTokens.loadError': '토큰 불러오기 실패',
+ 'admin.oauthSessions.sectionTitle': 'OAuth 세션',
+ 'admin.oauthSessions.clientName': '클라이언트',
+ 'admin.oauthSessions.owner': '소유자',
+ 'admin.oauthSessions.scopes': '권한 범위',
+ 'admin.oauthSessions.created': '생성일',
+ 'admin.oauthSessions.empty': '활성 OAuth 세션이 없습니다',
+ 'admin.oauthSessions.revokeTitle': '세션 취소',
+ 'admin.oauthSessions.revokeMessage':
+ 'OAuth 세션이 즉시 취소됩니다. 클라이언트는 MCP 접근 권한을 잃게 됩니다.',
+ 'admin.oauthSessions.revokeSuccess': '세션이 취소되었습니다',
+ 'admin.oauthSessions.revokeError': '세션 취소 실패',
+ 'admin.oauthSessions.loadError': 'OAuth 세션 불러오기 실패',
+ 'admin.tabs.github': 'GitHub',
+ 'admin.audit.subtitle': '보안 및 관리 이벤트 (백업, 사용자, MFA, 설정).',
+ 'admin.audit.empty': '아직 감사 항목이 없습니다.',
+ 'admin.audit.refresh': '새로 고침',
+ 'admin.audit.loadMore': '더 불러오기',
+ 'admin.audit.showing': '{count}개 불러옴 · 총 {total}개',
+ 'admin.audit.col.time': '시간',
+ 'admin.audit.col.user': '사용자',
+ 'admin.audit.col.action': '작업',
+ 'admin.audit.col.resource': '리소스',
+ 'admin.audit.col.ip': 'IP',
+ 'admin.audit.col.details': '상세',
+ 'admin.github.title': '릴리스 히스토리',
+ 'admin.github.subtitle': '{repo}의 최신 업데이트',
+ 'admin.github.latest': '최신',
+ 'admin.github.prerelease': '사전 릴리스',
+ 'admin.github.showDetails': '상세 보기',
+ 'admin.github.hideDetails': '상세 숨기기',
+ 'admin.github.loadMore': '더 불러오기',
+ 'admin.github.loading': '불러오는 중...',
+ 'admin.github.error': '릴리스 불러오기 실패',
+ 'admin.github.by': '작성자',
+ 'admin.github.support': 'TREK 개발 지속에 도움이 됩니다',
+ 'admin.update.available': '업데이트 사용 가능',
+ 'admin.update.text':
+ 'TREK {version}이(가) 사용 가능합니다. 현재 {current}을(를) 실행 중입니다.',
+ 'admin.update.button': 'GitHub에서 보기',
+ 'admin.update.install': '업데이트 설치',
+ 'admin.update.confirmTitle': '업데이트를 설치할까요?',
+ 'admin.update.confirmText':
+ 'TREK이 {current}에서 {version}으로 업데이트됩니다. 서버가 이후 자동으로 재시작됩니다.',
+ 'admin.update.dataInfo':
+ '모든 데이터 (여행, 사용자, API 키, 업로드, Vacay, Atlas, 예산)가 보존됩니다.',
+ 'admin.update.warning': '재시작 중에 앱이 잠시 사용할 수 없게 됩니다.',
+ 'admin.update.confirm': '지금 업데이트',
+ 'admin.update.installing': '업데이트 중…',
+ 'admin.update.success': '업데이트 완료! 서버가 재시작 중입니다…',
+ 'admin.update.failed': '업데이트 실패',
+ 'admin.update.backupHint': '업데이트 전에 백업을 생성하는 것을 권장합니다.',
+ 'admin.update.backupLink': '백업으로 이동',
+ 'admin.update.howTo': '업데이트 방법',
+ 'admin.update.dockerText':
+ 'TREK 인스턴스가 Docker에서 실행 중입니다. {version}으로 업데이트하려면 서버에서 다음 명령을 실행하세요:',
+ 'admin.update.reloadHint': '잠시 후 페이지를 새로 고침하세요.',
+ 'admin.tabs.permissions': '권한',
+ 'admin.addons.catalog.journey.name': 'Journey',
+ 'admin.addons.catalog.journey.description':
+ '체크인, 사진, 일별 이야기가 있는 여행 기록 및 여행 일지',
+ 'admin.passkey.title': '패스키 로그인',
+ 'admin.passkey.cardHint':
+ '사용자가 패스키(WebAuthn)로 로그인할 수 있게 합니다. 기본값은 꺼짐입니다.',
+ 'admin.passkey.login': '패스키 로그인 활성화',
+ 'admin.passkey.loginHint':
+ '"패스키로 로그인" 옵션을 표시하고 사용자가 설정에서 패스키를 등록할 수 있게 합니다.',
+ 'admin.passkey.notConfigured':
+ '이 배포에 아직 WebAuthn 도메인이 확인되지 않습니다. 아래에서 APP_URL 또는 Relying Party ID를 설정하세요 — 그 전까지 패스키는 숨겨진 상태로 유지됩니다.',
+ 'admin.passkey.rpId': 'Relying Party ID (도메인)',
+ 'admin.passkey.rpIdHint':
+ '패스키가 바인딩되는 순수 도메인입니다. 예: trek.example.org. 비워두면 APP_URL에서 자동으로 가져옵니다. 나중에 변경하면 기존 패스키가 무효화됩니다.',
+ 'admin.passkey.origins': '허용된 오리진',
+ 'admin.passkey.originsHint':
+ '쉼표로 구분된 전체 오리진입니다. 예: https://trek.example.org. 비워두면 APP_URL을 사용합니다.',
+ 'admin.passkey.reset': '패스키 초기화',
+ 'admin.passkey.resetHint':
+ '이 사용자의 모든 패스키를 삭제합니다 (예: 기기 분실 시). 사용자는 비밀번호로 계속 로그인할 수 있습니다.',
+ 'admin.passkey.resetConfirm': '{name}의 모든 패스키를 삭제할까요?',
+ 'admin.passkey.resetDone': '패스키 {count}개를 삭제했습니다',
+ 'admin.defaultSettings.mapProvider': '지도 엔진',
+ 'admin.defaultSettings.mapProviderHint': '이 인스턴스의 모든 사용자에게 적용되는 기본 지도입니다. 각 사용자는 자신의 설정에서 이를 변경할 수 있습니다.',
+ 'admin.defaultSettings.providerLeaflet': '표준 (무료)',
+ 'admin.defaultSettings.providerMapbox': 'Mapbox (3D)',
+ 'admin.defaultSettings.mapboxToken': '공유 Mapbox 토큰',
+ 'admin.defaultSettings.mapboxTokenHint': '자신의 토큰을 입력하지 않은 모든 사용자에게 사용됩니다 — 키를 개별적으로 공유하지 않아도 인스턴스 전체에서 Mapbox를 사용할 수 있습니다. 암호화하여 저장됩니다.',
+ 'admin.defaultSettings.mapboxStyle': '지도 스타일',
+ 'admin.defaultSettings.mapboxStylePlaceholder': '스타일을 선택하세요…',
+ 'admin.defaultSettings.mapbox3d': '3D 건물 & 지형',
+ 'admin.defaultSettings.mapboxQuality': '고품질 모드',
+};
+export default admin;
diff --git a/shared/src/i18n/ko/airport.ts b/shared/src/i18n/ko/airport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..19dccd83885ff840c13e379e1dc0540c471f240b
--- /dev/null
+++ b/shared/src/i18n/ko/airport.ts
@@ -0,0 +1,6 @@
+import type { TranslationStrings } from '../types';
+
+const airport: TranslationStrings = {
+ 'airport.searchPlaceholder': '공항 코드 또는 도시 (예: ICN)',
+};
+export default airport;
diff --git a/shared/src/i18n/ko/atlas.ts b/shared/src/i18n/ko/atlas.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ea8a9a4ee889c3eebd3fe090fb0c7135f27ed9ed
--- /dev/null
+++ b/shared/src/i18n/ko/atlas.ts
@@ -0,0 +1,58 @@
+import type { TranslationStrings } from '../types';
+
+const atlas: TranslationStrings = {
+ 'atlas.subtitle': '전 세계의 나의 여행 발자취',
+ 'atlas.countries': '국가',
+ 'atlas.trips': '여행',
+ 'atlas.places': '장소',
+ 'atlas.unmark': '제거',
+ 'atlas.confirmMark': '이 나라를 방문한 곳으로 표시할까요?',
+ 'atlas.confirmUnmark': '방문 목록에서 이 나라를 제거할까요?',
+ 'atlas.confirmUnmarkRegion': '방문 목록에서 이 지역을 제거할까요?',
+ 'atlas.markVisited': '방문으로 표시',
+ 'atlas.markVisitedHint': '방문 목록에 이 나라를 추가합니다',
+ 'atlas.markRegionVisitedHint': '방문 목록에 이 지역을 추가합니다',
+ 'atlas.addToBucket': '버킷 리스트에 추가',
+ 'atlas.addPoi': '장소 추가',
+ 'atlas.searchCountry': '국가 검색...',
+ 'atlas.bucketNamePlaceholder': '이름 (국가, 도시, 장소...)',
+ 'atlas.month': '월',
+ 'atlas.year': '연도',
+ 'atlas.addToBucketHint': '방문하고 싶은 장소로 저장',
+ 'atlas.bucketWhen': '방문 예정 시기는 언제인가요?',
+ 'atlas.statsTab': '통계',
+ 'atlas.bucketTab': '버킷 리스트',
+ 'atlas.addBucket': '버킷 리스트에 추가',
+ 'atlas.bucketNotesPlaceholder': '메모 (선택)',
+ 'atlas.bucketEmpty': '버킷 리스트가 비어 있습니다',
+ 'atlas.bucketEmptyHint': '꿈꾸는 방문지를 추가하세요',
+ 'atlas.days': '일',
+ 'atlas.visitedCountries': '방문한 나라',
+ 'atlas.cities': '도시',
+ 'atlas.noData': '아직 여행 데이터가 없습니다',
+ 'atlas.noDataHint': '여행을 만들고 장소를 추가하여 세계 지도를 확인하세요',
+ 'atlas.lastTrip': '마지막 여행',
+ 'atlas.nextTrip': '다음 여행',
+ 'atlas.daysLeft': '일 남음',
+ 'atlas.streak': '연속',
+ 'atlas.years': '년',
+ 'atlas.yearInRow': '연 연속',
+ 'atlas.yearsInRow': '년 연속',
+ 'atlas.tripIn': '에서 여행',
+ 'atlas.tripsIn': '에서 여행',
+ 'atlas.since': '부터',
+ 'atlas.europe': '유럽',
+ 'atlas.asia': '아시아',
+ 'atlas.northAmerica': '북미',
+ 'atlas.southAmerica': '남미',
+ 'atlas.africa': '아프리카',
+ 'atlas.oceania': '오세아니아',
+ 'atlas.other': '기타',
+ 'atlas.firstVisit': '첫 번째 여행',
+ 'atlas.lastVisitLabel': '마지막 여행',
+ 'atlas.tripSingular': '여행',
+ 'atlas.tripPlural': '여행',
+ 'atlas.placeVisited': '방문한 장소',
+ 'atlas.placesVisited': '방문한 장소',
+};
+export default atlas;
diff --git a/shared/src/i18n/ko/backup.ts b/shared/src/i18n/ko/backup.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e7de69c00fd879047e6fc8eb63443fb224e00bab
--- /dev/null
+++ b/shared/src/i18n/ko/backup.ts
@@ -0,0 +1,74 @@
+import type { TranslationStrings } from '../types';
+
+const backup: TranslationStrings = {
+ 'backup.title': '데이터 백업',
+ 'backup.subtitle': '데이터베이스 및 모든 업로드된 파일',
+ 'backup.refresh': '새로 고침',
+ 'backup.upload': '백업 업로드',
+ 'backup.uploading': '업로드 중…',
+ 'backup.create': '백업 만들기',
+ 'backup.creating': '생성 중…',
+ 'backup.empty': '아직 백업이 없습니다',
+ 'backup.createFirst': '첫 번째 백업 만들기',
+ 'backup.download': '다운로드',
+ 'backup.restore': '복원',
+ 'backup.confirm.restore':
+ '백업 "{name}"을(를) 복원할까요?\n\n현재 모든 데이터가 백업으로 교체됩니다.',
+ 'backup.confirm.uploadRestore':
+ '백업 파일 "{name}"을(를) 업로드하고 복원할까요?\n\n현재 모든 데이터가 덮어쓰여집니다.',
+ 'backup.confirm.delete': '백업 "{name}"을(를) 삭제할까요?',
+ 'backup.toast.loadError': '백업 불러오기 실패',
+ 'backup.toast.created': '백업이 성공적으로 생성되었습니다',
+ 'backup.toast.createError': '백업 생성 실패',
+ 'backup.toast.restored': '백업이 복원되었습니다. 페이지가 새로 고침됩니다…',
+ 'backup.toast.restoreError': '복원 실패',
+ 'backup.toast.uploadError': '업로드 실패',
+ 'backup.toast.deleted': '백업이 삭제되었습니다',
+ 'backup.toast.deleteError': '삭제 실패',
+ 'backup.toast.downloadError': '다운로드 실패',
+ 'backup.toast.settingsSaved': '자동 백업 설정이 저장되었습니다',
+ 'backup.toast.settingsError': '설정 저장 실패',
+ 'backup.auto.title': '자동 백업',
+ 'backup.auto.subtitle': '일정에 따른 자동 백업',
+ 'backup.auto.enable': '자동 백업 활성화',
+ 'backup.auto.enableHint': '선택한 일정에 따라 자동으로 백업이 생성됩니다',
+ 'backup.auto.interval': '간격',
+ 'backup.auto.hour': '실행 시간',
+ 'backup.auto.hourHint': '서버 현지 시간 ({format} 형식)',
+ 'backup.auto.dayOfWeek': '요일',
+ 'backup.auto.dayOfMonth': '매월 몇 일',
+ 'backup.auto.dayOfMonthHint': '모든 달과의 호환성을 위해 1-28로 제한',
+ 'backup.auto.scheduleSummary': '일정',
+ 'backup.auto.summaryDaily': '매일 {hour}:00',
+ 'backup.auto.summaryWeekly': '매주 {day} {hour}:00',
+ 'backup.auto.summaryMonthly': '매월 {day}일 {hour}:00',
+ 'backup.auto.envLocked': 'Docker',
+ 'backup.auto.envLockedHint':
+ '자동 백업이 Docker 환경 변수로 설정되어 있습니다. 설정을 변경하려면 docker-compose.yml을 업데이트하고 컨테이너를 재시작하세요.',
+ 'backup.auto.copyEnv': 'Docker 환경 변수 복사',
+ 'backup.auto.envCopied': 'Docker 환경 변수가 클립보드에 복사되었습니다',
+ 'backup.auto.keepLabel': '이후 오래된 백업 삭제',
+ 'backup.dow.sunday': '일',
+ 'backup.dow.monday': '월',
+ 'backup.dow.tuesday': '화',
+ 'backup.dow.wednesday': '수',
+ 'backup.dow.thursday': '목',
+ 'backup.dow.friday': '금',
+ 'backup.dow.saturday': '토',
+ 'backup.interval.hourly': '매시간',
+ 'backup.interval.daily': '매일',
+ 'backup.interval.weekly': '매주',
+ 'backup.interval.monthly': '매월',
+ 'backup.keep.1day': '1일',
+ 'backup.keep.3days': '3일',
+ 'backup.keep.7days': '7일',
+ 'backup.keep.14days': '14일',
+ 'backup.keep.30days': '30일',
+ 'backup.keep.forever': '영구 보관',
+ 'backup.restoreConfirmTitle': '백업을 복원할까요?',
+ 'backup.restoreWarning':
+ '현재 모든 데이터 (여행, 장소, 사용자, 업로드)가 백업으로 영구 교체됩니다. 이 작업은 취소할 수 없습니다.',
+ 'backup.restoreTip': '팁: 복원 전에 현재 상태의 백업을 만드세요.',
+ 'backup.restoreConfirm': '예, 복원',
+};
+export default backup;
diff --git a/shared/src/i18n/ko/budget.ts b/shared/src/i18n/ko/budget.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ef5cd4978042cba120c984746c457fe5a7f886b2
--- /dev/null
+++ b/shared/src/i18n/ko/budget.ts
@@ -0,0 +1,115 @@
+import type { TranslationStrings } from '../types';
+
+const budget: TranslationStrings = {
+ 'budget.title': '예산',
+ 'budget.exportCsv': 'CSV 내보내기',
+ 'budget.emptyTitle': '아직 예산이 없습니다',
+ 'budget.emptyText': '카테고리와 항목을 만들어 여행 예산을 계획하세요',
+ 'budget.emptyPlaceholder': '카테고리 이름을 입력하세요...',
+ 'budget.createCategory': '카테고리 만들기',
+ 'budget.category': '카테고리',
+ 'budget.categoryName': '카테고리 이름',
+ 'budget.table.name': '이름',
+ 'budget.table.total': '합계',
+ 'budget.table.persons': '인원',
+ 'budget.table.days': '일수',
+ 'budget.table.perPerson': '1인당',
+ 'budget.table.perDay': '일당',
+ 'budget.table.perPersonDay': '1인/일',
+ 'budget.table.note': '메모',
+ 'budget.table.date': '날짜',
+ 'budget.newEntry': '새 항목',
+ 'budget.defaultEntry': '새 항목',
+ 'budget.defaultCategory': '새 카테고리',
+ 'budget.total': '합계',
+ 'budget.totalBudget': '총 예산',
+ 'budget.byCategory': '카테고리별',
+ 'budget.editTooltip': '클릭하여 편집',
+ 'budget.linkedToReservation': '예약에 연결됨 — 이름은 예약에서 편집하세요',
+ 'budget.confirm.deleteCategory':
+ '카테고리 "{name}"을(를) {count}개 항목과 함께 삭제할까요?',
+ 'budget.deleteCategory': '카테고리 삭제',
+ 'budget.perPerson': '1인당',
+ 'budget.paid': '지불됨',
+ 'budget.open': '미결',
+ 'budget.noMembers': '배정된 멤버가 없습니다',
+ 'budget.settlement': '정산',
+ 'budget.settlementInfo':
+ '예산 항목의 멤버 아바타를 클릭하면 녹색으로 표시됩니다 — 해당 멤버가 지불했음을 의미합니다. 그러면 정산에서 누가 누구에게 얼마를 지불해야 하는지 보여줍니다.',
+ 'budget.netBalances': '순 잔액',
+ 'budget.categoriesLabel': '카테고리',
+ "costs.you": "나",
+ "costs.youShort": "Y",
+ "costs.youLower": "나",
+ "costs.youOwe": "내가 줄 돈",
+ "costs.youOweSub": "다른 사람에게 지불해야 합니다",
+ "costs.youreOwed": "받을 돈",
+ "costs.youreOwedSub": "다른 사람이 나에게 지불해야 합니다",
+ "costs.totalSpend": "총 여행 지출",
+ "costs.totalSpendSub": "모든 여행자 합계",
+ "costs.to": "받는 사람",
+ "costs.from": "보낸 사람",
+ "costs.allSettled": "모두 정산되었습니다",
+ "costs.nothingOwed": "받을 돈이 없습니다",
+ "costs.yourShare": "내 몫",
+ "costs.youPaid": "내가 지불함",
+ "costs.expenses": "지출",
+ "costs.entries": "{count}개 항목",
+ "costs.searchPlaceholder": "지출 검색…",
+ "costs.filter.all": "전체",
+ "costs.filter.mine": "내가 지불",
+ "costs.filter.owed": "받을 돈",
+ "costs.addExpense": "지출 추가",
+ "costs.editExpense": "지출 편집",
+ "costs.noMatch": "검색과 일치하는 지출이 없습니다.",
+ "costs.emptyText": "아직 지출이 없습니다. 첫 항목을 추가하세요.",
+ "costs.spent": "{amount} 지출",
+ "costs.noDate": "날짜 없음",
+ "costs.noOnePaid": "아직 아무도 지불하지 않음",
+ "costs.youLent": "{amount} 빌려줌",
+ "costs.youBorrowed": "{amount} 빌림",
+ "costs.settleUp": "정산하기",
+ "costs.history": "내역",
+ "costs.everyoneSquare": "모두 정산 완료",
+ "costs.nothingOutstanding": "현재 미결제 금액이 없습니다.",
+ "costs.pay": "지불",
+ "costs.pays": "지불",
+ "costs.settle": "정산",
+ "costs.balances": "잔액",
+ "costs.byCategory": "카테고리별",
+ "costs.noCategories": "아직 지출이 없습니다.",
+ "costs.settleHistory": "정산 내역",
+ "costs.noSettlements": "아직 정산된 결제가 없습니다.",
+ "costs.paymentsSettled": "{count}건 정산됨",
+ "costs.paid": "지불됨",
+ "costs.undo": "실행 취소",
+ "costs.whatFor": "무엇을 위한 것인가요?",
+ "costs.namePlaceholder": "예: 저녁 식사, 기념품, 주유…",
+ "costs.totalAmount": "총 금액",
+ "costs.currency": "통화",
+ "costs.day": "날짜",
+ "costs.rateLabel": "1 {from} = {to}",
+ "costs.category": "카테고리",
+ "costs.whoPaid": "누가 지불했나요?",
+ "costs.splitBetween": "균등 분할 대상",
+ "costs.pickSomeone": "분할할 사람을 한 명 이상 선택하세요.",
+ "costs.splitSummary": "{count}명 분할 · 각 {amount}",
+ "costs.cat.accommodation": "숙박",
+ "costs.cat.food": "식음료",
+ "costs.cat.groceries": "식료품",
+ "costs.cat.transport": "교통",
+ "costs.cat.flights": "항공편",
+ "costs.cat.activities": "액티비티",
+ "costs.cat.sightseeing": "관광",
+ "costs.cat.shopping": "쇼핑",
+ "costs.cat.fees": "요금 및 입장권",
+ "costs.cat.health": "건강",
+ "costs.cat.tips": "팁",
+ "costs.cat.other": "기타",
+ "costs.daysCount": "{count}일",
+ "costs.travelers": "여행자 {count}명",
+ "costs.liveRate": "실시간 환율",
+ "costs.settleAll": "전체 정산",
+};
+
+export default budget;
diff --git a/shared/src/i18n/ko/categories.ts b/shared/src/i18n/ko/categories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..793149f709274c434c786c437ff55e2fb22348af
--- /dev/null
+++ b/shared/src/i18n/ko/categories.ts
@@ -0,0 +1,26 @@
+import type { TranslationStrings } from '../types';
+
+const categories: TranslationStrings = {
+ 'categories.title': '카테고리',
+ 'categories.subtitle': '장소 카테고리 관리',
+ 'categories.new': '새 카테고리',
+ 'categories.empty': '아직 카테고리가 없습니다',
+ 'categories.namePlaceholder': '카테고리 이름',
+ 'categories.icon': '아이콘',
+ 'categories.color': '색상',
+ 'categories.customColor': '사용자 지정 색상 선택',
+ 'categories.preview': '미리보기',
+ 'categories.defaultName': '카테고리',
+ 'categories.update': '업데이트',
+ 'categories.create': '생성',
+ 'categories.confirm.delete':
+ '카테고리를 삭제할까요? 이 카테고리의 장소는 삭제되지 않습니다.',
+ 'categories.toast.loadError': '카테고리 불러오기 실패',
+ 'categories.toast.nameRequired': '이름을 입력하세요',
+ 'categories.toast.updated': '카테고리가 업데이트되었습니다',
+ 'categories.toast.created': '카테고리가 생성되었습니다',
+ 'categories.toast.saveError': '저장 실패',
+ 'categories.toast.deleted': '카테고리가 삭제되었습니다',
+ 'categories.toast.deleteError': '삭제 실패',
+};
+export default categories;
diff --git a/shared/src/i18n/ko/collab.ts b/shared/src/i18n/ko/collab.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e970e43fd1602d449da961be200bc0e0b305bf05
--- /dev/null
+++ b/shared/src/i18n/ko/collab.ts
@@ -0,0 +1,75 @@
+import type { TranslationStrings } from '../types';
+
+const collab: TranslationStrings = {
+ 'collab.tabs.chat': '채팅',
+ 'collab.tabs.notes': '메모',
+ 'collab.tabs.polls': '투표',
+ 'collab.whatsNext.title': '다음 할 일',
+ 'collab.whatsNext.today': '오늘',
+ 'collab.whatsNext.tomorrow': '내일',
+ 'collab.whatsNext.empty': '예정된 활동이 없습니다',
+ 'collab.whatsNext.until': '까지',
+ 'collab.whatsNext.emptyHint': '시간이 있는 활동이 여기에 표시됩니다',
+ 'collab.chat.send': '전송',
+ 'collab.chat.placeholder': '메시지 입력...',
+ 'collab.chat.empty': '대화를 시작하세요',
+ 'collab.chat.emptyHint': '메시지는 모든 여행 멤버와 공유됩니다',
+ 'collab.chat.emptyDesc': '여행 그룹과 아이디어, 계획, 업데이트를 공유하세요',
+ 'collab.chat.today': '오늘',
+ 'collab.chat.yesterday': '어제',
+ 'collab.chat.deletedMessage': '메시지를 삭제했습니다',
+ 'collab.chat.reply': '답장',
+ 'collab.chat.loadMore': '이전 메시지 불러오기',
+ 'collab.chat.justNow': '방금 전',
+ 'collab.chat.minutesAgo': '{n}분 전',
+ 'collab.chat.hoursAgo': '{n}시간 전',
+ 'collab.notes.title': '메모',
+ 'collab.notes.new': '새 메모',
+ 'collab.notes.empty': '아직 메모가 없습니다',
+ 'collab.notes.emptyHint': '아이디어와 계획을 기록하세요',
+ 'collab.notes.all': '전체',
+ 'collab.notes.titlePlaceholder': '메모 제목',
+ 'collab.notes.contentPlaceholder': '내용을 입력하세요...',
+ 'collab.notes.categoryPlaceholder': '카테고리',
+ 'collab.notes.newCategory': '새 카테고리...',
+ 'collab.notes.category': '카테고리',
+ 'collab.notes.noCategory': '카테고리 없음',
+ 'collab.notes.color': '색상',
+ 'collab.notes.save': '저장',
+ 'collab.notes.cancel': '취소',
+ 'collab.notes.edit': '편집',
+ 'collab.notes.delete': '삭제',
+ 'collab.notes.confirmDeleteTitle': '메모를 삭제할까요?',
+ 'collab.notes.confirmDeleteBody': '이 메모가 영구적으로 삭제됩니다.',
+ 'collab.notes.pin': '고정',
+ 'collab.notes.unpin': '고정 해제',
+ 'collab.notes.daysAgo': '{n}일 전',
+ 'collab.notes.categorySettings': '카테고리 관리',
+ 'collab.notes.create': '생성',
+ 'collab.notes.website': '웹사이트',
+ 'collab.notes.websitePlaceholder': 'https://...',
+ 'collab.notes.attachFiles': '파일 첨부',
+ 'collab.notes.noCategoriesYet': '아직 카테고리가 없습니다',
+ 'collab.notes.emptyDesc': '메모를 만들어 시작하세요',
+ 'collab.polls.title': '투표',
+ 'collab.polls.new': '새 투표',
+ 'collab.polls.empty': '아직 투표가 없습니다',
+ 'collab.polls.emptyHint': '그룹에 질문하고 함께 투표하세요',
+ 'collab.polls.question': '질문',
+ 'collab.polls.questionPlaceholder': '무엇을 할까요?',
+ 'collab.polls.addOption': '+ 옵션 추가',
+ 'collab.polls.optionPlaceholder': '옵션 {n}',
+ 'collab.polls.create': '투표 만들기',
+ 'collab.polls.close': '닫기',
+ 'collab.polls.closed': '종료됨',
+ 'collab.polls.votes': '{n}표',
+ 'collab.polls.vote': '{n}표',
+ 'collab.polls.multipleChoice': '복수 선택',
+ 'collab.polls.multiChoice': '복수 선택',
+ 'collab.polls.deadline': '마감일',
+ 'collab.polls.option': '옵션',
+ 'collab.polls.options': '옵션',
+ 'collab.polls.delete': '삭제',
+ 'collab.polls.closedSection': '종료됨',
+};
+export default collab;
diff --git a/shared/src/i18n/ko/common.ts b/shared/src/i18n/ko/common.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d12594693c29051b533f912bd2fe21764d01ca12
--- /dev/null
+++ b/shared/src/i18n/ko/common.ts
@@ -0,0 +1,55 @@
+import type { TranslationStrings } from '../types';
+
+const common: TranslationStrings = {
+ 'common.save': '저장',
+ 'common.showMore': '더 보기',
+ 'common.showLess': '접기',
+ 'common.cancel': '취소',
+ 'common.clear': '지우기',
+ 'common.delete': '삭제',
+ 'common.edit': '편집',
+ 'common.add': '추가',
+ 'common.loading': '로딩 중...',
+ 'common.import': '가져오기',
+ 'common.select': '선택',
+ 'common.selectAll': '전체 선택',
+ 'common.deselectAll': '전체 해제',
+ 'common.error': '오류',
+ 'common.unknownError': '알 수 없는 오류',
+ 'common.tooManyAttempts':
+ '시도 횟수가 너무 많습니다. 잠시 후 다시 시도해 주세요.',
+ 'common.back': '뒤로',
+ 'common.all': '전체',
+ 'common.close': '닫기',
+ 'common.open': '열기',
+ 'common.upload': '업로드',
+ 'common.search': '검색',
+ 'common.confirm': '확인',
+ 'common.ok': '확인',
+ 'common.yes': '예',
+ 'common.no': '아니오',
+ 'common.or': '또는',
+ 'common.none': '없음',
+ 'common.date': '날짜',
+ 'common.rename': '이름 변경',
+ 'common.discardChanges': '변경 사항 취소',
+ 'common.discard': '취소',
+ 'common.name': '이름',
+ 'common.email': '이메일',
+ 'common.password': '비밀번호',
+ 'common.saving': '저장 중...',
+ 'common.justNow': '방금 전',
+ 'common.hoursAgo': '{count}시간 전',
+ 'common.daysAgo': '{count}일 전',
+ 'common.saved': '저장됨',
+ 'common.update': '업데이트',
+ 'common.change': '변경',
+ 'common.uploading': '업로드 중…',
+ 'common.backToPlanning': '계획으로 돌아가기',
+ 'common.reset': '초기화',
+ 'common.expand': '펼치기',
+ 'common.collapse': '접기',
+ 'common.copy': '복사',
+ 'common.copied': '복사됨',
+};
+export default common;
diff --git a/shared/src/i18n/ko/dashboard.ts b/shared/src/i18n/ko/dashboard.ts
new file mode 100644
index 0000000000000000000000000000000000000000..801272c268b3247097376e3735620a2ec4e9ef14
--- /dev/null
+++ b/shared/src/i18n/ko/dashboard.ts
@@ -0,0 +1,167 @@
+import type { TranslationStrings } from '../types';
+
+const dashboard: TranslationStrings = {
+ 'dashboard.title': '내 여행',
+ 'dashboard.subtitle.loading': '여행 불러오는 중...',
+ 'dashboard.subtitle.trips': '{count}개 여행 ({archived}개 보관됨)',
+ 'dashboard.subtitle.empty': '첫 번째 여행을 시작하세요',
+ 'dashboard.subtitle.activeOne': '활성 여행 {count}개',
+ 'dashboard.subtitle.activeMany': '활성 여행 {count}개',
+ 'dashboard.subtitle.archivedSuffix': ' · {count}개 보관됨',
+ 'dashboard.newTrip': '새 여행',
+ 'dashboard.newTripSub': '새 여행을 처음부터 계획하기',
+ 'dashboard.gridView': '격자 보기',
+ 'dashboard.listView': '목록 보기',
+ 'dashboard.currency': '통화',
+ 'dashboard.timezone': '시간대',
+ 'dashboard.localTime': '현지',
+ 'dashboard.timezoneCustomTitle': '사용자 지정 시간대',
+ 'dashboard.timezoneCustomLabelPlaceholder': '레이블 (선택)',
+ 'dashboard.timezoneCustomTzPlaceholder': '예: America/New_York',
+ 'dashboard.timezoneCustomAdd': '추가',
+ 'dashboard.timezoneCustomErrorEmpty': '시간대 식별자를 입력하세요',
+ 'dashboard.timezoneCustomErrorInvalid':
+ '잘못된 시간대입니다. Europe/Berlin 같은 형식을 사용하세요',
+ 'dashboard.timezoneCustomErrorDuplicate': '이미 추가됨',
+ 'dashboard.emptyTitle': '아직 여행이 없습니다',
+ 'dashboard.emptyText': '첫 번째 여행을 만들고 계획을 시작하세요!',
+ 'dashboard.emptyButton': '첫 번째 여행 만들기',
+ 'dashboard.nextTrip': '다음 여행',
+ 'dashboard.shared': '공유됨',
+ 'dashboard.sharedBy': '{name}이(가) 공유',
+ 'dashboard.days': '일',
+ 'dashboard.places': '장소',
+ 'dashboard.members': '동행자',
+ 'dashboard.archive': '보관',
+ 'dashboard.copyTrip': '복사',
+ 'dashboard.copySuffix': '사본',
+ 'dashboard.restore': '복원',
+ 'dashboard.archived': '보관됨',
+ 'dashboard.status.ongoing': '진행 중',
+ 'dashboard.status.today': '오늘',
+ 'dashboard.status.tomorrow': '내일',
+ 'dashboard.status.past': '지난 여행',
+ 'dashboard.status.daysLeft': '{count}일 남음',
+ 'dashboard.toast.loadError': '여행 불러오기 실패',
+ 'dashboard.toast.created': '여행이 생성되었습니다!',
+ 'dashboard.toast.createError': '여행 생성 실패',
+ 'dashboard.toast.updated': '여행이 업데이트되었습니다!',
+ 'dashboard.toast.updateError': '여행 업데이트 실패',
+ 'dashboard.toast.deleted': '여행이 삭제되었습니다',
+ 'dashboard.toast.deleteError': '여행 삭제 실패',
+ 'dashboard.toast.archived': '여행이 보관되었습니다',
+ 'dashboard.toast.archiveError': '여행 보관 실패',
+ 'dashboard.toast.restored': '여행이 복원되었습니다',
+ 'dashboard.toast.restoreError': '여행 복원 실패',
+ 'dashboard.toast.copied': '여행이 복사되었습니다!',
+ 'dashboard.toast.copyError': '여행 복사 실패',
+ 'dashboard.confirm.delete':
+ '여행 "{title}"을(를) 삭제할까요? 모든 장소와 계획이 영구 삭제됩니다.',
+ 'dashboard.confirm.copy.title': '이 여행을 복사할까요?',
+ 'dashboard.confirm.copy.willCopy': '복사될 항목',
+ 'dashboard.confirm.copy.will1': '일정, 장소 및 일별 배정',
+ 'dashboard.confirm.copy.will2': '숙박 및 예약',
+ 'dashboard.confirm.copy.will3': '예산 항목 및 카테고리 순서',
+ 'dashboard.confirm.copy.will4': '짐 목록 (체크 해제 상태)',
+ 'dashboard.confirm.copy.will5': '할 일 (미배정 및 미완료)',
+ 'dashboard.confirm.copy.will6': '일별 메모',
+ 'dashboard.confirm.copy.wontCopy': '복사되지 않는 항목',
+ 'dashboard.confirm.copy.wont1': '공동 작업자 및 멤버 배정',
+ 'dashboard.confirm.copy.wont2': '공동 작업 메모, 투표 및 메시지',
+ 'dashboard.confirm.copy.wont3': '파일 및 사진',
+ 'dashboard.confirm.copy.wont4': '공유 토큰',
+ 'dashboard.confirm.copy.confirm': '여행 복사',
+ 'dashboard.editTrip': '여행 편집',
+ 'dashboard.createTrip': '새 여행 만들기',
+ 'dashboard.tripTitle': '제목',
+ 'dashboard.tripTitlePlaceholder': '예: 일본에서의 여름',
+ 'dashboard.tripDescription': '설명',
+ 'dashboard.tripDescriptionPlaceholder': '이 여행에 대해 설명해 주세요',
+ 'dashboard.startDate': '시작일',
+ 'dashboard.endDate': '종료일',
+ 'dashboard.dayCount': '일수',
+ 'dashboard.dayCountHint': '여행 날짜가 설정되지 않은 경우 계획할 일수입니다.',
+ 'dashboard.noDateHint':
+ '날짜 미설정 — 기본 7일이 생성됩니다. 언제든지 변경할 수 있습니다.',
+ 'dashboard.coverImage': '커버 이미지',
+ 'dashboard.addCoverImage': '커버 이미지 추가 (또는 끌어다 놓기)',
+ 'dashboard.addMembers': '동행자',
+ 'dashboard.addMember': '멤버 추가',
+ 'dashboard.coverSaved': '커버 이미지가 저장되었습니다',
+ 'dashboard.coverUploadError': '업로드 실패',
+ 'dashboard.coverRemoveError': '삭제 실패',
+ 'dashboard.titleRequired': '제목을 입력하세요',
+ 'dashboard.endDateError': '종료일은 시작일 이후여야 합니다',
+ 'dashboard.greeting.morning': '좋은 아침이에요,',
+ 'dashboard.greeting.afternoon': '안녕하세요,',
+ 'dashboard.greeting.evening': '좋은 저녁이에요,',
+ 'dashboard.mobile.liveNow': '지금 라이브',
+ 'dashboard.mobile.tripProgress': '여행 진행도',
+ 'dashboard.mobile.daysLeft': '{count}일 남음',
+ 'dashboard.mobile.places': '장소',
+ 'dashboard.mobile.buddies': '동행자',
+ 'dashboard.mobile.newTrip': '새 여행',
+ 'dashboard.mobile.currency': '통화',
+ 'dashboard.mobile.timezone': '시간대',
+ 'dashboard.mobile.upcomingTrips': '예정된 여행',
+ 'dashboard.mobile.yourTrips': '내 여행',
+ 'dashboard.mobile.trips': '개 여행',
+ 'dashboard.mobile.starts': '시작',
+ 'dashboard.mobile.duration': '기간',
+ 'dashboard.mobile.day': '일',
+ 'dashboard.mobile.days': '일',
+ 'dashboard.mobile.ongoing': '진행 중',
+ 'dashboard.mobile.startsToday': '오늘 시작',
+ 'dashboard.mobile.tomorrow': '내일',
+ 'dashboard.mobile.inDays': '{count}일 후',
+ 'dashboard.mobile.inMonths': '{count}개월 후',
+ 'dashboard.mobile.completed': '완료됨',
+ 'dashboard.mobile.currencyConverter': '환율 계산기',
+ 'dashboard.filter.planned': '예정',
+ 'dashboard.hero.badgeLive': '진행 중',
+ 'dashboard.hero.badgeToday': '오늘 시작',
+ 'dashboard.hero.badgeTomorrow': '내일',
+ 'dashboard.hero.badgeNext': '다음 여행',
+ 'dashboard.hero.badgeRecent': '최근',
+ 'dashboard.hero.tripDates': '여행 날짜',
+ 'dashboard.hero.noDates': '날짜 미설정',
+ 'dashboard.hero.travelerOne': '여행자 {count}명',
+ 'dashboard.hero.travelerMany': '여행자 {count}명',
+ 'dashboard.hero.destinationOne': '목적지 {count}곳',
+ 'dashboard.hero.destinationMany': '목적지 {count}곳',
+ 'dashboard.hero.dayUnitOne': '일',
+ 'dashboard.hero.dayUnitMany': '일',
+ 'dashboard.hero.dayLeft': '하루 남음',
+ 'dashboard.hero.daysLeft': '남은 일수',
+ 'dashboard.hero.lastDay': '마지막 날',
+ 'dashboard.hero.untilStart': '시작까지',
+ 'dashboard.hero.startsIn': '출발까지',
+ 'dashboard.atlas.countriesVisited': '아틀라스 · 방문한 국가',
+ 'dashboard.atlas.ofTotal': '/ {total}',
+ 'dashboard.atlas.tripsTotal': '총 여행',
+ 'dashboard.atlas.placesMapped': '{count}개 장소 기록',
+ 'dashboard.atlas.daysTraveled': '여행 일수',
+ 'dashboard.atlas.daysUnit': '일',
+ 'dashboard.atlas.acrossAllTrips': '모든 여행 통틀어',
+ 'dashboard.atlas.distanceFlown': '비행 거리',
+ 'dashboard.atlas.kmUnit': 'km',
+ 'dashboard.atlas.aroundEquator': '≈ 적도 {count}바퀴',
+ 'dashboard.card.idea': '아이디어',
+ 'dashboard.card.buddyOne': '동행자',
+ 'dashboard.fx.from': '보낼 통화',
+ 'dashboard.fx.to': '받을 통화',
+ 'dashboard.fx.unavailable': '환율을 사용할 수 없음',
+ 'dashboard.tz.searchPlaceholder': '시간대 검색…',
+ 'dashboard.tz.empty': '다른 시간대가 아직 없습니다 — +로 추가하세요',
+ 'dashboard.upcoming.title': '예정된 예약',
+ 'dashboard.upcoming.empty': '아직 예약이 없습니다.',
+ 'dashboard.aria.toggleView': '보기 전환',
+ 'dashboard.aria.filter': '필터',
+ 'dashboard.aria.duplicate': '복제',
+ 'dashboard.aria.refreshRates': '환율 새로고침',
+ 'dashboard.aria.swapCurrencies': '통화 바꾸기',
+ 'dashboard.aria.addTimezone': '시간대 추가',
+ 'dashboard.aria.removeTimezone': '{city} 제거',
+ 'dashboard.dayCountRequired': '일수는 필수입니다',
+};
+export default dashboard;
diff --git a/shared/src/i18n/ko/day.ts b/shared/src/i18n/ko/day.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b733f9dd97913d8eb60b18c0484303250d3938f9
--- /dev/null
+++ b/shared/src/i18n/ko/day.ts
@@ -0,0 +1,26 @@
+import type { TranslationStrings } from '../types';
+
+const day: TranslationStrings = {
+ 'day.precipProb': '강수 확률',
+ 'day.precipitation': '강수량',
+ 'day.wind': '바람',
+ 'day.sunrise': '일출',
+ 'day.sunset': '일몰',
+ 'day.hourlyForecast': '시간별 예보',
+ 'day.climateHint':
+ '역사적 평균값 — 이 날짜로부터 16일 이내의 실제 예보를 사용할 수 있습니다.',
+ 'day.noWeather': '날씨 데이터가 없습니다. 좌표가 있는 장소를 추가하세요.',
+ 'day.overview': '일별 개요',
+ 'day.accommodation': '숙박',
+ 'day.addAccommodation': '숙박 추가',
+ 'day.hotelDayRange': '적용할 날',
+ 'day.noPlacesForHotel': '먼저 여행에 장소를 추가하세요',
+ 'day.allDays': '전체',
+ 'day.checkIn': '체크인',
+ 'day.checkInUntil': '까지',
+ 'day.checkOut': '체크아웃',
+ 'day.confirmation': '확인',
+ 'day.editAccommodation': '숙박 편집',
+ 'day.reservations': '예약',
+};
+export default day;
diff --git a/shared/src/i18n/ko/dayplan.ts b/shared/src/i18n/ko/dayplan.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2f9f78cb62b09e605d5119498d9ae270892c0f16
--- /dev/null
+++ b/shared/src/i18n/ko/dayplan.ts
@@ -0,0 +1,58 @@
+import type { TranslationStrings } from '../types';
+
+const dayplan: TranslationStrings = {
+ 'dayplan.icsTooltip': '캘린더 내보내기 (ICS)',
+ 'dayplan.emptyDay': '이 날에 계획된 장소가 없습니다',
+ 'dayplan.cannotReorderTransport':
+ '고정된 시간이 있는 예약은 순서를 변경할 수 없습니다',
+ 'dayplan.confirmRemoveTimeTitle': '시간을 제거할까요?',
+ 'dayplan.confirmRemoveTimeBody':
+ '이 장소에 고정된 시간 ({time})이 있습니다. 이동하면 시간이 제거되고 자유 정렬이 허용됩니다.',
+ 'dayplan.confirmRemoveTimeAction': '시간 제거 및 이동',
+ 'dayplan.confirmDeleteNoteTitle': '메모를 삭제할까요?',
+ 'dayplan.confirmDeleteNoteBody': '이 메모가 영구적으로 삭제됩니다.',
+ 'dayplan.cannotDropOnTimed': '시간이 고정된 항목 사이에 배치할 수 없습니다',
+ 'dayplan.cannotBreakChronology':
+ '이 작업은 시간 고정 항목과 예약의 시간 순서를 깨뜨립니다',
+ 'dayplan.addNote': '메모 추가',
+ 'dayplan.expandAll': '모든 날 펼치기',
+ 'dayplan.collapseAll': '모든 날 접기',
+ 'dayplan.editNote': '메모 편집',
+ 'dayplan.noteAdd': '메모 추가',
+ 'dayplan.noteEdit': '메모 편집',
+ 'dayplan.noteTitle': '메모',
+ 'dayplan.noteSubtitle': '일별 메모',
+ 'dayplan.totalCost': '총 비용',
+ 'dayplan.days': '일',
+ 'dayplan.dayN': '{n}일차',
+ 'dayplan.calculating': '계산 중...',
+ 'dayplan.route': '경로',
+ 'dayplan.optimize': '최적화',
+ 'dayplan.optimized': '경로가 최적화되었습니다',
+ 'dayplan.routeError': '경로 계산 실패',
+ 'dayplan.toast.needTwoPlaces':
+ '경로 최적화에는 최소 두 개의 장소가 필요합니다',
+ 'dayplan.toast.routeOptimized': '경로가 최적화되었습니다',
+ 'dayplan.toast.routeOptimizedFromHotel': '숙소를 기준으로 경로가 최적화되었습니다',
+ 'dayplan.toast.noGeoPlaces': '경로 계산을 위한 좌표가 있는 장소가 없습니다',
+ 'dayplan.confirmed': '확정됨',
+ 'dayplan.pendingRes': '대기 중',
+ 'dayplan.pdf': 'PDF',
+ 'dayplan.pdfTooltip': '일별 계획을 PDF로 내보내기',
+ 'dayplan.pdfError': 'PDF 내보내기 실패',
+ 'dayplan.mobile.addPlace': '장소 추가',
+ 'dayplan.mobile.searchPlaces': '장소 검색...',
+ 'dayplan.mobile.allAssigned': '모든 장소가 배정되었습니다',
+ 'dayplan.mobile.noMatch': '일치 없음',
+ 'dayplan.mobile.createNew': '새 장소 만들기',
+ 'dayplan.reorderDays': '날짜 순서 변경',
+ 'dayplan.reorderTitle': '날짜 순서 변경',
+ 'dayplan.reorderHint': '해당 날짜의 장소, 메모, 예약이 함께 이동합니다.',
+ 'dayplan.addDay': '날짜 추가',
+ 'dayplan.moveUp': '위로 이동',
+ 'dayplan.moveDown': '아래로 이동',
+ 'dayplan.reorderUndo': '날짜 순서 변경',
+ 'dayplan.reorderError': '날짜 순서를 변경하지 못했습니다',
+ 'dayplan.addDayError': '날짜를 추가하지 못했습니다',
+};
+export default dayplan;
diff --git a/shared/src/i18n/ko/externalNotifications.ts b/shared/src/i18n/ko/externalNotifications.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fa5d70673bbc21abba9215489495dd7f1d8a2538
--- /dev/null
+++ b/shared/src/i18n/ko/externalNotifications.ts
@@ -0,0 +1,63 @@
+import type { NotificationLocale } from '../externalNotifications/types';
+
+const ko: NotificationLocale = {
+ email: {
+ footer: 'TREK에서 알림을 활성화했기 때문에 이 이메일을 받으셨습니다.',
+ manage: '설정에서 환경설정 관리',
+ madeWith: 'Made with',
+ openTrek: 'TREK 열기',
+ },
+ events: {
+ trip_invite: (p) => ({
+ title: `"${p.trip}" 여행 초대`,
+ body: `${p.actor}이(가) ${p.invitee || '멤버'}를 "${p.trip}" 여행에 초대했습니다.`,
+ }),
+ booking_change: (p) => ({
+ title: `새 예약: ${p.booking}`,
+ body: `${p.actor}이(가) "${p.trip}"에 "${p.booking}" (${p.type}) 예약을 추가했습니다.`,
+ }),
+ trip_reminder: (p) => ({
+ title: `여행 알림: ${p.trip}`,
+ body: `"${p.trip}" 여행이 곧 시작됩니다!`,
+ }),
+ todo_due: (p) => ({
+ title: `할 일 마감: ${p.todo}`,
+ body: `"${p.trip}"의 "${p.todo}"은(는) ${p.due}에 마감됩니다.`,
+ }),
+ vacay_invite: (p) => ({
+ title: 'Vacay Fusion 초대',
+ body: `${p.actor}이(가) 휴가 계획을 합치도록 초대했습니다. TREK을 열어 수락하거나 거절하세요.`,
+ }),
+ photos_shared: (p) => ({
+ title: `${p.count}장의 사진이 공유되었습니다`,
+ body: `${p.actor}이(가) "${p.trip}"에서 ${p.count}장의 사진을 공유했습니다.`,
+ }),
+ collab_message: (p) => ({
+ title: `"${p.trip}"의 새 메시지`,
+ body: `${p.actor}: ${p.preview}`,
+ }),
+ packing_tagged: (p) => ({
+ title: `짐 꾸리기: ${p.category}`,
+ body: `${p.actor}이(가) "${p.trip}"의 "${p.category}" 카테고리에 당신을 할당했습니다.`,
+ }),
+ version_available: (p) => ({
+ title: '새 TREK 버전 사용 가능',
+ body: `TREK ${p.version}을 사용할 수 있습니다. 관리자 패널에서 업데이트하세요.`,
+ }),
+ synology_session_cleared: () => ({
+ title: 'Synology 세션이 초기화되었습니다',
+ body: 'Synology 계정 또는 URL이 변경되었습니다. Synology Photos에서 로그아웃되었습니다.',
+ }),
+ },
+ passwordReset: {
+ subject: '비밀번호 재설정',
+ greeting: '안녕하세요',
+ body: 'TREK 계정 비밀번호 재설정 요청을 받았습니다. 아래 버튼을 클릭하여 새 비밀번호를 설정하세요.',
+ ctaIntro: '비밀번호 재설정',
+ expiry: '이 링크는 60분 후에 만료됩니다.',
+ ignore:
+ '본인이 요청하지 않으셨다면 이 이메일을 무시하셔도 됩니다 — 비밀번호는 변경되지 않습니다.',
+ },
+};
+
+export default ko;
diff --git a/shared/src/i18n/ko/files.ts b/shared/src/i18n/ko/files.ts
new file mode 100644
index 0000000000000000000000000000000000000000..540e9e6b9e82bfa4b8b595fba88df2e85f1e156a
--- /dev/null
+++ b/shared/src/i18n/ko/files.ts
@@ -0,0 +1,62 @@
+import type { TranslationStrings } from '../types';
+
+const files: TranslationStrings = {
+ 'files.title': '파일',
+ 'files.pageTitle': '파일 및 서류',
+ 'files.subtitle': '{trip}의 파일 {count}개',
+ 'files.download': '다운로드',
+ 'files.openError': '파일을 열 수 없습니다',
+ 'files.downloadPdf': 'PDF 다운로드',
+ 'files.count': '파일 {count}개',
+ 'files.countSingular': '파일 1개',
+ 'files.uploaded': '{count}개 업로드됨',
+ 'files.uploadError': '업로드 실패',
+ 'files.dropzone': '여기에 파일을 놓으세요',
+ 'files.dropzoneHint': '또는 클릭하여 탐색',
+ 'files.allowedTypes':
+ '이미지, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · 최대 50 MB',
+ 'files.uploading': '업로드 중...',
+ 'files.filterAll': '전체',
+ 'files.filterPdf': 'PDF',
+ 'files.filterImages': '이미지',
+ 'files.filterDocs': '문서',
+ 'files.filterCollab': 'Collab 메모',
+ 'files.sourceCollab': 'Collab 메모에서',
+ 'files.empty': '아직 파일이 없습니다',
+ 'files.emptyHint': '파일을 업로드하여 여행에 첨부하세요',
+ 'files.openTab': '새 탭에서 열기',
+ 'files.confirm.delete': '이 파일을 삭제할까요?',
+ 'files.toast.deleted': '파일이 삭제되었습니다',
+ 'files.toast.deleteError': '파일 삭제 실패',
+ 'files.sourcePlan': '일별 계획',
+ 'files.sourceBooking': '예약',
+ 'files.sourceTransport': '교통',
+ 'files.attach': '첨부',
+ 'files.pasteHint': '클립보드에서 이미지를 붙여넣을 수도 있습니다 (Ctrl+V)',
+ 'files.trash': '휴지통',
+ 'files.trashEmpty': '휴지통이 비어 있습니다',
+ 'files.emptyTrash': '휴지통 비우기',
+ 'files.restore': '복원',
+ 'files.star': '즐겨찾기',
+ 'files.unstar': '즐겨찾기 해제',
+ 'files.assign': '배정',
+ 'files.assignTitle': '파일 배정',
+ 'files.assignPlace': '장소',
+ 'files.assignBooking': '예약',
+ 'files.assignTransport': '교통',
+ 'files.unassigned': '미배정',
+ 'files.unlink': '연결 해제',
+ 'files.toast.trashed': '휴지통으로 이동됨',
+ 'files.toast.restored': '파일이 복원되었습니다',
+ 'files.toast.trashEmptied': '휴지통이 비워졌습니다',
+ 'files.toast.assigned': '파일이 배정되었습니다',
+ 'files.toast.assignError': '배정 실패',
+ 'files.toast.restoreError': '복원 실패',
+ 'files.confirm.permanentDelete':
+ '이 파일을 영구 삭제할까요? 이 작업은 취소할 수 없습니다.',
+ 'files.confirm.emptyTrash':
+ '휴지통의 모든 파일을 영구 삭제할까요? 이 작업은 취소할 수 없습니다.',
+ 'files.noteLabel': '메모',
+ 'files.notePlaceholder': '메모 추가...',
+};
+export default files;
diff --git a/shared/src/i18n/ko/index.ts b/shared/src/i18n/ko/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..77aeb6a09fb62e2522e8b4c04e4169a94c2f8d99
--- /dev/null
+++ b/shared/src/i18n/ko/index.ts
@@ -0,0 +1,86 @@
+import admin from './admin';
+import airport from './airport';
+import atlas from './atlas';
+import backup from './backup';
+import budget from './budget';
+import categories from './categories';
+import collab from './collab';
+import common from './common';
+import dashboard from './dashboard';
+import day from './day';
+import dayplan from './dayplan';
+import files from './files';
+import inspector from './inspector';
+import journey from './journey';
+import login from './login';
+import map from './map';
+import members from './members';
+import memories from './memories';
+import nav from './nav';
+import notif from './notif';
+import notifications from './notifications';
+import oauth from './oauth';
+import packing from './packing';
+import pdf from './pdf';
+import perm from './perm';
+import photos from './photos';
+import places from './places';
+import planner from './planner';
+import register from './register';
+import reservations from './reservations';
+import settings from './settings';
+import share from './share';
+import shared from './shared';
+import stats from './stats';
+import system_notice from './system_notice';
+import todo from './todo';
+import transport from './transport';
+import trip from './trip';
+import trips from './trips';
+import undo from './undo';
+import vacay from './vacay';
+
+const locale = {
+ ...common,
+ ...trips,
+ ...nav,
+ ...dashboard,
+ ...settings,
+ ...admin,
+ ...dayplan,
+ ...share,
+ ...shared,
+ ...login,
+ ...register,
+ ...vacay,
+ ...atlas,
+ ...trip,
+ ...places,
+ ...inspector,
+ ...reservations,
+ ...airport,
+ ...map,
+ ...budget,
+ ...files,
+ ...packing,
+ ...members,
+ ...categories,
+ ...backup,
+ ...photos,
+ ...pdf,
+ ...planner,
+ ...stats,
+ ...day,
+ ...memories,
+ ...collab,
+ ...perm,
+ ...undo,
+ ...notifications,
+ ...todo,
+ ...notif,
+ ...journey,
+ ...oauth,
+ ...system_notice,
+ ...transport,
+};
+export default locale;
diff --git a/shared/src/i18n/ko/inspector.ts b/shared/src/i18n/ko/inspector.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ba12761777f2f7225222703b94665096482ac13d
--- /dev/null
+++ b/shared/src/i18n/ko/inspector.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const inspector: TranslationStrings = {
+ 'inspector.opened': '영업 중',
+ 'inspector.closed': '영업 종료',
+ 'inspector.openingHours': '영업 시간',
+ 'inspector.showHours': '영업 시간 보기',
+ 'inspector.files': '파일',
+ 'inspector.filesCount': '{count}개 파일',
+ 'inspector.remove': '제거',
+ 'inspector.removeFromDay': '날에서 제거',
+ 'inspector.addToDay': '날에 추가',
+ 'inspector.confirmedRes': '확정된 예약',
+ 'inspector.pendingRes': '대기 중인 예약',
+ 'inspector.google': 'Google Maps에서 열기',
+ 'inspector.website': '웹사이트 열기',
+ 'inspector.addRes': '예약',
+ 'inspector.editRes': '예약 편집',
+ 'inspector.participants': '참가자',
+ 'inspector.trackStats': '트랙 통계',
+};
+export default inspector;
diff --git a/shared/src/i18n/ko/journey.ts b/shared/src/i18n/ko/journey.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b17d7f9734f148b3c3db371217c64f59282273b6
--- /dev/null
+++ b/shared/src/i18n/ko/journey.ts
@@ -0,0 +1,244 @@
+import type { TranslationStrings } from '../types';
+
+const journey: TranslationStrings = {
+ 'journey.search.placeholder': 'Journey 검색…',
+ 'journey.search.noResults': '"{query}"와(과) 일치하는 Journey가 없습니다',
+ 'journey.title': 'Journey',
+ 'journey.subtitle': '여행을 실시간으로 기록하세요',
+ 'journey.new': '새 Journey',
+ 'journey.create': '만들기',
+ 'journey.titlePlaceholder': '어디로 가시나요?',
+ 'journey.empty': '아직 Journey가 없습니다',
+ 'journey.emptyHint': '다음 여행을 기록하기 시작하세요',
+ 'journey.deleted': 'Journey가 삭제되었습니다',
+ 'journey.createError': 'Journey를 만들 수 없습니다',
+ 'journey.deleteError': 'Journey를 삭제할 수 없습니다',
+ 'journey.deleteConfirmTitle': '삭제',
+ 'journey.deleteConfirmMessage':
+ '"{title}"을(를) 삭제할까요? 이 작업은 취소할 수 없습니다.',
+ 'journey.deleteConfirmGeneric': '정말로 삭제할까요?',
+ 'journey.notFound': 'Journey를 찾을 수 없습니다',
+ 'journey.photos': '사진',
+ 'journey.timelineEmpty': '아직 정류장이 없습니다',
+ 'journey.timelineEmptyHint': '체크인을 추가하거나 일기를 작성하여 시작하세요',
+ 'journey.status.draft': '초안',
+ 'journey.status.active': '활성',
+ 'journey.status.completed': '완료됨',
+ 'journey.status.upcoming': '예정됨',
+ 'journey.status.archived': '보관됨',
+ 'journey.checkin.add': '체크인',
+ 'journey.checkin.namePlaceholder': '위치 이름',
+ 'journey.checkin.notesPlaceholder': '메모 (선택)',
+ 'journey.checkin.save': '저장',
+ 'journey.checkin.error': '체크인을 저장할 수 없습니다',
+ 'journey.entry.add': '일기',
+ 'journey.entry.edit': '항목 편집',
+ 'journey.entry.titlePlaceholder': '제목 (선택)',
+ 'journey.entry.bodyPlaceholder': '오늘 무슨 일이 있었나요?',
+ 'journey.entry.save': '저장',
+ 'journey.entry.error': '항목을 저장할 수 없습니다',
+ 'journey.photo.add': '사진',
+ 'journey.photo.uploadError': '업로드 실패',
+ 'journey.share.share': '공유',
+ 'journey.share.public': '공개',
+ 'journey.share.linkCopied': '공개 링크가 복사되었습니다',
+ 'journey.share.disabled': '공개 공유 비활성화됨',
+ 'journey.editor.titlePlaceholder': '이 순간에 이름을 붙여주세요...',
+ 'journey.editor.bodyPlaceholder': '오늘의 이야기를 들려주세요...',
+ 'journey.editor.placePlaceholder': '위치 (선택)',
+ 'journey.editor.tagsPlaceholder':
+ '태그: 숨겨진 명소, 최고의 식사, 다시 방문...',
+ 'journey.visibility.private': '비공개',
+ 'journey.visibility.shared': '공유됨',
+ 'journey.visibility.public': '공개',
+ 'journey.emptyState.title': '여기서 이야기가 시작됩니다',
+ 'journey.emptyState.subtitle':
+ '장소에 체크인하거나 첫 번째 일기 항목을 작성하세요',
+ 'journey.frontpage.subtitle': '여행을 영원히 잊지 못할 이야기로 만드세요',
+ 'journey.frontpage.createJourney': 'Journey 만들기',
+ 'journey.frontpage.activeJourney': '활성 Journey',
+ 'journey.frontpage.allJourneys': '모든 Journey',
+ 'journey.frontpage.journeys': '개 Journey',
+ 'journey.frontpage.createNew': '새 Journey 만들기',
+ 'journey.frontpage.createNewSub':
+ '여행을 선택하고, 이야기를 쓰고, 모험을 공유하세요',
+ 'journey.frontpage.live': '라이브',
+ 'journey.frontpage.synced': '동기화됨',
+ 'journey.frontpage.continueWriting': '계속 쓰기',
+ 'journey.frontpage.updated': '{time}에 업데이트됨',
+ 'journey.frontpage.suggestionLabel': '여행이 방금 종료됨',
+ 'journey.frontpage.suggestionText':
+ '{title}을(를) Journey로 만들어보세요',
+ 'journey.frontpage.dismiss': '닫기',
+ 'journey.frontpage.journeyName': 'Journey 이름',
+ 'journey.frontpage.namePlaceholder': '예: 동남아시아 2026',
+ 'journey.frontpage.selectTrips': '여행 선택',
+ 'journey.frontpage.tripsSelected': '개 여행 선택됨',
+ 'journey.frontpage.trips': '개 여행',
+ 'journey.frontpage.placesImported': '개 장소가 가져와집니다',
+ 'journey.frontpage.places': '개 장소',
+ 'journey.detail.backToJourney': 'Journey로 돌아가기',
+ 'journey.detail.syncedWithTrips': '여행과 동기화됨',
+ 'journey.detail.addEntry': '항목 추가',
+ 'journey.detail.newEntry': '새 항목',
+ 'journey.detail.editEntry': '항목 편집',
+ 'journey.detail.noEntries': '아직 항목이 없습니다',
+ 'journey.detail.noEntriesHint':
+ '여행을 추가하여 스켈레톤 항목으로 시작하세요',
+ 'journey.detail.noPhotos': '아직 사진이 없습니다',
+ 'journey.detail.noPhotosHint':
+ '항목에 사진을 업로드하거나 Immich/Synology 라이브러리를 탐색하세요',
+ 'journey.detail.journeyTab': 'Journey',
+ 'journey.detail.journeyStats': 'Journey 통계',
+ 'journey.detail.syncedTrips': '동기화된 여행',
+ 'journey.detail.noTripsLinked': '아직 연결된 여행이 없습니다',
+ 'journey.detail.contributors': '기여자',
+ 'journey.detail.readMore': '더 읽기',
+ 'journey.detail.prosCons': '장단점',
+ 'journey.detail.photos': '장',
+ 'journey.detail.day': '{number}일차',
+ 'journey.detail.places': '개 장소',
+ 'journey.stats.days': '일',
+ 'journey.stats.cities': '도시',
+ 'journey.stats.entries': '항목',
+ 'journey.stats.photos': '사진',
+ 'journey.stats.places': '장소',
+ 'journey.skeletons.show': '제안 보기',
+ 'journey.skeletons.hide': '제안 숨기기',
+ 'journey.verdict.lovedIt': '정말 좋았어요',
+ 'journey.verdict.couldBeBetter': '더 좋을 수 있었어요',
+ 'journey.synced.places': '개 장소',
+ 'journey.synced.synced': '동기화됨',
+ 'journey.editor.discardChangesConfirm':
+ '저장되지 않은 변경 사항이 있습니다. 취소할까요?',
+ 'journey.editor.uploadPhotos': '사진 업로드',
+ 'journey.editor.uploading': '업로드 중...',
+ 'journey.editor.fromGallery': '갤러리에서',
+ 'journey.editor.allPhotosAdded': '모든 사진이 이미 추가되었습니다',
+ 'journey.editor.writeStory': '이야기를 써주세요...',
+ 'journey.editor.prosCons': '장단점',
+ 'journey.editor.pros': '장점',
+ 'journey.editor.cons': '단점',
+ 'journey.editor.proPlaceholder': '좋은 점...',
+ 'journey.editor.conPlaceholder': '아쉬운 점...',
+ 'journey.editor.addAnother': '하나 더 추가',
+ 'journey.editor.date': '날짜',
+ 'journey.editor.location': '위치',
+ 'journey.editor.searchLocation': '위치 검색...',
+ 'journey.editor.mood': '기분',
+ 'journey.editor.weather': '날씨',
+ 'journey.editor.photoFirst': '1번째',
+ 'journey.editor.makeFirst': '1번째로 설정',
+ 'journey.editor.searching': '검색 중...',
+ 'journey.mood.amazing': '최고!',
+ 'journey.mood.good': '좋음',
+ 'journey.mood.neutral': '보통',
+ 'journey.mood.rough': '힘들었음',
+ 'journey.weather.sunny': '맑음',
+ 'journey.weather.partly': '구름 조금',
+ 'journey.weather.cloudy': '흐림',
+ 'journey.weather.rainy': '비',
+ 'journey.weather.stormy': '폭풍',
+ 'journey.weather.cold': '눈',
+ 'journey.trips.linkTrip': '여행 연결',
+ 'journey.trips.searchTrip': '여행 검색',
+ 'journey.trips.searchPlaceholder': '여행 이름 또는 목적지...',
+ 'journey.trips.noTripsAvailable': '사용 가능한 여행이 없습니다',
+ 'journey.trips.link': '연결',
+ 'journey.trips.tripLinked': '여행이 연결되었습니다',
+ 'journey.trips.linkFailed': '여행 연결 실패',
+ 'journey.trips.addTrip': '여행 추가',
+ 'journey.trips.unlinkTrip': '여행 연결 해제',
+ 'journey.trips.unlinkMessage':
+ '"{title}"을(를) 연결 해제할까요? 이 여행의 동기화된 모든 항목과 사진이 영구 삭제됩니다. 이 작업은 취소할 수 없습니다.',
+ 'journey.trips.unlink': '연결 해제',
+ 'journey.trips.tripUnlinked': '여행 연결이 해제되었습니다',
+ 'journey.trips.unlinkFailed': '여행 연결 해제 실패',
+ 'journey.trips.noTripsLinkedSettings': '연결된 여행이 없습니다',
+ 'journey.contributors.invite': '기여자 초대',
+ 'journey.contributors.searchUser': '사용자 검색',
+ 'journey.contributors.searchPlaceholder': '사용자 이름 또는 이메일...',
+ 'journey.contributors.noUsers': '사용자를 찾을 수 없습니다',
+ 'journey.contributors.role': '역할',
+ 'journey.contributors.added': '기여자가 추가되었습니다',
+ 'journey.contributors.addFailed': '기여자 추가 실패',
+ 'journey.contributors.remove': '기여자 제거',
+ 'journey.contributors.removeConfirm':
+ '{username}을(를) 이 Journey에서 제거할까요?',
+ 'journey.contributors.removed': '기여자가 제거되었습니다',
+ 'journey.contributors.removeFailed': '기여자 제거 실패',
+ 'journey.share.publicShare': '공개 공유',
+ 'journey.share.createLink': '공유 링크 만들기',
+ 'journey.share.linkCreated': '공유 링크가 생성되었습니다',
+ 'journey.share.createFailed': '링크 생성 실패',
+ 'journey.share.copy': '복사',
+ 'journey.share.copied': '복사됨!',
+ 'journey.share.timeline': '타임라인',
+ 'journey.share.gallery': '갤러리',
+ 'journey.share.map': '지도',
+ 'journey.share.removeLink': '공유 링크 제거',
+ 'journey.share.linkDeleted': '공유 링크가 삭제되었습니다',
+ 'journey.share.deleteFailed': '삭제 실패',
+ 'journey.share.updateFailed': '업데이트 실패',
+ 'journey.invite.role': '역할',
+ 'journey.invite.viewer': '뷰어',
+ 'journey.invite.editor': '편집자',
+ 'journey.invite.invite': '초대',
+ 'journey.invite.inviting': '초대 중...',
+ 'journey.settings.title': 'Journey 설정',
+ 'journey.settings.coverImage': '커버 이미지',
+ 'journey.settings.changeCover': '커버 변경',
+ 'journey.settings.addCover': '커버 이미지 추가',
+ 'journey.settings.name': '이름',
+ 'journey.settings.subtitle': '부제목',
+ 'journey.settings.subtitlePlaceholder': '예: 태국, 베트남 & 캄보디아',
+ 'journey.settings.endJourney': 'Journey 보관',
+ 'journey.settings.reopenJourney': 'Journey 복원',
+ 'journey.settings.archived': 'Journey가 보관되었습니다',
+ 'journey.settings.reopened': 'Journey가 복원되었습니다',
+ 'journey.settings.endDescription':
+ '라이브 배지를 숨깁니다. 언제든지 다시 열 수 있습니다.',
+ 'journey.settings.delete': '삭제',
+ 'journey.settings.deleteJourney': 'Journey 삭제',
+ 'journey.settings.deleteMessage':
+ '"{title}"을(를) 삭제할까요? 모든 항목과 사진이 삭제됩니다.',
+ 'journey.settings.saved': '설정이 저장되었습니다',
+ 'journey.settings.saveFailed': '저장 실패',
+ 'journey.settings.coverUpdated': '커버가 업데이트되었습니다',
+ 'journey.settings.coverFailed': '업로드 실패',
+ 'journey.settings.failedToDelete': '삭제 실패',
+ 'journey.entries.deleteTitle': '항목 삭제',
+ 'journey.photosUploaded': '{count}장 사진이 업로드되었습니다',
+ 'journey.photosAdded': '{count}장 사진이 추가되었습니다',
+ 'journey.public.notFound': '찾을 수 없습니다',
+ 'journey.public.notFoundMessage':
+ '이 Journey가 존재하지 않거나 링크가 만료되었습니다.',
+ 'journey.public.readOnly': '읽기 전용 · 공개 Journey',
+ 'journey.public.tagline': '여행 기록 및 탐험 키트',
+ 'journey.public.sharedVia': '공유 경로',
+ 'journey.public.madeWith': '으로 만들어짐',
+ 'journey.pdf.journeyBook': 'Journey 책',
+ 'journey.pdf.madeWith': 'TREK으로 만들어짐',
+ 'journey.pdf.day': '일차',
+ 'journey.pdf.theEnd': '끝',
+ 'journey.pdf.saveAsPdf': 'PDF로 저장',
+ 'journey.pdf.pages': '페이지',
+ 'journey.picker.tripPeriod': '여행 기간',
+ 'journey.picker.dateRange': '날짜 범위',
+ 'journey.picker.allPhotos': '모든 사진',
+ 'journey.picker.albums': '앨범',
+ 'journey.picker.selected': '선택됨',
+ 'journey.picker.addTo': '추가',
+ 'journey.picker.newGallery': '새 갤러리',
+ 'journey.picker.selectAll': '전체 선택',
+ 'journey.picker.deselectAll': '전체 해제',
+ 'journey.picker.noAlbums': '앨범을 찾을 수 없습니다',
+ 'journey.picker.selectDate': '날짜 선택',
+ 'journey.picker.search': '검색',
+ 'journey.editor.uploadingProgress': '업로드 중 {done}/{total}…',
+ 'journey.editor.uploadFailed': '사진 업로드 실패',
+ 'journey.editor.uploadPartialFailed':
+ '{total}개 중 {failed}개의 사진을 업로드하지 못했습니다 — 다시 저장하여 재시도하세요',
+ 'journey.photosUploadFailed': '일부 사진을 업로드하지 못했습니다',
+};
+export default journey;
diff --git a/shared/src/i18n/ko/login.ts b/shared/src/i18n/ko/login.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5cb0dfe34a02b0c3b34f71193c89d12c630e0393
--- /dev/null
+++ b/shared/src/i18n/ko/login.ts
@@ -0,0 +1,96 @@
+import type { TranslationStrings } from '../types';
+
+const login: TranslationStrings = {
+ 'login.error': '로그인 실패. 자격 증명을 확인하세요.',
+ 'login.tagline': '나의 여행.\n나의 계획.',
+ 'login.description':
+ '인터랙티브 지도, 예산, 실시간 동기화로 함께 여행을 계획하세요.',
+ 'login.features.maps': '인터랙티브 지도',
+ 'login.features.mapsDesc': 'Google Places, 경로 및 클러스터링',
+ 'login.features.realtime': '실시간 동기화',
+ 'login.features.realtimeDesc': 'WebSocket으로 함께 계획',
+ 'login.features.budget': '예산 추적',
+ 'login.features.budgetDesc': '카테고리, 차트 및 1인당 비용',
+ 'login.features.collab': '협업',
+ 'login.features.collabDesc': '공유 여행으로 다중 사용자 지원',
+ 'login.features.packing': '짐 목록',
+ 'login.features.packingDesc': '카테고리, 진행 상황 및 제안',
+ 'login.features.bookings': '예약',
+ 'login.features.bookingsDesc': '항공, 호텔, 레스토랑 등',
+ 'login.features.files': '문서',
+ 'login.features.filesDesc': '문서 업로드 및 관리',
+ 'login.features.routes': '스마트 경로',
+ 'login.features.routesDesc': '자동 최적화 및 Google Maps 내보내기',
+ 'login.selfHosted': '자체 호스팅 · 오픈 소스 · 내 데이터는 내 것',
+ 'login.title': '로그인',
+ 'login.subtitle': '다시 오신 것을 환영합니다',
+ 'login.signingIn': '로그인 중…',
+ 'login.signIn': '로그인',
+ 'login.createAdmin': '관리자 계정 만들기',
+ 'login.createAdminHint': 'TREK의 첫 번째 관리자 계정을 설정하세요.',
+ 'login.setNewPassword': '새 비밀번호 설정',
+ 'login.setNewPasswordHint': '계속하기 전에 비밀번호를 변경해야 합니다.',
+ 'login.createAccount': '계정 만들기',
+ 'login.createAccountHint': '새 계정을 등록하세요.',
+ 'login.creating': '생성 중…',
+ 'login.noAccount': '계정이 없으신가요?',
+ 'login.hasAccount': '이미 계정이 있으신가요?',
+ 'login.register': '회원가입',
+ 'login.emailPlaceholder': 'your@email.com',
+ 'login.username': '사용자 이름',
+ 'login.oidc.registrationDisabled':
+ '회원가입이 비활성화되어 있습니다. 관리자에게 문의하세요.',
+ 'login.oidc.noEmail': '공급자로부터 이메일을 받지 못했습니다.',
+ 'login.oidc.tokenFailed': '인증 실패.',
+ 'login.oidc.invalidState': '유효하지 않은 세션입니다. 다시 시도하세요.',
+ 'login.demoFailed': '데모 로그인 실패',
+ 'login.oidcSignIn': '{name}으로 로그인',
+ 'login.oidcOnly':
+ '비밀번호 인증이 비활성화되었습니다. SSO 공급자로 로그인하세요.',
+ 'login.oidcLoggedOut': '로그아웃되었습니다. SSO 공급자로 다시 로그인하세요.',
+ 'login.demoHint': '데모 체험 — 회원가입 불필요',
+ 'login.mfaTitle': '2단계 인증',
+ 'login.mfaSubtitle': '인증 앱의 6자리 코드를 입력하세요.',
+ 'login.mfaCodeLabel': '인증 코드',
+ 'login.mfaCodeRequired': '인증 앱의 코드를 입력하세요.',
+ 'login.mfaHint':
+ 'Google Authenticator, Authy 또는 다른 TOTP 앱을 열어주세요.',
+ 'login.mfaBack': '← 로그인으로 돌아가기',
+ 'login.mfaVerify': '인증',
+ 'login.invalidInviteLink': '유효하지 않거나 만료된 초대 링크입니다',
+ 'login.oidcFailed': 'OIDC 로그인 실패',
+ 'login.usernameRequired': '사용자 이름을 입력하세요',
+ 'login.passwordMinLength': '비밀번호는 최소 8자 이상이어야 합니다',
+ 'login.forgotPassword': '비밀번호를 잊으셨나요?',
+ 'login.rememberMe': '로그인 상태 유지',
+ 'login.forgotPasswordTitle': '비밀번호 재설정',
+ 'login.forgotPasswordBody':
+ '가입 시 사용한 이메일 주소를 입력하세요. 계정이 존재하면 재설정 링크를 보내드립니다.',
+ 'login.forgotPasswordSubmit': '재설정 링크 전송',
+ 'login.forgotPasswordSentTitle': '이메일을 확인하세요',
+ 'login.forgotPasswordSentBody':
+ '해당 이메일로 계정이 있다면 재설정 링크가 전송 중입니다. 링크는 60분 후 만료됩니다.',
+ 'login.forgotPasswordSmtpHintOff':
+ '알림: 관리자가 SMTP를 설정하지 않아 재설정 링크가 이메일 대신 서버 콘솔에 기록됩니다.',
+ 'login.backToLogin': '로그인으로 돌아가기',
+ 'login.newPassword': '새 비밀번호',
+ 'login.confirmPassword': '새 비밀번호 확인',
+ 'login.passwordsDontMatch': '비밀번호가 일치하지 않습니다',
+ 'login.mfaCode': '2FA 코드',
+ 'login.resetPasswordTitle': '새 비밀번호 설정',
+ 'login.resetPasswordBody':
+ '이전에 사용하지 않은 강력한 비밀번호를 선택하세요. 최소 8자.',
+ 'login.resetPasswordMfaBody':
+ '재설정을 완료하려면 2FA 코드 또는 백업 코드를 입력하세요.',
+ 'login.resetPasswordSubmit': '비밀번호 재설정',
+ 'login.resetPasswordVerify': '인증 및 재설정',
+ 'login.resetPasswordSuccessTitle': '비밀번호가 업데이트되었습니다',
+ 'login.resetPasswordSuccessBody': '새 비밀번호로 로그인할 수 있습니다.',
+ 'login.resetPasswordInvalidLink': '유효하지 않은 재설정 링크',
+ 'login.resetPasswordInvalidLinkBody':
+ '이 링크가 없거나 손상되었습니다. 새 링크를 요청하세요.',
+ 'login.resetPasswordFailed': '재설정 실패. 링크가 만료되었을 수 있습니다.',
+ 'login.passkey.signIn': '패스키로 로그인',
+ 'login.passkey.failed': '패스키 로그인에 실패했습니다. 다시 시도하세요.',
+};
+export default login;
diff --git a/shared/src/i18n/ko/map.ts b/shared/src/i18n/ko/map.ts
new file mode 100644
index 0000000000000000000000000000000000000000..839f49045c80c44be265e75cc6da97b9db89cd61
--- /dev/null
+++ b/shared/src/i18n/ko/map.ts
@@ -0,0 +1,17 @@
+import type { TranslationStrings } from '../types';
+
+const map: TranslationStrings = {
+ 'map.connections': '연결',
+ 'map.showConnections': '예약 경로 표시',
+ 'map.hideConnections': '예약 경로 숨기기',
+ 'poi.searchThisArea': '이 지역 검색',
+ 'poi.cat.restaurants': '음식점',
+ 'poi.cat.cafes': '카페',
+ 'poi.cat.bars': '바 & 나이트라이프',
+ 'poi.cat.hotels': '숙소',
+ 'poi.cat.sights': '명소',
+ 'poi.cat.museums': '박물관 & 문화',
+ 'poi.cat.nature': '자연 & 공원',
+ 'poi.cat.activities': '액티비티',
+};
+export default map;
diff --git a/shared/src/i18n/ko/members.ts b/shared/src/i18n/ko/members.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5824354177f42c53d859e7f89f2971756cca3316
--- /dev/null
+++ b/shared/src/i18n/ko/members.ts
@@ -0,0 +1,24 @@
+import type { TranslationStrings } from '../types';
+
+const members: TranslationStrings = {
+ 'members.shareTrip': '여행 공유',
+ 'members.inviteUser': '사용자 초대',
+ 'members.selectUser': '사용자 선택…',
+ 'members.invite': '초대',
+ 'members.allHaveAccess': '모든 사용자가 이미 접근 권한을 가지고 있습니다.',
+ 'members.access': '접근',
+ 'members.person': '명',
+ 'members.persons': '명',
+ 'members.you': '나',
+ 'members.owner': '소유자',
+ 'members.leaveTrip': '여행 떠나기',
+ 'members.removeAccess': '접근 권한 제거',
+ 'members.confirmLeave': '여행을 떠날까요? 접근 권한을 잃게 됩니다.',
+ 'members.confirmRemove': '이 사용자의 접근 권한을 제거할까요?',
+ 'members.loadError': '멤버 불러오기 실패',
+ 'members.added': '추가됨',
+ 'members.addError': '추가 실패',
+ 'members.removed': '멤버가 제거되었습니다',
+ 'members.removeError': '제거 실패',
+};
+export default members;
diff --git a/shared/src/i18n/ko/memories.ts b/shared/src/i18n/ko/memories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..874b5dff265ed1d06764e1cd69e4375f3ae847a2
--- /dev/null
+++ b/shared/src/i18n/ko/memories.ts
@@ -0,0 +1,80 @@
+import type { TranslationStrings } from '../types';
+
+const memories: TranslationStrings = {
+ 'memories.title': '사진',
+ 'memories.notConnected': '{provider_name}이(가) 연결되지 않았습니다',
+ 'memories.notConnectedHint':
+ '이 여행에 사진을 추가하려면 설정에서 {provider_name} 인스턴스를 연결하세요.',
+ 'memories.notConnectedMultipleHint':
+ '이 여행에 사진을 추가하려면 설정에서 다음 사진 공급자 중 하나를 연결하세요: {provider_names}',
+ 'memories.noDates': '사진을 불러오려면 여행에 날짜를 추가하세요.',
+ 'memories.noPhotos': '사진을 찾을 수 없습니다',
+ 'memories.noPhotosHint':
+ '{provider_name}에서 이 여행의 날짜 범위에 해당하는 사진을 찾을 수 없습니다.',
+ 'memories.photosFound': '장',
+ 'memories.fromOthers': '다른 사람으로부터',
+ 'memories.sharePhotos': '사진 공유',
+ 'memories.sharing': '공유',
+ 'memories.reviewTitle': '사진 검토',
+ 'memories.reviewHint': '사진을 클릭하여 공유에서 제외하세요.',
+ 'memories.shareCount': '사진 {count}장 공유',
+ 'memories.providerUrl': '서버 URL',
+ 'memories.providerApiKey': 'API 키',
+ 'memories.providerUsername': '사용자 이름',
+ 'memories.providerPassword': '비밀번호',
+ 'memories.providerOTP': 'MFA 코드 (활성화된 경우)',
+ 'memories.skipSSLVerification': 'SSL 인증서 확인 건너뛰기',
+ 'memories.immichAutoUpload': '업로드 시 Journey 사진을 Immich에 미러링',
+ 'memories.providerUrlHintSynology':
+ 'URL에 Photos 앱 경로를 포함하세요. 예: https://nas:5001/photo',
+ 'memories.testConnection': '연결 테스트',
+ 'memories.testShort': '테스트',
+ 'memories.testFirst': '먼저 연결을 테스트하세요',
+ 'memories.connected': '연결됨',
+ 'memories.disconnected': '연결되지 않음',
+ 'memories.connectionSuccess': '{provider_name}에 연결되었습니다',
+ 'memories.connectionError': '{provider_name}에 연결할 수 없습니다',
+ 'memories.saved': '{provider_name} 설정이 저장되었습니다',
+ 'memories.providerDisconnectedBanner':
+ '{provider_name} 연결이 끊어졌습니다. 사진을 보려면 설정에서 다시 연결하세요.',
+ 'memories.saveError': '{provider_name} 설정을 저장할 수 없습니다',
+ 'memories.addPhotos': '사진 추가',
+ 'memories.linkAlbum': '앨범 연결',
+ 'memories.selectAlbum': '{provider_name} 앨범 선택',
+ 'memories.selectAlbumMultiple': '앨범 선택',
+ 'memories.noAlbums': '앨범을 찾을 수 없습니다',
+ 'memories.syncAlbum': '앨범 동기화',
+ 'memories.unlinkAlbum': '앨범 연결 해제',
+ 'memories.photos': '장',
+ 'memories.selectPhotos': '{provider_name}에서 사진 선택',
+ 'memories.selectPhotosMultiple': '사진 선택',
+ 'memories.selectHint': '사진을 탭하여 선택하세요.',
+ 'memories.selected': '선택됨',
+ 'memories.addSelected': '{count}장 추가',
+ 'memories.alreadyAdded': '추가됨',
+ 'memories.private': '비공개',
+ 'memories.stopSharing': '공유 중지',
+ 'memories.oldest': '오래된 것 먼저',
+ 'memories.newest': '최신 것 먼저',
+ 'memories.allLocations': '모든 위치',
+ 'memories.tripDates': '여행 날짜',
+ 'memories.allPhotos': '모든 사진',
+ 'memories.confirmShareTitle': '여행 멤버와 공유할까요?',
+ 'memories.confirmShareHint':
+ '{count}장의 사진이 이 여행의 모든 멤버에게 표시됩니다. 나중에 개별 사진을 비공개로 만들 수 있습니다.',
+ 'memories.confirmShareButton': '사진 공유',
+ 'memories.error.loadAlbums': '앨범 불러오기 실패',
+ 'memories.error.linkAlbum': '앨범 연결 실패',
+ 'memories.error.unlinkAlbum': '앨범 연결 해제 실패',
+ 'memories.error.syncAlbum': '앨범 동기화 실패',
+ 'memories.error.loadPhotos': '사진 불러오기 실패',
+ 'memories.error.addPhotos': '사진 추가 실패',
+ 'memories.error.removePhoto': '사진 제거 실패',
+ 'memories.error.toggleSharing': '공유 업데이트 실패',
+ 'memories.saveRouteNotConfigured':
+ '이 공급자에 대해 저장 경로가 설정되지 않았습니다',
+ 'memories.testRouteNotConfigured':
+ '이 공급자에 대해 테스트 경로가 설정되지 않았습니다',
+ 'memories.fillRequiredFields': '모든 필수 항목을 입력하세요',
+};
+export default memories;
diff --git a/shared/src/i18n/ko/nav.ts b/shared/src/i18n/ko/nav.ts
new file mode 100644
index 0000000000000000000000000000000000000000..44cb2b11039505ed1bb21ab4ae42858e9898ac99
--- /dev/null
+++ b/shared/src/i18n/ko/nav.ts
@@ -0,0 +1,20 @@
+import type { TranslationStrings } from '../types';
+
+const nav: TranslationStrings = {
+ 'nav.trip': '여행',
+ 'nav.share': '공유',
+ 'nav.settings': '설정',
+ 'nav.admin': '관리자',
+ 'nav.logout': '로그아웃',
+ 'nav.lightMode': '라이트 모드',
+ 'nav.darkMode': '다크 모드',
+ 'nav.autoMode': '자동 모드',
+ 'nav.administrator': '관리자',
+ 'nav.myTrips': '내 여행',
+ 'nav.profile': '프로필',
+ 'nav.bottomSettings': '설정',
+ 'nav.bottomAdmin': '관리자 설정',
+ 'nav.bottomLogout': '로그아웃',
+ 'nav.bottomAdminBadge': '관리자',
+};
+export default nav;
diff --git a/shared/src/i18n/ko/notif.ts b/shared/src/i18n/ko/notif.ts
new file mode 100644
index 0000000000000000000000000000000000000000..984dc8b4248571a66c2d5e8e60f7128640cf3674
--- /dev/null
+++ b/shared/src/i18n/ko/notif.ts
@@ -0,0 +1,43 @@
+import type { TranslationStrings } from '../types';
+
+const notif: TranslationStrings = {
+ 'notif.test.title': '[테스트] 알림',
+ 'notif.test.simple.text': '간단한 테스트 알림입니다.',
+ 'notif.test.boolean.text': '이 테스트 알림을 수락하시겠습니까?',
+ 'notif.test.navigate.text': '아래를 클릭하여 대시보드로 이동하세요.',
+ 'notif.trip_invite.title': '여행 초대',
+ 'notif.trip_invite.text': '{actor}이(가) {trip}에 초대했습니다',
+ 'notif.booking_change.title': '예약 업데이트됨',
+ 'notif.booking_change.text': '{actor}이(가) {trip}의 예약을 업데이트했습니다',
+ 'notif.trip_reminder.title': '여행 리마인더',
+ 'notif.trip_reminder.text': '여행 {trip}이(가) 곧 시작됩니다!',
+ 'notif.todo_due.title': '할 일 마감',
+ 'notif.todo_due.text': '{trip}의 {todo}이(가) {due}에 마감됩니다',
+ 'notif.vacay_invite.title': 'Vacay 퓨전 초대',
+ 'notif.vacay_invite.text': '{actor}이(가) 휴가 계획 공유에 초대했습니다',
+ 'notif.photos_shared.title': '사진 공유됨',
+ 'notif.photos_shared.text':
+ '{actor}이(가) {trip}에서 {count}장의 사진을 공유했습니다',
+ 'notif.collab_message.title': '새 메시지',
+ 'notif.collab_message.text': '{actor}이(가) {trip}에서 메시지를 보냈습니다',
+ 'notif.packing_tagged.title': '짐 목록 배정',
+ 'notif.packing_tagged.text':
+ '{actor}이(가) {trip}의 {category}에 배정했습니다',
+ 'notif.version_available.title': '새 버전 사용 가능',
+ 'notif.version_available.text': 'TREK {version}이(가) 사용 가능합니다',
+ 'notif.action.view_trip': '여행 보기',
+ 'notif.action.view_collab': '메시지 보기',
+ 'notif.action.view_packing': '짐 목록 보기',
+ 'notif.action.view_photos': '사진 보기',
+ 'notif.action.view_vacay': 'Vacay 보기',
+ 'notif.action.view_admin': '관리자로 이동',
+ 'notif.action.view': '보기',
+ 'notif.action.accept': '수락',
+ 'notif.action.decline': '거절',
+ 'notif.generic.title': '알림',
+ 'notif.generic.text': '새 알림이 있습니다',
+ 'notif.dev.unknown_event.title': '[DEV] 알 수 없는 이벤트',
+ 'notif.dev.unknown_event.text':
+ '이벤트 유형 "{event}"이(가) EVENT_NOTIFICATION_CONFIG에 등록되지 않았습니다',
+};
+export default notif;
diff --git a/shared/src/i18n/ko/notifications.ts b/shared/src/i18n/ko/notifications.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e3d339e57398a3fea1e9715c01a0babf993cb734
--- /dev/null
+++ b/shared/src/i18n/ko/notifications.ts
@@ -0,0 +1,39 @@
+import type { TranslationStrings } from '../types';
+
+const notifications: TranslationStrings = {
+ 'notifications.title': '알림',
+ 'notifications.markAllRead': '모두 읽음으로 표시',
+ 'notifications.deleteAll': '모두 삭제',
+ 'notifications.showAll': '모든 알림 보기',
+ 'notifications.empty': '알림 없음',
+ 'notifications.emptyDescription': '모두 확인했습니다!',
+ 'notifications.all': '전체',
+ 'notifications.unreadOnly': '읽지 않음',
+ 'notifications.markRead': '읽음으로 표시',
+ 'notifications.markUnread': '읽지 않음으로 표시',
+ 'notifications.delete': '삭제',
+ 'notifications.system': '시스템',
+ 'notifications.synologySessionCleared.title': 'Synology Photos 연결 해제됨',
+ 'notifications.synologySessionCleared.text':
+ '서버 또는 계정이 변경되었습니다 — 설정에서 연결을 다시 테스트하세요.',
+ 'notifications.versionAvailable.title': '업데이트 사용 가능',
+ 'notifications.versionAvailable.text':
+ 'TREK {version}이(가) 사용 가능합니다.',
+ 'notifications.versionAvailable.button': '상세 보기',
+ 'notifications.test.title': '{actor}의 테스트 알림',
+ 'notifications.test.text': '간단한 테스트 알림입니다.',
+ 'notifications.test.booleanTitle': '{actor}이(가) 승인을 요청합니다',
+ 'notifications.test.booleanText':
+ '테스트 boolean 알림입니다. 아래에서 작업을 선택하세요.',
+ 'notifications.test.accept': '승인',
+ 'notifications.test.decline': '거절',
+ 'notifications.test.navigateTitle': '확인할 항목이 있습니다',
+ 'notifications.test.navigateText': '테스트 navigate 알림입니다.',
+ 'notifications.test.goThere': '이동',
+ 'notifications.test.adminTitle': '관리자 방송',
+ 'notifications.test.adminText':
+ '{actor}이(가) 모든 관리자에게 테스트 알림을 보냈습니다.',
+ 'notifications.test.tripTitle': '{actor}이(가) 여행에 게시했습니다',
+ 'notifications.test.tripText': '여행 "{trip}"의 테스트 알림입니다.',
+};
+export default notifications;
diff --git a/shared/src/i18n/ko/oauth.ts b/shared/src/i18n/ko/oauth.ts
new file mode 100644
index 0000000000000000000000000000000000000000..94e33a26c1a152f62eb9ccef6a13490e56a9bfbb
--- /dev/null
+++ b/shared/src/i18n/ko/oauth.ts
@@ -0,0 +1,111 @@
+import type { TranslationStrings } from '../types';
+
+const oauth: TranslationStrings = {
+ 'oauth.scope.group.trips': '여행',
+ 'oauth.scope.group.places': '장소',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': '짐 목록',
+ 'oauth.scope.group.todos': '할 일',
+ 'oauth.scope.group.budget': '예산',
+ 'oauth.scope.group.reservations': '예약',
+ 'oauth.scope.group.collab': '협업',
+ 'oauth.scope.group.notifications': '알림',
+ 'oauth.scope.group.vacay': '휴가',
+ 'oauth.scope.group.geo': '지리',
+ 'oauth.scope.group.weather': '날씨',
+ 'oauth.scope.group.journey': 'Journey',
+ 'oauth.scope.trips:read.label': '여행 및 일정 보기',
+ 'oauth.scope.trips:read.description': '여행, 날, 일별 메모, 멤버 읽기',
+ 'oauth.scope.trips:write.label': '여행 및 일정 편집',
+ 'oauth.scope.trips:write.description':
+ '여행, 날, 메모 만들기 및 업데이트, 멤버 관리',
+ 'oauth.scope.trips:delete.label': '여행 삭제',
+ 'oauth.scope.trips:delete.description':
+ '전체 여행 영구 삭제 — 이 작업은 되돌릴 수 없습니다',
+ 'oauth.scope.trips:share.label': '공유 링크 관리',
+ 'oauth.scope.trips:share.description':
+ '여행의 공개 공유 링크 만들기, 업데이트, 취소',
+ 'oauth.scope.places:read.label': '장소 및 지도 데이터 보기',
+ 'oauth.scope.places:read.description': '장소, 날 배정, 태그, 카테고리 읽기',
+ 'oauth.scope.places:write.label': '장소 관리',
+ 'oauth.scope.places:write.description':
+ '장소, 배정, 태그 만들기, 업데이트, 삭제',
+ 'oauth.scope.atlas:read.label': 'Atlas 보기',
+ 'oauth.scope.atlas:read.description': '방문한 나라, 지역, 버킷 리스트 읽기',
+ 'oauth.scope.atlas:write.label': 'Atlas 관리',
+ 'oauth.scope.atlas:write.description':
+ '방문한 나라 및 지역 표시, 버킷 리스트 관리',
+ 'oauth.scope.packing:read.label': '짐 목록 보기',
+ 'oauth.scope.packing:read.description': '짐 항목, 가방, 카테고리 배정 읽기',
+ 'oauth.scope.packing:write.label': '짐 목록 관리',
+ 'oauth.scope.packing:write.description':
+ '짐 항목 및 가방 추가, 업데이트, 삭제, 체크, 순서 변경',
+ 'oauth.scope.todos:read.label': '할 일 목록 보기',
+ 'oauth.scope.todos:read.description': '여행 할 일 항목 및 카테고리 배정 읽기',
+ 'oauth.scope.todos:write.label': '할 일 목록 관리',
+ 'oauth.scope.todos:write.description':
+ '할 일 항목 만들기, 업데이트, 체크, 삭제, 순서 변경',
+ 'oauth.scope.budget:read.label': '예산 보기',
+ 'oauth.scope.budget:read.description': '예산 항목 및 지출 내역 읽기',
+ 'oauth.scope.budget:write.label': '예산 관리',
+ 'oauth.scope.budget:write.description': '예산 항목 만들기, 업데이트, 삭제',
+ 'oauth.scope.reservations:read.label': '예약 보기',
+ 'oauth.scope.reservations:read.description': '예약 및 숙박 상세 정보 읽기',
+ 'oauth.scope.reservations:write.label': '예약 관리',
+ 'oauth.scope.reservations:write.description':
+ '예약 만들기, 업데이트, 삭제, 순서 변경',
+ 'oauth.scope.collab:read.label': '협업 보기',
+ 'oauth.scope.collab:read.description': '협업 메모, 투표, 메시지 읽기',
+ 'oauth.scope.collab:write.label': '협업 관리',
+ 'oauth.scope.collab:write.description':
+ '협업 메모, 투표, 메시지 만들기, 업데이트, 삭제',
+ 'oauth.scope.notifications:read.label': '알림 보기',
+ 'oauth.scope.notifications:read.description':
+ '앱 내 알림 및 읽지 않은 수 읽기',
+ 'oauth.scope.notifications:write.label': '알림 관리',
+ 'oauth.scope.notifications:write.description': '알림 읽음 표시 및 응답',
+ 'oauth.scope.vacay:read.label': '휴가 계획 보기',
+ 'oauth.scope.vacay:read.description': '휴가 계획 데이터, 항목, 통계 읽기',
+ 'oauth.scope.vacay:write.label': '휴가 계획 관리',
+ 'oauth.scope.vacay:write.description':
+ '휴가 항목, 공휴일, 팀 계획 만들기 및 관리',
+ 'oauth.scope.geo:read.label': '지도 및 지오코딩',
+ 'oauth.scope.geo:read.description':
+ '위치 검색, 지도 URL 확인, 좌표 역지오코딩',
+ 'oauth.scope.weather:read.label': '날씨 예보',
+ 'oauth.scope.weather:read.description':
+ '여행 위치 및 날짜의 날씨 예보 가져오기',
+ 'oauth.scope.journey:read.label': 'Journey 보기',
+ 'oauth.scope.journey:read.description': 'Journey, 항목, 기여자 목록 읽기',
+ 'oauth.scope.journey:write.label': 'Journey 관리',
+ 'oauth.scope.journey:write.description':
+ 'Journey 및 항목 만들기, 업데이트, 삭제',
+ 'oauth.scope.journey:share.label': 'Journey 링크 관리',
+ 'oauth.scope.journey:share.description':
+ 'Journey의 공개 공유 링크 만들기, 업데이트, 취소',
+ 'oauth.authorize.authorizing': 'Authorizing…', // en-fallback
+ 'oauth.authorize.loading': 'Loading…', // en-fallback
+ 'oauth.authorize.errorTitle': 'Authorization Error', // en-fallback
+ 'oauth.authorize.loginTitle': 'Sign in to continue', // en-fallback
+ 'oauth.authorize.loginDescription':
+ '{client} wants access to your TREK account. Please sign in first.', // en-fallback
+ 'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
+ 'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
+ 'oauth.authorize.requestDescription':
+ 'This application is requesting access to your TREK account.', // en-fallback
+ 'oauth.authorize.trustNote':
+ 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
+ 'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
+ 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
+ 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
+ 'oauth.authorize.deny': 'Deny', // en-fallback
+ 'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
+ 'oauth.authorize.permissionsRequested': 'Permissions requested', // en-fallback
+ 'oauth.authorize.alwaysIncluded': 'Always included', // en-fallback
+ 'oauth.authorize.alwaysTool.listTrips':
+ 'List your trips so the AI can discover trip IDs', // en-fallback
+ 'oauth.authorize.alwaysTool.getTripSummary':
+ 'Read a trip overview needed to use any other tool', // en-fallback
+};
+export default oauth;
diff --git a/shared/src/i18n/ko/packing.ts b/shared/src/i18n/ko/packing.ts
new file mode 100644
index 0000000000000000000000000000000000000000..042fbfe96dec4786f7bb7359e2703fb5335dcea9
--- /dev/null
+++ b/shared/src/i18n/ko/packing.ts
@@ -0,0 +1,185 @@
+import type { TranslationStrings } from '../types';
+
+const packing: TranslationStrings = {
+ 'packing.title': '짐 목록',
+ 'packing.empty': '짐 목록이 비어 있습니다',
+ 'packing.import': '가져오기',
+ 'packing.importTitle': '짐 목록 가져오기',
+ 'packing.importHint':
+ '한 줄에 하나의 항목. 형식: 카테고리, 이름, 무게(g, 선택), 가방(선택), checked/unchecked(선택)',
+ 'packing.importPlaceholder':
+ '위생, 칫솔\n의류, 티셔츠, 200\n서류, 여권, , 기내 수하물\n전자기기, 충전기, 50, 캐리어, checked',
+ 'packing.importCsv': 'CSV/TXT 불러오기',
+ 'packing.importAction': '{count}개 가져오기',
+ 'packing.importSuccess': '{count}개 항목을 가져왔습니다',
+ 'packing.importError': '가져오기 실패',
+ 'packing.importEmpty': '가져올 항목이 없습니다',
+ 'packing.progress': '{total}개 중 {packed}개 완료 ({percent}%)',
+ 'packing.clearChecked': '체크된 {count}개 제거',
+ 'packing.clearCheckedShort': '{count}개 제거',
+ 'packing.suggestions': '제안',
+ 'packing.suggestionsTitle': '제안 추가',
+ 'packing.allSuggested': '모든 제안이 추가되었습니다',
+ 'packing.allPacked': '모두 완료!',
+ 'packing.addPlaceholder': '새 항목 추가...',
+ 'packing.categoryPlaceholder': '카테고리...',
+ 'packing.filterAll': '전체',
+ 'packing.filterOpen': '미완료',
+ 'packing.filterDone': '완료',
+ 'packing.emptyTitle': '짐 목록이 비어 있습니다',
+ 'packing.emptyHint': '항목을 추가하거나 제안을 사용하세요',
+ 'packing.emptyFiltered': '이 필터와 일치하는 항목이 없습니다',
+ 'packing.menuRename': '이름 변경',
+ 'packing.menuCheckAll': '전체 체크',
+ 'packing.menuUncheckAll': '전체 체크 해제',
+ 'packing.menuDeleteCat': '카테고리 삭제',
+ 'packing.noMembers': '여행 멤버가 없습니다',
+ 'packing.addItem': '항목 추가',
+ 'packing.addItemPlaceholder': '항목 이름...',
+ 'packing.addCategory': '카테고리 추가',
+ 'packing.newCategoryPlaceholder': '카테고리 이름 (예: 의류)',
+ 'packing.applyTemplate': '템플릿 적용',
+ 'packing.template': '템플릿',
+ 'packing.templateApplied': '템플릿에서 {count}개 항목이 추가되었습니다',
+ 'packing.templateError': '템플릿 적용 실패',
+ 'packing.saveAsTemplate': '템플릿으로 저장',
+ 'packing.templateName': '템플릿 이름',
+ 'packing.templateSaved': '짐 목록이 템플릿으로 저장되었습니다',
+ 'packing.bags': '가방',
+ 'packing.noBag': '미배정',
+ 'packing.totalWeight': '총 무게',
+ 'packing.bagName': '가방 이름...',
+ 'packing.addBag': '가방 추가',
+ 'packing.changeCategory': '카테고리 변경',
+ 'packing.confirm.clearChecked': '체크된 {count}개 항목을 제거할까요?',
+ 'packing.confirm.deleteCat':
+ '카테고리 "{name}"을(를) {count}개 항목과 함께 삭제할까요?',
+ 'packing.defaultCategory': '기타',
+ 'packing.toast.saveError': '저장 실패',
+ 'packing.toast.deleteError': '삭제 실패',
+ 'packing.toast.renameError': '이름 변경 실패',
+ 'packing.toast.addError': '추가 실패',
+ 'packing.suggestions.items': [
+ {
+ name: '여권',
+ category: '서류',
+ },
+ {
+ name: '신분증',
+ category: '서류',
+ },
+ {
+ name: '여행자 보험',
+ category: '서류',
+ },
+ {
+ name: '항공권',
+ category: '서류',
+ },
+ {
+ name: '신용카드',
+ category: '금융',
+ },
+ {
+ name: '현금',
+ category: '금융',
+ },
+ {
+ name: '비자',
+ category: '서류',
+ },
+ {
+ name: '티셔츠',
+ category: '의류',
+ },
+ {
+ name: '바지',
+ category: '의류',
+ },
+ {
+ name: '속옷',
+ category: '의류',
+ },
+ {
+ name: '양말',
+ category: '의류',
+ },
+ {
+ name: '재킷',
+ category: '의류',
+ },
+ {
+ name: '잠옷',
+ category: '의류',
+ },
+ {
+ name: '수영복',
+ category: '의류',
+ },
+ {
+ name: '우비',
+ category: '의류',
+ },
+ {
+ name: '편한 신발',
+ category: '의류',
+ },
+ {
+ name: '칫솔',
+ category: '세면도구',
+ },
+ {
+ name: '치약',
+ category: '세면도구',
+ },
+ {
+ name: '샴푸',
+ category: '세면도구',
+ },
+ {
+ name: '데오도란트',
+ category: '세면도구',
+ },
+ {
+ name: '자외선 차단제',
+ category: '세면도구',
+ },
+ {
+ name: '면도기',
+ category: '세면도구',
+ },
+ {
+ name: '충전기',
+ category: '전자기기',
+ },
+ {
+ name: '보조 배터리',
+ category: '전자기기',
+ },
+ {
+ name: '헤드폰',
+ category: '전자기기',
+ },
+ {
+ name: '여행용 어댑터',
+ category: '전자기기',
+ },
+ {
+ name: '카메라',
+ category: '전자기기',
+ },
+ {
+ name: '진통제',
+ category: '건강',
+ },
+ {
+ name: '반창고',
+ category: '건강',
+ },
+ {
+ name: '소독제',
+ category: '건강',
+ },
+ ],
+};
+export default packing;
diff --git a/shared/src/i18n/ko/pdf.ts b/shared/src/i18n/ko/pdf.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f6ca14170ab7c1c628fe83d00aa85850ccc47fd8
--- /dev/null
+++ b/shared/src/i18n/ko/pdf.ts
@@ -0,0 +1,10 @@
+import type { TranslationStrings } from '../types';
+
+const pdf: TranslationStrings = {
+ 'pdf.travelPlan': '여행 계획',
+ 'pdf.planned': '계획됨',
+ 'pdf.costLabel': '비용 (원)',
+ 'pdf.preview': 'PDF 미리보기',
+ 'pdf.saveAsPdf': 'PDF로 저장',
+};
+export default pdf;
diff --git a/shared/src/i18n/ko/perm.ts b/shared/src/i18n/ko/perm.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2ff9efd2c72853c8ff8a13f7ad32f163bf4b696c
--- /dev/null
+++ b/shared/src/i18n/ko/perm.ts
@@ -0,0 +1,60 @@
+import type { TranslationStrings } from '../types';
+
+const perm: TranslationStrings = {
+ 'perm.title': '권한 설정',
+ 'perm.subtitle': '앱 전체에서 누가 작업을 수행할 수 있는지 제어합니다',
+ 'perm.saved': '권한 설정이 저장되었습니다',
+ 'perm.resetDefaults': '기본값으로 초기화',
+ 'perm.customized': '맞춤 설정됨',
+ 'perm.level.admin': '관리자만',
+ 'perm.level.tripOwner': '여행 소유자',
+ 'perm.level.tripMember': '여행 멤버',
+ 'perm.level.everybody': '모든 사람',
+ 'perm.cat.trip': '여행 관리',
+ 'perm.cat.members': '멤버 관리',
+ 'perm.cat.files': '파일',
+ 'perm.cat.content': '콘텐츠 및 일정',
+ 'perm.cat.extras': '예산, 짐 목록 및 협업',
+ 'perm.action.trip_create': '여행 만들기',
+ 'perm.action.trip_edit': '여행 상세 편집',
+ 'perm.action.trip_delete': '여행 삭제',
+ 'perm.action.trip_archive': '여행 보관/복원',
+ 'perm.action.trip_cover_upload': '커버 이미지 업로드',
+ 'perm.action.member_manage': '멤버 추가/제거',
+ 'perm.action.file_upload': '파일 업로드',
+ 'perm.action.file_edit': '파일 메타데이터 편집',
+ 'perm.action.file_delete': '파일 삭제',
+ 'perm.action.place_edit': '장소 추가/편집/삭제',
+ 'perm.action.day_edit': '날, 메모 및 배정 편집',
+ 'perm.action.reservation_edit': '예약 관리',
+ 'perm.action.budget_edit': '예산 관리',
+ 'perm.action.packing_edit': '짐 목록 관리',
+ 'perm.action.collab_edit': '협업 (메모, 투표, 채팅)',
+ 'perm.action.share_manage': '공유 링크 관리',
+ 'perm.actionHint.trip_create': '누가 새 여행을 만들 수 있는지',
+ 'perm.actionHint.trip_edit':
+ '누가 여행 이름, 날짜, 설명, 통화를 변경할 수 있는지',
+ 'perm.actionHint.trip_delete': '누가 여행을 영구 삭제할 수 있는지',
+ 'perm.actionHint.trip_archive': '누가 여행을 보관하거나 복원할 수 있는지',
+ 'perm.actionHint.trip_cover_upload':
+ '누가 커버 이미지를 업로드하거나 변경할 수 있는지',
+ 'perm.actionHint.member_manage':
+ '누가 여행 멤버를 초대하거나 제거할 수 있는지',
+ 'perm.actionHint.file_upload': '누가 여행에 파일을 업로드할 수 있는지',
+ 'perm.actionHint.file_edit': '누가 파일 설명 및 링크를 편집할 수 있는지',
+ 'perm.actionHint.file_delete':
+ '누가 파일을 휴지통으로 이동하거나 영구 삭제할 수 있는지',
+ 'perm.actionHint.place_edit': '누가 장소를 추가, 편집, 삭제할 수 있는지',
+ 'perm.actionHint.day_edit':
+ '누가 날, 일별 메모, 장소 배정을 편집할 수 있는지',
+ 'perm.actionHint.reservation_edit':
+ '누가 예약을 만들고, 편집하고, 삭제할 수 있는지',
+ 'perm.actionHint.budget_edit':
+ '누가 예산 항목을 만들고, 편집하고, 삭제할 수 있는지',
+ 'perm.actionHint.packing_edit': '누가 짐 항목과 가방을 관리할 수 있는지',
+ 'perm.actionHint.collab_edit':
+ '누가 메모, 투표를 만들고 메시지를 보낼 수 있는지',
+ 'perm.actionHint.share_manage':
+ '누가 공개 공유 링크를 만들거나 삭제할 수 있는지',
+};
+export default perm;
diff --git a/shared/src/i18n/ko/photos.ts b/shared/src/i18n/ko/photos.ts
new file mode 100644
index 0000000000000000000000000000000000000000..807d20a63eb6193868bcac9f4389f4ba6e3e64b4
--- /dev/null
+++ b/shared/src/i18n/ko/photos.ts
@@ -0,0 +1,25 @@
+import type { TranslationStrings } from '../types';
+
+const photos: TranslationStrings = {
+ 'photos.title': '사진',
+ 'photos.subtitle': '{trip}의 사진 {count}장',
+ 'photos.dropHere': '여기에 사진을 놓으세요...',
+ 'photos.dropHereActive': '여기에 사진을 놓으세요',
+ 'photos.captionForAll': '캡션 (전체)',
+ 'photos.captionPlaceholder': '선택적 캡션...',
+ 'photos.addCaption': '캡션 추가...',
+ 'photos.allDays': '모든 날',
+ 'photos.noPhotos': '아직 사진이 없습니다',
+ 'photos.uploadHint': '여행 사진을 업로드하세요',
+ 'photos.clickToSelect': '또는 클릭하여 선택',
+ 'photos.linkPlace': '장소 연결',
+ 'photos.noPlace': '장소 없음',
+ 'photos.uploadN': '{n}장 사진 업로드',
+ 'photos.linkDay': '날 연결',
+ 'photos.noDay': '날 없음',
+ 'photos.dayLabel': '{number}일차',
+ 'photos.photoSelected': '사진 선택됨',
+ 'photos.photosSelected': '사진 선택됨',
+ 'photos.fileTypeHint': 'JPG, PNG, WebP · 최대 10 MB · 최대 30장',
+};
+export default photos;
diff --git a/shared/src/i18n/ko/places.ts b/shared/src/i18n/ko/places.ts
new file mode 100644
index 0000000000000000000000000000000000000000..663aa87535e910eed666f60bb49d6c9bb39253bb
--- /dev/null
+++ b/shared/src/i18n/ko/places.ts
@@ -0,0 +1,95 @@
+import type { TranslationStrings } from '../types';
+
+const places: TranslationStrings = {
+ 'places.addPlace': '장소/활동 추가',
+ 'places.importFile': '파일 가져오기',
+ 'places.sidebarDrop': '끌어다 가져오기',
+ 'places.importFileHint':
+ 'Google My Maps, Google Earth 또는 GPS 추적기 등의 .gpx, .kml, .kmz 파일을 가져옵니다.',
+ 'places.importFileDropHere': '파일을 선택하거나 여기에 끌어다 놓으세요',
+ 'places.importFileDropActive': '파일을 놓으세요',
+ 'places.importFileUnsupported':
+ '지원하지 않는 파일 형식입니다. .gpx, .kml 또는 .kmz를 사용하세요.',
+ 'places.importFileTooLarge':
+ '파일이 너무 큽니다. 최대 업로드 크기는 {maxMb} MB입니다.',
+ 'places.importFileError': '가져오기 실패',
+ 'places.importAllSkipped': '모든 장소가 이미 여행에 포함되어 있습니다.',
+ 'places.gpxImported': 'GPX에서 {count}개 장소를 가져왔습니다',
+ 'places.gpxImportTypes': '무엇을 가져올까요?',
+ 'places.gpxImportWaypoints': '웨이포인트',
+ 'places.gpxImportRoutes': '경로',
+ 'places.gpxImportTracks': '트랙 (경로 형상 포함)',
+ 'places.gpxImportNoneSelected': '가져올 유형을 하나 이상 선택하세요.',
+ 'places.kmlImportTypes': '무엇을 가져올까요?',
+ 'places.kmlImportPoints': '포인트 (Placemarks)',
+ 'places.kmlImportPaths': '경로 (LineStrings)',
+ 'places.kmlImportNoneSelected': '가져올 유형을 하나 이상 선택하세요.',
+ 'places.selectionCount': '{count}개 선택됨',
+ 'places.deleteSelected': '선택 항목 삭제',
+ 'places.kmlKmzImported': 'KMZ/KML에서 {count}개 장소를 가져왔습니다',
+ 'places.urlResolved': 'URL에서 장소를 가져왔습니다',
+ 'places.importList': '목록 가져오기',
+ 'places.kmlKmzSummaryValues':
+ '총 Placemarks: {total} · 가져옴: {created} · 건너뜀: {skipped}',
+ 'places.importGoogleList': 'Google 목록',
+ 'places.importNaverList': '네이버 목록',
+ 'places.googleListHint':
+ '공유된 Google Maps 목록 링크를 붙여넣어 모든 장소를 가져옵니다.',
+ 'places.googleListImported': '"{list}"에서 {count}개 장소를 가져왔습니다',
+ 'places.googleListError': 'Google Maps 목록 가져오기 실패',
+ 'places.naverListHint':
+ '공유된 네이버 지도 목록 링크를 붙여넣어 모든 장소를 가져옵니다.',
+ 'places.naverListImported': '"{list}"에서 {count}개 장소를 가져왔습니다',
+ 'places.naverListError': '네이버 지도 목록 가져오기 실패',
+ 'places.viewDetails': '상세 보기',
+ 'places.assignToDay': '어느 날에 추가할까요?',
+ 'places.all': '전체',
+ 'places.unplanned': '미계획',
+ 'places.filterTracks': '트랙',
+ 'places.search': '장소 검색...',
+ 'places.allCategories': '모든 카테고리',
+ 'places.categoriesSelected': '카테고리',
+ 'places.clearFilter': '필터 지우기',
+ 'places.count': '장소 {count}개',
+ 'places.countSingular': '장소 1개',
+ 'places.allPlanned': '모든 장소가 계획되었습니다',
+ 'places.noneFound': '장소를 찾을 수 없습니다',
+ 'places.editPlace': '장소 편집',
+ 'places.formName': '이름',
+ 'places.formNamePlaceholder': '예: 에펠탑',
+ 'places.formDescription': '설명',
+ 'places.formDescriptionPlaceholder': '간단한 설명...',
+ 'places.formAddress': '주소',
+ 'places.formAddressPlaceholder': '도로, 도시, 국가',
+ 'places.formLat': '위도 (예: 48.8566)',
+ 'places.formLng': '경도 (예: 2.3522)',
+ 'places.formCategory': '카테고리',
+ 'places.noCategory': '카테고리 없음',
+ 'places.categoryNamePlaceholder': '카테고리 이름',
+ 'places.formTime': '시간',
+ 'places.startTime': '시작',
+ 'places.endTime': '종료',
+ 'places.endTimeBeforeStart': '종료 시간이 시작 시간보다 앞입니다',
+ 'places.timeCollision': '시간 겹침:',
+ 'places.formWebsite': '웹사이트',
+ 'places.formNotes': '메모',
+ 'places.formNotesPlaceholder': '개인 메모...',
+ 'places.formReservation': '예약',
+ 'places.reservationNotesPlaceholder': '예약 메모, 확인 번호...',
+ 'places.mapsSearchPlaceholder': '장소 검색...',
+ 'places.mapsSearchError': '장소 검색 실패.',
+ 'places.loadingDetails': '장소 상세 정보 불러오는 중…',
+ 'places.osmHint':
+ 'OpenStreetMap 검색 사용 중 (사진, 영업 시간, 평점 없음). 전체 정보를 위해 설정에서 Google API 키를 추가하세요.',
+ 'places.osmActive':
+ 'OpenStreetMap으로 검색 중 (사진, 평점, 영업 시간 없음). 향상된 데이터를 위해 설정에서 Google API 키를 추가하세요.',
+ 'places.categoryCreateError': '카테고리 생성 실패',
+ 'places.nameRequired': '이름을 입력하세요',
+ 'places.saveError': '저장 실패',
+ 'places.duplicateExists': "'{name}'은(는) 이미 이 여행에 있습니다.",
+ 'places.addAnyway': '그래도 추가',
+ 'places.enrichOnImport': 'Google로 장소 정보 보강',
+ 'places.enrichOnImportHint':
+ '가져온 각 장소를 검색해 사진, 주소, 연락처를 추가합니다. Google Maps 키가 필요합니다.',
+};
+export default places;
diff --git a/shared/src/i18n/ko/planner.ts b/shared/src/i18n/ko/planner.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f15132d76317abe0d28794a3f6b06c84112bf7d4
--- /dev/null
+++ b/shared/src/i18n/ko/planner.ts
@@ -0,0 +1,67 @@
+import type { TranslationStrings } from '../types';
+
+const planner: TranslationStrings = {
+ 'planner.places': '장소',
+ 'planner.bookings': '예약',
+ 'planner.packingList': '짐 목록',
+ 'planner.documents': '문서',
+ 'planner.dayPlan': '일별 계획',
+ 'planner.reservations': '예약',
+ 'planner.minTwoPlaces': '경로 계산에 좌표가 있는 장소가 최소 2개 필요합니다',
+ 'planner.noGeoPlaces': '좌표가 있는 장소가 없습니다',
+ 'planner.routeCalculated': '경로가 계산되었습니다',
+ 'planner.routeCalcFailed': '경로를 계산할 수 없습니다',
+ 'planner.routeError': '경로 계산 중 오류',
+ 'planner.icsExportFailed': 'ICS 내보내기 실패',
+ 'planner.routeOptimized': '경로가 최적화되었습니다',
+ 'planner.reservationUpdated': '예약이 업데이트되었습니다',
+ 'planner.reservationAdded': '예약이 추가되었습니다',
+ 'planner.confirmDeleteReservation': '예약을 삭제할까요?',
+ 'planner.reservationDeleted': '예약이 삭제되었습니다',
+ 'planner.days': '일',
+ 'planner.allPlaces': '모든 장소',
+ 'planner.totalPlaces': '총 {n}개 장소',
+ 'planner.noDaysPlanned': '아직 계획된 날이 없습니다',
+ 'planner.editTrip': '여행 편집 →',
+ 'planner.placeOne': '장소 1개',
+ 'planner.placeN': '장소 {n}개',
+ 'planner.addNote': '메모 추가',
+ 'planner.noEntries': '이 날에 항목이 없습니다',
+ 'planner.addPlace': '장소/활동 추가',
+ 'planner.addPlaceShort': '+ 장소/활동 추가',
+ 'planner.resPending': '예약 대기 중 · ',
+ 'planner.resConfirmed': '예약 확정 · ',
+ 'planner.notePlaceholder': '메모…',
+ 'planner.noteTimePlaceholder': '시간 (선택)',
+ 'planner.noteExamplePlaceholder':
+ '예: 14:30 중앙역에서 S3, 7번 부두에서 페리, 점심 휴식…',
+ 'planner.totalCost': '총 비용',
+ 'planner.searchPlaces': '장소 검색…',
+ 'planner.allCategories': '모든 카테고리',
+ 'planner.noPlacesFound': '장소를 찾을 수 없습니다',
+ 'planner.addFirstPlace': '첫 번째 장소 추가',
+ 'planner.noReservations': '예약 없음',
+ 'planner.addFirstReservation': '첫 번째 예약 추가',
+ 'planner.new': '새로 만들기',
+ 'planner.addToDay': '+ 날에 추가',
+ 'planner.calculating': '계산 중…',
+ 'planner.route': '경로',
+ 'planner.optimize': '최적화',
+ 'planner.openGoogleMaps': 'Google Maps에서 열기',
+ 'planner.selectDayHint': '왼쪽 목록에서 날을 선택하여 일별 계획을 보세요',
+ 'planner.noPlacesForDay': '이 날에 아직 장소가 없습니다',
+ 'planner.addPlacesLink': '장소 추가 →',
+ 'planner.minTotal': '분 합계',
+ 'planner.noReservation': '예약 없음',
+ 'planner.removeFromDay': '날에서 제거',
+ 'planner.addToThisDay': '날에 추가',
+ 'planner.overview': '개요',
+ 'planner.noDays': '아직 날이 없습니다',
+ 'planner.editTripToAddDays': '여행을 편집하여 날을 추가하세요',
+ 'planner.dayCount': '{n}일',
+ 'planner.clickToUnlock': '클릭하여 잠금 해제',
+ 'planner.keepPosition': '경로 최적화 중 위치 유지',
+ 'planner.dayDetails': '일별 상세',
+ 'planner.dayN': '{n}일차',
+};
+export default planner;
diff --git a/shared/src/i18n/ko/register.ts b/shared/src/i18n/ko/register.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3a8b3fe4c777c3717d92e6e2f7d964053716e5f6
--- /dev/null
+++ b/shared/src/i18n/ko/register.ts
@@ -0,0 +1,25 @@
+import type { TranslationStrings } from '../types';
+
+const register: TranslationStrings = {
+ 'register.passwordMismatch': '비밀번호가 일치하지 않습니다',
+ 'register.passwordTooShort': '비밀번호는 최소 8자 이상이어야 합니다',
+ 'register.failed': '회원가입 실패',
+ 'register.getStarted': '시작하기',
+ 'register.subtitle': '계정을 만들고 꿈의 여행을 계획하세요.',
+ 'register.feature1': '무제한 여행 계획',
+ 'register.feature2': '인터랙티브 지도 보기',
+ 'register.feature3': '장소 및 카테고리 관리',
+ 'register.feature4': '예약 추적',
+ 'register.feature5': '짐 목록 만들기',
+ 'register.feature6': '사진 및 파일 저장',
+ 'register.createAccount': '계정 만들기',
+ 'register.startPlanning': '여행 계획 시작',
+ 'register.minChars': '최소 6자',
+ 'register.confirmPassword': '비밀번호 확인',
+ 'register.repeatPassword': '비밀번호 재입력',
+ 'register.registering': '가입 중...',
+ 'register.register': '회원가입',
+ 'register.hasAccount': '이미 계정이 있으신가요?',
+ 'register.signIn': '로그인',
+};
+export default register;
diff --git a/shared/src/i18n/ko/reservations.ts b/shared/src/i18n/ko/reservations.ts
new file mode 100644
index 0000000000000000000000000000000000000000..544c8a8dee4282f814ee2f9dd102e2595f2a4802
--- /dev/null
+++ b/shared/src/i18n/ko/reservations.ts
@@ -0,0 +1,160 @@
+import type { TranslationStrings } from '../types';
+
+const reservations: TranslationStrings = {
+ 'reservations.title': '예약',
+ 'reservations.empty': '아직 예약이 없습니다',
+ 'reservations.emptyHint': '항공, 호텔 등의 예약을 추가하세요',
+ 'reservations.add': '예약 추가',
+ 'reservations.addManual': '직접 예약',
+ 'reservations.placeHint':
+ '팁: 예약은 일별 계획과 연결하기 위해 장소에서 직접 만드는 것이 가장 좋습니다.',
+ 'reservations.confirmed': '확정됨',
+ 'reservations.pending': '대기 중',
+ 'reservations.summary': '{confirmed}개 확정, {pending}개 대기 중',
+ 'reservations.fromPlan': '계획에서',
+ 'reservations.showFiles': '파일 보기',
+ 'reservations.editTitle': '예약 편집',
+ 'reservations.status': '상태',
+ 'reservations.datetime': '날짜 및 시간',
+ 'reservations.startTime': '시작 시간',
+ 'reservations.endTime': '종료 시간',
+ 'reservations.date': '날짜',
+ 'reservations.time': '시간',
+ 'reservations.timeAlt': '시간 (대안, 예: 19:30)',
+ 'reservations.notes': '메모',
+ 'reservations.notesPlaceholder': '추가 메모...',
+ 'reservations.meta.airline': '항공사',
+ 'reservations.meta.flightNumber': '항공편 번호',
+ 'reservations.meta.from': '출발',
+ 'reservations.meta.to': '도착',
+ 'reservations.layover.route': '경로',
+ 'reservations.layover.stop': '경유',
+ 'reservations.layover.addStop': '경유지 추가',
+ 'reservations.layover.connection': '연결편',
+ 'reservations.layover.layover': '경유 대기',
+ 'reservations.needsReview': '검토 필요',
+ 'reservations.needsReviewHint':
+ '공항이 자동으로 매칭되지 않았습니다 — 위치를 확인해 주세요.',
+ 'reservations.searchLocation': '역, 항구, 주소 검색…',
+ 'reservations.meta.trainNumber': '열차 번호',
+ 'reservations.meta.platform': '플랫폼',
+ 'reservations.meta.seat': '좌석',
+ 'reservations.meta.checkIn': '체크인',
+ 'reservations.meta.checkInUntil': '체크인 마감',
+ 'reservations.meta.checkOut': '체크아웃',
+ 'reservations.meta.linkAccommodation': '숙박',
+ 'reservations.meta.pickAccommodation': '숙박 연결',
+ 'reservations.meta.noAccommodation': '없음',
+ 'reservations.meta.hotelPlace': '숙박',
+ 'reservations.meta.pickHotel': '숙박 선택',
+ 'reservations.meta.fromDay': '부터',
+ 'reservations.meta.toDay': '까지',
+ 'reservations.meta.selectDay': '날 선택',
+ 'reservations.type.flight': '항공',
+ 'reservations.type.hotel': '숙박',
+ 'reservations.type.restaurant': '레스토랑',
+ 'reservations.type.train': '기차',
+ 'reservations.type.car': '차량',
+ 'reservations.type.cruise': '크루즈',
+ 'reservations.type.event': '이벤트',
+ 'reservations.type.tour': '투어',
+ 'reservations.type.other': '기타',
+ 'reservations.type.bus': '버스',
+ 'reservations.type.ferry': '페리',
+ 'reservations.type.bicycle': '자전거',
+ 'reservations.type.taxi': '택시',
+ 'reservations.type.transport_other': '기타',
+ 'reservations.confirm.delete': '예약 "{name}"을(를) 삭제할까요?',
+ 'reservations.confirm.deleteTitle': '예약을 삭제할까요?',
+ 'reservations.confirm.deleteBody': '"{name}"이(가) 영구 삭제됩니다.',
+ 'reservations.toast.updated': '예약이 업데이트되었습니다',
+ 'reservations.toast.removed': '예약이 삭제되었습니다',
+ 'reservations.toast.fileUploaded': '파일이 업로드되었습니다',
+ 'reservations.toast.uploadError': '업로드 실패',
+ 'reservations.newTitle': '새 예약',
+ 'reservations.bookingType': '예약 유형',
+ 'reservations.titleLabel': '제목',
+ 'reservations.titlePlaceholder': '예: 대한항공 KE123, 호텔 신라, ...',
+ 'reservations.locationAddress': '위치 / 주소',
+ 'reservations.locationPlaceholder': '주소, 공항, 호텔...',
+ 'reservations.confirmationCode': '예약 코드',
+ 'reservations.confirmationPlaceholder': '예: ABC12345',
+ 'reservations.day': '날',
+ 'reservations.noDay': '날 없음',
+ 'reservations.place': '장소',
+ 'reservations.noPlace': '장소 없음',
+ 'reservations.pendingSave': '저장될 예정…',
+ 'reservations.uploading': '업로드 중...',
+ 'reservations.attachFile': '파일 첨부',
+ 'reservations.linkExisting': '기존 파일 연결',
+ 'reservations.toast.saveError': '저장 실패',
+ 'reservations.toast.updateError': '업데이트 실패',
+ 'reservations.toast.deleteError': '삭제 실패',
+ 'reservations.confirm.remove': '"{name}"의 예약을 제거할까요?',
+ 'reservations.linkAssignment': '날 배정에 연결',
+ 'reservations.pickAssignment': '계획에서 배정을 선택하세요...',
+ 'reservations.noAssignment': '연결 없음 (독립)',
+ 'reservations.price': '가격',
+ 'reservations.budgetCategory': '예산 카테고리',
+ 'reservations.budgetCategoryPlaceholder': '예: 교통, 숙박',
+ 'reservations.budgetCategoryAuto': '자동 (예약 유형에서)',
+ 'reservations.budgetHint': '저장 시 예산 항목이 자동으로 생성됩니다.',
+ 'reservations.departureDate': '출발',
+ 'reservations.arrivalDate': '도착',
+ 'reservations.departureTime': '출발 시간',
+ 'reservations.arrivalTime': '도착 시간',
+ 'reservations.pickupDate': '픽업',
+ 'reservations.returnDate': '반납',
+ 'reservations.pickupTime': '픽업 시간',
+ 'reservations.returnTime': '반납 시간',
+ 'reservations.endDate': '종료 날짜',
+ 'reservations.meta.departureTimezone': '출발 시간대',
+ 'reservations.meta.arrivalTimezone': '도착 시간대',
+ 'reservations.span.departure': '출발',
+ 'reservations.span.arrival': '도착',
+ 'reservations.span.inTransit': '이동 중',
+ 'reservations.span.pickup': '픽업',
+ 'reservations.span.return': '반납',
+ 'reservations.span.active': '활성',
+ 'reservations.span.start': '시작',
+ 'reservations.span.end': '종료',
+ 'reservations.span.ongoing': '진행 중',
+ 'reservations.validation.endBeforeStart':
+ '종료 날짜/시간은 시작 날짜/시간 이후여야 합니다',
+ 'reservations.addBooking': '예약 추가',
+ 'reservations.import.title': '예약 확인서 가져오기',
+ 'reservations.import.cta': '파일에서 가져오기',
+ 'reservations.import.dropHere': '예약 확인 파일을 여기에 끌어다 놓거나 클릭하여 선택',
+ 'reservations.import.dropActive': '가져올 파일을 여기에 놓으세요',
+ 'reservations.import.acceptedFormats': '허용 형식: EML, PDF, PKPass, HTML, TXT (파일당 최대 10 MB, 최대 5개)',
+ 'reservations.import.parsing': '파일 분석 중…',
+ 'reservations.import.previewHeading': '{count}개 예약 발견',
+ 'reservations.import.previewEmpty': '업로드된 파일에서 예약을 추출할 수 없었습니다.',
+ 'reservations.import.removeItem': '제거',
+ 'reservations.import.confirm': '{count}개 예약 가져오기',
+ 'reservations.import.back': '뒤로',
+ 'reservations.import.success': '{count}개 예약을 가져왔습니다',
+ 'reservations.import.partialFailure': '{created}개 가져옴, {failed}개 실패',
+ 'reservations.import.error': '분석 실패. 파일이 유효한 예약 확인서인지 확인하세요.',
+ 'reservations.import.unavailable': '이 서버에서는 예약 가져오기를 사용할 수 없습니다.',
+ 'reservations.import.unsupportedFormat': '지원하지 않는 파일 형식입니다. EML, PDF, PKPass, HTML 또는 TXT를 사용하세요.',
+ 'reservations.import.fileTooLarge': '파일 "{name}"이(가) 10 MB 제한을 초과합니다.',
+ 'reservations.airtrail.title': 'AirTrail에서 가져오기',
+ 'reservations.airtrail.cta': 'AirTrail',
+ 'reservations.airtrail.synced': 'AirTrail',
+ 'reservations.airtrail.syncedHint': 'AirTrail에서 동기화됨 — 수정 사항이 양방향으로 동기화됩니다.',
+ 'reservations.airtrail.notSynced': '동기화되지 않음',
+ 'reservations.airtrail.notSyncedHint': '이 항공편은 AirTrail에서 삭제되어 더 이상 동기화되지 않습니다.',
+ 'reservations.airtrail.loadError': 'AirTrail 항공편을 불러올 수 없습니다.',
+ 'reservations.airtrail.imported': '{count}개 항공편을 가져왔습니다',
+ 'reservations.airtrail.skippedDuplicate': '{count}개는 이미 이 여행에 있어 건너뛰었습니다',
+ 'reservations.airtrail.nothingImported': '가져올 항목이 없습니다.',
+ 'reservations.airtrail.importError': '가져오기에 실패했습니다. 다시 시도하세요.',
+ 'reservations.airtrail.undo': 'AirTrail에서 가져오기',
+ 'reservations.airtrail.alreadyImported': '가져옴',
+ 'reservations.airtrail.duringTrip': '이 여행 기간',
+ 'reservations.airtrail.otherFlights': '기타 항공편',
+ 'reservations.airtrail.empty': 'AirTrail 계정에서 항공편을 찾을 수 없습니다.',
+ 'reservations.airtrail.importCta': '{count}개 가져오기',
+};
+export default reservations;
diff --git a/shared/src/i18n/ko/settings.ts b/shared/src/i18n/ko/settings.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d34b03a1ac7fec9cb836acf16d2dce56cdba41da
--- /dev/null
+++ b/shared/src/i18n/ko/settings.ts
@@ -0,0 +1,342 @@
+import type { TranslationStrings } from '../types';
+
+const settings: TranslationStrings = {
+ 'settings.title': '설정',
+ 'settings.subtitle': '개인 설정을 구성하세요',
+ 'settings.tabs.display': '화면',
+ 'settings.tabs.map': '지도',
+ 'settings.tabs.notifications': '알림',
+ 'settings.tabs.integrations': '통합',
+ 'settings.tabs.account': '계정',
+ 'settings.tabs.offline': '오프라인',
+ 'settings.tabs.about': '정보',
+ 'settings.map': '지도',
+ 'settings.mapTemplate': '지도 템플릿',
+ 'settings.mapTemplatePlaceholder.select': '템플릿 선택...',
+ 'settings.mapDefaultHint': '비워두면 OpenStreetMap (기본값) 사용',
+ 'settings.mapTemplatePlaceholder':
+ 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
+ 'settings.mapHint': '지도 타일 URL 템플릿',
+ 'settings.mapProvider': '지도 공급자',
+ 'settings.mapProviderHint':
+ '여행 플래너 및 Journey 지도에 영향을 줍니다. Atlas는 항상 Leaflet을 사용합니다.',
+ 'settings.mapLeafletSubtitle': '클래식 2D, 모든 래스터 타일',
+ 'settings.mapMapboxSubtitle': '벡터 타일, 3D 건물 및 지형',
+ 'settings.mapExperimental': '실험적',
+ 'settings.mapMapboxToken': 'Mapbox 액세스 토큰',
+ 'settings.mapMapboxTokenHint': '공개 토큰 (pk.*) 출처',
+ 'settings.mapMapboxTokenLink': 'mapbox.com → 액세스 토큰',
+ 'settings.mapStyle': '지도 스타일',
+ 'settings.mapStylePlaceholder': 'Mapbox 스타일 선택',
+ 'settings.mapStyleHint': '프리셋 또는 mapbox://styles/USER/ID URL 직접 입력',
+ 'settings.map3dBuildings': '3D 건물 및 지형',
+ 'settings.map3dHint':
+ '기울기 + 실제 3D 건물 돌출 — 위성 포함 모든 스타일에서 작동합니다.',
+ 'settings.mapHighQuality': '고품질 모드',
+ 'settings.mapHighQualityHint':
+ '안티앨리어싱 + 구형 투영으로 선명한 경계와 현실적인 지구 뷰를 제공합니다.',
+ 'settings.mapHighQualityWarning':
+ '저사양 기기에서 성능에 영향을 줄 수 있습니다.',
+ 'settings.mapTipLabel': '팁:',
+ 'settings.mapTip':
+ '우클릭 후 드래그하여 지도를 회전/기울이세요. 가운데 클릭으로 장소를 추가할 수 있습니다 (우클릭은 회전 전용).',
+ 'settings.latitude': '위도',
+ 'settings.longitude': '경도',
+ 'settings.saveMap': '지도 저장',
+ 'settings.apiKeys': 'API 키',
+ 'settings.mapsKey': 'Google Maps API 키',
+ 'settings.mapsKeyHint':
+ '장소 검색용. Places API (New) 필요. console.cloud.google.com에서 발급',
+ 'settings.weatherKey': 'OpenWeatherMap API 키',
+ 'settings.weatherKeyHint':
+ '날씨 데이터용. openweathermap.org/api에서 무료 발급',
+ 'settings.keyPlaceholder': '키 입력...',
+ 'settings.configured': '설정됨',
+ 'settings.saveKeys': '키 저장',
+ 'settings.display': '화면',
+ 'settings.colorMode': '색상 모드',
+ 'settings.light': '라이트',
+ 'settings.dark': '다크',
+ 'settings.auto': '자동',
+ 'settings.language': '언어',
+ 'settings.temperature': '온도 단위',
+ 'settings.timeFormat': '시간 형식',
+ 'settings.bookingLabels': '예약 경로 레이블',
+ 'settings.bookingLabelsHint':
+ '지도에 역 / 공항 이름을 표시합니다. 끄면 아이콘만 표시됩니다.',
+ 'settings.blurBookingCodes': '예약 코드 흐리게',
+ 'settings.optimizeFromAccommodation': '숙소 기준으로 경로 최적화',
+ 'settings.optimizeFromAccommodationHint':
+ '하루 일정을 최적화할 때, 아침에 머무는 숙소에서 경로를 시작하고 그날 저녁에 체크인하는 숙소에서 경로를 끝냅니다.',
+ 'settings.notifications': '알림',
+ 'settings.notifyTripInvite': '여행 초대',
+ 'settings.notifyBookingChange': '예약 변경',
+ 'settings.notifyTripReminder': '여행 리마인더',
+ 'settings.notifyTodoDue': '할 일 마감 임박',
+ 'settings.notifyVacayInvite': 'Vacay 퓨전 초대',
+ 'settings.notifyPhotosShared': '공유된 사진 (Immich)',
+ 'settings.notifyCollabMessage': '채팅 메시지 (Collab)',
+ 'settings.notifyPackingTagged': '짐 목록: 배정',
+ 'settings.notifyWebhook': '웹훅 알림',
+ 'settings.notifyVersionAvailable': '새 버전 사용 가능',
+ 'settings.notificationPreferences.email': '이메일',
+ 'settings.notificationPreferences.webhook': '웹훅',
+ 'settings.notificationPreferences.inapp': '앱 내',
+ 'settings.notificationPreferences.ntfy': 'Ntfy',
+ 'settings.notificationPreferences.noChannels':
+ '알림 채널이 설정되지 않았습니다. 관리자에게 이메일 또는 웹훅 알림 설정을 요청하세요.',
+ 'settings.webhookUrl.label': '웹훅 URL',
+ 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...',
+ 'settings.webhookUrl.hint':
+ 'Discord, Slack 또는 사용자 지정 웹훅 URL을 입력하여 알림을 받으세요.',
+ 'settings.webhookUrl.saved': '웹훅 URL이 저장되었습니다',
+ 'settings.webhookUrl.test': '테스트',
+ 'settings.webhookUrl.testSuccess': '테스트 웹훅이 성공적으로 전송되었습니다',
+ 'settings.webhookUrl.testFailed': '테스트 웹훅 실패',
+ 'settings.ntfyUrl.topicLabel': 'Ntfy 토픽',
+ 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts',
+ 'settings.ntfyUrl.serverLabel': 'Ntfy 서버 URL (선택)',
+ 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh',
+ 'settings.ntfyUrl.hint':
+ 'ntfy 토픽을 입력하여 푸시 알림을 받으세요. 서버를 비워두면 관리자가 설정한 기본값을 사용합니다.',
+ 'settings.ntfyUrl.tokenLabel': '액세스 토큰 (선택)',
+ 'settings.ntfyUrl.tokenHint': '비밀번호로 보호된 토픽에 필요합니다.',
+ 'settings.ntfyUrl.saved': 'Ntfy 설정이 저장되었습니다',
+ 'settings.ntfyUrl.test': '테스트',
+ 'settings.ntfyUrl.testSuccess':
+ '테스트 ntfy 알림이 성공적으로 전송되었습니다',
+ 'settings.ntfyUrl.testFailed': '테스트 ntfy 알림 실패',
+ 'settings.ntfyUrl.tokenCleared': '액세스 토큰이 삭제되었습니다',
+ 'settings.notificationsDisabled':
+ '알림이 설정되지 않았습니다. 관리자에게 이메일 또는 웹훅 알림 활성화를 요청하세요.',
+ 'settings.notificationsActive': '활성 채널',
+ 'settings.notificationsManagedByAdmin': '알림 이벤트는 관리자가 설정합니다.',
+ 'settings.on': '켜기',
+ 'settings.off': '끄기',
+ 'settings.mcp.title': 'MCP 설정',
+ 'settings.mcp.endpoint': 'MCP 엔드포인트',
+ 'settings.mcp.clientConfig': '클라이언트 설정',
+ 'settings.mcp.clientConfigHint':
+ '을 아래 목록의 API 토큰으로 교체하세요. npx 경로는 시스템에 따라 조정해야 할 수 있습니다 (예: Windows의 경우 C:\\PROGRA~1\\nodejs\\npx.cmd).',
+ 'settings.mcp.clientConfigHintOAuth':
+ '와 을 위에서 생성한 OAuth 2.1 클라이언트 자격 증명으로 교체하세요. mcp-remote가 처음 연결 시 브라우저를 열어 인증을 완료합니다. npx 경로는 시스템에 따라 조정해야 할 수 있습니다 (예: Windows의 경우 C:\\PROGRA~1\\nodejs\\npx.cmd).',
+ 'settings.mcp.copy': '복사',
+ 'settings.mcp.copied': '복사됨!',
+ 'settings.mcp.apiTokens': 'API 토큰',
+ 'settings.mcp.createToken': '새 토큰 만들기',
+ 'settings.mcp.noTokens':
+ '토큰이 없습니다. MCP 클라이언트 연결을 위해 토큰을 만드세요.',
+ 'settings.mcp.tokenCreatedAt': '생성일',
+ 'settings.mcp.tokenUsedAt': '사용일',
+ 'settings.mcp.deleteTokenTitle': '토큰 삭제',
+ 'settings.mcp.deleteTokenMessage':
+ '이 토큰은 즉시 무효화됩니다. 이 토큰을 사용하는 MCP 클라이언트는 접근 권한을 잃게 됩니다.',
+ 'settings.mcp.modal.createTitle': 'API 토큰 만들기',
+ 'settings.mcp.modal.tokenName': '토큰 이름',
+ 'settings.mcp.modal.tokenNamePlaceholder':
+ '예: Claude Desktop, 업무용 노트북',
+ 'settings.mcp.modal.creating': '생성 중…',
+ 'settings.mcp.modal.create': '토큰 만들기',
+ 'settings.mcp.modal.createdTitle': '토큰이 생성되었습니다',
+ 'settings.mcp.modal.createdWarning':
+ '이 토큰은 한 번만 표시됩니다. 지금 복사하여 저장하세요 — 다시 복구할 수 없습니다.',
+ 'settings.mcp.modal.done': '완료',
+ 'settings.mcp.toast.created': '토큰이 생성되었습니다',
+ 'settings.mcp.toast.createError': '토큰 생성 실패',
+ 'settings.mcp.toast.deleted': '토큰이 삭제되었습니다',
+ 'settings.mcp.toast.deleteError': '토큰 삭제 실패',
+ 'settings.mcp.apiTokensDeprecated':
+ 'API 토큰은 더 이상 사용되지 않으며 향후 버전에서 제거될 예정입니다. 대신 OAuth 2.1 클라이언트를 사용하세요.',
+ 'settings.oauth.clients': 'OAuth 2.1 클라이언트',
+ 'settings.oauth.clientsHint':
+ 'OAuth 2.1 클라이언트를 등록하여 타사 MCP 앱 (Claude Web, Cursor 등)이 정적 토큰 없이 연결할 수 있도록 하세요.',
+ 'settings.oauth.createClient': '새 클라이언트',
+ 'settings.oauth.noClients': '등록된 OAuth 클라이언트가 없습니다.',
+ 'settings.oauth.clientId': '클라이언트 ID',
+ 'settings.oauth.clientSecret': '클라이언트 시크릿',
+ 'settings.oauth.deleteClient': '클라이언트 삭제',
+ 'settings.oauth.deleteClientMessage':
+ '이 클라이언트와 모든 활성 세션이 영구 삭제됩니다. 이 클라이언트를 사용하는 앱은 즉시 접근 권한을 잃게 됩니다.',
+ 'settings.oauth.rotateSecret': '시크릿 교체',
+ 'settings.oauth.rotateSecretMessage':
+ '새 클라이언트 시크릿이 생성되고 기존 세션은 즉시 무효화됩니다. 이 대화창을 닫기 전에 앱을 업데이트하세요.',
+ 'settings.oauth.rotateSecretConfirm': '교체',
+ 'settings.oauth.rotateSecretConfirming': '교체 중…',
+ 'settings.oauth.rotateSecretDoneTitle': '새 시크릿이 생성되었습니다',
+ 'settings.oauth.rotateSecretDoneWarning':
+ '이 시크릿은 한 번만 표시됩니다. 지금 복사하여 앱을 업데이트하세요 — 이전 세션은 모두 무효화되었습니다.',
+ 'settings.oauth.activeSessions': '활성 OAuth 세션',
+ 'settings.oauth.sessionScopes': '권한 범위',
+ 'settings.oauth.sessionExpires': '만료',
+ 'settings.oauth.revoke': '취소',
+ 'settings.oauth.revokeSession': '세션 취소',
+ 'settings.oauth.revokeSessionMessage':
+ '이 OAuth 세션의 접근 권한이 즉시 취소됩니다.',
+ 'settings.oauth.modal.createTitle': 'OAuth 클라이언트 등록',
+ 'settings.oauth.modal.presets': '빠른 프리셋',
+ 'settings.oauth.modal.clientName': '앱 이름',
+ 'settings.oauth.modal.clientNamePlaceholder': '예: Claude Web, My MCP App',
+ 'settings.oauth.modal.redirectUris': '리디렉션 URI',
+ 'settings.oauth.modal.redirectUrisPlaceholder':
+ 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint':
+ '한 줄에 URI 하나. HTTPS 필수 (localhost 예외). 정확히 일치해야 합니다.',
+ 'settings.oauth.modal.scopes': '허용 권한 범위',
+ 'settings.oauth.modal.scopesHint':
+ 'list_trips 및 get_trip_summary는 항상 사용 가능합니다 — 권한 범위 불필요. AI가 다른 도구를 사용하는 데 필요한 여행 ID를 찾을 수 있습니다.',
+ 'settings.oauth.modal.selectAll': '전체 선택',
+ 'settings.oauth.modal.deselectAll': '전체 해제',
+ 'settings.oauth.modal.creating': '등록 중…',
+ 'settings.oauth.modal.create': '클라이언트 등록',
+ 'settings.oauth.modal.createdTitle': '클라이언트가 등록되었습니다',
+ 'settings.oauth.modal.createdWarning':
+ '클라이언트 시크릿은 한 번만 표시됩니다. 지금 복사하세요 — 다시 복구할 수 없습니다.',
+ 'settings.oauth.toast.createError': 'OAuth 클라이언트 등록 실패',
+ 'settings.oauth.toast.deleted': 'OAuth 클라이언트가 삭제되었습니다',
+ 'settings.oauth.toast.deleteError': 'OAuth 클라이언트 삭제 실패',
+ 'settings.oauth.toast.revoked': '세션이 취소되었습니다',
+ 'settings.oauth.toast.revokeError': '세션 취소 실패',
+ 'settings.oauth.toast.rotateError': '클라이언트 시크릿 교체 실패',
+ 'settings.account': '계정',
+ 'settings.about': '정보',
+ 'settings.about.reportBug': '버그 신고',
+ 'settings.about.reportBugHint': '문제를 발견하셨나요? 알려주세요',
+ 'settings.about.featureRequest': '기능 요청',
+ 'settings.about.featureRequestHint': '새로운 기능을 제안하세요',
+ 'settings.about.wikiHint': '문서 및 가이드',
+ 'settings.about.supporters.badge': '월간 후원자',
+ 'settings.about.supporters.title': 'TREK의 여행 동반자',
+ 'settings.about.supporters.subtitle':
+ '다음 여정을 계획하는 동안, 이분들이 TREK의 미래를 함께 만들어가고 있습니다. 월간 후원금은 개발과 실제 작업 시간에 직접 사용되어 TREK이 오픈 소스로 유지될 수 있게 합니다.',
+ 'settings.about.supporters.since': '{date}부터 후원자',
+ 'settings.about.supporters.tierEmpty': '첫 번째 후원자가 되어보세요',
+ 'settings.about.supporter.tier.noReturnTicket': '편도 티켓',
+ 'settings.about.supporter.tier.lostLuggageVip': '분실 수하물 VIP',
+ 'settings.about.supporter.tier.businessClassDreamer':
+ '비즈니스 클래스 꿈꾸기',
+ 'settings.about.supporter.tier.budgetTraveller': '알뜰 여행자',
+ 'settings.about.supporter.tier.hostelBunkmate': '호스텔 룸메이트',
+ 'settings.about.description':
+ 'TREK은 첫 아이디어부터 마지막 추억까지 여행을 체계적으로 관리하는 자체 호스팅 여행 플래너입니다. 일별 계획, 예산, 짐 목록, 사진 등 모든 것이 하나의 서버에 담겨 있습니다.',
+ 'settings.about.madeWith': '으로 만들어졌습니다',
+ 'settings.about.madeBy': 'Maurice와 성장하는 오픈 소스 커뮤니티가 함께',
+ 'settings.username': '사용자 이름',
+ 'settings.email': '이메일',
+ 'settings.role': '역할',
+ 'settings.roleAdmin': '관리자',
+ 'settings.oidcLinked': '연결됨',
+ 'settings.changePassword': '비밀번호 변경',
+ 'settings.currentPassword': '현재 비밀번호',
+ 'settings.currentPasswordRequired': '현재 비밀번호를 입력하세요',
+ 'settings.newPassword': '새 비밀번호',
+ 'settings.confirmPassword': '새 비밀번호 확인',
+ 'settings.updatePassword': '비밀번호 업데이트',
+ 'settings.passwordRequired': '현재 비밀번호와 새 비밀번호를 입력하세요',
+ 'settings.passwordTooShort': '비밀번호는 최소 8자 이상이어야 합니다',
+ 'settings.passwordMismatch': '비밀번호가 일치하지 않습니다',
+ 'settings.passwordWeak':
+ '비밀번호는 대문자, 소문자, 숫자, 특수문자를 포함해야 합니다',
+ 'settings.passwordChanged': '비밀번호가 성공적으로 변경되었습니다',
+ 'settings.mustChangePassword':
+ '계속하기 전에 비밀번호를 변경해야 합니다. 아래에서 새 비밀번호를 설정하세요.',
+ 'settings.deleteAccount': '계정 삭제',
+ 'settings.deleteAccountTitle': '계정을 삭제할까요?',
+ 'settings.deleteAccountWarning':
+ '계정과 모든 여행, 장소, 파일이 영구 삭제됩니다. 이 작업은 취소할 수 없습니다.',
+ 'settings.deleteAccountConfirm': '영구 삭제',
+ 'settings.deleteBlockedTitle': '삭제 불가',
+ 'settings.deleteBlockedMessage':
+ '유일한 관리자입니다. 계정을 삭제하기 전에 다른 사용자를 관리자로 승격하세요.',
+ 'settings.roleUser': '사용자',
+ 'settings.saveProfile': '프로필 저장',
+ 'settings.toast.mapSaved': '지도 설정이 저장되었습니다',
+ 'settings.toast.keysSaved': 'API 키가 저장되었습니다',
+ 'settings.toast.displaySaved': '화면 설정이 저장되었습니다',
+ 'settings.toast.profileSaved': '프로필이 저장되었습니다',
+ 'settings.uploadAvatar': '프로필 사진 업로드',
+ 'settings.removeAvatar': '프로필 사진 삭제',
+ 'settings.avatarUploaded': '프로필 사진이 업데이트되었습니다',
+ 'settings.avatarRemoved': '프로필 사진이 삭제되었습니다',
+ 'settings.avatarError': '업로드 실패',
+ 'settings.mfa.title': '2단계 인증 (2FA)',
+ 'settings.mfa.description':
+ '이메일 및 비밀번호로 로그인할 때 두 번째 단계를 추가합니다. 인증 앱 (Google Authenticator, Authy 등)을 사용하세요.',
+ 'settings.mfa.requiredByPolicy':
+ '관리자가 2단계 인증을 요구합니다. 앱을 계속 사용하려면 아래에서 인증 앱을 설정하세요.',
+ 'settings.mfa.backupTitle': '백업 코드',
+ 'settings.mfa.backupDescription':
+ '인증 앱에 접근할 수 없을 때 이 일회용 백업 코드를 사용하세요.',
+ 'settings.mfa.backupWarning':
+ '지금 이 코드를 저장하세요. 각 코드는 한 번만 사용할 수 있습니다.',
+ 'settings.mfa.backupCopy': '코드 복사',
+ 'settings.mfa.backupDownload': 'TXT 다운로드',
+ 'settings.mfa.backupPrint': '인쇄 / PDF',
+ 'settings.mfa.backupCopied': '백업 코드가 복사되었습니다',
+ 'settings.mfa.enabled': '계정에 2FA가 활성화되어 있습니다.',
+ 'settings.mfa.disabled': '2FA가 활성화되지 않았습니다.',
+ 'settings.mfa.setup': '인증 앱 설정',
+ 'settings.mfa.scanQr':
+ '앱으로 이 QR 코드를 스캔하거나 시크릿을 수동으로 입력하세요.',
+ 'settings.mfa.secretLabel': '시크릿 키 (수동 입력)',
+ 'settings.mfa.codePlaceholder': '6자리 코드',
+ 'settings.mfa.enable': '2FA 활성화',
+ 'settings.mfa.cancelSetup': '취소',
+ 'settings.mfa.disableTitle': '2FA 비활성화',
+ 'settings.mfa.disableHint':
+ '계정 비밀번호와 인증 앱의 현재 코드를 입력하세요.',
+ 'settings.mfa.disable': '2FA 비활성화',
+ 'settings.mfa.toastEnabled': '2단계 인증이 활성화되었습니다',
+ 'settings.mfa.toastDisabled': '2단계 인증이 비활성화되었습니다',
+ 'settings.mfa.demoBlocked': '데모 모드에서는 사용할 수 없습니다',
+ 'settings.oauth.modal.machineClient': '머신 클라이언트(브라우저 로그인 없음)',
+ 'settings.oauth.modal.machineClientHint':
+ 'client_credentials 권한 부여를 사용합니다 — 리디렉션 URI가 필요하지 않습니다. 토큰은 client_id + client_secret을 통해 직접 발급되며 선택한 범위 내에서 사용자로 작동합니다.',
+ 'settings.oauth.modal.machineClientUsage':
+ '토큰 받기: grant_type=client_credentials, client_id, client_secret으로 POST /oauth/token을 호출하세요. 브라우저도 새로 고침 토큰도 필요 없습니다.',
+ 'settings.oauth.badge.machine': '머신',
+ "settings.currency": "Currency",
+ "settings.currencyHint": "All amounts in Costs are converted to and shown in this currency.",
+ 'settings.passkey.title': '패스키',
+ 'settings.passkey.description':
+ '지문, 얼굴, PIN 또는 하드웨어 키 같은 패스키로 더 빠르고 피싱에 강하게 로그인하세요. 비밀번호는 백업으로 그대로 유지됩니다.',
+ 'settings.passkey.notConfigured':
+ '패스키가 활성화되어 있지만 이 서버에 아직 완전히 설정되지 않았습니다. 관리자에게 WebAuthn 도메인 설정을 요청하세요.',
+ 'settings.passkey.add': '패스키 추가',
+ 'settings.passkey.addTitle': '패스키 추가',
+ 'settings.passkey.passwordPrompt':
+ '현재 비밀번호를 확인한 뒤 기기 안내에 따라 진행하세요.',
+ 'settings.passkey.passwordRequired': '현재 비밀번호가 필요합니다.',
+ 'settings.passkey.namePlaceholder': '이름 (선택, 예: "iPhone")',
+ 'settings.passkey.addedToast': '패스키가 추가되었습니다',
+ 'settings.passkey.added': '추가됨',
+ 'settings.passkey.addError': '패스키를 추가할 수 없습니다',
+ 'settings.passkey.cancelled': '패스키 설정이 취소되었습니다',
+ 'settings.passkey.deleted': '패스키가 삭제되었습니다',
+ 'settings.passkey.deleteConfirm':
+ '이 패스키를 삭제할까요? 비밀번호로 확인하세요.',
+ 'settings.passkey.rename': '이름 변경',
+ 'settings.passkey.defaultName': '패스키',
+ 'settings.passkey.synced': '동기화됨',
+ 'settings.passkey.deviceBound': '이 기기',
+ 'settings.passkey.lastUsed': '마지막 사용',
+ 'settings.passkey.neverUsed': '사용한 적 없음',
+ 'settings.mapPoiPill': '지도에서 장소 탐색',
+ 'settings.mapPoiPillHint': '여행 지도에 카테고리 칩을 표시하여 OpenStreetMap에서 주변 음식점, 숙소 등을 찾아보세요.',
+ 'settings.airtrail.title': 'AirTrail',
+ 'settings.airtrail.hint': '자체 호스팅한 AirTrail을 연결하여 항공편을 가져오고 동기화하세요. AirTrail의 설정 → 보안에서 API 키를 생성하세요.',
+ 'settings.airtrail.url': '인스턴스 URL',
+ 'settings.airtrail.apiKey': 'API 키',
+ 'settings.airtrail.apiKeyPlaceholder': 'Bearer API 키',
+ 'settings.airtrail.apiKeyHint': 'AirTrail의 설정 → 보안에서 생성됩니다. 암호화하여 저장됩니다.',
+ 'settings.airtrail.allowInsecureTls': '자체 서명 인증서 허용',
+ 'settings.airtrail.allowInsecureTlsHint': '자체 네트워크의 신뢰할 수 있는 인스턴스에서만 활성화하세요.',
+ 'settings.airtrail.connected': '연결됨',
+ 'settings.airtrail.notConnected': '연결되지 않음',
+ 'settings.airtrail.toast.saved': 'AirTrail 연결이 저장되었습니다',
+ 'settings.airtrail.toast.saveError': '연결을 저장할 수 없습니다',
+ 'settings.airtrail.test.button': '연결 테스트',
+ 'settings.airtrail.test.success': '연결됨 — {count}개 항공편을 찾았습니다',
+ 'settings.airtrail.test.failed': '연결에 실패했습니다',
+};
+
+export default settings;
diff --git a/shared/src/i18n/ko/share.ts b/shared/src/i18n/ko/share.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3ff30d2dbde634912932b66bce168d96aa048cde
--- /dev/null
+++ b/shared/src/i18n/ko/share.ts
@@ -0,0 +1,16 @@
+import type { TranslationStrings } from '../types';
+
+const share: TranslationStrings = {
+ 'share.linkTitle': '공개 링크',
+ 'share.linkHint':
+ '로그인 없이 이 여행을 볼 수 있는 링크를 만드세요. 읽기 전용 — 편집 불가.',
+ 'share.createLink': '링크 만들기',
+ 'share.deleteLink': '링크 삭제',
+ 'share.createError': '링크 생성 실패',
+ 'share.permMap': '지도 및 계획',
+ 'share.permBookings': '예약',
+ 'share.permPacking': '짐 목록',
+ 'share.permBudget': '예산',
+ 'share.permCollab': '채팅',
+};
+export default share;
diff --git a/shared/src/i18n/ko/shared.ts b/shared/src/i18n/ko/shared.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dec8a1951eea68da0c89c040db34e4ad9ec13526
--- /dev/null
+++ b/shared/src/i18n/ko/shared.ts
@@ -0,0 +1,21 @@
+import type { TranslationStrings } from '../types';
+
+const shared: TranslationStrings = {
+ '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': '대기 중',
+};
+export default shared;
diff --git a/shared/src/i18n/ko/stats.ts b/shared/src/i18n/ko/stats.ts
new file mode 100644
index 0000000000000000000000000000000000000000..682197099a8509fa46977b8083f71080c5c45c84
--- /dev/null
+++ b/shared/src/i18n/ko/stats.ts
@@ -0,0 +1,13 @@
+import type { TranslationStrings } from '../types';
+
+const stats: TranslationStrings = {
+ 'stats.countries': '국가',
+ 'stats.cities': '도시',
+ 'stats.trips': '여행',
+ 'stats.places': '장소',
+ 'stats.worldProgress': '세계 진행도',
+ 'stats.visited': '방문함',
+ 'stats.remaining': '남음',
+ 'stats.visitedCountries': '방문한 나라',
+};
+export default stats;
diff --git a/shared/src/i18n/ko/system_notice.ts b/shared/src/i18n/ko/system_notice.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bf85cda80940580245d8b86ed41ee78a95b478ed
--- /dev/null
+++ b/shared/src/i18n/ko/system_notice.ts
@@ -0,0 +1,56 @@
+import type { TranslationStrings } from '../types';
+
+const system_notice: TranslationStrings = {
+ 'system_notice.v3_photos.title': '3.0에서 사진이 이동했습니다',
+ 'system_notice.v3_photos.body':
+ '여행 플래너의 **사진** 기능이 제거되었습니다. 사진은 안전합니다 — TREK은 Immich 또는 Synology 라이브러리를 수정하지 않았습니다.\n\n사진은 이제 **Journey** 애드온에 있습니다. Journey는 선택 사항입니다 — 아직 사용할 수 없다면 관리자에게 관리자 → 애드온에서 활성화를 요청하세요.',
+ 'system_notice.v3_journey.title': 'Journey를 만나보세요 — 여행 일지',
+ 'system_notice.v3_journey.body':
+ '타임라인, 사진 갤러리, 인터랙티브 지도가 있는 풍부한 여행 이야기로 여행을 기록하세요.',
+ 'system_notice.v3_journey.cta_label': 'Journey 열기',
+ 'system_notice.v3_journey.highlight_timeline': '일별 타임라인 및 갤러리',
+ 'system_notice.v3_journey.highlight_photos':
+ 'Immich 또는 Synology에서 가져오기',
+ 'system_notice.v3_journey.highlight_share': '공개 공유 — 로그인 불필요',
+ 'system_notice.v3_journey.highlight_export': 'PDF 사진 책으로 내보내기',
+ 'system_notice.v3_features.title': '3.0의 더 많은 하이라이트',
+ 'system_notice.v3_features.body': '이번 릴리스에서 알아두면 좋은 몇 가지 더.',
+ 'system_notice.v3_features.highlight_dashboard':
+ '모바일 우선 대시보드 재설계',
+ 'system_notice.v3_features.highlight_offline': 'PWA로 완전한 오프라인 모드',
+ 'system_notice.v3_features.highlight_search': '실시간 장소 검색 자동완성',
+ 'system_notice.v3_features.highlight_import':
+ 'KMZ/KML 파일에서 장소 가져오기',
+ 'system_notice.v3_mcp.title': 'MCP: OAuth 2.1 업그레이드',
+ 'system_notice.v3_mcp.body':
+ 'MCP 통합이 완전히 개선되었습니다. OAuth 2.1이 이제 권장 인증 방법입니다. 기존 정적 토큰 (trek_…)은 더 이상 사용되지 않으며 향후 릴리스에서 제거될 예정입니다.',
+ 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 권장 (mcp-remote)',
+ 'system_notice.v3_mcp.highlight_scopes': '24개 세분화된 권한 범위',
+ 'system_notice.v3_mcp.highlight_deprecated':
+ '정적 trek_ 토큰 더 이상 사용 안 됨',
+ 'system_notice.v3_mcp.highlight_tools': '확장된 도구 모음 및 프롬프트',
+ 'system_notice.v3_thankyou.title': '개인적인 감사 인사',
+ 'system_notice.v3_thankyou.body':
+ '떠나시기 전에 잠깐 시간을 내주세요.\n\nTREK은 제 자신의 여행을 위해 만든 사이드 프로젝트로 시작했습니다. 4,000명이 넘는 분들이 모험을 계획하는 데 신뢰해 주실 줄은 상상도 못 했습니다. 모든 별, 모든 이슈, 모든 기능 요청 — 저는 다 읽고, 그것들이 풀타임 직장과 대학 사이의 늦은 밤을 버티게 해줍니다.\n\n알아주셨으면 합니다: TREK은 항상 오픈 소스이고, 항상 자체 호스팅이며, 항상 여러분의 것입니다. 추적 없음, 구독 없음, 조건 없음. 그저 여러분만큼 여행을 사랑하는 누군가가 만든 도구입니다.\n\n[jubnl](https://github.com/jubnl)에게 특별한 감사를. 당신은 훌륭한 협력자가 되었습니다. 3.0을 훌륭하게 만든 많은 부분에 당신의 손길이 담겨 있습니다. 거칠던 초기에 이 프로젝트를 믿어줘서 고맙습니다.\n\n그리고 버그를 제출하고, 문자열을 번역하고, TREK을 친구에게 공유하거나, 단순히 여행 계획에 사용해 주신 모든 분들께 — **감사합니다**. 여러분이 바로 이것이 존재하는 이유입니다.\n\n함께하는 더 많은 모험을 위해.\n\n— Maurice\n\n---\n\n[Discord 커뮤니티에 참여하세요](https://discord.gg/7Q6M6jDwzf)\n\nTREK이 여행을 더 즐겁게 만들어 준다면, [커피 한 잔](https://ko-fi.com/mauriceboe)으로 불을 켜두는 데 도움이 됩니다.',
+ 'system_notice.v3014_whitespace_collision.title':
+ '조치 필요: 사용자 계정 충돌',
+ 'system_notice.v3014_whitespace_collision.body':
+ '3.0.14 업그레이드 중 저장된 계정의 앞뒤 공백으로 인한 사용자 이름 또는 이메일 충돌이 감지되었습니다. 영향받은 계정은 자동으로 이름이 변경되었습니다. 검토가 필요한 계정을 확인하려면 **[migration] WHITESPACE COLLISION**으로 시작하는 줄의 서버 로그를 확인하세요.',
+ 'system_notice.welcome_v1.title': 'TREK에 오신 것을 환영합니다',
+ 'system_notice.welcome_v1.body':
+ '올인원 여행 플래너. 일정을 만들고, 친구들과 여행을 공유하고, 온라인 또는 오프라인으로 체계적으로 유지하세요.',
+ 'system_notice.welcome_v1.cta_label': '여행 계획',
+ 'system_notice.welcome_v1.hero_alt':
+ 'TREK 계획 UI 오버레이가 있는 아름다운 여행지',
+ 'system_notice.welcome_v1.highlight_plan': '모든 여행을 위한 일별 일정',
+ 'system_notice.welcome_v1.highlight_share': '여행 파트너와 협업',
+ 'system_notice.welcome_v1.highlight_offline': '모바일에서 오프라인으로 작동',
+ 'system_notice.dev_test_modal.title': '[Dev] 테스트 공지',
+ 'system_notice.dev_test_modal.body': '개발 전용 테스트 공지입니다.',
+ 'system_notice.pager.prev': '이전 공지',
+ 'system_notice.pager.next': '다음 공지',
+ 'system_notice.pager.counter': '{current} / {total}',
+ 'system_notice.pager.goto': '{n}번 공지로 이동',
+ 'system_notice.pager.position': '공지 {current}/{total}',
+};
+export default system_notice;
diff --git a/shared/src/i18n/ko/todo.ts b/shared/src/i18n/ko/todo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fefcb183f6c76593f90dc56e89b5824e72ecc884
--- /dev/null
+++ b/shared/src/i18n/ko/todo.ts
@@ -0,0 +1,40 @@
+import type { TranslationStrings } from '../types';
+
+const todo: TranslationStrings = {
+ 'todo.subtab.packing': '짐 목록',
+ 'todo.subtab.todo': '할 일',
+ 'todo.completed': '완료됨',
+ 'todo.filter.all': '전체',
+ 'todo.filter.open': '미완료',
+ 'todo.filter.done': '완료',
+ 'todo.uncategorized': '미분류',
+ 'todo.namePlaceholder': '작업 이름',
+ 'todo.descriptionPlaceholder': '설명 (선택)',
+ 'todo.unassigned': '미배정',
+ 'todo.noCategory': '카테고리 없음',
+ 'todo.hasDescription': '설명 있음',
+ 'todo.addItem': '새 작업 추가',
+ 'todo.sidebar.sortBy': '정렬 기준',
+ 'todo.priority': '우선순위',
+ 'todo.newCategoryLabel': '새로 만들기',
+ 'todo.newCategory': '카테고리 이름',
+ 'todo.addCategory': '카테고리 추가',
+ 'todo.newItem': '새 작업',
+ 'todo.empty': '아직 작업이 없습니다. 작업을 추가하여 시작하세요!',
+ 'todo.filter.my': '내 작업',
+ 'todo.filter.overdue': '기한 초과',
+ 'todo.sidebar.tasks': '작업',
+ 'todo.sidebar.categories': '카테고리',
+ 'todo.detail.title': '작업',
+ 'todo.detail.description': '설명',
+ 'todo.detail.category': '카테고리',
+ 'todo.detail.dueDate': '마감일',
+ 'todo.detail.assignedTo': '배정 대상',
+ 'todo.detail.delete': '삭제',
+ 'todo.detail.save': '변경 사항 저장',
+ 'todo.sortByPrio': '우선순위',
+ 'todo.detail.priority': '우선순위',
+ 'todo.detail.noPriority': '없음',
+ 'todo.detail.create': '작업 만들기',
+};
+export default todo;
diff --git a/shared/src/i18n/ko/transport.ts b/shared/src/i18n/ko/transport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..62b7c8479727fe3b4972cd1e5705db20a0261589
--- /dev/null
+++ b/shared/src/i18n/ko/transport.ts
@@ -0,0 +1,10 @@
+import type { TranslationStrings } from '../types';
+
+const transport: TranslationStrings = {
+ 'transport.addTransport': '교통 추가',
+ 'transport.modalTitle.create': '교통 추가',
+ 'transport.modalTitle.edit': '교통 편집',
+ 'transport.title': '교통',
+ 'transport.addManual': '직접 교통 입력',
+};
+export default transport;
diff --git a/shared/src/i18n/ko/trip.ts b/shared/src/i18n/ko/trip.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6f20b2690eec60074fd9c3fdb2399764d120b1e5
--- /dev/null
+++ b/shared/src/i18n/ko/trip.ts
@@ -0,0 +1,31 @@
+import type { TranslationStrings } from '../types';
+
+const trip: TranslationStrings = {
+ 'trip.tabs.plan': '계획',
+ 'trip.tabs.transports': '교통',
+ 'trip.tabs.reservations': '예약',
+ 'trip.tabs.reservationsShort': '예약',
+ 'trip.tabs.packing': '짐 목록',
+ 'trip.tabs.packingShort': '짐',
+ 'trip.tabs.lists': '목록',
+ 'trip.tabs.listsShort': '목록',
+ 'trip.tabs.budget': "Costs",
+ 'trip.tabs.files': '파일',
+ 'trip.loading': '여행 불러오는 중...',
+ 'trip.loadingPhotos': '장소 사진 불러오는 중...',
+ 'trip.mobilePlan': '계획',
+ 'trip.mobilePlaces': '장소',
+ 'trip.toast.placeUpdated': '장소가 업데이트되었습니다',
+ 'trip.toast.placeAdded': '장소가 추가되었습니다',
+ 'trip.toast.placeDeleted': '장소가 삭제되었습니다',
+ 'trip.toast.selectDay': '먼저 날을 선택하세요',
+ 'trip.toast.assignedToDay': '장소가 날에 배정되었습니다',
+ 'trip.toast.reorderError': '순서 변경 실패',
+ 'trip.toast.reservationUpdated': '예약이 업데이트되었습니다',
+ 'trip.toast.reservationAdded': '예약이 추가되었습니다',
+ 'trip.toast.deleted': '삭제됨',
+ 'trip.confirm.deletePlace': '이 장소를 삭제할까요?',
+ 'trip.confirm.deletePlaces': '{count}개 장소를 삭제할까요?',
+ 'trip.toast.placesDeleted': '{count}개 장소가 삭제되었습니다',
+};
+export default trip;
diff --git a/shared/src/i18n/ko/trips.ts b/shared/src/i18n/ko/trips.ts
new file mode 100644
index 0000000000000000000000000000000000000000..39fb44551c4b3cc43332a8269434c892153c13f2
--- /dev/null
+++ b/shared/src/i18n/ko/trips.ts
@@ -0,0 +1,17 @@
+import type { TranslationStrings } from '../types';
+
+const trips: TranslationStrings = {
+ 'trips.memberRemoved': '{username} 제거됨',
+ 'trips.memberRemoveError': '제거 실패',
+ 'trips.memberAdded': '{username} 추가됨',
+ 'trips.memberAddError': '추가 실패',
+ 'trips.reminder': '리마인더',
+ 'trips.reminderNone': '없음',
+ 'trips.reminderDay': '일',
+ 'trips.reminderDays': '일',
+ 'trips.reminderCustom': '직접 설정',
+ 'trips.reminderDaysBefore': '일 전 출발',
+ 'trips.reminderDisabledHint':
+ '여행 리마인더가 비활성화되어 있습니다. 관리자 > 설정 > 알림에서 활성화하세요.',
+};
+export default trips;
diff --git a/shared/src/i18n/ko/undo.ts b/shared/src/i18n/ko/undo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dfe5cacbf08336201c9d010cef85540ec894c8d1
--- /dev/null
+++ b/shared/src/i18n/ko/undo.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const undo: TranslationStrings = {
+ 'undo.button': '실행 취소',
+ 'undo.tooltip': '실행 취소: {action}',
+ 'undo.assignPlace': '장소가 날에 배정되었습니다',
+ 'undo.removeAssignment': '장소가 날에서 제거되었습니다',
+ 'undo.reorder': '장소 순서가 변경되었습니다',
+ 'undo.optimize': '경로가 최적화되었습니다',
+ 'undo.deletePlace': '장소가 삭제되었습니다',
+ 'undo.deletePlaces': '장소들이 삭제되었습니다',
+ 'undo.moveDay': '장소가 다른 날로 이동되었습니다',
+ 'undo.lock': '장소 잠금이 변경되었습니다',
+ 'undo.importGpx': 'GPX 가져오기',
+ 'undo.importKeyholeMarkup': 'KMZ/KML 가져오기',
+ 'undo.importGoogleList': 'Google Maps 가져오기',
+ 'undo.importNaverList': '네이버 지도 가져오기',
+ 'undo.addPlace': '장소가 추가되었습니다',
+ 'undo.done': '실행 취소됨: {action}',
+ 'undo.importBooking': '예약 확인서 가져오기',
+};
+export default undo;
diff --git a/shared/src/i18n/ko/vacay.ts b/shared/src/i18n/ko/vacay.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0ac03992d5e6de149b2453a7ac01e449bef485c7
--- /dev/null
+++ b/shared/src/i18n/ko/vacay.ts
@@ -0,0 +1,99 @@
+import type { TranslationStrings } from '../types';
+
+const vacay: TranslationStrings = {
+ 'vacay.subtitle': '휴가 일수를 계획하고 관리하세요',
+ 'vacay.settings': '설정',
+ 'vacay.year': '연도',
+ 'vacay.addYear': '다음 연도 추가',
+ 'vacay.addPrevYear': '이전 연도 추가',
+ 'vacay.removeYear': '연도 제거',
+ 'vacay.removeYearConfirm': '{year}을(를) 제거할까요?',
+ 'vacay.removeYearHint':
+ '이 연도의 모든 휴가 항목 및 회사 공휴일이 영구 삭제됩니다.',
+ 'vacay.remove': '제거',
+ 'vacay.persons': '인원',
+ 'vacay.noPersons': '추가된 인원이 없습니다',
+ 'vacay.addPerson': '인원 추가',
+ 'vacay.editPerson': '인원 편집',
+ 'vacay.removePerson': '인원 제거',
+ 'vacay.removePersonConfirm': '{name}을(를) 제거할까요?',
+ 'vacay.removePersonHint': '이 인원의 모든 휴가 항목이 영구 삭제됩니다.',
+ 'vacay.personName': '이름',
+ 'vacay.personNamePlaceholder': '이름 입력',
+ 'vacay.color': '색상',
+ 'vacay.add': '추가',
+ 'vacay.legend': '범례',
+ 'vacay.publicHoliday': '공휴일',
+ 'vacay.companyHoliday': '회사 휴일',
+ 'vacay.weekend': '주말',
+ 'vacay.modeVacation': '휴가',
+ 'vacay.modeCompany': '회사 휴일',
+ 'vacay.entitlement': '휴가 일수',
+ 'vacay.entitlementDays': '일',
+ 'vacay.used': '사용',
+ 'vacay.remaining': '남음',
+ 'vacay.carriedOver': '{year}에서 이월',
+ 'vacay.blockWeekends': '주말 차단',
+ 'vacay.blockWeekendsHint': '주말에 휴가 항목 추가를 방지합니다',
+ 'vacay.weekendDays': '주말 요일',
+ 'vacay.mon': '월',
+ 'vacay.tue': '화',
+ 'vacay.wed': '수',
+ 'vacay.thu': '목',
+ 'vacay.fri': '금',
+ 'vacay.sat': '토',
+ 'vacay.sun': '일',
+ 'vacay.publicHolidays': '공휴일',
+ 'vacay.publicHolidaysHint': '캘린더에 공휴일을 표시합니다',
+ 'vacay.selectCountry': '국가 선택',
+ 'vacay.selectRegion': '지역 선택 (선택)',
+ 'vacay.addCalendar': '캘린더 추가',
+ 'vacay.calendarLabel': '레이블 (선택)',
+ 'vacay.calendarColor': '색상',
+ 'vacay.noCalendars': '아직 공휴일 캘린더가 없습니다',
+ 'vacay.companyHolidays': '회사 휴일',
+ 'vacay.companyHolidaysHint': '회사 전체 휴일 표시를 허용합니다',
+ 'vacay.companyHolidaysNoDeduct':
+ '회사 휴일은 휴가 일수에서 차감되지 않습니다.',
+ 'vacay.weekStart': '주 시작 요일',
+ 'vacay.weekStartHint': '캘린더 주가 월요일 또는 일요일에 시작할지 선택하세요',
+ 'vacay.carryOver': '이월',
+ 'vacay.carryOverHint': '남은 휴가 일수를 다음 연도로 자동 이월합니다',
+ 'vacay.sharing': '공유',
+ 'vacay.sharingHint': '다른 TREK 사용자와 휴가 계획을 공유합니다',
+ 'vacay.owner': '소유자',
+ 'vacay.shareEmailPlaceholder': 'TREK 사용자 이메일',
+ 'vacay.shareSuccess': '계획이 성공적으로 공유되었습니다',
+ 'vacay.shareError': '계획 공유 실패',
+ 'vacay.dissolve': '퓨전 해제',
+ 'vacay.dissolveHint': '캘린더를 다시 분리합니다. 항목은 유지됩니다.',
+ 'vacay.dissolveAction': '해제',
+ 'vacay.dissolved': '캘린더가 분리되었습니다',
+ 'vacay.fusedWith': '퓨전됨',
+ 'vacay.you': '나',
+ 'vacay.noData': '데이터 없음',
+ 'vacay.changeColor': '색상 변경',
+ 'vacay.inviteUser': '사용자 초대',
+ 'vacay.inviteHint':
+ '다른 TREK 사용자를 초대하여 통합 휴가 캘린더를 공유하세요.',
+ 'vacay.selectUser': '사용자 선택',
+ 'vacay.sendInvite': '초대 전송',
+ 'vacay.inviteSent': '초대가 전송되었습니다',
+ 'vacay.inviteError': '초대 전송 실패',
+ 'vacay.pending': '대기 중',
+ 'vacay.noUsersAvailable': '사용 가능한 사용자가 없습니다',
+ 'vacay.accept': '수락',
+ 'vacay.decline': '거절',
+ 'vacay.acceptFusion': '수락 및 퓨전',
+ 'vacay.inviteTitle': '퓨전 요청',
+ 'vacay.inviteWantsToFuse': '귀하와 휴가 캘린더를 공유하고 싶어 합니다.',
+ 'vacay.fuseInfo1':
+ '두 사람 모두 하나의 공유 캘린더에서 모든 휴가 항목을 볼 수 있습니다.',
+ 'vacay.fuseInfo2': '양측 모두 서로의 항목을 만들고 편집할 수 있습니다.',
+ 'vacay.fuseInfo3':
+ '양측 모두 항목을 삭제하고 휴가 일수를 변경할 수 있습니다.',
+ 'vacay.fuseInfo4': '공휴일 및 회사 휴일 등의 설정이 공유됩니다.',
+ 'vacay.fuseInfo5':
+ '어느 쪽이든 언제든지 퓨전을 해제할 수 있습니다. 항목은 보존됩니다.',
+};
+export default vacay;
diff --git a/shared/src/i18n/languages.ts b/shared/src/i18n/languages.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b55d6d148b9766b8556bae17bdfc3412900555ff
--- /dev/null
+++ b/shared/src/i18n/languages.ts
@@ -0,0 +1,50 @@
+export const SUPPORTED_LANGUAGES = [
+ { value: 'de', label: 'Deutsch', locale: 'de-DE' },
+ { value: 'en', label: 'English', locale: 'en-US' },
+ { value: 'es', label: 'Español', locale: 'es-ES' },
+ { value: 'fr', label: 'Français', locale: 'fr-FR' },
+ { value: 'hu', label: 'Magyar', locale: 'hu-HU' },
+ { value: 'nl', label: 'Nederlands', locale: 'nl-NL' },
+ { value: 'br', label: 'Português (Brasil)', locale: 'pt-BR' },
+ { value: 'cs', label: 'Česky', locale: 'cs-CZ' },
+ { value: 'pl', label: 'Polski', locale: 'pl-PL' },
+ { value: 'ru', label: 'Русский', locale: 'ru-RU' },
+ { value: 'zh', label: '简体中文', locale: 'zh-CN' },
+ { value: 'zh-TW', label: '繁體中文', locale: 'zh-TW' },
+ { value: 'it', label: 'Italiano', locale: 'it-IT' },
+ { value: 'tr', label: 'Türkçe', locale: 'tr-TR' },
+ { value: 'ar', label: 'العربية', locale: 'ar-SA' },
+ { value: 'id', label: 'Bahasa Indonesia', locale: 'id-ID' },
+ { value: 'ja', label: '日本語', locale: 'ja-JP' },
+ { value: 'ko', label: '한국어', locale: 'ko-KR' },
+ { value: 'uk', label: 'Українська', locale: 'uk-UA' },
+ { value: 'gr', label: 'Ελληνικά', locale: 'el-GR' },
+] as const;
+
+export type SupportedLanguageCode =
+ (typeof SUPPORTED_LANGUAGES)[number]['value'];
+
+export const SUPPORTED_LANGUAGE_CODES: string[] = SUPPORTED_LANGUAGES.map(
+ (l) => l.value,
+);
+
+const LOCALES: Partial> = Object.fromEntries(
+ SUPPORTED_LANGUAGES.map((l) => [l.value, l.locale]),
+);
+
+// Languages displayed right-to-left.
+const RTL_LANGUAGES = new Set(['ar']);
+
+export function getLocaleForLanguage(language: string): string {
+ return LOCALES[language] ?? LOCALES['en'] ?? 'en-US';
+}
+
+// Returns a BCP-47 tag suitable for Intl APIs.
+export function getIntlLanguage(language: string): string {
+ if (language === 'br') return 'pt-BR';
+ return SUPPORTED_LANGUAGE_CODES.includes(language) ? language : 'en';
+}
+
+export function isRtlLanguage(language: string): boolean {
+ return RTL_LANGUAGES.has(language);
+}
diff --git a/shared/src/i18n/nl/admin.ts b/shared/src/i18n/nl/admin.ts
new file mode 100644
index 0000000000000000000000000000000000000000..879ff467b32708c2afae93379f10238f1cfb4235
--- /dev/null
+++ b/shared/src/i18n/nl/admin.ts
@@ -0,0 +1,393 @@
+import type { TranslationStrings } from '../types';
+
+const admin: TranslationStrings = {
+ 'admin.notifications.title': 'Meldingen',
+ 'admin.notifications.hint':
+ 'Kies een meldingskanaal. Er kan er slechts één tegelijk actief zijn.',
+ 'admin.notifications.none': 'Uitgeschakeld',
+ 'admin.notifications.email': 'E-mail (SMTP)',
+ 'admin.notifications.webhook': 'Webhook',
+ 'admin.notifications.save': 'Meldingsinstellingen opslaan',
+ 'admin.notifications.saved': 'Meldingsinstellingen opgeslagen',
+ 'admin.notifications.testWebhook': 'Testwebhook verzenden',
+ 'admin.notifications.testWebhookSuccess': 'Testwebhook succesvol verzonden',
+ 'admin.notifications.testWebhookFailed': 'Testwebhook mislukt',
+ 'admin.smtp.title': 'E-mail en meldingen',
+ 'admin.smtp.hint':
+ 'SMTP-configuratie voor het verzenden van e-mailmeldingen.',
+ 'admin.smtp.testButton': 'Test-e-mail verzenden',
+ 'admin.webhook.hint':
+ 'Meldingen verzenden naar een externe webhook (Discord, Slack, enz.).',
+ 'admin.smtp.testSuccess': 'Test-e-mail succesvol verzonden',
+ 'admin.smtp.testFailed': 'Test-e-mail mislukt',
+ 'admin.title': 'Beheer',
+ 'admin.subtitle': 'Gebruikersbeheer en systeeminstellingen',
+ 'admin.tabs.users': 'Gebruikers',
+ 'admin.tabs.categories': 'Categorieën',
+ 'admin.tabs.backup': 'Back-up',
+ 'admin.tabs.audit': 'Audit',
+ 'admin.stats.users': 'Gebruikers',
+ 'admin.stats.trips': 'Reizen',
+ 'admin.stats.places': 'Plaatsen',
+ 'admin.stats.photos': "Foto's",
+ 'admin.stats.files': 'Bestanden',
+ 'admin.table.user': 'Gebruiker',
+ 'admin.table.email': 'E-mail',
+ 'admin.table.role': 'Rol',
+ 'admin.table.created': 'Aangemaakt',
+ 'admin.table.lastLogin': 'Laatste login',
+ 'admin.table.actions': 'Acties',
+ 'admin.you': '(Jij)',
+ 'admin.editUser': 'Gebruiker bewerken',
+ 'admin.newPassword': 'Nieuw wachtwoord',
+ 'admin.newPasswordHint': 'Laat leeg om het huidige wachtwoord te behouden',
+ 'admin.deleteUser':
+ 'Gebruiker "{name}" verwijderen? Alle reizen worden permanent verwijderd.',
+ 'admin.deleteUserTitle': 'Gebruiker verwijderen',
+ 'admin.newPasswordPlaceholder': 'Nieuw wachtwoord invoeren…',
+ 'admin.toast.loadError': 'Beheergegevens laden mislukt',
+ 'admin.toast.userUpdated': 'Gebruiker bijgewerkt',
+ 'admin.toast.updateError': 'Bijwerken mislukt',
+ 'admin.toast.userDeleted': 'Gebruiker verwijderd',
+ 'admin.toast.deleteError': 'Verwijderen mislukt',
+ 'admin.toast.cannotDeleteSelf': 'Je kunt je eigen account niet verwijderen',
+ 'admin.toast.userCreated': 'Gebruiker aangemaakt',
+ 'admin.toast.createError': 'Gebruiker aanmaken mislukt',
+ 'admin.toast.fieldsRequired':
+ 'Gebruikersnaam, e-mail en wachtwoord zijn verplicht',
+ 'admin.createUser': 'Gebruiker aanmaken',
+ 'admin.invite.title': 'Uitnodigingslinks',
+ 'admin.invite.subtitle': 'Eenmalige registratielinks aanmaken',
+ 'admin.invite.create': 'Link aanmaken',
+ 'admin.invite.createAndCopy': 'Aanmaken en kopiëren',
+ 'admin.invite.empty': 'Nog geen uitnodigingslinks aangemaakt',
+ 'admin.invite.maxUses': 'Max. gebruik',
+ 'admin.invite.expiry': 'Verloopt na',
+ 'admin.invite.uses': 'gebruikt',
+ 'admin.invite.expiresAt': 'verloopt op',
+ 'admin.invite.createdBy': 'door',
+ 'admin.invite.active': 'Actief',
+ 'admin.invite.expired': 'Verlopen',
+ 'admin.invite.usedUp': 'Opgebruikt',
+ 'admin.invite.copied': 'Uitnodigingslink gekopieerd',
+ 'admin.invite.copyLink': 'Link kopiëren',
+ 'admin.invite.deleted': 'Uitnodigingslink verwijderd',
+ 'admin.invite.createError': 'Fout bij aanmaken van link',
+ 'admin.invite.deleteError': 'Fout bij verwijderen van link',
+ 'admin.tabs.settings': 'Instellingen',
+ 'admin.allowRegistration': 'Registratie toestaan',
+ 'admin.allowRegistrationHint':
+ 'Nieuwe gebruikers kunnen zichzelf registreren',
+ 'admin.authMethods': 'Authentication Methods',
+ 'admin.passwordLogin': 'Password Login',
+ 'admin.passwordLoginHint': 'Allow users to sign in with email and password',
+ 'admin.passwordRegistration': 'Password Registration',
+ 'admin.passwordRegistrationHint':
+ 'Allow new users to register with email and password',
+ 'admin.oidcLogin': 'SSO Login',
+ 'admin.oidcLoginHint': 'Allow users to sign in with SSO',
+ 'admin.oidcRegistration': 'SSO Auto-Provisioning',
+ 'admin.oidcRegistrationHint':
+ 'Automatically create accounts for new SSO users',
+ 'admin.envOverrideHint':
+ 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.',
+ 'admin.lockoutWarning': 'At least one login method must remain enabled',
+ 'admin.requireMfa': 'Tweestapsverificatie (2FA) verplicht stellen',
+ 'admin.requireMfaHint':
+ 'Gebruikers zonder 2FA moeten de installatie in Instellingen voltooien voordat ze de app kunnen gebruiken.',
+ 'admin.apiKeys': 'API-sleutels',
+ 'admin.apiKeysHint':
+ "Optioneel. Schakelt uitgebreide plaatsgegevens in zoals foto's en weer.",
+ 'admin.mapsKey': 'Google Maps API-sleutel',
+ 'admin.mapsKeyHint':
+ 'Vereist voor het zoeken van plaatsen. Verkrijgbaar op console.cloud.google.com',
+ 'admin.mapsKeyHintLong':
+ "Zonder API-sleutel wordt OpenStreetMap gebruikt voor het zoeken van plaatsen. Met een Google API-sleutel kunnen ook foto's, beoordelingen en openingstijden worden geladen. Verkrijgbaar op console.cloud.google.com.",
+ 'admin.recommended': 'Aanbevolen',
+ 'admin.weatherKey': 'OpenWeatherMap API-sleutel',
+ 'admin.weatherKeyHint': 'Voor weergegevens. Gratis op openweathermap.org',
+ 'admin.validateKey': 'Testen',
+ 'admin.keyValid': 'Verbonden',
+ 'admin.keyInvalid': 'Ongeldig',
+ 'admin.keySaved': 'API-sleutels opgeslagen',
+ 'admin.oidcTitle': 'Single Sign-On (OIDC)',
+ 'admin.oidcSubtitle':
+ 'Sta inloggen toe via externe providers zoals Google, Apple, Authentik of Keycloak.',
+ 'admin.oidcDisplayName': 'Weergavenaam',
+ 'admin.oidcIssuer': 'Issuer-URL',
+ 'admin.oidcIssuerHint':
+ 'De OpenID Connect Issuer-URL van de provider. Bijv. https://accounts.google.com',
+ 'admin.oidcSaved': 'OIDC-configuratie opgeslagen',
+ 'admin.oidcOnlyMode': 'Wachtwoordauthenticatie uitschakelen',
+ 'admin.oidcOnlyModeHint':
+ 'Indien ingeschakeld, is alleen SSO-login toegestaan. Inloggen en registreren met wachtwoord worden geblokkeerd.',
+ 'admin.fileTypes': 'Toegestane bestandstypen',
+ 'admin.fileTypesHint':
+ 'Configureer welke bestandstypen gebruikers kunnen uploaden.',
+ 'admin.fileTypesFormat':
+ 'Kommagescheiden extensies (bijv. jpg,png,pdf,doc). Gebruik * om alle typen toe te staan.',
+ 'admin.fileTypesSaved': 'Bestandstype-instellingen opgeslagen',
+ 'admin.placesPhotos.title': "Plaatsfoto's",
+ 'admin.placesPhotos.subtitle':
+ "Haalt foto's op via de Google Places API. Schakel uit om API-quota te besparen. Wikimedia-foto's worden niet beïnvloed.",
+ 'admin.placesAutocomplete.title': 'Plaatsautocomplete',
+ 'admin.placesAutocomplete.subtitle':
+ 'Gebruikt de Google Places API voor zoeksuggesties. Schakel uit om API-quota te besparen.',
+ 'admin.placesDetails.title': 'Plaatsdetails',
+ 'admin.placesDetails.subtitle':
+ 'Haalt gedetailleerde plaatsinformatie (openingstijden, beoordeling, website) op via de Google Places API. Schakel uit om API-quota te besparen.',
+ 'admin.bagTracking.title': 'Bagagetracking',
+ 'admin.bagTracking.subtitle':
+ 'Gewicht en bagagetoewijzing inschakelen voor paklijstitems',
+ 'admin.collab.chat.title': 'Chat',
+ 'admin.collab.chat.subtitle': 'Realtime berichten voor reissamenwerking',
+ 'admin.collab.notes.title': 'Notities',
+ 'admin.collab.notes.subtitle': 'Gedeelde notities en documenten',
+ 'admin.collab.polls.title': 'Polls',
+ 'admin.collab.polls.subtitle': 'Groepspolls en stemmen',
+ 'admin.collab.whatsnext.title': 'Wat nu',
+ 'admin.collab.whatsnext.subtitle':
+ 'Activiteitssuggesties en volgende stappen',
+ 'admin.tabs.config': 'Personalisatie',
+ 'admin.tabs.defaults': 'Standaardinstellingen',
+ 'admin.defaultSettings.title': 'Standaard gebruikersinstellingen',
+ 'admin.defaultSettings.description':
+ 'Stel instantiebrede standaardwaarden in. Gebruikers die een instelling niet hebben gewijzigd, zien deze waarden. Hun eigen wijzigingen hebben altijd voorrang.',
+ 'admin.defaultSettings.saved': 'Standaard opgeslagen',
+ 'admin.defaultSettings.reset': 'Terugzetten naar ingebouwde standaard',
+ 'admin.defaultSettings.resetToBuiltIn': 'terugzetten',
+ 'admin.tabs.templates': 'Paksjablonen',
+ 'admin.packingTemplates.title': 'Paksjablonen',
+ 'admin.packingTemplates.subtitle':
+ 'Herbruikbare paklijsten maken voor je reizen',
+ 'admin.packingTemplates.create': 'Nieuw sjabloon',
+ 'admin.packingTemplates.namePlaceholder':
+ 'Sjabloonnaam (bijv. Strandvakantie)',
+ 'admin.packingTemplates.empty': 'Nog geen sjablonen aangemaakt',
+ 'admin.packingTemplates.items': 'items',
+ 'admin.packingTemplates.categories': 'categorieën',
+ 'admin.packingTemplates.itemName': 'Itemnaam',
+ 'admin.packingTemplates.itemCategory': 'Categorie',
+ 'admin.packingTemplates.categoryName': 'Categorienaam (bijv. Kleding)',
+ 'admin.packingTemplates.addCategory': 'Categorie toevoegen',
+ 'admin.packingTemplates.created': 'Sjabloon aangemaakt',
+ 'admin.packingTemplates.deleted': 'Sjabloon verwijderd',
+ 'admin.packingTemplates.loadError': 'Fout bij laden van sjablonen',
+ 'admin.packingTemplates.createError': 'Fout bij aanmaken van sjabloon',
+ 'admin.packingTemplates.deleteError': 'Fout bij verwijderen van sjabloon',
+ 'admin.packingTemplates.saveError': 'Fout bij opslaan',
+ 'admin.tabs.addons': 'Add-ons',
+ 'admin.addons.title': 'Add-ons',
+ 'admin.addons.subtitle':
+ 'Schakel functies in of uit om je TREK-ervaring aan te passen.',
+ 'admin.addons.catalog.memories.name': "Foto's (Immich)",
+ 'admin.addons.catalog.memories.description':
+ "Deel reisfoto's via je Immich-instantie",
+ 'admin.addons.catalog.mcp.name': 'MCP',
+ 'admin.addons.catalog.mcp.description':
+ 'Model Context Protocol voor AI-assistent integratie',
+ 'admin.addons.catalog.packing.name': 'Lijsten',
+ 'admin.addons.catalog.packing.description':
+ 'Paklijsten en to-dotaken voor je reizen',
+ 'admin.addons.catalog.budget.name': 'Budget',
+ 'admin.addons.catalog.budget.description':
+ 'Houd uitgaven bij en plan je reisbudget',
+ 'admin.addons.catalog.documents.name': 'Documenten',
+ 'admin.addons.catalog.documents.description':
+ 'Bewaar en beheer reisdocumenten',
+ 'admin.addons.catalog.vacay.name': 'Vakantie',
+ 'admin.addons.catalog.vacay.description':
+ 'Persoonlijke vakantieplanner met kalenderweergave',
+ 'admin.addons.catalog.atlas.name': 'Atlas',
+ 'admin.addons.catalog.atlas.description':
+ 'Wereldkaart met bezochte landen en reisstatistieken',
+ 'admin.addons.catalog.collab.name': 'Samenwerking',
+ 'admin.addons.catalog.collab.description':
+ 'Realtime notities, polls en chat voor het plannen van reizen',
+ 'admin.addons.subtitleBefore': 'Schakel functies in of uit om je ',
+ 'admin.addons.subtitleAfter': '-ervaring aan te passen.',
+ 'admin.addons.enabled': 'Ingeschakeld',
+ 'admin.addons.disabled': 'Uitgeschakeld',
+ 'admin.addons.type.trip': 'Reis',
+ 'admin.addons.type.global': 'Globaal',
+ 'admin.addons.type.integration': 'Integratie',
+ 'admin.addons.tripHint': 'Beschikbaar als tabblad binnen elke reis',
+ 'admin.addons.globalHint':
+ 'Beschikbaar als zelfstandig onderdeel in de hoofdnavigatie',
+ 'admin.addons.integrationHint':
+ 'Backenddiensten en API-integraties zonder eigen pagina',
+ 'admin.addons.toast.updated': 'Add-on bijgewerkt',
+ 'admin.addons.toast.error': 'Add-on bijwerken mislukt',
+ 'admin.addons.noAddons': 'Geen add-ons beschikbaar',
+ 'admin.weather.title': 'Weergegevens',
+ 'admin.weather.badge': 'Sinds 24 maart 2026',
+ 'admin.weather.description':
+ 'TREK gebruikt Open-Meteo als weerbron. Open-Meteo is een gratis, open-source weerdienst — geen API-sleutel vereist.',
+ 'admin.weather.forecast': '16-daagse voorspelling',
+ 'admin.weather.forecastDesc': 'Voorheen 5 dagen (OpenWeatherMap)',
+ 'admin.weather.climate': 'Historische klimaatgegevens',
+ 'admin.weather.climateDesc':
+ 'Gemiddelden over de afgelopen 85 jaar voor dagen buiten de 16-daagse voorspelling',
+ 'admin.weather.requests': '10.000 verzoeken / dag',
+ 'admin.weather.requestsDesc': 'Gratis, geen API-sleutel vereist',
+ 'admin.weather.locationHint':
+ 'Het weer is gebaseerd op de eerste plaats met coördinaten op elke dag. Als er geen plaats aan een dag is toegewezen, wordt een plaats uit de lijst als referentie gebruikt.',
+ 'admin.tabs.mcpTokens': 'MCP-toegang',
+ 'admin.mcpTokens.title': 'MCP-toegang',
+ 'admin.mcpTokens.subtitle':
+ 'OAuth-sessies en API-tokens van alle gebruikers beheren',
+ 'admin.mcpTokens.sectionTitle': 'API-tokens',
+ 'admin.mcpTokens.owner': 'Eigenaar',
+ 'admin.mcpTokens.tokenName': 'Tokennaam',
+ 'admin.mcpTokens.created': 'Aangemaakt',
+ 'admin.mcpTokens.lastUsed': 'Laatst gebruikt',
+ 'admin.mcpTokens.never': 'Nooit',
+ 'admin.mcpTokens.empty': 'Er zijn nog geen MCP-tokens aangemaakt',
+ 'admin.mcpTokens.deleteTitle': 'Token verwijderen',
+ 'admin.mcpTokens.deleteMessage':
+ 'Dit token wordt onmiddellijk ingetrokken. De gebruiker verliest MCP-toegang via dit token.',
+ 'admin.mcpTokens.deleteSuccess': 'Token verwijderd',
+ 'admin.mcpTokens.deleteError': 'Token kon niet worden verwijderd',
+ 'admin.mcpTokens.loadError': 'Tokens konden niet worden geladen',
+ 'admin.oauthSessions.sectionTitle': 'OAuth-sessies',
+ 'admin.oauthSessions.clientName': 'Client',
+ 'admin.oauthSessions.owner': 'Eigenaar',
+ 'admin.oauthSessions.scopes': 'Rechten',
+ 'admin.oauthSessions.created': 'Aangemaakt',
+ 'admin.oauthSessions.empty': 'Geen actieve OAuth-sessies',
+ 'admin.oauthSessions.revokeTitle': 'Sessie intrekken',
+ 'admin.oauthSessions.revokeMessage':
+ 'Deze OAuth-sessie wordt onmiddellijk ingetrokken. De client verliest MCP-toegang.',
+ 'admin.oauthSessions.revokeSuccess': 'Sessie ingetrokken',
+ 'admin.oauthSessions.revokeError': 'Sessie kon niet worden ingetrokken',
+ 'admin.oauthSessions.loadError': 'OAuth-sessies konden niet worden geladen',
+ 'admin.tabs.github': 'GitHub',
+ 'admin.audit.subtitle':
+ 'Beveiligingsgevoelige en beheerdersgebeurtenissen (back-ups, gebruikers, MFA, instellingen).',
+ 'admin.audit.empty': 'Nog geen auditregistraties.',
+ 'admin.audit.refresh': 'Vernieuwen',
+ 'admin.audit.loadMore': 'Meer laden',
+ 'admin.audit.showing': '{count} geladen · {total} totaal',
+ 'admin.audit.col.time': 'Tijd',
+ 'admin.audit.col.user': 'Gebruiker',
+ 'admin.audit.col.action': 'Actie',
+ 'admin.audit.col.resource': 'Bron',
+ 'admin.audit.col.ip': 'IP',
+ 'admin.audit.col.details': 'Details',
+ 'admin.github.title': 'Release-geschiedenis',
+ 'admin.github.subtitle': 'Laatste updates van {repo}',
+ 'admin.github.latest': 'Nieuwste',
+ 'admin.github.prerelease': 'Pre-release',
+ 'admin.github.showDetails': 'Details tonen',
+ 'admin.github.hideDetails': 'Details verbergen',
+ 'admin.github.loadMore': 'Meer laden',
+ 'admin.github.loading': 'Laden...',
+ 'admin.github.support': 'Helpt mij TREK verder te ontwikkelen',
+ 'admin.github.error': 'Releases laden mislukt',
+ 'admin.github.by': 'door',
+ 'admin.update.available': 'Update beschikbaar',
+ 'admin.update.text': 'TREK {version} is beschikbaar. Je draait {current}.',
+ 'admin.update.button': 'Bekijk op GitHub',
+ 'admin.update.install': 'Update installeren',
+ 'admin.update.confirmTitle': 'Update installeren?',
+ 'admin.update.confirmText':
+ 'TREK wordt bijgewerkt van {current} naar {version}. De server herstart automatisch.',
+ 'admin.update.dataInfo':
+ 'Al je gegevens (reizen, gebruikers, API-sleutels, uploads, Vacay, Atlas, budgetten) worden bewaard.',
+ 'admin.update.warning':
+ 'De app is kort niet beschikbaar tijdens het herstarten.',
+ 'admin.update.confirm': 'Nu bijwerken',
+ 'admin.update.installing': 'Bijwerken…',
+ 'admin.update.success': 'Update geïnstalleerd! Server herstart…',
+ 'admin.update.failed': 'Update mislukt',
+ 'admin.update.backupHint':
+ 'We raden aan een back-up te maken voordat je bijwerkt.',
+ 'admin.update.backupLink': 'Naar back-up',
+ 'admin.update.howTo': 'Hoe bij te werken',
+ 'admin.update.dockerText':
+ "Je TREK-instantie draait in Docker. Om bij te werken naar {version}, voer de volgende commando's uit op je server:",
+ 'admin.update.reloadHint': 'Herlaad de pagina over een paar seconden.',
+ 'admin.tabs.permissions': 'Rechten',
+ 'admin.notifications.emailPanel.title': 'Email (SMTP)',
+ 'admin.notifications.webhookPanel.title': 'Webhook',
+ 'admin.notifications.inappPanel.title': 'In-App',
+ 'admin.notifications.inappPanel.hint':
+ 'In-app-meldingen zijn altijd actief en kunnen niet globaal worden uitgeschakeld.',
+ 'admin.notifications.adminWebhookPanel.title': 'Admin-webhook',
+ 'admin.notifications.adminWebhookPanel.hint':
+ 'Deze webhook wordt uitsluitend gebruikt voor admin-meldingen (bijv. versie-updates). Hij staat los van gebruikerswebhooks en verstuurt automatisch als er een URL is ingesteld.',
+ 'admin.notifications.adminWebhookPanel.saved': 'Admin-webhook-URL opgeslagen',
+ 'admin.notifications.adminWebhookPanel.testSuccess':
+ 'Test-webhook succesvol verzonden',
+ 'admin.notifications.adminWebhookPanel.testFailed': 'Test-webhook mislukt',
+ 'admin.notifications.adminWebhookPanel.alwaysOnHint':
+ 'Admin-webhook verstuurt automatisch als er een URL is ingesteld',
+ 'admin.notifications.ntfy': 'Ntfy',
+ 'admin.ntfy.hint':
+ 'Hiermee kunnen gebruikers hun eigen ntfy-onderwerpen instellen voor pushmeldingen. Stel de standaardserver hieronder in om de gebruikersinstellingen vooraf in te vullen.',
+ 'admin.notifications.testNtfy': 'Test-Ntfy verzenden',
+ 'admin.notifications.testNtfySuccess': 'Test-Ntfy succesvol verzonden',
+ 'admin.notifications.testNtfyFailed': 'Test-Ntfy mislukt',
+ 'admin.notifications.adminNtfyPanel.title': 'Admin-Ntfy',
+ 'admin.notifications.adminNtfyPanel.hint':
+ 'Dit Ntfy-onderwerp wordt uitsluitend gebruikt voor admin-meldingen (bijv. versie-updates). Het staat los van onderwerpen per gebruiker en verstuurt altijd wanneer het geconfigureerd is.',
+ 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy-server-URL',
+ 'admin.notifications.adminNtfyPanel.serverHint':
+ 'Wordt ook gebruikt als standaardserver voor ntfy-meldingen van gebruikers. Laat leeg om ntfy.sh te gebruiken. Gebruikers kunnen dit aanpassen in hun eigen instellingen.',
+ 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh',
+ 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin-onderwerp',
+ 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts',
+ 'admin.notifications.adminNtfyPanel.tokenLabel': 'Toegangstoken (optioneel)',
+ 'admin.notifications.adminNtfyPanel.tokenCleared':
+ 'Admin-toegangstoken gewist',
+ 'admin.notifications.adminNtfyPanel.saved':
+ 'Admin-Ntfy-instellingen opgeslagen',
+ 'admin.notifications.adminNtfyPanel.test': 'Test-Ntfy verzenden',
+ 'admin.notifications.adminNtfyPanel.testSuccess':
+ 'Test-Ntfy succesvol verzonden',
+ 'admin.notifications.adminNtfyPanel.testFailed': 'Test-Ntfy mislukt',
+ 'admin.notifications.adminNtfyPanel.alwaysOnHint':
+ 'Admin-Ntfy verstuurt altijd wanneer een onderwerp is geconfigureerd',
+ 'admin.notifications.adminNotificationsHint':
+ 'Stel in via welke kanalen admin-meldingen worden bezorgd (bijv. versie-updates). De webhook verstuurt automatisch als er een admin-webhook-URL is ingesteld.',
+ 'admin.notifications.tripReminders.title': 'Reisherinneringen',
+ 'admin.notifications.tripReminders.hint':
+ 'Stuurt een herinneringsmelding voor de start van een reis (vereist ingestelde herinneringsdagen bij de reis).',
+ 'admin.notifications.tripReminders.enabled': 'Reisherinneringen ingeschakeld',
+ 'admin.notifications.tripReminders.disabled':
+ 'Reisherinneringen uitgeschakeld',
+ 'admin.tabs.notifications': 'Meldingen',
+ 'admin.addons.catalog.journey.name': 'Reisverslag',
+ 'admin.addons.catalog.journey.description':
+ "Reistracking & reisdagboek met check-ins, foto's en dagelijkse verhalen",
+ 'admin.passkey.title': 'Inloggen met passkey',
+ 'admin.passkey.cardHint':
+ 'Laat gebruikers inloggen met passkeys (WebAuthn). Standaard uit.',
+ 'admin.passkey.login': 'Inloggen met passkey inschakelen',
+ 'admin.passkey.loginHint':
+ 'Toon een optie "Inloggen met een passkey" en laat gebruikers passkeys registreren in hun instellingen.',
+ 'admin.passkey.notConfigured':
+ 'Voor deze installatie wordt nog geen WebAuthn-domein herleid. Stel APP_URL of de Relying Party ID hieronder in — tot dan blijven passkeys verborgen.',
+ 'admin.passkey.rpId': 'Relying Party ID (domein)',
+ 'admin.passkey.rpIdHint':
+ 'Het kale domein waaraan passkeys zijn gebonden, bijv. trek.example.org. Laat leeg om het af te leiden uit APP_URL. Als je dit later wijzigt, worden bestaande passkeys ongeldig.',
+ 'admin.passkey.origins': 'Toegestane origins',
+ 'admin.passkey.originsHint':
+ 'Volledige origins, gescheiden door komma\'s, bijv. https://trek.example.org. Laat leeg om APP_URL te gebruiken.',
+ 'admin.passkey.reset': 'Passkeys resetten',
+ 'admin.passkey.resetHint':
+ 'Verwijder alle passkeys van deze gebruiker (bijv. bij een verloren apparaat). Ze kunnen nog steeds inloggen met hun wachtwoord.',
+ 'admin.passkey.resetConfirm': 'Alle passkeys voor {name} verwijderen?',
+ 'admin.passkey.resetDone': '{count} passkey(s) verwijderd',
+ 'admin.defaultSettings.mapProvider': 'Kaartmotor',
+ 'admin.defaultSettings.mapProviderHint': 'De standaardkaart voor iedereen op deze instantie. Elke gebruiker kan dit nog steeds aanpassen in zijn eigen instellingen.',
+ 'admin.defaultSettings.providerLeaflet': 'Standaard (gratis)',
+ 'admin.defaultSettings.providerMapbox': 'Mapbox (3D)',
+ 'admin.defaultSettings.mapboxToken': 'Gedeeld Mapbox-token',
+ 'admin.defaultSettings.mapboxTokenHint': 'Wordt gebruikt voor elke gebruiker die nog geen eigen token heeft ingevoerd — zo krijgt de hele instantie Mapbox zonder de sleutel apart te delen. Versleuteld opgeslagen.',
+ 'admin.defaultSettings.mapboxStyle': 'Kaartstijl',
+ 'admin.defaultSettings.mapboxStylePlaceholder': 'Kies een stijl…',
+ 'admin.defaultSettings.mapbox3d': '3D-gebouwen & terrein',
+ 'admin.defaultSettings.mapboxQuality': 'Hogekwaliteitsmodus',
+};
+export default admin;
diff --git a/shared/src/i18n/nl/airport.ts b/shared/src/i18n/nl/airport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..84912b32a3c5c239b21046bdad114b9d6a2a7551
--- /dev/null
+++ b/shared/src/i18n/nl/airport.ts
@@ -0,0 +1,6 @@
+import type { TranslationStrings } from '../types';
+
+const airport: TranslationStrings = {
+ 'airport.searchPlaceholder': 'Luchthavencode of stad (bijv. FRA)',
+};
+export default airport;
diff --git a/shared/src/i18n/nl/atlas.ts b/shared/src/i18n/nl/atlas.ts
new file mode 100644
index 0000000000000000000000000000000000000000..49148f310c3f5c1968084f2beb3054991ca9254f
--- /dev/null
+++ b/shared/src/i18n/nl/atlas.ts
@@ -0,0 +1,59 @@
+import type { TranslationStrings } from '../types';
+
+const atlas: TranslationStrings = {
+ 'atlas.subtitle': 'Je reisvoetafdruk over de wereld',
+ 'atlas.countries': 'Landen',
+ 'atlas.trips': 'Reizen',
+ 'atlas.places': 'Plaatsen',
+ 'atlas.days': 'Dagen',
+ 'atlas.visitedCountries': 'Bezochte landen',
+ 'atlas.cities': 'Steden',
+ 'atlas.noData': 'Nog geen reisgegevens',
+ 'atlas.noDataHint':
+ 'Maak een reis aan en voeg plaatsen toe om je wereldkaart te zien',
+ 'atlas.lastTrip': 'Laatste reis',
+ 'atlas.nextTrip': 'Volgende reis',
+ 'atlas.daysLeft': 'dagen te gaan',
+ 'atlas.streak': 'Reeks',
+ 'atlas.year': 'jaar',
+ 'atlas.years': 'jaar',
+ 'atlas.yearInRow': 'jaar op rij',
+ 'atlas.yearsInRow': 'jaar op rij',
+ 'atlas.tripIn': 'reis in',
+ 'atlas.tripsIn': 'reizen in',
+ 'atlas.since': 'sinds',
+ 'atlas.europe': 'Europa',
+ 'atlas.asia': 'Azië',
+ 'atlas.northAmerica': 'N.-Amerika',
+ 'atlas.southAmerica': 'Z.-Amerika',
+ 'atlas.africa': 'Afrika',
+ 'atlas.oceania': 'Oceanië',
+ 'atlas.other': 'Overig',
+ 'atlas.firstVisit': 'Eerste reis',
+ 'atlas.lastVisitLabel': 'Laatste reis',
+ 'atlas.tripSingular': 'Reis',
+ 'atlas.tripPlural': 'Reizen',
+ 'atlas.placeVisited': 'Bezochte plaats',
+ 'atlas.placesVisited': 'Bezochte plaatsen',
+ 'atlas.statsTab': 'Statistieken',
+ 'atlas.bucketTab': 'Bucketlist',
+ 'atlas.addBucket': 'Toevoegen aan bucket list',
+ 'atlas.bucketNamePlaceholder': 'Plaats of bestemming...',
+ 'atlas.bucketNotesPlaceholder': 'Notities (optioneel)',
+ 'atlas.bucketEmpty': 'Je bucket list is leeg',
+ 'atlas.bucketEmptyHint': 'Voeg plekken toe die je wilt bezoeken',
+ 'atlas.unmark': 'Verwijderen',
+ 'atlas.confirmMark': 'Dit land als bezocht markeren?',
+ 'atlas.confirmUnmark': 'Dit land van je bezochte lijst verwijderen?',
+ 'atlas.confirmUnmarkRegion': 'Deze regio van je bezochte lijst verwijderen?',
+ 'atlas.markVisited': 'Markeren als bezocht',
+ 'atlas.markVisitedHint': 'Dit land toevoegen aan je bezochte lijst',
+ 'atlas.markRegionVisitedHint': 'Deze regio toevoegen aan je bezochte lijst',
+ 'atlas.addToBucket': 'Aan bucket list toevoegen',
+ 'atlas.addPoi': 'Plaats toevoegen',
+ 'atlas.searchCountry': 'Zoek een land...',
+ 'atlas.month': 'Maand',
+ 'atlas.addToBucketHint': 'Opslaan als plek die je wilt bezoeken',
+ 'atlas.bucketWhen': 'Wanneer ben je van plan te gaan?',
+};
+export default atlas;
diff --git a/shared/src/i18n/nl/backup.ts b/shared/src/i18n/nl/backup.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3657df02b3430be77689463d53ed160b4be783c8
--- /dev/null
+++ b/shared/src/i18n/nl/backup.ts
@@ -0,0 +1,78 @@
+import type { TranslationStrings } from '../types';
+
+const backup: TranslationStrings = {
+ 'backup.title': 'Gegevensback-up',
+ 'backup.subtitle': 'Database en alle geüploade bestanden',
+ 'backup.refresh': 'Vernieuwen',
+ 'backup.upload': 'Back-up uploaden',
+ 'backup.uploading': 'Uploaden…',
+ 'backup.create': 'Back-up aanmaken',
+ 'backup.creating': 'Aanmaken…',
+ 'backup.empty': 'Nog geen back-ups',
+ 'backup.createFirst': 'Eerste back-up aanmaken',
+ 'backup.download': 'Downloaden',
+ 'backup.restore': 'Herstellen',
+ 'backup.confirm.restore':
+ 'Back-up "{name}" herstellen?\n\nAlle huidige gegevens worden vervangen door de back-up.',
+ 'backup.confirm.uploadRestore':
+ 'Back-upbestand "{name}" uploaden en herstellen?\n\nAlle huidige gegevens worden overschreven.',
+ 'backup.confirm.delete': 'Back-up "{name}" verwijderen?',
+ 'backup.toast.loadError': 'Back-ups laden mislukt',
+ 'backup.toast.created': 'Back-up succesvol aangemaakt',
+ 'backup.toast.createError': 'Back-up aanmaken mislukt',
+ 'backup.toast.restored': 'Back-up hersteld. Pagina wordt herladen…',
+ 'backup.toast.restoreError': 'Herstellen mislukt',
+ 'backup.toast.uploadError': 'Uploaden mislukt',
+ 'backup.toast.deleted': 'Back-up verwijderd',
+ 'backup.toast.deleteError': 'Verwijderen mislukt',
+ 'backup.toast.downloadError': 'Downloaden mislukt',
+ 'backup.toast.settingsSaved': 'Auto-back-up-instellingen opgeslagen',
+ 'backup.toast.settingsError': 'Instellingen opslaan mislukt',
+ 'backup.auto.title': 'Auto-back-up',
+ 'backup.auto.subtitle': 'Automatische back-up volgens schema',
+ 'backup.auto.enable': 'Auto-back-up inschakelen',
+ 'backup.auto.enableHint':
+ 'Back-ups worden automatisch aangemaakt volgens het gekozen schema',
+ 'backup.auto.interval': 'Interval',
+ 'backup.auto.hour': 'Uitvoeren om',
+ 'backup.auto.hourHint': 'Lokale servertijd ({format}-notatie)',
+ 'backup.auto.dayOfWeek': 'Dag van de week',
+ 'backup.auto.dayOfMonth': 'Dag van de maand',
+ 'backup.auto.dayOfMonthHint':
+ 'Beperkt tot 1–28 voor compatibiliteit met alle maanden',
+ 'backup.auto.scheduleSummary': 'Planning',
+ 'backup.auto.summaryDaily': 'Elke dag om {hour}:00',
+ 'backup.auto.summaryWeekly': 'Elke {day} om {hour}:00',
+ 'backup.auto.summaryMonthly': 'Dag {day} van elke maand om {hour}:00',
+ 'backup.auto.envLocked': 'Docker',
+ 'backup.auto.envLockedHint':
+ 'Auto-back-up is geconfigureerd via Docker-omgevingsvariabelen. Pas je docker-compose.yml aan en herstart de container om deze instellingen te wijzigen.',
+ 'backup.auto.copyEnv': 'Docker-omgevingsvariabelen kopiëren',
+ 'backup.auto.envCopied':
+ 'Docker-omgevingsvariabelen gekopieerd naar klembord',
+ 'backup.auto.keepLabel': 'Oude back-ups verwijderen na',
+ 'backup.dow.sunday': 'Zo',
+ 'backup.dow.monday': 'Ma',
+ 'backup.dow.tuesday': 'Di',
+ 'backup.dow.wednesday': 'Wo',
+ 'backup.dow.thursday': 'Do',
+ 'backup.dow.friday': 'Vr',
+ 'backup.dow.saturday': 'Za',
+ 'backup.interval.hourly': 'Elk uur',
+ 'backup.interval.daily': 'Dagelijks',
+ 'backup.interval.weekly': 'Wekelijks',
+ 'backup.interval.monthly': 'Maandelijks',
+ 'backup.keep.1day': '1 dag',
+ 'backup.keep.3days': '3 dagen',
+ 'backup.keep.7days': '7 dagen',
+ 'backup.keep.14days': '14 dagen',
+ 'backup.keep.30days': '30 dagen',
+ 'backup.keep.forever': 'Voor altijd bewaren',
+ 'backup.restoreConfirmTitle': 'Back-up herstellen?',
+ 'backup.restoreWarning':
+ 'Alle huidige gegevens (reizen, plaatsen, gebruikers, uploads) worden permanent vervangen door de back-up. Deze actie kan niet ongedaan worden gemaakt.',
+ 'backup.restoreTip':
+ 'Tip: Maak een back-up van de huidige status voordat je herstelt.',
+ 'backup.restoreConfirm': 'Ja, herstellen',
+};
+export default backup;
diff --git a/shared/src/i18n/nl/budget.ts b/shared/src/i18n/nl/budget.ts
new file mode 100644
index 0000000000000000000000000000000000000000..37532e3c0acfc3e70ea6ca9539a01de7c5f9494a
--- /dev/null
+++ b/shared/src/i18n/nl/budget.ts
@@ -0,0 +1,117 @@
+import type { TranslationStrings } from '../types';
+
+const budget: TranslationStrings = {
+ 'budget.title': 'Budget',
+ 'budget.exportCsv': 'CSV exporteren',
+ 'budget.emptyTitle': 'Nog geen budget aangemaakt',
+ 'budget.emptyText':
+ 'Maak categorieën en invoeren aan om je reisbudget te plannen',
+ 'budget.emptyPlaceholder': 'Categorienaam invoeren...',
+ 'budget.createCategory': 'Categorie aanmaken',
+ 'budget.category': 'Categorie',
+ 'budget.categoryName': 'Categorienaam',
+ 'budget.table.name': 'Naam',
+ 'budget.table.total': 'Totaal',
+ 'budget.table.persons': 'Personen',
+ 'budget.table.days': 'Dagen',
+ 'budget.table.perPerson': 'Per persoon',
+ 'budget.table.perDay': 'Per dag',
+ 'budget.table.perPersonDay': 'P. p. / dag',
+ 'budget.table.note': 'Notitie',
+ 'budget.table.date': 'Datum',
+ 'budget.newEntry': 'Nieuwe invoer',
+ 'budget.defaultEntry': 'Nieuwe invoer',
+ 'budget.defaultCategory': 'Nieuwe categorie',
+ 'budget.total': 'Totaal',
+ 'budget.totalBudget': 'Totaal budget',
+ 'budget.byCategory': 'Per categorie',
+ 'budget.editTooltip': 'Klik om te bewerken',
+ 'budget.linkedToReservation':
+ 'Gekoppeld aan een reservering — bewerk de naam daar',
+ 'budget.confirm.deleteCategory':
+ 'Weet je zeker dat je de categorie "{name}" met {count} invoeren wilt verwijderen?',
+ 'budget.deleteCategory': 'Categorie verwijderen',
+ 'budget.perPerson': 'Per persoon',
+ 'budget.paid': 'Betaald',
+ 'budget.open': 'Openstaand',
+ 'budget.noMembers': 'Geen leden toegewezen',
+ 'budget.settlement': 'Afrekening',
+ 'budget.settlementInfo':
+ 'Klik op de avatar van een lid bij een budgetpost om deze groen te markeren — dit betekent dat diegene heeft betaald. De afrekening toont vervolgens wie wie hoeveel verschuldigd is.',
+ 'budget.netBalances': 'Nettosaldi',
+ 'budget.categoriesLabel': 'categorieën',
+ "costs.you": "Jij",
+ "costs.youShort": "J",
+ "costs.youLower": "jij",
+ "costs.youOwe": "Jij bent verschuldigd",
+ "costs.youOweSub": "Jij moet anderen betalen",
+ "costs.youreOwed": "Jij krijgt nog",
+ "costs.youreOwedSub": "Anderen moeten jou betalen",
+ "costs.totalSpend": "Totale reisuitgaven",
+ "costs.totalSpendSub": "Voor alle reizigers",
+ "costs.to": "Aan",
+ "costs.from": "Van",
+ "costs.allSettled": "Je bent helemaal afgerekend",
+ "costs.nothingOwed": "Niemand is jou iets verschuldigd",
+ "costs.yourShare": "Jouw aandeel",
+ "costs.youPaid": "Jij hebt betaald",
+ "costs.expenses": "Uitgaven",
+ "costs.entries": "{count} invoeren",
+ "costs.searchPlaceholder": "Uitgaven zoeken…",
+ "costs.filter.all": "Alles",
+ "costs.filter.mine": "Door mij betaald",
+ "costs.filter.owed": "Mij verschuldigd",
+ "costs.addExpense": "Uitgave toevoegen",
+ "costs.editExpense": "Uitgave bewerken",
+ "costs.noMatch": "Geen uitgaven komen overeen met je zoekopdracht.",
+ "costs.emptyText": "Nog geen uitgaven. Voeg je eerste toe.",
+ "costs.spent": "{amount} uitgegeven",
+ "costs.noDate": "Geen datum",
+ "costs.noOnePaid": "Nog niemand heeft betaald",
+ "costs.youLent": "je hebt {amount} voorgeschoten",
+ "costs.youBorrowed": "je hebt {amount} geleend",
+ "costs.settleUp": "Afrekenen",
+ "costs.history": "Geschiedenis",
+ "costs.everyoneSquare": "Iedereen is quitte",
+ "costs.nothingOutstanding": "Er staan op dit moment geen betalingen open.",
+ "costs.pay": "betaalt",
+ "costs.pays": "betaalt",
+ "costs.settle": "Afrekenen",
+ "costs.balances": "Saldi",
+ "costs.byCategory": "Per categorie",
+ "costs.noCategories": "Nog geen uitgaven.",
+ "costs.settleHistory": "Afrekengeschiedenis",
+ "costs.noSettlements": "Nog geen afgerekende betalingen.",
+ "costs.paymentsSettled": "{count} betalingen afgerekend",
+ "costs.paid": "betaald",
+ "costs.undo": "Ongedaan maken",
+ "costs.whatFor": "Waar was het voor?",
+ "costs.namePlaceholder": "bijv. Diner, souvenirs, benzine…",
+ "costs.totalAmount": "Totaalbedrag",
+ "costs.currency": "Valuta",
+ "costs.day": "Dag",
+ "costs.rateLabel": "1 {from} in {to}",
+ "costs.category": "Categorie",
+ "costs.whoPaid": "Wie heeft betaald?",
+ "costs.splitBetween": "Gelijk verdelen over",
+ "costs.pickSomeone": "Kies minstens één persoon om mee te delen.",
+ "costs.splitSummary": "Verdeeld over {count} · {amount} elk",
+ "costs.cat.accommodation": "Accommodatie",
+ "costs.cat.food": "Eten & drinken",
+ "costs.cat.groceries": "Boodschappen",
+ "costs.cat.transport": "Vervoer",
+ "costs.cat.flights": "Vluchten",
+ "costs.cat.activities": "Activiteiten",
+ "costs.cat.sightseeing": "Bezienswaardigheden",
+ "costs.cat.shopping": "Winkelen",
+ "costs.cat.fees": "Kosten & tickets",
+ "costs.cat.health": "Gezondheid",
+ "costs.cat.tips": "Fooien",
+ "costs.cat.other": "Overig",
+ "costs.daysCount": "{count} dagen",
+ "costs.travelers": "{count} reizigers",
+ "costs.liveRate": "live koers",
+ "costs.settleAll": "Alles afrekenen",
+};
+
+export default budget;
diff --git a/shared/src/i18n/nl/categories.ts b/shared/src/i18n/nl/categories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..37b17eb06e3c3c68d23082b104e01d21dbe58736
--- /dev/null
+++ b/shared/src/i18n/nl/categories.ts
@@ -0,0 +1,26 @@
+import type { TranslationStrings } from '../types';
+
+const categories: TranslationStrings = {
+ 'categories.title': 'Categorieën',
+ 'categories.subtitle': 'Beheer categorieën voor plaatsen',
+ 'categories.new': 'Nieuwe categorie',
+ 'categories.empty': 'Nog geen categorieën',
+ 'categories.namePlaceholder': 'Categorienaam',
+ 'categories.icon': 'Pictogram',
+ 'categories.color': 'Kleur',
+ 'categories.customColor': 'Kies een aangepaste kleur',
+ 'categories.preview': 'Voorbeeld',
+ 'categories.defaultName': 'Categorie',
+ 'categories.update': 'Bijwerken',
+ 'categories.create': 'Aanmaken',
+ 'categories.confirm.delete':
+ 'Categorie verwijderen? Plaatsen in deze categorie worden niet verwijderd.',
+ 'categories.toast.loadError': 'Categorieën laden mislukt',
+ 'categories.toast.nameRequired': 'Voer een naam in',
+ 'categories.toast.updated': 'Categorie bijgewerkt',
+ 'categories.toast.created': 'Categorie aangemaakt',
+ 'categories.toast.saveError': 'Opslaan mislukt',
+ 'categories.toast.deleted': 'Categorie verwijderd',
+ 'categories.toast.deleteError': 'Verwijderen mislukt',
+};
+export default categories;
diff --git a/shared/src/i18n/nl/collab.ts b/shared/src/i18n/nl/collab.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bb09b6a42f09c6339dbcd39c12ac58911658e644
--- /dev/null
+++ b/shared/src/i18n/nl/collab.ts
@@ -0,0 +1,75 @@
+import type { TranslationStrings } from '../types';
+
+const collab: TranslationStrings = {
+ 'collab.tabs.chat': 'Chat',
+ 'collab.tabs.notes': 'Notities',
+ 'collab.tabs.polls': 'Polls',
+ 'collab.whatsNext.title': 'Wat komt er',
+ 'collab.whatsNext.today': 'Vandaag',
+ 'collab.whatsNext.tomorrow': 'Morgen',
+ 'collab.whatsNext.empty': 'Geen komende activiteiten',
+ 'collab.whatsNext.until': 'tot',
+ 'collab.whatsNext.emptyHint': 'Activiteiten met tijden verschijnen hier',
+ 'collab.chat.send': 'Verzenden',
+ 'collab.chat.placeholder': 'Typ een bericht...',
+ 'collab.chat.empty': 'Start het gesprek',
+ 'collab.chat.emptyHint': 'Berichten worden gedeeld met alle reisleden',
+ 'collab.chat.emptyDesc': 'Deel ideeën, plannen en updates met je reisgroep',
+ 'collab.chat.today': 'Vandaag',
+ 'collab.chat.yesterday': 'Gisteren',
+ 'collab.chat.deletedMessage': 'heeft een bericht verwijderd',
+ 'collab.chat.reply': 'Beantwoorden',
+ 'collab.chat.loadMore': 'Oudere berichten laden',
+ 'collab.chat.justNow': 'zojuist',
+ 'collab.chat.minutesAgo': '{n} min. geleden',
+ 'collab.chat.hoursAgo': '{n} uur geleden',
+ 'collab.notes.title': 'Notities',
+ 'collab.notes.new': 'Nieuwe notitie',
+ 'collab.notes.empty': 'Nog geen notities',
+ 'collab.notes.emptyHint': 'Begin met het vastleggen van ideeën en plannen',
+ 'collab.notes.all': 'Alle',
+ 'collab.notes.titlePlaceholder': 'Notitietitel',
+ 'collab.notes.contentPlaceholder': 'Schrijf iets...',
+ 'collab.notes.categoryPlaceholder': 'Categorie',
+ 'collab.notes.newCategory': 'Nieuwe categorie...',
+ 'collab.notes.category': 'Categorie',
+ 'collab.notes.noCategory': 'Geen categorie',
+ 'collab.notes.color': 'Kleur',
+ 'collab.notes.save': 'Opslaan',
+ 'collab.notes.cancel': 'Annuleren',
+ 'collab.notes.edit': 'Bewerken',
+ 'collab.notes.delete': 'Verwijderen',
+ 'collab.notes.confirmDeleteTitle': 'Notitie verwijderen?',
+ 'collab.notes.confirmDeleteBody': 'Deze notitie wordt definitief verwijderd.',
+ 'collab.notes.pin': 'Vastpinnen',
+ 'collab.notes.unpin': 'Losmaken',
+ 'collab.notes.daysAgo': '{n}d geleden',
+ 'collab.notes.categorySettings': 'Categorieën beheren',
+ 'collab.notes.create': 'Aanmaken',
+ 'collab.notes.website': 'Website',
+ 'collab.notes.websitePlaceholder': 'https://...',
+ 'collab.notes.attachFiles': 'Bestanden bijvoegen',
+ 'collab.notes.noCategoriesYet': 'Nog geen categorieën',
+ 'collab.notes.emptyDesc': 'Maak een notitie om te beginnen',
+ 'collab.polls.title': 'Polls',
+ 'collab.polls.new': 'Nieuwe poll',
+ 'collab.polls.empty': 'Nog geen polls',
+ 'collab.polls.emptyHint': 'Stel de groep een vraag en stem samen',
+ 'collab.polls.question': 'Vraag',
+ 'collab.polls.questionPlaceholder': 'Wat zullen we doen?',
+ 'collab.polls.addOption': '+ Optie toevoegen',
+ 'collab.polls.optionPlaceholder': 'Optie {n}',
+ 'collab.polls.create': 'Poll aanmaken',
+ 'collab.polls.close': 'Sluiten',
+ 'collab.polls.closed': 'Gesloten',
+ 'collab.polls.votes': '{n} stemmen',
+ 'collab.polls.vote': '{n} stem',
+ 'collab.polls.multipleChoice': 'Meerkeuze',
+ 'collab.polls.multiChoice': 'Meerkeuze',
+ 'collab.polls.deadline': 'Deadline',
+ 'collab.polls.option': 'Optie',
+ 'collab.polls.options': 'Opties',
+ 'collab.polls.delete': 'Verwijderen',
+ 'collab.polls.closedSection': 'Gesloten',
+};
+export default collab;
diff --git a/shared/src/i18n/nl/common.ts b/shared/src/i18n/nl/common.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c7aa8173479479a1f88fa3fa6369e289a6aaeea6
--- /dev/null
+++ b/shared/src/i18n/nl/common.ts
@@ -0,0 +1,54 @@
+import type { TranslationStrings } from '../types';
+
+const common: TranslationStrings = {
+ 'common.save': 'Opslaan',
+ 'common.showMore': 'Meer tonen',
+ 'common.showLess': 'Minder tonen',
+ 'common.cancel': 'Annuleren',
+ 'common.clear': 'Wissen',
+ 'common.delete': 'Verwijderen',
+ 'common.edit': 'Bewerken',
+ 'common.add': 'Toevoegen',
+ 'common.loading': 'Laden...',
+ 'common.import': 'Importeren',
+ 'common.select': 'Selecteren',
+ 'common.selectAll': 'Alles selecteren',
+ 'common.deselectAll': 'Alles deselecteren',
+ 'common.error': 'Fout',
+ 'common.unknownError': 'Onbekende fout',
+ 'common.tooManyAttempts': 'Te veel pogingen. Probeer het later opnieuw.',
+ 'common.back': 'Terug',
+ 'common.all': 'Alles',
+ 'common.close': 'Sluiten',
+ 'common.open': 'Openen',
+ 'common.upload': 'Uploaden',
+ 'common.search': 'Zoeken',
+ 'common.confirm': 'Bevestigen',
+ 'common.ok': 'OK',
+ 'common.yes': 'Ja',
+ 'common.no': 'Nee',
+ 'common.or': 'of',
+ 'common.none': 'Geen',
+ 'common.date': 'Datum',
+ 'common.rename': 'Hernoemen',
+ 'common.discardChanges': 'Wijzigingen verwerpen',
+ 'common.discard': 'Verwerpen',
+ 'common.name': 'Naam',
+ 'common.email': 'E-mail',
+ 'common.password': 'Wachtwoord',
+ 'common.saving': 'Opslaan...',
+ 'common.saved': 'Opgeslagen',
+ 'common.expand': 'Uitvouwen',
+ 'common.collapse': 'Inklappen',
+ 'common.update': 'Bijwerken',
+ 'common.change': 'Wijzigen',
+ 'common.uploading': 'Uploaden…',
+ 'common.backToPlanning': 'Terug naar planning',
+ 'common.reset': 'Resetten',
+ 'common.copy': 'Kopiëren',
+ 'common.copied': 'Gekopieerd',
+ 'common.justNow': 'zojuist',
+ 'common.hoursAgo': '{count}u geleden',
+ 'common.daysAgo': '{count}d geleden',
+};
+export default common;
diff --git a/shared/src/i18n/nl/dashboard.ts b/shared/src/i18n/nl/dashboard.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e339c5964b2e95b7912dc3049fa06b7f3ffae9cd
--- /dev/null
+++ b/shared/src/i18n/nl/dashboard.ts
@@ -0,0 +1,168 @@
+import type { TranslationStrings } from '../types';
+
+const dashboard: TranslationStrings = {
+ 'dashboard.title': 'Mijn reizen',
+ 'dashboard.subtitle.loading': 'Reizen laden...',
+ 'dashboard.subtitle.trips': '{count} reizen ({archived} gearchiveerd)',
+ 'dashboard.subtitle.empty': 'Begin je eerste reis',
+ 'dashboard.subtitle.activeOne': '{count} actieve reis',
+ 'dashboard.subtitle.activeMany': '{count} actieve reizen',
+ 'dashboard.subtitle.archivedSuffix': ' · {count} gearchiveerd',
+ 'dashboard.newTrip': 'Nieuwe reis',
+ 'dashboard.newTripSub': 'Plan een nieuwe reis vanaf nul',
+ 'dashboard.gridView': 'Rasterweergave',
+ 'dashboard.listView': 'Lijstweergave',
+ 'dashboard.currency': 'Valuta',
+ 'dashboard.timezone': 'Tijdzones',
+ 'dashboard.localTime': 'Lokaal',
+ 'dashboard.timezoneCustomTitle': 'Aangepaste tijdzone',
+ 'dashboard.timezoneCustomLabelPlaceholder': 'Label (optioneel)',
+ 'dashboard.timezoneCustomTzPlaceholder': 'bijv. America/New_York',
+ 'dashboard.timezoneCustomAdd': 'Toevoegen',
+ 'dashboard.timezoneCustomErrorEmpty': 'Voer een tijdzone-identificatie in',
+ 'dashboard.timezoneCustomErrorInvalid':
+ 'Ongeldige tijdzone. Gebruik een formaat zoals Europe/Berlin',
+ 'dashboard.timezoneCustomErrorDuplicate': 'Al toegevoegd',
+ 'dashboard.emptyTitle': 'Nog geen reizen',
+ 'dashboard.emptyText': 'Maak je eerste reis aan en begin met plannen!',
+ 'dashboard.emptyButton': 'Eerste reis aanmaken',
+ 'dashboard.nextTrip': 'Volgende reis',
+ 'dashboard.shared': 'Gedeeld',
+ 'dashboard.sharedBy': 'Gedeeld door {name}',
+ 'dashboard.days': 'Dagen',
+ 'dashboard.places': 'Plaatsen',
+ 'dashboard.members': 'Reisgenoten',
+ 'dashboard.archive': 'Archiveren',
+ 'dashboard.copyTrip': 'Kopiëren',
+ 'dashboard.copySuffix': 'kopie',
+ 'dashboard.restore': 'Herstellen',
+ 'dashboard.archived': 'Gearchiveerd',
+ 'dashboard.status.ongoing': 'Lopend',
+ 'dashboard.status.today': 'Vandaag',
+ 'dashboard.status.tomorrow': 'Morgen',
+ 'dashboard.status.past': 'Afgelopen',
+ 'dashboard.status.daysLeft': 'nog {count} dagen',
+ 'dashboard.toast.loadError': 'Reizen laden mislukt',
+ 'dashboard.toast.created': 'Reis aangemaakt!',
+ 'dashboard.toast.createError': 'Reis aanmaken mislukt',
+ 'dashboard.toast.updated': 'Reis bijgewerkt!',
+ 'dashboard.toast.updateError': 'Reis bijwerken mislukt',
+ 'dashboard.toast.deleted': 'Reis verwijderd',
+ 'dashboard.toast.deleteError': 'Reis verwijderen mislukt',
+ 'dashboard.toast.archived': 'Reis gearchiveerd',
+ 'dashboard.toast.archiveError': 'Reis archiveren mislukt',
+ 'dashboard.toast.restored': 'Reis hersteld',
+ 'dashboard.toast.restoreError': 'Reis herstellen mislukt',
+ 'dashboard.toast.copied': 'Reis gekopieerd!',
+ 'dashboard.toast.copyError': 'Reis kopiëren mislukt',
+ 'dashboard.confirm.delete':
+ 'Reis "{title}" verwijderen? Alle plaatsen en plannen worden permanent verwijderd.',
+ 'dashboard.editTrip': 'Reis bewerken',
+ 'dashboard.createTrip': 'Nieuwe reis aanmaken',
+ 'dashboard.tripTitle': 'Titel',
+ 'dashboard.tripTitlePlaceholder': 'bijv. Zomer in Japan',
+ 'dashboard.tripDescription': 'Beschrijving',
+ 'dashboard.tripDescriptionPlaceholder': 'Waar gaat deze reis over?',
+ 'dashboard.startDate': 'Startdatum',
+ 'dashboard.endDate': 'Einddatum',
+ 'dashboard.dayCount': 'Aantal dagen',
+ 'dashboard.dayCountHint':
+ 'Hoeveel dagen te plannen wanneer er geen reisdata zijn ingesteld.',
+ 'dashboard.noDateHint':
+ 'Geen datum ingesteld — er worden standaard 7 dagen aangemaakt. Je kunt dit altijd wijzigen.',
+ 'dashboard.coverImage': 'Omslagafbeelding',
+ 'dashboard.addCoverImage': 'Omslagafbeelding toevoegen',
+ 'dashboard.addMembers': 'Reisgenoten',
+ 'dashboard.addMember': 'Lid toevoegen',
+ 'dashboard.coverSaved': 'Omslagafbeelding opgeslagen',
+ 'dashboard.coverUploadError': 'Uploaden mislukt',
+ 'dashboard.coverRemoveError': 'Verwijderen mislukt',
+ 'dashboard.titleRequired': 'Titel is verplicht',
+ 'dashboard.endDateError': 'Einddatum moet na de startdatum liggen',
+ 'dashboard.greeting.morning': 'Goedemorgen,',
+ 'dashboard.greeting.afternoon': 'Goedemiddag,',
+ 'dashboard.greeting.evening': 'Goedenavond,',
+ 'dashboard.mobile.liveNow': 'Nu live',
+ 'dashboard.mobile.tripProgress': 'Reisvoortgang',
+ 'dashboard.mobile.daysLeft': '{count} dagen over',
+ 'dashboard.mobile.places': 'Plaatsen',
+ 'dashboard.mobile.buddies': 'Reisgenoten',
+ 'dashboard.mobile.newTrip': 'Nieuwe reis',
+ 'dashboard.mobile.currency': 'Valuta',
+ 'dashboard.mobile.timezone': 'Tijdzone',
+ 'dashboard.mobile.upcomingTrips': 'Aankomende reizen',
+ 'dashboard.mobile.yourTrips': 'Jouw reizen',
+ 'dashboard.mobile.trips': 'reizen',
+ 'dashboard.mobile.starts': 'Begint',
+ 'dashboard.mobile.duration': 'Duur',
+ 'dashboard.mobile.day': 'dag',
+ 'dashboard.mobile.days': 'dagen',
+ 'dashboard.mobile.ongoing': 'Bezig',
+ 'dashboard.mobile.startsToday': 'Begint vandaag',
+ 'dashboard.mobile.tomorrow': 'Morgen',
+ 'dashboard.mobile.inDays': 'Over {count} dagen',
+ 'dashboard.mobile.inMonths': 'Over {count} maanden',
+ 'dashboard.mobile.completed': 'Voltooid',
+ 'dashboard.mobile.currencyConverter': 'Valutaomrekener',
+ 'dashboard.filter.planned': 'Gepland',
+ 'dashboard.hero.badgeLive': 'NU LIVE',
+ 'dashboard.hero.badgeToday': 'START VANDAAG',
+ 'dashboard.hero.badgeTomorrow': 'MORGEN',
+ 'dashboard.hero.badgeNext': 'VOLGENDE',
+ 'dashboard.hero.badgeRecent': 'RECENT',
+ 'dashboard.hero.tripDates': 'Reisdata',
+ 'dashboard.hero.noDates': 'Geen data ingesteld',
+ 'dashboard.hero.travelerOne': '{count} reiziger',
+ 'dashboard.hero.travelerMany': '{count} reizigers',
+ 'dashboard.hero.destinationOne': '{count} bestemming',
+ 'dashboard.hero.destinationMany': '{count} bestemmingen',
+ 'dashboard.hero.dayUnitOne': 'dag',
+ 'dashboard.hero.dayUnitMany': 'dagen',
+ 'dashboard.hero.dayLeft': 'Dag over',
+ 'dashboard.hero.daysLeft': 'Dagen over',
+ 'dashboard.hero.lastDay': 'Laatste dag',
+ 'dashboard.hero.untilStart': 'Tot start',
+ 'dashboard.hero.startsIn': 'Reis begint over',
+ 'dashboard.atlas.countriesVisited': 'Atlas · Bezochte landen',
+ 'dashboard.atlas.ofTotal': 'van {total}',
+ 'dashboard.atlas.tripsTotal': 'Reizen totaal',
+ 'dashboard.atlas.placesMapped': '{count} plaatsen in kaart',
+ 'dashboard.atlas.daysTraveled': 'Reisdagen',
+ 'dashboard.atlas.daysUnit': 'dagen',
+ 'dashboard.atlas.acrossAllTrips': 'over alle reizen',
+ 'dashboard.atlas.distanceFlown': 'Gevlogen afstand',
+ 'dashboard.atlas.kmUnit': 'km',
+ 'dashboard.atlas.aroundEquator': '≈ {count}× rond de evenaar',
+ 'dashboard.card.idea': 'Idee',
+ 'dashboard.card.buddyOne': 'Reisgenoot',
+ 'dashboard.fx.from': 'Van',
+ 'dashboard.fx.to': 'Naar',
+ 'dashboard.fx.unavailable': 'Koers niet beschikbaar',
+ 'dashboard.tz.searchPlaceholder': 'Tijdzone zoeken…',
+ 'dashboard.tz.empty': 'Nog geen andere tijdzones — voeg er een toe met +',
+ 'dashboard.upcoming.title': 'Aankomende reserveringen',
+ 'dashboard.upcoming.empty': 'Nog niets geboekt.',
+ 'dashboard.confirm.copy.title': 'Deze reis kopiëren?',
+ 'dashboard.confirm.copy.willCopy': 'Wordt gekopieerd',
+ 'dashboard.confirm.copy.will1': 'Dagen, plaatsen & dagindelingen',
+ 'dashboard.confirm.copy.will2': 'Accommodaties & reserveringen',
+ 'dashboard.confirm.copy.will3': 'Budgetposten & categorievolgorde',
+ 'dashboard.confirm.copy.will4': 'Paklijsten (niet afgevinkt)',
+ 'dashboard.confirm.copy.will5': 'Taken (niet toegewezen & niet afgevinkt)',
+ 'dashboard.confirm.copy.will6': 'Dagnotities',
+ 'dashboard.confirm.copy.wontCopy': 'Wordt niet gekopieerd',
+ 'dashboard.confirm.copy.wont1': 'Medewerkers & ledentoewijzingen',
+ 'dashboard.confirm.copy.wont2': 'Gedeelde notities, peilingen & berichten',
+ 'dashboard.confirm.copy.wont3': "Bestanden & foto's",
+ 'dashboard.confirm.copy.wont4': 'Deeltokens',
+ 'dashboard.confirm.copy.confirm': 'Reis kopiëren',
+ 'dashboard.aria.toggleView': 'Weergave wisselen',
+ 'dashboard.aria.filter': 'Filter',
+ 'dashboard.aria.duplicate': 'Dupliceren',
+ 'dashboard.aria.refreshRates': 'Koersen vernieuwen',
+ 'dashboard.aria.swapCurrencies': "Valuta's omwisselen",
+ 'dashboard.aria.addTimezone': 'Tijdzone toevoegen',
+ 'dashboard.aria.removeTimezone': '{city} verwijderen',
+ 'dashboard.dayCountRequired': 'Aantal dagen is verplicht',
+};
+export default dashboard;
diff --git a/shared/src/i18n/nl/day.ts b/shared/src/i18n/nl/day.ts
new file mode 100644
index 0000000000000000000000000000000000000000..055faae17f387726ea8b73e8ac536728a2f5698c
--- /dev/null
+++ b/shared/src/i18n/nl/day.ts
@@ -0,0 +1,27 @@
+import type { TranslationStrings } from '../types';
+
+const day: TranslationStrings = {
+ 'day.precipProb': 'Regenkans',
+ 'day.precipitation': 'Neerslag',
+ 'day.wind': 'Wind',
+ 'day.sunrise': 'Zonsopgang',
+ 'day.sunset': 'Zonsondergang',
+ 'day.hourlyForecast': 'Uurlijkse voorspelling',
+ 'day.climateHint':
+ 'Historische gemiddelden — echte voorspelling beschikbaar binnen 16 dagen voor deze datum.',
+ 'day.noWeather':
+ 'Geen weergegevens beschikbaar. Voeg een plaats met coördinaten toe.',
+ 'day.overview': 'Dagoverzicht',
+ 'day.accommodation': 'Accommodatie',
+ 'day.addAccommodation': 'Accommodatie toevoegen',
+ 'day.hotelDayRange': 'Toepassen op dagen',
+ 'day.noPlacesForHotel': 'Voeg eerst plaatsen toe aan je reis',
+ 'day.allDays': 'Alle',
+ 'day.checkIn': 'Inchecken',
+ 'day.checkInUntil': 'Tot',
+ 'day.checkOut': 'Uitchecken',
+ 'day.confirmation': 'Bevestiging',
+ 'day.editAccommodation': 'Accommodatie bewerken',
+ 'day.reservations': 'Reserveringen',
+};
+export default day;
diff --git a/shared/src/i18n/nl/dayplan.ts b/shared/src/i18n/nl/dayplan.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9070dc9d654889ea54bf9939776b8e6ed0f2e0b4
--- /dev/null
+++ b/shared/src/i18n/nl/dayplan.ts
@@ -0,0 +1,61 @@
+import type { TranslationStrings } from '../types';
+
+const dayplan: TranslationStrings = {
+ 'dayplan.icsTooltip': 'Kalender exporteren (ICS)',
+ 'dayplan.emptyDay': 'Geen plaatsen gepland voor deze dag',
+ 'dayplan.addNote': 'Notitie toevoegen',
+ 'dayplan.editNote': 'Notitie bewerken',
+ 'dayplan.noteAdd': 'Notitie toevoegen',
+ 'dayplan.noteEdit': 'Notitie bewerken',
+ 'dayplan.noteTitle': 'Notitie',
+ 'dayplan.noteSubtitle': 'Dagnotitie',
+ 'dayplan.totalCost': 'Totale kosten',
+ 'dayplan.days': 'Dagen',
+ 'dayplan.dayN': 'Dag {n}',
+ 'dayplan.calculating': 'Berekenen...',
+ 'dayplan.route': 'Route',
+ 'dayplan.optimize': 'Optimaliseren',
+ 'dayplan.optimized': 'Route geoptimaliseerd',
+ 'dayplan.routeError': 'Route berekenen mislukt',
+ 'dayplan.toast.needTwoPlaces':
+ 'Minimaal twee plaatsen nodig voor route-optimalisatie',
+ 'dayplan.toast.routeOptimized': 'Route geoptimaliseerd',
+ 'dayplan.toast.routeOptimizedFromHotel':
+ 'Route geoptimaliseerd vanaf je accommodatie',
+ 'dayplan.toast.noGeoPlaces':
+ 'Geen plaatsen met coördinaten gevonden voor routeberekening',
+ 'dayplan.confirmed': 'Bevestigd',
+ 'dayplan.pendingRes': 'In behandeling',
+ 'dayplan.pdf': 'PDF',
+ 'dayplan.pdfTooltip': 'Dagplan exporteren als PDF',
+ 'dayplan.pdfError': 'PDF-export mislukt',
+ 'dayplan.cannotReorderTransport':
+ 'Boekingen met een vast tijdstip kunnen niet worden verplaatst',
+ 'dayplan.confirmRemoveTimeTitle': 'Tijd verwijderen?',
+ 'dayplan.confirmRemoveTimeBody':
+ 'Deze plek heeft een vast tijdstip ({time}). Verplaatsen verwijdert het tijdstip en maakt vrije sortering mogelijk.',
+ 'dayplan.confirmRemoveTimeAction': 'Tijd verwijderen en verplaatsen',
+ 'dayplan.confirmDeleteNoteTitle': 'Notitie verwijderen?',
+ 'dayplan.confirmDeleteNoteBody': 'Deze notitie wordt definitief verwijderd.',
+ 'dayplan.cannotDropOnTimed':
+ 'Items kunnen niet tussen tijdgebonden items worden geplaatst',
+ 'dayplan.cannotBreakChronology':
+ 'Dit zou de chronologische volgorde van geplande items en boekingen doorbreken',
+ 'dayplan.mobile.addPlace': 'Plaats toevoegen',
+ 'dayplan.mobile.searchPlaces': 'Plaatsen zoeken...',
+ 'dayplan.mobile.allAssigned': 'Alle plaatsen toegewezen',
+ 'dayplan.mobile.noMatch': 'Geen resultaat',
+ 'dayplan.mobile.createNew': 'Nieuwe plaats aanmaken',
+ 'dayplan.expandAll': 'Expand all days', // en-fallback
+ 'dayplan.collapseAll': 'Collapse all days', // en-fallback
+ 'dayplan.reorderDays': 'Dagen herordenen',
+ 'dayplan.reorderTitle': 'Dagen herordenen',
+ 'dayplan.reorderHint': 'De plaatsen, notities en boekingen van een dag gaan mee.',
+ 'dayplan.addDay': 'Dag toevoegen',
+ 'dayplan.moveUp': 'Omhoog',
+ 'dayplan.moveDown': 'Omlaag',
+ 'dayplan.reorderUndo': 'Dagen herordenen',
+ 'dayplan.reorderError': 'Dagen herordenen mislukt',
+ 'dayplan.addDayError': 'Dag toevoegen mislukt',
+};
+export default dayplan;
diff --git a/shared/src/i18n/nl/externalNotifications.ts b/shared/src/i18n/nl/externalNotifications.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5b9af4a19589b7e961a526bf26fd2da3b1999959
--- /dev/null
+++ b/shared/src/i18n/nl/externalNotifications.ts
@@ -0,0 +1,63 @@
+import type { NotificationLocale } from '../externalNotifications/types';
+
+const nl: NotificationLocale = {
+ email: {
+ footer: 'Je ontvangt dit omdat je meldingen hebt ingeschakeld in TREK.',
+ manage: 'Voorkeuren beheren',
+ madeWith: 'Made with',
+ openTrek: 'TREK openen',
+ },
+ events: {
+ trip_invite: (p) => ({
+ title: `Uitnodiging voor "${p.trip}"`,
+ body: `${p.actor} heeft ${p.invitee || 'een lid'} uitgenodigd voor de reis "${p.trip}".`,
+ }),
+ booking_change: (p) => ({
+ title: `Nieuwe boeking: ${p.booking}`,
+ body: `${p.actor} heeft een boeking "${p.booking}" (${p.type}) toegevoegd aan "${p.trip}".`,
+ }),
+ trip_reminder: (p) => ({
+ title: `Reisherinnering: ${p.trip}`,
+ body: `Je reis "${p.trip}" komt eraan!`,
+ }),
+ todo_due: (p) => ({
+ title: `Taak verloopt: ${p.todo}`,
+ body: `"${p.todo}" in "${p.trip}" verloopt op ${p.due}.`,
+ }),
+ vacay_invite: (p) => ({
+ title: 'Vacay Fusion uitnodiging',
+ body: `${p.actor} nodigt je uit om vakantieplannen te fuseren. Open TREK om te accepteren of af te wijzen.`,
+ }),
+ photos_shared: (p) => ({
+ title: `${p.count} foto's gedeeld`,
+ body: `${p.actor} heeft ${p.count} foto('s) gedeeld in "${p.trip}".`,
+ }),
+ collab_message: (p) => ({
+ title: `Nieuw bericht in "${p.trip}"`,
+ body: `${p.actor}: ${p.preview}`,
+ }),
+ packing_tagged: (p) => ({
+ title: `Paklijst: ${p.category}`,
+ body: `${p.actor} heeft je toegewezen aan de categorie "${p.category}" in "${p.trip}".`,
+ }),
+ version_available: (p) => ({
+ title: 'Nieuwe TREK-versie beschikbaar',
+ body: `TREK ${p.version} is nu beschikbaar. Bezoek het beheerderspaneel om bij te werken.`,
+ }),
+ synology_session_cleared: () => ({
+ title: 'Synology-sessie gewist',
+ body: 'Je Synology-account of URL is gewijzigd. Je bent uitgelogd bij Synology Photos.',
+ }),
+ },
+ passwordReset: {
+ subject: 'Reset je wachtwoord',
+ greeting: 'Hallo',
+ body: 'We hebben een verzoek ontvangen om het wachtwoord voor je TREK-account te resetten. Klik op de knop hieronder om een nieuw wachtwoord in te stellen.',
+ ctaIntro: 'Wachtwoord resetten',
+ expiry: 'Deze link verloopt over 60 minuten.',
+ ignore:
+ 'Als jij dit niet hebt aangevraagd, kun je deze e-mail negeren — je wachtwoord blijft ongewijzigd.',
+ },
+};
+
+export default nl;
diff --git a/shared/src/i18n/nl/files.ts b/shared/src/i18n/nl/files.ts
new file mode 100644
index 0000000000000000000000000000000000000000..be63097e4bdf4fa2f1e5893031cf2cd13a7576a3
--- /dev/null
+++ b/shared/src/i18n/nl/files.ts
@@ -0,0 +1,63 @@
+import type { TranslationStrings } from '../types';
+
+const files: TranslationStrings = {
+ 'files.title': 'Bestanden',
+ 'files.pageTitle': 'Bestanden en documenten',
+ 'files.subtitle': '{count} bestanden voor {trip}',
+ 'files.download': 'Downloaden',
+ 'files.openError': 'Bestand kon niet worden geopend',
+ 'files.downloadPdf': 'PDF downloaden',
+ 'files.count': '{count} bestanden',
+ 'files.countSingular': '1 bestand',
+ 'files.uploaded': '{count} geüpload',
+ 'files.uploadError': 'Uploaden mislukt',
+ 'files.dropzone': 'Sleep bestanden hierheen',
+ 'files.dropzoneHint': 'of klik om te bladeren',
+ 'files.allowedTypes':
+ 'Afbeeldingen, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Max 50 MB',
+ 'files.uploading': 'Uploaden...',
+ 'files.filterAll': 'Alle',
+ 'files.filterPdf': "PDF's",
+ 'files.filterImages': 'Afbeeldingen',
+ 'files.filterDocs': 'Documenten',
+ 'files.filterCollab': 'Collab-notities',
+ 'files.sourceCollab': 'Uit Collab-notities',
+ 'files.empty': 'Nog geen bestanden',
+ 'files.emptyHint': 'Upload bestanden om ze aan je reis toe te voegen',
+ 'files.openTab': 'Openen in nieuw tabblad',
+ 'files.confirm.delete': 'Weet je zeker dat je dit bestand wilt verwijderen?',
+ 'files.toast.deleted': 'Bestand verwijderd',
+ 'files.toast.deleteError': 'Bestand verwijderen mislukt',
+ 'files.sourcePlan': 'Dagplan',
+ 'files.sourceBooking': 'Boeking',
+ 'files.sourceTransport': 'Transport',
+ 'files.attach': 'Bijvoegen',
+ 'files.pasteHint':
+ 'Je kunt ook afbeeldingen plakken vanuit het klembord (Ctrl+V)',
+ 'files.trash': 'Prullenbak',
+ 'files.trashEmpty': 'Prullenbak is leeg',
+ 'files.emptyTrash': 'Prullenbak legen',
+ 'files.restore': 'Herstellen',
+ 'files.star': 'Ster',
+ 'files.unstar': 'Ster verwijderen',
+ 'files.assign': 'Toewijzen',
+ 'files.assignTitle': 'Bestand toewijzen',
+ 'files.assignPlace': 'Plaats',
+ 'files.assignBooking': 'Boeking',
+ 'files.assignTransport': 'Transport',
+ 'files.unassigned': 'Niet toegewezen',
+ 'files.unlink': 'Koppeling verwijderen',
+ 'files.toast.trashed': 'Naar prullenbak verplaatst',
+ 'files.toast.restored': 'Bestand hersteld',
+ 'files.toast.trashEmptied': 'Prullenbak geleegd',
+ 'files.toast.assigned': 'Bestand toegewezen',
+ 'files.toast.assignError': 'Toewijzing mislukt',
+ 'files.toast.restoreError': 'Herstellen mislukt',
+ 'files.confirm.permanentDelete':
+ 'Dit bestand permanent verwijderen? Dit kan niet ongedaan worden gemaakt.',
+ 'files.confirm.emptyTrash':
+ 'Alle bestanden in de prullenbak permanent verwijderen? Dit kan niet ongedaan worden gemaakt.',
+ 'files.noteLabel': 'Notitie',
+ 'files.notePlaceholder': 'Notitie toevoegen...',
+};
+export default files;
diff --git a/shared/src/i18n/nl/index.ts b/shared/src/i18n/nl/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..77aeb6a09fb62e2522e8b4c04e4169a94c2f8d99
--- /dev/null
+++ b/shared/src/i18n/nl/index.ts
@@ -0,0 +1,86 @@
+import admin from './admin';
+import airport from './airport';
+import atlas from './atlas';
+import backup from './backup';
+import budget from './budget';
+import categories from './categories';
+import collab from './collab';
+import common from './common';
+import dashboard from './dashboard';
+import day from './day';
+import dayplan from './dayplan';
+import files from './files';
+import inspector from './inspector';
+import journey from './journey';
+import login from './login';
+import map from './map';
+import members from './members';
+import memories from './memories';
+import nav from './nav';
+import notif from './notif';
+import notifications from './notifications';
+import oauth from './oauth';
+import packing from './packing';
+import pdf from './pdf';
+import perm from './perm';
+import photos from './photos';
+import places from './places';
+import planner from './planner';
+import register from './register';
+import reservations from './reservations';
+import settings from './settings';
+import share from './share';
+import shared from './shared';
+import stats from './stats';
+import system_notice from './system_notice';
+import todo from './todo';
+import transport from './transport';
+import trip from './trip';
+import trips from './trips';
+import undo from './undo';
+import vacay from './vacay';
+
+const locale = {
+ ...common,
+ ...trips,
+ ...nav,
+ ...dashboard,
+ ...settings,
+ ...admin,
+ ...dayplan,
+ ...share,
+ ...shared,
+ ...login,
+ ...register,
+ ...vacay,
+ ...atlas,
+ ...trip,
+ ...places,
+ ...inspector,
+ ...reservations,
+ ...airport,
+ ...map,
+ ...budget,
+ ...files,
+ ...packing,
+ ...members,
+ ...categories,
+ ...backup,
+ ...photos,
+ ...pdf,
+ ...planner,
+ ...stats,
+ ...day,
+ ...memories,
+ ...collab,
+ ...perm,
+ ...undo,
+ ...notifications,
+ ...todo,
+ ...notif,
+ ...journey,
+ ...oauth,
+ ...system_notice,
+ ...transport,
+};
+export default locale;
diff --git a/shared/src/i18n/nl/inspector.ts b/shared/src/i18n/nl/inspector.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a7727eec4a6d44ccd9d5c010792598011f497d23
--- /dev/null
+++ b/shared/src/i18n/nl/inspector.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const inspector: TranslationStrings = {
+ 'inspector.opened': 'Openingstijden',
+ 'inspector.closed': 'Gesloten',
+ 'inspector.openingHours': 'Openingstijden',
+ 'inspector.showHours': 'Openingstijden tonen',
+ 'inspector.files': 'Bestanden',
+ 'inspector.filesCount': '{count} bestanden',
+ 'inspector.removeFromDay': 'Verwijderen van dag',
+ 'inspector.remove': 'Verwijderen',
+ 'inspector.addToDay': 'Toevoegen aan dag',
+ 'inspector.confirmedRes': 'Bevestigde reservering',
+ 'inspector.pendingRes': 'Reservering in behandeling',
+ 'inspector.google': 'Openen in Google Maps',
+ 'inspector.website': 'Website openen',
+ 'inspector.addRes': 'Reservering',
+ 'inspector.editRes': 'Reservering bewerken',
+ 'inspector.participants': 'Deelnemers',
+ 'inspector.trackStats': 'Routegegevens',
+};
+export default inspector;
diff --git a/shared/src/i18n/nl/journey.ts b/shared/src/i18n/nl/journey.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2d83b077b9a6848bd82ee69bc5ef727d9f535e8f
--- /dev/null
+++ b/shared/src/i18n/nl/journey.ts
@@ -0,0 +1,245 @@
+import type { TranslationStrings } from '../types';
+
+const journey: TranslationStrings = {
+ 'journey.search.placeholder': 'Reizen zoeken…',
+ 'journey.search.noResults': 'Geen reizen komen overeen met "{query}"',
+ 'journey.title': 'Reisverslag',
+ 'journey.subtitle': 'Leg je reizen vast terwijl je onderweg bent',
+ 'journey.new': 'Nieuw reisverslag',
+ 'journey.create': 'Aanmaken',
+ 'journey.titlePlaceholder': 'Waar ga je naartoe?',
+ 'journey.empty': 'Nog geen reisverslagen',
+ 'journey.emptyHint': 'Begin met het vastleggen van je volgende reis',
+ 'journey.deleted': 'Reisverslag verwijderd',
+ 'journey.createError': 'Kon reisverslag niet aanmaken',
+ 'journey.deleteError': 'Kon reisverslag niet verwijderen',
+ 'journey.deleteConfirmTitle': 'Verwijderen',
+ 'journey.deleteConfirmMessage':
+ '"{title}" verwijderen? Dit kan niet ongedaan worden gemaakt.',
+ 'journey.deleteConfirmGeneric': 'Weet je zeker dat je dit wilt verwijderen?',
+ 'journey.notFound': 'Reisverslag niet gevonden',
+ 'journey.photos': "Foto's",
+ 'journey.timelineEmpty': 'Nog geen stops',
+ 'journey.timelineEmptyHint':
+ 'Voeg een check-in toe of schrijf een dagboekvermelding om te beginnen',
+ 'journey.status.draft': 'Concept',
+ 'journey.status.active': 'Actief',
+ 'journey.status.completed': 'Voltooid',
+ 'journey.status.upcoming': 'Gepland',
+ 'journey.status.archived': 'Gearchiveerd',
+ 'journey.checkin.add': 'Inchecken',
+ 'journey.checkin.namePlaceholder': 'Locatienaam',
+ 'journey.checkin.notesPlaceholder': 'Notities (optioneel)',
+ 'journey.checkin.save': 'Opslaan',
+ 'journey.checkin.error': 'Kon check-in niet opslaan',
+ 'journey.entry.add': 'Dagboek',
+ 'journey.entry.edit': 'Vermelding bewerken',
+ 'journey.entry.titlePlaceholder': 'Titel (optioneel)',
+ 'journey.entry.bodyPlaceholder': 'Wat is er vandaag gebeurd?',
+ 'journey.entry.save': 'Opslaan',
+ 'journey.entry.error': 'Kon vermelding niet opslaan',
+ 'journey.photo.add': 'Foto',
+ 'journey.photo.uploadError': 'Uploaden mislukt',
+ 'journey.share.share': 'Delen',
+ 'journey.share.public': 'Openbaar',
+ 'journey.share.linkCopied': 'Openbare link gekopieerd',
+ 'journey.share.disabled': 'Openbaar delen uitgeschakeld',
+ 'journey.editor.titlePlaceholder': 'Geef dit moment een naam...',
+ 'journey.editor.bodyPlaceholder': 'Vertel het verhaal van deze dag...',
+ 'journey.editor.placePlaceholder': 'Locatie (optioneel)',
+ 'journey.editor.tagsPlaceholder':
+ 'Tags: verborgen parel, beste maaltijd, moet terugkomen...',
+ 'journey.visibility.private': 'Privé',
+ 'journey.visibility.shared': 'Gedeeld',
+ 'journey.visibility.public': 'Openbaar',
+ 'journey.emptyState.title': 'Je verhaal begint hier',
+ 'journey.emptyState.subtitle':
+ 'Check in op een plek of schrijf je eerste dagboekvermelding',
+ 'journey.frontpage.subtitle':
+ 'Maak van je reizen verhalen die je nooit vergeet',
+ 'journey.frontpage.createJourney': 'Reisverslag aanmaken',
+ 'journey.frontpage.activeJourney': 'Actief reisverslag',
+ 'journey.frontpage.allJourneys': 'Alle reisverslagen',
+ 'journey.frontpage.journeys': 'reisverslagen',
+ 'journey.frontpage.createNew': 'Nieuw reisverslag aanmaken',
+ 'journey.frontpage.createNewSub':
+ 'Kies reizen, schrijf verhalen, deel je avonturen',
+ 'journey.frontpage.live': 'Live',
+ 'journey.frontpage.synced': 'Gesynchroniseerd',
+ 'journey.frontpage.continueWriting': 'Verder schrijven',
+ 'journey.frontpage.updated': 'Bijgewerkt {time}',
+ 'journey.frontpage.suggestionLabel': 'Reis net afgelopen',
+ 'journey.frontpage.suggestionText':
+ 'Maak van {title} een reisverslag',
+ 'journey.frontpage.dismiss': 'Sluiten',
+ 'journey.frontpage.journeyName': 'Naam reisverslag',
+ 'journey.frontpage.namePlaceholder': 'bijv. Zuidoost-Azië 2026',
+ 'journey.frontpage.selectTrips': 'Selecteer reizen',
+ 'journey.frontpage.tripsSelected': 'reizen geselecteerd',
+ 'journey.frontpage.trips': 'reizen',
+ 'journey.frontpage.placesImported': 'plaatsen worden geïmporteerd',
+ 'journey.frontpage.places': 'plaatsen',
+ 'journey.detail.backToJourney': 'Terug naar reisverslag',
+ 'journey.detail.syncedWithTrips': 'Gesynchroniseerd met reizen',
+ 'journey.detail.addEntry': 'Vermelding toevoegen',
+ 'journey.detail.newEntry': 'Nieuwe vermelding',
+ 'journey.detail.editEntry': 'Vermelding bewerken',
+ 'journey.detail.noEntries': 'Nog geen vermeldingen',
+ 'journey.detail.noEntriesHint':
+ 'Voeg een reis toe om te beginnen met skeletvermeldingen',
+ 'journey.detail.noPhotos': "Nog geen foto's",
+ 'journey.detail.noPhotosHint':
+ "Upload foto's naar vermeldingen of blader door je Immich/Synology-bibliotheek",
+ 'journey.detail.journeyStats': 'Reisstatistieken',
+ 'journey.detail.syncedTrips': 'Gesynchroniseerde reizen',
+ 'journey.detail.noTripsLinked': 'Nog geen reizen gekoppeld',
+ 'journey.detail.contributors': 'Bijdragers',
+ 'journey.detail.readMore': 'Lees meer',
+ 'journey.detail.prosCons': 'Voor- & nadelen',
+ 'journey.detail.photos': "foto's",
+ 'journey.detail.day': 'Dag {number}',
+ 'journey.detail.places': 'plaatsen',
+ 'journey.stats.days': 'Dagen',
+ 'journey.stats.cities': 'Steden',
+ 'journey.stats.entries': 'Vermeldingen',
+ 'journey.stats.photos': "Foto's",
+ 'journey.stats.places': 'Plaatsen',
+ 'journey.skeletons.show': 'Suggesties tonen',
+ 'journey.skeletons.hide': 'Suggesties verbergen',
+ 'journey.verdict.lovedIt': 'Geweldig',
+ 'journey.verdict.couldBeBetter': 'Kan beter',
+ 'journey.synced.places': 'plaatsen',
+ 'journey.synced.synced': 'gesynchroniseerd',
+ 'journey.editor.discardChangesConfirm':
+ 'Je hebt niet-opgeslagen wijzigingen. Verwerpen?',
+ 'journey.editor.uploadFailed': 'Foto uploaden mislukt',
+ 'journey.editor.uploadPhotos': "Foto's uploaden",
+ 'journey.editor.uploading': 'Uploaden...',
+ 'journey.editor.uploadingProgress': 'Uploaden {done}/{total}…',
+ 'journey.editor.uploadPartialFailed':
+ "{failed} van {total} foto's mislukt — sla opnieuw op om het opnieuw te proberen",
+ 'journey.editor.fromGallery': 'Uit galerij',
+ 'journey.editor.allPhotosAdded': "Alle foto's al toegevoegd",
+ 'journey.editor.writeStory': 'Schrijf je verhaal...',
+ 'journey.editor.prosCons': 'Voor- & nadelen',
+ 'journey.editor.pros': 'Voordelen',
+ 'journey.editor.cons': 'Nadelen',
+ 'journey.editor.proPlaceholder': 'Iets geweldigs...',
+ 'journey.editor.conPlaceholder': 'Niet zo geweldig...',
+ 'journey.editor.addAnother': 'Nog een toevoegen',
+ 'journey.editor.date': 'Datum',
+ 'journey.editor.location': 'Locatie',
+ 'journey.editor.searchLocation': 'Locatie zoeken...',
+ 'journey.editor.mood': 'Stemming',
+ 'journey.editor.weather': 'Weer',
+ 'journey.editor.photoFirst': '1e',
+ 'journey.editor.makeFirst': 'Maak 1e',
+ 'journey.editor.searching': 'Zoeken...',
+ 'journey.mood.amazing': 'Fantastisch',
+ 'journey.mood.good': 'Goed',
+ 'journey.mood.neutral': 'Neutraal',
+ 'journey.mood.rough': 'Zwaar',
+ 'journey.weather.sunny': 'Zonnig',
+ 'journey.weather.partly': 'Halfbewolkt',
+ 'journey.weather.cloudy': 'Bewolkt',
+ 'journey.weather.rainy': 'Regenachtig',
+ 'journey.weather.stormy': 'Stormachtig',
+ 'journey.weather.cold': 'Sneeuw',
+ 'journey.trips.linkTrip': 'Reis koppelen',
+ 'journey.trips.searchTrip': 'Reis zoeken',
+ 'journey.trips.searchPlaceholder': 'Reisnaam of bestemming...',
+ 'journey.trips.noTripsAvailable': 'Geen reizen beschikbaar',
+ 'journey.trips.link': 'Koppelen',
+ 'journey.trips.tripLinked': 'Reis gekoppeld',
+ 'journey.trips.linkFailed': 'Koppelen van reis mislukt',
+ 'journey.trips.addTrip': 'Reis toevoegen',
+ 'journey.trips.unlinkTrip': 'Reis ontkoppelen',
+ 'journey.trips.unlinkMessage':
+ '"{title}" ontkoppelen? Alle gesynchroniseerde vermeldingen en foto\'s van deze reis worden permanent verwijderd. Dit kan niet ongedaan worden gemaakt.',
+ 'journey.trips.unlink': 'Ontkoppelen',
+ 'journey.trips.tripUnlinked': 'Reis ontkoppeld',
+ 'journey.trips.unlinkFailed': 'Ontkoppelen van reis mislukt',
+ 'journey.trips.noTripsLinkedSettings': 'Geen reizen gekoppeld',
+ 'journey.contributors.invite': 'Bijdrager uitnodigen',
+ 'journey.contributors.searchUser': 'Gebruiker zoeken',
+ 'journey.contributors.searchPlaceholder': 'Gebruikersnaam of e-mail...',
+ 'journey.contributors.noUsers': 'Geen gebruikers gevonden',
+ 'journey.contributors.role': 'Rol',
+ 'journey.contributors.added': 'Bijdrager toegevoegd',
+ 'journey.contributors.addFailed': 'Toevoegen van bijdrager mislukt',
+ 'journey.share.publicShare': 'Openbaar delen',
+ 'journey.share.createLink': 'Deellink aanmaken',
+ 'journey.share.linkCreated': 'Deellink aangemaakt',
+ 'journey.share.createFailed': 'Aanmaken van link mislukt',
+ 'journey.share.copy': 'Kopiëren',
+ 'journey.share.copied': 'Gekopieerd!',
+ 'journey.share.timeline': 'Tijdlijn',
+ 'journey.share.gallery': 'Galerij',
+ 'journey.share.map': 'Kaart',
+ 'journey.share.removeLink': 'Deellink verwijderen',
+ 'journey.share.linkDeleted': 'Deellink verwijderd',
+ 'journey.share.deleteFailed': 'Verwijderen mislukt',
+ 'journey.share.updateFailed': 'Bijwerken mislukt',
+ 'journey.invite.role': 'Rol',
+ 'journey.invite.viewer': 'Kijker',
+ 'journey.invite.editor': 'Bewerker',
+ 'journey.invite.invite': 'Uitnodigen',
+ 'journey.invite.inviting': 'Uitnodigen...',
+ 'journey.settings.title': 'Reisverslaginstellingen',
+ 'journey.settings.coverImage': 'Omslagfoto',
+ 'journey.settings.changeCover': 'Omslag wijzigen',
+ 'journey.settings.addCover': 'Omslagfoto toevoegen',
+ 'journey.settings.name': 'Naam',
+ 'journey.settings.subtitle': 'Ondertitel',
+ 'journey.settings.subtitlePlaceholder': 'bijv. Thailand, Vietnam & Cambodja',
+ 'journey.settings.endJourney': 'Reis archiveren',
+ 'journey.settings.reopenJourney': 'Reis herstellen',
+ 'journey.settings.archived': 'Reis gearchiveerd',
+ 'journey.settings.reopened': 'Reis heropend',
+ 'journey.settings.endDescription':
+ 'Verbergt het Live-badge. Je kunt het altijd heropenen.',
+ 'journey.settings.delete': 'Verwijderen',
+ 'journey.settings.deleteJourney': 'Reisverslag verwijderen',
+ 'journey.settings.deleteMessage':
+ '"{title}" verwijderen? Alle vermeldingen en foto\'s gaan verloren.',
+ 'journey.settings.saved': 'Instellingen opgeslagen',
+ 'journey.settings.saveFailed': 'Opslaan mislukt',
+ 'journey.settings.coverUpdated': 'Omslag bijgewerkt',
+ 'journey.settings.coverFailed': 'Uploaden mislukt',
+ 'journey.settings.failedToDelete': 'Verwijderen mislukt',
+ 'journey.entries.deleteTitle': 'Vermelding verwijderen',
+ 'journey.photosUploaded': "{count} foto's geüpload",
+ 'journey.photosUploadFailed': "Sommige foto's konden niet worden geüpload",
+ 'journey.photosAdded': "{count} foto's toegevoegd",
+ 'journey.public.notFound': 'Niet gevonden',
+ 'journey.public.notFoundMessage':
+ 'Dit reisverslag bestaat niet of de link is verlopen.',
+ 'journey.public.readOnly': 'Alleen-lezen · Openbaar reisverslag',
+ 'journey.public.tagline': 'Travel Resource & Exploration Kit',
+ 'journey.public.sharedVia': 'Gedeeld via',
+ 'journey.public.madeWith': 'Gemaakt met',
+ 'journey.pdf.journeyBook': 'Reisboek',
+ 'journey.pdf.madeWith': 'Gemaakt met TREK',
+ 'journey.pdf.day': 'Dag',
+ 'journey.pdf.theEnd': 'Einde',
+ 'journey.pdf.saveAsPdf': 'Opslaan als PDF',
+ 'journey.pdf.pages': "pagina's",
+ 'journey.picker.tripPeriod': 'Reisperiode',
+ 'journey.picker.dateRange': 'Datumbereik',
+ 'journey.picker.allPhotos': "Alle foto's",
+ 'journey.picker.albums': 'Albums',
+ 'journey.picker.selected': 'geselecteerd',
+ 'journey.picker.addTo': 'Toevoegen aan',
+ 'journey.picker.newGallery': 'Nieuwe galerij',
+ 'journey.picker.selectAll': 'Alles selecteren',
+ 'journey.picker.deselectAll': 'Alles deselecteren',
+ 'journey.picker.noAlbums': 'Geen albums gevonden',
+ 'journey.picker.selectDate': 'Selecteer datum',
+ 'journey.picker.search': 'Zoeken',
+ 'journey.detail.journeyTab': 'Journey', // en-fallback
+ 'journey.contributors.remove': 'Remove contributor', // en-fallback
+ 'journey.contributors.removeConfirm': 'Remove {username} from this journey?', // en-fallback
+ 'journey.contributors.removed': 'Contributor removed', // en-fallback
+ 'journey.contributors.removeFailed': 'Failed to remove contributor', // en-fallback
+};
+export default journey;
diff --git a/shared/src/i18n/nl/login.ts b/shared/src/i18n/nl/login.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3e681ccdf19effe8247d30f8d97858031a2e58ec
--- /dev/null
+++ b/shared/src/i18n/nl/login.ts
@@ -0,0 +1,101 @@
+import type { TranslationStrings } from '../types';
+
+const login: TranslationStrings = {
+ 'login.error': 'Inloggen mislukt. Controleer je inloggegevens.',
+ 'login.tagline': 'Jouw reizen.\nJouw plan.',
+ 'login.description':
+ 'Plan reizen samen met interactieve kaarten, budgetten en realtime synchronisatie.',
+ 'login.features.maps': 'Interactieve kaarten',
+ 'login.features.mapsDesc': 'Google Places, routes en clustering',
+ 'login.features.realtime': 'Realtime synchronisatie',
+ 'login.features.realtimeDesc': 'Plan samen via WebSocket',
+ 'login.features.budget': 'Budgetbeheer',
+ 'login.features.budgetDesc': 'Categorieën, grafieken en kosten per persoon',
+ 'login.features.collab': 'Samenwerking',
+ 'login.features.collabDesc': 'Meerdere gebruikers met gedeelde reizen',
+ 'login.features.packing': 'Paklijsten',
+ 'login.features.packingDesc': 'Categorieën, voortgang en suggesties',
+ 'login.features.bookings': 'Reserveringen',
+ 'login.features.bookingsDesc': 'Vluchten, hotels, restaurants en meer',
+ 'login.features.files': 'Documenten',
+ 'login.features.filesDesc': 'Upload en beheer documenten',
+ 'login.features.routes': 'Slimme routes',
+ 'login.features.routesDesc':
+ 'Automatisch optimaliseren en exporteren naar Google Maps',
+ 'login.selfHosted':
+ 'Zelf gehost · Open Source · Jouw gegevens blijven van jou',
+ 'login.title': 'Inloggen',
+ 'login.subtitle': 'Welkom terug',
+ 'login.signingIn': 'Inloggen…',
+ 'login.signIn': 'Inloggen',
+ 'login.createAdmin': 'Beheerdersaccount aanmaken',
+ 'login.createAdminHint': 'Stel het eerste beheerdersaccount in voor TREK.',
+ 'login.setNewPassword': 'Nieuw wachtwoord instellen',
+ 'login.setNewPasswordHint':
+ 'U moet uw wachtwoord wijzigen voordat u verder kunt gaan.',
+ 'login.createAccount': 'Account aanmaken',
+ 'login.createAccountHint': 'Registreer een nieuw account.',
+ 'login.creating': 'Aanmaken…',
+ 'login.noAccount': 'Nog geen account?',
+ 'login.hasAccount': 'Heb je al een account?',
+ 'login.register': 'Registreren',
+ 'login.emailPlaceholder': 'jouw@email.com',
+ 'login.username': 'Gebruikersnaam',
+ 'login.oidc.registrationDisabled':
+ 'Registratie is uitgeschakeld. Neem contact op met je beheerder.',
+ 'login.oidc.noEmail': 'Geen e-mailadres ontvangen van de provider.',
+ 'login.mfaTitle': 'Tweefactorauthenticatie',
+ 'login.mfaSubtitle': 'Voer de 6-cijferige code van je authenticator-app in.',
+ 'login.mfaCodeLabel': 'Verificatiecode',
+ 'login.mfaCodeRequired': 'Voer de code van je authenticator-app in.',
+ 'login.mfaHint': 'Open Google Authenticator, Authy of een andere TOTP-app.',
+ 'login.mfaBack': '← Terug naar inloggen',
+ 'login.mfaVerify': 'Verifiëren',
+ 'login.invalidInviteLink': 'Ongeldige of verlopen uitnodigingslink',
+ 'login.oidcFailed': 'OIDC-aanmelding mislukt',
+ 'login.usernameRequired': 'Gebruikersnaam is vereist',
+ 'login.passwordMinLength': 'Wachtwoord moet minimaal 8 tekens bevatten',
+ 'login.forgotPassword': 'Wachtwoord vergeten?',
+ 'login.rememberMe': 'Ingelogd blijven',
+ 'login.forgotPasswordTitle': 'Wachtwoord resetten',
+ 'login.forgotPasswordBody':
+ 'Voer het e-mailadres van je account in. Als er een account bestaat, sturen we een resetlink.',
+ 'login.forgotPasswordSubmit': 'Resetlink verzenden',
+ 'login.forgotPasswordSentTitle': 'Controleer je e-mail',
+ 'login.forgotPasswordSentBody':
+ 'Als er een account bestaat met dit adres, is de resetlink onderweg. Hij verloopt over 60 minuten.',
+ 'login.forgotPasswordSmtpHintOff':
+ 'Let op: de beheerder heeft SMTP niet ingesteld. De resetlink wordt naar de serverconsole geschreven in plaats van via e-mail verzonden.',
+ 'login.backToLogin': 'Terug naar inloggen',
+ 'login.newPassword': 'Nieuw wachtwoord',
+ 'login.confirmPassword': 'Nieuw wachtwoord bevestigen',
+ 'login.passwordsDontMatch': 'Wachtwoorden komen niet overeen',
+ 'login.mfaCode': '2FA-code',
+ 'login.resetPasswordTitle': 'Nieuw wachtwoord instellen',
+ 'login.resetPasswordBody':
+ 'Kies een sterk wachtwoord dat je hier nog niet hebt gebruikt. Minimaal 8 tekens.',
+ 'login.resetPasswordMfaBody':
+ 'Voer je 2FA-code of een back-upcode in om de reset te voltooien.',
+ 'login.resetPasswordSubmit': 'Wachtwoord resetten',
+ 'login.resetPasswordVerify': 'Verifiëren en resetten',
+ 'login.resetPasswordSuccessTitle': 'Wachtwoord bijgewerkt',
+ 'login.resetPasswordSuccessBody':
+ 'Je kunt nu inloggen met je nieuwe wachtwoord.',
+ 'login.resetPasswordInvalidLink': 'Ongeldige resetlink',
+ 'login.resetPasswordInvalidLinkBody':
+ 'Deze link ontbreekt of is ongeldig. Vraag een nieuwe aan om door te gaan.',
+ 'login.resetPasswordFailed':
+ 'Resetten mislukt. De link is mogelijk verlopen.',
+ 'login.oidc.tokenFailed': 'Authenticatie mislukt.',
+ 'login.oidc.invalidState': 'Ongeldige sessie. Probeer het opnieuw.',
+ 'login.demoFailed': 'Demo-login mislukt',
+ 'login.oidcSignIn': 'Inloggen met {name}',
+ 'login.oidcOnly':
+ 'Wachtwoordauthenticatie is uitgeschakeld. Log in via je SSO-provider.',
+ 'login.oidcLoggedOut':
+ 'Je bent uitgelogd. Log opnieuw in via je SSO-provider.',
+ 'login.demoHint': 'Probeer de demo — geen registratie nodig',
+ 'login.passkey.signIn': 'Inloggen met een passkey',
+ 'login.passkey.failed': 'Inloggen met passkey mislukt. Probeer het opnieuw.',
+};
+export default login;
diff --git a/shared/src/i18n/nl/map.ts b/shared/src/i18n/nl/map.ts
new file mode 100644
index 0000000000000000000000000000000000000000..139f136595bcef6bb5c6ef767124b3a58c2ab43c
--- /dev/null
+++ b/shared/src/i18n/nl/map.ts
@@ -0,0 +1,17 @@
+import type { TranslationStrings } from '../types';
+
+const map: TranslationStrings = {
+ 'map.connections': 'Verbindingen',
+ 'map.showConnections': 'Boekingsroutes tonen',
+ 'map.hideConnections': 'Boekingsroutes verbergen',
+ 'poi.searchThisArea': 'Dit gebied doorzoeken',
+ 'poi.cat.restaurants': 'Restaurants',
+ 'poi.cat.cafes': 'Cafés',
+ 'poi.cat.bars': 'Bars & uitgaan',
+ 'poi.cat.hotels': 'Accommodatie',
+ 'poi.cat.sights': 'Bezienswaardigheden',
+ 'poi.cat.museums': 'Musea & cultuur',
+ 'poi.cat.nature': 'Natuur & parken',
+ 'poi.cat.activities': 'Activiteiten',
+};
+export default map;
diff --git a/shared/src/i18n/nl/members.ts b/shared/src/i18n/nl/members.ts
new file mode 100644
index 0000000000000000000000000000000000000000..074c3b10aa9dd3640449a79ec40ba6b88a574e91
--- /dev/null
+++ b/shared/src/i18n/nl/members.ts
@@ -0,0 +1,24 @@
+import type { TranslationStrings } from '../types';
+
+const members: TranslationStrings = {
+ 'members.shareTrip': 'Reis delen',
+ 'members.inviteUser': 'Gebruiker uitnodigen',
+ 'members.selectUser': 'Selecteer gebruiker…',
+ 'members.invite': 'Uitnodigen',
+ 'members.allHaveAccess': 'Alle gebruikers hebben al toegang.',
+ 'members.access': 'Toegang',
+ 'members.person': 'persoon',
+ 'members.persons': 'personen',
+ 'members.you': 'jij',
+ 'members.owner': 'Eigenaar',
+ 'members.leaveTrip': 'Reis verlaten',
+ 'members.removeAccess': 'Toegang verwijderen',
+ 'members.confirmLeave': 'Reis verlaten? Je verliest de toegang.',
+ 'members.confirmRemove': 'Toegang voor deze gebruiker verwijderen?',
+ 'members.loadError': 'Leden laden mislukt',
+ 'members.added': 'toegevoegd',
+ 'members.addError': 'Toevoegen mislukt',
+ 'members.removed': 'Lid verwijderd',
+ 'members.removeError': 'Verwijderen mislukt',
+};
+export default members;
diff --git a/shared/src/i18n/nl/memories.ts b/shared/src/i18n/nl/memories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..20c7af3d31537e07ac4c3b0cf2c7943aef586dd3
--- /dev/null
+++ b/shared/src/i18n/nl/memories.ts
@@ -0,0 +1,82 @@
+import type { TranslationStrings } from '../types';
+
+const memories: TranslationStrings = {
+ 'memories.title': "Foto's",
+ 'memories.notConnected': 'Immich niet verbonden',
+ 'memories.notConnectedHint':
+ "Verbind je Immich-instantie in Instellingen om je reisfoto's hier te zien.",
+ 'memories.notConnectedMultipleHint':
+ "Verbind een van deze fotoproviders: {provider_names} in Instellingen om foto's aan dit reisplan toe te voegen.",
+ 'memories.noDates': "Voeg data toe aan je reis om foto's te laden.",
+ 'memories.noPhotos': "Geen foto's gevonden",
+ 'memories.noPhotosHint':
+ "Geen foto's gevonden in Immich voor de datumreeks van deze reis.",
+ 'memories.photosFound': "foto's",
+ 'memories.fromOthers': 'van anderen',
+ 'memories.sharePhotos': "Foto's delen",
+ 'memories.sharing': 'Wordt gedeeld',
+ 'memories.reviewTitle': "Je foto's bekijken",
+ 'memories.reviewHint': "Klik op foto's om ze uit te sluiten van delen.",
+ 'memories.shareCount': "{count} foto's delen",
+ 'memories.providerUrl': 'Server-URL',
+ 'memories.providerApiKey': 'API-sleutel',
+ 'memories.providerUsername': 'Gebruikersnaam',
+ 'memories.providerPassword': 'Wachtwoord',
+ 'memories.providerOTP': 'MFA-code (indien ingeschakeld)',
+ 'memories.skipSSLVerification': 'SSL-certificaatverificatie overslaan',
+ 'memories.immichAutoUpload':
+ "Journey-foto's bij upload ook naar Immich spiegelen",
+ 'memories.providerUrlHintSynology':
+ 'Voeg het pad van de Photos-app toe aan de URL, bijv. https://nas:5001/photo',
+ 'memories.testConnection': 'Verbinding testen',
+ 'memories.testShort': 'Testen',
+ 'memories.testFirst': 'Test eerst de verbinding',
+ 'memories.connected': 'Verbonden',
+ 'memories.disconnected': 'Niet verbonden',
+ 'memories.connectionSuccess': 'Verbonden met Immich',
+ 'memories.connectionError': 'Kon niet verbinden met Immich',
+ 'memories.saved': '{provider_name}-instellingen opgeslagen',
+ 'memories.providerDisconnectedBanner':
+ "Je {provider_name}-verbinding is verbroken. Maak opnieuw verbinding in Instellingen om foto's te bekijken.",
+ 'memories.saveError':
+ '{provider_name}-instellingen konden niet worden opgeslagen',
+ 'memories.saveRouteNotConfigured':
+ 'Opslagroute is niet geconfigureerd voor deze provider',
+ 'memories.testRouteNotConfigured':
+ 'Testroute is niet geconfigureerd voor deze provider',
+ 'memories.fillRequiredFields': 'Vul alle verplichte velden in',
+ 'memories.oldest': 'Oudste eerst',
+ 'memories.newest': 'Nieuwste eerst',
+ 'memories.allLocations': 'Alle locaties',
+ 'memories.addPhotos': "Foto's toevoegen",
+ 'memories.linkAlbum': 'Album koppelen',
+ 'memories.selectAlbum': 'Immich-album selecteren',
+ 'memories.selectAlbumMultiple': 'Album selecteren',
+ 'memories.noAlbums': 'Geen albums gevonden',
+ 'memories.syncAlbum': 'Album synchroniseren',
+ 'memories.unlinkAlbum': 'Ontkoppelen',
+ 'memories.photos': 'fotos',
+ 'memories.selectPhotos': "Selecteer foto's uit Immich",
+ 'memories.selectPhotosMultiple': "Foto's selecteren",
+ 'memories.selectHint': "Tik op foto's om ze te selecteren.",
+ 'memories.selected': 'geselecteerd',
+ 'memories.addSelected': "{count} foto's toevoegen",
+ 'memories.alreadyAdded': 'Toegevoegd',
+ 'memories.private': 'Privé',
+ 'memories.stopSharing': 'Delen stoppen',
+ 'memories.tripDates': 'Reisdata',
+ 'memories.allPhotos': "Alle foto's",
+ 'memories.confirmShareTitle': 'Delen met reisgenoten?',
+ 'memories.confirmShareHint':
+ "{count} foto's worden zichtbaar voor alle leden van deze reis. Je kunt individuele foto's later privé maken.",
+ 'memories.confirmShareButton': "Foto's delen",
+ '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',
+};
+export default memories;
diff --git a/shared/src/i18n/nl/nav.ts b/shared/src/i18n/nl/nav.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3c88eb7daaf554e93ed7bdbfa23aad6660f055bd
--- /dev/null
+++ b/shared/src/i18n/nl/nav.ts
@@ -0,0 +1,20 @@
+import type { TranslationStrings } from '../types';
+
+const nav: TranslationStrings = {
+ 'nav.trip': 'Reis',
+ 'nav.share': 'Delen',
+ 'nav.settings': 'Instellingen',
+ 'nav.admin': 'Admin',
+ 'nav.logout': 'Uitloggen',
+ 'nav.lightMode': 'Lichte modus',
+ 'nav.darkMode': 'Donkere modus',
+ 'nav.autoMode': 'Automatisch',
+ 'nav.administrator': 'Beheerder',
+ 'nav.myTrips': 'Mijn reizen',
+ 'nav.profile': 'Profiel',
+ 'nav.bottomSettings': 'Instellingen',
+ 'nav.bottomAdmin': 'Beheerdersinstellingen',
+ 'nav.bottomLogout': 'Uitloggen',
+ 'nav.bottomAdminBadge': 'Beheerder',
+};
+export default nav;
diff --git a/shared/src/i18n/nl/notif.ts b/shared/src/i18n/nl/notif.ts
new file mode 100644
index 0000000000000000000000000000000000000000..93f5056659b24b62d1333d596debc303e3b1b724
--- /dev/null
+++ b/shared/src/i18n/nl/notif.ts
@@ -0,0 +1,44 @@
+import type { TranslationStrings } from '../types';
+
+const notif: TranslationStrings = {
+ 'notif.test.title': '[Test] Melding',
+ 'notif.test.simple.text': 'Dit is een eenvoudige testmelding.',
+ 'notif.test.boolean.text': 'Accepteer je deze testmelding?',
+ 'notif.test.navigate.text': 'Klik hieronder om naar het dashboard te gaan.',
+ 'notif.trip_invite.title': 'Reisuitnodiging',
+ 'notif.trip_invite.text': '{actor} heeft je uitgenodigd voor {trip}',
+ 'notif.booking_change.title': 'Boeking bijgewerkt',
+ 'notif.booking_change.text': '{actor} heeft een boeking bijgewerkt in {trip}',
+ 'notif.trip_reminder.title': 'Reisherinnering',
+ 'notif.trip_reminder.text': 'Je reis {trip} komt eraan!',
+ 'notif.todo_due.title': 'Taak verloopt',
+ 'notif.todo_due.text': '{todo} in {trip} verloopt op {due}',
+ 'notif.vacay_invite.title': 'Vacay Fusion-uitnodiging',
+ 'notif.vacay_invite.text':
+ '{actor} nodigt je uit om vakantieplannen te fuseren',
+ 'notif.photos_shared.title': "Foto's gedeeld",
+ 'notif.photos_shared.text':
+ "{actor} heeft {count} foto('s) gedeeld in {trip}",
+ 'notif.collab_message.title': 'Nieuw bericht',
+ 'notif.collab_message.text': '{actor} heeft een bericht gestuurd in {trip}',
+ 'notif.packing_tagged.title': 'Paklijsttaak',
+ 'notif.packing_tagged.text':
+ '{actor} heeft je toegewezen aan {category} in {trip}',
+ 'notif.version_available.title': 'Nieuwe versie beschikbaar',
+ 'notif.version_available.text': 'TREK {version} is nu beschikbaar',
+ 'notif.action.view_trip': 'Reis bekijken',
+ 'notif.action.view_collab': 'Berichten bekijken',
+ 'notif.action.view_packing': 'Paklijst bekijken',
+ 'notif.action.view_photos': "Foto's bekijken",
+ 'notif.action.view_vacay': 'Vacay bekijken',
+ 'notif.action.view_admin': 'Naar admin',
+ 'notif.action.view': 'Bekijken',
+ 'notif.action.accept': 'Accepteren',
+ 'notif.action.decline': 'Weigeren',
+ 'notif.generic.title': 'Melding',
+ 'notif.generic.text': 'Je hebt een nieuwe melding',
+ 'notif.dev.unknown_event.title': '[DEV] Onbekende gebeurtenis',
+ 'notif.dev.unknown_event.text':
+ 'Gebeurtenistype "{event}" is niet geregistreerd in EVENT_NOTIFICATION_CONFIG',
+};
+export default notif;
diff --git a/shared/src/i18n/nl/notifications.ts b/shared/src/i18n/nl/notifications.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0ef542d425ac139d703e40c7ce8f477ea9692d28
--- /dev/null
+++ b/shared/src/i18n/nl/notifications.ts
@@ -0,0 +1,37 @@
+import type { TranslationStrings } from '../types';
+
+const notifications: TranslationStrings = {
+ 'notifications.title': 'Meldingen',
+ 'notifications.markAllRead': 'Alles als gelezen markeren',
+ 'notifications.deleteAll': 'Alles verwijderen',
+ 'notifications.showAll': 'Alle meldingen weergeven',
+ 'notifications.empty': 'Geen meldingen',
+ 'notifications.emptyDescription': 'Je bent helemaal bijgewerkt!',
+ 'notifications.all': 'Alle',
+ 'notifications.unreadOnly': 'Ongelezen',
+ 'notifications.markRead': 'Markeren als gelezen',
+ 'notifications.markUnread': 'Markeren als ongelezen',
+ 'notifications.delete': 'Verwijderen',
+ 'notifications.system': 'Systeem',
+ 'notifications.synologySessionCleared.title': 'Synology Photos verbroken',
+ 'notifications.synologySessionCleared.text':
+ 'Je server of account is gewijzigd — ga naar Instellingen om je verbinding opnieuw te testen.',
+ '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}".',
+ 'notifications.versionAvailable.title': 'Update beschikbaar',
+ 'notifications.versionAvailable.text': 'TREK {version} is nu beschikbaar.',
+ 'notifications.versionAvailable.button': 'Details bekijken',
+};
+export default notifications;
diff --git a/shared/src/i18n/nl/oauth.ts b/shared/src/i18n/nl/oauth.ts
new file mode 100644
index 0000000000000000000000000000000000000000..caf8112a996caf6a1d38cda6d840f90c9fb0db2b
--- /dev/null
+++ b/shared/src/i18n/nl/oauth.ts
@@ -0,0 +1,123 @@
+import type { TranslationStrings } from '../types';
+
+const oauth: TranslationStrings = {
+ 'oauth.scope.group.trips': 'Reizen',
+ 'oauth.scope.group.places': 'Plaatsen',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'Paklijst',
+ 'oauth.scope.group.todos': 'Taken',
+ 'oauth.scope.group.budget': 'Budget',
+ 'oauth.scope.group.reservations': 'Reserveringen',
+ 'oauth.scope.group.collab': 'Samenwerking',
+ 'oauth.scope.group.notifications': 'Meldingen',
+ 'oauth.scope.group.vacay': 'Vakantie',
+ 'oauth.scope.group.geo': 'Geo',
+ 'oauth.scope.group.weather': 'Weer',
+ 'oauth.scope.group.journey': 'Reisverslag',
+ 'oauth.scope.trips:read.label': 'Reizen en reisplannen bekijken',
+ 'oauth.scope.trips:read.description':
+ 'Reizen, dagen, notities en leden lezen',
+ 'oauth.scope.trips:write.label': 'Reizen en reisplannen bewerken',
+ 'oauth.scope.trips:write.description':
+ 'Reizen, dagen en notities aanmaken, bijwerken en leden beheren',
+ 'oauth.scope.trips:delete.label': 'Reizen verwijderen',
+ 'oauth.scope.trips:delete.description':
+ 'Hele reizen permanent verwijderen — deze actie is onomkeerbaar',
+ 'oauth.scope.trips:share.label': 'Deellinks beheren',
+ 'oauth.scope.trips:share.description':
+ 'Publieke deellinks aanmaken, bijwerken en intrekken',
+ 'oauth.scope.places:read.label': 'Plaatsen en kaartgegevens bekijken',
+ 'oauth.scope.places:read.description':
+ 'Plaatsen, dagtoewijzingen, tags en categorieën lezen',
+ 'oauth.scope.places:write.label': 'Plaatsen beheren',
+ 'oauth.scope.places:write.description':
+ 'Plaatsen, toewijzingen en tags aanmaken, bijwerken en verwijderen',
+ 'oauth.scope.atlas:read.label': 'Atlas bekijken',
+ 'oauth.scope.atlas:read.description':
+ "Bezochte landen, regio's en bucketlist lezen",
+ 'oauth.scope.atlas:write.label': 'Atlas beheren',
+ 'oauth.scope.atlas:write.description':
+ "Landen en regio's markeren als bezocht, bucketlist beheren",
+ 'oauth.scope.packing:read.label': 'Paklijsten bekijken',
+ 'oauth.scope.packing:read.description':
+ 'Pakartikelen, tassen en categorietoewijzingen lezen',
+ 'oauth.scope.packing:write.label': 'Paklijsten beheren',
+ 'oauth.scope.packing:write.description':
+ 'Pakartikelen en tassen toevoegen, bijwerken, verwijderen, omschakelen en herordenen',
+ 'oauth.scope.todos:read.label': 'Takenlijsten bekijken',
+ 'oauth.scope.todos:read.description':
+ 'Reistaakitems en categorietoewijzingen lezen',
+ 'oauth.scope.todos:write.label': 'Takenlijsten beheren',
+ 'oauth.scope.todos:write.description':
+ 'Taakitems aanmaken, bijwerken, omschakelen, verwijderen en herordenen',
+ 'oauth.scope.budget:read.label': 'Budget bekijken',
+ 'oauth.scope.budget:read.description':
+ 'Budgetitems en kostenspecificatie lezen',
+ 'oauth.scope.budget:write.label': 'Budget beheren',
+ 'oauth.scope.budget:write.description':
+ 'Budgetitems aanmaken, bijwerken en verwijderen',
+ 'oauth.scope.reservations:read.label': 'Reserveringen bekijken',
+ 'oauth.scope.reservations:read.description':
+ 'Reserveringen en accommodatiedetails lezen',
+ 'oauth.scope.reservations:write.label': 'Reserveringen beheren',
+ 'oauth.scope.reservations:write.description':
+ 'Reserveringen aanmaken, bijwerken, verwijderen en herordenen',
+ 'oauth.scope.collab:read.label': 'Samenwerking bekijken',
+ 'oauth.scope.collab:read.description':
+ 'Samenwerkingsnotities, polls en berichten lezen',
+ 'oauth.scope.collab:write.label': 'Samenwerking beheren',
+ 'oauth.scope.collab:write.description':
+ 'Samenwerkingsnotities, polls en berichten aanmaken, bijwerken en verwijderen',
+ 'oauth.scope.notifications:read.label': 'Meldingen bekijken',
+ 'oauth.scope.notifications:read.description':
+ 'In-app meldingen en ongelezen aantallen lezen',
+ 'oauth.scope.notifications:write.label': 'Meldingen beheren',
+ 'oauth.scope.notifications:write.description':
+ 'Meldingen als gelezen markeren en erop reageren',
+ 'oauth.scope.vacay:read.label': 'Vakantieplannen bekijken',
+ 'oauth.scope.vacay:read.description':
+ 'Vakantieplanningsgegevens, invoeren en statistieken lezen',
+ 'oauth.scope.vacay:write.label': 'Vakantieplannen beheren',
+ 'oauth.scope.vacay:write.description':
+ 'Vakantie-invoeren, feestdagen en teamplannen aanmaken en beheren',
+ 'oauth.scope.geo:read.label': 'Kaarten & geocodering',
+ 'oauth.scope.geo:read.description':
+ "Locaties zoeken, kaart-URL's oplossen en coördinaten omgekeerd geocoderen",
+ 'oauth.scope.weather:read.label': 'Weersverwachtingen',
+ 'oauth.scope.weather:read.description':
+ 'Weersverwachtingen ophalen voor reislocaties en -datums',
+ 'oauth.scope.journey:read.label': 'Reisverslagen bekijken',
+ 'oauth.scope.journey:read.description':
+ 'Reisverslagen, vermeldingen en lijst van bijdragers lezen',
+ 'oauth.scope.journey:write.label': 'Reisverslagen beheren',
+ 'oauth.scope.journey:write.description':
+ 'Reisverslagen en hun vermeldingen aanmaken, bijwerken en verwijderen',
+ 'oauth.scope.journey:share.label': 'Reisverslag-links beheren',
+ 'oauth.scope.journey:share.description':
+ 'Publieke deellinks voor reisverslagen aanmaken, bijwerken en intrekken',
+ 'oauth.authorize.authorizing': 'Authorizing…', // en-fallback
+ 'oauth.authorize.loading': 'Loading…', // en-fallback
+ 'oauth.authorize.errorTitle': 'Authorization Error', // en-fallback
+ 'oauth.authorize.loginTitle': 'Sign in to continue', // en-fallback
+ 'oauth.authorize.loginDescription':
+ '{client} wants access to your TREK account. Please sign in first.', // en-fallback
+ 'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
+ 'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
+ 'oauth.authorize.requestDescription':
+ 'This application is requesting access to your TREK account.', // en-fallback
+ 'oauth.authorize.trustNote':
+ 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
+ 'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
+ 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
+ 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
+ 'oauth.authorize.deny': 'Deny', // en-fallback
+ 'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
+ 'oauth.authorize.permissionsRequested': 'Permissions requested', // en-fallback
+ 'oauth.authorize.alwaysIncluded': 'Always included', // en-fallback
+ 'oauth.authorize.alwaysTool.listTrips':
+ 'List your trips so the AI can discover trip IDs', // en-fallback
+ 'oauth.authorize.alwaysTool.getTripSummary':
+ 'Read a trip overview needed to use any other tool', // en-fallback
+};
+export default oauth;
diff --git a/shared/src/i18n/nl/packing.ts b/shared/src/i18n/nl/packing.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5ee7a4b813e365da457e3a096d3cfca6e8953c70
--- /dev/null
+++ b/shared/src/i18n/nl/packing.ts
@@ -0,0 +1,186 @@
+import type { TranslationStrings } from '../types';
+
+const packing: TranslationStrings = {
+ 'packing.title': 'Paklijst',
+ 'packing.empty': 'Paklijst is leeg',
+ 'packing.import': 'Importeren',
+ 'packing.importTitle': 'Paklijst importeren',
+ 'packing.importHint':
+ 'Eén item per regel. Optioneel categorie en aantal gescheiden door komma, puntkomma of tab: Naam, Categorie, Aantal',
+ 'packing.importPlaceholder':
+ 'Tandenborstel\nZonnebrand, Hygiëne\nT-Shirts, Kleding, 5\nPaspoort, Documenten',
+ 'packing.importCsv': 'CSV/TXT laden',
+ 'packing.importAction': '{count} importeren',
+ 'packing.importSuccess': '{count} items geïmporteerd',
+ 'packing.importError': 'Import mislukt',
+ 'packing.importEmpty': 'Geen items om te importeren',
+ 'packing.progress': '{packed} van {total} ingepakt ({percent}%)',
+ 'packing.clearChecked': '{count} aangevinkte verwijderen',
+ 'packing.clearCheckedShort': '{count} verwijderen',
+ 'packing.suggestions': 'Suggesties',
+ 'packing.suggestionsTitle': 'Suggesties toevoegen',
+ 'packing.allSuggested': 'Alle suggesties toegevoegd',
+ 'packing.allPacked': 'Alles ingepakt!',
+ 'packing.addPlaceholder': 'Nieuw item toevoegen...',
+ 'packing.categoryPlaceholder': 'Categorie...',
+ 'packing.filterAll': 'Alle',
+ 'packing.filterOpen': 'Openstaand',
+ 'packing.filterDone': 'Klaar',
+ 'packing.emptyTitle': 'Paklijst is leeg',
+ 'packing.emptyHint': 'Voeg items toe of gebruik de suggesties',
+ 'packing.emptyFiltered': 'Geen items gevonden voor dit filter',
+ 'packing.menuRename': 'Hernoemen',
+ 'packing.menuCheckAll': 'Alles aanvinken',
+ 'packing.menuUncheckAll': 'Alles uitvinken',
+ 'packing.menuDeleteCat': 'Categorie verwijderen',
+ 'packing.addItem': 'Item toevoegen',
+ 'packing.addItemPlaceholder': 'Itemnaam...',
+ 'packing.addCategory': 'Categorie toevoegen',
+ 'packing.newCategoryPlaceholder': 'Categorienaam (bijv. Kleding)',
+ 'packing.applyTemplate': 'Sjabloon toepassen',
+ 'packing.template': 'Sjabloon',
+ 'packing.templateApplied': '{count} items toegevoegd vanuit sjabloon',
+ 'packing.templateError': 'Fout bij toepassen van sjabloon',
+ 'packing.saveAsTemplate': 'Opslaan als sjabloon',
+ 'packing.templateName': 'Sjabloonnaam',
+ 'packing.templateSaved': 'Paklijst opgeslagen als sjabloon',
+ 'packing.noMembers': 'Geen leden',
+ 'packing.bags': 'Bagage',
+ 'packing.noBag': 'Niet toegewezen',
+ 'packing.totalWeight': 'Totaalgewicht',
+ 'packing.bagName': 'Naam...',
+ 'packing.addBag': 'Bagage toevoegen',
+ 'packing.changeCategory': 'Categorie wijzigen',
+ 'packing.confirm.clearChecked':
+ 'Weet je zeker dat je {count} aangevinkte items wilt verwijderen?',
+ 'packing.confirm.deleteCat':
+ 'Weet je zeker dat je de categorie "{name}" met {count} items wilt verwijderen?',
+ 'packing.defaultCategory': 'Overig',
+ 'packing.toast.saveError': 'Opslaan mislukt',
+ 'packing.toast.deleteError': 'Verwijderen mislukt',
+ 'packing.toast.renameError': 'Hernoemen mislukt',
+ 'packing.toast.addError': 'Toevoegen mislukt',
+ 'packing.suggestions.items': [
+ {
+ name: 'Paspoort',
+ category: 'Documenten',
+ },
+ {
+ name: 'Identiteitskaart',
+ category: 'Documenten',
+ },
+ {
+ name: 'Reisverzekering',
+ category: 'Documenten',
+ },
+ {
+ name: 'Vliegtickets',
+ category: 'Documenten',
+ },
+ {
+ name: 'Creditcard',
+ category: 'Financiën',
+ },
+ {
+ name: 'Contant geld',
+ category: 'Financiën',
+ },
+ {
+ name: 'Visum',
+ category: 'Documenten',
+ },
+ {
+ name: 'T-shirts',
+ category: 'Kleding',
+ },
+ {
+ name: 'Broeken',
+ category: 'Kleding',
+ },
+ {
+ name: 'Ondergoed',
+ category: 'Kleding',
+ },
+ {
+ name: 'Sokken',
+ category: 'Kleding',
+ },
+ {
+ name: 'Jas',
+ category: 'Kleding',
+ },
+ {
+ name: 'Slaapkleding',
+ category: 'Kleding',
+ },
+ {
+ name: 'Zwemkleding',
+ category: 'Kleding',
+ },
+ {
+ name: 'Regenjas',
+ category: 'Kleding',
+ },
+ {
+ name: 'Comfortabele schoenen',
+ category: 'Kleding',
+ },
+ {
+ name: 'Tandenborstel',
+ category: 'Toiletartikelen',
+ },
+ {
+ name: 'Tandpasta',
+ category: 'Toiletartikelen',
+ },
+ {
+ name: 'Shampoo',
+ category: 'Toiletartikelen',
+ },
+ {
+ name: 'Deodorant',
+ category: 'Toiletartikelen',
+ },
+ {
+ name: 'Zonnebrandcrème',
+ category: 'Toiletartikelen',
+ },
+ {
+ name: 'Scheermesje',
+ category: 'Toiletartikelen',
+ },
+ {
+ name: 'Oplader',
+ category: 'Elektronica',
+ },
+ {
+ name: 'Powerbank',
+ category: 'Elektronica',
+ },
+ {
+ name: 'Koptelefoon',
+ category: 'Elektronica',
+ },
+ {
+ name: 'Reisadapter',
+ category: 'Elektronica',
+ },
+ {
+ name: 'Camera',
+ category: 'Elektronica',
+ },
+ {
+ name: 'Pijnstillers',
+ category: 'Gezondheid',
+ },
+ {
+ name: 'Pleisters',
+ category: 'Gezondheid',
+ },
+ {
+ name: 'Ontsmettingsmiddel',
+ category: 'Gezondheid',
+ },
+ ],
+};
+export default packing;
diff --git a/shared/src/i18n/nl/pdf.ts b/shared/src/i18n/nl/pdf.ts
new file mode 100644
index 0000000000000000000000000000000000000000..89cc8a2468807fccf5badaa3fc74784dc5d676ef
--- /dev/null
+++ b/shared/src/i18n/nl/pdf.ts
@@ -0,0 +1,10 @@
+import type { TranslationStrings } from '../types';
+
+const pdf: TranslationStrings = {
+ 'pdf.travelPlan': 'Reisplan',
+ 'pdf.planned': 'Gepland',
+ 'pdf.costLabel': 'Kosten EUR',
+ 'pdf.preview': 'PDF-voorbeeld',
+ 'pdf.saveAsPdf': 'Opslaan als PDF',
+};
+export default pdf;
diff --git a/shared/src/i18n/nl/perm.ts b/shared/src/i18n/nl/perm.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7957b4fc071d6a1f1864bb27aa54fff1958bfc14
--- /dev/null
+++ b/shared/src/i18n/nl/perm.ts
@@ -0,0 +1,62 @@
+import type { TranslationStrings } from '../types';
+
+const perm: TranslationStrings = {
+ 'perm.title': 'Rechtinstellingen',
+ 'perm.subtitle': 'Bepaal wie welke acties mag uitvoeren in de applicatie',
+ 'perm.saved': 'Rechtinstellingen opgeslagen',
+ 'perm.resetDefaults': 'Standaardwaarden herstellen',
+ 'perm.customized': 'aangepast',
+ 'perm.level.admin': 'Alleen beheerder',
+ 'perm.level.tripOwner': 'Reiseigenaar',
+ 'perm.level.tripMember': 'Reisleden',
+ 'perm.level.everybody': 'Iedereen',
+ 'perm.cat.trip': 'Reisbeheer',
+ 'perm.cat.members': 'Ledenbeheer',
+ 'perm.cat.files': 'Bestanden',
+ 'perm.cat.content': 'Inhoud & planning',
+ 'perm.cat.extras': 'Budget, paklijsten & samenwerking',
+ 'perm.action.trip_create': 'Reizen aanmaken',
+ 'perm.action.trip_edit': 'Reisdetails bewerken',
+ 'perm.action.trip_delete': 'Reizen verwijderen',
+ 'perm.action.trip_archive': 'Reizen archiveren / dearchiveren',
+ 'perm.action.trip_cover_upload': 'Omslagfoto uploaden',
+ 'perm.action.member_manage': 'Leden toevoegen / verwijderen',
+ 'perm.action.file_upload': 'Bestanden uploaden',
+ 'perm.action.file_edit': 'Bestandsmetadata bewerken',
+ 'perm.action.file_delete': 'Bestanden verwijderen',
+ 'perm.action.place_edit': 'Plaatsen toevoegen / bewerken / verwijderen',
+ 'perm.action.day_edit': 'Dagen, notities & toewijzingen bewerken',
+ 'perm.action.reservation_edit': 'Reserveringen beheren',
+ 'perm.action.budget_edit': 'Budget beheren',
+ 'perm.action.packing_edit': 'Paklijsten beheren',
+ 'perm.action.collab_edit': 'Samenwerking (notities, polls, chat)',
+ 'perm.action.share_manage': 'Deellinks beheren',
+ 'perm.actionHint.trip_create': 'Wie kan nieuwe reizen aanmaken',
+ 'perm.actionHint.trip_edit':
+ 'Wie kan reisnaam, data, beschrijving en valuta wijzigen',
+ 'perm.actionHint.trip_delete': 'Wie kan een reis permanent verwijderen',
+ 'perm.actionHint.trip_archive': 'Wie kan een reis archiveren of dearchiveren',
+ 'perm.actionHint.trip_cover_upload':
+ 'Wie kan de omslagfoto uploaden of wijzigen',
+ 'perm.actionHint.member_manage':
+ 'Wie kan reisleden uitnodigen of verwijderen',
+ 'perm.actionHint.file_upload': 'Wie kan bestanden uploaden naar een reis',
+ 'perm.actionHint.file_edit':
+ 'Wie kan bestandsbeschrijvingen en links bewerken',
+ 'perm.actionHint.file_delete':
+ 'Wie kan bestanden naar de prullenbak verplaatsen of permanent verwijderen',
+ 'perm.actionHint.place_edit':
+ 'Wie kan plaatsen toevoegen, bewerken of verwijderen',
+ 'perm.actionHint.day_edit':
+ 'Wie kan dagen, dagnotities en plaatstoewijzingen bewerken',
+ 'perm.actionHint.reservation_edit':
+ 'Wie kan reserveringen aanmaken, bewerken of verwijderen',
+ 'perm.actionHint.budget_edit':
+ 'Wie kan budgetposten aanmaken, bewerken of verwijderen',
+ 'perm.actionHint.packing_edit': 'Wie kan pakitems en tassen beheren',
+ 'perm.actionHint.collab_edit':
+ 'Wie kan notities, polls aanmaken en berichten versturen',
+ 'perm.actionHint.share_manage':
+ 'Wie kan openbare deellinks aanmaken of verwijderen',
+};
+export default perm;
diff --git a/shared/src/i18n/nl/photos.ts b/shared/src/i18n/nl/photos.ts
new file mode 100644
index 0000000000000000000000000000000000000000..04f5d5fcd5ad15e9bfd575f5c295a09ab6d2c14b
--- /dev/null
+++ b/shared/src/i18n/nl/photos.ts
@@ -0,0 +1,25 @@
+import type { TranslationStrings } from '../types';
+
+const photos: TranslationStrings = {
+ 'photos.title': "Foto's",
+ 'photos.subtitle': "{count} foto's voor {trip}",
+ 'photos.dropHere': "Foto's hier neerzetten...",
+ 'photos.dropHereActive': "Foto's hier neerzetten",
+ 'photos.captionForAll': 'Bijschrift (voor alle)',
+ 'photos.captionPlaceholder': 'Optioneel bijschrift...',
+ 'photos.addCaption': 'Bijschrift toevoegen...',
+ 'photos.allDays': 'Alle dagen',
+ 'photos.noPhotos': "Nog geen foto's",
+ 'photos.uploadHint': "Upload je reisfoto's",
+ 'photos.clickToSelect': 'of klik om te selecteren',
+ 'photos.linkPlace': 'Koppel plaats',
+ 'photos.noPlace': 'Geen plaats',
+ 'photos.uploadN': "{n} foto('s) uploaden",
+ 'photos.linkDay': 'Dag koppelen',
+ 'photos.noDay': 'Geen dag',
+ 'photos.dayLabel': 'Dag {number}',
+ 'photos.photoSelected': 'Foto geselecteerd',
+ 'photos.photosSelected': "Foto's geselecteerd",
+ 'photos.fileTypeHint': "JPG, PNG, WebP · max. 10 MB · tot 30 foto's",
+};
+export default photos;
diff --git a/shared/src/i18n/nl/places.ts b/shared/src/i18n/nl/places.ts
new file mode 100644
index 0000000000000000000000000000000000000000..04f47890041225a405a38e7715364ac23ef9fbb3
--- /dev/null
+++ b/shared/src/i18n/nl/places.ts
@@ -0,0 +1,98 @@
+import type { TranslationStrings } from '../types';
+
+const places: TranslationStrings = {
+ 'places.addPlace': 'Plaats/activiteit toevoegen',
+ 'places.importFile': 'Bestand importeren',
+ 'places.sidebarDrop': 'Loslaten om te importeren',
+ 'places.importFileHint':
+ 'Importeer .gpx-, .kml- of .kmz-bestanden uit tools zoals Google My Maps, Google Earth of een GPS-tracker.',
+ 'places.importFileDropHere':
+ 'Klik om een bestand te selecteren of sleep het hier naartoe',
+ 'places.importFileDropActive': 'Laat het bestand los om het te selecteren',
+ 'places.importFileUnsupported':
+ 'Niet-ondersteund bestandstype. Gebruik .gpx, .kml of .kmz.',
+ 'places.importFileTooLarge':
+ 'Bestand is te groot. Maximale uploadgrootte is {maxMb} MB.',
+ 'places.importFileError': 'Importeren mislukt',
+ 'places.importAllSkipped': 'Alle plaatsen waren al in de reis.',
+ 'places.gpxImported': '{count} plaatsen geïmporteerd uit GPX',
+ 'places.gpxImportTypes': 'Wat wil je importeren?',
+ 'places.gpxImportWaypoints': 'Waypoints',
+ 'places.gpxImportRoutes': 'Routes',
+ 'places.gpxImportTracks': 'Tracks (met routegeometrie)',
+ 'places.gpxImportNoneSelected':
+ 'Selecteer minstens één type om te importeren.',
+ 'places.kmlImportTypes': 'Wat wil je importeren?',
+ 'places.kmlImportPoints': 'Punten (Placemarks)',
+ 'places.kmlImportPaths': 'Paden (LineStrings)',
+ 'places.kmlImportNoneSelected': 'Selecteer minstens één type.',
+ 'places.selectionCount': '{count} geselecteerd',
+ 'places.deleteSelected': 'Selectie verwijderen',
+ 'places.kmlKmzImported': '{count} plaatsen geïmporteerd uit KMZ/KML',
+ 'places.urlResolved': 'Plaats geïmporteerd van URL',
+ 'places.importList': 'Lijst importeren',
+ 'places.kmlKmzSummaryValues':
+ 'Placemarks: {total} • Geïmporteerd: {created} • Overgeslagen: {skipped}',
+ 'places.importGoogleList': 'Google Lijst',
+ 'places.importNaverList': 'Naver Lijst',
+ 'places.googleListHint':
+ 'Plak een gedeelde Google Maps lijstlink om alle plaatsen te importeren.',
+ 'places.googleListImported': '{count} plaatsen geimporteerd uit "{list}"',
+ 'places.googleListError': 'Google Maps lijst importeren mislukt',
+ 'places.naverListHint':
+ 'Plak een gedeelde Naver Maps lijstlink om alle plaatsen te importeren.',
+ 'places.naverListImported': '{count} plaatsen geimporteerd uit "{list}"',
+ 'places.naverListError': 'Naver Maps lijst importeren mislukt',
+ 'places.viewDetails': 'Details bekijken',
+ 'places.assignToDay': 'Aan welke dag toevoegen?',
+ 'places.all': 'Alle',
+ 'places.unplanned': 'Ongepland',
+ 'places.filterTracks': 'Tracks',
+ 'places.search': 'Plaatsen zoeken...',
+ 'places.allCategories': 'Alle categorieën',
+ 'places.categoriesSelected': 'categorieën',
+ 'places.clearFilter': 'Filter wissen',
+ 'places.count': '{count} plaatsen',
+ 'places.countSingular': '1 plaats',
+ 'places.allPlanned': 'Alle plaatsen zijn gepland',
+ 'places.noneFound': 'Geen plaatsen gevonden',
+ 'places.editPlace': 'Plaats bewerken',
+ 'places.formName': 'Naam',
+ 'places.formNamePlaceholder': 'bijv. Eiffeltoren',
+ 'places.formDescription': 'Beschrijving',
+ 'places.formDescriptionPlaceholder': 'Korte beschrijving...',
+ 'places.formAddress': 'Adres',
+ 'places.formAddressPlaceholder': 'Straat, stad, land',
+ 'places.formLat': 'Breedtegraad (bijv. 48.8566)',
+ 'places.formLng': 'Lengtegraad (bijv. 2.3522)',
+ 'places.formCategory': 'Categorie',
+ 'places.noCategory': 'Geen categorie',
+ 'places.categoryNamePlaceholder': 'Categorienaam',
+ 'places.formTime': 'Tijd',
+ 'places.startTime': 'Starttijd',
+ 'places.endTime': 'Einde',
+ 'places.endTimeBeforeStart': 'Eindtijd is vóór de starttijd',
+ 'places.timeCollision': 'Tijdoverlap met:',
+ 'places.formWebsite': 'Website',
+ 'places.formNotes': 'Notities',
+ 'places.formNotesPlaceholder': 'Persoonlijke notities...',
+ 'places.formReservation': 'Reservering',
+ 'places.reservationNotesPlaceholder':
+ 'Reserveringsnotities, bevestigingsnummer...',
+ 'places.mapsSearchPlaceholder': 'Plaatsen zoeken...',
+ 'places.mapsSearchError': 'Zoeken naar plaatsen mislukt.',
+ 'places.loadingDetails': 'Plaatsgegevens laden…',
+ 'places.osmHint':
+ "Zoeken via OpenStreetMap (geen foto's, openingstijden of beoordelingen). Voeg een Google API-sleutel toe in instellingen voor volledige details.",
+ 'places.osmActive':
+ "Zoeken via OpenStreetMap (geen foto's, beoordelingen of openingstijden). Voeg een Google API-sleutel toe in Instellingen voor uitgebreide gegevens.",
+ 'places.categoryCreateError': 'Categorie aanmaken mislukt',
+ 'places.nameRequired': 'Voer een naam in',
+ 'places.saveError': 'Opslaan mislukt',
+ 'places.duplicateExists': "'{name}' staat al in deze reis.",
+ 'places.addAnyway': 'Toch toevoegen',
+ 'places.enrichOnImport': 'Plaatsen verrijken via Google',
+ 'places.enrichOnImportHint':
+ 'Zoekt elke geïmporteerde plaats op om fotos, adres en contactgegevens toe te voegen. Gebruikt je Google Maps-sleutel.',
+};
+export default places;
diff --git a/shared/src/i18n/nl/planner.ts b/shared/src/i18n/nl/planner.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d193eee7f8d9adde2777b7468f05fcb82d221893
--- /dev/null
+++ b/shared/src/i18n/nl/planner.ts
@@ -0,0 +1,68 @@
+import type { TranslationStrings } from '../types';
+
+const planner: TranslationStrings = {
+ 'planner.places': 'Plaatsen',
+ 'planner.bookings': 'Boekingen',
+ 'planner.packingList': 'Paklijst',
+ 'planner.documents': 'Documenten',
+ 'planner.dayPlan': 'Dagplan',
+ 'planner.reservations': 'Reserveringen',
+ 'planner.minTwoPlaces': 'Minimaal 2 plaatsen met coördinaten nodig',
+ 'planner.noGeoPlaces': 'Geen plaatsen met coördinaten beschikbaar',
+ 'planner.routeCalculated': 'Route berekend',
+ 'planner.routeCalcFailed': 'Route kon niet worden berekend',
+ 'planner.routeError': 'Fout bij routeberekening',
+ 'planner.icsExportFailed': 'ICS-export mislukt',
+ 'planner.routeOptimized': 'Route geoptimaliseerd',
+ 'planner.reservationUpdated': 'Reservering bijgewerkt',
+ 'planner.reservationAdded': 'Reservering toegevoegd',
+ 'planner.confirmDeleteReservation': 'Reservering verwijderen?',
+ 'planner.reservationDeleted': 'Reservering verwijderd',
+ 'planner.days': 'Dagen',
+ 'planner.allPlaces': 'Alle plaatsen',
+ 'planner.totalPlaces': '{n} plaatsen totaal',
+ 'planner.noDaysPlanned': 'Nog geen dagen gepland',
+ 'planner.editTrip': 'Reis bewerken →',
+ 'planner.placeOne': '1 plaats',
+ 'planner.placeN': '{n} plaatsen',
+ 'planner.addNote': 'Notitie toevoegen',
+ 'planner.noEntries': 'Geen invoeren voor deze dag',
+ 'planner.addPlace': 'Plaats/activiteit toevoegen',
+ 'planner.addPlaceShort': '+ Plaats/activiteit toevoegen',
+ 'planner.resPending': 'Reservering in behandeling · ',
+ 'planner.resConfirmed': 'Reservering bevestigd · ',
+ 'planner.notePlaceholder': 'Notitie…',
+ 'planner.noteTimePlaceholder': 'Tijd (optioneel)',
+ 'planner.noteExamplePlaceholder':
+ 'bijv. S3 om 14:30 vanaf centraal station, veerboot van pier 7, lunchpauze…',
+ 'planner.totalCost': 'Totale kosten',
+ 'planner.searchPlaces': 'Plaatsen zoeken…',
+ 'planner.allCategories': 'Alle categorieën',
+ 'planner.noPlacesFound': 'Geen plaatsen gevonden',
+ 'planner.addFirstPlace': 'Eerste plaats toevoegen',
+ 'planner.noReservations': 'Geen reserveringen',
+ 'planner.addFirstReservation': 'Eerste reservering toevoegen',
+ 'planner.new': 'Nieuw',
+ 'planner.addToDay': '+ Dag',
+ 'planner.calculating': 'Berekenen…',
+ 'planner.route': 'Route',
+ 'planner.optimize': 'Optimaliseren',
+ 'planner.openGoogleMaps': 'Openen in Google Maps',
+ 'planner.selectDayHint':
+ 'Selecteer een dag uit de lijst links om het dagplan te bekijken',
+ 'planner.noPlacesForDay': 'Nog geen plaatsen voor deze dag',
+ 'planner.addPlacesLink': 'Plaatsen toevoegen →',
+ 'planner.minTotal': 'min. totaal',
+ 'planner.noReservation': 'Geen reservering',
+ 'planner.removeFromDay': 'Verwijderen van dag',
+ 'planner.addToThisDay': 'Toevoegen aan dag',
+ 'planner.overview': 'Overzicht',
+ 'planner.noDays': 'Geen dagen',
+ 'planner.editTripToAddDays': 'Bewerk de reis om dagen toe te voegen',
+ 'planner.dayCount': '{n} dagen',
+ 'planner.clickToUnlock': 'Klik om te ontgrendelen',
+ 'planner.keepPosition': 'Positie behouden tijdens route-optimalisatie',
+ 'planner.dayDetails': 'Dagdetails',
+ 'planner.dayN': 'Dag {n}',
+};
+export default planner;
diff --git a/shared/src/i18n/nl/register.ts b/shared/src/i18n/nl/register.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a0e3b5358340d690ac4aa46399f0cbc06b986b77
--- /dev/null
+++ b/shared/src/i18n/nl/register.ts
@@ -0,0 +1,26 @@
+import type { TranslationStrings } from '../types';
+
+const register: TranslationStrings = {
+ 'register.passwordMismatch': 'Wachtwoorden komen niet overeen',
+ 'register.passwordTooShort': 'Wachtwoord moet minimaal 8 tekens bevatten',
+ 'register.failed': 'Registratie mislukt',
+ 'register.getStarted': 'Aan de slag',
+ 'register.subtitle':
+ 'Maak een account aan en begin met het plannen van je droomreizen.',
+ 'register.feature1': 'Onbeperkte reisplannen',
+ 'register.feature2': 'Interactieve kaartweergave',
+ 'register.feature3': 'Beheer plaatsen en categorieën',
+ 'register.feature4': 'Houd reserveringen bij',
+ 'register.feature5': 'Maak paklijsten',
+ 'register.feature6': "Bewaar foto's en bestanden",
+ 'register.createAccount': 'Account aanmaken',
+ 'register.startPlanning': 'Begin met het plannen van je reis',
+ 'register.minChars': 'Min. 6 tekens',
+ 'register.confirmPassword': 'Bevestig wachtwoord',
+ 'register.repeatPassword': 'Herhaal wachtwoord',
+ 'register.registering': 'Registreren...',
+ 'register.register': 'Registreren',
+ 'register.hasAccount': 'Heb je al een account?',
+ 'register.signIn': 'Inloggen',
+};
+export default register;
diff --git a/shared/src/i18n/nl/reservations.ts b/shared/src/i18n/nl/reservations.ts
new file mode 100644
index 0000000000000000000000000000000000000000..40615f145cb9b163c3f17f3d8a8d56f51fd02d56
--- /dev/null
+++ b/shared/src/i18n/nl/reservations.ts
@@ -0,0 +1,165 @@
+import type { TranslationStrings } from '../types';
+
+const reservations: TranslationStrings = {
+ 'reservations.title': 'Boekingen',
+ 'reservations.empty': 'Nog geen reserveringen',
+ 'reservations.emptyHint':
+ 'Voeg reserveringen toe voor vluchten, hotels en meer',
+ 'reservations.add': 'Reservering toevoegen',
+ 'reservations.addManual': 'Handmatige boeking',
+ 'reservations.placeHint':
+ 'Tip: Reserveringen kun je het beste direct vanuit een plaats aanmaken om ze te koppelen aan je dagplan.',
+ 'reservations.confirmed': 'Bevestigd',
+ 'reservations.pending': 'In behandeling',
+ 'reservations.summary': '{confirmed} bevestigd, {pending} in behandeling',
+ 'reservations.fromPlan': 'Vanuit plan',
+ 'reservations.showFiles': 'Bestanden tonen',
+ 'reservations.editTitle': 'Reservering bewerken',
+ 'reservations.status': 'Status',
+ 'reservations.datetime': 'Datum en tijd',
+ 'reservations.startTime': 'Starttijd',
+ 'reservations.endTime': 'Eindtijd',
+ 'reservations.date': 'Datum',
+ 'reservations.time': 'Tijd',
+ 'reservations.timeAlt': 'Tijd (alternatief, bijv. 19:30)',
+ 'reservations.notes': 'Notities',
+ 'reservations.notesPlaceholder': 'Extra notities...',
+ 'reservations.meta.airline': 'Luchtvaartmaatschappij',
+ 'reservations.meta.flightNumber': 'Vluchtnr.',
+ 'reservations.meta.from': 'Van',
+ 'reservations.meta.to': 'Naar',
+ 'reservations.layover.route': 'Route',
+ 'reservations.layover.stop': 'Tussenstop',
+ 'reservations.layover.addStop': 'Tussenstop toevoegen',
+ 'reservations.layover.connection': 'Aansluiting',
+ 'reservations.layover.layover': 'Overstap',
+ 'reservations.needsReview': 'Controleren',
+ 'reservations.needsReviewHint':
+ 'Luchthaven kon niet automatisch worden herkend — bevestig de locatie.',
+ 'reservations.searchLocation': 'Station, haven, adres zoeken...',
+ 'reservations.meta.trainNumber': 'Treinnr.',
+ 'reservations.meta.platform': 'Perron',
+ 'reservations.meta.seat': 'Stoel',
+ 'reservations.meta.checkIn': 'Inchecken',
+ 'reservations.meta.checkInUntil': 'Check-in tot',
+ 'reservations.meta.checkOut': 'Uitchecken',
+ 'reservations.meta.linkAccommodation': 'Accommodatie',
+ 'reservations.meta.pickAccommodation': 'Koppel aan accommodatie',
+ 'reservations.meta.noAccommodation': 'Geen',
+ 'reservations.meta.hotelPlace': 'Accommodatie',
+ 'reservations.meta.pickHotel': 'Selecteer accommodatie',
+ 'reservations.meta.fromDay': 'Van',
+ 'reservations.meta.toDay': 'Tot',
+ 'reservations.meta.selectDay': 'Selecteer dag',
+ 'reservations.type.flight': 'Vlucht',
+ 'reservations.type.hotel': 'Accommodatie',
+ 'reservations.type.restaurant': 'Restaurant',
+ 'reservations.type.train': 'Trein',
+ 'reservations.type.car': 'Auto',
+ 'reservations.type.cruise': 'Cruise',
+ 'reservations.type.event': 'Evenement',
+ 'reservations.type.tour': 'Rondleiding',
+ 'reservations.type.other': 'Overig',
+ 'reservations.type.bus': 'Bus',
+ 'reservations.type.ferry': 'Veerboot',
+ 'reservations.type.bicycle': 'Fiets',
+ 'reservations.type.taxi': 'Taxi',
+ 'reservations.type.transport_other': 'Overig',
+ 'reservations.confirm.delete':
+ 'Weet je zeker dat je de reservering "{name}" wilt verwijderen?',
+ 'reservations.confirm.deleteTitle': 'Boeking verwijderen?',
+ 'reservations.confirm.deleteBody': '"{name}" wordt permanent verwijderd.',
+ 'reservations.toast.updated': 'Reservering bijgewerkt',
+ 'reservations.toast.removed': 'Reservering verwijderd',
+ 'reservations.toast.fileUploaded': 'Bestand geüpload',
+ 'reservations.toast.uploadError': 'Uploaden mislukt',
+ 'reservations.newTitle': 'Nieuwe reservering',
+ 'reservations.bookingType': 'Boekingstype',
+ 'reservations.titleLabel': 'Titel',
+ 'reservations.titlePlaceholder': 'bijv. Lufthansa LH123, Hotel Adlon, ...',
+ 'reservations.locationAddress': 'Locatie / Adres',
+ 'reservations.locationPlaceholder': 'Adres, luchthaven, hotel...',
+ 'reservations.confirmationCode': 'Boekingscode',
+ 'reservations.confirmationPlaceholder': 'bijv. ABC12345',
+ 'reservations.day': 'Dag',
+ 'reservations.noDay': 'Geen dag',
+ 'reservations.place': 'Plaats',
+ 'reservations.noPlace': 'Geen plaats',
+ 'reservations.pendingSave': 'wordt opgeslagen…',
+ 'reservations.uploading': 'Uploaden...',
+ 'reservations.attachFile': 'Bestand bijvoegen',
+ 'reservations.linkExisting': 'Bestaand bestand koppelen',
+ 'reservations.toast.saveError': 'Opslaan mislukt',
+ 'reservations.toast.updateError': 'Bijwerken mislukt',
+ 'reservations.toast.deleteError': 'Verwijderen mislukt',
+ 'reservations.confirm.remove': 'Reservering voor "{name}" verwijderen?',
+ 'reservations.linkAssignment': 'Koppelen aan dagtoewijzing',
+ 'reservations.pickAssignment': 'Selecteer een toewijzing uit je plan...',
+ 'reservations.noAssignment': 'Geen koppeling (zelfstandig)',
+ 'reservations.price': 'Prijs',
+ 'reservations.budgetCategory': 'Budgetcategorie',
+ 'reservations.budgetCategoryPlaceholder': 'bijv. Transport, Accommodatie',
+ 'reservations.budgetCategoryAuto': 'Automatisch (op basis van boekingstype)',
+ 'reservations.budgetHint':
+ 'Er wordt automatisch een budgetpost aangemaakt bij het opslaan.',
+ 'reservations.departureDate': 'Vertrek',
+ 'reservations.arrivalDate': 'Aankomst',
+ 'reservations.departureTime': 'Vertrektijd',
+ 'reservations.arrivalTime': 'Aankomsttijd',
+ 'reservations.pickupDate': 'Ophalen',
+ 'reservations.returnDate': 'Inleveren',
+ 'reservations.pickupTime': 'Ophaaltijd',
+ 'reservations.returnTime': 'Inlevertijd',
+ 'reservations.endDate': 'Einddatum',
+ 'reservations.meta.departureTimezone': 'TZ vertrek',
+ 'reservations.meta.arrivalTimezone': 'TZ aankomst',
+ 'reservations.span.departure': 'Vertrek',
+ 'reservations.span.arrival': 'Aankomst',
+ 'reservations.span.inTransit': 'Onderweg',
+ 'reservations.span.pickup': 'Ophalen',
+ 'reservations.span.return': 'Inleveren',
+ 'reservations.span.active': 'Actief',
+ 'reservations.span.start': 'Start',
+ 'reservations.span.end': 'Einde',
+ 'reservations.span.ongoing': 'Lopend',
+ 'reservations.validation.endBeforeStart':
+ 'Einddatum/-tijd moet na de startdatum/-tijd liggen',
+ 'reservations.addBooking': 'Boeking toevoegen',
+ 'reservations.import.title': 'Boekingsbevestigingen importeren',
+ 'reservations.import.cta': 'Importeren vanuit bestand',
+ 'reservations.import.dropHere': 'Zet hier bevestigingsbestanden neer of klik om te selecteren',
+ 'reservations.import.dropActive': 'Laat bestanden los om te importeren',
+ 'reservations.import.acceptedFormats': 'Geaccepteerd: EML, PDF, PKPass, HTML, TXT (max. 10 MB per stuk, tot 5 bestanden)',
+ 'reservations.import.parsing': 'Bestanden verwerken…',
+ 'reservations.import.previewHeading': '{count} reservering(en) gevonden',
+ 'reservations.import.previewEmpty': 'Er konden geen reserveringen worden geëxtraheerd uit de geüploade bestanden.',
+ 'reservations.import.removeItem': 'Verwijderen',
+ 'reservations.import.confirm': '{count} reservering(en) importeren',
+ 'reservations.import.back': 'Terug',
+ 'reservations.import.success': '{count} reservering(en) geïmporteerd',
+ 'reservations.import.partialFailure': '{created} geïmporteerd, {failed} mislukt',
+ 'reservations.import.error': 'Verwerking mislukt. Zorg ervoor dat het bestand een geldige boekingsbevestiging is.',
+ 'reservations.import.unavailable': 'Boeking importeren is niet beschikbaar op deze server.',
+ 'reservations.import.unsupportedFormat': 'Niet-ondersteund bestandsformaat. Gebruik EML, PDF, PKPass, HTML of TXT.',
+ 'reservations.import.fileTooLarge': 'Bestand "{name}" overschrijdt de limiet van 10 MB.',
+ 'reservations.airtrail.title': 'Importeren uit AirTrail',
+ 'reservations.airtrail.cta': 'AirTrail',
+ 'reservations.airtrail.synced': 'AirTrail',
+ 'reservations.airtrail.syncedHint':
+ 'Gesynchroniseerd vanuit AirTrail — wijzigingen blijven beide kanten op gesynchroniseerd.',
+ 'reservations.airtrail.notSynced': 'Niet gesynchroniseerd',
+ 'reservations.airtrail.notSyncedHint':
+ 'Deze vlucht is in AirTrail verwijderd en wordt niet meer gesynchroniseerd.',
+ 'reservations.airtrail.loadError': 'Je AirTrail-vluchten konden niet worden geladen.',
+ 'reservations.airtrail.imported': '{count} vlucht(en) geïmporteerd',
+ 'reservations.airtrail.skippedDuplicate': '{count} al in deze reis, overgeslagen',
+ 'reservations.airtrail.nothingImported': 'Niets om te importeren.',
+ 'reservations.airtrail.importError': 'Importeren mislukt. Probeer het opnieuw.',
+ 'reservations.airtrail.undo': 'Importeren uit AirTrail',
+ 'reservations.airtrail.alreadyImported': 'Geïmporteerd',
+ 'reservations.airtrail.duringTrip': 'Tijdens deze reis',
+ 'reservations.airtrail.otherFlights': 'Andere vluchten',
+ 'reservations.airtrail.empty': 'Geen vluchten gevonden in je AirTrail-account.',
+ 'reservations.airtrail.importCta': '{count} importeren',
+};
+export default reservations;
diff --git a/shared/src/i18n/nl/settings.ts b/shared/src/i18n/nl/settings.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6639f4c16445e63446d4fa17abaabc0f4bdcc9fd
--- /dev/null
+++ b/shared/src/i18n/nl/settings.ts
@@ -0,0 +1,348 @@
+import type { TranslationStrings } from '../types';
+
+const settings: TranslationStrings = {
+ 'settings.title': 'Instellingen',
+ 'settings.subtitle': 'Configureer je persoonlijke instellingen',
+ 'settings.tabs.display': 'Weergave',
+ 'settings.tabs.map': 'Kaart',
+ 'settings.tabs.notifications': 'Meldingen',
+ 'settings.tabs.integrations': 'Integraties',
+ 'settings.tabs.account': 'Account',
+ 'settings.tabs.offline': 'Offline',
+ 'settings.tabs.about': 'Over',
+ 'settings.map': 'Kaart',
+ 'settings.mapTemplate': 'Kaartsjabloon',
+ 'settings.mapTemplatePlaceholder.select': 'Selecteer sjabloon...',
+ 'settings.mapDefaultHint': 'Laat leeg voor OpenStreetMap (standaard)',
+ 'settings.mapTemplatePlaceholder':
+ 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
+ 'settings.mapHint': 'URL-sjabloon voor kaarttegels',
+ 'settings.mapProvider': 'Kaartprovider',
+ 'settings.mapProviderHint':
+ 'Geldt voor Trip Planner en Journey kaarten. Atlas gebruikt altijd Leaflet.',
+ 'settings.mapLeafletSubtitle': 'Klassiek 2D, elke raster-tile',
+ 'settings.mapMapboxSubtitle': 'Vector tiles, 3D-gebouwen & terrein',
+ 'settings.mapExperimental': 'Experimenteel',
+ 'settings.mapMapboxToken': 'Mapbox Access Token',
+ 'settings.mapMapboxTokenHint': 'Openbaar token (pk.*) van',
+ 'settings.mapMapboxTokenLink': 'mapbox.com → Access tokens',
+ 'settings.mapStyle': 'Kaartstijl',
+ 'settings.mapStylePlaceholder': 'Kies een Mapbox-stijl',
+ 'settings.mapStyleHint': 'Preset of eigen mapbox://styles/USER/ID URL',
+ 'settings.map3dBuildings': '3D-gebouwen & terrein',
+ 'settings.map3dHint':
+ 'Kanteling + echte 3D-gebouwenextrusies — werkt op elke stijl, inclusief satelliet.',
+ 'settings.mapHighQuality': 'Hoge kwaliteit modus',
+ 'settings.mapHighQualityHint':
+ 'Antialiasing + globeprojectie voor scherpere randen en een realistische wereldweergave.',
+ 'settings.mapHighQualityWarning':
+ 'Kan de prestaties op minder krachtige apparaten beïnvloeden.',
+ 'settings.mapTipLabel': 'Tip:',
+ 'settings.mapTip':
+ 'Rechts-klik en sleep om de kaart te roteren/kantelen. Middenklik om een locatie toe te voegen (rechts-klik is voor rotatie).',
+ 'settings.latitude': 'Breedtegraad',
+ 'settings.longitude': 'Lengtegraad',
+ 'settings.saveMap': 'Kaart opslaan',
+ 'settings.apiKeys': 'API-sleutels',
+ 'settings.mapsKey': 'Google Maps API-sleutel',
+ 'settings.mapsKeyHint':
+ 'Voor plaatsen zoeken. Vereist Places API (New). Verkrijgbaar op console.cloud.google.com',
+ 'settings.weatherKey': 'OpenWeatherMap API-sleutel',
+ 'settings.weatherKeyHint':
+ 'Voor weergegevens. Gratis op openweathermap.org/api',
+ 'settings.keyPlaceholder': 'Sleutel invoeren...',
+ 'settings.configured': 'Geconfigureerd',
+ 'settings.saveKeys': 'Sleutels opslaan',
+ 'settings.display': 'Weergave',
+ 'settings.colorMode': 'Kleurmodus',
+ 'settings.light': 'Licht',
+ 'settings.dark': 'Donker',
+ 'settings.auto': 'Automatisch',
+ 'settings.language': 'Taal',
+ 'settings.temperature': 'Temperatuureenheid',
+ 'settings.timeFormat': 'Tijdnotatie',
+ 'settings.blurBookingCodes': 'Boekingscodes vervagen',
+ 'settings.optimizeFromAccommodation': 'Route optimaliseren vanaf accommodatie',
+ 'settings.optimizeFromAccommodationHint':
+ 'Begin bij het optimaliseren van een dag de route bij het hotel waar je wakker wordt en eindig bij het hotel waar je die avond incheckt.',
+ 'settings.notifications': 'Meldingen',
+ 'settings.notifyTripInvite': 'Reisuitnodigingen',
+ 'settings.notifyBookingChange': 'Boekingswijzigingen',
+ 'settings.notifyTripReminder': 'Reisherinneringen',
+ 'settings.notifyTodoDue': 'Taak verloopt',
+ 'settings.notifyVacayInvite': 'Vacay-fusieuitnodigingen',
+ 'settings.notifyPhotosShared': "Gedeelde foto's (Immich)",
+ 'settings.notifyCollabMessage': 'Chatberichten (Collab)',
+ 'settings.notifyPackingTagged': 'Paklijst: toewijzingen',
+ 'settings.notifyWebhook': 'Webhook-meldingen',
+ 'settings.notificationsDisabled':
+ 'Meldingen zijn niet geconfigureerd. Vraag een beheerder om e-mail- of webhookmeldingen in te schakelen.',
+ 'settings.notificationsActive': 'Actief kanaal',
+ 'settings.notificationsManagedByAdmin':
+ 'Meldingsgebeurtenissen worden geconfigureerd door je beheerder.',
+ 'settings.on': 'Aan',
+ 'settings.off': 'Uit',
+ 'settings.mcp.title': 'MCP-configuratie',
+ 'settings.mcp.endpoint': 'MCP-eindpunt',
+ 'settings.mcp.clientConfig': 'Clientconfiguratie',
+ 'settings.mcp.clientConfigHint':
+ 'Vervang door een API-token uit de onderstaande lijst. Het pad naar npx moet mogelijk worden aangepast voor jouw systeem (bijv. C:\\PROGRA~1\\nodejs\\npx.cmd op Windows).',
+ 'settings.mcp.clientConfigHintOAuth':
+ 'Replace and with the credentials shown in the OAuth 2.1 client you created above. mcp-remote will open your browser to complete the authorization the first time you connect. The path to npx may need to be adjusted for your system (e.g. C:PROGRA~1\nodejs\npx.cmd on Windows).',
+ 'settings.mcp.copy': 'Kopiëren',
+ 'settings.mcp.copied': 'Gekopieerd!',
+ 'settings.mcp.apiTokens': 'API-tokens',
+ 'settings.mcp.createToken': 'Nieuw token aanmaken',
+ 'settings.mcp.noTokens':
+ 'Nog geen tokens. Maak er een aan om MCP-clients te verbinden.',
+ 'settings.mcp.tokenCreatedAt': 'Aangemaakt',
+ 'settings.mcp.tokenUsedAt': 'Gebruikt',
+ 'settings.mcp.deleteTokenTitle': 'Token verwijderen',
+ 'settings.mcp.deleteTokenMessage':
+ 'Dit token werkt onmiddellijk niet meer. Elke MCP-client die het gebruikt verliest de toegang.',
+ 'settings.mcp.modal.createTitle': 'API-token aanmaken',
+ 'settings.mcp.modal.tokenName': 'Tokennaam',
+ 'settings.mcp.modal.tokenNamePlaceholder': 'bijv. Claude Desktop, Werklaptop',
+ 'settings.mcp.modal.creating': 'Aanmaken…',
+ 'settings.mcp.modal.create': 'Token aanmaken',
+ 'settings.mcp.modal.createdTitle': 'Token aangemaakt',
+ 'settings.mcp.modal.createdWarning':
+ 'Dit token wordt slechts één keer getoond. Kopieer en bewaar het nu — het kan niet worden hersteld.',
+ 'settings.mcp.modal.done': 'Klaar',
+ 'settings.mcp.toast.created': 'Token aangemaakt',
+ 'settings.mcp.toast.createError': 'Token aanmaken mislukt',
+ 'settings.mcp.toast.deleted': 'Token verwijderd',
+ 'settings.mcp.toast.deleteError': 'Token verwijderen mislukt',
+ 'settings.mcp.apiTokensDeprecated':
+ 'API-tokens zijn verouderd en worden in een toekomstige versie verwijderd. Gebruik OAuth 2.1-clients in plaats daarvan.',
+ 'settings.oauth.clients': 'OAuth 2.1-clients',
+ 'settings.oauth.clientsHint':
+ 'Registreer OAuth 2.1-clients zodat externe MCP-toepassingen (Claude Web, Cursor, enz.) verbinding kunnen maken zonder statische tokens.',
+ 'settings.oauth.createClient': 'Nieuwe client',
+ 'settings.oauth.noClients': 'Geen OAuth-clients geregistreerd.',
+ 'settings.oauth.clientId': 'Client-ID',
+ 'settings.oauth.clientSecret': 'Clientgeheim',
+ 'settings.oauth.deleteClient': 'Client verwijderen',
+ 'settings.oauth.deleteClientMessage':
+ 'Deze client en alle actieve sessies worden permanent verwijderd. Elke toepassing die deze client gebruikt, verliest onmiddellijk de toegang.',
+ 'settings.oauth.rotateSecret': 'Geheim vernieuwen',
+ 'settings.oauth.rotateSecretMessage':
+ 'Er wordt een nieuw clientgeheim gegenereerd en alle bestaande sessies worden direct ongeldig. Werk uw toepassing bij voordat u dit venster sluit.',
+ 'settings.oauth.rotateSecretConfirm': 'Vernieuwen',
+ 'settings.oauth.rotateSecretConfirming': 'Vernieuwen…',
+ 'settings.oauth.rotateSecretDoneTitle': 'Nieuw geheim gegenereerd',
+ 'settings.oauth.rotateSecretDoneWarning':
+ 'Dit geheim wordt slechts eenmalig getoond. Kopieer het nu en werk uw toepassing bij — alle vorige sessies zijn ongeldig gemaakt.',
+ 'settings.oauth.activeSessions': 'Actieve OAuth-sessies',
+ 'settings.oauth.sessionScopes': 'Rechten',
+ 'settings.oauth.sessionExpires': 'Verloopt',
+ 'settings.oauth.revoke': 'Intrekken',
+ 'settings.oauth.revokeSession': 'Sessie intrekken',
+ 'settings.oauth.revokeSessionMessage':
+ 'Dit trekt onmiddellijk de toegang voor deze OAuth-sessie in.',
+ 'settings.oauth.modal.createTitle': 'OAuth-client registreren',
+ 'settings.oauth.modal.presets': 'Snelle instellingen',
+ 'settings.oauth.modal.clientName': 'Toepassingsnaam',
+ 'settings.oauth.modal.clientNamePlaceholder':
+ 'bijv. Claude Web, Mijn MCP-app',
+ 'settings.oauth.modal.redirectUris': "Redirect-URI's",
+ 'settings.oauth.modal.redirectUrisPlaceholder':
+ 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint':
+ 'Eén URI per regel. HTTPS vereist (localhost uitgezonderd). Exacte overeenkomst vereist.',
+ 'settings.oauth.modal.scopes': 'Toegestane rechten',
+ 'settings.oauth.modal.scopesHint':
+ "list_trips en get_trip_summary zijn altijd beschikbaar — geen recht vereist. Ze helpen de AI trip-ID's te ontdekken.",
+ 'settings.oauth.modal.selectAll': 'Alles selecteren',
+ 'settings.oauth.modal.deselectAll': 'Alles deselecteren',
+ 'settings.oauth.modal.creating': 'Registreren…',
+ 'settings.oauth.modal.create': 'Client registreren',
+ 'settings.oauth.modal.createdTitle': 'Client geregistreerd',
+ 'settings.oauth.modal.createdWarning':
+ 'Het clientgeheim wordt slechts eenmalig getoond. Kopieer het nu — het kan niet worden hersteld.',
+ 'settings.oauth.toast.createError':
+ 'OAuth-client kon niet worden geregistreerd',
+ 'settings.oauth.toast.deleted': 'OAuth-client verwijderd',
+ 'settings.oauth.toast.deleteError': 'OAuth-client kon niet worden verwijderd',
+ 'settings.oauth.toast.revoked': 'Sessie ingetrokken',
+ 'settings.oauth.toast.revokeError': 'Sessie kon niet worden ingetrokken',
+ 'settings.oauth.toast.rotateError': 'Clientgeheim kon niet worden vernieuwd',
+ 'settings.oauth.modal.machineClient': 'Machineclient (zonder browserinlog)',
+ 'settings.oauth.modal.machineClientHint':
+ "Gebruikt de client_credentials grant — geen redirect-URI's nodig. Het token wordt direct verstrekt via client_id + client_secret en handelt namens jou binnen de geselecteerde scopes.",
+ 'settings.oauth.modal.machineClientUsage':
+ 'Token ophalen: POST /oauth/token met grant_type=client_credentials, client_id en client_secret. Geen browser, geen vernieuwingstoken.',
+ 'settings.oauth.badge.machine': 'machine',
+ 'settings.account': 'Account',
+ 'settings.about': 'Over',
+ 'settings.about.reportBug': 'Bug melden',
+ 'settings.about.reportBugHint': 'Probleem gevonden? Laat het ons weten',
+ 'settings.about.featureRequest': 'Feature aanvragen',
+ 'settings.about.featureRequestHint': 'Stel een nieuwe functie voor',
+ 'settings.about.wikiHint': 'Documentatie en handleidingen',
+ 'settings.about.supporters.badge': 'Maandelijkse Steuners',
+ 'settings.about.supporters.title': 'Reisgezelschap voor TREK',
+ 'settings.about.supporters.subtitle':
+ 'Terwijl jij je volgende route plant, plannen deze mensen mee aan de toekomst van TREK. Hun maandelijkse bijdrage gaat rechtstreeks naar ontwikkeling en echte uren — zodat TREK Open Source blijft.',
+ 'settings.about.supporters.since': 'steuner sinds {date}',
+ 'settings.about.supporters.tierEmpty': 'Wees de eerste',
+ 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket',
+ 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP',
+ 'settings.about.supporter.tier.businessClassDreamer':
+ 'Business Class Dreamer',
+ 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller',
+ 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate',
+ 'settings.about.description':
+ "TREK is een zelf-gehoste reisplanner die je helpt je reizen te organiseren van het eerste idee tot de laatste herinnering. Dagplanning, budget, paklijsten, foto's en nog veel meer — alles op één plek, op je eigen server.",
+ 'settings.about.madeWith': 'Gemaakt met',
+ 'settings.about.madeBy':
+ 'door Maurice en een groeiende open-source community.',
+ 'settings.username': 'Gebruikersnaam',
+ 'settings.email': 'E-mail',
+ 'settings.role': 'Rol',
+ 'settings.roleAdmin': 'Beheerder',
+ 'settings.oidcLinked': 'Gekoppeld met',
+ 'settings.changePassword': 'Wachtwoord wijzigen',
+ 'settings.mustChangePassword':
+ 'U moet uw wachtwoord wijzigen voordat u kunt doorgaan. Stel hieronder een nieuw wachtwoord in.',
+ 'settings.currentPassword': 'Huidig wachtwoord',
+ 'settings.currentPasswordRequired': 'Huidig wachtwoord is verplicht',
+ 'settings.newPassword': 'Nieuw wachtwoord',
+ 'settings.confirmPassword': 'Bevestig nieuw wachtwoord',
+ 'settings.updatePassword': 'Wachtwoord bijwerken',
+ 'settings.passwordRequired': 'Voer het huidige en nieuwe wachtwoord in',
+ 'settings.passwordTooShort': 'Wachtwoord moet minimaal 8 tekens bevatten',
+ 'settings.passwordMismatch': 'Wachtwoorden komen niet overeen',
+ 'settings.passwordWeak':
+ 'Wachtwoord moet hoofdletters, kleine letters, een cijfer en een speciaal teken bevatten',
+ 'settings.passwordChanged': 'Wachtwoord succesvol gewijzigd',
+ 'settings.deleteAccount': 'Account verwijderen',
+ 'settings.deleteAccountTitle': 'Account verwijderen?',
+ 'settings.deleteAccountWarning':
+ 'Je account en al je reizen, plaatsen en bestanden worden permanent verwijderd. Deze actie kan niet ongedaan worden gemaakt.',
+ 'settings.deleteAccountConfirm': 'Permanent verwijderen',
+ 'settings.deleteBlockedTitle': 'Verwijderen niet mogelijk',
+ 'settings.deleteBlockedMessage':
+ 'Je bent de enige beheerder. Maak eerst een andere gebruiker beheerder voordat je je account verwijdert.',
+ 'settings.roleUser': 'Gebruiker',
+ 'settings.saveProfile': 'Profiel opslaan',
+ 'settings.mfa.title': 'Tweefactorauthenticatie (2FA)',
+ 'settings.mfa.description':
+ 'Voegt een tweede stap toe bij het inloggen. Gebruik een authenticator-app (Google Authenticator, Authy, etc.).',
+ 'settings.mfa.requiredByPolicy':
+ 'Je beheerder vereist tweestapsverificatie. Stel hieronder een authenticator-app in voordat je verdergaat.',
+ 'settings.mfa.backupTitle': 'Back-upcodes',
+ 'settings.mfa.backupDescription':
+ 'Gebruik deze eenmalige codes als je geen toegang meer hebt tot je authenticator-app.',
+ 'settings.mfa.backupWarning':
+ 'Sla deze codes nu op. Elke code kan maar een keer worden gebruikt.',
+ 'settings.mfa.backupCopy': 'Codes kopiëren',
+ 'settings.mfa.backupDownload': 'TXT downloaden',
+ 'settings.mfa.backupPrint': 'Afdrukken / PDF',
+ 'settings.mfa.backupCopied': 'Back-upcodes gekopieerd',
+ 'settings.mfa.enabled': '2FA is ingeschakeld op je account.',
+ 'settings.mfa.disabled': '2FA is niet ingeschakeld.',
+ 'settings.mfa.setup': 'Authenticator instellen',
+ 'settings.mfa.scanQr':
+ 'Scan deze QR-code met je app of voer de sleutel handmatig in.',
+ 'settings.mfa.secretLabel': 'Geheime sleutel (handmatige invoer)',
+ 'settings.mfa.codePlaceholder': '6-cijferige code',
+ 'settings.mfa.enable': '2FA inschakelen',
+ 'settings.mfa.cancelSetup': 'Annuleren',
+ 'settings.mfa.disableTitle': '2FA uitschakelen',
+ 'settings.mfa.disableHint':
+ 'Voer je wachtwoord en een huidige code van je authenticator in.',
+ 'settings.mfa.disable': '2FA uitschakelen',
+ 'settings.mfa.toastEnabled': 'Tweefactorauthenticatie ingeschakeld',
+ 'settings.mfa.toastDisabled': 'Tweefactorauthenticatie uitgeschakeld',
+ 'settings.mfa.demoBlocked': 'Niet beschikbaar in demomodus',
+ 'settings.toast.mapSaved': 'Kaartinstellingen opgeslagen',
+ 'settings.toast.keysSaved': 'API-sleutels opgeslagen',
+ 'settings.toast.displaySaved': 'Weergave-instellingen opgeslagen',
+ 'settings.toast.profileSaved': 'Profiel opgeslagen',
+ 'settings.uploadAvatar': 'Profielfoto uploaden',
+ 'settings.removeAvatar': 'Profielfoto verwijderen',
+ 'settings.avatarUploaded': 'Profielfoto bijgewerkt',
+ 'settings.avatarRemoved': 'Profielfoto verwijderd',
+ 'settings.avatarError': 'Uploaden mislukt',
+ 'settings.bookingLabels': 'Routelabels voor boekingen',
+ 'settings.bookingLabelsHint':
+ 'Toon station- / luchthavennamen op de kaart. Indien uit, alleen het icoon.',
+ 'settings.notifyVersionAvailable': 'Nieuwe versie beschikbaar',
+ 'settings.notificationPreferences.noChannels':
+ 'Er zijn geen meldingskanalen geconfigureerd. Vraag een beheerder om e-mail- of webhookmeldingen in te stellen.',
+ 'settings.webhookUrl.label': 'Webhook-URL',
+ 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...',
+ 'settings.webhookUrl.hint':
+ 'Voer je Discord-, Slack- of aangepaste webhook-URL in om meldingen te ontvangen.',
+ 'settings.webhookUrl.saved': 'Webhook-URL opgeslagen',
+ 'settings.webhookUrl.test': 'Testen',
+ 'settings.webhookUrl.testSuccess': 'Test-webhook succesvol verzonden',
+ 'settings.webhookUrl.testFailed': 'Test-webhook mislukt',
+ 'settings.ntfyUrl.topicLabel': 'Ntfy-onderwerp',
+ 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts',
+ 'settings.ntfyUrl.serverLabel': 'Ntfy-server-URL (optioneel)',
+ 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh',
+ 'settings.ntfyUrl.hint':
+ 'Voer je Ntfy-onderwerp in om pushmeldingen te ontvangen. Laat het serverveld leeg om de standaard te gebruiken die door je beheerder is ingesteld.',
+ 'settings.ntfyUrl.tokenLabel': 'Toegangstoken (optioneel)',
+ 'settings.ntfyUrl.tokenHint':
+ 'Vereist voor onderwerpen die met een wachtwoord zijn beveiligd.',
+ 'settings.ntfyUrl.saved': 'Ntfy-instellingen opgeslagen',
+ 'settings.ntfyUrl.test': 'Testen',
+ 'settings.ntfyUrl.testSuccess': 'Test-Ntfy-melding succesvol verzonden',
+ 'settings.ntfyUrl.testFailed': 'Test-Ntfy-melding mislukt',
+ 'settings.ntfyUrl.tokenCleared': 'Toegangstoken gewist',
+ 'settings.notificationPreferences.inapp': 'In-App',
+ 'settings.notificationPreferences.webhook': 'Webhook',
+ 'settings.notificationPreferences.email': 'Email',
+ 'settings.notificationPreferences.ntfy': 'Ntfy',
+ "settings.currency": "Currency",
+ "settings.currencyHint": "All amounts in Costs are converted to and shown in this currency.",
+ 'settings.passkey.title': 'Passkeys',
+ 'settings.passkey.description':
+ 'Log sneller en phishingbestendig in met een passkey — je vingerafdruk, gezicht, pincode of een hardwaresleutel. Je wachtwoord blijft als back-up bestaan.',
+ 'settings.passkey.notConfigured':
+ 'Passkeys zijn ingeschakeld maar nog niet volledig geconfigureerd op deze server. Vraag je beheerder om het WebAuthn-domein in te stellen.',
+ 'settings.passkey.add': 'Een passkey toevoegen',
+ 'settings.passkey.addTitle': 'Een passkey toevoegen',
+ 'settings.passkey.passwordPrompt':
+ 'Bevestig je huidige wachtwoord en volg daarna de aanwijzingen van je apparaat.',
+ 'settings.passkey.passwordRequired': 'Je huidige wachtwoord is vereist.',
+ 'settings.passkey.namePlaceholder': 'Naam (optioneel, bijv. "iPhone")',
+ 'settings.passkey.addedToast': 'Passkey toegevoegd',
+ 'settings.passkey.added': 'Toegevoegd',
+ 'settings.passkey.addError': 'Passkey kon niet worden toegevoegd',
+ 'settings.passkey.cancelled': 'Passkey instellen geannuleerd',
+ 'settings.passkey.deleted': 'Passkey verwijderd',
+ 'settings.passkey.deleteConfirm':
+ 'Deze passkey verwijderen? Bevestig met je wachtwoord.',
+ 'settings.passkey.rename': 'Naam wijzigen',
+ 'settings.passkey.defaultName': 'Passkey',
+ 'settings.passkey.synced': 'Gesynchroniseerd',
+ 'settings.passkey.deviceBound': 'Dit apparaat',
+ 'settings.passkey.lastUsed': 'Laatst gebruikt',
+ 'settings.passkey.neverUsed': 'Nooit gebruikt',
+ 'settings.mapPoiPill': 'Plaatsen op de kaart ontdekken',
+ 'settings.mapPoiPillHint': 'Toon een categorielabel op de reiskaart om restaurants, hotels en meer in de buurt te vinden via OpenStreetMap.',
+ 'settings.airtrail.title': 'AirTrail',
+ 'settings.airtrail.hint':
+ 'Verbind je zelf-gehoste AirTrail om vluchten te importeren en te synchroniseren. Maak een API-sleutel aan in AirTrail onder Instellingen → Beveiliging.',
+ 'settings.airtrail.url': 'Instantie-URL',
+ 'settings.airtrail.apiKey': 'API-sleutel',
+ 'settings.airtrail.apiKeyPlaceholder': 'Bearer API-sleutel',
+ 'settings.airtrail.apiKeyHint':
+ 'Aangemaakt in AirTrail onder Instellingen → Beveiliging. Versleuteld opgeslagen.',
+ 'settings.airtrail.allowInsecureTls': 'Zelfondertekende certificaten toestaan',
+ 'settings.airtrail.allowInsecureTlsHint':
+ 'Schakel dit alleen in voor een vertrouwde instantie op je eigen netwerk.',
+ 'settings.airtrail.connected': 'Verbonden',
+ 'settings.airtrail.notConnected': 'Niet verbonden',
+ 'settings.airtrail.toast.saved': 'AirTrail-verbinding opgeslagen',
+ 'settings.airtrail.toast.saveError': 'De verbinding kon niet worden opgeslagen',
+ 'settings.airtrail.test.button': 'Verbinding testen',
+ 'settings.airtrail.test.success': 'Verbonden — {count} vlucht(en) gevonden',
+ 'settings.airtrail.test.failed': 'Verbinding mislukt',
+};
+
+export default settings;
diff --git a/shared/src/i18n/nl/share.ts b/shared/src/i18n/nl/share.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a3f0a20874069d88bec1fe319b1fc45ccf9d04ff
--- /dev/null
+++ b/shared/src/i18n/nl/share.ts
@@ -0,0 +1,16 @@
+import type { TranslationStrings } from '../types';
+
+const share: TranslationStrings = {
+ 'share.linkTitle': 'Openbare link',
+ 'share.linkHint':
+ 'Maak een link die iedereen kan gebruiken om deze reis te bekijken zonder in te loggen. Alleen-lezen — bewerken niet mogelijk.',
+ 'share.createLink': 'Link aanmaken',
+ 'share.deleteLink': 'Link verwijderen',
+ 'share.createError': 'Kon link niet aanmaken',
+ 'share.permMap': 'Kaart en plan',
+ 'share.permBookings': 'Boekingen',
+ 'share.permPacking': 'Paklijst',
+ 'share.permBudget': 'Budget',
+ 'share.permCollab': 'Chat',
+};
+export default share;
diff --git a/shared/src/i18n/nl/shared.ts b/shared/src/i18n/nl/shared.ts
new file mode 100644
index 0000000000000000000000000000000000000000..308e87fd22783f7407d13bef33ef79ecceaa7185
--- /dev/null
+++ b/shared/src/i18n/nl/shared.ts
@@ -0,0 +1,21 @@
+import type { TranslationStrings } from '../types';
+
+const shared: TranslationStrings = {
+ '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',
+};
+export default shared;
diff --git a/shared/src/i18n/nl/stats.ts b/shared/src/i18n/nl/stats.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ca2903542aa92d534b90e55d9912bf60ecadd8db
--- /dev/null
+++ b/shared/src/i18n/nl/stats.ts
@@ -0,0 +1,13 @@
+import type { TranslationStrings } from '../types';
+
+const stats: TranslationStrings = {
+ 'stats.countries': 'Landen',
+ 'stats.cities': 'Steden',
+ 'stats.trips': 'Reizen',
+ 'stats.places': 'Plaatsen',
+ 'stats.worldProgress': 'Wereldvoortgang',
+ 'stats.visited': 'bezocht',
+ 'stats.remaining': 'resterend',
+ 'stats.visitedCountries': 'Bezochte landen',
+};
+export default stats;
diff --git a/shared/src/i18n/nl/system_notice.ts b/shared/src/i18n/nl/system_notice.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eb423d37f74b96248b1392413b5da9179ef73e11
--- /dev/null
+++ b/shared/src/i18n/nl/system_notice.ts
@@ -0,0 +1,61 @@
+import type { TranslationStrings } from '../types';
+
+const system_notice: TranslationStrings = {
+ 'system_notice.welcome_v1.title': 'Welkom bij TREK',
+ 'system_notice.welcome_v1.body':
+ "Jouw alles-in-één reisplanner. Maak reisschema's, deel trips met vrienden en blijf georganiseerd — online en offline.",
+ 'system_notice.welcome_v1.cta_label': 'Reis plannen',
+ 'system_notice.welcome_v1.hero_alt':
+ 'Schilderachtige reisbestemming met TREK interface',
+ 'system_notice.welcome_v1.highlight_plan': "Dag-voor-dag reisschema's",
+ 'system_notice.welcome_v1.highlight_share': 'Samenwerken met reisgezelschap',
+ 'system_notice.welcome_v1.highlight_offline': 'Werkt offline op mobiel',
+ 'system_notice.dev_test_modal.title': '[Dev] Test notice',
+ 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.',
+ 'system_notice.pager.prev': 'Vorige melding',
+ 'system_notice.pager.next': 'Volgende melding',
+ 'system_notice.pager.counter': '{current} / {total}',
+ 'system_notice.pager.goto': 'Ga naar melding {n}',
+ 'system_notice.pager.position': 'Melding {current} van {total}',
+ 'system_notice.v3_photos.title': "Foto's zijn verplaatst in 3.0",
+ 'system_notice.v3_photos.body':
+ "**Foto's** in de Reisplanner zijn verwijderd. Je foto's zijn veilig — TREK heeft je Immich- of Synology-bibliotheek nooit gewijzigd.\n\nFoto's leven nu in de **Journey**-addon. Journey is optioneel — als het nog niet beschikbaar is, vraag je admin het te activeren via Admin → Addons.",
+ 'system_notice.v3_journey.title': 'Maak kennis met Journey — reisdagboek',
+ 'system_notice.v3_journey.body':
+ 'Documenteer je reizen als rijke verhalen met tijdlijnen, fotogalerijen en interactieve kaarten.',
+ 'system_notice.v3_journey.cta_label': 'Journey openen',
+ 'system_notice.v3_journey.highlight_timeline':
+ 'Dag-voor-dag tijdlijn & galerij',
+ 'system_notice.v3_journey.highlight_photos':
+ 'Importeer van Immich of Synology',
+ 'system_notice.v3_journey.highlight_share':
+ 'Openbaar delen — geen login vereist',
+ 'system_notice.v3_journey.highlight_export': 'Exporteer als PDF-fotoboek',
+ 'system_notice.v3_features.title': 'Meer hoogtepunten in 3.0',
+ 'system_notice.v3_features.body':
+ 'Nog een paar dingen die het weten waard zijn in deze release.',
+ 'system_notice.v3_features.highlight_dashboard':
+ 'Mobile-first dashboard herontwerp',
+ 'system_notice.v3_features.highlight_offline':
+ 'Volledige offline modus als PWA',
+ 'system_notice.v3_features.highlight_search': 'Realtime plaatsautocomplete',
+ 'system_notice.v3_features.highlight_import':
+ 'Importeer plaatsen uit KMZ/KML-bestanden',
+ 'system_notice.v3_mcp.title': 'MCP: OAuth 2.1-upgrade',
+ 'system_notice.v3_mcp.body':
+ 'De MCP-integratie is volledig vernieuwd. OAuth 2.1 is nu de aanbevolen authenticatiemethode. Statische tokens (trek_…) zijn verouderd en worden verwijderd in een toekomstige versie.',
+ 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 aanbevolen (mcp-remote)',
+ 'system_notice.v3_mcp.highlight_scopes':
+ '24 gedetailleerde toestemmingsscopes',
+ 'system_notice.v3_mcp.highlight_deprecated':
+ 'Statische trek_-tokens verouderd',
+ 'system_notice.v3_mcp.highlight_tools': 'Uitgebreide tools & prompts',
+ 'system_notice.v3_thankyou.title': 'Een persoonlijk woord van mij',
+ 'system_notice.v3_thankyou.body':
+ 'Voordat je verdergaat — ik wil even stilstaan.\n\nTREK begon als een zijproject dat ik bouwde voor mijn eigen reizen. Ik had nooit gedacht dat het zou uitgroeien tot iets waar 4.000 van jullie op vertrouwen om avonturen te plannen. Elke ster, elke issue, elk functieverzoek — ik lees ze allemaal, en ze houden me op de been tijdens de late avonden tussen een fulltime baan en de universiteit.\n\nIk wil dat jullie weten: TREK zal altijd open source zijn, altijd self-hosted, altijd van jullie. Geen tracking, geen abonnementen, geen addertjes. Gewoon een tool gebouwd door iemand die net zo veel van reizen houdt als jullie.\n\nSpeciale dank aan [jubnl](https://github.com/jubnl) — je bent een ongelooflijke medewerker geworden. Zo veel van wat 3.0 geweldig maakt draagt jouw vingerafdruk. Bedankt dat je in dit project geloofde toen het nog ruw was.\n\nEn aan ieder van jullie die een bug meldde, een string vertaalde, TREK deelde met een vriend of het simpelweg gebruikte om een reis te plannen — **bedankt**. Jullie zijn de reden dat dit bestaat.\n\nOp nog vele avonturen samen.\n\n— Maurice\n\n---\n\n[Sluit je aan bij de community op Discord](https://discord.gg/7Q6M6jDwzf)\n\nAls TREK je reizen beter maakt, houdt een [klein kopje koffie](https://ko-fi.com/mauriceboe) altijd de lichten aan.',
+ 'system_notice.v3014_whitespace_collision.title':
+ 'Actie vereist: gebruikersaccountconflict',
+ 'system_notice.v3014_whitespace_collision.body':
+ 'De 3.0.14-upgrade heeft één of meer conflicten in gebruikersnaam of e-mailadres gedetecteerd, veroorzaakt door spaties aan het begin of einde van opgeslagen waarden. Getroffen accounts zijn automatisch hernoemd. Controleer de serverlogboeken op regels die beginnen met **[migration] WHITESPACE COLLISION** om te achterhalen welke accounts moeten worden beoordeeld.',
+};
+export default system_notice;
diff --git a/shared/src/i18n/nl/todo.ts b/shared/src/i18n/nl/todo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ac17300e6e30b332cbe90541a4964a5a8f74dd47
--- /dev/null
+++ b/shared/src/i18n/nl/todo.ts
@@ -0,0 +1,40 @@
+import type { TranslationStrings } from '../types';
+
+const todo: TranslationStrings = {
+ 'todo.subtab.packing': 'Paklijst',
+ 'todo.subtab.todo': 'Taken',
+ 'todo.completed': 'voltooid',
+ 'todo.filter.all': 'Alles',
+ 'todo.filter.open': 'Openstaand',
+ 'todo.filter.done': 'Klaar',
+ 'todo.uncategorized': 'Zonder categorie',
+ 'todo.namePlaceholder': 'Taaknaam',
+ 'todo.descriptionPlaceholder': 'Beschrijving (optioneel)',
+ 'todo.unassigned': 'Niet toegewezen',
+ 'todo.noCategory': 'Geen categorie',
+ 'todo.hasDescription': 'Heeft beschrijving',
+ 'todo.addItem': 'Nieuwe taak',
+ 'todo.sidebar.sortBy': 'Sorteren op',
+ 'todo.priority': 'Prioriteit',
+ 'todo.newCategoryLabel': 'nieuw',
+ 'todo.newCategory': 'Categorienaam',
+ 'todo.addCategory': 'Categorie toevoegen',
+ 'todo.newItem': 'Nieuwe taak',
+ 'todo.empty': 'Nog geen taken. Voeg een taak toe om te beginnen!',
+ 'todo.filter.my': 'Mijn taken',
+ 'todo.filter.overdue': 'Verlopen',
+ 'todo.sidebar.tasks': 'Taken',
+ 'todo.sidebar.categories': 'Categorieën',
+ 'todo.detail.title': 'Taak',
+ 'todo.detail.description': 'Beschrijving',
+ 'todo.detail.category': 'Categorie',
+ 'todo.detail.dueDate': 'Vervaldatum',
+ 'todo.detail.assignedTo': 'Toegewezen aan',
+ 'todo.detail.delete': 'Verwijderen',
+ 'todo.detail.save': 'Wijzigingen opslaan',
+ 'todo.detail.create': 'Taak aanmaken',
+ 'todo.detail.priority': 'Prioriteit',
+ 'todo.detail.noPriority': 'Geen',
+ 'todo.sortByPrio': 'Prioriteit',
+};
+export default todo;
diff --git a/shared/src/i18n/nl/transport.ts b/shared/src/i18n/nl/transport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d7d79b33cd16a2ac56bc999c1ae71614eaff70d5
--- /dev/null
+++ b/shared/src/i18n/nl/transport.ts
@@ -0,0 +1,10 @@
+import type { TranslationStrings } from '../types';
+
+const transport: TranslationStrings = {
+ 'transport.addTransport': 'Vervoer toevoegen',
+ 'transport.modalTitle.create': 'Vervoer toevoegen',
+ 'transport.modalTitle.edit': 'Vervoer bewerken',
+ 'transport.title': 'Transport',
+ 'transport.addManual': 'Handmatig transport',
+};
+export default transport;
diff --git a/shared/src/i18n/nl/trip.ts b/shared/src/i18n/nl/trip.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b3ced4437320e52b283c08c5dee216856417445e
--- /dev/null
+++ b/shared/src/i18n/nl/trip.ts
@@ -0,0 +1,32 @@
+import type { TranslationStrings } from '../types';
+
+const trip: TranslationStrings = {
+ 'trip.tabs.plan': 'Plan',
+ 'trip.tabs.transports': 'Transport',
+ 'trip.tabs.reservations': 'Boekingen',
+ 'trip.tabs.reservationsShort': 'Boek',
+ 'trip.tabs.packing': 'Paklijst',
+ 'trip.tabs.packingShort': 'Inpakken',
+ 'trip.tabs.lists': 'Lijsten',
+ 'trip.tabs.listsShort': 'Lijsten',
+ 'trip.tabs.budget': "Costs",
+ 'trip.tabs.files': 'Bestanden',
+ 'trip.loading': 'Reis laden...',
+ 'trip.loadingPhotos': 'Plaatsfoto laden...',
+ 'trip.mobilePlan': 'Plan',
+ 'trip.mobilePlaces': 'Plaatsen',
+ 'trip.toast.placeUpdated': 'Plaats bijgewerkt',
+ 'trip.toast.placeAdded': 'Plaats toegevoegd',
+ 'trip.toast.placeDeleted': 'Plaats verwijderd',
+ 'trip.toast.selectDay': 'Selecteer eerst een dag',
+ 'trip.toast.assignedToDay': 'Plaats toegewezen aan dag',
+ 'trip.toast.reorderError': 'Herordenen mislukt',
+ 'trip.toast.reservationUpdated': 'Reservering bijgewerkt',
+ 'trip.toast.reservationAdded': 'Reservering toegevoegd',
+ 'trip.toast.deleted': 'Verwijderd',
+ 'trip.confirm.deletePlace':
+ 'Weet je zeker dat je deze plaats wilt verwijderen?',
+ 'trip.confirm.deletePlaces': '{count} plaatsen verwijderen?',
+ 'trip.toast.placesDeleted': '{count} plaatsen verwijderd',
+};
+export default trip;
diff --git a/shared/src/i18n/nl/trips.ts b/shared/src/i18n/nl/trips.ts
new file mode 100644
index 0000000000000000000000000000000000000000..601215be215ca737e9a3179530100542f66164f9
--- /dev/null
+++ b/shared/src/i18n/nl/trips.ts
@@ -0,0 +1,17 @@
+import type { TranslationStrings } from '../types';
+
+const trips: TranslationStrings = {
+ 'trips.memberRemoved': '{username} verwijderd',
+ 'trips.memberRemoveError': 'Verwijderen mislukt',
+ 'trips.memberAdded': '{username} toegevoegd',
+ 'trips.memberAddError': 'Toevoegen mislukt',
+ 'trips.reminder': 'Herinnering',
+ 'trips.reminderNone': 'Geen',
+ 'trips.reminderDay': 'dag',
+ 'trips.reminderDays': 'dagen',
+ 'trips.reminderCustom': 'Aangepast',
+ 'trips.reminderDaysBefore': 'dagen voor vertrek',
+ 'trips.reminderDisabledHint':
+ 'Reisherinneringen zijn uitgeschakeld. Schakel ze in via Admin > Instellingen > Meldingen.',
+};
+export default trips;
diff --git a/shared/src/i18n/nl/undo.ts b/shared/src/i18n/nl/undo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4215dc3efb4ec39f07c51ad6f09a953648c7018b
--- /dev/null
+++ b/shared/src/i18n/nl/undo.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const undo: TranslationStrings = {
+ '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.deletePlaces': 'Plaatsen verwijderd',
+ 'undo.moveDay': 'Locatie naar andere dag verplaatst',
+ 'undo.lock': 'Vergrendeling locatie gewijzigd',
+ 'undo.importGpx': 'GPX-import',
+ 'undo.importKeyholeMarkup': 'KMZ/KML-import',
+ 'undo.importGoogleList': 'Google Maps-import',
+ 'undo.importNaverList': 'Naver Maps-import',
+ 'undo.addPlace': 'Locatie toegevoegd',
+ 'undo.done': 'Ongedaan gemaakt: {action}',
+ 'undo.importBooking': 'Boekingsbevestiging importeren',
+};
+export default undo;
diff --git a/shared/src/i18n/nl/vacay.ts b/shared/src/i18n/nl/vacay.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2b99888f7d6930c7fc8910f5918b75ca21ec6f8c
--- /dev/null
+++ b/shared/src/i18n/nl/vacay.ts
@@ -0,0 +1,105 @@
+import type { TranslationStrings } from '../types';
+
+const vacay: TranslationStrings = {
+ 'vacay.subtitle': 'Plan en beheer vakantiedagen',
+ 'vacay.settings': 'Instellingen',
+ 'vacay.year': 'Jaar',
+ 'vacay.addYear': 'Volgend jaar toevoegen',
+ 'vacay.addPrevYear': 'Vorig jaar toevoegen',
+ 'vacay.removeYear': 'Jaar verwijderen',
+ 'vacay.removeYearConfirm': '{year} verwijderen?',
+ 'vacay.removeYearHint':
+ 'Alle vakantie-invoeren en bedrijfsvakanties voor dit jaar worden permanent verwijderd.',
+ 'vacay.remove': 'Verwijderen',
+ 'vacay.persons': 'Personen',
+ 'vacay.noPersons': 'Geen personen toegevoegd',
+ 'vacay.addPerson': 'Persoon toevoegen',
+ 'vacay.editPerson': 'Persoon bewerken',
+ 'vacay.removePerson': 'Persoon verwijderen',
+ 'vacay.removePersonConfirm': '{name} verwijderen?',
+ 'vacay.removePersonHint':
+ 'Alle vakantie-invoeren voor deze persoon worden permanent verwijderd.',
+ 'vacay.personName': 'Naam',
+ 'vacay.personNamePlaceholder': 'Naam invoeren',
+ 'vacay.color': 'Kleur',
+ 'vacay.add': 'Toevoegen',
+ 'vacay.legend': 'Legenda',
+ 'vacay.publicHoliday': 'Feestdag',
+ 'vacay.companyHoliday': 'Bedrijfsvakantie',
+ 'vacay.weekend': 'Weekend',
+ 'vacay.modeVacation': 'Vakantie',
+ 'vacay.modeCompany': 'Bedrijfsvakantie',
+ 'vacay.entitlement': 'Recht',
+ 'vacay.entitlementDays': 'Dagen',
+ 'vacay.used': 'Gebruikt',
+ 'vacay.remaining': 'Resterend',
+ 'vacay.carriedOver': 'van {year}',
+ 'vacay.blockWeekends': 'Weekenden blokkeren',
+ 'vacay.blockWeekendsHint': 'Voorkom vakantie-invoeren op zaterdag en zondag',
+ 'vacay.weekendDays': 'Weekenddagen',
+ 'vacay.mon': 'Ma',
+ 'vacay.tue': 'Di',
+ 'vacay.wed': 'Wo',
+ 'vacay.thu': 'Do',
+ 'vacay.fri': 'Vr',
+ 'vacay.sat': 'Za',
+ 'vacay.sun': 'Zo',
+ 'vacay.publicHolidays': 'Feestdagen',
+ 'vacay.publicHolidaysHint': 'Markeer feestdagen in de kalender',
+ 'vacay.selectCountry': 'Selecteer land',
+ 'vacay.selectRegion': 'Selecteer regio (optioneel)',
+ 'vacay.companyHolidays': 'Bedrijfsvakanties',
+ 'vacay.companyHolidaysHint':
+ 'Sta het markeren van bedrijfsbrede vakantiedagen toe',
+ 'vacay.companyHolidaysNoDeduct':
+ 'Bedrijfsvakanties worden niet afgetrokken van vakantiedagen.',
+ 'vacay.weekStart': 'Week begint op',
+ 'vacay.weekStartHint': 'Kies of de kalenderweek op maandag of zondag begint',
+ 'vacay.carryOver': 'Overdracht',
+ 'vacay.carryOverHint':
+ 'Draag resterende vakantiedagen automatisch over naar het volgende jaar',
+ 'vacay.sharing': 'Delen',
+ 'vacay.sharingHint': 'Deel je vakantieplan met andere TREK-gebruikers',
+ 'vacay.owner': 'Eigenaar',
+ 'vacay.shareEmailPlaceholder': 'E-mail van TREK-gebruiker',
+ 'vacay.shareSuccess': 'Plan succesvol gedeeld',
+ 'vacay.shareError': 'Plan delen mislukt',
+ 'vacay.dissolve': 'Fusie opheffen',
+ 'vacay.dissolveHint':
+ 'Kalenders weer scheiden. Je invoeren blijven behouden.',
+ 'vacay.dissolveAction': 'Opheffen',
+ 'vacay.dissolved': 'Kalender gescheiden',
+ 'vacay.fusedWith': 'Gefuseerd met',
+ 'vacay.you': 'jij',
+ 'vacay.noData': 'Geen gegevens',
+ 'vacay.changeColor': 'Kleur wijzigen',
+ 'vacay.inviteUser': 'Gebruiker uitnodigen',
+ 'vacay.inviteHint':
+ 'Nodig een andere TREK-gebruiker uit om een gecombineerde vakantiekalender te delen.',
+ 'vacay.selectUser': 'Selecteer gebruiker',
+ 'vacay.sendInvite': 'Uitnodiging verzenden',
+ 'vacay.inviteSent': 'Uitnodiging verzonden',
+ 'vacay.inviteError': 'Uitnodiging verzenden mislukt',
+ 'vacay.pending': 'in behandeling',
+ 'vacay.noUsersAvailable': 'Geen gebruikers beschikbaar',
+ 'vacay.accept': 'Accepteren',
+ 'vacay.decline': 'Afwijzen',
+ 'vacay.acceptFusion': 'Accepteren en fuseren',
+ 'vacay.inviteTitle': 'Fusieverzoek',
+ 'vacay.inviteWantsToFuse': 'wil een vakantiekalender met je delen.',
+ 'vacay.fuseInfo1':
+ 'Jullie zien allebei alle vakantie-invoeren in één gedeelde kalender.',
+ 'vacay.fuseInfo2':
+ 'Beide partijen kunnen invoeren voor elkaar aanmaken en bewerken.',
+ 'vacay.fuseInfo3':
+ 'Beide partijen kunnen invoeren verwijderen en vakantierechten wijzigen.',
+ 'vacay.fuseInfo4':
+ 'Instellingen zoals feestdagen en bedrijfsvakanties worden gedeeld.',
+ 'vacay.fuseInfo5':
+ 'De fusie kan op elk moment door beide partijen worden opgeheven. Je invoeren blijven behouden.',
+ 'vacay.addCalendar': 'Kalender toevoegen',
+ 'vacay.calendarColor': 'Kleur',
+ 'vacay.calendarLabel': 'Label',
+ 'vacay.noCalendars': 'Geen kalenders',
+};
+export default vacay;
diff --git a/shared/src/i18n/pl/admin.ts b/shared/src/i18n/pl/admin.ts
new file mode 100644
index 0000000000000000000000000000000000000000..666a3d6d749eeca4a4bc5634b7fc4cd8d0a4ee39
--- /dev/null
+++ b/shared/src/i18n/pl/admin.ts
@@ -0,0 +1,395 @@
+import type { TranslationStrings } from '../types';
+
+const admin: TranslationStrings = {
+ 'admin.smtp.title': 'E-maile i powiadomienia',
+ 'admin.smtp.hint':
+ 'Konfiguracja SMTP dla powiadomień e-mail. Opcjonalnie: URL Webhooka dla Discorda, Slacka, itp.',
+ 'admin.smtp.testButton': 'Wyślij testowego e-maila',
+ 'admin.smtp.testSuccess': 'Testowy e-mail został wysłany pomyślnie',
+ 'admin.smtp.testFailed': 'Nie udało się wysłać testowego e-maila',
+ 'admin.title': 'Administracja',
+ 'admin.subtitle': 'Zarządzanie użytkownikami i ustawienia systemowe',
+ 'admin.tabs.users': 'Użytkownicy',
+ 'admin.tabs.categories': 'Kategorie',
+ 'admin.tabs.backup': 'Backupy',
+ 'admin.tabs.notifications': 'Powiadomienia',
+ 'admin.tabs.audit': 'Audit',
+ 'admin.stats.users': 'Użytkownicy',
+ 'admin.stats.trips': 'Podróże',
+ 'admin.stats.places': 'Miejsca',
+ 'admin.stats.photos': 'Zdjęcia',
+ 'admin.stats.files': 'Pliki',
+ 'admin.table.user': 'Użytkownik',
+ 'admin.table.email': 'E-mail',
+ 'admin.table.role': 'Rola',
+ 'admin.table.created': 'Utworzono',
+ 'admin.table.lastLogin': 'Ostatnie logowanie',
+ 'admin.table.actions': 'Akcje',
+ 'admin.you': '(Ty)',
+ 'admin.editUser': 'Edytuj użytkownika',
+ 'admin.newPassword': 'Nowe hasło',
+ 'admin.newPasswordHint': 'Pozostaw puste, aby zachować obecne hasło',
+ 'admin.deleteUser':
+ 'Usunąć użytkownika "{name}"? Wszystkie jego podróże zostaną trwale usunięte.',
+ 'admin.deleteUserTitle': 'Usuń użytkownika',
+ 'admin.newPasswordPlaceholder': 'Podaj nowe hasło...',
+ 'admin.toast.loadError': 'Nie udało się załadować danych administratora',
+ 'admin.toast.userUpdated': 'Użytkownik został zaktualizowany',
+ 'admin.toast.updateError': 'Nie udało się zaktualizować użytkownika',
+ 'admin.toast.userDeleted': 'Użytkownik został usunięty',
+ 'admin.toast.deleteError': 'Nie udało się usunąć użytkownika',
+ 'admin.toast.cannotDeleteSelf': 'Nie można usunąć własnego konta',
+ 'admin.toast.userCreated': 'Użytkownik został utworzony',
+ 'admin.toast.createError': 'Nie udało się utworzyć użytkownika',
+ 'admin.toast.fieldsRequired': 'Nazwa użytkownika, e-mail i hasło są wymagane',
+ 'admin.createUser': 'Utwórz użytkownika',
+ 'admin.invite.title': 'Linki zaproszeń',
+ 'admin.invite.subtitle': 'Twórz jednorazowe linki do rejestracji',
+ 'admin.invite.create': 'Utwórz link',
+ 'admin.invite.createAndCopy': 'Utwórz i skopiuj',
+ 'admin.invite.empty': 'Nie utworzono jeszcze żadnych linków zaproszeń',
+ 'admin.invite.maxUses': 'Maksymalna liczba użyć',
+ 'admin.invite.expiry': 'Wygasa po',
+ 'admin.invite.uses': 'użycia',
+ 'admin.invite.expiresAt': 'wygasa',
+ 'admin.invite.createdBy': 'utworzone przez',
+ 'admin.invite.active': 'Aktywny',
+ 'admin.invite.expired': 'Wygasł',
+ 'admin.invite.usedUp': 'Wykorzystany',
+ 'admin.invite.copied': 'Link zaproszenia został skopiowany do schowka',
+ 'admin.invite.copyLink': 'Skopiuj link',
+ 'admin.invite.deleted': 'Link zaproszenia został usunięty',
+ 'admin.invite.createError': 'Nie udało się utworzyć linku zaproszenia',
+ 'admin.invite.deleteError': 'Nie udało się usunąć linku zaproszenia',
+ 'admin.tabs.settings': 'Ustawienia',
+ 'admin.allowRegistration': 'Zezwól na rejestrację',
+ 'admin.allowRegistrationHint':
+ 'Nowi użytkownicy mogą się rejestrować samodzielnie',
+ 'admin.authMethods': 'Authentication Methods',
+ 'admin.passwordLogin': 'Password Login',
+ 'admin.passwordLoginHint': 'Allow users to sign in with email and password',
+ 'admin.passwordRegistration': 'Password Registration',
+ 'admin.passwordRegistrationHint':
+ 'Allow new users to register with email and password',
+ 'admin.oidcLogin': 'SSO Login',
+ 'admin.oidcLoginHint': 'Allow users to sign in with SSO',
+ 'admin.oidcRegistration': 'SSO Auto-Provisioning',
+ 'admin.oidcRegistrationHint':
+ 'Automatically create accounts for new SSO users',
+ 'admin.envOverrideHint':
+ 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.',
+ 'admin.lockoutWarning': 'At least one login method must remain enabled',
+ 'admin.requireMfa': 'Wymagaj uwierzytelniania dwuskładnikowego (2FA)',
+ 'admin.requireMfaHint':
+ 'Użytkownicy bez 2FA muszą ukończyć konfigurację w Ustawieniach zanim zaczną korzystać z aplikacji.',
+ 'admin.apiKeys': 'Klucze API',
+ 'admin.apiKeysHint':
+ 'Opcjonalne. Umożliwiają pobieranie większej ilości danych o miejscach, takich jak zdjęcia i pogoda.',
+ 'admin.mapsKey': 'Klucz Google Maps API',
+ 'admin.mapsKeyHint':
+ 'Wymagany do wyszukiwania miejsc. Uzyskaj go na console.cloud.google.com',
+ 'admin.mapsKeyHintLong':
+ 'Bez klucza API, OpenStreetMap jest wykorzystywany do wyszukiwania miejsc. Z kluczem API Google, zdjęcia, oceny i godziny otwarcia również mogą być pobierane. Uzyskaj go na console.cloud.google.com.',
+ 'admin.recommended': 'Polecane',
+ 'admin.weatherKey': 'Klucz OpenWeatherMap API',
+ 'admin.weatherKeyHint':
+ 'Do danych pogodowych. Uzyskaj go bezpłatnie na openweathermap.org',
+ 'admin.validateKey': 'Testuj',
+ 'admin.keyValid': 'Połączono',
+ 'admin.keyInvalid': 'Niepoprawny',
+ 'admin.keySaved': 'Klucze API zostały zapisane',
+ 'admin.oidcTitle': 'Logowanie jednokrotne (OIDC)',
+ 'admin.oidcSubtitle':
+ 'Zezwól na logowanie za pomocą zewnętrznych dostawców, takich jak Google, Apple, Authentik lub Keycloak.',
+ 'admin.oidcDisplayName': 'Wyświetlana nazwa',
+ 'admin.oidcIssuer': 'URL wystawcy',
+ 'admin.oidcIssuerHint':
+ 'Adres URL wystawcy OpenID Connect dostawcy, np. https://accounts.google.com',
+ 'admin.oidcSaved': 'Konfiguracja OIDC została zapisana',
+ 'admin.oidcOnlyMode': 'Wyłącz uwierzytelnianie hasłem',
+ 'admin.oidcOnlyModeHint':
+ 'Po włączeniu dozwolone jest tylko logowanie jednokrotne. Logowanie i rejestracja za pomocą hasła są zablokowane.',
+ 'admin.fileTypes': 'Dozwolone typy plików',
+ 'admin.fileTypesHint':
+ 'Ustaw, które typy plików mogą być przesyłane przez użytkowników.',
+ 'admin.fileTypesFormat':
+ 'Rozszerzenia oddzielone przecinkami (np. jpg,png,pdf,doc). Użyj * aby zezwolić na wszystkie typy.',
+ 'admin.fileTypesSaved': 'Ustawienia typów plików zostały zapisane',
+ 'admin.placesPhotos.title': 'Zdjęcia miejsc',
+ 'admin.placesPhotos.subtitle':
+ 'Pobiera zdjęcia z Google Places API. Wyłącz, aby zaoszczędzić limit API. Zdjęcia z Wikimedia nie są objęte.',
+ 'admin.placesAutocomplete.title': 'Autouzupełnianie miejsc',
+ 'admin.placesAutocomplete.subtitle':
+ 'Używa Google Places API do sugestii wyszukiwania. Wyłącz, aby zaoszczędzić limit API.',
+ 'admin.placesDetails.title': 'Szczegóły miejsca',
+ 'admin.placesDetails.subtitle':
+ 'Pobiera szczegółowe informacje o miejscu (godziny, ocena, strona) z Google Places API. Wyłącz, aby zaoszczędzić limit API.',
+ 'admin.bagTracking.title': 'Kontrola bagażu',
+ 'admin.bagTracking.subtitle':
+ 'Włącz wagę i przypisywanie do toreb dla przedmiotów do pakowania',
+ 'admin.collab.chat.title': 'Czat',
+ 'admin.collab.chat.subtitle': 'Wiadomości w czasie rzeczywistym',
+ 'admin.collab.notes.title': 'Notatki',
+ 'admin.collab.notes.subtitle': 'Wspólne notatki i dokumenty',
+ 'admin.collab.polls.title': 'Ankiety',
+ 'admin.collab.polls.subtitle': 'Ankiety grupowe i głosowania',
+ 'admin.collab.whatsnext.title': 'Co dalej',
+ 'admin.collab.whatsnext.subtitle': 'Sugestie aktywności i następne kroki',
+ 'admin.tabs.config': 'Personalizacja',
+ 'admin.tabs.defaults': 'Domyślne ustawienia',
+ 'admin.defaultSettings.title': 'Domyślne ustawienia użytkownika',
+ 'admin.defaultSettings.description':
+ 'Ustaw domyślne wartości dla całej instancji. Użytkownicy, którzy nie zmienili ustawienia, zobaczą te wartości. Ich własne zmiany zawsze mają pierwszeństwo.',
+ 'admin.defaultSettings.saved': 'Domyślne zapisane',
+ 'admin.defaultSettings.reset': 'Przywróć wbudowaną wartość domyślną',
+ 'admin.defaultSettings.resetToBuiltIn': 'przywróć',
+ 'admin.tabs.templates': 'Szablony pakowania',
+ 'admin.packingTemplates.title': 'Szablony pakowania',
+ 'admin.packingTemplates.subtitle':
+ 'Twórz szablony list pakowania do wielokrotnego użycia dla swoich podróży',
+ 'admin.packingTemplates.create': 'Nowy szablon',
+ 'admin.packingTemplates.namePlaceholder':
+ 'Nazwa szablonu (np. Wakacje na plaży)',
+ 'admin.packingTemplates.empty': 'Nie utworzono jeszcze żadnych szablonów',
+ 'admin.packingTemplates.items': 'przedmiotów',
+ 'admin.packingTemplates.categories': 'kategorie',
+ 'admin.packingTemplates.itemName': 'Nazwa przedmiotu',
+ 'admin.packingTemplates.itemCategory': 'Kategoria',
+ 'admin.packingTemplates.categoryName': 'Nazwa kategorii (np. Ubrania)',
+ 'admin.packingTemplates.addCategory': 'Dodaj kategorię',
+ 'admin.packingTemplates.created': 'Szablon został utworzony',
+ 'admin.packingTemplates.deleted': 'Szablon został usunięty',
+ 'admin.packingTemplates.loadError': 'Nie udało się załadować szablonów',
+ 'admin.packingTemplates.createError': 'Nie udało się utworzyć szablonu',
+ 'admin.packingTemplates.deleteError': 'Nie udało się usunąć szablonu',
+ 'admin.packingTemplates.saveError': 'Nie udało się zapisać szablonu',
+ 'admin.tabs.addons': 'Dodatki',
+ 'admin.addons.title': 'Dodatki',
+ 'admin.addons.subtitle':
+ 'Włączaj lub wyłączaj funkcje, aby dostosować swoje doświadczenie w TREK.',
+ 'admin.addons.catalog.packing.name': 'Listy',
+ 'admin.addons.catalog.packing.description':
+ 'Listy pakowania i zadania do wykonania dla Twoich podróży',
+ 'admin.addons.catalog.budget.name': 'Budżet',
+ 'admin.addons.catalog.budget.description':
+ 'Śledź wydatki i planuj budżet podróży',
+ 'admin.addons.catalog.documents.name': 'Dokumenty',
+ 'admin.addons.catalog.documents.description':
+ 'Przechowuj i zarządzaj dokumentami podróżnymi',
+ 'admin.addons.catalog.vacay.name': 'Urlopy',
+ 'admin.addons.catalog.vacay.description':
+ 'Osobisty planer urlopu z widokiem kalendarza',
+ 'admin.addons.catalog.atlas.name': 'Atlas',
+ 'admin.addons.catalog.atlas.description':
+ 'Mapa świata z odwiedzonymi krajami i statystykami podróży',
+ 'admin.addons.catalog.collab.name': 'Współpraca',
+ 'admin.addons.catalog.collab.description':
+ 'Notatki w czasie rzeczywistym, ankiety i czat do planowania podróży',
+ 'admin.addons.catalog.memories.name': 'Zdjęcia (Immich)',
+ 'admin.addons.catalog.memories.description':
+ 'Udostępniaj zdjęcia z podróży za pośrednictwem swojej instancji Immich',
+ 'admin.addons.catalog.mcp.name': 'MCP',
+ 'admin.addons.catalog.mcp.description':
+ 'Model Context Protocol dla integracji asystenta AI',
+ 'admin.addons.subtitleBefore':
+ 'Włączaj lub wyłączaj funkcje, aby dostosować swoje doświadczenie w ',
+ 'admin.addons.subtitleAfter': '.',
+ 'admin.addons.enabled': 'Włączone',
+ 'admin.addons.disabled': 'Wyłączone',
+ 'admin.addons.type.trip': 'Podróż',
+ 'admin.addons.type.global': 'Globalne',
+ 'admin.addons.type.integration': 'Integracja',
+ 'admin.addons.tripHint': 'Dostępne jako zakładka w każdej podróży',
+ 'admin.addons.globalHint': 'Dostępne jako osobna sekcja w menu głównym',
+ 'admin.addons.integrationHint':
+ 'Usługi backendowe i integracje API bez dedykowanej strony',
+ 'admin.addons.toast.updated': 'Dodatek został zaktualizowany',
+ 'admin.addons.toast.error': 'Nie udało się zaktualizować dodatku',
+ 'admin.addons.noAddons': 'Brak dostępnych dodatków',
+ 'admin.weather.title': 'Dane pogodowe',
+ 'admin.weather.badge': 'Od 24 marca 2026',
+ 'admin.weather.description':
+ 'TREK korzysta z Open-Meteo jako źródła danych pogodowych. Open-Meteo to darmowy, otwartoźródłowy serwis pogodowy — klucz API nie jest wymagany.',
+ 'admin.weather.forecast': '16-dniowa prognoza',
+ 'admin.weather.forecastDesc': 'Wcześniej 5 dni (OpenWeatherMap)',
+ 'admin.weather.climate': 'Historyczne dane klimatyczne',
+ 'admin.weather.climateDesc':
+ 'Średnie z ostatnich 85 lat dla dni poza 16-dniową prognozą',
+ 'admin.weather.requests': '10,000 zapytań / dzień',
+ 'admin.weather.requestsDesc': 'Bezpłatnie, bez klucza API',
+ 'admin.weather.locationHint':
+ 'Pogoda jest określana na podstawie pierwszego miejsca z przypisanymi współrzędnymi w danym dniu. Jeśli do dnia nie przypisano żadnego miejsca, jako punkt odniesienia używane jest dowolne miejsce z listy.',
+ 'admin.tabs.mcpTokens': 'Dostęp MCP',
+ 'admin.mcpTokens.title': 'Dostęp MCP',
+ 'admin.mcpTokens.subtitle':
+ 'Zarządzaj sesjami OAuth i tokenami API dla wszystkich użytkowników',
+ 'admin.mcpTokens.sectionTitle': 'Tokeny API',
+ 'admin.mcpTokens.owner': 'Właściciel',
+ 'admin.mcpTokens.tokenName': 'Nazwa tokenu',
+ 'admin.mcpTokens.created': 'Utworzono',
+ 'admin.mcpTokens.lastUsed': 'Ostatnio użyto',
+ 'admin.mcpTokens.never': 'Nigdy',
+ 'admin.mcpTokens.empty': 'Nie utworzono jeszcze żadnych tokenów MCP',
+ 'admin.mcpTokens.deleteTitle': 'Usuń token',
+ 'admin.mcpTokens.deleteMessage':
+ 'Spowoduje to natychmiastowe unieważnienie tokenu. Użytkownik straci dostęp MCP przez ten token.',
+ 'admin.mcpTokens.deleteSuccess': 'Token został usunięty',
+ 'admin.mcpTokens.deleteError': 'Nie udało się usunąć tokenu',
+ 'admin.mcpTokens.loadError': 'Nie udało się załadować tokenów',
+ 'admin.oauthSessions.sectionTitle': 'Sesje OAuth',
+ 'admin.oauthSessions.clientName': 'Klient',
+ 'admin.oauthSessions.owner': 'Właściciel',
+ 'admin.oauthSessions.scopes': 'Uprawnienia',
+ 'admin.oauthSessions.created': 'Utworzono',
+ 'admin.oauthSessions.empty': 'Brak aktywnych sesji OAuth',
+ 'admin.oauthSessions.revokeTitle': 'Unieważnij sesję',
+ 'admin.oauthSessions.revokeMessage':
+ 'Ta sesja OAuth zostanie natychmiast unieważniona. Klient straci dostęp do MCP.',
+ 'admin.oauthSessions.revokeSuccess': 'Sesja unieważniona',
+ 'admin.oauthSessions.revokeError': 'Nie udało się unieważnić sesji',
+ 'admin.oauthSessions.loadError': 'Nie udało się załadować sesji OAuth',
+ 'admin.tabs.github': 'GitHub',
+ 'admin.audit.subtitle':
+ 'Zdarzenia związane z bezpieczeństwem i administracją (kopie zapasowe, użytkownicy, MFA, ustawienia).',
+ 'admin.audit.empty': 'Brak zapisów w historii aktywności.',
+ 'admin.audit.refresh': 'Odśwież',
+ 'admin.audit.loadMore': 'Załaduj więcej',
+ 'admin.audit.showing': '{count} załadowanych · {total} łącznie',
+ 'admin.audit.col.time': 'Czas',
+ 'admin.audit.col.user': 'Użytkownik',
+ 'admin.audit.col.action': 'Akcja',
+ 'admin.audit.col.resource': 'Zasób',
+ 'admin.audit.col.ip': 'IP',
+ 'admin.audit.col.details': 'Szczegóły',
+ 'admin.github.title': 'Historia wydań',
+ 'admin.github.subtitle': 'Najnowsze aktualizacje z {repo}',
+ 'admin.github.latest': 'Najnowsze',
+ 'admin.github.prerelease': 'Wersja testowa',
+ 'admin.github.showDetails': 'Pokaż szczegóły',
+ 'admin.github.hideDetails': 'Ukryj szczegóły',
+ 'admin.github.loadMore': 'Załaduj więcej',
+ 'admin.github.loading': 'Ładowanie...',
+ 'admin.github.error': 'Nie udało się załadować wydań',
+ 'admin.github.by': 'przez',
+ 'admin.github.support': 'Pomóż mi rozwijać TREK',
+ 'admin.update.available': 'Dostępna aktualizacja',
+ 'admin.update.text':
+ 'Dostępna jest wersja TREK {version}. Używasz {current}.',
+ 'admin.update.button': 'Zobacz na GitHubie',
+ 'admin.update.install': 'Zainstaluj aktualizację',
+ 'admin.update.confirmTitle': 'Zainstalować aktualizację?',
+ 'admin.update.confirmText':
+ 'TREK zostanie zaktualizowany z {current} do {version}. Serwer zostanie automatycznie zrestartowany po zakończeniu.',
+ 'admin.update.dataInfo':
+ 'Wszystkie twoje dane (podróże, użytkownicy, klucze API, przesłane pliki, urlopy, Atlas, budżety) zostaną zachowane.',
+ 'admin.update.warning':
+ 'Aplikacja będzie niedostępna przez krótki czas podczas restartu.',
+ 'admin.update.confirm': 'Zaktualizuj',
+ 'admin.update.installing': 'Aktualizowanie...',
+ 'admin.update.success': 'Aktualizacja zakończona! Serwer restartuje się...',
+ 'admin.update.failed': 'Aktualizacja nie powiodła się',
+ 'admin.update.backupHint':
+ 'Zalecamy utworzenie kopii zapasowej przed aktualizacją.',
+ 'admin.update.backupLink': 'Zrób kopię zapasową',
+ 'admin.update.howTo': 'Jak zaktualizować',
+ 'admin.update.dockerText':
+ 'Twoja instancja TREK działa w Dockerze. Aby zaktualizować do {version}, uruchom następujące polecenia na swoim serwerze:',
+ 'admin.update.reloadHint': 'Proszę odświeżyć stronę za kilka sekund.',
+ 'admin.notifications.title': 'Powiadomienia',
+ 'admin.notifications.hint': 'Wybierz jeden kanał powiadomień.',
+ 'admin.notifications.none': 'Wyłączone',
+ 'admin.notifications.email': 'E-mail (SMTP)',
+ 'admin.notifications.webhook': '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.notifications.emailPanel.title': 'Email (SMTP)',
+ 'admin.notifications.webhookPanel.title': 'Webhook',
+ 'admin.notifications.inappPanel.title': 'In-App',
+ 'admin.notifications.inappPanel.hint':
+ 'Powiadomienia w aplikacji są zawsze aktywne i nie można ich globalnie wyłączyć.',
+ 'admin.notifications.adminWebhookPanel.title': 'Webhook admina',
+ 'admin.notifications.adminWebhookPanel.hint':
+ 'Ten webhook służy wyłącznie do powiadomień admina (np. alertów o nowych wersjach). Jest niezależny od webhooków użytkowników i wysyła automatycznie, gdy URL jest skonfigurowany.',
+ 'admin.notifications.adminWebhookPanel.saved': 'URL webhooka admina zapisany',
+ 'admin.notifications.adminWebhookPanel.testSuccess':
+ 'Testowy webhook wysłany pomyślnie',
+ 'admin.notifications.adminWebhookPanel.testFailed':
+ 'Wysyłanie testowego webhooka nie powiodło się',
+ 'admin.notifications.adminWebhookPanel.alwaysOnHint':
+ 'Webhook admina wysyła automatycznie, gdy URL jest skonfigurowany',
+ 'admin.notifications.ntfy': 'Ntfy',
+ 'admin.ntfy.hint':
+ 'Pozwala użytkownikom skonfigurować własne tematy ntfy dla powiadomień push. Ustaw domyślny serwer poniżej, aby wstępnie wypełnić ustawienia użytkownika.',
+ 'admin.notifications.testNtfy': 'Wyślij testowe Ntfy',
+ 'admin.notifications.testNtfySuccess': 'Testowe Ntfy wysłane pomyślnie',
+ 'admin.notifications.testNtfyFailed':
+ 'Wysyłanie testowego Ntfy nie powiodło się',
+ 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy',
+ 'admin.notifications.adminNtfyPanel.hint':
+ 'Ten temat Ntfy jest używany wyłącznie do powiadomień admina (np. alertów o wersjach). Jest niezależny od tematów użytkowników i zawsze wysyła po skonfigurowaniu.',
+ 'admin.notifications.adminNtfyPanel.serverLabel': 'URL serwera Ntfy',
+ 'admin.notifications.adminNtfyPanel.serverHint':
+ 'Używany również jako domyślny serwer dla powiadomień ntfy użytkowników. Pozostaw puste, aby użyć ntfy.sh. Użytkownicy mogą to nadpisać w swoich ustawieniach.',
+ 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh',
+ 'admin.notifications.adminNtfyPanel.topicLabel': 'Temat admina',
+ 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts',
+ 'admin.notifications.adminNtfyPanel.tokenLabel': 'Token dostępu (opcjonalne)',
+ 'admin.notifications.adminNtfyPanel.tokenCleared':
+ 'Token dostępu admina wyczyszczony',
+ 'admin.notifications.adminNtfyPanel.saved': 'Ustawienia admin Ntfy zapisane',
+ 'admin.notifications.adminNtfyPanel.test': 'Wyślij testowe Ntfy',
+ 'admin.notifications.adminNtfyPanel.testSuccess':
+ 'Testowe Ntfy wysłane pomyślnie',
+ 'admin.notifications.adminNtfyPanel.testFailed':
+ 'Wysyłanie testowego Ntfy nie powiodło się',
+ 'admin.notifications.adminNtfyPanel.alwaysOnHint':
+ 'Admin Ntfy zawsze wysyła po skonfigurowaniu tematu',
+ 'admin.notifications.adminNotificationsHint':
+ 'Skonfiguruj, które kanały dostarczają powiadomienia admina (np. alerty o wersjach). Webhook wysyła automatycznie, gdy ustawiony jest URL webhooka admina.',
+ 'admin.notifications.tripReminders.title': 'Przypomnienia o podróżach',
+ 'admin.notifications.tripReminders.hint':
+ 'Wysyła powiadomienie z przypomnieniem przed rozpoczęciem podróży (wymaga ustawienia dni przypomnienia dla podróży).',
+ 'admin.notifications.tripReminders.enabled':
+ 'Przypomnienia o podróżach włączone',
+ 'admin.notifications.tripReminders.disabled':
+ 'Przypomnienia o podróżach wyłączone',
+ 'admin.webhook.hint':
+ 'Pozwól użytkownikom konfigurować własne adresy URL webhooka dla powiadomień (Discord, Slack itp.).',
+ 'admin.tabs.permissions': 'Uprawnienia',
+ 'admin.addons.catalog.journey.name': 'Dziennik podróży',
+ 'admin.addons.catalog.journey.description':
+ 'Śledzenie podróży i dziennik z zameldowaniami, zdjęciami i codziennymi historiami',
+ 'admin.passkey.title': 'Logowanie kluczem dostępu',
+ 'admin.passkey.cardHint':
+ 'Pozwól użytkownikom logować się kluczami dostępu (WebAuthn). Domyślnie wyłączone.',
+ 'admin.passkey.login': 'Włącz logowanie kluczem dostępu',
+ 'admin.passkey.loginHint':
+ 'Pokaż opcję „Zaloguj się kluczem dostępu” i pozwól użytkownikom rejestrować klucze dostępu w swoich ustawieniach.',
+ 'admin.passkey.notConfigured':
+ 'Dla tego wdrożenia nie ustalono jeszcze żadnej domeny WebAuthn. Ustaw APP_URL lub Relying Party ID poniżej — do tego czasu klucze dostępu pozostaną ukryte.',
+ 'admin.passkey.rpId': 'Relying Party ID (domena)',
+ 'admin.passkey.rpIdHint':
+ 'Sama domena, do której przypisane są klucze dostępu, np. trek.example.org. Pozostaw puste, aby wyprowadzić ją z APP_URL. Późniejsza zmiana unieważnia istniejące klucze dostępu.',
+ 'admin.passkey.origins': 'Dozwolone origins',
+ 'admin.passkey.originsHint':
+ 'Pełne origins oddzielone przecinkami, np. https://trek.example.org. Pozostaw puste, aby użyć APP_URL.',
+ 'admin.passkey.reset': 'Zresetuj klucze dostępu',
+ 'admin.passkey.resetHint':
+ 'Usuń wszystkie klucze dostępu tego użytkownika (np. po utracie urządzenia). Nadal będzie mógł logować się hasłem.',
+ 'admin.passkey.resetConfirm': 'Usunąć wszystkie klucze dostępu dla {name}?',
+ 'admin.passkey.resetDone': 'Usunięto {count} kluczy dostępu',
+ 'admin.defaultSettings.mapProvider': 'Silnik map',
+ 'admin.defaultSettings.mapProviderHint': 'Domyślna mapa dla wszystkich na tej instancji. Każdy użytkownik może ją zmienić we własnych ustawieniach.',
+ 'admin.defaultSettings.providerLeaflet': 'Standardowa (bezpłatna)',
+ 'admin.defaultSettings.providerMapbox': 'Mapbox (3D)',
+ 'admin.defaultSettings.mapboxToken': 'Współdzielony token Mapbox',
+ 'admin.defaultSettings.mapboxTokenHint': 'Używany dla każdego użytkownika, który nie wprowadził własnego tokena — dzięki temu cała instancja korzysta z Mapbox bez udostępniania klucza każdemu z osobna. Przechowywany w postaci zaszyfrowanej.',
+ 'admin.defaultSettings.mapboxStyle': 'Styl mapy',
+ 'admin.defaultSettings.mapboxStylePlaceholder': 'Wybierz styl…',
+ 'admin.defaultSettings.mapbox3d': 'Budynki i teren 3D',
+ 'admin.defaultSettings.mapboxQuality': 'Tryb wysokiej jakości',
+};
+export default admin;
diff --git a/shared/src/i18n/pl/airport.ts b/shared/src/i18n/pl/airport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eb87e9bdcb4f69110551c87e988f0e5c6bd3c5c6
--- /dev/null
+++ b/shared/src/i18n/pl/airport.ts
@@ -0,0 +1,6 @@
+import type { TranslationStrings } from '../types';
+
+const airport: TranslationStrings = {
+ 'airport.searchPlaceholder': 'Kod lotniska lub miasto (np. FRA)',
+};
+export default airport;
diff --git a/shared/src/i18n/pl/atlas.ts b/shared/src/i18n/pl/atlas.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5fb652c73bb29bc3eb0a8461a21962df2188e967
--- /dev/null
+++ b/shared/src/i18n/pl/atlas.ts
@@ -0,0 +1,59 @@
+import type { TranslationStrings } from '../types';
+
+const atlas: TranslationStrings = {
+ 'atlas.subtitle': 'Twój ślad podróżniczy po świecie',
+ 'atlas.countries': 'Kraje',
+ 'atlas.trips': 'Podróże',
+ 'atlas.places': 'Miejsca',
+ 'atlas.unmark': 'Usuń',
+ 'atlas.confirmMark': 'Oznaczyć ten kraj jako odwiedzony?',
+ 'atlas.confirmUnmark': 'Usunąć ten kraj z listy odwiedzonych?',
+ 'atlas.confirmUnmarkRegion': 'Usunąć ten region z listy odwiedzonych?',
+ 'atlas.markVisited': 'Oznacz jako odwiedzony',
+ 'atlas.markVisitedHint': 'Dodaj ten kraj do listy odwiedzonych',
+ 'atlas.markRegionVisitedHint': 'Dodaj ten region do listy odwiedzonych',
+ 'atlas.addToBucket': 'Dodaj do listy marzeń',
+ 'atlas.addPoi': 'Dodaj miejsce',
+ 'atlas.bucketNamePlaceholder': 'Nazwa (kraj, miasto, miejsce...)',
+ 'atlas.month': 'Miesiąc',
+ 'atlas.year': 'Rok',
+ 'atlas.addToBucketHint': 'Zapisz jako miejsce, które chcesz odwiedzić',
+ 'atlas.bucketWhen': 'Kiedy planujesz je odwiedzić?',
+ 'atlas.statsTab': 'Statystyki',
+ 'atlas.bucketTab': 'Lista marzeń',
+ 'atlas.addBucket': 'Dodaj do listy marzeń',
+ 'atlas.bucketNotesPlaceholder': 'Notatki (opcjonalnie)',
+ 'atlas.bucketEmpty': 'Twoja lista marzeń jest pusta',
+ 'atlas.bucketEmptyHint': 'Dodaj miejsca, które chcesz odwiedzić',
+ 'atlas.days': 'Dni',
+ 'atlas.visitedCountries': 'Odwiedzone kraje',
+ 'atlas.cities': 'Miasta',
+ 'atlas.noData': 'Brak danych o podróżach',
+ 'atlas.noDataHint':
+ 'Utwórz podróż i dodaj miejsca, aby zobaczyć swoją mapę świata',
+ 'atlas.lastTrip': 'Ostatnia podróż',
+ 'atlas.nextTrip': 'Następna podróż',
+ 'atlas.daysLeft': 'dni do wyjazdu',
+ 'atlas.streak': 'Seria',
+ 'atlas.years': 'lata',
+ 'atlas.yearInRow': 'rok z rzędu',
+ 'atlas.yearsInRow': 'lat z rzędu',
+ 'atlas.tripIn': 'podróż w',
+ 'atlas.tripsIn': 'podróży w',
+ 'atlas.since': 'od',
+ 'atlas.europe': 'Europa',
+ 'atlas.asia': 'Azja',
+ 'atlas.northAmerica': 'Ameryka Pn.',
+ 'atlas.southAmerica': 'Ameryka Pd.',
+ 'atlas.africa': 'Afryka',
+ 'atlas.oceania': 'Oceania',
+ 'atlas.other': 'Inne',
+ 'atlas.firstVisit': 'Pierwsza podróż',
+ 'atlas.lastVisitLabel': 'Ostatnia podróż',
+ 'atlas.tripSingular': 'Podróż',
+ 'atlas.tripPlural': 'Podróże',
+ 'atlas.placeVisited': 'Odwiedzone miejsce',
+ 'atlas.placesVisited': 'Odwiedzone miejsca',
+ 'atlas.searchCountry': 'Szukaj kraju...',
+};
+export default atlas;
diff --git a/shared/src/i18n/pl/backup.ts b/shared/src/i18n/pl/backup.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a440d721f11f4b5f50a393f6c13a4c42bb70b9a6
--- /dev/null
+++ b/shared/src/i18n/pl/backup.ts
@@ -0,0 +1,81 @@
+import type { TranslationStrings } from '../types';
+
+const backup: TranslationStrings = {
+ 'backup.title': 'Kopia zapasowa danych',
+ 'backup.subtitle': 'Baza danych i wszystkie przesłane pliki',
+ 'backup.refresh': 'Odśwież',
+ 'backup.upload': 'Prześlij kopię zapasową',
+ 'backup.uploading': 'Przesyłanie...',
+ 'backup.create': 'Utwórz kopię zapasową',
+ 'backup.creating': 'Tworzenie...',
+ 'backup.empty': 'Brak kopii zapasowych',
+ 'backup.createFirst': 'Utwórz pierwszą kopię zapasową',
+ 'backup.download': 'Pobierz',
+ 'backup.restore': 'Przywróć',
+ 'backup.confirm.restore':
+ 'Przywrócić kopię zapasową "{name}"?\n\nWszystkie obecne dane zostaną zastąpione danymi z kopii zapasowej.',
+ 'backup.confirm.uploadRestore':
+ 'Przesłać i przywrócić plik kopii zapasowej "{name}"?\n\nWszystkie obecne dane zostaną nadpisane.',
+ 'backup.confirm.delete': 'Usunąć kopię zapasową "{name}"?',
+ 'backup.toast.loadError': 'Nie udało się załadować kopii zapasowych',
+ 'backup.toast.created': 'Kopia zapasowa została utworzona pomyślnie',
+ 'backup.toast.createError': 'Nie udało się utworzyć kopii zapasowej',
+ 'backup.toast.restored':
+ 'Kopia zapasowa została przywrócona. Strona zostanie przeładowana...',
+ 'backup.toast.restoreError': 'Nie udało się przywrócić kopii zapasowej',
+ 'backup.toast.uploadError': 'Nie udało się przesłać kopii zapasowej',
+ 'backup.toast.deleted': 'Kopia zapasowa została usunięta',
+ 'backup.toast.deleteError': 'Nie udało się usunąć kopii zapasowej',
+ 'backup.toast.downloadError': 'Nie udało się pobrać kopii zapasowej',
+ 'backup.toast.settingsSaved':
+ 'Ustawienia automatycznej kopii zapasowej zostały zapisane',
+ 'backup.toast.settingsError': 'Nie udało się zapisać ustawień',
+ 'backup.auto.title': 'Automatyczna kopia zapasowa',
+ 'backup.auto.subtitle':
+ 'Tworzenie automatycznej kopii zapasowej według harmonogramu',
+ 'backup.auto.enable': 'Włącz automatyczną kopię zapasową',
+ 'backup.auto.enableHint':
+ 'Kopie zapasowe będą tworzone automatycznie zgodnie z wybranym harmonogramem',
+ 'backup.auto.interval': 'Częstotliwość',
+ 'backup.auto.hour': 'Uruchom o godzinie',
+ 'backup.auto.hourHint': 'Czas lokalny serwera ({format} format)',
+ 'backup.auto.dayOfWeek': 'Dzień tygodnia',
+ 'backup.auto.dayOfMonth': 'Dzień miesiąca',
+ 'backup.auto.dayOfMonthHint':
+ 'Ograniczone do 1–28 dla kompatybilności ze wszystkimi miesiącami',
+ 'backup.auto.scheduleSummary': 'Harmonogram',
+ 'backup.auto.summaryDaily': 'Każdego dnia o {hour}:00',
+ 'backup.auto.summaryWeekly': 'Co {day} o {hour}:00',
+ 'backup.auto.summaryMonthly': '{day}. dnia każdego miesiąca o {hour}:00',
+ 'backup.auto.envLocked': 'Docker',
+ 'backup.auto.envLockedHint':
+ 'Automatyczne kopie zapasowe są konfigurowane za pomocą zmiennych środowiskowych Dockera. Aby zmienić te ustawienia, zaktualizuj plik docker-compose.yml i uruchom ponownie kontener.',
+ 'backup.auto.copyEnv': 'Kopiuj zmienne środowiskowe Dockera',
+ 'backup.auto.envCopied':
+ 'Zmienne środowiskowe Dockera zostały skopiowane do schowka',
+ 'backup.auto.keepLabel': 'Usuń stare kopie zapasowe po',
+ 'backup.dow.sunday': 'Nd',
+ 'backup.dow.monday': 'Pon',
+ 'backup.dow.tuesday': 'Wt',
+ 'backup.dow.wednesday': 'Śr',
+ 'backup.dow.thursday': 'Czw',
+ 'backup.dow.friday': 'Pt',
+ 'backup.dow.saturday': 'Sob',
+ 'backup.interval.hourly': 'Co godzinę',
+ 'backup.interval.daily': 'Co dzień',
+ 'backup.interval.weekly': 'Co tydzień',
+ 'backup.interval.monthly': 'Co miesiąc',
+ 'backup.keep.1day': '1 dzień',
+ 'backup.keep.3days': '3 dni',
+ 'backup.keep.7days': '7 dni',
+ 'backup.keep.14days': '14 dni',
+ 'backup.keep.30days': '30 dni',
+ 'backup.keep.forever': 'Przechowuj na zawsze',
+ 'backup.restoreConfirmTitle': 'Przywrócić kopię zapasową?',
+ 'backup.restoreWarning':
+ 'Wszystkie obecne dane (podróże, miejsca, użytkownicy, przesłane pliki) zostaną trwale zastąpione danymi z kopii zapasowej. Tej operacji nie można cofnąć.',
+ 'backup.restoreTip':
+ 'Wskazówka: Przed przywróceniem utwórz kopię zapasową bieżącej instancji.',
+ 'backup.restoreConfirm': 'Tak, przywróć',
+};
+export default backup;
diff --git a/shared/src/i18n/pl/budget.ts b/shared/src/i18n/pl/budget.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8f39ea49c9d24c33007288475f4754f3065e965f
--- /dev/null
+++ b/shared/src/i18n/pl/budget.ts
@@ -0,0 +1,115 @@
+import type { TranslationStrings } from '../types';
+
+const budget: TranslationStrings = {
+ 'budget.title': 'Budżet',
+ 'budget.emptyTitle': 'Nie utworzono jeszcze budżetu',
+ 'budget.emptyText': 'Utwórz kategorie i wpisy, aby zaplanować budżet podróży',
+ 'budget.emptyPlaceholder': 'Podaj nazwę kategorii...',
+ 'budget.createCategory': 'Utwórz kategorię',
+ 'budget.category': 'Kategoria',
+ 'budget.categoryName': 'Nazwa kategorii',
+ 'budget.table.name': 'Nazwa',
+ 'budget.table.total': 'Łącznie',
+ 'budget.table.persons': 'Osoby',
+ 'budget.table.days': 'Dni',
+ 'budget.table.perPerson': 'Za osobę',
+ 'budget.table.perDay': 'Za dzień',
+ 'budget.table.perPersonDay': 'Za osobę/dzień',
+ 'budget.table.note': 'Notatka',
+ 'budget.newEntry': 'Nowy wpis',
+ 'budget.defaultEntry': 'Nowy wpis',
+ 'budget.defaultCategory': 'Nowa kategoria',
+ 'budget.total': 'Łącznie',
+ 'budget.totalBudget': 'Całkowity budżet',
+ 'budget.byCategory': 'Według kategorii',
+ 'budget.editTooltip': 'Kliknij, aby edytować',
+ 'budget.linkedToReservation': 'Powiązano z rezerwacją — edytuj nazwę tam',
+ 'budget.confirm.deleteCategory':
+ 'Czy na pewno chcesz usunąć kategorię "{name}" z {count} wpisami?',
+ 'budget.deleteCategory': 'Usuń kategorię',
+ 'budget.perPerson': 'Za osobę',
+ 'budget.paid': 'Zapłacone',
+ 'budget.open': 'Otwarte',
+ 'budget.noMembers': 'Brak przypisanych członków',
+ 'budget.settlement': 'Rozliczenie',
+ 'budget.settlementInfo':
+ 'Kliknij avatar członka przy pozycji w budżecie, aby oznaczyć go na zielono — oznacza to, że zapłacił. Rozliczenie pokaże, kto komu i ile jest winien.',
+ 'budget.netBalances': 'Bilans',
+ 'budget.exportCsv': 'Eksportuj CSV',
+ 'budget.table.date': 'Data',
+ 'budget.categoriesLabel': 'kategorie',
+ "costs.you": "Ty",
+ "costs.youShort": "T",
+ "costs.youLower": "ty",
+ "costs.youOwe": "Jesteś winien",
+ "costs.youOweSub": "Powinieneś zapłacić innym",
+ "costs.youreOwed": "Należy ci się",
+ "costs.youreOwedSub": "Inni powinni zapłacić tobie",
+ "costs.totalSpend": "Łączne wydatki na podróż",
+ "costs.totalSpendSub": "Wszystkich podróżnych",
+ "costs.to": "Do",
+ "costs.from": "Od",
+ "costs.allSettled": "Wszystko rozliczone",
+ "costs.nothingOwed": "Nikt nie jest ci nic winien",
+ "costs.yourShare": "Twój udział",
+ "costs.youPaid": "Zapłaciłeś",
+ "costs.expenses": "Wydatki",
+ "costs.entries": "Wpisów: {count}",
+ "costs.searchPlaceholder": "Szukaj wydatków…",
+ "costs.filter.all": "Wszystkie",
+ "costs.filter.mine": "Opłacone przeze mnie",
+ "costs.filter.owed": "Należy mi się",
+ "costs.addExpense": "Dodaj wydatek",
+ "costs.editExpense": "Edytuj wydatek",
+ "costs.noMatch": "Brak wydatków pasujących do wyszukiwania.",
+ "costs.emptyText": "Brak wydatków. Dodaj pierwszy.",
+ "costs.spent": "wydano {amount}",
+ "costs.noDate": "Brak daty",
+ "costs.noOnePaid": "Nikt jeszcze nie zapłacił",
+ "costs.youLent": "pożyczyłeś {amount}",
+ "costs.youBorrowed": "pożyczyłeś od innych {amount}",
+ "costs.settleUp": "Rozlicz",
+ "costs.history": "Historia",
+ "costs.everyoneSquare": "Wszyscy rozliczeni",
+ "costs.nothingOutstanding": "Brak nierozliczonych płatności.",
+ "costs.pay": "zapłać",
+ "costs.pays": "płaci",
+ "costs.settle": "Rozlicz",
+ "costs.balances": "Salda",
+ "costs.byCategory": "Według kategorii",
+ "costs.noCategories": "Brak wydatków.",
+ "costs.settleHistory": "Historia rozliczeń",
+ "costs.noSettlements": "Brak rozliczonych płatności.",
+ "costs.paymentsSettled": "Rozliczonych płatności: {count}",
+ "costs.paid": "zapłacono",
+ "costs.undo": "Cofnij",
+ "costs.whatFor": "Na co to było?",
+ "costs.namePlaceholder": "np. kolacja, pamiątki, paliwo…",
+ "costs.totalAmount": "Łączna kwota",
+ "costs.currency": "Waluta",
+ "costs.day": "Dzień",
+ "costs.rateLabel": "1 {from} w {to}",
+ "costs.category": "Kategoria",
+ "costs.whoPaid": "Kto zapłacił?",
+ "costs.splitBetween": "Podziel równo między",
+ "costs.pickSomeone": "Wybierz co najmniej jedną osobę do podziału.",
+ "costs.splitSummary": "Podział na {count} · {amount} na osobę",
+ "costs.cat.accommodation": "Nocleg",
+ "costs.cat.food": "Jedzenie i napoje",
+ "costs.cat.groceries": "Zakupy spożywcze",
+ "costs.cat.transport": "Transport",
+ "costs.cat.flights": "Loty",
+ "costs.cat.activities": "Atrakcje",
+ "costs.cat.sightseeing": "Zwiedzanie",
+ "costs.cat.shopping": "Zakupy",
+ "costs.cat.fees": "Opłaty i bilety",
+ "costs.cat.health": "Zdrowie",
+ "costs.cat.tips": "Napiwki",
+ "costs.cat.other": "Inne",
+ "costs.daysCount": "Dni: {count}",
+ "costs.travelers": "Podróżnych: {count}",
+ "costs.liveRate": "kurs na żywo",
+ "costs.settleAll": "Rozlicz wszystko",
+};
+
+export default budget;
diff --git a/shared/src/i18n/pl/categories.ts b/shared/src/i18n/pl/categories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9e247acbffb9e196a80aa31e58835a13499bfaa8
--- /dev/null
+++ b/shared/src/i18n/pl/categories.ts
@@ -0,0 +1,26 @@
+import type { TranslationStrings } from '../types';
+
+const categories: TranslationStrings = {
+ 'categories.title': 'Kategorie',
+ 'categories.subtitle': 'Zarządzaj kategoriami miejsc',
+ 'categories.new': 'Nowa kategoria',
+ 'categories.empty': 'Brak kategorii',
+ 'categories.namePlaceholder': 'Nazwa kategorii',
+ 'categories.icon': 'Ikona',
+ 'categories.color': 'Kolor',
+ 'categories.customColor': 'Wybierz własny kolor',
+ 'categories.preview': 'Podgląd',
+ 'categories.defaultName': 'Kategoria',
+ 'categories.update': 'Aktualizuj',
+ 'categories.create': 'Utwórz',
+ 'categories.confirm.delete':
+ 'Usunąć kategorię? Miejsca w tej kategorii nie zostaną usunięte.',
+ 'categories.toast.loadError': 'Nie udało się załadować kategorii',
+ 'categories.toast.nameRequired': 'Proszę podać nazwę',
+ 'categories.toast.updated': 'Kategoria została zaktualizowana',
+ 'categories.toast.created': 'Kategoria została utworzona',
+ 'categories.toast.saveError': 'Nie udało się zapisać kategorii',
+ 'categories.toast.deleted': 'Kategoria została usunięta',
+ 'categories.toast.deleteError': 'Nie udało się usunąć kategorii',
+};
+export default categories;
diff --git a/shared/src/i18n/pl/collab.ts b/shared/src/i18n/pl/collab.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2982b8cf24ee3e91cf285e2a7f679a4d92ff1c30
--- /dev/null
+++ b/shared/src/i18n/pl/collab.ts
@@ -0,0 +1,77 @@
+import type { TranslationStrings } from '../types';
+
+const collab: TranslationStrings = {
+ 'collab.tabs.chat': 'Czat',
+ 'collab.tabs.notes': 'Notatki',
+ 'collab.tabs.polls': 'Ankiety',
+ 'collab.whatsNext.title': 'Co dalej',
+ 'collab.whatsNext.today': 'Dzisiaj',
+ 'collab.whatsNext.tomorrow': 'Jutro',
+ 'collab.whatsNext.empty': 'Brak nadchodzących aktywności',
+ 'collab.whatsNext.until': 'do',
+ 'collab.whatsNext.emptyHint': 'Aktywności z godzinami pojawią się tutaj',
+ 'collab.chat.send': 'Wyślij',
+ 'collab.chat.placeholder': 'Napisz wiadomość...',
+ 'collab.chat.empty': 'Rozpocznij konwersację',
+ 'collab.chat.emptyHint':
+ 'Wiadomości są widoczne dla wszystkich uczestników podróży',
+ 'collab.chat.emptyDesc':
+ 'Dziel się pomysłami, planami i aktualizacjami z uczestnikami podróży',
+ 'collab.chat.today': 'Dzisiaj',
+ 'collab.chat.yesterday': 'Wczoraj',
+ 'collab.chat.deletedMessage': 'usunięto wiadomość',
+ 'collab.chat.loadMore': 'Załaduj starsze wiadomości',
+ 'collab.chat.justNow': 'teraz',
+ 'collab.chat.minutesAgo': '{n}m temu',
+ 'collab.chat.hoursAgo': '{n}h temu',
+ 'collab.notes.title': 'Notatki',
+ 'collab.notes.new': 'Nowa notatka',
+ 'collab.notes.empty': 'Brak notatek',
+ 'collab.notes.emptyHint': 'Zapisuj pomysły i plany',
+ 'collab.notes.all': 'Wszystkie',
+ 'collab.notes.titlePlaceholder': 'Tytuł notatki',
+ 'collab.notes.contentPlaceholder': 'Napisz coś...',
+ 'collab.notes.categoryPlaceholder': 'Kategoria',
+ 'collab.notes.newCategory': 'Nowa kategoria...',
+ 'collab.notes.category': 'Kategoria',
+ 'collab.notes.noCategory': 'Brak kategorii',
+ 'collab.notes.color': 'Kolor',
+ 'collab.notes.save': 'Zapisz',
+ 'collab.notes.cancel': 'Anuluj',
+ 'collab.notes.edit': 'Edytuj',
+ 'collab.notes.delete': 'Usuń',
+ 'collab.notes.confirmDeleteTitle': 'Usunąć notatkę?',
+ 'collab.notes.confirmDeleteBody': 'Ta notatka zostanie trwale usunięta.',
+ 'collab.notes.pin': 'Przypnij',
+ 'collab.notes.unpin': 'Odepnij',
+ 'collab.notes.daysAgo': '{n}d temu',
+ 'collab.notes.categorySettings': 'Zarządzaj kategoriami',
+ 'collab.notes.create': 'Utwórz',
+ 'collab.notes.website': 'Strona internetowa',
+ 'collab.notes.websitePlaceholder': 'https://...',
+ 'collab.notes.attachFiles': 'Załącz pliki',
+ 'collab.notes.noCategoriesYet': 'Brak kategorii',
+ 'collab.notes.emptyDesc': 'Utwórz notatkę, aby rozpocząć',
+ 'collab.polls.title': 'Ankiety',
+ 'collab.polls.new': 'Nowa ankieta',
+ 'collab.polls.empty': 'Brak ankiet',
+ 'collab.polls.emptyHint': 'Zapytaj grupę i głosujcie razem',
+ 'collab.polls.question': 'Pytanie',
+ 'collab.polls.questionPlaceholder': 'Co powinniśmy zrobić?',
+ 'collab.polls.addOption': '+ Dodaj opcję',
+ 'collab.polls.optionPlaceholder': 'Opcja {n}',
+ 'collab.polls.create': 'Utwórz ankietę',
+ 'collab.polls.close': 'Zamknij',
+ 'collab.polls.closed': 'Zamknięta',
+ 'collab.polls.votes': '{n} głosów',
+ 'collab.polls.vote': '{n} głos',
+ 'collab.polls.multipleChoice': 'Wielokrotny wybór',
+ 'collab.polls.multiChoice': 'Wielokrotny wybór',
+ 'collab.polls.deadline': 'Koniec',
+ 'collab.polls.option': 'Opcja',
+ 'collab.polls.options': 'Opcje',
+ 'collab.polls.delete': 'Usuń',
+ 'collab.polls.closedSection': 'Zamknięte',
+ 'collab.chat.reply': 'Odpowiedz',
+};
+export default collab;
diff --git a/shared/src/i18n/pl/common.ts b/shared/src/i18n/pl/common.ts
new file mode 100644
index 0000000000000000000000000000000000000000..349fa25b702d22730e62e5bbf47e29a5b219ea97
--- /dev/null
+++ b/shared/src/i18n/pl/common.ts
@@ -0,0 +1,54 @@
+import type { TranslationStrings } from '../types';
+
+const common: TranslationStrings = {
+ 'common.save': 'Zapisz',
+ 'common.showMore': 'Pokaż więcej',
+ 'common.showLess': 'Pokaż mniej',
+ 'common.cancel': 'Anuluj',
+ 'common.clear': 'Wyczyść',
+ 'common.delete': 'Usuń',
+ 'common.edit': 'Edytuj',
+ 'common.add': 'Dodaj',
+ 'common.loading': 'Ładowanie...',
+ 'common.error': 'Błąd',
+ 'common.unknownError': 'Nieznany błąd',
+ 'common.tooManyAttempts': 'Zbyt wiele prób. Spróbuj ponownie później.',
+ 'common.back': 'Wstecz',
+ 'common.all': 'Wszystko',
+ 'common.close': 'Zamknij',
+ 'common.open': 'Otwórz',
+ 'common.upload': 'Prześlij',
+ 'common.search': 'Szukaj',
+ 'common.confirm': 'Potwierdź',
+ 'common.ok': 'OK',
+ 'common.yes': 'Tak',
+ 'common.no': 'Nie',
+ 'common.or': 'lub',
+ 'common.none': 'Brak',
+ 'common.date': 'Data',
+ 'common.rename': 'Zmień nazwę',
+ 'common.discardChanges': 'Odrzuć zmiany',
+ 'common.discard': 'Odrzuć',
+ 'common.name': 'Nazwa',
+ 'common.email': 'E-mail',
+ 'common.password': 'Hasło',
+ 'common.saving': 'Zapisywanie...',
+ 'common.expand': 'Rozwiń',
+ 'common.collapse': 'Zwiń',
+ 'common.update': 'Aktualizuj',
+ 'common.change': 'Zmień',
+ 'common.uploading': 'Przesyłanie...',
+ 'common.backToPlanning': 'Powrót do planowania',
+ 'common.reset': 'Resetuj',
+ 'common.copy': 'Kopiuj',
+ 'common.copied': 'Skopiowano',
+ 'common.import': 'Importuj',
+ 'common.select': 'Wybierz',
+ 'common.selectAll': 'Zaznacz wszystko',
+ 'common.deselectAll': 'Odznacz wszystko',
+ 'common.saved': 'Zapisano',
+ 'common.justNow': 'przed chwilą',
+ 'common.hoursAgo': '{count} godz. temu',
+ 'common.daysAgo': '{count} dn. temu',
+};
+export default common;
diff --git a/shared/src/i18n/pl/dashboard.ts b/shared/src/i18n/pl/dashboard.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9aeab64711d468b260d41d76198d03deecb7326a
--- /dev/null
+++ b/shared/src/i18n/pl/dashboard.ts
@@ -0,0 +1,168 @@
+import type { TranslationStrings } from '../types';
+
+const dashboard: TranslationStrings = {
+ 'dashboard.title': 'Moje podróże',
+ 'dashboard.subtitle.loading': 'Ładowanie podróży...',
+ 'dashboard.subtitle.trips': '{count} podróży ({archived} zarchiwizowanych)',
+ 'dashboard.subtitle.empty': 'Rozpocznij swoją pierwszą podróż',
+ 'dashboard.subtitle.activeOne': '{count} aktywna podróż',
+ 'dashboard.subtitle.activeMany': '{count} aktywnych podróży',
+ 'dashboard.subtitle.archivedSuffix': ' · {count} zarchiwizowanych',
+ 'dashboard.newTrip': 'Nowa podróż',
+ 'dashboard.newTripSub': 'Zaplanuj nową podróż od zera',
+ 'dashboard.gridView': 'Widok siatki',
+ 'dashboard.listView': 'Widok listy',
+ 'dashboard.currency': 'Waluta',
+ 'dashboard.timezone': 'Strefy czasowe',
+ 'dashboard.localTime': 'Czas lokalny',
+ 'dashboard.timezoneCustomTitle': 'Własna strefa czasowa',
+ 'dashboard.timezoneCustomLabelPlaceholder': 'Nazwa (opcjonalnie)',
+ 'dashboard.timezoneCustomTzPlaceholder': 'np. Europe/Warsaw',
+ 'dashboard.timezoneCustomAdd': 'Dodaj',
+ 'dashboard.timezoneCustomErrorEmpty': 'Podaj identyfikator strefy czasowej',
+ 'dashboard.timezoneCustomErrorInvalid':
+ 'Nieprawidłowa strefa czasowa. Użyj formatu takiego jak Europe/Warsaw',
+ 'dashboard.timezoneCustomErrorDuplicate': 'Już dodana',
+ 'dashboard.emptyTitle': 'Brak podróży',
+ 'dashboard.emptyText': 'Utwórz swoją pierwszą podróż i zacznij planować!',
+ 'dashboard.emptyButton': 'Utwórz pierwszą podróż',
+ 'dashboard.nextTrip': 'Następna podróż',
+ 'dashboard.shared': 'Udostępniona',
+ 'dashboard.sharedBy': 'Udostępniona przez {name}',
+ 'dashboard.days': 'Dni',
+ 'dashboard.places': 'Miejsca',
+ 'dashboard.archive': 'Archiwizuj',
+ 'dashboard.restore': 'Przywróć',
+ 'dashboard.archived': 'Zarchiwizowana',
+ 'dashboard.status.ongoing': 'W trakcie',
+ 'dashboard.status.today': 'Dzisiaj',
+ 'dashboard.status.tomorrow': 'Jutro',
+ 'dashboard.status.past': 'Zakończona',
+ 'dashboard.status.daysLeft': '{count} dni do końca',
+ 'dashboard.toast.loadError': 'Nie udało się załadować podróży',
+ 'dashboard.toast.created': 'Podróż została utworzona pomyślnie!',
+ 'dashboard.toast.createError': 'Nie udało się utworzyć podróży',
+ 'dashboard.toast.updated': 'Podróż została zaktualizowana!',
+ 'dashboard.toast.updateError': 'Nie udało się zaktualizować podróży',
+ 'dashboard.toast.deleted': 'Podróż została usunięta',
+ 'dashboard.toast.deleteError': 'Nie udało się usunąć podróży',
+ 'dashboard.toast.archived': 'Podróż została zarchiwizowana',
+ 'dashboard.toast.archiveError': 'Nie udało się zarchiwizować podróży',
+ 'dashboard.toast.restored': 'Podróż została przywrócona',
+ 'dashboard.toast.restoreError': 'Nie udało się przywrócić podróży',
+ 'dashboard.confirm.delete':
+ 'Usunąć podróż "{title}"? Wszystkie miejsca i plany zostaną trwale usunięte.',
+ 'dashboard.editTrip': 'Edytuj podróż',
+ 'dashboard.createTrip': 'Utwórz nową podróż',
+ 'dashboard.tripTitle': 'Nazwa podróży',
+ 'dashboard.tripTitlePlaceholder': 'np. Lato w Japonii',
+ 'dashboard.tripDescription': 'Opis',
+ 'dashboard.tripDescriptionPlaceholder': 'Opisz swoją podróż',
+ 'dashboard.startDate': 'Data rozpoczęcia',
+ 'dashboard.endDate': 'Data zakończenia',
+ 'dashboard.dayCount': 'Liczba dni',
+ 'dashboard.dayCountHint':
+ 'Ile dni zaplanować, gdy nie ustawiono dat podróży.',
+ 'dashboard.noDateHint':
+ 'Nie ustawiono daty — zostanie utworzonych 7 domyślnych dni. Możesz to zmienić w dowolnym momencie.',
+ 'dashboard.coverImage': 'Okładka',
+ 'dashboard.addCoverImage': 'Dodaj okładkę (lub przeciągnij i upuść)',
+ 'dashboard.addMembers': 'Uczestnicy podróży',
+ 'dashboard.addMember': 'Dodaj uczestnika',
+ 'dashboard.coverSaved': 'Okładka została zapisana',
+ 'dashboard.coverUploadError': 'Nie udało się przesłać okładki',
+ 'dashboard.coverRemoveError': 'Nie udało się usunąć okładki',
+ 'dashboard.titleRequired': 'Nazwa podróży jest wymagana',
+ 'dashboard.endDateError': 'Data zakończenia musi być po dacie rozpoczęcia',
+ 'dashboard.members': 'Towarzysze',
+ 'dashboard.copyTrip': 'Kopiuj',
+ 'dashboard.copySuffix': 'kopia',
+ 'dashboard.toast.copied': 'Podróż skopiowana!',
+ 'dashboard.toast.copyError': 'Nie udało się skopiować podróży',
+ 'dashboard.greeting.morning': 'Dzień dobry,',
+ 'dashboard.greeting.afternoon': 'Dzień dobry,',
+ 'dashboard.greeting.evening': 'Dobry wieczór,',
+ 'dashboard.mobile.liveNow': 'Na żywo',
+ 'dashboard.mobile.tripProgress': 'Postęp podróży',
+ 'dashboard.mobile.daysLeft': 'Pozostało {count} dni',
+ 'dashboard.mobile.places': 'Miejsca',
+ 'dashboard.mobile.buddies': 'Towarzysze',
+ 'dashboard.mobile.newTrip': 'Nowa podróż',
+ 'dashboard.mobile.currency': 'Waluta',
+ 'dashboard.mobile.timezone': 'Strefa czasowa',
+ 'dashboard.mobile.upcomingTrips': 'Nadchodzące podróże',
+ 'dashboard.mobile.yourTrips': 'Twoje podróże',
+ 'dashboard.mobile.trips': 'podróże',
+ 'dashboard.mobile.starts': 'Początek',
+ 'dashboard.mobile.duration': 'Czas trwania',
+ 'dashboard.mobile.day': 'dzień',
+ 'dashboard.mobile.days': 'dni',
+ 'dashboard.mobile.ongoing': 'W trakcie',
+ 'dashboard.mobile.startsToday': 'Zaczyna się dziś',
+ 'dashboard.mobile.tomorrow': 'Jutro',
+ 'dashboard.mobile.inDays': 'Za {count} dni',
+ 'dashboard.mobile.inMonths': 'Za {count} miesięcy',
+ 'dashboard.mobile.completed': 'Zakończone',
+ 'dashboard.mobile.currencyConverter': 'Przelicznik walut',
+ 'dashboard.filter.planned': 'Zaplanowane',
+ 'dashboard.hero.badgeLive': 'NA ŻYWO',
+ 'dashboard.hero.badgeToday': 'ZACZYNA SIĘ DZIŚ',
+ 'dashboard.hero.badgeTomorrow': 'JUTRO',
+ 'dashboard.hero.badgeNext': 'NASTĘPNA',
+ 'dashboard.hero.badgeRecent': 'NIEDAWNO',
+ 'dashboard.hero.tripDates': 'Daty podróży',
+ 'dashboard.hero.noDates': 'Brak ustawionych dat',
+ 'dashboard.hero.travelerOne': '{count} podróżny',
+ 'dashboard.hero.travelerMany': '{count} podróżnych',
+ 'dashboard.hero.destinationOne': '{count} cel',
+ 'dashboard.hero.destinationMany': '{count} celów',
+ 'dashboard.hero.dayUnitOne': 'dzień',
+ 'dashboard.hero.dayUnitMany': 'dni',
+ 'dashboard.hero.dayLeft': 'Pozostał dzień',
+ 'dashboard.hero.daysLeft': 'Pozostało dni',
+ 'dashboard.hero.lastDay': 'Ostatni dzień',
+ 'dashboard.hero.untilStart': 'Do startu',
+ 'dashboard.hero.startsIn': 'Start za',
+ 'dashboard.atlas.countriesVisited': 'Atlas · Odwiedzone kraje',
+ 'dashboard.atlas.ofTotal': 'z {total}',
+ 'dashboard.atlas.tripsTotal': 'Podróże łącznie',
+ 'dashboard.atlas.placesMapped': '{count} miejsc na mapie',
+ 'dashboard.atlas.daysTraveled': 'Dni w podróży',
+ 'dashboard.atlas.daysUnit': 'dni',
+ 'dashboard.atlas.acrossAllTrips': 'we wszystkich podróżach',
+ 'dashboard.atlas.distanceFlown': 'Przebyty dystans',
+ 'dashboard.atlas.kmUnit': 'km',
+ 'dashboard.atlas.aroundEquator': '≈ {count}× dookoła równika',
+ 'dashboard.card.idea': 'Pomysł',
+ 'dashboard.card.buddyOne': 'Towarzysz',
+ 'dashboard.fx.from': 'Z',
+ 'dashboard.fx.to': 'Na',
+ 'dashboard.fx.unavailable': 'Kurs niedostępny',
+ 'dashboard.tz.searchPlaceholder': 'Szukaj strefy czasowej…',
+ 'dashboard.tz.empty': 'Brak innych stref czasowych — dodaj jedną za pomocą +',
+ 'dashboard.upcoming.title': 'Nadchodzące rezerwacje',
+ 'dashboard.upcoming.empty': 'Nic jeszcze nie zarezerwowano.',
+ 'dashboard.confirm.copy.title': 'Skopiować tę podróż?',
+ 'dashboard.confirm.copy.willCopy': 'Zostanie skopiowane',
+ 'dashboard.confirm.copy.will1': 'Dni, miejsca i przypisania do dni',
+ 'dashboard.confirm.copy.will2': 'Noclegi i rezerwacje',
+ 'dashboard.confirm.copy.will3': 'Pozycje budżetu i kolejność kategorii',
+ 'dashboard.confirm.copy.will4': 'Listy pakowania (niezaznaczone)',
+ 'dashboard.confirm.copy.will5': 'Zadania (nieprzypisane i niezaznaczone)',
+ 'dashboard.confirm.copy.will6': 'Notatki dnia',
+ 'dashboard.confirm.copy.wontCopy': 'Nie zostanie skopiowane',
+ 'dashboard.confirm.copy.wont1': 'Współpracownicy i przypisania członków',
+ 'dashboard.confirm.copy.wont2': 'Wspólne notatki, ankiety i wiadomości',
+ 'dashboard.confirm.copy.wont3': 'Pliki i zdjęcia',
+ 'dashboard.confirm.copy.wont4': 'Tokeny udostępniania',
+ 'dashboard.confirm.copy.confirm': 'Kopiuj podróż',
+ 'dashboard.aria.toggleView': 'Przełącz widok',
+ 'dashboard.aria.filter': 'Filtruj',
+ 'dashboard.aria.duplicate': 'Duplikuj',
+ 'dashboard.aria.refreshRates': 'Odśwież kursy',
+ 'dashboard.aria.swapCurrencies': 'Zamień waluty',
+ 'dashboard.aria.addTimezone': 'Dodaj strefę czasową',
+ 'dashboard.aria.removeTimezone': 'Usuń {city}',
+ 'dashboard.dayCountRequired': 'Liczba dni jest wymagana',
+};
+export default dashboard;
diff --git a/shared/src/i18n/pl/day.ts b/shared/src/i18n/pl/day.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ee8aef19183f785f43da859dd49b2c92e3b32916
--- /dev/null
+++ b/shared/src/i18n/pl/day.ts
@@ -0,0 +1,26 @@
+import type { TranslationStrings } from '../types';
+
+const day: TranslationStrings = {
+ 'day.precipProb': 'Prawdopodobieństwo opadów',
+ 'day.precipitation': 'Opady',
+ 'day.wind': 'Wiatr',
+ 'day.sunrise': 'Wschód słońca',
+ 'day.sunset': 'Zachód słońca',
+ 'day.hourlyForecast': 'Prognoza godzinowa',
+ 'day.climateHint':
+ 'Historyczne średnie — rzeczywista prognoza dostępna na następne 16 dni od tej daty.',
+ 'day.noWeather': 'Brak danych pogodowych. Dodaj miejsce ze współrzędnymi.',
+ 'day.overview': 'Przegląd dnia',
+ 'day.accommodation': 'Zakwaterowanie',
+ 'day.addAccommodation': 'Dodaj zakwaterowanie',
+ 'day.hotelDayRange': 'Zastosuj do dni',
+ 'day.noPlacesForHotel': 'Najpierw dodaj miejsca do swojej podróży',
+ 'day.allDays': 'Wszystkie',
+ 'day.checkIn': 'Zameldowanie',
+ 'day.checkInUntil': 'Do',
+ 'day.checkOut': 'Wymeldowanie',
+ 'day.confirmation': 'Potwierdzenie',
+ 'day.editAccommodation': 'Edytuj zakwaterowanie',
+ 'day.reservations': 'Rezerwacje',
+};
+export default day;
diff --git a/shared/src/i18n/pl/dayplan.ts b/shared/src/i18n/pl/dayplan.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0d0f6c2eee6aa5231a1d5b051c019b535d685263
--- /dev/null
+++ b/shared/src/i18n/pl/dayplan.ts
@@ -0,0 +1,61 @@
+import type { TranslationStrings } from '../types';
+
+const dayplan: TranslationStrings = {
+ 'dayplan.icsTooltip': 'Eksportuj kalendarz (ICS)',
+ 'dayplan.emptyDay': 'Brak miejsc zaplanowanych na ten dzień',
+ 'dayplan.cannotReorderTransport':
+ 'Nie można zmieniać kolejności dla rezerwacji z określoną godziną',
+ 'dayplan.confirmRemoveTimeTitle': 'Usunąć godzinę?',
+ 'dayplan.confirmRemoveTimeBody':
+ 'To miejsce ma określoną godzinę ({time}). Przeniesienie go usunie godzinę i umożliwi swobodne sortowanie.',
+ 'dayplan.confirmRemoveTimeAction': 'Usuń godzinę i przenieś',
+ 'dayplan.confirmDeleteNoteTitle': 'Usunąć notatkę?',
+ 'dayplan.confirmDeleteNoteBody': 'Ta notatka zostanie trwale usunięta.',
+ 'dayplan.cannotDropOnTimed':
+ 'Nie można umieszczać elementów pomiędzy wpisami z określoną godziną',
+ 'dayplan.cannotBreakChronology':
+ 'Spowodowałoby to naruszenie chronologicznej kolejności elementów i rezerwacji z określoną godziną',
+ 'dayplan.addNote': 'Dodaj notatkę',
+ 'dayplan.editNote': 'Edytuj notatkę',
+ 'dayplan.noteAdd': 'Dodaj notatkę',
+ 'dayplan.noteEdit': 'Edytuj notatkę',
+ 'dayplan.noteTitle': 'Notatka',
+ 'dayplan.noteSubtitle': 'Notatka dnia',
+ 'dayplan.totalCost': 'Łączny koszt',
+ 'dayplan.days': 'Dni',
+ 'dayplan.dayN': 'Dzień {n}',
+ 'dayplan.calculating': 'Obliczanie...',
+ 'dayplan.route': 'Trasa',
+ 'dayplan.optimize': 'Optymalizuj',
+ 'dayplan.optimized': 'Trasa została zoptymalizowana',
+ 'dayplan.routeError': 'Nie udało się obliczyć trasy',
+ 'dayplan.toast.needTwoPlaces':
+ 'Potrzeba co najmniej dwóch miejsc, aby zoptymalizować trasę',
+ 'dayplan.toast.routeOptimized': 'Trasa została zoptymalizowana',
+ 'dayplan.toast.routeOptimizedFromHotel':
+ 'Trasa została zoptymalizowana względem Twojego zakwaterowania',
+ 'dayplan.toast.noGeoPlaces':
+ 'Nie znaleziono miejsc ze współrzędnymi do obliczenia trasy',
+ 'dayplan.confirmed': 'Potwierdzono',
+ 'dayplan.pendingRes': 'Oczekujące',
+ 'dayplan.pdf': 'PDF',
+ 'dayplan.pdfTooltip': 'Eksportuj plan dnia jako PDF',
+ 'dayplan.pdfError': 'Nie udało się wyeksportować pliku PDF',
+ 'dayplan.mobile.addPlace': 'Dodaj miejsce',
+ 'dayplan.mobile.searchPlaces': 'Szukaj miejsc...',
+ 'dayplan.mobile.allAssigned': 'Wszystkie miejsca przypisane',
+ 'dayplan.mobile.noMatch': 'Brak wyników',
+ 'dayplan.mobile.createNew': 'Utwórz nowe miejsce',
+ 'dayplan.expandAll': 'Expand all days', // en-fallback
+ 'dayplan.collapseAll': 'Collapse all days', // en-fallback
+ 'dayplan.reorderDays': 'Zmień kolejność dni',
+ 'dayplan.reorderTitle': 'Zmień kolejność dni',
+ 'dayplan.reorderHint': 'Miejsca, notatki i rezerwacje danego dnia przenoszą się razem z nim.',
+ 'dayplan.addDay': 'Dodaj dzień',
+ 'dayplan.moveUp': 'Przenieś w górę',
+ 'dayplan.moveDown': 'Przenieś w dół',
+ 'dayplan.reorderUndo': 'Zmień kolejność dni',
+ 'dayplan.reorderError': 'Nie udało się zmienić kolejności dni',
+ 'dayplan.addDayError': 'Nie udało się dodać dnia',
+};
+export default dayplan;
diff --git a/shared/src/i18n/pl/externalNotifications.ts b/shared/src/i18n/pl/externalNotifications.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3944c58625d39e77b5d6d61feaaf8d4e9d8c50cb
--- /dev/null
+++ b/shared/src/i18n/pl/externalNotifications.ts
@@ -0,0 +1,64 @@
+import type { NotificationLocale } from '../externalNotifications/types';
+
+const pl: NotificationLocale = {
+ email: {
+ footer:
+ 'Otrzymałeś/aś tę wiadomość, ponieważ masz włączone powiadomienia w TREK.',
+ manage: 'Zarządzaj preferencjami w ustawieniach',
+ madeWith: 'Made with',
+ openTrek: 'Otwórz TREK',
+ },
+ events: {
+ trip_invite: (p) => ({
+ title: `Zaproszenie do "${p.trip}"`,
+ body: `${p.actor} zaprosił ${p.invitee || 'członka'} do podróży "${p.trip}".`,
+ }),
+ booking_change: (p) => ({
+ title: `Nowa rezerwacja: ${p.booking}`,
+ body: `${p.actor} dodał rezerwację "${p.booking}" (${p.type}) do "${p.trip}".`,
+ }),
+ trip_reminder: (p) => ({
+ title: `Przypomnienie o podróży: ${p.trip}`,
+ body: `Twoja podróż "${p.trip}" zbliża się!`,
+ }),
+ todo_due: (p) => ({
+ title: `Zadanie z terminem: ${p.todo}`,
+ body: `"${p.todo}" w "${p.trip}" — termin ${p.due}.`,
+ }),
+ vacay_invite: (p) => ({
+ title: 'Zaproszenie Vacay Fusion',
+ body: `${p.actor} zaprosił Cię do połączenia planów urlopowych. Otwórz TREK, aby zaakceptować lub odrzucić.`,
+ }),
+ photos_shared: (p) => ({
+ title: `${p.count} zdjęć udostępnionych`,
+ body: `${p.actor} udostępnił ${p.count} zdjęcie/zdjęcia w "${p.trip}".`,
+ }),
+ collab_message: (p) => ({
+ title: `Nowa wiadomość w "${p.trip}"`,
+ body: `${p.actor}: ${p.preview}`,
+ }),
+ packing_tagged: (p) => ({
+ title: `Pakowanie: ${p.category}`,
+ body: `${p.actor} przypisał Cię do kategorii "${p.category}" w "${p.trip}".`,
+ }),
+ version_available: (p) => ({
+ title: 'Nowa wersja TREK dostępna',
+ body: `TREK ${p.version} jest teraz dostępny. Odwiedź panel administracyjny, aby zaktualizować.`,
+ }),
+ synology_session_cleared: () => ({
+ title: 'Sesja Synology wyczyszczona',
+ body: 'Twoje konto lub URL Synology uległo zmianie. Zostałeś wylogowany z Synology Photos.',
+ }),
+ },
+ passwordReset: {
+ subject: 'Zresetuj hasło',
+ greeting: 'Cześć',
+ body: 'Otrzymaliśmy prośbę o zresetowanie hasła do Twojego konta TREK. Kliknij przycisk poniżej, aby ustawić nowe hasło.',
+ ctaIntro: 'Zresetuj hasło',
+ expiry: 'Link wygaśnie za 60 minut.',
+ ignore:
+ 'Jeśli to nie Ty, zignoruj tę wiadomość — Twoje hasło pozostanie bez zmian.',
+ },
+};
+
+export default pl;
diff --git a/shared/src/i18n/pl/files.ts b/shared/src/i18n/pl/files.ts
new file mode 100644
index 0000000000000000000000000000000000000000..85d7e739823c29ab145af56c62aeb7a0d4ad0c9f
--- /dev/null
+++ b/shared/src/i18n/pl/files.ts
@@ -0,0 +1,62 @@
+import type { TranslationStrings } from '../types';
+
+const files: TranslationStrings = {
+ 'files.title': 'Pliki',
+ 'files.pageTitle': 'Pliki i dokumenty',
+ 'files.subtitle': '{count} plików dla {trip}',
+ 'files.download': 'Pobierz',
+ 'files.openError': 'Nie można otworzyć pliku',
+ 'files.downloadPdf': 'Pobierz PDF',
+ 'files.count': '{count} plików',
+ 'files.countSingular': '1 plik',
+ 'files.uploaded': '{count} przesłanych',
+ 'files.uploadError': 'Przesyłanie nie powiodło się',
+ 'files.dropzone': 'Przeciągnij pliki tutaj',
+ 'files.dropzoneHint': 'lub kliknij, aby przeglądać',
+ 'files.allowedTypes':
+ 'Obrazki, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Maks 50 MB',
+ 'files.uploading': 'Przesyłanie...',
+ 'files.filterAll': 'Wszystkie',
+ 'files.filterPdf': 'PDF',
+ 'files.filterImages': 'Obrazki',
+ 'files.filterDocs': 'Dokumenty',
+ 'files.filterCollab': 'Wspólne notatki',
+ 'files.sourceCollab': 'Z wspólnych notatek',
+ 'files.empty': 'Brak plików',
+ 'files.emptyHint': 'Prześlij pliki, aby dodać je do swojej podróży',
+ 'files.openTab': 'Otwórz w nowej karcie',
+ 'files.confirm.delete': 'Czy na pewno chcesz usunąć ten plik?',
+ 'files.toast.deleted': 'Plik został usunięty',
+ 'files.toast.deleteError': 'Nie udało się usunąć pliku',
+ 'files.sourcePlan': 'Plan dni',
+ 'files.sourceBooking': 'Rezerwacje',
+ 'files.sourceTransport': 'Transport',
+ 'files.attach': 'Załącz',
+ 'files.pasteHint': 'Możesz również wkleić obrazki ze schowka (Ctrl+V)',
+ 'files.trash': 'Kosz',
+ 'files.trashEmpty': 'Kosz jest pusty',
+ 'files.emptyTrash': 'Opróżnij kosz',
+ 'files.restore': 'Przywróć',
+ 'files.star': 'Oznacz',
+ 'files.unstar': 'Usuń oznaczenie',
+ 'files.assign': 'Przypisz',
+ 'files.assignTitle': 'Przypisz plik',
+ 'files.assignPlace': 'Miejsce',
+ 'files.assignBooking': 'Rezerwacja',
+ 'files.assignTransport': 'Transport',
+ 'files.unassigned': 'Nieprzypisane',
+ 'files.unlink': 'Usuń link',
+ 'files.toast.trashed': 'Przeniesiono do kosza',
+ 'files.toast.restored': 'Plik został przywrócony',
+ 'files.toast.trashEmptied': 'Kosz został opróżniony',
+ 'files.toast.assigned': 'Plik został przypisany',
+ 'files.toast.assignError': 'Nie udało się przypisać',
+ 'files.toast.restoreError': 'Nie udało się przywrócić',
+ 'files.confirm.permanentDelete':
+ 'Czy na pewno chcesz trwale usunąć ten plik? Tej operacji nie można cofnąć.',
+ 'files.confirm.emptyTrash':
+ 'Czy na pewno chcesz trwale usunąć wszystkie pliki z kosza? Tej operacji nie można cofnąć.',
+ 'files.noteLabel': 'Notatka',
+ 'files.notePlaceholder': 'Dodaj notatkę...',
+};
+export default files;
diff --git a/shared/src/i18n/pl/index.ts b/shared/src/i18n/pl/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..77aeb6a09fb62e2522e8b4c04e4169a94c2f8d99
--- /dev/null
+++ b/shared/src/i18n/pl/index.ts
@@ -0,0 +1,86 @@
+import admin from './admin';
+import airport from './airport';
+import atlas from './atlas';
+import backup from './backup';
+import budget from './budget';
+import categories from './categories';
+import collab from './collab';
+import common from './common';
+import dashboard from './dashboard';
+import day from './day';
+import dayplan from './dayplan';
+import files from './files';
+import inspector from './inspector';
+import journey from './journey';
+import login from './login';
+import map from './map';
+import members from './members';
+import memories from './memories';
+import nav from './nav';
+import notif from './notif';
+import notifications from './notifications';
+import oauth from './oauth';
+import packing from './packing';
+import pdf from './pdf';
+import perm from './perm';
+import photos from './photos';
+import places from './places';
+import planner from './planner';
+import register from './register';
+import reservations from './reservations';
+import settings from './settings';
+import share from './share';
+import shared from './shared';
+import stats from './stats';
+import system_notice from './system_notice';
+import todo from './todo';
+import transport from './transport';
+import trip from './trip';
+import trips from './trips';
+import undo from './undo';
+import vacay from './vacay';
+
+const locale = {
+ ...common,
+ ...trips,
+ ...nav,
+ ...dashboard,
+ ...settings,
+ ...admin,
+ ...dayplan,
+ ...share,
+ ...shared,
+ ...login,
+ ...register,
+ ...vacay,
+ ...atlas,
+ ...trip,
+ ...places,
+ ...inspector,
+ ...reservations,
+ ...airport,
+ ...map,
+ ...budget,
+ ...files,
+ ...packing,
+ ...members,
+ ...categories,
+ ...backup,
+ ...photos,
+ ...pdf,
+ ...planner,
+ ...stats,
+ ...day,
+ ...memories,
+ ...collab,
+ ...perm,
+ ...undo,
+ ...notifications,
+ ...todo,
+ ...notif,
+ ...journey,
+ ...oauth,
+ ...system_notice,
+ ...transport,
+};
+export default locale;
diff --git a/shared/src/i18n/pl/inspector.ts b/shared/src/i18n/pl/inspector.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7e652dccb9ac5bf4afe193b0b69a1f79489037d1
--- /dev/null
+++ b/shared/src/i18n/pl/inspector.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const inspector: TranslationStrings = {
+ 'inspector.opened': 'Otwarte',
+ 'inspector.closed': 'Zamknięte',
+ 'inspector.openingHours': 'Godziny otwarcia',
+ 'inspector.showHours': 'Pokaż godziny otwarcia',
+ 'inspector.files': 'Pliki',
+ 'inspector.filesCount': '{count} plików',
+ 'inspector.removeFromDay': 'Usuń z dnia',
+ 'inspector.remove': 'Usuń',
+ 'inspector.addToDay': 'Dodaj do dnia',
+ 'inspector.confirmedRes': 'Potwierdzona rezerwacja',
+ 'inspector.pendingRes': 'Oczekująca rezerwacja',
+ 'inspector.google': 'Otwórz w Mapach Google',
+ 'inspector.website': 'Otwórz stronę internetową',
+ 'inspector.addRes': 'Rezerwacja',
+ 'inspector.editRes': 'Edytuj rezerwację',
+ 'inspector.participants': 'Uczestnicy',
+ 'inspector.trackStats': 'Statystyki trasy',
+};
+export default inspector;
diff --git a/shared/src/i18n/pl/journey.ts b/shared/src/i18n/pl/journey.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e6b7a8a82b186c1df0a3dd3d6fbe310b27069486
--- /dev/null
+++ b/shared/src/i18n/pl/journey.ts
@@ -0,0 +1,244 @@
+import type { TranslationStrings } from '../types';
+
+const journey: TranslationStrings = {
+ 'journey.search.placeholder': 'Szukaj podróży…',
+ 'journey.search.noResults': 'Brak podróży pasujących do „{query}"',
+ 'journey.title': 'Dziennik podróży',
+ 'journey.subtitle': 'Dokumentuj swoje podróże na bieżąco',
+ 'journey.new': 'Nowy dziennik podróży',
+ 'journey.create': 'Utwórz',
+ 'journey.titlePlaceholder': 'Dokąd jedziesz?',
+ 'journey.empty': 'Brak dzienników podróży',
+ 'journey.emptyHint': 'Zacznij dokumentować swoją następną podróż',
+ 'journey.deleted': 'Dziennik podróży usunięty',
+ 'journey.createError': 'Nie udało się utworzyć dziennika podróży',
+ 'journey.deleteError': 'Nie udało się usunąć dziennika podróży',
+ 'journey.deleteConfirmTitle': 'Usuń',
+ 'journey.deleteConfirmMessage':
+ 'Usunąć „{title}"? Tej operacji nie można cofnąć.',
+ 'journey.deleteConfirmGeneric': 'Czy na pewno chcesz to usunąć?',
+ 'journey.notFound': 'Nie znaleziono dziennika podróży',
+ 'journey.photos': 'Zdjęcia',
+ 'journey.timelineEmpty': 'Brak przystanków',
+ 'journey.timelineEmptyHint':
+ 'Dodaj zameldowanie lub napisz wpis w dzienniku, aby rozpocząć',
+ 'journey.status.draft': 'Szkic',
+ 'journey.status.active': 'Aktywny',
+ 'journey.status.completed': 'Zakończony',
+ 'journey.status.upcoming': 'Nadchodzący',
+ 'journey.status.archived': 'Zarchiwizowano',
+ 'journey.checkin.add': 'Zamelduj się',
+ 'journey.checkin.namePlaceholder': 'Nazwa miejsca',
+ 'journey.checkin.notesPlaceholder': 'Notatki (opcjonalnie)',
+ 'journey.checkin.save': 'Zapisz',
+ 'journey.checkin.error': 'Nie udało się zapisać zameldowania',
+ 'journey.entry.add': 'Dziennik',
+ 'journey.entry.edit': 'Edytuj wpis',
+ 'journey.entry.titlePlaceholder': 'Tytuł (opcjonalnie)',
+ 'journey.entry.bodyPlaceholder': 'Co się dziś wydarzyło?',
+ 'journey.entry.save': 'Zapisz',
+ 'journey.entry.error': 'Nie udało się zapisać wpisu',
+ 'journey.photo.add': 'Zdjęcie',
+ 'journey.photo.uploadError': 'Przesyłanie nie powiodło się',
+ 'journey.share.share': 'Udostępnij',
+ 'journey.share.public': 'Publiczny',
+ 'journey.share.linkCopied': 'Publiczny link skopiowany',
+ 'journey.share.disabled': 'Udostępnianie publiczne wyłączone',
+ 'journey.editor.titlePlaceholder': 'Nadaj temu momentowi nazwę...',
+ 'journey.editor.bodyPlaceholder': 'Opowiedz historię tego dnia...',
+ 'journey.editor.placePlaceholder': 'Lokalizacja (opcjonalnie)',
+ 'journey.editor.tagsPlaceholder':
+ 'Tagi: ukryty skarb, najlepszy posiłek, warto wrócić...',
+ 'journey.visibility.private': 'Prywatny',
+ 'journey.visibility.shared': 'Udostępniony',
+ 'journey.visibility.public': 'Publiczny',
+ 'journey.emptyState.title': 'Twoja historia zaczyna się tutaj',
+ 'journey.emptyState.subtitle':
+ 'Zamelduj się w miejscu lub napisz swój pierwszy wpis w dzienniku',
+ 'journey.frontpage.subtitle':
+ 'Zamień swoje podróże w historie, których nigdy nie zapomnisz',
+ 'journey.frontpage.createJourney': 'Utwórz dziennik podróży',
+ 'journey.frontpage.activeJourney': 'Aktywny dziennik podróży',
+ 'journey.frontpage.allJourneys': 'Wszystkie dzienniki podróży',
+ 'journey.frontpage.journeys': 'dzienniki podróży',
+ 'journey.frontpage.createNew': 'Utwórz nowy dziennik podróży',
+ 'journey.frontpage.createNewSub':
+ 'Wybierz podróże, pisz historie, dziel się przygodami',
+ 'journey.frontpage.live': 'Na żywo',
+ 'journey.frontpage.synced': 'Zsynchronizowany',
+ 'journey.frontpage.continueWriting': 'Kontynuuj pisanie',
+ 'journey.frontpage.updated': 'Zaktualizowano {time}',
+ 'journey.frontpage.suggestionLabel': 'Podróż właśnie się zakończyła',
+ 'journey.frontpage.suggestionText':
+ 'Zamień {title} w dziennik podróży',
+ 'journey.frontpage.dismiss': 'Odrzuć',
+ 'journey.frontpage.journeyName': 'Nazwa dziennika podróży',
+ 'journey.frontpage.namePlaceholder': 'np. Azja Południowo-Wschodnia 2026',
+ 'journey.frontpage.selectTrips': 'Wybierz podróże',
+ 'journey.frontpage.tripsSelected': 'podróży wybranych',
+ 'journey.frontpage.trips': 'podróże',
+ 'journey.frontpage.placesImported': 'miejsc zostanie zaimportowanych',
+ 'journey.frontpage.places': 'miejsca',
+ 'journey.detail.backToJourney': 'Powrót do dziennika podróży',
+ 'journey.detail.syncedWithTrips': 'Zsynchronizowany z podróżami',
+ 'journey.detail.addEntry': 'Dodaj wpis',
+ 'journey.detail.newEntry': 'Nowy wpis',
+ 'journey.detail.editEntry': 'Edytuj wpis',
+ 'journey.detail.noEntries': 'Brak wpisów',
+ 'journey.detail.noEntriesHint':
+ 'Dodaj podróż, aby rozpocząć ze szkieletowymi wpisami',
+ 'journey.detail.noPhotos': 'Brak zdjęć',
+ 'journey.detail.noPhotosHint':
+ 'Prześlij zdjęcia do wpisów lub przeglądaj bibliotekę Immich/Synology',
+ 'journey.detail.journeyStats': 'Statystyki podróży',
+ 'journey.detail.syncedTrips': 'Zsynchronizowane podróże',
+ 'journey.detail.noTripsLinked': 'Brak powiązanych podróży',
+ 'journey.detail.contributors': 'Współtwórcy',
+ 'journey.detail.readMore': 'Czytaj dalej',
+ 'journey.detail.prosCons': 'Zalety i wady',
+ 'journey.detail.photos': 'zdjęć',
+ 'journey.detail.day': 'Dzień {number}',
+ 'journey.detail.places': 'miejsc',
+ 'journey.stats.days': 'Dni',
+ 'journey.stats.cities': 'Miasta',
+ 'journey.stats.entries': 'Wpisy',
+ 'journey.stats.photos': 'Zdjęcia',
+ 'journey.stats.places': 'Miejsca',
+ 'journey.skeletons.show': 'Pokaż sugestie',
+ 'journey.skeletons.hide': 'Ukryj sugestie',
+ 'journey.verdict.lovedIt': 'Świetne',
+ 'journey.verdict.couldBeBetter': 'Mogłoby być lepiej',
+ 'journey.synced.places': 'miejsca',
+ 'journey.synced.synced': 'zsynchronizowane',
+ 'journey.editor.discardChangesConfirm': 'Masz niezapisane zmiany. Odrzucić?',
+ 'journey.editor.uploadFailed': 'Przesyłanie zdjęć nie powiodło się',
+ 'journey.editor.uploadPhotos': 'Prześlij zdjęcia',
+ 'journey.editor.uploading': 'Przesyłanie...',
+ 'journey.editor.uploadingProgress': 'Przesyłanie {done}/{total}…',
+ 'journey.editor.uploadPartialFailed':
+ '{failed} z {total} zdjęć nie powiodło się — zapisz ponownie, aby spróbować',
+ 'journey.editor.fromGallery': 'Z galerii',
+ 'journey.editor.allPhotosAdded': 'Wszystkie zdjęcia już dodane',
+ 'journey.editor.writeStory': 'Napisz swoją historię...',
+ 'journey.editor.prosCons': 'Zalety i wady',
+ 'journey.editor.pros': 'Zalety',
+ 'journey.editor.cons': 'Wady',
+ 'journey.editor.proPlaceholder': 'Coś świetnego...',
+ 'journey.editor.conPlaceholder': 'Nie tak świetne...',
+ 'journey.editor.addAnother': 'Dodaj kolejny',
+ 'journey.editor.date': 'Data',
+ 'journey.editor.location': 'Lokalizacja',
+ 'journey.editor.searchLocation': 'Szukaj lokalizacji...',
+ 'journey.editor.mood': 'Nastrój',
+ 'journey.editor.weather': 'Pogoda',
+ 'journey.editor.photoFirst': '1.',
+ 'journey.editor.makeFirst': 'Ustaw jako 1.',
+ 'journey.editor.searching': 'Szukanie...',
+ 'journey.mood.amazing': 'Niesamowity',
+ 'journey.mood.good': 'Dobry',
+ 'journey.mood.neutral': 'Neutralny',
+ 'journey.mood.rough': 'Ciężki',
+ 'journey.weather.sunny': 'Słonecznie',
+ 'journey.weather.partly': 'Częściowe zachmurzenie',
+ 'journey.weather.cloudy': 'Pochmurno',
+ 'journey.weather.rainy': 'Deszczowo',
+ 'journey.weather.stormy': 'Burzowo',
+ 'journey.weather.cold': 'Śnieżnie',
+ 'journey.trips.linkTrip': 'Powiąż podróż',
+ 'journey.trips.searchTrip': 'Szukaj podróży',
+ 'journey.trips.searchPlaceholder': 'Nazwa podróży lub cel...',
+ 'journey.trips.noTripsAvailable': 'Brak dostępnych podróży',
+ 'journey.trips.link': 'Powiąż',
+ 'journey.trips.tripLinked': 'Podróż powiązana',
+ 'journey.trips.linkFailed': 'Powiązanie podróży nie powiodło się',
+ 'journey.trips.addTrip': 'Dodaj podróż',
+ 'journey.trips.unlinkTrip': 'Odłącz podróż',
+ 'journey.trips.unlinkMessage':
+ 'Odłączyć „{title}"? Wszystkie zsynchronizowane wpisy i zdjęcia z tej podróży zostaną trwale usunięte. Tej operacji nie można cofnąć.',
+ 'journey.trips.unlink': 'Odłącz',
+ 'journey.trips.tripUnlinked': 'Podróż odłączona',
+ 'journey.trips.unlinkFailed': 'Odłączenie podróży nie powiodło się',
+ 'journey.trips.noTripsLinkedSettings': 'Brak powiązanych podróży',
+ 'journey.contributors.invite': 'Zaproś współtwórcę',
+ 'journey.contributors.searchUser': 'Szukaj użytkownika',
+ 'journey.contributors.searchPlaceholder': 'Nazwa użytkownika lub e-mail...',
+ 'journey.contributors.noUsers': 'Nie znaleziono użytkowników',
+ 'journey.contributors.role': 'Rola',
+ 'journey.contributors.added': 'Współtwórca dodany',
+ 'journey.contributors.addFailed': 'Dodawanie współtwórcy nie powiodło się',
+ 'journey.share.publicShare': 'Udostępnianie publiczne',
+ 'journey.share.createLink': 'Utwórz link udostępniania',
+ 'journey.share.linkCreated': 'Link udostępniania utworzony',
+ 'journey.share.createFailed': 'Tworzenie linku nie powiodło się',
+ 'journey.share.copy': 'Kopiuj',
+ 'journey.share.copied': 'Skopiowano!',
+ 'journey.share.timeline': 'Oś czasu',
+ 'journey.share.gallery': 'Galeria',
+ 'journey.share.map': 'Mapa',
+ 'journey.share.removeLink': 'Usuń link udostępniania',
+ 'journey.share.linkDeleted': 'Link udostępniania usunięty',
+ 'journey.share.deleteFailed': 'Usunięcie nie powiodło się',
+ 'journey.share.updateFailed': 'Aktualizacja nie powiodła się',
+ 'journey.invite.role': 'Rola',
+ 'journey.invite.viewer': 'Obserwator',
+ 'journey.invite.editor': 'Redaktor',
+ 'journey.invite.invite': 'Zaproś',
+ 'journey.invite.inviting': 'Zapraszanie...',
+ 'journey.settings.title': 'Ustawienia dziennika podróży',
+ 'journey.settings.coverImage': 'Zdjęcie okładkowe',
+ 'journey.settings.changeCover': 'Zmień okładkę',
+ 'journey.settings.addCover': 'Dodaj zdjęcie okładkowe',
+ 'journey.settings.name': 'Nazwa',
+ 'journey.settings.subtitle': 'Podtytuł',
+ 'journey.settings.subtitlePlaceholder': 'np. Tajlandia, Wietnam i Kambodża',
+ 'journey.settings.endJourney': 'Archiwizuj podróż',
+ 'journey.settings.reopenJourney': 'Przywróć podróż',
+ 'journey.settings.archived': 'Podróż zarchiwizowana',
+ 'journey.settings.reopened': 'Podróż wznowiona',
+ 'journey.settings.endDescription':
+ 'Ukrywa odznakę Na żywo. Możesz wznowić w dowolnym momencie.',
+ 'journey.settings.delete': 'Usuń',
+ 'journey.settings.deleteJourney': 'Usuń dziennik podróży',
+ 'journey.settings.deleteMessage':
+ 'Usunąć „{title}"? Wszystkie wpisy i zdjęcia zostaną utracone.',
+ 'journey.settings.saved': 'Ustawienia zapisane',
+ 'journey.settings.saveFailed': 'Zapisywanie nie powiodło się',
+ 'journey.settings.coverUpdated': 'Okładka zaktualizowana',
+ 'journey.settings.coverFailed': 'Przesyłanie nie powiodło się',
+ 'journey.settings.failedToDelete': 'Nie udało się usunąć',
+ 'journey.entries.deleteTitle': 'Usuń wpis',
+ 'journey.photosUploaded': '{count} zdjęć przesłanych',
+ 'journey.photosUploadFailed': 'Nie udało się przesłać niektórych zdjęć',
+ 'journey.photosAdded': '{count} zdjęć dodanych',
+ 'journey.public.notFound': 'Nie znaleziono',
+ 'journey.public.notFoundMessage':
+ 'Ten dziennik podróży nie istnieje lub link wygasł.',
+ 'journey.public.readOnly': 'Tylko do odczytu · Publiczny dziennik podróży',
+ 'journey.public.tagline': 'Travel Resource & Exploration Kit',
+ 'journey.public.sharedVia': 'Udostępnione przez',
+ 'journey.public.madeWith': 'Stworzone z',
+ 'journey.pdf.journeyBook': 'Książka podróży',
+ 'journey.pdf.madeWith': 'Stworzone z TREK',
+ 'journey.pdf.day': 'Dzień',
+ 'journey.pdf.theEnd': 'Koniec',
+ 'journey.pdf.saveAsPdf': 'Zapisz jako PDF',
+ 'journey.pdf.pages': 'stron',
+ 'journey.picker.tripPeriod': 'Okres podróży',
+ 'journey.picker.dateRange': 'Zakres dat',
+ 'journey.picker.allPhotos': 'Wszystkie zdjęcia',
+ 'journey.picker.albums': 'Albumy',
+ 'journey.picker.selected': 'wybranych',
+ 'journey.picker.addTo': 'Dodaj do',
+ 'journey.picker.newGallery': 'Nowa galeria',
+ 'journey.picker.selectAll': 'Zaznacz wszystko',
+ 'journey.picker.deselectAll': 'Odznacz wszystko',
+ 'journey.picker.noAlbums': 'Nie znaleziono albumów',
+ 'journey.picker.selectDate': 'Wybierz datę',
+ 'journey.picker.search': 'Szukaj',
+ 'journey.detail.journeyTab': 'Journey', // en-fallback
+ 'journey.contributors.remove': 'Remove contributor', // en-fallback
+ 'journey.contributors.removeConfirm': 'Remove {username} from this journey?', // en-fallback
+ 'journey.contributors.removed': 'Contributor removed', // en-fallback
+ 'journey.contributors.removeFailed': 'Failed to remove contributor', // en-fallback
+};
+export default journey;
diff --git a/shared/src/i18n/pl/login.ts b/shared/src/i18n/pl/login.ts
new file mode 100644
index 0000000000000000000000000000000000000000..12206b31c4a9832c6bb17523452f607c850373a3
--- /dev/null
+++ b/shared/src/i18n/pl/login.ts
@@ -0,0 +1,102 @@
+import type { TranslationStrings } from '../types';
+
+const login: TranslationStrings = {
+ 'login.error': 'Logowanie nie powiodło się. Sprawdź dane logowania.',
+ 'login.tagline': 'Twoje podróże.\nTwój plan.',
+ 'login.description':
+ 'Planuj wspólnie podróże z interaktywnymi mapami, budżetami i synchronizacją w czasie rzeczywistym.',
+ 'login.features.maps': 'Interaktywne mapy',
+ 'login.features.mapsDesc': 'Google Places, trasy i grupowanie',
+ 'login.features.realtime': 'Synchronizacja w czasie rzeczywistym',
+ 'login.features.realtimeDesc': 'Planuj wspólnie przez WebSocket',
+ 'login.features.budget': 'Śledzenie budżetu',
+ 'login.features.budgetDesc':
+ 'Kategorie, wykresy i koszty w przeliczeniu na osobę',
+ 'login.features.collab': 'Współpraca',
+ 'login.features.collabDesc': 'Wielu użytkowników i wspólne podróże',
+ 'login.features.packing': 'Listy pakowania',
+ 'login.features.packingDesc': 'Kategorie, postęp i sugestie',
+ 'login.features.bookings': 'Rezerwacje',
+ 'login.features.bookingsDesc': 'Loty, hotele, restauracje i więcej',
+ 'login.features.files': 'Dokumenty',
+ 'login.features.filesDesc': 'Przesyłaj i zarządzaj dokumentami',
+ 'login.features.routes': 'Inteligentne trasy',
+ 'login.features.routesDesc':
+ 'Automatyczna optymalizacja i eksport do Google Maps',
+ 'login.selfHosted':
+ 'Własny hosting · Otwarty kod źródłowy · Twoje dane pozostają Twoje',
+ 'login.title': 'Zaloguj się',
+ 'login.subtitle': 'Witaj ponownie',
+ 'login.signingIn': 'Logowanie...',
+ 'login.signIn': 'Zaloguj się',
+ 'login.createAdmin': 'Utwórz konto administratora',
+ 'login.createAdminHint':
+ 'Skonfiguruj pierwsze konto administratora dla TREK.',
+ 'login.createAccount': 'Utwórz konto',
+ 'login.createAccountHint': 'Zarejestruj nowe konto.',
+ 'login.creating': 'Tworzenie...',
+ 'login.noAccount': 'Nie masz konta?',
+ 'login.hasAccount': 'Masz już konto?',
+ 'login.register': 'Zarejestruj się',
+ 'login.emailPlaceholder': 'twoj@email.pl',
+ 'login.username': 'Nazwa użytkownika',
+ 'login.oidc.registrationDisabled':
+ 'Rejestracja jest wyłączona. Skontaktuj się z administratorem.',
+ 'login.oidc.noEmail': 'Nie otrzymano e-maila od dostawcy.',
+ 'login.oidc.tokenFailed': 'Nie udało się uwierzytelnić.',
+ 'login.oidc.invalidState': 'Nieprawidłowa sesja. Spróbuj ponownie.',
+ 'login.demoFailed': 'Nie udało się zalogować do wersji demonstracyjnej',
+ 'login.oidcSignIn': 'Zaloguj się z {name}',
+ 'login.oidcOnly':
+ 'Uwierzytelnianie hasłem jest wyłączone. Zaloguj się za pomocą swojego dostawcy SSO.',
+ 'login.oidcLoggedOut':
+ 'Zostałeś wylogowany. Zaloguj się ponownie za pomocą swojego dostawcy SSO.',
+ 'login.demoHint': 'Wypróbuj demo — nie wymaga rejestracji',
+ 'login.mfaTitle': 'Uwierzytelnianie dwuskładnikowe',
+ 'login.mfaSubtitle': 'Wprowadź 6-cyfrowy kod z aplikacji uwierzytelniającej.',
+ 'login.mfaCodeLabel': 'Kod weryfikacyjny',
+ 'login.mfaCodeRequired': 'Wprowadź kod z aplikacji uwierzytelniającej.',
+ 'login.mfaHint':
+ 'Otwórz Google Authenticator, Authy lub inną aplikację TOTP.',
+ 'login.mfaBack': '← Powrót do logowania',
+ 'login.mfaVerify': 'Weryfikuj',
+ 'login.invalidInviteLink': 'Nieprawidłowy lub wygasły link zaproszenia',
+ 'login.oidcFailed': 'Logowanie OIDC nie powiodło się',
+ 'login.usernameRequired': 'Nazwa użytkownika jest wymagana',
+ 'login.passwordMinLength': 'Hasło musi mieć co najmniej 8 znaków',
+ 'login.forgotPassword': 'Nie pamiętasz hasła?',
+ 'login.rememberMe': 'Zapamiętaj mnie',
+ 'login.forgotPasswordTitle': 'Zresetuj hasło',
+ 'login.forgotPasswordBody':
+ 'Wpisz adres e-mail użyty przy rejestracji. Jeśli konto istnieje, wyślemy link do resetu.',
+ 'login.forgotPasswordSubmit': 'Wyślij link',
+ 'login.forgotPasswordSentTitle': 'Sprawdź swoją pocztę',
+ 'login.forgotPasswordSentBody':
+ 'Jeśli istnieje konto dla tego adresu, link jest już w drodze. Wygaśnie za 60 minut.',
+ 'login.forgotPasswordSmtpHintOff':
+ 'Uwaga: administrator nie skonfigurował SMTP, więc link resetujący zostanie zapisany w konsoli serwera zamiast wysłania e-mailem.',
+ 'login.backToLogin': 'Wróć do logowania',
+ 'login.newPassword': 'Nowe hasło',
+ 'login.confirmPassword': 'Potwierdź nowe hasło',
+ 'login.passwordsDontMatch': 'Hasła nie są zgodne',
+ 'login.mfaCode': 'Kod 2FA',
+ 'login.resetPasswordTitle': 'Ustaw nowe hasło',
+ 'login.resetPasswordBody':
+ 'Wybierz silne hasło, którego tu jeszcze nie używałeś. Minimum 8 znaków.',
+ 'login.resetPasswordMfaBody':
+ 'Wpisz kod 2FA lub kod zapasowy, aby zakończyć reset.',
+ 'login.resetPasswordSubmit': 'Zresetuj hasło',
+ 'login.resetPasswordVerify': 'Zweryfikuj i zresetuj',
+ 'login.resetPasswordSuccessTitle': 'Hasło zaktualizowane',
+ 'login.resetPasswordSuccessBody': 'Możesz się teraz zalogować nowym hasłem.',
+ 'login.resetPasswordInvalidLink': 'Nieprawidłowy link',
+ 'login.resetPasswordInvalidLinkBody':
+ 'Brakuje linku lub jest uszkodzony. Poproś o nowy, aby kontynuować.',
+ 'login.resetPasswordFailed': 'Reset nie powiódł się. Link mógł wygasnąć.',
+ 'login.setNewPassword': 'Ustaw nowe hasło',
+ 'login.setNewPasswordHint': 'Musisz zmienić hasło.',
+ 'login.passkey.signIn': 'Zaloguj się kluczem dostępu',
+ 'login.passkey.failed':
+ 'Logowanie kluczem dostępu nie powiodło się. Spróbuj ponownie.',
+};
+export default login;
diff --git a/shared/src/i18n/pl/map.ts b/shared/src/i18n/pl/map.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4295a9b79c172162c8df0845ef6d4fcd158be901
--- /dev/null
+++ b/shared/src/i18n/pl/map.ts
@@ -0,0 +1,17 @@
+import type { TranslationStrings } from '../types';
+
+const map: TranslationStrings = {
+ 'map.connections': 'Połączenia',
+ 'map.showConnections': 'Pokaż trasy rezerwacji',
+ 'map.hideConnections': 'Ukryj trasy rezerwacji',
+ 'poi.searchThisArea': 'Szukaj w tym obszarze',
+ 'poi.cat.restaurants': 'Restauracje',
+ 'poi.cat.cafes': 'Kawiarnie',
+ 'poi.cat.bars': 'Bary i życie nocne',
+ 'poi.cat.hotels': 'Noclegi',
+ 'poi.cat.sights': 'Atrakcje',
+ 'poi.cat.museums': 'Muzea i kultura',
+ 'poi.cat.nature': 'Przyroda i parki',
+ 'poi.cat.activities': 'Aktywności',
+};
+export default map;
diff --git a/shared/src/i18n/pl/members.ts b/shared/src/i18n/pl/members.ts
new file mode 100644
index 0000000000000000000000000000000000000000..99f756ea42af8ccaa37a2b234ba6c9ceeec1cacc
--- /dev/null
+++ b/shared/src/i18n/pl/members.ts
@@ -0,0 +1,24 @@
+import type { TranslationStrings } from '../types';
+
+const members: TranslationStrings = {
+ 'members.shareTrip': 'Udostępnij podróż',
+ 'members.inviteUser': 'Zaproś użytkownika',
+ 'members.selectUser': 'Wybierz użytkownika...',
+ 'members.invite': 'Zaproś',
+ 'members.allHaveAccess': 'Wszyscy użytkownicy mają już dostęp.',
+ 'members.access': 'Dostęp',
+ 'members.person': 'osoba',
+ 'members.persons': 'osoby',
+ 'members.you': 'ty',
+ 'members.owner': 'Właściciel',
+ 'members.leaveTrip': 'Opuść podróż',
+ 'members.removeAccess': 'Usuń dostęp',
+ 'members.confirmLeave': 'Opuścić podróż? Stracisz dostęp.',
+ 'members.confirmRemove': 'Usunąć dostęp dla tego użytkownika?',
+ 'members.loadError': 'Nie udało się załadować członków',
+ 'members.added': 'dodano',
+ 'members.addError': 'Nie udało się dodać członka',
+ 'members.removed': 'Usunięto członka',
+ 'members.removeError': 'Nie udało się usunąć członka',
+};
+export default members;
diff --git a/shared/src/i18n/pl/memories.ts b/shared/src/i18n/pl/memories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b35a0e239eb0fabdc605ba970ce5ec8a1cf247fa
--- /dev/null
+++ b/shared/src/i18n/pl/memories.ts
@@ -0,0 +1,81 @@
+import type { TranslationStrings } from '../types';
+
+const memories: TranslationStrings = {
+ 'memories.title': 'Zdjęcia',
+ 'memories.notConnected': 'Immich nie jest połączony',
+ 'memories.notConnectedHint':
+ 'Połącz swoją instancję Immich w ustawieniach, aby przeglądać tutaj swoje zdjęcia z podróży.',
+ 'memories.notConnectedMultipleHint':
+ 'Połącz jednego z tych dostawców zdjęć: {provider_names} w Ustawieniach, aby móc dodawać zdjęcia do tej podróży.',
+ 'memories.noDates': 'Dodaj daty do swojej podróży, aby załadować zdjęcia.',
+ 'memories.noPhotos': 'Nie znaleziono zdjęć',
+ 'memories.noPhotosHint':
+ 'Nie znaleziono zdjęć w Immich dla tego zakresu dat podróży.',
+ 'memories.photosFound': 'zdjęć',
+ 'memories.fromOthers': 'od innych',
+ 'memories.sharePhotos': 'Udostępnij zdjęcia',
+ 'memories.sharing': 'Udostępnianie',
+ 'memories.reviewTitle': 'Przejrzyj swoje zdjęcia',
+ 'memories.reviewHint': 'Kliknij w zdjęcia, aby wykluczyć je z udostępnienia.',
+ 'memories.shareCount': 'Udostępnij {count} zdjęć',
+ 'memories.providerUrl': 'URL serwera',
+ 'memories.providerApiKey': 'Klucz API',
+ 'memories.providerUsername': 'Nazwa użytkownika',
+ 'memories.providerPassword': 'Hasło',
+ 'memories.providerOTP': 'Kod MFA (jeśli włączony)',
+ 'memories.skipSSLVerification': 'Pomiń weryfikację certyfikatu SSL',
+ 'memories.immichAutoUpload':
+ 'Przy przesyłaniu kopiuj zdjęcia journey także do Immich',
+ 'memories.providerUrlHintSynology':
+ 'Uwzględnij ścieżkę aplikacji Photos w URL, np. https://nas:5001/photo',
+ 'memories.testConnection': 'Test',
+ 'memories.testShort': 'Test',
+ 'memories.connected': 'Połączono',
+ 'memories.disconnected': 'Nie połączono',
+ 'memories.connectionSuccess': 'Połączono z Immich',
+ 'memories.connectionError': 'Nie udało się połączyć z Immich',
+ 'memories.saved': 'Ustawienia {provider_name} zostały zapisane',
+ 'memories.providerDisconnectedBanner':
+ 'Połączenie z {provider_name} zostało utracone. Połącz ponownie w Ustawieniach, aby wyświetlać zdjęcia.',
+ 'memories.saveError': 'Nie można zapisać ustawień {provider_name}',
+ 'memories.addPhotos': 'Dodaj zdjęcia',
+ 'memories.selectPhotos': 'Wybierz zdjęcia z Immich',
+ 'memories.selectPhotosMultiple': 'Wybierz zdjęcia',
+ 'memories.selectHint': 'Dotknij zdjęć, aby je zaznaczyć.',
+ 'memories.selected': 'wybranych',
+ 'memories.addSelected': 'Dodaj {count} zdjęć',
+ 'memories.alreadyAdded': 'Dodano',
+ 'memories.private': 'Prywatne',
+ 'memories.stopSharing': 'Przestań udostępniać',
+ 'memories.oldest': 'Od najstarszych',
+ 'memories.newest': 'Od najnowszych',
+ 'memories.allLocations': 'Wszystkie lokalizacje',
+ 'memories.tripDates': 'Daty podróży',
+ 'memories.allPhotos': 'Wszystkie zdjęcia',
+ 'memories.confirmShareTitle': 'Udostępnić członkom podróży?',
+ 'memories.confirmShareHint':
+ '{count} zdjęć będzie widocznych dla wszystkich członków tej podróży. Możesz później ustawić poszczególne zdjęcia jako prywatne.',
+ 'memories.confirmShareButton': 'Udostępnij zdjęcia',
+ 'memories.testFirst': 'Najpierw przetestuj połączenie',
+ 'memories.linkAlbum': 'Połącz album',
+ 'memories.selectAlbum': 'Wybierz album Immich',
+ 'memories.selectAlbumMultiple': 'Wybierz album',
+ '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',
+ 'memories.saveRouteNotConfigured':
+ 'Trasa zapisu nie jest skonfigurowana dla tego dostawcy',
+ 'memories.testRouteNotConfigured':
+ 'Trasa testowa nie jest skonfigurowana dla tego dostawcy',
+ 'memories.fillRequiredFields': 'Proszę wypełnić wszystkie wymagane pola',
+};
+export default memories;
diff --git a/shared/src/i18n/pl/nav.ts b/shared/src/i18n/pl/nav.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9f6e46166e5da57db8837f95778069c759f91b1f
--- /dev/null
+++ b/shared/src/i18n/pl/nav.ts
@@ -0,0 +1,20 @@
+import type { TranslationStrings } from '../types';
+
+const nav: TranslationStrings = {
+ 'nav.trip': 'Podróż',
+ 'nav.share': 'Udostępnij',
+ 'nav.settings': 'Ustawienia',
+ 'nav.admin': 'Admin',
+ 'nav.logout': 'Wyloguj',
+ 'nav.lightMode': 'Tryb jasny',
+ 'nav.darkMode': 'Tryb ciemny',
+ 'nav.autoMode': 'Tryb automatyczny',
+ 'nav.administrator': 'Administrator',
+ 'nav.myTrips': 'Moje podróże',
+ 'nav.profile': 'Profil',
+ 'nav.bottomSettings': 'Ustawienia',
+ 'nav.bottomAdmin': 'Ustawienia administratora',
+ 'nav.bottomLogout': 'Wyloguj się',
+ 'nav.bottomAdminBadge': 'Administrator',
+};
+export default nav;
diff --git a/shared/src/i18n/pl/notif.ts b/shared/src/i18n/pl/notif.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8d3d1d1c211afef8e648bf492fd630ae898f952b
--- /dev/null
+++ b/shared/src/i18n/pl/notif.ts
@@ -0,0 +1,43 @@
+import type { TranslationStrings } from '../types';
+
+const notif: TranslationStrings = {
+ 'notif.test.title': '[Test] Powiadomienie',
+ 'notif.test.simple.text': 'To jest proste powiadomienie testowe.',
+ 'notif.test.boolean.text': 'Czy akceptujesz to powiadomienie testowe?',
+ 'notif.test.navigate.text': 'Kliknij poniżej, aby przejść do pulpitu.',
+ 'notif.trip_invite.title': 'Zaproszenie do podróży',
+ 'notif.trip_invite.text': '{actor} zaprosił Cię do {trip}',
+ 'notif.booking_change.title': 'Rezerwacja zaktualizowana',
+ 'notif.booking_change.text': '{actor} zaktualizował rezerwację w {trip}',
+ 'notif.trip_reminder.title': 'Przypomnienie o podróży',
+ 'notif.trip_reminder.text': 'Twoja podróż {trip} zbliża się!',
+ 'notif.todo_due.title': 'Zadanie z terminem',
+ 'notif.todo_due.text': '{todo} w {trip} — termin {due}',
+ 'notif.vacay_invite.title': 'Zaproszenie Vacay Fusion',
+ 'notif.vacay_invite.text':
+ '{actor} zaprosił Cię do połączenia planów urlopowych',
+ 'notif.photos_shared.title': 'Zdjęcia udostępnione',
+ 'notif.photos_shared.text':
+ '{actor} udostępnił {count} zdjęcie/zdjęcia w {trip}',
+ 'notif.collab_message.title': 'Nowa wiadomość',
+ 'notif.collab_message.text': '{actor} wysłał wiadomość w {trip}',
+ 'notif.packing_tagged.title': 'Zadanie pakowania',
+ 'notif.packing_tagged.text': '{actor} przypisał Cię do {category} w {trip}',
+ 'notif.version_available.title': 'Nowa wersja dostępna',
+ 'notif.version_available.text': 'TREK {version} jest teraz dostępny',
+ 'notif.action.view_trip': 'Zobacz podróż',
+ 'notif.action.view_collab': 'Zobacz wiadomości',
+ 'notif.action.view_packing': 'Zobacz pakowanie',
+ 'notif.action.view_photos': 'Zobacz zdjęcia',
+ 'notif.action.view_vacay': 'Zobacz Vacay',
+ 'notif.action.view_admin': 'Przejdź do admina',
+ 'notif.action.view': 'Zobacz',
+ 'notif.action.accept': 'Akceptuj',
+ 'notif.action.decline': 'Odrzuć',
+ 'notif.generic.title': 'Powiadomienie',
+ 'notif.generic.text': 'Masz nowe powiadomienie',
+ 'notif.dev.unknown_event.title': '[DEV] Nieznane zdarzenie',
+ 'notif.dev.unknown_event.text':
+ 'Typ zdarzenia "{event}" nie jest zarejestrowany w EVENT_NOTIFICATION_CONFIG',
+};
+export default notif;
diff --git a/shared/src/i18n/pl/notifications.ts b/shared/src/i18n/pl/notifications.ts
new file mode 100644
index 0000000000000000000000000000000000000000..068fc02b3b6dce2de77d5668c6404626b10af782
--- /dev/null
+++ b/shared/src/i18n/pl/notifications.ts
@@ -0,0 +1,36 @@
+import type { TranslationStrings } from '../types';
+
+const notifications: TranslationStrings = {
+ 'notifications.title': 'Powiadomienia',
+ 'notifications.markAllRead': 'Oznacz wszystkie jako przeczytane',
+ 'notifications.deleteAll': 'Usuń wszystkie',
+ 'notifications.showAll': 'Pokaż wszystkie',
+ 'notifications.empty': 'Brak powiadomień',
+ 'notifications.emptyDescription': 'Jesteś na bieżąco!',
+ 'notifications.all': 'Wszystkie',
+ 'notifications.unreadOnly': 'Nieprzeczytane',
+ 'notifications.markRead': 'Oznacz jako przeczytane',
+ 'notifications.markUnread': 'Oznacz jako nieprzeczytane',
+ 'notifications.delete': 'Usuń',
+ 'notifications.system': 'System',
+ 'notifications.synologySessionCleared.title': 'Synology Photos rozłączone',
+ 'notifications.synologySessionCleared.text':
+ 'Twój serwer lub konto zostało zmienione — przejdź do Ustawień, aby ponownie przetestować połączenie.',
+ 'notifications.versionAvailable.title': 'Dostępna aktualizacja',
+ 'notifications.versionAvailable.text': 'TREK {version} jest już dostępny.',
+ 'notifications.versionAvailable.button': 'Zobacz szczegóły',
+ '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 notifications;
diff --git a/shared/src/i18n/pl/oauth.ts b/shared/src/i18n/pl/oauth.ts
new file mode 100644
index 0000000000000000000000000000000000000000..42b0601835c5c2a1f7faaf99e9a41fb770cbb121
--- /dev/null
+++ b/shared/src/i18n/pl/oauth.ts
@@ -0,0 +1,123 @@
+import type { TranslationStrings } from '../types';
+
+const oauth: TranslationStrings = {
+ 'oauth.scope.group.trips': 'Podróże',
+ 'oauth.scope.group.places': 'Miejsca',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'Pakowanie',
+ 'oauth.scope.group.todos': 'Zadania',
+ 'oauth.scope.group.budget': 'Budżet',
+ 'oauth.scope.group.reservations': 'Rezerwacje',
+ 'oauth.scope.group.collab': 'Współpraca',
+ 'oauth.scope.group.notifications': 'Powiadomienia',
+ 'oauth.scope.group.vacay': 'Urlop',
+ 'oauth.scope.group.geo': 'Geo',
+ 'oauth.scope.group.weather': 'Pogoda',
+ 'oauth.scope.group.journey': 'Dziennik podróży',
+ 'oauth.scope.trips:read.label': 'Przeglądaj podróże i itineraria',
+ 'oauth.scope.trips:read.description':
+ 'Odczytuj podróże, dni, notatki i członków',
+ 'oauth.scope.trips:write.label': 'Edytuj podróże i itineraria',
+ 'oauth.scope.trips:write.description':
+ 'Twórz i aktualizuj podróże, dni, notatki oraz zarządzaj członkami',
+ 'oauth.scope.trips:delete.label': 'Usuń podróże',
+ 'oauth.scope.trips:delete.description':
+ 'Trwale usuń całe podróże — ta akcja jest nieodwracalna',
+ 'oauth.scope.trips:share.label': 'Zarządzaj linkami udostępniania',
+ 'oauth.scope.trips:share.description':
+ 'Twórz, aktualizuj i unieważniaj publiczne linki udostępniania',
+ 'oauth.scope.places:read.label': 'Przeglądaj miejsca i dane mapy',
+ 'oauth.scope.places:read.description':
+ 'Odczytuj miejsca, przypisania dni, tagi i kategorie',
+ 'oauth.scope.places:write.label': 'Zarządzaj miejscami',
+ 'oauth.scope.places:write.description':
+ 'Twórz, aktualizuj i usuń miejsca, przypisania i tagi',
+ 'oauth.scope.atlas:read.label': 'Przeglądaj Atlas',
+ 'oauth.scope.atlas:read.description':
+ 'Odczytuj odwiedzone kraje, regiony i listę marzeń',
+ 'oauth.scope.atlas:write.label': 'Zarządzaj Atlasem',
+ 'oauth.scope.atlas:write.description':
+ 'Oznaczaj kraje i regiony jako odwiedzone, zarządzaj listą marzeń',
+ 'oauth.scope.packing:read.label': 'Przeglądaj listy pakowania',
+ 'oauth.scope.packing:read.description':
+ 'Odczytuj przedmioty, torby i przypisania kategorii',
+ 'oauth.scope.packing:write.label': 'Zarządzaj listami pakowania',
+ 'oauth.scope.packing:write.description':
+ 'Dodawaj, aktualizuj, usuwaj, zaznaczaj i porządkuj przedmioty i torby',
+ 'oauth.scope.todos:read.label': 'Przeglądaj listy zadań',
+ 'oauth.scope.todos:read.description':
+ 'Odczytuj zadania podróży i przypisania kategorii',
+ 'oauth.scope.todos:write.label': 'Zarządzaj listami zadań',
+ 'oauth.scope.todos:write.description':
+ 'Twórz, aktualizuj, zaznaczaj, usuwaj i porządkuj zadania',
+ 'oauth.scope.budget:read.label': 'Przeglądaj budżet',
+ 'oauth.scope.budget:read.description':
+ 'Odczytuj pozycje budżetu i zestawienie wydatków',
+ 'oauth.scope.budget:write.label': 'Zarządzaj budżetem',
+ 'oauth.scope.budget:write.description':
+ 'Twórz, aktualizuj i usuń pozycje budżetu',
+ 'oauth.scope.reservations:read.label': 'Przeglądaj rezerwacje',
+ 'oauth.scope.reservations:read.description':
+ 'Odczytuj rezerwacje i szczegóły zakwaterowania',
+ 'oauth.scope.reservations:write.label': 'Zarządzaj rezerwacjami',
+ 'oauth.scope.reservations:write.description':
+ 'Twórz, aktualizuj, usuwaj i porządkuj rezerwacje',
+ 'oauth.scope.collab:read.label': 'Przeglądaj współpracę',
+ 'oauth.scope.collab:read.description':
+ 'Odczytuj notatki, ankiety i wiadomości',
+ 'oauth.scope.collab:write.label': 'Zarządzaj współpracą',
+ 'oauth.scope.collab:write.description':
+ 'Twórz, aktualizuj i usuń notatki, ankiety i wiadomości',
+ 'oauth.scope.notifications:read.label': 'Przeglądaj powiadomienia',
+ 'oauth.scope.notifications:read.description':
+ 'Odczytuj powiadomienia i liczby nieprzeczytanych',
+ 'oauth.scope.notifications:write.label': 'Zarządzaj powiadomieniami',
+ 'oauth.scope.notifications:write.description':
+ 'Oznaczaj powiadomienia jako przeczytane i odpowiadaj na nie',
+ 'oauth.scope.vacay:read.label': 'Przeglądaj plany urlopowe',
+ 'oauth.scope.vacay:read.description':
+ 'Odczytuj dane planowania urlopu, wpisy i statystyki',
+ 'oauth.scope.vacay:write.label': 'Zarządzaj planami urlopowymi',
+ 'oauth.scope.vacay:write.description':
+ 'Twórz i zarządzaj wpisami urlopowymi, świętami i planami zespołu',
+ 'oauth.scope.geo:read.label': 'Mapy i geokodowanie',
+ 'oauth.scope.geo:read.description':
+ 'Wyszukuj miejsca, rozwiązuj adresy URL map i odwrotnie geokoduj współrzędne',
+ 'oauth.scope.weather:read.label': 'Prognozy pogody',
+ 'oauth.scope.weather:read.description':
+ 'Pobieraj prognozy pogody dla miejsc i dat podróży',
+ 'oauth.scope.journey:read.label': 'Przeglądaj dzienniki podróży',
+ 'oauth.scope.journey:read.description':
+ 'Odczytuj dzienniki podróży, wpisy i listę współautorów',
+ 'oauth.scope.journey:write.label': 'Zarządzaj dziennikami podróży',
+ 'oauth.scope.journey:write.description':
+ 'Twórz, aktualizuj i usuwaj dzienniki podróży oraz ich wpisy',
+ 'oauth.scope.journey:share.label': 'Zarządzaj linkami dzienników podróży',
+ 'oauth.scope.journey:share.description':
+ 'Twórz, aktualizuj i unieważniaj publiczne linki udostępniania dzienników podróży',
+ 'oauth.authorize.authorizing': 'Authorizing…', // en-fallback
+ 'oauth.authorize.loading': 'Loading…', // en-fallback
+ 'oauth.authorize.errorTitle': 'Authorization Error', // en-fallback
+ 'oauth.authorize.loginTitle': 'Sign in to continue', // en-fallback
+ 'oauth.authorize.loginDescription':
+ '{client} wants access to your TREK account. Please sign in first.', // en-fallback
+ 'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
+ 'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
+ 'oauth.authorize.requestDescription':
+ 'This application is requesting access to your TREK account.', // en-fallback
+ 'oauth.authorize.trustNote':
+ 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
+ 'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
+ 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
+ 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
+ 'oauth.authorize.deny': 'Deny', // en-fallback
+ 'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
+ 'oauth.authorize.permissionsRequested': 'Permissions requested', // en-fallback
+ 'oauth.authorize.alwaysIncluded': 'Always included', // en-fallback
+ 'oauth.authorize.alwaysTool.listTrips':
+ 'List your trips so the AI can discover trip IDs', // en-fallback
+ 'oauth.authorize.alwaysTool.getTripSummary':
+ 'Read a trip overview needed to use any other tool', // en-fallback
+};
+export default oauth;
diff --git a/shared/src/i18n/pl/packing.ts b/shared/src/i18n/pl/packing.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f277ace39d8cefd24d7318e5a595275e8c2593f9
--- /dev/null
+++ b/shared/src/i18n/pl/packing.ts
@@ -0,0 +1,186 @@
+import type { TranslationStrings } from '../types';
+
+const packing: TranslationStrings = {
+ 'packing.title': 'Lista pakowania',
+ 'packing.empty': 'Lista pakowania jest pusta',
+ 'packing.import': 'Importuj',
+ 'packing.importTitle': 'Importuj listę pakowania',
+ 'packing.importHint':
+ 'Jedna pozycja w wierszu. Format: kategoria, nazwa, waga w gramach (opcjonalnie), Torba (opcjonalnie), checked/unchecked (opcjonalnie)',
+ 'packing.importPlaceholder':
+ 'Higiena, Szczoteczka do zębów\nOdzież, Koszulki, 200\nDokumenty, Paszport, , Podręczny\nElektronika, Ładowarka, 50, Walizka, checked',
+ 'packing.importCsv': 'Załaduj CSV/TXT',
+ 'packing.importAction': 'Importuj {count}',
+ 'packing.importSuccess': '{count} pozycji zaimportowanych',
+ 'packing.importError': 'Import nie powiódł się',
+ 'packing.importEmpty': 'Brak pozycji do zaimportowania',
+ 'packing.progress': '{packed} z {total} spakowanych ({percent}%)',
+ 'packing.clearChecked': 'Usuń {count} spakowanych',
+ 'packing.clearCheckedShort': 'Usuń {count}',
+ 'packing.suggestions': 'Sugestie',
+ 'packing.suggestionsTitle': 'Dodaj sugestie',
+ 'packing.allSuggested': 'Dodano wszystkie sugestie',
+ 'packing.allPacked': 'Wszystko spakowane!',
+ 'packing.addPlaceholder': 'Dodaj nowy przedmiot...',
+ 'packing.categoryPlaceholder': 'Kategoria...',
+ 'packing.filterAll': 'Wszystkie',
+ 'packing.filterOpen': 'Do spakowania',
+ 'packing.filterDone': 'Spakowane',
+ 'packing.emptyTitle': 'Lista pakowania jest pusta',
+ 'packing.emptyHint': 'Dodaj przedmioty lub użyj sugestii',
+ 'packing.emptyFiltered': 'Brak przedmiotów pasujących do filtra',
+ 'packing.menuRename': 'Zmień nazwę',
+ 'packing.menuCheckAll': 'Zaznacz wszystko',
+ 'packing.menuUncheckAll': 'Odznacz wszystko',
+ 'packing.menuDeleteCat': 'Usuń kategorię',
+ 'packing.saveAsTemplate': 'Zapisz jako szablon',
+ 'packing.templateName': 'Nazwa szablonu',
+ 'packing.templateSaved': 'Lista pakowania zapisana jako szablon',
+ 'packing.noMembers': 'Brak członków podróży',
+ 'packing.addItem': 'Dodaj przedmiot',
+ 'packing.addItemPlaceholder': 'Nazwa przedmiotu...',
+ 'packing.addCategory': 'Dodaj kategorię',
+ 'packing.newCategoryPlaceholder': 'Nazwa kategorii (np. Odzież)',
+ 'packing.applyTemplate': 'Zastosuj szablon',
+ 'packing.template': 'Szablon',
+ 'packing.templateApplied': '{count} przedmiotów dodanych z szablonu',
+ 'packing.templateError': 'Nie udało się zastosować szablonu',
+ 'packing.bags': 'Torby',
+ 'packing.noBag': 'Nieprzypisane',
+ 'packing.totalWeight': 'Waga całkowita',
+ 'packing.bagName': 'Nazwa torby...',
+ 'packing.addBag': 'Dodaj torbę',
+ 'packing.changeCategory': 'Zmień kategorię',
+ 'packing.confirm.clearChecked':
+ 'Czy na pewno chcesz usunąć {count} spakowanych przedmiotów?',
+ 'packing.confirm.deleteCat':
+ 'Czy na pewno chcesz usunąć kategorię "{name}" z {count} przedmiotami?',
+ 'packing.defaultCategory': 'Inne',
+ 'packing.toast.saveError': 'Nie udało się zapisać',
+ 'packing.toast.deleteError': 'Nie udało się usunąć',
+ 'packing.toast.renameError': 'Nie udało się zmienić nazwy',
+ 'packing.toast.addError': 'Nie udało się dodać',
+ 'packing.suggestions.items': [
+ {
+ name: 'Paszport',
+ category: 'Dokumenty',
+ },
+ {
+ name: 'Dowód osobisty',
+ category: 'Dokumenty',
+ },
+ {
+ name: 'Ubezpieczenie turystyczne',
+ category: 'Dokumenty',
+ },
+ {
+ name: 'Bilety lotnicze',
+ category: 'Dokumenty',
+ },
+ {
+ name: 'Karta kredytowa',
+ category: 'Finanse',
+ },
+ {
+ name: 'Gotówka',
+ category: 'Finanse',
+ },
+ {
+ name: 'Wiza',
+ category: 'Dokumenty',
+ },
+ {
+ name: 'Koszulki',
+ category: 'Odzież',
+ },
+ {
+ name: 'Spodnie',
+ category: 'Odzież',
+ },
+ {
+ name: 'Bielizna',
+ category: 'Odzież',
+ },
+ {
+ name: 'Skarpetki',
+ category: 'Odzież',
+ },
+ {
+ name: 'Kurtka',
+ category: 'Odzież',
+ },
+ {
+ name: 'Piżama',
+ category: 'Odzież',
+ },
+ {
+ name: 'Strój kąpielowy',
+ category: 'Odzież',
+ },
+ {
+ name: 'Kurtka przeciwdeszczowa',
+ category: 'Odzież',
+ },
+ {
+ name: 'Wygodne buty',
+ category: 'Obuwie',
+ },
+ {
+ name: 'Szczoteczka do zębów',
+ category: 'Higiena',
+ },
+ {
+ name: 'Pasta do zębów',
+ category: 'Higiena',
+ },
+ {
+ name: 'Szampon',
+ category: 'Higiena',
+ },
+ {
+ name: 'Dezodorant',
+ category: 'Higiena',
+ },
+ {
+ name: 'Krem z filtrem',
+ category: 'Higiena',
+ },
+ {
+ name: 'Maszynka do golenia',
+ category: 'Higiena',
+ },
+ {
+ name: 'Ładowarka',
+ category: 'Elektronika',
+ },
+ {
+ name: 'Powerbank',
+ category: 'Elektronika',
+ },
+ {
+ name: 'Słuchawki',
+ category: 'Elektronika',
+ },
+ {
+ name: 'Adapter podróżny',
+ category: 'Elektronika',
+ },
+ {
+ name: 'Aparat',
+ category: 'Elektronika',
+ },
+ {
+ name: 'Leki',
+ category: 'Zdrowie',
+ },
+ {
+ name: 'Plastry',
+ category: 'Zdrowie',
+ },
+ {
+ name: 'Środek dezynfekujący',
+ category: 'Zdrowie',
+ },
+ ],
+};
+export default packing;
diff --git a/shared/src/i18n/pl/pdf.ts b/shared/src/i18n/pl/pdf.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3b9f89e6436dea33c32c59f866530643be1d890c
--- /dev/null
+++ b/shared/src/i18n/pl/pdf.ts
@@ -0,0 +1,10 @@
+import type { TranslationStrings } from '../types';
+
+const pdf: TranslationStrings = {
+ 'pdf.travelPlan': 'Plan podróży',
+ 'pdf.planned': 'Zaplanowane',
+ 'pdf.costLabel': 'Koszt w EUR',
+ 'pdf.preview': 'Podgląd PDF',
+ 'pdf.saveAsPdf': 'Zapisz jako PDF',
+};
+export default pdf;
diff --git a/shared/src/i18n/pl/perm.ts b/shared/src/i18n/pl/perm.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2c94d9b87ae753be57d63f31f32e87a0f3660dbd
--- /dev/null
+++ b/shared/src/i18n/pl/perm.ts
@@ -0,0 +1,51 @@
+import type { TranslationStrings } from '../types';
+
+const perm: TranslationStrings = {
+ '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',
+};
+export default perm;
diff --git a/shared/src/i18n/pl/photos.ts b/shared/src/i18n/pl/photos.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bb0f8dd9095d7b98473853fa62df036ec157f582
--- /dev/null
+++ b/shared/src/i18n/pl/photos.ts
@@ -0,0 +1,25 @@
+import type { TranslationStrings } from '../types';
+
+const photos: TranslationStrings = {
+ 'photos.title': 'Zdjęcia',
+ 'photos.subtitle': '{count} zdjęć dla {trip}',
+ 'photos.dropHere': 'Przeciągnij zdjęcia tutaj...',
+ 'photos.dropHereActive': 'Przeciągnij zdjęcia tutaj',
+ 'photos.captionForAll': 'Podpis (dla wszystkich)',
+ 'photos.captionPlaceholder': 'Opcjonalny podpis...',
+ 'photos.addCaption': 'Dodaj podpis...',
+ 'photos.allDays': 'Wszystkie dni',
+ 'photos.noPhotos': 'Brak zdjęć',
+ 'photos.uploadHint': 'Prześlij zdjęcia z podróży',
+ 'photos.clickToSelect': 'lub kliknij, aby wybrać',
+ 'photos.linkPlace': 'Połącz z miejscem',
+ 'photos.noPlace': 'Brak miejsca',
+ 'photos.uploadN': 'Prześlij {n} zdjęć',
+ 'photos.linkDay': 'Połącz dzień',
+ 'photos.noDay': 'Brak dnia',
+ 'photos.dayLabel': 'Dzień {number}',
+ 'photos.photoSelected': 'Zdjęcie wybrane',
+ 'photos.photosSelected': 'Zdjęcia wybrane',
+ 'photos.fileTypeHint': 'JPG, PNG, WebP · maks. 10 MB · do 30 zdjęć',
+};
+export default photos;
diff --git a/shared/src/i18n/pl/places.ts b/shared/src/i18n/pl/places.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6a6ae6918add17f4ef2d644f3d085b51cb514fdc
--- /dev/null
+++ b/shared/src/i18n/pl/places.ts
@@ -0,0 +1,97 @@
+import type { TranslationStrings } from '../types';
+
+const places: TranslationStrings = {
+ 'places.addPlace': 'Dodaj miejsce/atrakcję',
+ 'places.importFile': 'Importuj plik',
+ 'places.sidebarDrop': 'Upuść, aby zaimportować',
+ 'places.importFileHint':
+ 'Importuj pliki .gpx, .kml lub .kmz z narzędzi takich jak Google My Maps, Google Earth lub tracker GPS.',
+ 'places.importFileDropHere':
+ 'Kliknij, aby wybrać plik lub przeciągnij i upuść tutaj',
+ 'places.importFileDropActive': 'Upuść plik, aby go wybrać',
+ 'places.importFileUnsupported':
+ 'Nieobsługiwany typ pliku. Użyj .gpx, .kml lub .kmz.',
+ 'places.importFileTooLarge':
+ 'Plik jest za duży. Maksymalny rozmiar przesyłania to {maxMb} MB.',
+ 'places.importFileError': 'Import nie powiódł się',
+ 'places.importAllSkipped': 'Wszystkie miejsca były już w podróży.',
+ 'places.gpxImported': '{count} miejsc zaimportowanych z GPX',
+ 'places.gpxImportTypes': 'Co chcesz zaimportować?',
+ 'places.gpxImportWaypoints': 'Punkty trasy',
+ 'places.gpxImportRoutes': 'Trasy',
+ 'places.gpxImportTracks': 'Trasy GPS (ze śladem)',
+ 'places.gpxImportNoneSelected': 'Wybierz co najmniej jeden typ do importu.',
+ 'places.kmlImportTypes': 'Co chcesz zaimportować?',
+ 'places.kmlImportPoints': 'Punkty (Placemarks)',
+ 'places.kmlImportPaths': 'Ścieżki (LineStrings)',
+ 'places.kmlImportNoneSelected': 'Wybierz co najmniej jeden typ.',
+ 'places.selectionCount': '{count} zaznaczono',
+ 'places.deleteSelected': 'Usuń wybrane',
+ 'places.kmlKmzImported': 'Zaimportowano {count} miejsc z KMZ/KML',
+ 'places.urlResolved': 'Miejsce zaimportowane z URL',
+ 'places.kmlKmzSummaryValues':
+ 'Placemarks: {total} • Zaimportowano: {created} • Pominięto: {skipped}',
+ 'places.importGoogleList': 'Lista Google',
+ 'places.assignToDay': 'Do którego dnia dodać?',
+ 'places.all': 'Wszystkie',
+ 'places.unplanned': 'Niezaplanowane',
+ 'places.filterTracks': 'Trasy',
+ 'places.search': 'Szukaj miejsc...',
+ 'places.allCategories': 'Wszystkie kategorie',
+ 'places.categoriesSelected': 'kategorii',
+ 'places.clearFilter': 'Wyczyść filtr',
+ 'places.count': '{count} miejsc',
+ 'places.countSingular': '1 miejsce',
+ 'places.allPlanned': 'Wszystkie miejsca są zaplanowane',
+ 'places.noneFound': 'Nie znaleziono miejsc',
+ 'places.editPlace': 'Edytuj miejsce',
+ 'places.formName': 'Nazwa',
+ 'places.formNamePlaceholder': 'np. Wieża Eiffla',
+ 'places.formDescription': 'Opis',
+ 'places.formDescriptionPlaceholder': 'Krótki opis...',
+ 'places.formAddress': 'Adres',
+ 'places.formAddressPlaceholder': 'Ulica, miasto, kraj',
+ 'places.formLat': 'Szerokość (np. 48.8566)',
+ 'places.formLng': 'Długość (np. 2.3522)',
+ 'places.formCategory': 'Kategoria',
+ 'places.noCategory': 'Brak kategorii',
+ 'places.categoryNamePlaceholder': 'Nazwa kategorii',
+ 'places.formTime': 'Godzina',
+ 'places.startTime': 'Początek',
+ 'places.endTime': 'Koniec',
+ 'places.endTimeBeforeStart':
+ 'Godzina zakończenia jest przed godziną rozpoczęcia',
+ 'places.timeCollision': 'Nakładanie się godzin z:',
+ 'places.formWebsite': 'Strona internetowa',
+ 'places.formNotes': 'Notatki',
+ 'places.formNotesPlaceholder': 'Osobiste notatki...',
+ 'places.formReservation': 'Rezerwacja',
+ 'places.reservationNotesPlaceholder':
+ 'Notatki z rezerwacji, numer potwierdzenia...',
+ 'places.mapsSearchPlaceholder': 'Szukaj miejsc...',
+ 'places.mapsSearchError': 'Nie udało się wyszukać miejsca.',
+ 'places.loadingDetails': 'Ładowanie szczegółów miejsca…',
+ 'places.osmHint':
+ 'Korzystając z OpenStreetMap (brak zdjęć, godzin otwarcia czy ocen). Dodaj klucz API Google w ustawieniach aby uzyskać pełne dane.',
+ 'places.osmActive':
+ 'Szukaj przez OpenStreetMap (brak zdjęć, ocen czy godzin otwarcia). Dodaj klucz API Google w ustawieniach aby uzyskać pełne dane.',
+ 'places.categoryCreateError': 'Nie udało się utworzyć kategorii',
+ 'places.nameRequired': 'Proszę podać nazwę',
+ 'places.saveError': 'Nie udało się zapisać',
+ 'places.duplicateExists': "'{name}' jest już w tej podróży.",
+ 'places.addAnyway': 'Dodaj mimo to',
+ 'places.importNaverList': 'Lista Naver',
+ 'places.importList': 'Import listy',
+ 'places.googleListHint': 'Wklej link do listy Google Maps.',
+ 'places.googleListImported': 'Zaimportowano {count} miejsc',
+ 'places.googleListError': 'Nie udało się zaimportować listy',
+ 'places.naverListHint':
+ 'Wklej link do udostępnionej listy Naver Maps, aby zaimportować wszystkie miejsca.',
+ 'places.naverListImported': 'Zaimportowano {count} miejsc z "{list}"',
+ 'places.naverListError': 'Nie udało się zaimportować listy Naver Maps',
+ 'places.viewDetails': 'Zobacz szczegóły',
+ 'places.enrichOnImport': 'Wzbogać miejsca przez Google',
+ 'places.enrichOnImportHint':
+ 'Wyszukuje każde zaimportowane miejsce, aby dodać zdjęcia, adres i dane kontaktowe. Wymaga klucza Google Maps.',
+};
+export default places;
diff --git a/shared/src/i18n/pl/planner.ts b/shared/src/i18n/pl/planner.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3c6babb0e0cc9935536d94af0f8f506f4713898c
--- /dev/null
+++ b/shared/src/i18n/pl/planner.ts
@@ -0,0 +1,69 @@
+import type { TranslationStrings } from '../types';
+
+const planner: TranslationStrings = {
+ 'planner.places': 'Miejsca',
+ 'planner.bookings': 'Rezerwacje',
+ 'planner.packingList': 'Lista pakowania',
+ 'planner.documents': 'Dokumenty',
+ 'planner.dayPlan': 'Plan',
+ 'planner.reservations': 'Rezerwacje',
+ 'planner.minTwoPlaces':
+ 'Wymagane są przynajmniej dwa miejsca ze współrzędnymi',
+ 'planner.noGeoPlaces': 'Brak miejsc ze współrzędnymi',
+ 'planner.routeCalculated': 'Trasa została obliczona',
+ 'planner.routeCalcFailed': 'Nie udało się obliczyć trasy',
+ 'planner.routeError': 'Błąd obliczania trasy',
+ 'planner.icsExportFailed': 'Eksport ICS nie powiódł się',
+ 'planner.routeOptimized': 'Trasa została zoptymalizowana',
+ 'planner.reservationUpdated': 'Rezerwacja została zaktualizowana',
+ 'planner.reservationAdded': 'Rezerwacja została dodana',
+ 'planner.confirmDeleteReservation': 'Usunąć rezerwację?',
+ 'planner.reservationDeleted': 'Rezerwacja została usunięta',
+ 'planner.days': 'Dni',
+ 'planner.allPlaces': 'Wszystkie miejsca',
+ 'planner.totalPlaces': '{n} miejsc ogółem',
+ 'planner.noDaysPlanned': 'Nie zaplanowano jeszcze dni',
+ 'planner.editTrip': 'Edytuj podróż →',
+ 'planner.placeOne': '1 miejsce',
+ 'planner.placeN': '{n} miejsc',
+ 'planner.addNote': 'Dodaj notatkę',
+ 'planner.noEntries': 'Brak wpisów dla tego dnia',
+ 'planner.addPlace': 'Dodaj miejsce/atrakcję',
+ 'planner.addPlaceShort': '+ Dodaj miejsce/atrakcję',
+ 'planner.resPending': 'Rezerwacja oczekująca · ',
+ 'planner.resConfirmed': 'Rezerwacja potwierdzona · ',
+ 'planner.notePlaceholder': 'Notatka…',
+ 'planner.noteTimePlaceholder': 'Godzina (opcjonalnie)',
+ 'planner.noteExamplePlaceholder':
+ 'np. S3 o 14:30 z dworca centralnego, prom z molo 7, przerwa na lunch…',
+ 'planner.totalCost': 'Całkowity koszt',
+ 'planner.searchPlaces': 'Szukaj miejsc…',
+ 'planner.allCategories': 'Wszystkie kategorie',
+ 'planner.noPlacesFound': 'Nie znaleziono miejsc',
+ 'planner.addFirstPlace': 'Dodaj pierwsze miejsce',
+ 'planner.noReservations': 'Brak rezerwacji',
+ 'planner.addFirstReservation': 'Dodaj pierwszą rezerwację',
+ 'planner.new': 'Nowy',
+ 'planner.addToDay': '+ Dzień',
+ 'planner.calculating': 'Obliczanie…',
+ 'planner.route': 'Trasa',
+ 'planner.optimize': 'Optymalizuj',
+ 'planner.openGoogleMaps': 'Otwórz w Google Maps',
+ 'planner.selectDayHint':
+ 'Wybierz dzień z listy po lewej, aby zobaczyć jego plan',
+ 'planner.noPlacesForDay': 'Brak miejsc dla tego dnia',
+ 'planner.addPlacesLink': 'Dodaj miejsca →',
+ 'planner.minTotal': 'min. łącznie',
+ 'planner.noReservation': 'Brak rezerwacji',
+ 'planner.removeFromDay': 'Usuń z dnia',
+ 'planner.addToThisDay': 'Dodaj do dnia',
+ 'planner.overview': 'Przegląd',
+ 'planner.noDays': 'Brak dni',
+ 'planner.editTripToAddDays': 'Edytuj podróż, aby dodać dni',
+ 'planner.dayCount': '{n} dni',
+ 'planner.clickToUnlock': 'Kliknij, aby odblokować',
+ 'planner.keepPosition': 'Zachowaj pozycję podczas optymalizacji trasy',
+ 'planner.dayDetails': 'Szczegóły dnia',
+ 'planner.dayN': 'Dzień {n}',
+};
+export default planner;
diff --git a/shared/src/i18n/pl/register.ts b/shared/src/i18n/pl/register.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1a65ff03b7275ad97fb30b8118d7758bc38b1968
--- /dev/null
+++ b/shared/src/i18n/pl/register.ts
@@ -0,0 +1,26 @@
+import type { TranslationStrings } from '../types';
+
+const register: TranslationStrings = {
+ 'register.passwordMismatch': 'Hasła nie są identyczne',
+ 'register.passwordTooShort': 'Hasło musi mieć co najmniej 6 znaków',
+ 'register.failed': 'Rejestracja nie powiodła się',
+ 'register.getStarted': 'Rozpocznij',
+ 'register.subtitle':
+ 'Utwórz konto i zacznij planować swoje wymarzone podróże.',
+ 'register.feature1': 'Nieograniczone plany podróży',
+ 'register.feature2': 'Interaktywna mapa',
+ 'register.feature3': 'Zarządzaj miejscami i kategoriami',
+ 'register.feature4': 'Śledź rezerwacje',
+ 'register.feature5': 'Twórz listy pakowania',
+ 'register.feature6': 'Przechowuj zdjęcia i pliki',
+ 'register.createAccount': 'Utwórz konto',
+ 'register.startPlanning': 'Zacznij planować podróż',
+ 'register.minChars': 'Min. 6 znaków',
+ 'register.confirmPassword': 'Potwierdź hasło',
+ 'register.repeatPassword': 'Powtórz hasło',
+ 'register.registering': 'Rejestrowanie...',
+ 'register.register': 'Zarejestruj się',
+ 'register.hasAccount': 'Masz już konto?',
+ 'register.signIn': 'Zaloguj się',
+};
+export default register;
diff --git a/shared/src/i18n/pl/reservations.ts b/shared/src/i18n/pl/reservations.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3a20a7a3531fa7d1f3545584bc8403f37d2e1b1f
--- /dev/null
+++ b/shared/src/i18n/pl/reservations.ts
@@ -0,0 +1,163 @@
+import type { TranslationStrings } from '../types';
+
+const reservations: TranslationStrings = {
+ 'reservations.title': 'Rezerwacje',
+ 'reservations.empty': 'Brak rezerwacji',
+ 'reservations.emptyHint': 'Dodaj rezerwacje lotów, hoteli i innych',
+ 'reservations.add': 'Dodaj rezerwację',
+ 'reservations.addManual': 'Rezerwacja ręczna',
+ 'reservations.placeHint':
+ 'Wskazówka: Rezerwacje najlepiej tworzyć bezpośrednio z miejsca, aby powiązać je z planem dnia.',
+ 'reservations.confirmed': 'Potwierdzona',
+ 'reservations.pending': 'Oczekująca',
+ 'reservations.summary': '{confirmed} potwierdzonych, {pending} oczekujących',
+ 'reservations.fromPlan': 'Z planu',
+ 'reservations.showFiles': 'Pokaż pliki',
+ 'reservations.editTitle': 'Edytuj rezerwację',
+ 'reservations.status': 'Status',
+ 'reservations.datetime': 'Data i czas',
+ 'reservations.startTime': 'Godzina rozpoczęcia',
+ 'reservations.endTime': 'Godzina zakończenia',
+ 'reservations.date': 'Data',
+ 'reservations.time': 'Godzina',
+ 'reservations.timeAlt': 'Godzina (alternatywna, np. 19:30)',
+ 'reservations.notes': 'Notatki',
+ 'reservations.notesPlaceholder': 'Dodatkowe notatki...',
+ 'reservations.meta.airline': 'Linia lotnicza',
+ 'reservations.meta.flightNumber': 'Numer lotu',
+ 'reservations.meta.from': 'Skąd',
+ 'reservations.meta.to': 'Dokąd',
+ 'reservations.layover.route': 'Trasa',
+ 'reservations.layover.stop': 'Przystanek',
+ 'reservations.layover.addStop': 'Dodaj przystanek',
+ 'reservations.layover.connection': 'Połączenie',
+ 'reservations.layover.layover': 'Przesiadka',
+ 'reservations.meta.trainNumber': 'Numer pociągu',
+ 'reservations.meta.platform': 'Peron',
+ 'reservations.meta.seat': 'Miejsce',
+ 'reservations.meta.checkIn': 'Zameldowanie',
+ 'reservations.meta.checkInUntil': 'Check-in do',
+ 'reservations.meta.checkOut': 'Wymeldowanie',
+ 'reservations.meta.linkAccommodation': 'Zakwaterowanie',
+ 'reservations.meta.pickAccommodation': 'Link do zakwaterowania',
+ 'reservations.meta.noAccommodation': 'Brak',
+ 'reservations.meta.hotelPlace': 'Zakwaterowanie',
+ 'reservations.meta.pickHotel': 'Wybierz zakwaterowanie',
+ 'reservations.meta.fromDay': 'Od',
+ 'reservations.meta.toDay': 'Do',
+ 'reservations.meta.selectDay': 'Wybierz dzień',
+ 'reservations.type.flight': 'Lot',
+ 'reservations.type.hotel': 'Zakwaterowanie',
+ 'reservations.type.restaurant': 'Restauracja',
+ 'reservations.type.train': 'Pociąg',
+ 'reservations.type.car': 'Samochód',
+ 'reservations.needsReview': 'Sprawdź',
+ 'reservations.needsReviewHint':
+ 'Nie udało się automatycznie dopasować lotniska — potwierdź lokalizację.',
+ 'reservations.searchLocation': 'Szukaj stacji, portu, adresu...',
+ 'reservations.type.cruise': 'Rejs',
+ 'reservations.type.event': 'Wydarzenie',
+ 'reservations.type.tour': 'Wycieczka',
+ 'reservations.type.other': 'Inne',
+ 'reservations.type.bus': 'Autobus',
+ 'reservations.type.ferry': 'Prom',
+ 'reservations.type.bicycle': 'Rower',
+ 'reservations.type.taxi': 'Taksówka',
+ 'reservations.type.transport_other': 'Inne',
+ 'reservations.confirm.delete':
+ 'Czy na pewno chcesz usunąć rezerwację "{name}"?',
+ 'reservations.confirm.deleteTitle': 'Usunąć rezerwację?',
+ 'reservations.confirm.deleteBody':
+ 'Rezerwacja "{name}" zostanie trwale usunięta.',
+ 'reservations.toast.updated': 'Rezerwacja została zaktualizowana',
+ 'reservations.toast.removed': 'Rezerwacja została usunięta',
+ 'reservations.toast.fileUploaded': 'Plik został przesłany',
+ 'reservations.toast.uploadError': 'Nie udało się przesłać pliku',
+ 'reservations.newTitle': 'Nowa rezerwacja',
+ 'reservations.bookingType': 'Rodzaj rezerwacji',
+ 'reservations.titleLabel': 'Tytuł',
+ 'reservations.titlePlaceholder': 'np. Ryanair FR123, Hotel Dubaj, ...',
+ 'reservations.locationAddress': 'Lokalizacja / Adres',
+ 'reservations.locationPlaceholder': 'Adres, Lotnisko, Hotel...',
+ 'reservations.confirmationCode': 'Kod rezerwacji',
+ 'reservations.confirmationPlaceholder': 'np. ABC12345',
+ 'reservations.day': 'Dzień',
+ 'reservations.noDay': 'Brak dnia',
+ 'reservations.place': 'Miejsce',
+ 'reservations.noPlace': 'Brak miejsca',
+ 'reservations.pendingSave': 'zostanie zapisane...',
+ 'reservations.uploading': 'Przesyłanie...',
+ 'reservations.attachFile': 'Załącz plik',
+ 'reservations.linkExisting': 'Podlinkuj przesłany plik',
+ 'reservations.toast.saveError': 'Nie udało się zapisać',
+ 'reservations.toast.updateError': 'Nie udało się zaktualizować',
+ 'reservations.toast.deleteError': 'Nie udało się usunąć',
+ 'reservations.confirm.remove': 'Usunąć rezerwację "{name}"?',
+ 'reservations.linkAssignment': 'Przypisz do miejsca',
+ 'reservations.pickAssignment': 'Wybierz miejsce z planu...',
+ 'reservations.noAssignment': 'Brak przypisania (samodzielna)',
+ 'reservations.price': 'Cena',
+ 'reservations.budgetCategory': 'Kategoria budżetu',
+ 'reservations.budgetCategoryPlaceholder': 'np. Transport, Zakwaterowanie',
+ 'reservations.budgetCategoryAuto': 'Auto (na podstawie typu rezerwacji)',
+ 'reservations.budgetHint':
+ 'Wpis budżetowy zostanie automatycznie utworzony podczas zapisywania.',
+ 'reservations.departureDate': 'Wylot',
+ 'reservations.arrivalDate': 'Przylot',
+ 'reservations.departureTime': 'Godz. wylotu',
+ 'reservations.arrivalTime': 'Godz. przylotu',
+ 'reservations.pickupDate': 'Odbiór',
+ 'reservations.returnDate': 'Zwrot',
+ 'reservations.pickupTime': 'Godz. odbioru',
+ 'reservations.returnTime': 'Godz. zwrotu',
+ 'reservations.endDate': 'Data końca',
+ 'reservations.meta.departureTimezone': 'TZ wylotu',
+ 'reservations.meta.arrivalTimezone': 'TZ przylotu',
+ 'reservations.span.departure': 'Wylot',
+ 'reservations.span.arrival': 'Przylot',
+ 'reservations.span.inTransit': 'W tranzycie',
+ 'reservations.span.pickup': 'Odbiór',
+ 'reservations.span.return': 'Zwrot',
+ 'reservations.span.active': 'Aktywny',
+ 'reservations.span.start': 'Start',
+ 'reservations.span.end': 'Koniec',
+ 'reservations.span.ongoing': 'W trakcie',
+ 'reservations.validation.endBeforeStart':
+ 'Data/godzina zakończenia musi być późniejsza niż data/godzina rozpoczęcia',
+ 'reservations.addBooking': 'Dodaj rezerwację',
+ 'reservations.import.title': 'Importuj potwierdzenia rezerwacji',
+ 'reservations.import.cta': 'Importuj z pliku',
+ 'reservations.import.dropHere': 'Upuść pliki potwierdzeń rezerwacji tutaj lub kliknij, aby wybrać',
+ 'reservations.import.dropActive': 'Upuść pliki, aby zaimportować',
+ 'reservations.import.acceptedFormats': 'Akceptowane: EML, PDF, PKPass, HTML, TXT (maks. 10 MB każdy, do 5 plików)',
+ 'reservations.import.parsing': 'Przetwarzanie plików…',
+ 'reservations.import.previewHeading': 'Znaleziono {count} rezerwację/rezerwacje',
+ 'reservations.import.previewEmpty': 'Nie udało się wyodrębnić rezerwacji z przesłanych plików.',
+ 'reservations.import.removeItem': 'Usuń',
+ 'reservations.import.confirm': 'Importuj {count} rezerwację/rezerwacje',
+ 'reservations.import.back': 'Wstecz',
+ 'reservations.import.success': 'Zaimportowano {count} rezerwację/rezerwacje',
+ 'reservations.import.partialFailure': '{created} zaimportowano, {failed} nieudane',
+ 'reservations.import.error': 'Przetwarzanie nieudane. Upewnij się, że plik jest prawidłowym potwierdzeniem rezerwacji.',
+ 'reservations.import.unavailable': 'Import rezerwacji nie jest dostępny na tym serwerze.',
+ 'reservations.import.unsupportedFormat': 'Nieobsługiwany format pliku. Użyj EML, PDF, PKPass, HTML lub TXT.',
+ 'reservations.import.fileTooLarge': 'Plik „{name}" przekracza limit 10 MB.',
+ 'reservations.airtrail.title': 'Importuj z AirTrail',
+ 'reservations.airtrail.cta': 'AirTrail',
+ 'reservations.airtrail.synced': 'AirTrail',
+ 'reservations.airtrail.syncedHint': 'Zsynchronizowano z AirTrail — zmiany są synchronizowane w obie strony.',
+ 'reservations.airtrail.notSynced': 'Niezsynchronizowane',
+ 'reservations.airtrail.notSyncedHint': 'Ten lot został usunięty w AirTrail i nie jest już synchronizowany.',
+ 'reservations.airtrail.loadError': 'Nie udało się wczytać Twoich lotów z AirTrail.',
+ 'reservations.airtrail.imported': 'Zaimportowano {count} lot(y/ów)',
+ 'reservations.airtrail.skippedDuplicate': '{count} już w tej wyprawie, pominięto',
+ 'reservations.airtrail.nothingImported': 'Nic do zaimportowania.',
+ 'reservations.airtrail.importError': 'Import nieudany. Spróbuj ponownie.',
+ 'reservations.airtrail.undo': 'Importuj z AirTrail',
+ 'reservations.airtrail.alreadyImported': 'Zaimportowano',
+ 'reservations.airtrail.duringTrip': 'Podczas tej wyprawy',
+ 'reservations.airtrail.otherFlights': 'Inne loty',
+ 'reservations.airtrail.empty': 'Nie znaleziono lotów na Twoim koncie AirTrail.',
+ 'reservations.airtrail.importCta': 'Importuj {count}',
+};
+export default reservations;
diff --git a/shared/src/i18n/pl/settings.ts b/shared/src/i18n/pl/settings.ts
new file mode 100644
index 0000000000000000000000000000000000000000..43db4f7bb8567bf2b8d388c4f5a462b815ebde15
--- /dev/null
+++ b/shared/src/i18n/pl/settings.ts
@@ -0,0 +1,347 @@
+import type { TranslationStrings } from '../types';
+
+const settings: TranslationStrings = {
+ 'settings.title': 'Ustawienia',
+ 'settings.subtitle': 'Skonfiguruj swoje ustawienia',
+ 'settings.tabs.display': 'Wygląd',
+ 'settings.tabs.map': 'Mapa',
+ 'settings.tabs.notifications': 'Powiadomienia',
+ 'settings.tabs.integrations': 'Integracje',
+ 'settings.tabs.account': 'Konto',
+ 'settings.tabs.offline': 'Offline',
+ 'settings.tabs.about': 'O aplikacji',
+ 'settings.map': 'Mapa',
+ 'settings.mapTemplate': 'Szablon mapy',
+ 'settings.mapTemplatePlaceholder.select': 'Wybierz szablon...',
+ 'settings.mapDefaultHint': 'Pozostaw puste dla OpenStreetMap (domyślnie)',
+ 'settings.mapTemplatePlaceholder':
+ 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
+ 'settings.mapHint': 'Szablon URL dla kafelków mapy',
+ 'settings.mapProvider': 'Dostawca mapy',
+ 'settings.mapProviderHint':
+ 'Dotyczy map Trip Planner i Journey. Atlas zawsze używa Leaflet.',
+ 'settings.mapLeafletSubtitle': 'Klasyczne 2D, dowolne kafelki rastrowe',
+ 'settings.mapMapboxSubtitle': 'Kafelki wektorowe, budynki 3D i teren',
+ 'settings.mapExperimental': 'Eksperymentalne',
+ 'settings.mapMapboxToken': 'Token dostępu Mapbox',
+ 'settings.mapMapboxTokenHint': 'Token publiczny (pk.*) z',
+ 'settings.mapMapboxTokenLink': 'mapbox.com → Tokeny dostępu',
+ 'settings.mapStyle': 'Styl mapy',
+ 'settings.mapStylePlaceholder': 'Wybierz styl Mapbox',
+ 'settings.mapStyleHint': 'Preset lub własny URL mapbox://styles/USER/ID',
+ 'settings.map3dBuildings': 'Budynki 3D i teren',
+ 'settings.map3dHint':
+ 'Nachylenie + prawdziwe wytłaczanie budynków 3D — działa w każdym stylu, także satelitarnym.',
+ 'settings.mapHighQuality': 'Tryb wysokiej jakości',
+ 'settings.mapHighQualityHint':
+ 'Antialiasing + projekcja globusa dla ostrzejszych krawędzi i realistycznego widoku świata.',
+ 'settings.mapHighQualityWarning':
+ 'Może wpływać na wydajność na słabszych urządzeniach.',
+ 'settings.mapTipLabel': 'Wskazówka:',
+ 'settings.mapTip':
+ 'Kliknij prawym przyciskiem i przeciągnij, aby obrócić/pochylić mapę. Środkowy przycisk dodaje miejsce (prawy jest zarezerwowany dla obrotu).',
+ 'settings.latitude': 'Szerokość',
+ 'settings.longitude': 'Długość',
+ 'settings.saveMap': 'Zapisz mapę',
+ 'settings.apiKeys': 'Klucze API',
+ 'settings.mapsKey': 'Klucz Google Maps API',
+ 'settings.mapsKeyHint':
+ 'Do wyszukiwania miejsc. Wymaga Places API (New). Uzyskaj dostęp na console.cloud.google.com',
+ 'settings.weatherKey': 'Klucz OpenWeatherMap API',
+ 'settings.weatherKeyHint':
+ 'Do danych pogodowych. Bezpłatnie na openweathermap.org/api',
+ 'settings.keyPlaceholder': 'Podaj klucz...',
+ 'settings.configured': 'Skonfigurowano',
+ 'settings.saveKeys': 'Zapisz klucze',
+ 'settings.display': 'Preferencje',
+ 'settings.colorMode': 'Motyw',
+ 'settings.light': 'Jasny',
+ 'settings.dark': 'Ciemny',
+ 'settings.auto': 'Automatyczny',
+ 'settings.language': 'Język',
+ 'settings.temperature': 'Jednostka temperatury',
+ 'settings.timeFormat': 'Format czasu',
+ 'settings.blurBookingCodes': 'Rozmyj kody rezerwacji',
+ 'settings.optimizeFromAccommodation': 'Optymalizuj trasę od zakwaterowania',
+ 'settings.optimizeFromAccommodationHint':
+ 'Przy optymalizacji dnia rozpocznij trasę w hotelu, w którym się budzisz, a zakończ ją w tym, do którego się zameldujesz tego wieczoru.',
+ 'settings.notifications': 'Powiadomienia',
+ 'settings.notifyTripInvite': 'Zaproszenia do podróży',
+ 'settings.notifyBookingChange': 'Zmiany w rezerwacjach',
+ 'settings.notifyTripReminder': 'Przypomnienia o podróżach',
+ 'settings.notifyTodoDue': 'Zadanie z terminem',
+ 'settings.notifyVacayInvite': 'Zaproszenia do połączenia kalendarzy',
+ 'settings.notifyPhotosShared': 'Udostępnione zdjęcia (Immich)',
+ 'settings.notifyCollabMessage': 'Wiadomości czatu (Collab)',
+ 'settings.notifyPackingTagged': 'Lista pakowania: przypisania',
+ 'settings.notifyWebhook': 'Powiadomienia Webhook',
+ 'settings.notifyVersionAvailable': 'Nowa wersja dostępna',
+ 'settings.on': 'Włączone',
+ 'settings.off': 'Wyłączone',
+ 'settings.mcp.title': 'Konfiguracja MCP',
+ 'settings.mcp.endpoint': 'Endpoint MCP',
+ 'settings.mcp.clientConfig': 'Konfiguracja klienta',
+ 'settings.mcp.clientConfigHint':
+ 'Zastąp tokenem API z listy poniżej. Ścieżka do npx może wymagać dostosowania do Twojego systemu (np. C:\\PROGRA~1\\nodejs\\npx.cmd w systemie Windows).',
+ 'settings.mcp.clientConfigHintOAuth':
+ 'Zastąp i danymi uwierzytelniającymi z klienta OAuth 2.1 utworzonego powyżej. mcp-remote otworzy przeglądarkę, aby dokończyć autoryzację przy pierwszym połączeniu. Ścieżka do npx może wymagać dostosowania do Twojego systemu (np. C:\\PROGRA~1\\nodejs\\npx.cmd w systemie Windows).',
+ 'settings.mcp.copy': 'Kopiuj',
+ 'settings.mcp.copied': 'Skopiowano!',
+ 'settings.mcp.apiTokens': 'Tokeny API',
+ 'settings.mcp.createToken': 'Utwórz nowy token',
+ 'settings.mcp.noTokens':
+ 'Brak tokenów. Utwórz go, aby połączyć klientów MCP.',
+ 'settings.mcp.tokenCreatedAt': 'Utworzono',
+ 'settings.mcp.tokenUsedAt': 'Użyto',
+ 'settings.mcp.deleteTokenTitle': 'Usuń token',
+ 'settings.mcp.deleteTokenMessage':
+ 'Ten token przestanie działać natychmiastowo. Każdy klient MCP używający go straci dostęp.',
+ 'settings.mcp.modal.createTitle': 'Utwórz token API',
+ 'settings.mcp.modal.tokenName': 'Nazwa tokenu',
+ 'settings.mcp.modal.tokenNamePlaceholder':
+ 'np. Claude Desktop, Laptop służbowy',
+ 'settings.mcp.modal.creating': 'Tworzenie...',
+ 'settings.mcp.modal.create': 'Utwórz token',
+ 'settings.mcp.modal.createdTitle': 'Token został utworzony',
+ 'settings.mcp.modal.createdWarning':
+ 'Ten token zostanie wyświetlony tylko raz. Skopiuj i zapisz go — nie będzie można go zobaczyć ponownie.',
+ 'settings.mcp.modal.done': 'Gotowe',
+ 'settings.mcp.toast.created': 'Token został utworzony',
+ 'settings.mcp.toast.createError': 'Nie udało się utworzyć tokenu',
+ 'settings.mcp.toast.deleted': 'Token został usunięty',
+ 'settings.mcp.toast.deleteError': 'Nie udało się usunąć tokenu',
+ 'settings.mcp.apiTokensDeprecated':
+ 'Tokeny API są przestarzałe i zostaną usunięte w przyszłej wersji. Użyj zamiast tego klientów OAuth 2.1.',
+ 'settings.oauth.clients': 'Klienci OAuth 2.1',
+ 'settings.oauth.clientsHint':
+ 'Zarejestruj klientów OAuth 2.1, aby zewnętrzne aplikacje MCP (Claude Web, Cursor itp.) mogły się łączyć bez statycznych tokenów.',
+ 'settings.oauth.createClient': 'Nowy klient',
+ 'settings.oauth.noClients': 'Brak zarejestrowanych klientów OAuth.',
+ 'settings.oauth.clientId': 'ID klienta',
+ 'settings.oauth.clientSecret': 'Sekret klienta',
+ 'settings.oauth.deleteClient': 'Usuń klienta',
+ 'settings.oauth.deleteClientMessage':
+ 'Ten klient i wszystkie aktywne sesje zostaną trwale usunięte. Każda aplikacja, która go używa, natychmiast utraci dostęp.',
+ 'settings.oauth.rotateSecret': 'Odnów sekret',
+ 'settings.oauth.rotateSecretMessage':
+ 'Zostanie wygenerowany nowy sekret klienta, a wszystkie istniejące sesje zostaną natychmiast unieważnione. Zaktualizuj aplikację przed zamknięciem tego okna.',
+ 'settings.oauth.rotateSecretConfirm': 'Odnów',
+ 'settings.oauth.rotateSecretConfirming': 'Odnawianie…',
+ 'settings.oauth.rotateSecretDoneTitle': 'Wygenerowano nowy sekret',
+ 'settings.oauth.rotateSecretDoneWarning':
+ 'Ten sekret jest wyświetlany tylko raz. Skopiuj go teraz i zaktualizuj aplikację — wszystkie poprzednie sesje zostały unieważnione.',
+ 'settings.oauth.activeSessions': 'Aktywne sesje OAuth',
+ 'settings.oauth.sessionScopes': 'Uprawnienia',
+ 'settings.oauth.sessionExpires': 'Wygasa',
+ 'settings.oauth.revoke': 'Unieważnij',
+ 'settings.oauth.revokeSession': 'Unieważnij sesję',
+ 'settings.oauth.revokeSessionMessage':
+ 'Spowoduje to natychmiastowe unieważnienie dostępu dla tej sesji OAuth.',
+ 'settings.oauth.modal.createTitle': 'Zarejestruj klienta OAuth',
+ 'settings.oauth.modal.presets': 'Szybkie ustawienia',
+ 'settings.oauth.modal.clientName': 'Nazwa aplikacji',
+ 'settings.oauth.modal.clientNamePlaceholder':
+ 'np. Claude Web, Moja aplikacja MCP',
+ 'settings.oauth.modal.redirectUris': 'URI przekierowania',
+ 'settings.oauth.modal.redirectUrisPlaceholder':
+ 'https://your-app.com/callback\nhttps://your-app.com/auth',
+ 'settings.oauth.modal.redirectUrisHint':
+ 'Jeden URI na linię. Wymagane HTTPS (localhost zwolniony). Wymagana dokładna zgodność.',
+ 'settings.oauth.modal.scopes': 'Dozwolone uprawnienia',
+ 'settings.oauth.modal.scopesHint':
+ 'list_trips i get_trip_summary są zawsze dostępne — bez wymaganych uprawnień. Umożliwiają AI odkrycie potrzebnych ID podróży.',
+ 'settings.oauth.modal.selectAll': 'Zaznacz wszystko',
+ 'settings.oauth.modal.deselectAll': 'Odznacz wszystko',
+ 'settings.oauth.modal.creating': 'Rejestrowanie…',
+ 'settings.oauth.modal.create': 'Zarejestruj klienta',
+ 'settings.oauth.modal.createdTitle': 'Klient zarejestrowany',
+ 'settings.oauth.modal.createdWarning':
+ 'Sekret klienta jest wyświetlany tylko raz. Skopiuj go teraz — nie można go odzyskać.',
+ 'settings.oauth.toast.createError':
+ 'Nie udało się zarejestrować klienta OAuth',
+ 'settings.oauth.toast.deleted': 'Klient OAuth usunięty',
+ 'settings.oauth.toast.deleteError': 'Nie udało się usunąć klienta OAuth',
+ 'settings.oauth.toast.revoked': 'Sesja unieważniona',
+ 'settings.oauth.toast.revokeError': 'Nie udało się unieważnić sesji',
+ 'settings.oauth.toast.rotateError': 'Nie udało się odnowić sekretu klienta',
+ 'settings.oauth.modal.machineClient':
+ 'Klient maszynowy (bez logowania przez przeglądarkę)',
+ 'settings.oauth.modal.machineClientHint':
+ 'Używa grantu client_credentials — nie są potrzebne URI przekierowania. Token jest wystawiany bezpośrednio przez client_id + client_secret i działa w Twoim imieniu w ramach wybranych zakresów.',
+ 'settings.oauth.modal.machineClientUsage':
+ 'Pobierz token: POST /oauth/token z grant_type=client_credentials, client_id i client_secret. Bez przeglądarki, bez tokenu odświeżania.',
+ 'settings.oauth.badge.machine': 'maszynowy',
+ 'settings.account': 'Konto',
+ 'settings.about': 'O aplikacji',
+ 'settings.about.reportBug': 'Zgłoś błąd',
+ 'settings.about.reportBugHint': 'Znalazłeś problem? Daj nam znać',
+ 'settings.about.featureRequest': 'Zaproponuj funkcję',
+ 'settings.about.featureRequestHint': 'Zaproponuj nową funkcję',
+ 'settings.about.wikiHint': 'Dokumentacja i poradniki',
+ 'settings.about.supporters.badge': 'Miesięczni Patroni',
+ 'settings.about.supporters.title': 'Towarzystwo podróży dla TREK',
+ 'settings.about.supporters.subtitle':
+ 'Gdy planujesz kolejną trasę, te osoby planują razem ze mną przyszłość TREK. Ich comiesięczny wkład idzie bezpośrednio na rozwój i realnie przepracowane godziny — aby TREK pozostał Open Source.',
+ 'settings.about.supporters.since': 'patron od {date}',
+ 'settings.about.supporters.tierEmpty': 'Bądź pierwszy',
+ 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket',
+ 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP',
+ 'settings.about.supporter.tier.businessClassDreamer':
+ 'Business Class Dreamer',
+ 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller',
+ 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate',
+ 'settings.about.description':
+ 'TREK to samodzielnie hostowany planer podróży, który pomaga organizować wyprawy od pierwszego pomysłu po ostatnie wspomnienie. Planowanie dzienne, budżet, listy pakowania, zdjęcia i wiele więcej — wszystko w jednym miejscu, na własnym serwerze.',
+ 'settings.about.madeWith': 'Stworzone z',
+ 'settings.about.madeBy': "przez Maurice'a i rosnącą społeczność open-source.",
+ 'settings.username': 'Nazwa użytkownika',
+ 'settings.email': 'E-mail',
+ 'settings.role': 'Rola',
+ 'settings.roleAdmin': 'Administrator',
+ 'settings.oidcLinked': 'Połączono z',
+ 'settings.changePassword': 'Zmień hasło',
+ 'settings.currentPassword': 'Aktualne hasło',
+ 'settings.currentPasswordRequired': 'Aktualne hasło jest wymagane',
+ 'settings.newPassword': 'Nowe hasło',
+ 'settings.confirmPassword': 'Potwierdź nowe hasło',
+ 'settings.updatePassword': 'Zaktualizuj hasło',
+ 'settings.passwordRequired': 'Proszę podać aktualne i nowe hasło',
+ 'settings.passwordTooShort': 'Hasło musi mieć co najmniej 8 znaków',
+ 'settings.passwordMismatch': 'Hasła nie są identyczne',
+ 'settings.passwordWeak':
+ 'Hasło musi zawierać wielką literę, małą literę i cyfrę',
+ 'settings.passwordChanged': 'Hasło zostało zmienione pomyślnie',
+ 'settings.deleteAccount': 'Usuń konto',
+ 'settings.deleteAccountTitle': 'Usunąć twoje konto?',
+ 'settings.deleteAccountWarning':
+ 'Twoje konto i wszystkie twoje podróże, miejsca i pliki zostaną trwale usunięte. Tej akcji nie można cofnąć.',
+ 'settings.deleteAccountConfirm': 'Usuń na zawsze',
+ 'settings.deleteBlockedTitle': 'Nie można usunąć konta',
+ 'settings.deleteBlockedMessage':
+ 'Jesteś jedynym administratorem. Wyznacz innego użytkownika na administratora przed usunięciem konta.',
+ 'settings.roleUser': 'Użytkownik',
+ 'settings.saveProfile': 'Zapisz profil',
+ 'settings.toast.mapSaved': 'Ustawienia mapy zostały zapisane',
+ 'settings.toast.keysSaved': 'Klucze API zostały zapisane',
+ 'settings.toast.displaySaved': 'Preferencje zostały zapisane',
+ 'settings.toast.profileSaved': 'Profil został zapisany',
+ 'settings.uploadAvatar': 'Prześlij zdjęcie profilowe',
+ 'settings.removeAvatar': 'Usuń zdjęcie profilowe',
+ 'settings.avatarUploaded': 'Zdjęcie profilowe zostało zaktualizowane',
+ 'settings.avatarRemoved': 'Zdjęcie profilowe zostało usunięte',
+ 'settings.avatarError': 'Przesyłanie nie powiodło się',
+ 'settings.mfa.title': 'Uwierzytelnianie dwuskładnikowe (2FA)',
+ 'settings.mfa.description':
+ 'Dodaje drugi krok, kiedy logujesz się e-mailem i hasłem. Użyj aplikacji uwierzytelniającej (Google Authenticator, Authy, itp.).',
+ 'settings.mfa.requiredByPolicy':
+ 'Twój administrator wymaga uwierzytelniania dwuskładnikowego. Skonfiguruj aplikację uwierzytelniającą poniżej, zanim przejdziesz dalej.',
+ 'settings.mfa.backupTitle': 'Kody zapasowe',
+ 'settings.mfa.backupDescription':
+ 'Użyj tych jednorazowych kodów zapasowych, jeżeli stracisz dostęp do swojej aplikacji uwierzytelniającej.',
+ 'settings.mfa.backupWarning':
+ 'Zapisz te kody. Każdy z nich może być wykorzystany tylko raz.',
+ 'settings.mfa.backupCopy': 'Kopiuj kody',
+ 'settings.mfa.backupDownload': 'Pobierz TXT',
+ 'settings.mfa.backupPrint': 'Drukuj / PDF',
+ 'settings.mfa.backupCopied': 'Kody zapasowe zostały skopiowane',
+ 'settings.mfa.enabled': '2FA jest włączone dla Twojego konta.',
+ 'settings.mfa.disabled': '2FA jest wyłączone.',
+ 'settings.mfa.setup': 'Skonfiguruj aplikację uwierzytelniającą',
+ 'settings.mfa.scanQr':
+ 'Zeskanuj ten kod QR za pomocą aplikacji lub wprowadź klucz ręcznie.',
+ 'settings.mfa.secretLabel': 'Tajny klucz (wprowadź ręcznie)',
+ 'settings.mfa.codePlaceholder': '6-cyfrowy kod',
+ 'settings.mfa.enable': 'Włącz 2FA',
+ 'settings.mfa.cancelSetup': 'Anuluj',
+ 'settings.mfa.disableTitle': 'Wyłącz 2FA',
+ 'settings.mfa.disableHint':
+ 'Podaj hasło do konta i aktualny kod z aplikacji uwierzytelniającej.',
+ 'settings.mfa.disable': 'Wyłącz 2FA',
+ 'settings.mfa.toastEnabled':
+ 'Uwierzytelnianie dwuskładnikowe zostało włączone',
+ 'settings.mfa.toastDisabled':
+ 'Uwierzytelnianie dwuskładnikowe zostało wyłączone',
+ 'settings.mfa.demoBlocked': 'Niedostępne w trybie demonstracyjnym',
+ 'settings.bookingLabels': 'Etykiety tras rezerwacji',
+ 'settings.bookingLabelsHint':
+ 'Pokazuje nazwy stacji / lotnisk na mapie. Gdy wyłączone, wyświetlana jest tylko ikona.',
+ 'settings.notificationsDisabled': 'Powiadomienia nie są skonfigurowane.',
+ 'settings.notificationPreferences.noChannels':
+ 'Brak skonfigurowanych kanałów powiadomień. Poproś administratora o skonfigurowanie powiadomień e-mail lub webhook.',
+ 'settings.webhookUrl.label': 'URL webhooka',
+ 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...',
+ 'settings.webhookUrl.hint':
+ 'Wprowadź adres URL webhooka Discord, Slack lub własnego, aby otrzymywać powiadomienia.',
+ 'settings.webhookUrl.saved': 'URL webhooka zapisany',
+ 'settings.webhookUrl.test': 'Testuj',
+ 'settings.webhookUrl.testSuccess': 'Testowy webhook wysłany pomyślnie',
+ 'settings.webhookUrl.testFailed':
+ 'Wysyłanie testowego webhooka nie powiodło się',
+ 'settings.ntfyUrl.topicLabel': 'Temat Ntfy',
+ 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts',
+ 'settings.ntfyUrl.serverLabel': 'URL serwera Ntfy (opcjonalne)',
+ 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh',
+ 'settings.ntfyUrl.hint':
+ 'Wprowadź swój temat Ntfy, aby otrzymywać powiadomienia push. Pozostaw pole serwera puste, aby użyć domyślnego ustawienia skonfigurowanego przez administratora.',
+ 'settings.ntfyUrl.tokenLabel': 'Token dostępu (opcjonalne)',
+ 'settings.ntfyUrl.tokenHint': 'Wymagane dla tematów chronionych hasłem.',
+ 'settings.ntfyUrl.saved': 'Ustawienia Ntfy zapisane',
+ 'settings.ntfyUrl.test': 'Testuj',
+ 'settings.ntfyUrl.testSuccess':
+ 'Testowe powiadomienie Ntfy wysłane pomyślnie',
+ 'settings.ntfyUrl.testFailed': 'Testowe powiadomienie Ntfy nie powiodło się',
+ 'settings.ntfyUrl.tokenCleared': 'Token dostępu wyczyszczony',
+ 'settings.notificationPreferences.inapp': 'In-App',
+ 'settings.notificationPreferences.webhook': 'Webhook',
+ 'settings.notificationPreferences.email': 'Email',
+ 'settings.notificationPreferences.ntfy': 'Ntfy',
+ 'settings.notificationsActive': 'Aktywny kanał',
+ 'settings.notificationsManagedByAdmin':
+ 'Zdarzenia konfigurowane przez administratora.',
+ 'settings.mustChangePassword': 'Musisz zmienić hasło przed kontynuowaniem.',
+ "settings.currency": "Currency",
+ "settings.currencyHint": "All amounts in Costs are converted to and shown in this currency.",
+ 'settings.passkey.title': 'Klucze dostępu',
+ 'settings.passkey.description':
+ 'Loguj się szybciej i z odpornością na phishing za pomocą klucza dostępu — odcisku palca, twarzy, kodu PIN lub klucza sprzętowego. Twoje hasło pozostaje jako zapasowa opcja.',
+ 'settings.passkey.notConfigured':
+ 'Klucze dostępu są włączone, ale nie zostały jeszcze w pełni skonfigurowane na tym serwerze. Poproś administratora o ustawienie domeny WebAuthn.',
+ 'settings.passkey.add': 'Dodaj klucz dostępu',
+ 'settings.passkey.addTitle': 'Dodaj klucz dostępu',
+ 'settings.passkey.passwordPrompt':
+ 'Potwierdź swoje aktualne hasło, a następnie postępuj zgodnie z komunikatem na urządzeniu.',
+ 'settings.passkey.passwordRequired': 'Twoje aktualne hasło jest wymagane.',
+ 'settings.passkey.namePlaceholder': 'Nazwa (opcjonalnie, np. "iPhone")',
+ 'settings.passkey.addedToast': 'Klucz dostępu został dodany',
+ 'settings.passkey.added': 'Dodano',
+ 'settings.passkey.addError': 'Nie udało się dodać klucza dostępu',
+ 'settings.passkey.cancelled': 'Konfiguracja klucza dostępu anulowana',
+ 'settings.passkey.deleted': 'Klucz dostępu został usunięty',
+ 'settings.passkey.deleteConfirm':
+ 'Usunąć ten klucz dostępu? Potwierdź swoim hasłem.',
+ 'settings.passkey.rename': 'Zmień nazwę',
+ 'settings.passkey.defaultName': 'Klucz dostępu',
+ 'settings.passkey.synced': 'Zsynchronizowany',
+ 'settings.passkey.deviceBound': 'To urządzenie',
+ 'settings.passkey.lastUsed': 'Ostatnio użyty',
+ 'settings.passkey.neverUsed': 'Nigdy nieużywany',
+ 'settings.mapPoiPill': 'Odkrywaj miejsca na mapie',
+ 'settings.mapPoiPillHint': 'Pokaż na mapie wyprawy pasek z kategoriami, aby znaleźć pobliskie restauracje, hotele i więcej z OpenStreetMap.',
+ 'settings.airtrail.title': 'AirTrail',
+ 'settings.airtrail.hint': 'Połącz swój własny AirTrail, aby importować i synchronizować loty. Utwórz klucz API w AirTrail w sekcji Ustawienia → Bezpieczeństwo.',
+ 'settings.airtrail.url': 'Adres URL instancji',
+ 'settings.airtrail.apiKey': 'Klucz API',
+ 'settings.airtrail.apiKeyPlaceholder': 'Klucz API typu Bearer',
+ 'settings.airtrail.apiKeyHint': 'Wygenerowany w AirTrail w sekcji Ustawienia → Bezpieczeństwo. Przechowywany w postaci zaszyfrowanej.',
+ 'settings.airtrail.allowInsecureTls': 'Zezwalaj na certyfikaty samopodpisane',
+ 'settings.airtrail.allowInsecureTlsHint': 'Włącz tylko dla zaufanej instancji we własnej sieci.',
+ 'settings.airtrail.connected': 'Połączono',
+ 'settings.airtrail.notConnected': 'Nie połączono',
+ 'settings.airtrail.toast.saved': 'Zapisano połączenie z AirTrail',
+ 'settings.airtrail.toast.saveError': 'Nie udało się zapisać połączenia',
+ 'settings.airtrail.test.button': 'Testuj połączenie',
+ 'settings.airtrail.test.success': 'Połączono — znaleziono {count} lot(y/ów)',
+ 'settings.airtrail.test.failed': 'Połączenie nieudane',
+};
+
+export default settings;
diff --git a/shared/src/i18n/pl/share.ts b/shared/src/i18n/pl/share.ts
new file mode 100644
index 0000000000000000000000000000000000000000..33ec51c3b20ba912c735ec6b216630037a44ccfe
--- /dev/null
+++ b/shared/src/i18n/pl/share.ts
@@ -0,0 +1,16 @@
+import type { TranslationStrings } from '../types';
+
+const share: TranslationStrings = {
+ 'share.linkTitle': 'Publiczny link',
+ 'share.linkHint':
+ 'Utwórz link umożliwiający przeglądanie tej podróży bez logowania. Tylko do odczytu — bez możliwości edycji.',
+ 'share.createLink': 'Utwórz link',
+ 'share.deleteLink': 'Usuń link',
+ 'share.createError': 'Nie udało się utworzyć linku',
+ 'share.permMap': 'Mapa i plan',
+ 'share.permBookings': 'Rezerwacje',
+ 'share.permPacking': 'Lista pakowania',
+ 'share.permBudget': 'Budżet',
+ 'share.permCollab': 'Czat',
+};
+export default share;
diff --git a/shared/src/i18n/pl/shared.ts b/shared/src/i18n/pl/shared.ts
new file mode 100644
index 0000000000000000000000000000000000000000..69b577b271c5e218c10b18f84fc2f20d7483d3e3
--- /dev/null
+++ b/shared/src/i18n/pl/shared.ts
@@ -0,0 +1,21 @@
+import type { TranslationStrings } from '../types';
+
+const shared: TranslationStrings = {
+ 'shared.expired': 'Link wygasł lub jest nieprawidłowy',
+ 'shared.expiredHint': 'Ten link do podróży jest już nieaktywny.',
+ 'shared.readOnly': 'Widok udostępniony tylko do odczytu',
+ 'shared.tabPlan': 'Plan',
+ 'shared.tabBookings': 'Rezerwacje',
+ 'shared.tabPacking': 'Lista pakowania',
+ 'shared.tabBudget': 'Budżet',
+ 'shared.tabChat': 'Czat',
+ 'shared.days': 'dni',
+ 'shared.places': 'miejsca',
+ 'shared.other': 'Inne',
+ 'shared.totalBudget': 'Całkowity budżet',
+ 'shared.messages': 'wiadomości',
+ 'shared.sharedVia': 'Udostępnione przez',
+ 'shared.confirmed': 'Potwierdzone',
+ 'shared.pending': 'Oczekujące',
+};
+export default shared;
diff --git a/shared/src/i18n/pl/stats.ts b/shared/src/i18n/pl/stats.ts
new file mode 100644
index 0000000000000000000000000000000000000000..55a72bdd5d6285b349082e16fddbb2c5cd3a9af0
--- /dev/null
+++ b/shared/src/i18n/pl/stats.ts
@@ -0,0 +1,13 @@
+import type { TranslationStrings } from '../types';
+
+const stats: TranslationStrings = {
+ 'stats.countries': 'Kraje',
+ 'stats.cities': 'Miasta',
+ 'stats.trips': 'Podróże',
+ 'stats.places': 'Miejsca',
+ 'stats.worldProgress': 'Postęp',
+ 'stats.visited': 'odwiedzone',
+ 'stats.remaining': 'pozostałe',
+ 'stats.visitedCountries': 'Odwiedzone kraje',
+};
+export default stats;
diff --git a/shared/src/i18n/pl/system_notice.ts b/shared/src/i18n/pl/system_notice.ts
new file mode 100644
index 0000000000000000000000000000000000000000..14bc23bbaaa3dff6f9b2f654c2993554729e9f07
--- /dev/null
+++ b/shared/src/i18n/pl/system_notice.ts
@@ -0,0 +1,60 @@
+import type { TranslationStrings } from '../types';
+
+const system_notice: TranslationStrings = {
+ 'system_notice.welcome_v1.title': 'Witaj w TREK',
+ 'system_notice.welcome_v1.body':
+ 'Twój kompleksowy planer podróży. Twórz trasy, dziel się wycieczkami ze znajomymi i bądź zorganizowany — online i offline.',
+ 'system_notice.welcome_v1.cta_label': 'Zaplanuj podróż',
+ 'system_notice.welcome_v1.hero_alt':
+ 'Malownicze miejsce z interfejsem planowania TREK',
+ 'system_notice.welcome_v1.highlight_plan': 'Trasy dzień po dniu',
+ 'system_notice.welcome_v1.highlight_share': 'Współpraca z partnerami podróży',
+ 'system_notice.welcome_v1.highlight_offline': 'Działa offline na telefonie',
+ 'system_notice.dev_test_modal.title': '[Dev] Test notice',
+ 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.',
+ 'system_notice.pager.prev': 'Poprzednie powiadomienie',
+ 'system_notice.pager.next': 'Następne powiadomienie',
+ 'system_notice.pager.counter': '{current} / {total}',
+ 'system_notice.pager.goto': 'Przejdź do powiadomienia {n}',
+ 'system_notice.pager.position': 'Powiadomienie {current} z {total}',
+ 'system_notice.v3_photos.title': 'Zdjęcia zostały przeniesione w 3.0',
+ 'system_notice.v3_photos.body':
+ '**Zdjęcia** w Planerze Podróży zostały usunięte. Twoje zdjęcia są bezpieczne — TREK nigdy nie modyfikował Twojej biblioteki Immich lub Synology.\n\nZdjęcia są teraz dostępne w dodatku **Journey**. Journey jest opcjonalny — jeśli jeszcze nie jest dostępny, poproś administratora o jego włączenie w Admin → Dodatki.',
+ 'system_notice.v3_journey.title': 'Poznaj Journey — dziennik podróży',
+ 'system_notice.v3_journey.body':
+ 'Dokumentuj swoje podróże jako bogatrze opowieści z osami czasu, galeriami i mapami interaktywnymi.',
+ 'system_notice.v3_journey.cta_label': 'Otwórz Journey',
+ 'system_notice.v3_journey.highlight_timeline': 'Dzienna oś czasu i galeria',
+ 'system_notice.v3_journey.highlight_photos': 'Import z Immich lub Synology',
+ 'system_notice.v3_journey.highlight_share':
+ 'Udostępnij publicznie — bez logowania',
+ 'system_notice.v3_journey.highlight_export':
+ 'Eksportuj jako książkę fotograficzną PDF',
+ 'system_notice.v3_features.title': 'Więcej nowości w 3.0',
+ 'system_notice.v3_features.body':
+ 'Kilka innych rzeczy wartych uwagi w tym wydaniu.',
+ 'system_notice.v3_features.highlight_dashboard':
+ 'Przeprojektowany pulpit mobile-first',
+ 'system_notice.v3_features.highlight_offline': 'Pełny tryb offline jako PWA',
+ 'system_notice.v3_features.highlight_search':
+ 'Autouzupełnianie wyszukiwania miejsc',
+ 'system_notice.v3_features.highlight_import':
+ 'Import miejsc z plików KMZ/KML',
+ 'system_notice.v3_mcp.title': 'MCP: aktualizacja OAuth 2.1',
+ 'system_notice.v3_mcp.body':
+ 'Integracja MCP została całkowicie przeprojektowana. OAuth 2.1 jest teraz zalecaną metodą uwierzytelniania. Statyczne tokeny (trek_…) są przestarzałe i zostaną usunięte w przyszłej wersji.',
+ 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 zalecany (mcp-remote)',
+ 'system_notice.v3_mcp.highlight_scopes': '24 szczegółowe zakresy uprawnień',
+ 'system_notice.v3_mcp.highlight_deprecated':
+ 'Statyczne tokeny trek_ przestarzałe',
+ 'system_notice.v3_mcp.highlight_tools':
+ 'Rozszerzony zestaw narzędzi i promptów',
+ 'system_notice.v3_thankyou.title': 'Osobiste słowo ode mnie',
+ 'system_notice.v3_thankyou.body':
+ 'Zanim pójdziesz dalej — chcę się na chwilę zatrzymać.\n\nTREK zaczął się jako poboczny projekt, który zbudowałem na własne podróże. Nigdy nie wyobrażałem sobie, że wyrośnie na coś, czemu 4000 z was ufa przy planowaniu swoich przygód. Każda gwiazdka, każdy issue, każda prośba o funkcję — czytam je wszystkie i to one trzymają mnie na nogach podczas późnych nocy między pracą na pełny etat a uczelnią.\n\nChcę, żebyście wiedzieli: TREK zawsze będzie open source, zawsze self-hosted, zawsze wasz. Bez śledzenia, bez subskrypcji, bez haczyków. Po prostu narzędzie zbudowane przez kogoś, kto kocha podróżowanie tak samo jak wy.\n\nSzczególne podziękowania dla [jubnl](https://github.com/jubnl) — stałeś się niesamowitym współpracownikiem. Tak wiele z tego, co czyni wersję 3.0 wspaniałą, nosi twój ślad. Dziękuję, że uwierzyłeś w ten projekt, gdy był jeszcze surowy.\n\nI każdemu z was, kto zgłosił błąd, przetłumaczył tekst, podzielił się TREK z przyjacielem lub po prostu użył go do zaplanowania podróży — **dziękuję**. To wy jesteście powodem, dla którego to istnieje.\n\nZa wiele kolejnych wspólnych przygód.\n\n— Maurice\n\n---\n\n[Dołącz do społeczności na Discordzie](https://discord.gg/7Q6M6jDwzf)\n\nJeśli TREK sprawia, że Twoje podróże są lepsze, [mała kawa](https://ko-fi.com/mauriceboe) zawsze pomaga utrzymać światła włączone.',
+ 'system_notice.v3014_whitespace_collision.title':
+ 'Wymagane działanie: konflikt konta użytkownika',
+ 'system_notice.v3014_whitespace_collision.body':
+ 'Aktualizacja 3.0.14 wykryła jeden lub więcej konfliktów nazwy użytkownika lub adresu e-mail spowodowanych spacjami na początku lub końcu przechowywanych wartości. Dotknięte konta zostały automatycznie przemianowane. Sprawdź logi serwera pod kątem wierszy zaczynających się od **[migration] WHITESPACE COLLISION**, aby zidentyfikować konta wymagające przeglądu.',
+};
+export default system_notice;
diff --git a/shared/src/i18n/pl/todo.ts b/shared/src/i18n/pl/todo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e655895b57602c8405c017ccd4c842d322252631
--- /dev/null
+++ b/shared/src/i18n/pl/todo.ts
@@ -0,0 +1,40 @@
+import type { TranslationStrings } from '../types';
+
+const todo: TranslationStrings = {
+ 'todo.subtab.packing': 'Lista pakowania',
+ 'todo.subtab.todo': 'Do zrobienia',
+ 'todo.completed': 'ukończono',
+ 'todo.filter.all': 'Wszystkie',
+ 'todo.filter.open': 'Otwarte',
+ 'todo.filter.done': 'Gotowe',
+ 'todo.uncategorized': 'Bez kategorii',
+ 'todo.namePlaceholder': 'Nazwa zadania',
+ 'todo.descriptionPlaceholder': 'Opis (opcjonalnie)',
+ 'todo.unassigned': 'Nieprzypisane',
+ 'todo.noCategory': 'Brak kategorii',
+ 'todo.hasDescription': 'Ma opis',
+ 'todo.addItem': 'Nowe zadanie',
+ 'todo.sidebar.sortBy': 'Sortuj wg',
+ 'todo.priority': 'Priorytet',
+ 'todo.newCategoryLabel': 'nowa',
+ 'todo.newCategory': 'Nazwa kategorii',
+ 'todo.addCategory': 'Dodaj kategorię',
+ 'todo.newItem': 'Nowe zadanie',
+ 'todo.empty': 'Brak zadań. Dodaj zadanie, aby zacząć!',
+ 'todo.filter.my': 'Moje zadania',
+ 'todo.filter.overdue': 'Przeterminowane',
+ 'todo.sidebar.tasks': 'Zadania',
+ 'todo.sidebar.categories': 'Kategorie',
+ 'todo.detail.title': 'Zadanie',
+ 'todo.detail.description': 'Opis',
+ 'todo.detail.category': 'Kategoria',
+ 'todo.detail.dueDate': 'Termin',
+ 'todo.detail.assignedTo': 'Przypisano do',
+ 'todo.detail.delete': 'Usuń',
+ 'todo.detail.save': 'Zapisz zmiany',
+ 'todo.detail.create': 'Utwórz zadanie',
+ 'todo.detail.priority': 'Priorytet',
+ 'todo.detail.noPriority': 'Brak',
+ 'todo.sortByPrio': 'Priorytet',
+};
+export default todo;
diff --git a/shared/src/i18n/pl/transport.ts b/shared/src/i18n/pl/transport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..208702cad996914d6a18ade836e7c56d84c4a65a
--- /dev/null
+++ b/shared/src/i18n/pl/transport.ts
@@ -0,0 +1,10 @@
+import type { TranslationStrings } from '../types';
+
+const transport: TranslationStrings = {
+ 'transport.addTransport': 'Dodaj transport',
+ 'transport.modalTitle.create': 'Dodaj transport',
+ 'transport.modalTitle.edit': 'Edytuj transport',
+ 'transport.title': 'Transport',
+ 'transport.addManual': 'Ręczny transport',
+};
+export default transport;
diff --git a/shared/src/i18n/pl/trip.ts b/shared/src/i18n/pl/trip.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c2bd8e3868c5c31bbf4ef719b7b2ed81da1f7d89
--- /dev/null
+++ b/shared/src/i18n/pl/trip.ts
@@ -0,0 +1,31 @@
+import type { TranslationStrings } from '../types';
+
+const trip: TranslationStrings = {
+ 'trip.tabs.plan': 'Plan',
+ 'trip.tabs.transports': 'Transport',
+ 'trip.tabs.reservations': 'Rezerwacje',
+ 'trip.tabs.reservationsShort': 'Rezerwacje',
+ 'trip.tabs.packing': 'Lista pakowania',
+ 'trip.tabs.packingShort': 'Pakowanie',
+ 'trip.tabs.lists': 'Listy',
+ 'trip.tabs.listsShort': 'Listy',
+ 'trip.tabs.budget': "Costs",
+ 'trip.tabs.files': 'Pliki',
+ 'trip.loading': 'Ładowanie podróży...',
+ 'trip.mobilePlan': 'Plan',
+ 'trip.mobilePlaces': 'Miejsca',
+ 'trip.toast.placeUpdated': 'Miejsce zostało zaktualizowane',
+ 'trip.toast.placeAdded': 'Miejsce zostało dodane',
+ 'trip.toast.placeDeleted': 'Miejsce zostało usunięte',
+ 'trip.toast.selectDay': 'Proszę najpierw wybrać dzień',
+ 'trip.toast.assignedToDay': 'Miejsce przypisane do dnia',
+ 'trip.toast.reorderError': 'Nie udało się zmienić kolejności',
+ 'trip.toast.reservationUpdated': 'Rezerwacja została zaktualizowana',
+ 'trip.toast.reservationAdded': 'Rezerwacja została dodana',
+ 'trip.toast.deleted': 'Usunięto',
+ 'trip.confirm.deletePlace': 'Czy na pewno chcesz usunąć to miejsce?',
+ 'trip.confirm.deletePlaces': 'Usunąć {count} miejsc?',
+ 'trip.toast.placesDeleted': '{count} miejsc usunięto',
+ 'trip.loadingPhotos': 'Ładowanie zdjęć...',
+};
+export default trip;
diff --git a/shared/src/i18n/pl/trips.ts b/shared/src/i18n/pl/trips.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ef0701fa4be8e4a18f45d1804bd0e2528ecf8107
--- /dev/null
+++ b/shared/src/i18n/pl/trips.ts
@@ -0,0 +1,16 @@
+import type { TranslationStrings } from '../types';
+
+const trips: TranslationStrings = {
+ 'trips.memberRemoved': '{username} usunięty',
+ 'trips.memberRemoveError': 'Nie udało się usunąć',
+ 'trips.memberAdded': '{username} dodany',
+ 'trips.memberAddError': 'Nie udało się dodać',
+ '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.',
+};
+export default trips;
diff --git a/shared/src/i18n/pl/undo.ts b/shared/src/i18n/pl/undo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..241fd91d038f55d925e4eed6950c79388c960b10
--- /dev/null
+++ b/shared/src/i18n/pl/undo.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const undo: TranslationStrings = {
+ '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.deletePlaces': 'Miejsca usunięte',
+ 'undo.moveDay': 'Miejsce przeniesione',
+ 'undo.lock': 'Blokada przełączona',
+ 'undo.importGpx': 'Import GPX',
+ 'undo.importKeyholeMarkup': 'Import KMZ/KML',
+ 'undo.importGoogleList': 'Import Google Maps',
+ 'undo.importNaverList': 'Import Naver Maps',
+ 'undo.addPlace': 'Miejsce dodane',
+ 'undo.done': 'Cofnięto: {action}',
+ 'undo.importBooking': 'Import potwierdzenia rezerwacji',
+};
+export default undo;
diff --git a/shared/src/i18n/pl/vacay.ts b/shared/src/i18n/pl/vacay.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b6600a0766b1b448c6b6a1221eec569f4a15e732
--- /dev/null
+++ b/shared/src/i18n/pl/vacay.ts
@@ -0,0 +1,106 @@
+import type { TranslationStrings } from '../types';
+
+const vacay: TranslationStrings = {
+ 'vacay.subtitle': 'Planuj i zarządzaj dniami urlopu',
+ 'vacay.settings': 'Ustawienia',
+ 'vacay.year': 'Rok',
+ 'vacay.addYear': 'Dodaj następny rok',
+ 'vacay.addPrevYear': 'Dodaj poprzedni rok',
+ 'vacay.removeYear': 'Usuń rok',
+ 'vacay.removeYearConfirm': 'Usunąć {year}?',
+ 'vacay.removeYearHint':
+ 'Wszystkie wpisy dotyczące urlopów oraz dni wolnych w tym roku zostaną trwale usunięte.',
+ 'vacay.remove': 'Usuń',
+ 'vacay.persons': 'Osoby',
+ 'vacay.noPersons': 'Nie dodano osób',
+ 'vacay.addPerson': 'Dodaj osobę',
+ 'vacay.editPerson': 'Edytuj osobę',
+ 'vacay.removePerson': 'Usuń osobę',
+ 'vacay.removePersonConfirm': 'Usunąć {name}?',
+ 'vacay.removePersonHint':
+ 'Wszystkie wpisy dotyczące urlopów dla tej osoby zostaną trwale usunięte.',
+ 'vacay.personName': 'Imię',
+ 'vacay.personNamePlaceholder': 'Podaj imię',
+ 'vacay.color': 'Kolor',
+ 'vacay.add': 'Dodaj',
+ 'vacay.legend': 'Legenda',
+ 'vacay.publicHoliday': 'Święto państwowe',
+ 'vacay.companyHoliday': 'Urlop firmowy',
+ 'vacay.weekend': 'Weekendowy',
+ 'vacay.modeVacation': 'Urlop',
+ 'vacay.modeCompany': 'Urlop firmowy',
+ 'vacay.entitlement': 'Wymiar',
+ 'vacay.entitlementDays': 'Dni',
+ 'vacay.used': 'Wykorzystane',
+ 'vacay.remaining': 'Pozostało',
+ 'vacay.carriedOver': 'z {year}',
+ 'vacay.blockWeekends': 'Blokuj weekendy',
+ 'vacay.blockWeekendsHint': 'Zapobiegaj wpisywaniu urlopów w weekendy',
+ 'vacay.weekendDays': 'Dni weekendowe',
+ 'vacay.mon': 'Pon',
+ 'vacay.tue': 'Wt',
+ 'vacay.wed': 'Śr',
+ 'vacay.thu': 'Czw',
+ 'vacay.fri': 'Pt',
+ 'vacay.sat': 'Sob',
+ 'vacay.sun': 'Nd',
+ 'vacay.publicHolidays': 'Święta państwowe',
+ 'vacay.publicHolidaysHint': 'Oznacz święta państwowe w kalendarzu',
+ 'vacay.selectCountry': 'Wybierz kraj',
+ 'vacay.selectRegion': 'Wybierz region (opcjonalnie)',
+ 'vacay.addCalendar': 'Dodaj kalendarz',
+ 'vacay.calendarLabel': 'Etykieta (opcjonalnie)',
+ 'vacay.calendarColor': 'Kolor',
+ 'vacay.noCalendars': 'Nie dodano jeszcze kalendarzy świąt',
+ 'vacay.companyHolidays': 'Urlopy firmowe',
+ 'vacay.companyHolidaysHint':
+ 'Pozwala oznaczać dni wolne od pracy w kalendarzu',
+ 'vacay.companyHolidaysNoDeduct':
+ 'Urlopy firmowe nie są odejmowane od puli dni urlopowych.',
+ 'vacay.weekStart': 'Tydzień zaczyna się w',
+ 'vacay.weekStartHint':
+ 'Wybierz czy tydzień zaczyna się w poniedziałek czy niedzielę',
+ 'vacay.carryOver': 'Przeniesienie na kolejny rok',
+ 'vacay.carryOverHint':
+ 'Automatycznie przenosi pozostałe dni urlopowe na kolejny rok',
+ 'vacay.sharing': 'Udostępnianie',
+ 'vacay.sharingHint': 'Udostępnij swój plan urlopów innym użytkownikom TREK',
+ 'vacay.owner': 'Właściciel',
+ 'vacay.shareEmailPlaceholder': 'E-mail użytkownika TREK',
+ 'vacay.shareSuccess': 'Plan został udostępniony pomyślnie',
+ 'vacay.shareError': 'Nie udało się udostępnić planu',
+ 'vacay.dissolve': 'Rozłącz kalendarze',
+ 'vacay.dissolveHint':
+ 'Rozłącz kalendarze ponownie. Twoje wpisy zostaną zachowane.',
+ 'vacay.dissolveAction': 'Rozłącz',
+ 'vacay.dissolved': 'Kalendarz został rozłączony',
+ 'vacay.fusedWith': 'Połączono z',
+ 'vacay.you': 'ty',
+ 'vacay.noData': 'Brak danych',
+ 'vacay.changeColor': 'Zmień kolor',
+ 'vacay.inviteUser': 'Zaproś użytkownika',
+ 'vacay.inviteHint':
+ 'Zaproś innego użytkownika TREK do wspólnego kalendarza urlopów.',
+ 'vacay.selectUser': 'Wybierz użytkownika',
+ 'vacay.sendInvite': 'Wyślij zaproszenie',
+ 'vacay.inviteSent': 'Zaproszenie zostało wysłane',
+ 'vacay.inviteError': 'Nie udało się wysłać zaproszenia',
+ 'vacay.pending': 'oczekujące',
+ 'vacay.noUsersAvailable': 'Brak dostępnych użytkowników',
+ 'vacay.accept': 'Akceptuj',
+ 'vacay.decline': 'Odrzuć',
+ 'vacay.acceptFusion': 'Akceptuj i połącz',
+ 'vacay.inviteTitle': 'Zaproszenie do połączenia',
+ 'vacay.inviteWantsToFuse': 'chce udostępnić kalendarz urlopów.',
+ 'vacay.fuseInfo1':
+ 'Obie strony będą widzieć wszystkie wpisy urlopowe w jednym wspólnym kalendarzu.',
+ 'vacay.fuseInfo2':
+ 'Obie strony mogą tworzyć i edytować wpisy dla drugiej strony.',
+ 'vacay.fuseInfo3':
+ 'Obie strony mogą usuwać wpisy i zmieniać pulę dni urlopowych.',
+ 'vacay.fuseInfo4':
+ 'Ustawienia, takie jak święta państwowe i urlopy firmowe, są współdzielone.',
+ 'vacay.fuseInfo5':
+ 'Połączenie może zostać rozwiązane w dowolnym momencie przez każdą ze stron. Twoje wpisy zostaną zachowane.',
+};
+export default vacay;
diff --git a/shared/src/i18n/ru/admin.ts b/shared/src/i18n/ru/admin.ts
new file mode 100644
index 0000000000000000000000000000000000000000..07bac157c1a6a3195d615f97ad682ab47e1da904
--- /dev/null
+++ b/shared/src/i18n/ru/admin.ts
@@ -0,0 +1,399 @@
+import type { TranslationStrings } from '../types';
+
+const admin: TranslationStrings = {
+ 'admin.notifications.title': 'Уведомления',
+ 'admin.notifications.hint':
+ 'Выберите канал уведомлений. Одновременно может быть активен только один.',
+ 'admin.notifications.none': 'Отключено',
+ 'admin.notifications.email': 'Эл. почта (SMTP)',
+ 'admin.notifications.webhook': 'Webhook',
+ 'admin.notifications.save': 'Сохранить настройки уведомлений',
+ 'admin.notifications.saved': 'Настройки уведомлений сохранены',
+ 'admin.notifications.testWebhook': 'Отправить тестовый вебхук',
+ 'admin.notifications.testWebhookSuccess': 'Тестовый вебхук успешно отправлен',
+ 'admin.notifications.testWebhookFailed': 'Ошибка отправки тестового вебхука',
+ 'admin.smtp.title': 'Почта и уведомления',
+ 'admin.smtp.hint':
+ 'Конфигурация SMTP для отправки уведомлений по электронной почте.',
+ 'admin.smtp.testButton': 'Отправить тестовое письмо',
+ 'admin.webhook.hint':
+ 'Отправлять уведомления через внешний webhook (Discord, Slack и т.д.).',
+ 'admin.smtp.testSuccess': 'Тестовое письмо успешно отправлено',
+ 'admin.smtp.testFailed': 'Ошибка отправки тестового письма',
+ 'admin.title': 'Администрирование',
+ 'admin.subtitle': 'Управление пользователями и системные настройки',
+ 'admin.tabs.users': 'Пользователи',
+ 'admin.tabs.categories': 'Категории',
+ 'admin.tabs.backup': 'Резервная копия',
+ 'admin.tabs.audit': 'Аудит',
+ 'admin.stats.users': 'Пользователи',
+ 'admin.stats.trips': 'Поездки',
+ 'admin.stats.places': 'Места',
+ 'admin.stats.photos': 'Фото',
+ 'admin.stats.files': 'Файлы',
+ 'admin.table.user': 'Пользователь',
+ 'admin.table.email': 'Эл. почта',
+ 'admin.table.role': 'Роль',
+ 'admin.table.created': 'Создан',
+ 'admin.table.lastLogin': 'Последний вход',
+ 'admin.table.actions': 'Действия',
+ 'admin.you': '(Вы)',
+ 'admin.editUser': 'Редактировать пользователя',
+ 'admin.newPassword': 'Новый пароль',
+ 'admin.newPasswordHint': 'Оставьте пустым, чтобы сохранить текущий пароль',
+ 'admin.deleteUser':
+ 'Удалить пользователя «{name}»? Все поездки будут безвозвратно удалены.',
+ 'admin.deleteUserTitle': 'Удалить пользователя',
+ 'admin.newPasswordPlaceholder': 'Введите новый пароль…',
+ 'admin.toast.loadError': 'Не удалось загрузить данные администрирования',
+ 'admin.toast.userUpdated': 'Пользователь обновлён',
+ 'admin.toast.updateError': 'Ошибка обновления',
+ 'admin.toast.userDeleted': 'Пользователь удалён',
+ 'admin.toast.deleteError': 'Ошибка удаления',
+ 'admin.toast.cannotDeleteSelf': 'Нельзя удалить собственный аккаунт',
+ 'admin.toast.userCreated': 'Пользователь создан',
+ 'admin.toast.createError': 'Ошибка создания пользователя',
+ 'admin.toast.fieldsRequired':
+ 'Имя пользователя, эл. почта и пароль обязательны',
+ 'admin.createUser': 'Создать пользователя',
+ 'admin.invite.title': 'Ссылки-приглашения',
+ 'admin.invite.subtitle': 'Создание одноразовых ссылок для регистрации',
+ 'admin.invite.create': 'Создать ссылку',
+ 'admin.invite.createAndCopy': 'Создать и скопировать',
+ 'admin.invite.empty': 'Ссылки-приглашения ещё не созданы',
+ 'admin.invite.maxUses': 'Макс. использований',
+ 'admin.invite.expiry': 'Действует',
+ 'admin.invite.uses': 'использовано',
+ 'admin.invite.expiresAt': 'истекает',
+ 'admin.invite.createdBy': 'от',
+ 'admin.invite.active': 'Активна',
+ 'admin.invite.expired': 'Истекла',
+ 'admin.invite.usedUp': 'Исчерпана',
+ 'admin.invite.copied': 'Ссылка-приглашение скопирована',
+ 'admin.invite.copyLink': 'Копировать ссылку',
+ 'admin.invite.deleted': 'Ссылка-приглашение удалена',
+ 'admin.invite.createError': 'Ошибка при создании ссылки',
+ 'admin.invite.deleteError': 'Ошибка при удалении ссылки',
+ 'admin.tabs.settings': 'Настройки',
+ 'admin.allowRegistration': 'Разрешить регистрацию',
+ 'admin.allowRegistrationHint':
+ 'Новые пользователи могут регистрироваться самостоятельно',
+ 'admin.authMethods': 'Authentication Methods',
+ 'admin.passwordLogin': 'Password Login',
+ 'admin.passwordLoginHint': 'Allow users to sign in with email and password',
+ 'admin.passwordRegistration': 'Password Registration',
+ 'admin.passwordRegistrationHint':
+ 'Allow new users to register with email and password',
+ 'admin.oidcLogin': 'SSO Login',
+ 'admin.oidcLoginHint': 'Allow users to sign in with SSO',
+ 'admin.oidcRegistration': 'SSO Auto-Provisioning',
+ 'admin.oidcRegistrationHint':
+ 'Automatically create accounts for new SSO users',
+ 'admin.envOverrideHint':
+ 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.',
+ 'admin.lockoutWarning': 'At least one login method must remain enabled',
+ 'admin.requireMfa': 'Требовать двухфакторную аутентификацию (2FA)',
+ 'admin.requireMfaHint':
+ 'Пользователи без 2FA должны завершить настройку в разделе «Настройки» перед использованием приложения.',
+ 'admin.apiKeys': 'API-ключи',
+ 'admin.apiKeysHint':
+ 'Необязательно. Включает расширенные данные о местах, такие как фото и погода.',
+ 'admin.mapsKey': 'API-ключ Google Maps',
+ 'admin.mapsKeyHint':
+ 'Необходим для поиска мест. Получите на console.cloud.google.com',
+ 'admin.mapsKeyHintLong':
+ 'Без API-ключа используется OpenStreetMap для поиска мест. С ключом Google API можно загружать фото, рейтинги и часы работы. Получите ключ на console.cloud.google.com.',
+ 'admin.recommended': 'Рекомендуется',
+ 'admin.weatherKey': 'API-ключ OpenWeatherMap',
+ 'admin.weatherKeyHint':
+ 'Для данных о погоде. Бесплатно на openweathermap.org',
+ 'admin.validateKey': 'Проверить',
+ 'admin.keyValid': 'Подключено',
+ 'admin.keyInvalid': 'Недействителен',
+ 'admin.keySaved': 'API-ключи сохранены',
+ 'admin.oidcTitle': 'Единый вход (OIDC)',
+ 'admin.oidcSubtitle':
+ 'Разрешить вход через внешних провайдеров, таких как Google, Apple, Authentik или Keycloak.',
+ 'admin.oidcDisplayName': 'Отображаемое имя',
+ 'admin.oidcIssuer': 'URL издателя',
+ 'admin.oidcIssuerHint':
+ 'URL издателя OpenID Connect провайдера. Напр. https://accounts.google.com',
+ 'admin.oidcSaved': 'Конфигурация OIDC сохранена',
+ 'admin.oidcOnlyMode': 'Отключить вход по паролю',
+ 'admin.oidcOnlyModeHint':
+ 'При включении разрешён только вход через SSO. Вход и регистрация по паролю будут заблокированы.',
+ 'admin.fileTypes': 'Разрешённые типы файлов',
+ 'admin.fileTypesHint':
+ 'Настройте, какие типы файлов могут загружать пользователи.',
+ 'admin.fileTypesFormat':
+ 'Расширения через запятую (напр. jpg,png,pdf,doc). Используйте * для разрешения всех типов.',
+ 'admin.fileTypesSaved': 'Настройки типов файлов сохранены',
+ 'admin.placesPhotos.title': 'Фотографии мест',
+ 'admin.placesPhotos.subtitle':
+ 'Загрузка фотографий из Google Places API. Отключите для экономии квоты API. Фотографии Wikimedia не затронуты.',
+ 'admin.placesAutocomplete.title': 'Автодополнение мест',
+ 'admin.placesAutocomplete.subtitle':
+ 'Использование Google Places API для поисковых подсказок. Отключите для экономии квоты API.',
+ 'admin.placesDetails.title': 'Сведения о месте',
+ 'admin.placesDetails.subtitle':
+ 'Загрузка подробной информации о месте (часы работы, рейтинг, веб-сайт) из Google Places API. Отключите для экономии квоты API.',
+ 'admin.bagTracking.title': 'Отслеживание багажа',
+ 'admin.bagTracking.subtitle': 'Включить вес и привязку к багажу для вещей',
+ 'admin.collab.chat.title': 'Чат',
+ 'admin.collab.chat.subtitle': 'Обмен сообщениями для совместной работы',
+ 'admin.collab.notes.title': 'Заметки',
+ 'admin.collab.notes.subtitle': 'Общие заметки и документы',
+ 'admin.collab.polls.title': 'Опросы',
+ 'admin.collab.polls.subtitle': 'Групповые опросы и голосования',
+ 'admin.collab.whatsnext.title': 'Что дальше',
+ 'admin.collab.whatsnext.subtitle': 'Предложения активностей и следующие шаги',
+ 'admin.tabs.config': 'Персонализация',
+ 'admin.tabs.defaults': 'Настройки по умолчанию',
+ 'admin.defaultSettings.title': 'Настройки пользователей по умолчанию',
+ 'admin.defaultSettings.description':
+ 'Задайте значения по умолчанию для всего экземпляра. Пользователи, не изменившие параметр, увидят эти значения. Их собственные изменения всегда имеют приоритет.',
+ 'admin.defaultSettings.saved': 'Значение по умолчанию сохранено',
+ 'admin.defaultSettings.reset': 'Сбросить до встроенного значения',
+ 'admin.defaultSettings.resetToBuiltIn': 'сбросить',
+ 'admin.tabs.templates': 'Шаблоны упаковки',
+ 'admin.packingTemplates.title': 'Шаблоны упаковки',
+ 'admin.packingTemplates.subtitle':
+ 'Создавайте многоразовые списки вещей для поездок',
+ 'admin.packingTemplates.create': 'Новый шаблон',
+ 'admin.packingTemplates.namePlaceholder':
+ 'Название шаблона (напр. Пляжный отдых)',
+ 'admin.packingTemplates.empty': 'Шаблоны ещё не созданы',
+ 'admin.packingTemplates.items': 'вещей',
+ 'admin.packingTemplates.categories': 'категорий',
+ 'admin.packingTemplates.itemName': 'Название вещи',
+ 'admin.packingTemplates.itemCategory': 'Категория',
+ 'admin.packingTemplates.categoryName': 'Название категории (напр. Одежда)',
+ 'admin.packingTemplates.addCategory': 'Добавить категорию',
+ 'admin.packingTemplates.created': 'Шаблон создан',
+ 'admin.packingTemplates.deleted': 'Шаблон удалён',
+ 'admin.packingTemplates.loadError': 'Ошибка загрузки шаблонов',
+ 'admin.packingTemplates.createError': 'Ошибка создания шаблона',
+ 'admin.packingTemplates.deleteError': 'Ошибка удаления шаблона',
+ 'admin.packingTemplates.saveError': 'Ошибка сохранения',
+ 'admin.tabs.addons': 'Дополнения',
+ 'admin.addons.title': 'Дополнения',
+ 'admin.addons.subtitle':
+ 'Включайте или отключайте функции для настройки TREK под себя.',
+ 'admin.addons.catalog.memories.name': 'Фото (Immich)',
+ 'admin.addons.catalog.memories.description':
+ 'Делитесь фотографиями из поездок через Immich',
+ 'admin.addons.catalog.mcp.name': 'MCP',
+ 'admin.addons.catalog.mcp.description':
+ 'Протокол контекста модели для интеграции с ИИ-ассистентами',
+ 'admin.addons.catalog.packing.name': 'Списки',
+ 'admin.addons.catalog.packing.description':
+ 'Списки вещей и задачи для ваших поездок',
+ 'admin.addons.catalog.budget.name': 'Бюджет',
+ 'admin.addons.catalog.budget.description':
+ 'Отслеживайте расходы и планируйте бюджет поездки',
+ 'admin.addons.catalog.documents.name': 'Документы',
+ 'admin.addons.catalog.documents.description':
+ 'Храните и управляйте документами для путешествий',
+ 'admin.addons.catalog.vacay.name': 'Vacay',
+ 'admin.addons.catalog.vacay.description':
+ 'Личный планировщик отпусков с календарём',
+ 'admin.addons.catalog.atlas.name': 'Atlas',
+ 'admin.addons.catalog.atlas.description':
+ 'Карта мира с посещёнными странами и статистикой путешествий',
+ 'admin.addons.catalog.collab.name': 'Collab',
+ 'admin.addons.catalog.collab.description':
+ 'Заметки в реальном времени, опросы и чат для планирования поездок',
+ 'admin.addons.subtitleBefore':
+ 'Включайте или отключайте функции для настройки ',
+ 'admin.addons.subtitleAfter': ' под себя.',
+ 'admin.addons.enabled': 'Включено',
+ 'admin.addons.disabled': 'Отключено',
+ 'admin.addons.type.trip': 'Поездка',
+ 'admin.addons.type.global': 'Глобально',
+ 'admin.addons.type.integration': 'Интеграция',
+ 'admin.addons.tripHint': 'Доступно как вкладка внутри каждой поездки',
+ 'admin.addons.globalHint':
+ 'Доступно как отдельный раздел в основной навигации',
+ 'admin.addons.integrationHint':
+ 'Фоновые сервисы и API-интеграции без отдельной страницы',
+ 'admin.addons.toast.updated': 'Дополнение обновлено',
+ 'admin.addons.toast.error': 'Не удалось обновить дополнение',
+ 'admin.addons.noAddons': 'Нет доступных дополнений',
+ 'admin.weather.title': 'Данные о погоде',
+ 'admin.weather.badge': 'С 24 марта 2026',
+ 'admin.weather.description':
+ 'TREK использует Open-Meteo как источник данных о погоде. Open-Meteo — бесплатный сервис с открытым кодом, API-ключ не требуется.',
+ 'admin.weather.forecast': 'Прогноз на 16 дней',
+ 'admin.weather.forecastDesc': 'Ранее 5 дней (OpenWeatherMap)',
+ 'admin.weather.climate': 'Исторические климатические данные',
+ 'admin.weather.climateDesc':
+ 'Средние значения за последние 85 лет для дней за пределами 16-дневного прогноза',
+ 'admin.weather.requests': '10 000 запросов / день',
+ 'admin.weather.requestsDesc': 'Бесплатно, API-ключ не требуется',
+ 'admin.weather.locationHint':
+ 'Погода основана на первом месте с координатами в каждом дне. Если ни одно место не назначено на день, в качестве ориентира используется любое место из списка.',
+ 'admin.tabs.mcpTokens': 'MCP-доступ',
+ 'admin.mcpTokens.title': 'MCP-доступ',
+ 'admin.mcpTokens.subtitle':
+ 'Управление OAuth-сессиями и API-токенами всех пользователей',
+ 'admin.mcpTokens.sectionTitle': 'API-токены',
+ 'admin.mcpTokens.owner': 'Владелец',
+ 'admin.mcpTokens.tokenName': 'Название токена',
+ 'admin.mcpTokens.created': 'Создан',
+ 'admin.mcpTokens.lastUsed': 'Последнее использование',
+ 'admin.mcpTokens.never': 'Никогда',
+ 'admin.mcpTokens.empty': 'MCP-токены ещё не созданы',
+ 'admin.mcpTokens.deleteTitle': 'Удалить токен',
+ 'admin.mcpTokens.deleteMessage':
+ 'Токен будет немедленно отозван. Пользователь потеряет доступ к MCP через этот токен.',
+ 'admin.mcpTokens.deleteSuccess': 'Токен удалён',
+ 'admin.mcpTokens.deleteError': 'Не удалось удалить токен',
+ 'admin.mcpTokens.loadError': 'Не удалось загрузить токены',
+ 'admin.oauthSessions.sectionTitle': 'OAuth-сессии',
+ 'admin.oauthSessions.clientName': 'Клиент',
+ 'admin.oauthSessions.owner': 'Владелец',
+ 'admin.oauthSessions.scopes': 'Права доступа',
+ 'admin.oauthSessions.created': 'Создано',
+ 'admin.oauthSessions.empty': 'Нет активных OAuth-сессий',
+ 'admin.oauthSessions.revokeTitle': 'Отозвать сессию',
+ 'admin.oauthSessions.revokeMessage':
+ 'Эта OAuth-сессия будет немедленно отозвана. Клиент потеряет доступ к MCP.',
+ 'admin.oauthSessions.revokeSuccess': 'Сессия отозвана',
+ 'admin.oauthSessions.revokeError': 'Не удалось отозвать сессию',
+ 'admin.oauthSessions.loadError': 'Не удалось загрузить OAuth-сессии',
+ 'admin.tabs.github': 'GitHub',
+ 'admin.audit.subtitle':
+ 'События, связанные с безопасностью и администрированием (резервные копии, пользователи, MFA, настройки).',
+ 'admin.audit.empty': 'Записей аудита пока нет.',
+ 'admin.audit.refresh': 'Обновить',
+ 'admin.audit.loadMore': 'Загрузить ещё',
+ 'admin.audit.showing': 'Загружено: {count} · всего {total}',
+ 'admin.audit.col.time': 'Время',
+ 'admin.audit.col.user': 'Пользователь',
+ 'admin.audit.col.action': 'Действие',
+ 'admin.audit.col.resource': 'Объект',
+ 'admin.audit.col.ip': 'IP',
+ 'admin.audit.col.details': 'Подробности',
+ 'admin.github.title': 'История релизов',
+ 'admin.github.subtitle': 'Последние обновления из {repo}',
+ 'admin.github.latest': 'Последний',
+ 'admin.github.prerelease': 'Пре-релиз',
+ 'admin.github.showDetails': 'Показать подробности',
+ 'admin.github.hideDetails': 'Скрыть подробности',
+ 'admin.github.loadMore': 'Загрузить ещё',
+ 'admin.github.loading': 'Загрузка...',
+ 'admin.github.support': 'Помогает продолжать разработку TREK',
+ 'admin.github.error': 'Не удалось загрузить релизы',
+ 'admin.github.by': 'от',
+ 'admin.update.available': 'Доступно обновление',
+ 'admin.update.text':
+ 'Доступна версия TREK {version}. У вас установлена {current}.',
+ 'admin.update.button': 'Посмотреть на GitHub',
+ 'admin.update.install': 'Установить обновление',
+ 'admin.update.confirmTitle': 'Установить обновление?',
+ 'admin.update.confirmText':
+ 'TREK будет обновлён с {current} до {version}. Сервер перезапустится автоматически.',
+ 'admin.update.dataInfo':
+ 'Все ваши данные (поездки, пользователи, API-ключи, загрузки, Vacay, Atlas, бюджеты) будут сохранены.',
+ 'admin.update.warning':
+ 'Приложение будет кратковременно недоступно во время перезапуска.',
+ 'admin.update.confirm': 'Обновить сейчас',
+ 'admin.update.installing': 'Обновление…',
+ 'admin.update.success': 'Обновление установлено! Сервер перезапускается…',
+ 'admin.update.failed': 'Ошибка обновления',
+ 'admin.update.backupHint':
+ 'Рекомендуем создать резервную копию перед обновлением.',
+ 'admin.update.backupLink': 'Перейти к резервным копиям',
+ 'admin.update.howTo': 'Как обновить',
+ 'admin.update.dockerText':
+ 'Ваш экземпляр TREK работает в Docker. Для обновления до {version} выполните следующие команды на сервере:',
+ 'admin.update.reloadHint': 'Перезагрузите страницу через несколько секунд.',
+ 'admin.tabs.permissions': 'Разрешения',
+ 'admin.notifications.emailPanel.title': 'Email (SMTP)',
+ 'admin.notifications.webhookPanel.title': 'Webhook',
+ 'admin.notifications.inappPanel.title': 'In-App',
+ 'admin.notifications.inappPanel.hint':
+ 'Уведомления в приложении всегда активны и не могут быть отключены глобально.',
+ 'admin.notifications.adminWebhookPanel.title': 'Вебхук администратора',
+ 'admin.notifications.adminWebhookPanel.hint':
+ 'Этот вебхук используется исключительно для уведомлений администратора (например, оповещения о версиях). Он независим от пользовательских вебхуков и отправляется автоматически при наличии URL.',
+ 'admin.notifications.adminWebhookPanel.saved':
+ 'URL вебхука администратора сохранён',
+ 'admin.notifications.adminWebhookPanel.testSuccess':
+ 'Тестовый вебхук успешно отправлен',
+ 'admin.notifications.adminWebhookPanel.testFailed':
+ 'Ошибка тестового вебхука',
+ 'admin.notifications.adminWebhookPanel.alwaysOnHint':
+ 'Вебхук администратора отправляется автоматически при наличии URL',
+ 'admin.notifications.ntfy': 'Ntfy',
+ 'admin.ntfy.hint':
+ 'Позволяет пользователям настраивать собственные темы ntfy для push-уведомлений. Установите сервер по умолчанию ниже, чтобы предварительно заполнить настройки пользователей.',
+ 'admin.notifications.testNtfy': 'Отправить тестовое Ntfy',
+ 'admin.notifications.testNtfySuccess': 'Тестовое Ntfy успешно отправлено',
+ 'admin.notifications.testNtfyFailed': 'Ошибка отправки тестового Ntfy',
+ 'admin.notifications.adminNtfyPanel.title': 'Ntfy администратора',
+ 'admin.notifications.adminNtfyPanel.hint':
+ 'Эта тема Ntfy используется исключительно для уведомлений администратора (например, оповещения о версиях). Она независима от тем пользователей и всегда отправляется при наличии настройки.',
+ 'admin.notifications.adminNtfyPanel.serverLabel': 'URL сервера Ntfy',
+ 'admin.notifications.adminNtfyPanel.serverHint':
+ 'Также используется как сервер по умолчанию для ntfy-уведомлений пользователей. Оставьте пустым, чтобы использовать ntfy.sh. Пользователи могут изменить это в своих настройках.',
+ 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh',
+ 'admin.notifications.adminNtfyPanel.topicLabel': 'Тема администратора',
+ 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts',
+ 'admin.notifications.adminNtfyPanel.tokenLabel':
+ 'Токен доступа (необязательно)',
+ 'admin.notifications.adminNtfyPanel.tokenCleared':
+ 'Токен доступа администратора очищен',
+ 'admin.notifications.adminNtfyPanel.saved':
+ 'Настройки Ntfy администратора сохранены',
+ 'admin.notifications.adminNtfyPanel.test': 'Отправить тестовое Ntfy',
+ 'admin.notifications.adminNtfyPanel.testSuccess':
+ 'Тестовое Ntfy успешно отправлено',
+ 'admin.notifications.adminNtfyPanel.testFailed':
+ 'Ошибка отправки тестового Ntfy',
+ 'admin.notifications.adminNtfyPanel.alwaysOnHint':
+ 'Ntfy администратора всегда отправляется при наличии настроенной темы',
+ 'admin.notifications.adminNotificationsHint':
+ 'Настройте, какие каналы доставляют уведомления администратора (например, оповещения о версиях). Вебхук отправляется автоматически, если задан URL вебхука администратора.',
+ 'admin.notifications.tripReminders.title': 'Напоминания о поездках',
+ 'admin.notifications.tripReminders.hint':
+ 'Отправляет напоминание перед началом поездки (необходимо указать дни напоминания в параметрах поездки).',
+ 'admin.notifications.tripReminders.enabled':
+ 'Напоминания о поездках включены',
+ 'admin.notifications.tripReminders.disabled':
+ 'Напоминания о поездках отключены',
+ 'admin.tabs.notifications': 'Уведомления',
+ 'admin.addons.catalog.journey.name': 'Путешествие',
+ 'admin.addons.catalog.journey.description':
+ 'Отслеживание поездок и дневник путешествий с отметками, фото и ежедневными историями',
+ 'admin.passkey.title': 'Вход по passkey',
+ 'admin.passkey.cardHint':
+ 'Разрешите пользователям входить с помощью passkeys (WebAuthn). По умолчанию выключено.',
+ 'admin.passkey.login': 'Включить вход по passkey',
+ 'admin.passkey.loginHint':
+ 'Показывать вариант «Войти с помощью passkey» и разрешить пользователям регистрировать passkeys в своих настройках.',
+ 'admin.passkey.notConfigured':
+ 'Для этого развёртывания пока не определён домен WebAuthn. Задайте APP_URL или Relying Party ID ниже — до этого passkeys остаются скрытыми.',
+ 'admin.passkey.rpId': 'Relying Party ID (домен)',
+ 'admin.passkey.rpIdHint':
+ 'Голый домен, к которому привязаны passkeys, напр. trek.example.org. Оставьте пустым, чтобы определить его из APP_URL. Последующее изменение делает существующие passkeys недействительными.',
+ 'admin.passkey.origins': 'Разрешённые источники',
+ 'admin.passkey.originsHint':
+ 'Полные источники через запятую, напр. https://trek.example.org. Оставьте пустым, чтобы использовать APP_URL.',
+ 'admin.passkey.reset': 'Сбросить passkeys',
+ 'admin.passkey.resetHint':
+ 'Удалить все passkeys этого пользователя (напр. при потере устройства). Он по-прежнему сможет войти по паролю.',
+ 'admin.passkey.resetConfirm': 'Удалить все passkeys пользователя {name}?',
+ 'admin.passkey.resetDone': 'Удалено passkeys: {count}',
+ 'admin.defaultSettings.mapProvider': 'Картографический движок',
+ 'admin.defaultSettings.mapProviderHint': 'Карта по умолчанию для всех на этом сервере. Каждый пользователь по-прежнему может изменить её в своих настройках.',
+ 'admin.defaultSettings.providerLeaflet': 'Стандартная (бесплатно)',
+ 'admin.defaultSettings.providerMapbox': 'Mapbox (3D)',
+ 'admin.defaultSettings.mapboxToken': 'Общий токен Mapbox',
+ 'admin.defaultSettings.mapboxTokenHint': 'Используется для каждого пользователя, который не ввёл собственный токен — так весь сервер получает Mapbox без необходимости делиться ключом по отдельности. Хранится в зашифрованном виде.',
+ 'admin.defaultSettings.mapboxStyle': 'Стиль карты',
+ 'admin.defaultSettings.mapboxStylePlaceholder': 'Выберите стиль…',
+ 'admin.defaultSettings.mapbox3d': '3D-здания и рельеф',
+ 'admin.defaultSettings.mapboxQuality': 'Режим высокого качества',
+};
+export default admin;
diff --git a/shared/src/i18n/ru/airport.ts b/shared/src/i18n/ru/airport.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9ba11f16e1c0ffe643ed5b9fa1be2f88739dbed3
--- /dev/null
+++ b/shared/src/i18n/ru/airport.ts
@@ -0,0 +1,6 @@
+import type { TranslationStrings } from '../types';
+
+const airport: TranslationStrings = {
+ 'airport.searchPlaceholder': 'Код аэропорта или город (напр. FRA)',
+};
+export default airport;
diff --git a/shared/src/i18n/ru/atlas.ts b/shared/src/i18n/ru/atlas.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e7c1db8d137101dcda4a44fd8c251f53ee68192c
--- /dev/null
+++ b/shared/src/i18n/ru/atlas.ts
@@ -0,0 +1,59 @@
+import type { TranslationStrings } from '../types';
+
+const atlas: TranslationStrings = {
+ 'atlas.subtitle': 'Ваш след путешествий по всему миру',
+ 'atlas.countries': 'Страны',
+ 'atlas.trips': 'Поездки',
+ 'atlas.places': 'Места',
+ 'atlas.days': 'Дни',
+ 'atlas.visitedCountries': 'Посещённые страны',
+ 'atlas.cities': 'Города',
+ 'atlas.noData': 'Данных о поездках пока нет',
+ 'atlas.noDataHint':
+ 'Создайте поездку и добавьте места, чтобы увидеть карту мира',
+ 'atlas.lastTrip': 'Последняя поездка',
+ 'atlas.nextTrip': 'Следующая поездка',
+ 'atlas.daysLeft': 'дней осталось',
+ 'atlas.streak': 'Серия',
+ 'atlas.year': 'год',
+ 'atlas.years': 'лет',
+ 'atlas.yearInRow': 'год подряд',
+ 'atlas.yearsInRow': 'лет подряд',
+ 'atlas.tripIn': 'поездка в',
+ 'atlas.tripsIn': 'поездок в',
+ 'atlas.since': 'с',
+ 'atlas.europe': 'Европа',
+ 'atlas.asia': 'Азия',
+ 'atlas.northAmerica': 'Сев. Америка',
+ 'atlas.southAmerica': 'Юж. Америка',
+ 'atlas.africa': 'Африка',
+ 'atlas.oceania': 'Океания',
+ 'atlas.other': 'Другое',
+ 'atlas.firstVisit': 'Первая поездка',
+ 'atlas.lastVisitLabel': 'Последняя поездка',
+ 'atlas.tripSingular': 'Поездка',
+ 'atlas.tripPlural': 'Поездки',
+ 'atlas.placeVisited': 'Посещённое место',
+ 'atlas.placesVisited': 'Посещённые места',
+ 'atlas.statsTab': 'Статистика',
+ 'atlas.bucketTab': 'Список желаний',
+ 'atlas.addBucket': 'Добавить в список желаний',
+ 'atlas.bucketNamePlaceholder': 'Место или направление...',
+ 'atlas.bucketNotesPlaceholder': 'Заметки (необязательно)',
+ 'atlas.bucketEmpty': 'Ваш список желаний пуст',
+ 'atlas.bucketEmptyHint': 'Добавьте места, которые мечтаете посетить',
+ 'atlas.unmark': 'Удалить',
+ 'atlas.confirmMark': 'Отметить эту страну как посещённую?',
+ 'atlas.confirmUnmark': 'Удалить эту страну из списка посещённых?',
+ 'atlas.confirmUnmarkRegion': 'Удалить этот регион из списка посещённых?',
+ 'atlas.markVisited': 'Отметить как посещённую',
+ 'atlas.markVisitedHint': 'Добавить эту страну в список посещённых',
+ 'atlas.markRegionVisitedHint': 'Добавить этот регион в список посещённых',
+ 'atlas.addToBucket': 'В список желаний',
+ 'atlas.addPoi': 'Добавить место',
+ 'atlas.searchCountry': 'Поиск страны...',
+ 'atlas.month': 'Месяц',
+ 'atlas.addToBucketHint': 'Сохранить как место для посещения',
+ 'atlas.bucketWhen': 'Когда вы планируете поехать?',
+};
+export default atlas;
diff --git a/shared/src/i18n/ru/backup.ts b/shared/src/i18n/ru/backup.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ab880398daf65d20c09951b9b428dd091d1f4c51
--- /dev/null
+++ b/shared/src/i18n/ru/backup.ts
@@ -0,0 +1,78 @@
+import type { TranslationStrings } from '../types';
+
+const backup: TranslationStrings = {
+ 'backup.title': 'Резервная копия',
+ 'backup.subtitle': 'База данных и все загруженные файлы',
+ 'backup.refresh': 'Обновить',
+ 'backup.upload': 'Загрузить копию',
+ 'backup.uploading': 'Загрузка…',
+ 'backup.create': 'Создать копию',
+ 'backup.creating': 'Создание…',
+ 'backup.empty': 'Резервных копий нет',
+ 'backup.createFirst': 'Создать первую копию',
+ 'backup.download': 'Скачать',
+ 'backup.restore': 'Восстановить',
+ 'backup.confirm.restore':
+ 'Восстановить копию «{name}»?\n\nВсе текущие данные будут заменены данными из копии.',
+ 'backup.confirm.uploadRestore':
+ 'Загрузить и восстановить файл копии «{name}»?\n\nВсе текущие данные будут перезаписаны.',
+ 'backup.confirm.delete': 'Удалить копию «{name}»?',
+ 'backup.toast.loadError': 'Не удалось загрузить резервные копии',
+ 'backup.toast.created': 'Резервная копия создана',
+ 'backup.toast.createError': 'Не удалось создать резервную копию',
+ 'backup.toast.restored': 'Копия восстановлена. Страница перезагрузится…',
+ 'backup.toast.restoreError': 'Ошибка восстановления',
+ 'backup.toast.uploadError': 'Ошибка загрузки',
+ 'backup.toast.deleted': 'Резервная копия удалена',
+ 'backup.toast.deleteError': 'Ошибка удаления',
+ 'backup.toast.downloadError': 'Ошибка скачивания',
+ 'backup.toast.settingsSaved': 'Настройки автокопирования сохранены',
+ 'backup.toast.settingsError': 'Не удалось сохранить настройки',
+ 'backup.auto.title': 'Автокопирование',
+ 'backup.auto.subtitle': 'Автоматическое резервное копирование по расписанию',
+ 'backup.auto.enable': 'Включить автокопирование',
+ 'backup.auto.enableHint':
+ 'Резервные копии будут создаваться автоматически по выбранному расписанию',
+ 'backup.auto.interval': 'Интервал',
+ 'backup.auto.hour': 'Запуск в час',
+ 'backup.auto.hourHint': 'Местное время сервера (формат {format})',
+ 'backup.auto.dayOfWeek': 'День недели',
+ 'backup.auto.dayOfMonth': 'День месяца',
+ 'backup.auto.dayOfMonthHint':
+ 'Ограничено 1–28 для совместимости со всеми месяцами',
+ 'backup.auto.scheduleSummary': 'Расписание',
+ 'backup.auto.summaryDaily': 'Каждый день в {hour}:00',
+ 'backup.auto.summaryWeekly': 'Каждый {day} в {hour}:00',
+ 'backup.auto.summaryMonthly': '{day}-го числа каждого месяца в {hour}:00',
+ 'backup.auto.envLocked': 'Docker',
+ 'backup.auto.envLockedHint':
+ 'Автокопирование настроено через переменные окружения Docker. Чтобы изменить параметры, обновите docker-compose.yml и перезапустите контейнер.',
+ 'backup.auto.copyEnv': 'Скопировать переменные окружения Docker',
+ 'backup.auto.envCopied':
+ 'Переменные окружения Docker скопированы в буфер обмена',
+ 'backup.auto.keepLabel': 'Удалять старые копии через',
+ 'backup.dow.sunday': 'Вс',
+ 'backup.dow.monday': 'Пн',
+ 'backup.dow.tuesday': 'Вт',
+ 'backup.dow.wednesday': 'Ср',
+ 'backup.dow.thursday': 'Чт',
+ 'backup.dow.friday': 'Пт',
+ 'backup.dow.saturday': 'Сб',
+ 'backup.interval.hourly': 'Каждый час',
+ 'backup.interval.daily': 'Ежедневно',
+ 'backup.interval.weekly': 'Еженедельно',
+ 'backup.interval.monthly': 'Ежемесячно',
+ 'backup.keep.1day': '1 день',
+ 'backup.keep.3days': '3 дня',
+ 'backup.keep.7days': '7 дней',
+ 'backup.keep.14days': '14 дней',
+ 'backup.keep.30days': '30 дней',
+ 'backup.keep.forever': 'Хранить вечно',
+ 'backup.restoreConfirmTitle': 'Восстановить копию?',
+ 'backup.restoreWarning':
+ 'Все текущие данные (поездки, места, пользователи, загрузки) будут безвозвратно заменены данными из копии. Это действие нельзя отменить.',
+ 'backup.restoreTip':
+ 'Совет: создайте резервную копию текущего состояния перед восстановлением.',
+ 'backup.restoreConfirm': 'Да, восстановить',
+};
+export default backup;
diff --git a/shared/src/i18n/ru/budget.ts b/shared/src/i18n/ru/budget.ts
new file mode 100644
index 0000000000000000000000000000000000000000..df75787d71fb42fd77db559c58ee53e366ecf43d
--- /dev/null
+++ b/shared/src/i18n/ru/budget.ts
@@ -0,0 +1,117 @@
+import type { TranslationStrings } from '../types';
+
+const budget: TranslationStrings = {
+ 'budget.title': 'Бюджет',
+ 'budget.exportCsv': 'Экспорт CSV',
+ 'budget.emptyTitle': 'Бюджет ещё не создан',
+ 'budget.emptyText':
+ 'Создайте категории и записи для планирования бюджета поездки',
+ 'budget.emptyPlaceholder': 'Введите название категории...',
+ 'budget.createCategory': 'Создать категорию',
+ 'budget.category': 'Категория',
+ 'budget.categoryName': 'Название категории',
+ 'budget.table.name': 'Название',
+ 'budget.table.total': 'Итого',
+ 'budget.table.persons': 'Человек',
+ 'budget.table.days': 'Дней',
+ 'budget.table.perPerson': 'На человека',
+ 'budget.table.perDay': 'В день',
+ 'budget.table.perPersonDay': 'Чел. / день',
+ 'budget.table.note': 'Заметка',
+ 'budget.table.date': 'Дата',
+ 'budget.newEntry': 'Новая запись',
+ 'budget.defaultEntry': 'Новая запись',
+ 'budget.defaultCategory': 'Новая категория',
+ 'budget.total': 'Итого',
+ 'budget.totalBudget': 'Общий бюджет',
+ 'budget.byCategory': 'По категориям',
+ 'budget.editTooltip': 'Нажмите для редактирования',
+ 'budget.linkedToReservation':
+ 'Связано с бронированием — редактируйте название там',
+ 'budget.confirm.deleteCategory':
+ 'Вы уверены, что хотите удалить категорию «{name}» с {count} записями?',
+ 'budget.deleteCategory': 'Удалить категорию',
+ 'budget.perPerson': 'На человека',
+ 'budget.paid': 'Оплачено',
+ 'budget.open': 'Не оплачено',
+ 'budget.noMembers': 'Участники не назначены',
+ 'budget.settlement': 'Взаиморасчёт',
+ 'budget.settlementInfo':
+ 'Нажмите на аватар участника в строке бюджета, чтобы отметить его зелёным — это значит, что он заплатил. Взаиморасчёт покажет, кто кому и сколько должен.',
+ 'budget.netBalances': 'Чистые балансы',
+ 'budget.categoriesLabel': 'категорий',
+ "costs.you": "Вы",
+ "costs.youShort": "В",
+ "costs.youLower": "вы",
+ "costs.youOwe": "Вы должны",
+ "costs.youOweSub": "Вы должны заплатить другим",
+ "costs.youreOwed": "Вам должны",
+ "costs.youreOwedSub": "Другие должны заплатить вам",
+ "costs.totalSpend": "Общие расходы поездки",
+ "costs.totalSpendSub": "По всем участникам",
+ "costs.to": "Кому",
+ "costs.from": "От",
+ "costs.allSettled": "У вас всё рассчитано",
+ "costs.nothingOwed": "Вам ничего не должны",
+ "costs.yourShare": "Ваша доля",
+ "costs.youPaid": "Вы заплатили",
+ "costs.expenses": "Расходы",
+ "costs.entries": "{count} записей",
+ "costs.searchPlaceholder": "Поиск расходов…",
+ "costs.filter.all": "Все",
+ "costs.filter.mine": "Оплачено мной",
+ "costs.filter.owed": "Мне должны",
+ "costs.addExpense": "Добавить расход",
+ "costs.editExpense": "Изменить расход",
+ "costs.noMatch": "Нет расходов по вашему запросу.",
+ "costs.emptyText": "Расходов пока нет. Добавьте первый.",
+ "costs.spent": "потрачено {amount}",
+ "costs.noDate": "Без даты",
+ "costs.noOnePaid": "Пока никто не заплатил",
+ "costs.youLent": "вы одолжили {amount}",
+ "costs.youBorrowed": "вы заняли {amount}",
+ "costs.settleUp": "Рассчитаться",
+ "costs.history": "История",
+ "costs.everyoneSquare": "Все в расчёте",
+ "costs.nothingOutstanding": "Сейчас нет неоплаченных платежей.",
+ "costs.pay": "платит",
+ "costs.pays": "платит",
+ "costs.settle": "Рассчитать",
+ "costs.balances": "Балансы",
+ "costs.byCategory": "По категориям",
+ "costs.noCategories": "Расходов пока нет.",
+ "costs.settleHistory": "История расчётов",
+ "costs.noSettlements": "Расчётов пока нет.",
+ "costs.paymentsSettled": "Рассчитано платежей: {count}",
+ "costs.paid": "оплачено",
+ "costs.undo": "Отменить",
+ "costs.whatFor": "За что это было?",
+ "costs.namePlaceholder": "напр. ужин, сувениры, бензин…",
+ "costs.totalAmount": "Общая сумма",
+ "costs.currency": "Валюта",
+ "costs.day": "День",
+ "costs.rateLabel": "1 {from} в {to}",
+ "costs.category": "Категория",
+ "costs.whoPaid": "Кто заплатил?",
+ "costs.splitBetween": "Поделить поровну между",
+ "costs.pickSomeone": "Выберите хотя бы одного человека для разделения.",
+ "costs.splitSummary": "Разделено на {count} · по {amount}",
+ "costs.cat.accommodation": "Проживание",
+ "costs.cat.food": "Еда и напитки",
+ "costs.cat.groceries": "Продукты",
+ "costs.cat.transport": "Транспорт",
+ "costs.cat.flights": "Авиаперелёты",
+ "costs.cat.activities": "Развлечения",
+ "costs.cat.sightseeing": "Достопримечательности",
+ "costs.cat.shopping": "Покупки",
+ "costs.cat.fees": "Сборы и билеты",
+ "costs.cat.health": "Здоровье",
+ "costs.cat.tips": "Чаевые",
+ "costs.cat.other": "Прочее",
+ "costs.daysCount": "{count} дней",
+ "costs.travelers": "{count} путешественников",
+ "costs.liveRate": "актуальный курс",
+ "costs.settleAll": "Рассчитать всё",
+};
+
+export default budget;
diff --git a/shared/src/i18n/ru/categories.ts b/shared/src/i18n/ru/categories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f0bc09de6c4ccf19098732dc7b095fb7d17b4db2
--- /dev/null
+++ b/shared/src/i18n/ru/categories.ts
@@ -0,0 +1,26 @@
+import type { TranslationStrings } from '../types';
+
+const categories: TranslationStrings = {
+ 'categories.title': 'Категории',
+ 'categories.subtitle': 'Управление категориями мест',
+ 'categories.new': 'Новая категория',
+ 'categories.empty': 'Категорий пока нет',
+ 'categories.namePlaceholder': 'Название категории',
+ 'categories.icon': 'Иконка',
+ 'categories.color': 'Цвет',
+ 'categories.customColor': 'Выбрать свой цвет',
+ 'categories.preview': 'Предпросмотр',
+ 'categories.defaultName': 'Категория',
+ 'categories.update': 'Обновить',
+ 'categories.create': 'Создать',
+ 'categories.confirm.delete':
+ 'Удалить категорию? Места в этой категории не будут удалены.',
+ 'categories.toast.loadError': 'Не удалось загрузить категории',
+ 'categories.toast.nameRequired': 'Введите название',
+ 'categories.toast.updated': 'Категория обновлена',
+ 'categories.toast.created': 'Категория создана',
+ 'categories.toast.saveError': 'Ошибка сохранения',
+ 'categories.toast.deleted': 'Категория удалена',
+ 'categories.toast.deleteError': 'Ошибка удаления',
+};
+export default categories;
diff --git a/shared/src/i18n/ru/collab.ts b/shared/src/i18n/ru/collab.ts
new file mode 100644
index 0000000000000000000000000000000000000000..88ada7b906260061246dd43c1bf1d2b7f99a2f0b
--- /dev/null
+++ b/shared/src/i18n/ru/collab.ts
@@ -0,0 +1,77 @@
+import type { TranslationStrings } from '../types';
+
+const collab: TranslationStrings = {
+ 'collab.tabs.chat': 'Чат',
+ 'collab.tabs.notes': 'Заметки',
+ 'collab.tabs.polls': 'Опросы',
+ 'collab.whatsNext.title': 'Что дальше',
+ 'collab.whatsNext.today': 'Сегодня',
+ 'collab.whatsNext.tomorrow': 'Завтра',
+ 'collab.whatsNext.empty': 'Нет предстоящих активностей',
+ 'collab.whatsNext.until': 'до',
+ 'collab.whatsNext.emptyHint':
+ 'Активности со временем будут отображаться здесь',
+ 'collab.chat.send': 'Отправить',
+ 'collab.chat.placeholder': 'Введите сообщение...',
+ 'collab.chat.empty': 'Начните разговор',
+ 'collab.chat.emptyHint': 'Сообщения видны всем участникам поездки',
+ 'collab.chat.emptyDesc':
+ 'Делитесь идеями, планами и новостями с вашей группой',
+ 'collab.chat.today': 'Сегодня',
+ 'collab.chat.yesterday': 'Вчера',
+ 'collab.chat.deletedMessage': 'удалил(а) сообщение',
+ 'collab.chat.reply': 'Ответить',
+ 'collab.chat.loadMore': 'Загрузить старые сообщения',
+ 'collab.chat.justNow': 'только что',
+ 'collab.chat.minutesAgo': '{n} мин. назад',
+ 'collab.chat.hoursAgo': '{n} ч. назад',
+ 'collab.notes.title': 'Заметки',
+ 'collab.notes.new': 'Новая заметка',
+ 'collab.notes.empty': 'Заметок пока нет',
+ 'collab.notes.emptyHint': 'Начните записывать идеи и планы',
+ 'collab.notes.all': 'Все',
+ 'collab.notes.titlePlaceholder': 'Название заметки',
+ 'collab.notes.contentPlaceholder': 'Напишите что-нибудь...',
+ 'collab.notes.categoryPlaceholder': 'Категория',
+ 'collab.notes.newCategory': 'Новая категория...',
+ 'collab.notes.category': 'Категория',
+ 'collab.notes.noCategory': 'Без категории',
+ 'collab.notes.color': 'Цвет',
+ 'collab.notes.save': 'Сохранить',
+ 'collab.notes.cancel': 'Отмена',
+ 'collab.notes.edit': 'Редактировать',
+ 'collab.notes.delete': 'Удалить',
+ 'collab.notes.confirmDeleteTitle': 'Удалить заметку?',
+ 'collab.notes.confirmDeleteBody': 'Эта заметка будет удалена безвозвратно.',
+ 'collab.notes.pin': 'Закрепить',
+ 'collab.notes.unpin': 'Открепить',
+ 'collab.notes.daysAgo': '{n} дн. назад',
+ 'collab.notes.categorySettings': 'Управление категориями',
+ 'collab.notes.create': 'Создать',
+ 'collab.notes.website': 'Сайт',
+ 'collab.notes.websitePlaceholder': 'https://...',
+ 'collab.notes.attachFiles': 'Прикрепить файлы',
+ 'collab.notes.noCategoriesYet': 'Категорий пока нет',
+ 'collab.notes.emptyDesc': 'Создайте заметку, чтобы начать',
+ 'collab.polls.title': 'Опросы',
+ 'collab.polls.new': 'Новый опрос',
+ 'collab.polls.empty': 'Опросов пока нет',
+ 'collab.polls.emptyHint': 'Задайте вопрос группе и голосуйте вместе',
+ 'collab.polls.question': 'Вопрос',
+ 'collab.polls.questionPlaceholder': 'Что нам делать?',
+ 'collab.polls.addOption': '+ Добавить вариант',
+ 'collab.polls.optionPlaceholder': 'Вариант {n}',
+ 'collab.polls.create': 'Создать опрос',
+ 'collab.polls.close': 'Закрыть',
+ 'collab.polls.closed': 'Закрыт',
+ 'collab.polls.votes': '{n} голосов',
+ 'collab.polls.vote': '{n} голос',
+ 'collab.polls.multipleChoice': 'Множественный выбор',
+ 'collab.polls.multiChoice': 'Множественный выбор',
+ 'collab.polls.deadline': 'Срок',
+ 'collab.polls.option': 'Вариант',
+ 'collab.polls.options': 'Варианты',
+ 'collab.polls.delete': 'Удалить',
+ 'collab.polls.closedSection': 'Закрытые',
+};
+export default collab;
diff --git a/shared/src/i18n/ru/common.ts b/shared/src/i18n/ru/common.ts
new file mode 100644
index 0000000000000000000000000000000000000000..73c255fd12ee0a52ec7a45979594530c43663524
--- /dev/null
+++ b/shared/src/i18n/ru/common.ts
@@ -0,0 +1,54 @@
+import type { TranslationStrings } from '../types';
+
+const common: TranslationStrings = {
+ 'common.save': 'Сохранить',
+ 'common.showMore': 'Показать больше',
+ 'common.showLess': 'Показать меньше',
+ 'common.cancel': 'Отмена',
+ 'common.clear': 'Очистить',
+ 'common.delete': 'Удалить',
+ 'common.edit': 'Редактировать',
+ 'common.add': 'Добавить',
+ 'common.loading': 'Загрузка...',
+ 'common.import': 'Импорт',
+ 'common.select': 'Выбрать',
+ 'common.selectAll': 'Выбрать всё',
+ 'common.deselectAll': 'Снять выделение со всех',
+ 'common.error': 'Ошибка',
+ 'common.unknownError': 'Неизвестная ошибка',
+ 'common.tooManyAttempts': 'Слишком много попыток. Попробуйте позже.',
+ 'common.back': 'Назад',
+ 'common.all': 'Все',
+ 'common.close': 'Закрыть',
+ 'common.open': 'Открыть',
+ 'common.upload': 'Загрузить',
+ 'common.search': 'Поиск',
+ 'common.confirm': 'Подтвердить',
+ 'common.ok': 'ОК',
+ 'common.yes': 'Да',
+ 'common.no': 'Нет',
+ 'common.or': 'или',
+ 'common.none': 'Нет',
+ 'common.date': 'Дата',
+ 'common.rename': 'Переименовать',
+ 'common.discardChanges': 'Отменить изменения',
+ 'common.discard': 'Отменить',
+ 'common.name': 'Имя',
+ 'common.email': 'Эл. почта',
+ 'common.password': 'Пароль',
+ 'common.saving': 'Сохранение...',
+ 'common.saved': 'Сохранено',
+ 'common.expand': 'Развернуть',
+ 'common.collapse': 'Свернуть',
+ 'common.update': 'Обновить',
+ 'common.change': 'Изменить',
+ 'common.uploading': 'Загрузка…',
+ 'common.backToPlanning': 'Вернуться к планированию',
+ 'common.reset': 'Сбросить',
+ 'common.copy': 'Копировать',
+ 'common.copied': 'Скопировано',
+ 'common.justNow': 'только что',
+ 'common.hoursAgo': '{count} ч назад',
+ 'common.daysAgo': '{count} д назад',
+};
+export default common;
diff --git a/shared/src/i18n/ru/dashboard.ts b/shared/src/i18n/ru/dashboard.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c2ac8cf129abbb8def5c596c481b6ec36bf87779
--- /dev/null
+++ b/shared/src/i18n/ru/dashboard.ts
@@ -0,0 +1,168 @@
+import type { TranslationStrings } from '../types';
+
+const dashboard: TranslationStrings = {
+ 'dashboard.title': 'Мои поездки',
+ 'dashboard.subtitle.loading': 'Загрузка поездок...',
+ 'dashboard.subtitle.trips': '{count} поездок ({archived} в архиве)',
+ 'dashboard.subtitle.empty': 'Начните свою первую поездку',
+ 'dashboard.subtitle.activeOne': '{count} активная поездка',
+ 'dashboard.subtitle.activeMany': '{count} активных поездок',
+ 'dashboard.subtitle.archivedSuffix': ' · {count} в архиве',
+ 'dashboard.newTrip': 'Новая поездка',
+ 'dashboard.newTripSub': 'Спланируйте новую поездку с нуля',
+ 'dashboard.gridView': 'Плитка',
+ 'dashboard.listView': 'Список',
+ 'dashboard.currency': 'Валюта',
+ 'dashboard.timezone': 'Часовые пояса',
+ 'dashboard.localTime': 'Местное',
+ 'dashboard.timezoneCustomTitle': 'Свой часовой пояс',
+ 'dashboard.timezoneCustomLabelPlaceholder': 'Название (необязательно)',
+ 'dashboard.timezoneCustomTzPlaceholder': 'напр. America/New_York',
+ 'dashboard.timezoneCustomAdd': 'Добавить',
+ 'dashboard.timezoneCustomErrorEmpty': 'Введите идентификатор часового пояса',
+ 'dashboard.timezoneCustomErrorInvalid':
+ 'Неверный часовой пояс. Используйте формат Europe/Berlin',
+ 'dashboard.timezoneCustomErrorDuplicate': 'Уже добавлен',
+ 'dashboard.emptyTitle': 'Нет поездок',
+ 'dashboard.emptyText': 'Создайте свою первую поездку и начните планировать!',
+ 'dashboard.emptyButton': 'Создать первую поездку',
+ 'dashboard.nextTrip': 'Следующая поездка',
+ 'dashboard.shared': 'Общая',
+ 'dashboard.sharedBy': 'Поделился {name}',
+ 'dashboard.days': 'Дни',
+ 'dashboard.places': 'Места',
+ 'dashboard.members': 'Попутчики',
+ 'dashboard.archive': 'Архивировать',
+ 'dashboard.copyTrip': 'Копировать',
+ 'dashboard.copySuffix': 'копия',
+ 'dashboard.restore': 'Восстановить',
+ 'dashboard.archived': 'В архиве',
+ 'dashboard.status.ongoing': 'В процессе',
+ 'dashboard.status.today': 'Сегодня',
+ 'dashboard.status.tomorrow': 'Завтра',
+ 'dashboard.status.past': 'Прошло',
+ 'dashboard.status.daysLeft': 'осталось {count} дн.',
+ 'dashboard.toast.loadError': 'Не удалось загрузить поездки',
+ 'dashboard.toast.created': 'Поездка создана!',
+ 'dashboard.toast.createError': 'Не удалось создать поездку',
+ 'dashboard.toast.updated': 'Поездка обновлена!',
+ 'dashboard.toast.updateError': 'Не удалось обновить поездку',
+ 'dashboard.toast.deleted': 'Поездка удалена',
+ 'dashboard.toast.deleteError': 'Не удалось удалить поездку',
+ 'dashboard.toast.archived': 'Поездка архивирована',
+ 'dashboard.toast.archiveError': 'Не удалось архивировать поездку',
+ 'dashboard.toast.restored': 'Поездка восстановлена',
+ 'dashboard.toast.restoreError': 'Не удалось восстановить поездку',
+ 'dashboard.toast.copied': 'Поездка скопирована!',
+ 'dashboard.toast.copyError': 'Не удалось скопировать поездку',
+ 'dashboard.confirm.delete':
+ 'Удалить поездку «{title}»? Все места и планы будут безвозвратно удалены.',
+ 'dashboard.editTrip': 'Редактировать поездку',
+ 'dashboard.createTrip': 'Создать новую поездку',
+ 'dashboard.tripTitle': 'Название',
+ 'dashboard.tripTitlePlaceholder': 'напр. Лето в Японии',
+ 'dashboard.tripDescription': 'Описание',
+ 'dashboard.tripDescriptionPlaceholder': 'О чём эта поездка?',
+ 'dashboard.startDate': 'Дата начала',
+ 'dashboard.endDate': 'Дата окончания',
+ 'dashboard.dayCount': 'Количество дней',
+ 'dashboard.dayCountHint':
+ 'Сколько дней планировать, если даты поездки не указаны.',
+ 'dashboard.noDateHint':
+ 'Дата не указана — будет создано 7 дней по умолчанию. Вы можете изменить это в любое время.',
+ 'dashboard.coverImage': 'Обложка',
+ 'dashboard.addCoverImage': 'Добавить обложку',
+ 'dashboard.addMembers': 'Попутчики',
+ 'dashboard.addMember': 'Добавить участника',
+ 'dashboard.coverSaved': 'Обложка сохранена',
+ 'dashboard.coverUploadError': 'Ошибка загрузки',
+ 'dashboard.coverRemoveError': 'Ошибка удаления',
+ 'dashboard.titleRequired': 'Название обязательно',
+ 'dashboard.endDateError': 'Дата окончания должна быть позже даты начала',
+ 'dashboard.greeting.morning': 'Доброе утро,',
+ 'dashboard.greeting.afternoon': 'Добрый день,',
+ 'dashboard.greeting.evening': 'Добрый вечер,',
+ 'dashboard.mobile.liveNow': 'Сейчас в пути',
+ 'dashboard.mobile.tripProgress': 'Прогресс поездки',
+ 'dashboard.mobile.daysLeft': 'осталось {count} дн.',
+ 'dashboard.mobile.places': 'Места',
+ 'dashboard.mobile.buddies': 'Попутчики',
+ 'dashboard.mobile.newTrip': 'Новая поездка',
+ 'dashboard.mobile.currency': 'Валюта',
+ 'dashboard.mobile.timezone': 'Часовой пояс',
+ 'dashboard.mobile.upcomingTrips': 'Предстоящие поездки',
+ 'dashboard.mobile.yourTrips': 'Ваши поездки',
+ 'dashboard.mobile.trips': 'поездок',
+ 'dashboard.mobile.starts': 'Начало',
+ 'dashboard.mobile.duration': 'Продолжительность',
+ 'dashboard.mobile.day': 'день',
+ 'dashboard.mobile.days': 'дней',
+ 'dashboard.mobile.ongoing': 'В процессе',
+ 'dashboard.mobile.startsToday': 'Начинается сегодня',
+ 'dashboard.mobile.tomorrow': 'Завтра',
+ 'dashboard.mobile.inDays': 'Через {count} дн.',
+ 'dashboard.mobile.inMonths': 'Через {count} мес.',
+ 'dashboard.mobile.completed': 'Завершено',
+ 'dashboard.mobile.currencyConverter': 'Конвертер валют',
+ 'dashboard.filter.planned': 'Запланированные',
+ 'dashboard.hero.badgeLive': 'СЕЙЧАС В ПУТИ',
+ 'dashboard.hero.badgeToday': 'НАЧИНАЕТСЯ СЕГОДНЯ',
+ 'dashboard.hero.badgeTomorrow': 'ЗАВТРА',
+ 'dashboard.hero.badgeNext': 'ДАЛЕЕ',
+ 'dashboard.hero.badgeRecent': 'НЕДАВНО',
+ 'dashboard.hero.tripDates': 'Даты поездки',
+ 'dashboard.hero.noDates': 'Даты не заданы',
+ 'dashboard.hero.travelerOne': '{count} путешественник',
+ 'dashboard.hero.travelerMany': '{count} путешественников',
+ 'dashboard.hero.destinationOne': '{count} направление',
+ 'dashboard.hero.destinationMany': '{count} направлений',
+ 'dashboard.hero.dayUnitOne': 'день',
+ 'dashboard.hero.dayUnitMany': 'дн.',
+ 'dashboard.hero.dayLeft': 'День остался',
+ 'dashboard.hero.daysLeft': 'Дней осталось',
+ 'dashboard.hero.lastDay': 'Последний день',
+ 'dashboard.hero.untilStart': 'До начала',
+ 'dashboard.hero.startsIn': 'Старт через',
+ 'dashboard.atlas.countriesVisited': 'Атлас · Посещённые страны',
+ 'dashboard.atlas.ofTotal': 'из {total}',
+ 'dashboard.atlas.tripsTotal': 'Всего поездок',
+ 'dashboard.atlas.placesMapped': '{count} мест на карте',
+ 'dashboard.atlas.daysTraveled': 'Дней в пути',
+ 'dashboard.atlas.daysUnit': 'дн.',
+ 'dashboard.atlas.acrossAllTrips': 'по всем поездкам',
+ 'dashboard.atlas.distanceFlown': 'Пройдено по воздуху',
+ 'dashboard.atlas.kmUnit': 'km',
+ 'dashboard.atlas.aroundEquator': '≈ {count}× вокруг экватора',
+ 'dashboard.card.idea': 'Идея',
+ 'dashboard.card.buddyOne': 'Попутчик',
+ 'dashboard.fx.from': 'Из',
+ 'dashboard.fx.to': 'В',
+ 'dashboard.fx.unavailable': 'Курс недоступен',
+ 'dashboard.tz.searchPlaceholder': 'Поиск часового пояса…',
+ 'dashboard.tz.empty': 'Других часовых поясов пока нет — добавьте с помощью +',
+ 'dashboard.upcoming.title': 'Ближайшие брони',
+ 'dashboard.upcoming.empty': 'Пока ничего не забронировано.',
+ 'dashboard.confirm.copy.title': 'Скопировать эту поездку?',
+ 'dashboard.confirm.copy.willCopy': 'Будет скопировано',
+ 'dashboard.confirm.copy.will1': 'Дни, места и распределение по дням',
+ 'dashboard.confirm.copy.will2': 'Жильё и брони',
+ 'dashboard.confirm.copy.will3': 'Статьи бюджета и порядок категорий',
+ 'dashboard.confirm.copy.will4': 'Списки вещей (без отметок)',
+ 'dashboard.confirm.copy.will5': 'Задачи (без назначений и отметок)',
+ 'dashboard.confirm.copy.will6': 'Заметки дня',
+ 'dashboard.confirm.copy.wontCopy': 'Не будет скопировано',
+ 'dashboard.confirm.copy.wont1': 'Участники и назначения',
+ 'dashboard.confirm.copy.wont2': 'Совместные заметки, опросы и сообщения',
+ 'dashboard.confirm.copy.wont3': 'Файлы и фото',
+ 'dashboard.confirm.copy.wont4': 'Токены доступа',
+ 'dashboard.confirm.copy.confirm': 'Копировать поездку',
+ 'dashboard.aria.toggleView': 'Переключить вид',
+ 'dashboard.aria.filter': 'Фильтр',
+ 'dashboard.aria.duplicate': 'Дублировать',
+ 'dashboard.aria.refreshRates': 'Обновить курсы',
+ 'dashboard.aria.swapCurrencies': 'Поменять валюты',
+ 'dashboard.aria.addTimezone': 'Добавить часовой пояс',
+ 'dashboard.aria.removeTimezone': 'Удалить {city}',
+ 'dashboard.dayCountRequired': 'Количество дней обязательно',
+};
+export default dashboard;
diff --git a/shared/src/i18n/ru/day.ts b/shared/src/i18n/ru/day.ts
new file mode 100644
index 0000000000000000000000000000000000000000..39354320347c1b9ad07a8695f6e66222ddbaf2c5
--- /dev/null
+++ b/shared/src/i18n/ru/day.ts
@@ -0,0 +1,26 @@
+import type { TranslationStrings } from '../types';
+
+const day: TranslationStrings = {
+ 'day.precipProb': 'Вероятность осадков',
+ 'day.precipitation': 'Осадки',
+ 'day.wind': 'Ветер',
+ 'day.sunrise': 'Восход',
+ 'day.sunset': 'Закат',
+ 'day.hourlyForecast': 'Почасовой прогноз',
+ 'day.climateHint':
+ 'Исторические средние — реальный прогноз доступен за 16 дней до этой даты.',
+ 'day.noWeather': 'Данные о погоде недоступны. Добавьте место с координатами.',
+ 'day.overview': 'Обзор дня',
+ 'day.accommodation': 'Жильё',
+ 'day.addAccommodation': 'Добавить жильё',
+ 'day.hotelDayRange': 'Применить к дням',
+ 'day.noPlacesForHotel': 'Сначала добавьте места в поездку',
+ 'day.allDays': 'Все',
+ 'day.checkIn': 'Заезд',
+ 'day.checkInUntil': 'До',
+ 'day.checkOut': 'Выезд',
+ 'day.confirmation': 'Подтверждение',
+ 'day.editAccommodation': 'Редактировать жильё',
+ 'day.reservations': 'Бронирования',
+};
+export default day;
diff --git a/shared/src/i18n/ru/dayplan.ts b/shared/src/i18n/ru/dayplan.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0074840482169f772b2df97382eea94b575e74f3
--- /dev/null
+++ b/shared/src/i18n/ru/dayplan.ts
@@ -0,0 +1,61 @@
+import type { TranslationStrings } from '../types';
+
+const dayplan: TranslationStrings = {
+ 'dayplan.icsTooltip': 'Экспорт календаря (ICS)',
+ 'dayplan.emptyDay': 'На этот день мест не запланировано',
+ 'dayplan.addNote': 'Добавить заметку',
+ 'dayplan.editNote': 'Редактировать заметку',
+ 'dayplan.noteAdd': 'Добавить заметку',
+ 'dayplan.noteEdit': 'Редактировать заметку',
+ 'dayplan.noteTitle': 'Заметка',
+ 'dayplan.noteSubtitle': 'Заметка на день',
+ 'dayplan.totalCost': 'Общая стоимость',
+ 'dayplan.days': 'Дни',
+ 'dayplan.dayN': 'День {n}',
+ 'dayplan.calculating': 'Расчёт...',
+ 'dayplan.route': 'Маршрут',
+ 'dayplan.optimize': 'Оптимизировать',
+ 'dayplan.optimized': 'Маршрут оптимизирован',
+ 'dayplan.routeError': 'Не удалось рассчитать маршрут',
+ 'dayplan.toast.needTwoPlaces':
+ 'Для оптимизации маршрута нужно минимум два места',
+ 'dayplan.toast.routeOptimized': 'Маршрут оптимизирован',
+ 'dayplan.toast.routeOptimizedFromHotel':
+ 'Маршрут оптимизирован от места проживания',
+ 'dayplan.toast.noGeoPlaces':
+ 'Не найдено мест с координатами для расчёта маршрута',
+ 'dayplan.confirmed': 'Подтверждено',
+ 'dayplan.pendingRes': 'Ожидание',
+ 'dayplan.pdf': 'PDF',
+ 'dayplan.pdfTooltip': 'Экспортировать план дня в PDF',
+ 'dayplan.pdfError': 'Ошибка экспорта PDF',
+ 'dayplan.cannotReorderTransport':
+ 'Бронирования с фиксированным временем нельзя перемещать',
+ 'dayplan.confirmRemoveTimeTitle': 'Удалить время?',
+ 'dayplan.confirmRemoveTimeBody':
+ 'У этого места фиксированное время ({time}). При перемещении время будет удалено, и станет доступна свободная сортировка.',
+ 'dayplan.confirmRemoveTimeAction': 'Удалить время и переместить',
+ 'dayplan.confirmDeleteNoteTitle': 'Удалить заметку?',
+ 'dayplan.confirmDeleteNoteBody': 'Эта заметка будет удалена безвозвратно.',
+ 'dayplan.cannotDropOnTimed':
+ 'Элементы нельзя размещать между записями с фиксированным временем',
+ 'dayplan.cannotBreakChronology':
+ 'Это нарушит хронологический порядок запланированных элементов и бронирований',
+ 'dayplan.mobile.addPlace': 'Добавить место',
+ 'dayplan.mobile.searchPlaces': 'Поиск мест...',
+ 'dayplan.mobile.allAssigned': 'Все места распределены',
+ 'dayplan.mobile.noMatch': 'Нет совпадений',
+ 'dayplan.mobile.createNew': 'Создать новое место',
+ 'dayplan.expandAll': 'Expand all days', // en-fallback
+ 'dayplan.collapseAll': 'Collapse all days', // en-fallback
+ 'dayplan.reorderDays': 'Изменить порядок дней',
+ 'dayplan.reorderTitle': 'Изменить порядок дней',
+ 'dayplan.reorderHint': 'Места, заметки и бронирования дня перемещаются вместе с ним.',
+ 'dayplan.addDay': 'Добавить день',
+ 'dayplan.moveUp': 'Вверх',
+ 'dayplan.moveDown': 'Вниз',
+ 'dayplan.reorderUndo': 'Изменить порядок дней',
+ 'dayplan.reorderError': 'Не удалось изменить порядок дней',
+ 'dayplan.addDayError': 'Не удалось добавить день',
+};
+export default dayplan;
diff --git a/shared/src/i18n/ru/externalNotifications.ts b/shared/src/i18n/ru/externalNotifications.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4202da2d44d924ebb9dcda13b8605b19e475a553
--- /dev/null
+++ b/shared/src/i18n/ru/externalNotifications.ts
@@ -0,0 +1,63 @@
+import type { NotificationLocale } from '../externalNotifications/types';
+
+const ru: NotificationLocale = {
+ email: {
+ footer: 'Вы получили это, потому что у вас включены уведомления в TREK.',
+ manage: 'Управление настройками',
+ madeWith: 'Made with',
+ openTrek: 'Открыть TREK',
+ },
+ events: {
+ trip_invite: (p) => ({
+ title: `Приглашение в "${p.trip}"`,
+ body: `${p.actor} пригласил ${p.invitee || 'участника'} в поездку "${p.trip}".`,
+ }),
+ booking_change: (p) => ({
+ title: `Новое бронирование: ${p.booking}`,
+ body: `${p.actor} добавил бронирование "${p.booking}" (${p.type}) в "${p.trip}".`,
+ }),
+ trip_reminder: (p) => ({
+ title: `Напоминание: ${p.trip}`,
+ body: `Ваша поездка "${p.trip}" скоро начнётся!`,
+ }),
+ todo_due: (p) => ({
+ title: `Задача к сроку: ${p.todo}`,
+ body: `"${p.todo}" в поездке "${p.trip}" — срок ${p.due}.`,
+ }),
+ vacay_invite: (p) => ({
+ title: 'Приглашение Vacay Fusion',
+ body: `${p.actor} приглашает вас объединить планы отпуска. Откройте TREK для подтверждения.`,
+ }),
+ photos_shared: (p) => ({
+ title: `${p.count} фото`,
+ body: `${p.actor} поделился ${p.count} фото в "${p.trip}".`,
+ }),
+ collab_message: (p) => ({
+ title: `Новое сообщение в "${p.trip}"`,
+ body: `${p.actor}: ${p.preview}`,
+ }),
+ packing_tagged: (p) => ({
+ title: `Список вещей: ${p.category}`,
+ body: `${p.actor} назначил вас в категорию "${p.category}" в "${p.trip}".`,
+ }),
+ version_available: (p) => ({
+ title: 'Доступна новая версия TREK',
+ body: `TREK ${p.version} теперь доступен. Перейдите в панель администратора для обновления.`,
+ }),
+ synology_session_cleared: () => ({
+ title: 'Сессия Synology сброшена',
+ body: 'Ваш аккаунт или URL Synology изменился. Вы вышли из Synology Photos.',
+ }),
+ },
+ passwordReset: {
+ subject: 'Сброс пароля',
+ greeting: 'Здравствуйте',
+ body: 'Мы получили запрос на сброс пароля вашего аккаунта TREK. Нажмите кнопку ниже, чтобы установить новый пароль.',
+ ctaIntro: 'Сбросить пароль',
+ expiry: 'Ссылка действительна 60 минут.',
+ ignore:
+ 'Если вы не запрашивали сброс — просто проигнорируйте это письмо, пароль останется прежним.',
+ },
+};
+
+export default ru;
diff --git a/shared/src/i18n/ru/files.ts b/shared/src/i18n/ru/files.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f706806985d6e882feac2d24665e8aac0f0bffad
--- /dev/null
+++ b/shared/src/i18n/ru/files.ts
@@ -0,0 +1,63 @@
+import type { TranslationStrings } from '../types';
+
+const files: TranslationStrings = {
+ 'files.title': 'Файлы',
+ 'files.pageTitle': 'Файлы и документы',
+ 'files.subtitle': '{count} файлов для {trip}',
+ 'files.download': 'Скачать',
+ 'files.openError': 'Не удалось открыть файл',
+ 'files.downloadPdf': 'Скачать PDF',
+ 'files.count': '{count} файлов',
+ 'files.countSingular': '1 файл',
+ 'files.uploaded': '{count} загружено',
+ 'files.uploadError': 'Ошибка загрузки',
+ 'files.dropzone': 'Перетащите файлы сюда',
+ 'files.dropzoneHint': 'или нажмите для выбора',
+ 'files.allowedTypes':
+ 'Изображения, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Макс. 50 МБ',
+ 'files.uploading': 'Загрузка...',
+ 'files.filterAll': 'Все',
+ 'files.filterPdf': 'PDF',
+ 'files.filterImages': 'Изображения',
+ 'files.filterDocs': 'Документы',
+ 'files.filterCollab': 'Заметки Collab',
+ 'files.sourceCollab': 'Из заметок Collab',
+ 'files.empty': 'Файлов пока нет',
+ 'files.emptyHint': 'Загрузите файлы, чтобы прикрепить их к поездке',
+ 'files.openTab': 'Открыть в новой вкладке',
+ 'files.confirm.delete': 'Вы уверены, что хотите удалить этот файл?',
+ 'files.toast.deleted': 'Файл удалён',
+ 'files.toast.deleteError': 'Не удалось удалить файл',
+ 'files.sourcePlan': 'План дня',
+ 'files.sourceBooking': 'Бронирование',
+ 'files.sourceTransport': 'Транспорт',
+ 'files.attach': 'Прикрепить',
+ 'files.pasteHint':
+ 'Также можно вставить изображения из буфера обмена (Ctrl+V)',
+ 'files.trash': 'Корзина',
+ 'files.trashEmpty': 'Корзина пуста',
+ 'files.emptyTrash': 'Очистить корзину',
+ 'files.restore': 'Восстановить',
+ 'files.star': 'В избранное',
+ 'files.unstar': 'Из избранного',
+ 'files.assign': 'Назначить',
+ 'files.assignTitle': 'Назначить файл',
+ 'files.assignPlace': 'Место',
+ 'files.assignBooking': 'Бронирование',
+ 'files.assignTransport': 'Транспорт',
+ 'files.unassigned': 'Не назначен',
+ 'files.unlink': 'Удалить связь',
+ 'files.toast.trashed': 'Перемещено в корзину',
+ 'files.toast.restored': 'Файл восстановлен',
+ 'files.toast.trashEmptied': 'Корзина очищена',
+ 'files.toast.assigned': 'Файл назначен',
+ 'files.toast.assignError': 'Ошибка назначения',
+ 'files.toast.restoreError': 'Ошибка восстановления',
+ 'files.confirm.permanentDelete':
+ 'Безвозвратно удалить этот файл? Это действие нельзя отменить.',
+ 'files.confirm.emptyTrash':
+ 'Безвозвратно удалить все файлы из корзины? Это действие нельзя отменить.',
+ 'files.noteLabel': 'Заметка',
+ 'files.notePlaceholder': 'Добавить заметку...',
+};
+export default files;
diff --git a/shared/src/i18n/ru/index.ts b/shared/src/i18n/ru/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..77aeb6a09fb62e2522e8b4c04e4169a94c2f8d99
--- /dev/null
+++ b/shared/src/i18n/ru/index.ts
@@ -0,0 +1,86 @@
+import admin from './admin';
+import airport from './airport';
+import atlas from './atlas';
+import backup from './backup';
+import budget from './budget';
+import categories from './categories';
+import collab from './collab';
+import common from './common';
+import dashboard from './dashboard';
+import day from './day';
+import dayplan from './dayplan';
+import files from './files';
+import inspector from './inspector';
+import journey from './journey';
+import login from './login';
+import map from './map';
+import members from './members';
+import memories from './memories';
+import nav from './nav';
+import notif from './notif';
+import notifications from './notifications';
+import oauth from './oauth';
+import packing from './packing';
+import pdf from './pdf';
+import perm from './perm';
+import photos from './photos';
+import places from './places';
+import planner from './planner';
+import register from './register';
+import reservations from './reservations';
+import settings from './settings';
+import share from './share';
+import shared from './shared';
+import stats from './stats';
+import system_notice from './system_notice';
+import todo from './todo';
+import transport from './transport';
+import trip from './trip';
+import trips from './trips';
+import undo from './undo';
+import vacay from './vacay';
+
+const locale = {
+ ...common,
+ ...trips,
+ ...nav,
+ ...dashboard,
+ ...settings,
+ ...admin,
+ ...dayplan,
+ ...share,
+ ...shared,
+ ...login,
+ ...register,
+ ...vacay,
+ ...atlas,
+ ...trip,
+ ...places,
+ ...inspector,
+ ...reservations,
+ ...airport,
+ ...map,
+ ...budget,
+ ...files,
+ ...packing,
+ ...members,
+ ...categories,
+ ...backup,
+ ...photos,
+ ...pdf,
+ ...planner,
+ ...stats,
+ ...day,
+ ...memories,
+ ...collab,
+ ...perm,
+ ...undo,
+ ...notifications,
+ ...todo,
+ ...notif,
+ ...journey,
+ ...oauth,
+ ...system_notice,
+ ...transport,
+};
+export default locale;
diff --git a/shared/src/i18n/ru/inspector.ts b/shared/src/i18n/ru/inspector.ts
new file mode 100644
index 0000000000000000000000000000000000000000..51bd2aa1d08d70eb5231bdbc80e8613021fa52b2
--- /dev/null
+++ b/shared/src/i18n/ru/inspector.ts
@@ -0,0 +1,22 @@
+import type { TranslationStrings } from '../types';
+
+const inspector: TranslationStrings = {
+ 'inspector.opened': 'Открыто',
+ 'inspector.closed': 'Закрыто',
+ 'inspector.openingHours': 'Часы работы',
+ 'inspector.showHours': 'Показать часы работы',
+ 'inspector.files': 'Файлы',
+ 'inspector.filesCount': '{count} файлов',
+ 'inspector.removeFromDay': 'Убрать из дня',
+ 'inspector.remove': 'Удалить',
+ 'inspector.addToDay': 'Добавить в день',
+ 'inspector.confirmedRes': 'Подтверждённое бронирование',
+ 'inspector.pendingRes': 'Ожидающее бронирование',
+ 'inspector.google': 'Открыть в Google Maps',
+ 'inspector.website': 'Открыть сайт',
+ 'inspector.addRes': 'Бронирование',
+ 'inspector.editRes': 'Редактировать бронирование',
+ 'inspector.participants': 'Участники',
+ 'inspector.trackStats': 'Данные маршрута',
+};
+export default inspector;
diff --git a/shared/src/i18n/ru/journey.ts b/shared/src/i18n/ru/journey.ts
new file mode 100644
index 0000000000000000000000000000000000000000..97f8f37698b5ec5b7848453424852c52259c00db
--- /dev/null
+++ b/shared/src/i18n/ru/journey.ts
@@ -0,0 +1,245 @@
+import type { TranslationStrings } from '../types';
+
+const journey: TranslationStrings = {
+ 'journey.search.placeholder': 'Поиск путешествий…',
+ 'journey.search.noResults': 'Путешествий по запросу «{query}» не найдено',
+ 'journey.title': 'Путешествие',
+ 'journey.subtitle': 'Отслеживайте свои путешествия в реальном времени',
+ 'journey.new': 'Новое путешествие',
+ 'journey.create': 'Создать',
+ 'journey.titlePlaceholder': 'Куда вы едете?',
+ 'journey.empty': 'Пока нет путешествий',
+ 'journey.emptyHint': 'Начните документировать свою следующую поездку',
+ 'journey.deleted': 'Путешествие удалено',
+ 'journey.createError': 'Не удалось создать путешествие',
+ 'journey.deleteError': 'Не удалось удалить путешествие',
+ 'journey.deleteConfirmTitle': 'Удалить',
+ 'journey.deleteConfirmMessage':
+ 'Удалить «{title}»? Это действие нельзя отменить.',
+ 'journey.deleteConfirmGeneric': 'Вы уверены, что хотите удалить это?',
+ 'journey.notFound': 'Путешествие не найдено',
+ 'journey.photos': 'Фото',
+ 'journey.timelineEmpty': 'Пока нет остановок',
+ 'journey.timelineEmptyHint': 'Добавьте отметку или напишите запись в дневник',
+ 'journey.status.draft': 'Черновик',
+ 'journey.status.active': 'Активно',
+ 'journey.status.completed': 'Завершено',
+ 'journey.status.upcoming': 'Предстоящее',
+ 'journey.status.archived': 'В архиве',
+ 'journey.checkin.add': 'Отметиться',
+ 'journey.checkin.namePlaceholder': 'Название места',
+ 'journey.checkin.notesPlaceholder': 'Заметки (необязательно)',
+ 'journey.checkin.save': 'Сохранить',
+ 'journey.checkin.error': 'Не удалось сохранить отметку',
+ 'journey.entry.add': 'Дневник',
+ 'journey.entry.edit': 'Редактировать запись',
+ 'journey.entry.titlePlaceholder': 'Заголовок (необязательно)',
+ 'journey.entry.bodyPlaceholder': 'Что произошло сегодня?',
+ 'journey.entry.save': 'Сохранить',
+ 'journey.entry.error': 'Не удалось сохранить запись',
+ 'journey.photo.add': 'Фото',
+ 'journey.photo.uploadError': 'Загрузка не удалась',
+ 'journey.share.share': 'Поделиться',
+ 'journey.share.public': 'Публичный',
+ 'journey.share.linkCopied': 'Публичная ссылка скопирована',
+ 'journey.share.disabled': 'Публичный доступ отключён',
+ 'journey.editor.titlePlaceholder': 'Дайте название этому моменту...',
+ 'journey.editor.bodyPlaceholder': 'Расскажите историю этого дня...',
+ 'journey.editor.placePlaceholder': 'Местоположение (необязательно)',
+ 'journey.editor.tagsPlaceholder':
+ 'Теги: скрытая жемчужина, лучшая еда, стоит вернуться...',
+ 'journey.visibility.private': 'Приватный',
+ 'journey.visibility.shared': 'Общий',
+ 'journey.visibility.public': 'Публичный',
+ 'journey.emptyState.title': 'Ваша история начинается здесь',
+ 'journey.emptyState.subtitle':
+ 'Отметьтесь в месте или напишите первую запись в дневник',
+ 'journey.frontpage.subtitle':
+ 'Превращайте поездки в истории, которые вы никогда не забудете',
+ 'journey.frontpage.createJourney': 'Создать путешествие',
+ 'journey.frontpage.activeJourney': 'Активное путешествие',
+ 'journey.frontpage.allJourneys': 'Все путешествия',
+ 'journey.frontpage.journeys': 'путешествий',
+ 'journey.frontpage.createNew': 'Создать новое путешествие',
+ 'journey.frontpage.createNewSub':
+ 'Выберите поездки, пишите истории, делитесь приключениями',
+ 'journey.frontpage.live': 'В эфире',
+ 'journey.frontpage.synced': 'Синхронизировано',
+ 'journey.frontpage.continueWriting': 'Продолжить писать',
+ 'journey.frontpage.updated': 'Обновлено {time}',
+ 'journey.frontpage.suggestionLabel': 'Поездка только что завершилась',
+ 'journey.frontpage.suggestionText':
+ 'Превратите {title} в путешествие',
+ 'journey.frontpage.dismiss': 'Скрыть',
+ 'journey.frontpage.journeyName': 'Название путешествия',
+ 'journey.frontpage.namePlaceholder': 'напр. Юго-Восточная Азия 2026',
+ 'journey.frontpage.selectTrips': 'Выбрать поездки',
+ 'journey.frontpage.tripsSelected': 'поездок выбрано',
+ 'journey.frontpage.trips': 'поездок',
+ 'journey.frontpage.placesImported': 'мест будет импортировано',
+ 'journey.frontpage.places': 'мест',
+ 'journey.detail.backToJourney': 'Назад к путешествию',
+ 'journey.detail.syncedWithTrips': 'Синхронизировано с поездками',
+ 'journey.detail.addEntry': 'Добавить запись',
+ 'journey.detail.newEntry': 'Новая запись',
+ 'journey.detail.editEntry': 'Редактировать запись',
+ 'journey.detail.noEntries': 'Пока нет записей',
+ 'journey.detail.noEntriesHint':
+ 'Добавьте поездку, чтобы начать с шаблонных записей',
+ 'journey.detail.noPhotos': 'Пока нет фото',
+ 'journey.detail.noPhotosHint':
+ 'Загрузите фото в записи или просмотрите библиотеку Immich/Synology',
+ 'journey.detail.journeyStats': 'Статистика путешествия',
+ 'journey.detail.syncedTrips': 'Синхронизированные поездки',
+ 'journey.detail.noTripsLinked': 'Пока нет привязанных поездок',
+ 'journey.detail.contributors': 'Участники',
+ 'journey.detail.readMore': 'Читать далее',
+ 'journey.detail.prosCons': 'Плюсы и минусы',
+ 'journey.detail.photos': 'фото',
+ 'journey.detail.day': 'День {number}',
+ 'journey.detail.places': 'мест',
+ 'journey.stats.days': 'Дней',
+ 'journey.stats.cities': 'Городов',
+ 'journey.stats.entries': 'Записей',
+ 'journey.stats.photos': 'Фото',
+ 'journey.stats.places': 'Мест',
+ 'journey.skeletons.show': 'Показать предложения',
+ 'journey.skeletons.hide': 'Скрыть предложения',
+ 'journey.verdict.lovedIt': 'Понравилось',
+ 'journey.verdict.couldBeBetter': 'Могло быть лучше',
+ 'journey.synced.places': 'мест',
+ 'journey.synced.synced': 'синхронизировано',
+ 'journey.editor.discardChangesConfirm':
+ 'У вас есть несохранённые изменения. Отменить?',
+ 'journey.editor.uploadFailed': 'Не удалось загрузить фото',
+ 'journey.editor.uploadPhotos': 'Загрузить фото',
+ 'journey.editor.uploading': 'Загрузка...',
+ 'journey.editor.uploadingProgress': 'Загрузка {done}/{total}…',
+ 'journey.editor.uploadPartialFailed':
+ '{failed} из {total} фото не удалось загрузить — сохраните снова для повтора',
+ 'journey.editor.fromGallery': 'Из галереи',
+ 'journey.editor.allPhotosAdded': 'Все фото уже добавлены',
+ 'journey.editor.writeStory': 'Напишите свою историю...',
+ 'journey.editor.prosCons': 'Плюсы и минусы',
+ 'journey.editor.pros': 'Плюсы',
+ 'journey.editor.cons': 'Минусы',
+ 'journey.editor.proPlaceholder': 'Что-то отличное...',
+ 'journey.editor.conPlaceholder': 'Не очень хорошо...',
+ 'journey.editor.addAnother': 'Добавить ещё',
+ 'journey.editor.date': 'Дата',
+ 'journey.editor.location': 'Местоположение',
+ 'journey.editor.searchLocation': 'Поиск местоположения...',
+ 'journey.editor.mood': 'Настроение',
+ 'journey.editor.weather': 'Погода',
+ 'journey.editor.photoFirst': '1-е',
+ 'journey.editor.makeFirst': 'Сделать 1-м',
+ 'journey.editor.searching': 'Поиск...',
+ 'journey.mood.amazing': 'Потрясающе',
+ 'journey.mood.good': 'Хорошо',
+ 'journey.mood.neutral': 'Нейтрально',
+ 'journey.mood.rough': 'Тяжело',
+ 'journey.weather.sunny': 'Солнечно',
+ 'journey.weather.partly': 'Переменная облачность',
+ 'journey.weather.cloudy': 'Облачно',
+ 'journey.weather.rainy': 'Дождливо',
+ 'journey.weather.stormy': 'Гроза',
+ 'journey.weather.cold': 'Снежно',
+ 'journey.trips.linkTrip': 'Привязать поездку',
+ 'journey.trips.searchTrip': 'Поиск поездки',
+ 'journey.trips.searchPlaceholder': 'Название поездки или направление...',
+ 'journey.trips.noTripsAvailable': 'Нет доступных поездок',
+ 'journey.trips.link': 'Привязать',
+ 'journey.trips.tripLinked': 'Поездка привязана',
+ 'journey.trips.linkFailed': 'Не удалось привязать поездку',
+ 'journey.trips.addTrip': 'Добавить поездку',
+ 'journey.trips.unlinkTrip': 'Отвязать поездку',
+ 'journey.trips.unlinkMessage':
+ 'Отвязать «{title}»? Все синхронизированные записи и фото из этой поездки будут безвозвратно удалены. Это действие нельзя отменить.',
+ 'journey.trips.unlink': 'Отвязать',
+ 'journey.trips.tripUnlinked': 'Поездка отвязана',
+ 'journey.trips.unlinkFailed': 'Не удалось отвязать поездку',
+ 'journey.trips.noTripsLinkedSettings': 'Нет привязанных поездок',
+ 'journey.contributors.invite': 'Пригласить участника',
+ 'journey.contributors.searchUser': 'Поиск пользователя',
+ 'journey.contributors.searchPlaceholder': 'Имя пользователя или email...',
+ 'journey.contributors.noUsers': 'Пользователи не найдены',
+ 'journey.contributors.role': 'Роль',
+ 'journey.contributors.added': 'Участник добавлен',
+ 'journey.contributors.addFailed': 'Не удалось добавить участника',
+ 'journey.share.publicShare': 'Публичный доступ',
+ 'journey.share.createLink': 'Создать ссылку для общего доступа',
+ 'journey.share.linkCreated': 'Ссылка создана',
+ 'journey.share.createFailed': 'Не удалось создать ссылку',
+ 'journey.share.copy': 'Копировать',
+ 'journey.share.copied': 'Скопировано!',
+ 'journey.share.timeline': 'Хронология',
+ 'journey.share.gallery': 'Галерея',
+ 'journey.share.map': 'Карта',
+ 'journey.share.removeLink': 'Удалить ссылку',
+ 'journey.share.linkDeleted': 'Ссылка удалена',
+ 'journey.share.deleteFailed': 'Не удалось удалить',
+ 'journey.share.updateFailed': 'Не удалось обновить',
+ 'journey.invite.role': 'Роль',
+ 'journey.invite.viewer': 'Наблюдатель',
+ 'journey.invite.editor': 'Редактор',
+ 'journey.invite.invite': 'Пригласить',
+ 'journey.invite.inviting': 'Приглашаем...',
+ 'journey.settings.title': 'Настройки путешествия',
+ 'journey.settings.coverImage': 'Обложка',
+ 'journey.settings.changeCover': 'Сменить обложку',
+ 'journey.settings.addCover': 'Добавить обложку',
+ 'journey.settings.name': 'Название',
+ 'journey.settings.subtitle': 'Подзаголовок',
+ 'journey.settings.subtitlePlaceholder': 'напр. Таиланд, Вьетнам и Камбоджа',
+ 'journey.settings.endJourney': 'Архивировать путешествие',
+ 'journey.settings.reopenJourney': 'Восстановить путешествие',
+ 'journey.settings.archived': 'Путешествие архивировано',
+ 'journey.settings.reopened': 'Путешествие возобновлено',
+ 'journey.settings.endDescription':
+ 'Скрывает значок «В эфире». Вы можете возобновить в любое время.',
+ 'journey.settings.delete': 'Удалить',
+ 'journey.settings.deleteJourney': 'Удалить путешествие',
+ 'journey.settings.deleteMessage':
+ 'Удалить «{title}»? Все записи и фото будут потеряны.',
+ 'journey.settings.saved': 'Настройки сохранены',
+ 'journey.settings.saveFailed': 'Не удалось сохранить',
+ 'journey.settings.coverUpdated': 'Обложка обновлена',
+ 'journey.settings.coverFailed': 'Загрузка не удалась',
+ 'journey.settings.failedToDelete': 'Не удалось удалить',
+ 'journey.entries.deleteTitle': 'Удалить запись',
+ 'journey.photosUploaded': '{count} фото загружено',
+ 'journey.photosUploadFailed': 'Некоторые фото не удалось загрузить',
+ 'journey.photosAdded': '{count} фото добавлено',
+ 'journey.public.notFound': 'Не найдено',
+ 'journey.public.notFoundMessage':
+ 'Это путешествие не существует или ссылка устарела.',
+ 'journey.public.readOnly': 'Только для чтения · Публичное путешествие',
+ 'journey.public.tagline':
+ 'Инструмент планирования и исследования путешествий',
+ 'journey.public.sharedVia': 'Опубликовано через',
+ 'journey.public.madeWith': 'Сделано с помощью',
+ 'journey.pdf.journeyBook': 'Книга путешествия',
+ 'journey.pdf.madeWith': 'Сделано с помощью TREK',
+ 'journey.pdf.day': 'День',
+ 'journey.pdf.theEnd': 'Конец',
+ 'journey.pdf.saveAsPdf': 'Сохранить как PDF',
+ 'journey.pdf.pages': 'страниц',
+ 'journey.picker.tripPeriod': 'Период поездки',
+ 'journey.picker.dateRange': 'Диапазон дат',
+ 'journey.picker.allPhotos': 'Все фото',
+ 'journey.picker.albums': 'Альбомы',
+ 'journey.picker.selected': 'выбрано',
+ 'journey.picker.addTo': 'Добавить в',
+ 'journey.picker.newGallery': 'Новая галерея',
+ 'journey.picker.selectAll': 'Выбрать все',
+ 'journey.picker.deselectAll': 'Снять выбор',
+ 'journey.picker.noAlbums': 'Альбомы не найдены',
+ 'journey.picker.selectDate': 'Выберите дату',
+ 'journey.picker.search': 'Поиск',
+ 'journey.detail.journeyTab': 'Journey', // en-fallback
+ 'journey.contributors.remove': 'Remove contributor', // en-fallback
+ 'journey.contributors.removeConfirm': 'Remove {username} from this journey?', // en-fallback
+ 'journey.contributors.removed': 'Contributor removed', // en-fallback
+ 'journey.contributors.removeFailed': 'Failed to remove contributor', // en-fallback
+};
+export default journey;
diff --git a/shared/src/i18n/ru/login.ts b/shared/src/i18n/ru/login.ts
new file mode 100644
index 0000000000000000000000000000000000000000..15ac8db49ca51eaafa644dfc68bf738b879d4428
--- /dev/null
+++ b/shared/src/i18n/ru/login.ts
@@ -0,0 +1,100 @@
+import type { TranslationStrings } from '../types';
+
+const login: TranslationStrings = {
+ 'login.error': 'Ошибка входа. Проверьте свои учётные данные.',
+ 'login.tagline': 'Ваши поездки.\nВаш план.',
+ 'login.description':
+ 'Планируйте поездки совместно с интерактивными картами, бюджетами и синхронизацией в реальном времени.',
+ 'login.features.maps': 'Интерактивные карты',
+ 'login.features.mapsDesc': 'Google Places, маршруты и кластеризация',
+ 'login.features.realtime': 'Синхронизация в реальном времени',
+ 'login.features.realtimeDesc': 'Планируйте вместе через WebSocket',
+ 'login.features.budget': 'Контроль бюджета',
+ 'login.features.budgetDesc': 'Категории, графики и расходы на человека',
+ 'login.features.collab': 'Совместная работа',
+ 'login.features.collabDesc': 'Многопользовательский режим с общими поездками',
+ 'login.features.packing': 'Списки вещей',
+ 'login.features.packingDesc': 'Категории, прогресс и подсказки',
+ 'login.features.bookings': 'Бронирования',
+ 'login.features.bookingsDesc': 'Авиабилеты, отели, рестораны и многое другое',
+ 'login.features.files': 'Документы',
+ 'login.features.filesDesc': 'Загружайте и управляйте документами',
+ 'login.features.routes': 'Умные маршруты',
+ 'login.features.routesDesc': 'Автооптимизация и экспорт в Google Maps',
+ 'login.selfHosted':
+ 'Самостоятельный хостинг · Открытый код · Ваши данные остаются у вас',
+ 'login.title': 'Вход',
+ 'login.subtitle': 'С возвращением',
+ 'login.signingIn': 'Вход…',
+ 'login.signIn': 'Войти',
+ 'login.createAdmin': 'Создать аккаунт администратора',
+ 'login.createAdminHint': 'Настройте первый аккаунт администратора для TREK.',
+ 'login.setNewPassword': 'Установить новый пароль',
+ 'login.setNewPasswordHint':
+ 'Вы должны сменить пароль, прежде чем продолжить.',
+ 'login.createAccount': 'Создать аккаунт',
+ 'login.createAccountHint': 'Зарегистрируйте новый аккаунт.',
+ 'login.creating': 'Создание…',
+ 'login.noAccount': 'Нет аккаунта?',
+ 'login.hasAccount': 'Уже есть аккаунт?',
+ 'login.register': 'Регистрация',
+ 'login.emailPlaceholder': 'ваш@email.com',
+ 'login.username': 'Имя пользователя',
+ 'login.oidc.registrationDisabled':
+ 'Регистрация отключена. Обратитесь к администратору.',
+ 'login.oidc.noEmail': 'Провайдер не предоставил адрес эл. почты.',
+ 'login.mfaTitle': 'Двухфакторная аутентификация',
+ 'login.mfaSubtitle': 'Введите 6-значный код из приложения-аутентификатора.',
+ 'login.mfaCodeLabel': 'Код подтверждения',
+ 'login.mfaCodeRequired': 'Введите код из приложения-аутентификатора.',
+ 'login.mfaHint':
+ 'Откройте Google Authenticator, Authy или другое TOTP-приложение.',
+ 'login.mfaBack': '← Назад к входу',
+ 'login.mfaVerify': 'Подтвердить',
+ 'login.invalidInviteLink': 'Недействительная или истёкшая ссылка-приглашение',
+ 'login.oidcFailed': 'Ошибка входа через OIDC',
+ 'login.usernameRequired': 'Имя пользователя обязательно',
+ 'login.passwordMinLength': 'Пароль должен содержать не менее 8 символов',
+ 'login.forgotPassword': 'Забыли пароль?',
+ 'login.rememberMe': 'Запомнить меня',
+ 'login.forgotPasswordTitle': 'Сброс пароля',
+ 'login.forgotPasswordBody':
+ 'Введите e-mail, с которым вы регистрировались. Если аккаунт найдём — отправим ссылку для сброса.',
+ 'login.forgotPasswordSubmit': 'Отправить ссылку',
+ 'login.forgotPasswordSentTitle': 'Проверьте почту',
+ 'login.forgotPasswordSentBody':
+ 'Если аккаунт существует, ссылка для сброса уже летит к вам. Она действительна 60 минут.',
+ 'login.forgotPasswordSmtpHintOff':
+ 'Обратите внимание: администратор не настроил SMTP, поэтому ссылка для сброса будет записана в консоль сервера, а не отправлена по почте.',
+ 'login.backToLogin': 'Вернуться ко входу',
+ 'login.newPassword': 'Новый пароль',
+ 'login.confirmPassword': 'Подтвердите новый пароль',
+ 'login.passwordsDontMatch': 'Пароли не совпадают',
+ 'login.mfaCode': 'Код 2FA',
+ 'login.resetPasswordTitle': 'Задайте новый пароль',
+ 'login.resetPasswordBody':
+ 'Выберите надёжный пароль, который вы здесь ещё не использовали. Минимум 8 символов.',
+ 'login.resetPasswordMfaBody':
+ 'Введите код 2FA или резервный код, чтобы завершить сброс.',
+ 'login.resetPasswordSubmit': 'Сбросить пароль',
+ 'login.resetPasswordVerify': 'Проверить и сбросить',
+ 'login.resetPasswordSuccessTitle': 'Пароль обновлён',
+ 'login.resetPasswordSuccessBody': 'Теперь вы можете войти с новым паролем.',
+ 'login.resetPasswordInvalidLink': 'Неверная ссылка сброса',
+ 'login.resetPasswordInvalidLinkBody':
+ 'Ссылка отсутствует или повреждена. Запросите новую, чтобы продолжить.',
+ 'login.resetPasswordFailed':
+ 'Сброс не удался. Возможно, срок действия ссылки истёк.',
+ 'login.oidc.tokenFailed': 'Аутентификация не удалась.',
+ 'login.oidc.invalidState': 'Недействительная сессия. Попробуйте снова.',
+ 'login.demoFailed': 'Ошибка демо-входа',
+ 'login.oidcSignIn': 'Войти через {name}',
+ 'login.oidcOnly':
+ 'Вход по паролю отключён. Используйте вашего провайдера SSO для входа.',
+ 'login.oidcLoggedOut':
+ 'Вы вышли из системы. Войдите снова через вашего провайдера SSO.',
+ 'login.demoHint': 'Попробуйте демо — регистрация не требуется',
+ 'login.passkey.signIn': 'Войти с помощью passkey',
+ 'login.passkey.failed': 'Не удалось войти с помощью passkey. Попробуйте ещё раз.',
+};
+export default login;
diff --git a/shared/src/i18n/ru/map.ts b/shared/src/i18n/ru/map.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b6ad804ce9468c2c7ee24d850fe9d76c5e3aafe9
--- /dev/null
+++ b/shared/src/i18n/ru/map.ts
@@ -0,0 +1,17 @@
+import type { TranslationStrings } from '../types';
+
+const map: TranslationStrings = {
+ 'map.connections': 'Соединения',
+ 'map.showConnections': 'Показать маршруты бронирований',
+ 'map.hideConnections': 'Скрыть маршруты бронирований',
+ 'poi.searchThisArea': 'Искать в этой области',
+ 'poi.cat.restaurants': 'Рестораны',
+ 'poi.cat.cafes': 'Кафе',
+ 'poi.cat.bars': 'Бары и ночная жизнь',
+ 'poi.cat.hotels': 'Жильё',
+ 'poi.cat.sights': 'Достопримечательности',
+ 'poi.cat.museums': 'Музеи и культура',
+ 'poi.cat.nature': 'Природа и парки',
+ 'poi.cat.activities': 'Развлечения',
+};
+export default map;
diff --git a/shared/src/i18n/ru/members.ts b/shared/src/i18n/ru/members.ts
new file mode 100644
index 0000000000000000000000000000000000000000..237d0556ae2c87fdf7265c755e0185ffd160520b
--- /dev/null
+++ b/shared/src/i18n/ru/members.ts
@@ -0,0 +1,24 @@
+import type { TranslationStrings } from '../types';
+
+const members: TranslationStrings = {
+ 'members.shareTrip': 'Поделиться поездкой',
+ 'members.inviteUser': 'Пригласить пользователя',
+ 'members.selectUser': 'Выберите пользователя…',
+ 'members.invite': 'Пригласить',
+ 'members.allHaveAccess': 'У всех пользователей уже есть доступ.',
+ 'members.access': 'Доступ',
+ 'members.person': 'человек',
+ 'members.persons': 'человек',
+ 'members.you': 'вы',
+ 'members.owner': 'Владелец',
+ 'members.leaveTrip': 'Покинуть поездку',
+ 'members.removeAccess': 'Отозвать доступ',
+ 'members.confirmLeave': 'Покинуть поездку? Вы потеряете доступ.',
+ 'members.confirmRemove': 'Отозвать доступ у этого пользователя?',
+ 'members.loadError': 'Не удалось загрузить участников',
+ 'members.added': 'добавлен',
+ 'members.addError': 'Ошибка добавления',
+ 'members.removed': 'Участник удалён',
+ 'members.removeError': 'Ошибка удаления',
+};
+export default members;
diff --git a/shared/src/i18n/ru/memories.ts b/shared/src/i18n/ru/memories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6d375f365e533be6904c69fe5c2ca93f915ffb74
--- /dev/null
+++ b/shared/src/i18n/ru/memories.ts
@@ -0,0 +1,80 @@
+import type { TranslationStrings } from '../types';
+
+const memories: TranslationStrings = {
+ 'memories.title': 'Фото',
+ 'memories.notConnected': 'Immich не подключён',
+ 'memories.notConnectedHint':
+ 'Подключите Immich в настройках, чтобы видеть фотографии из поездок.',
+ 'memories.notConnectedMultipleHint':
+ 'Подключите одного из этих фотопровайдеров: {provider_names} в Настройках, чтобы добавлять фотографии к этому путешествию.',
+ 'memories.noDates': 'Добавьте даты поездки для загрузки фотографий.',
+ 'memories.noPhotos': 'Фотографии не найдены',
+ 'memories.noPhotosHint': 'В Immich нет фотографий за период этой поездки.',
+ 'memories.photosFound': 'фото',
+ 'memories.fromOthers': 'от других',
+ 'memories.sharePhotos': 'Поделиться фото',
+ 'memories.sharing': 'Общий доступ',
+ 'memories.reviewTitle': 'Проверьте ваши фото',
+ 'memories.reviewHint':
+ 'Нажмите на фото, чтобы исключить его из общего доступа.',
+ 'memories.shareCount': 'Поделиться ({count} фото)',
+ 'memories.providerUrl': 'URL сервера',
+ 'memories.providerApiKey': 'API-ключ',
+ 'memories.providerUsername': 'Имя пользователя',
+ 'memories.providerPassword': 'Пароль',
+ 'memories.providerOTP': 'Код MFA (если включён)',
+ 'memories.skipSSLVerification': 'Пропустить проверку SSL-сертификата',
+ 'memories.immichAutoUpload': 'Дублировать фото journey в Immich при загрузке',
+ 'memories.providerUrlHintSynology':
+ 'Включите путь приложения Photos в URL, например https://nas:5001/photo',
+ 'memories.testConnection': 'Проверить подключение',
+ 'memories.testShort': 'Проверить',
+ 'memories.testFirst': 'Сначала проверьте подключение',
+ 'memories.connected': 'Подключено',
+ 'memories.disconnected': 'Не подключено',
+ 'memories.connectionSuccess': 'Подключение к Immich установлено',
+ 'memories.connectionError': 'Не удалось подключиться к Immich',
+ 'memories.saved': 'Настройки {provider_name} сохранены',
+ 'memories.providerDisconnectedBanner':
+ 'Соединение с {provider_name} потеряно. Переподключитесь в Настройках для просмотра фотографий.',
+ 'memories.saveError': 'Не удалось сохранить настройки {provider_name}',
+ 'memories.oldest': 'Сначала старые',
+ 'memories.newest': 'Сначала новые',
+ 'memories.allLocations': 'Все места',
+ 'memories.addPhotos': 'Добавить фото',
+ 'memories.linkAlbum': 'Привязать альбом',
+ 'memories.selectAlbum': 'Выбрать альбом Immich',
+ 'memories.selectAlbumMultiple': 'Выбрать альбом',
+ 'memories.noAlbums': 'Альбомы не найдены',
+ 'memories.syncAlbum': 'Синхронизировать',
+ 'memories.unlinkAlbum': 'Отвязать',
+ 'memories.photos': 'фото',
+ 'memories.selectPhotos': 'Выбрать фото из Immich',
+ 'memories.selectPhotosMultiple': 'Выбрать фотографии',
+ 'memories.selectHint': 'Нажмите на фото, чтобы выбрать их.',
+ 'memories.selected': 'выбрано',
+ 'memories.addSelected': 'Добавить {count} фото',
+ 'memories.alreadyAdded': 'Добавлено',
+ 'memories.private': 'Приватное',
+ 'memories.stopSharing': 'Прекратить доступ',
+ 'memories.tripDates': 'Даты поездки',
+ 'memories.allPhotos': 'Все фото',
+ 'memories.confirmShareTitle': 'Поделиться с участниками поездки?',
+ 'memories.confirmShareHint':
+ '{count} фото станут видны всем участникам этой поездки. Вы сможете сделать отдельные фото приватными позже.',
+ 'memories.confirmShareButton': 'Поделиться фото',
+ 'memories.error.loadAlbums': 'Не удалось загрузить альбомы',
+ 'memories.error.linkAlbum': 'Не удалось привязать альбом',
+ 'memories.error.unlinkAlbum': 'Не удалось отвязать альбом',
+ 'memories.error.syncAlbum': 'Не удалось синхронизировать альбом',
+ 'memories.error.loadPhotos': 'Не удалось загрузить фотографии',
+ 'memories.error.addPhotos': 'Не удалось добавить фотографии',
+ 'memories.error.removePhoto': 'Не удалось удалить фотографию',
+ 'memories.error.toggleSharing': 'Не удалось обновить настройки доступа',
+ 'memories.saveRouteNotConfigured':
+ 'Маршрут сохранения не настроен для этого провайдера',
+ 'memories.testRouteNotConfigured':
+ 'Маршрут тестирования не настроен для этого провайдера',
+ 'memories.fillRequiredFields': 'Пожалуйста, заполните все обязательные поля',
+};
+export default memories;
diff --git a/shared/src/i18n/ru/nav.ts b/shared/src/i18n/ru/nav.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c5f9a46247629a16fe645b1ca607a0af80ac8f8b
--- /dev/null
+++ b/shared/src/i18n/ru/nav.ts
@@ -0,0 +1,20 @@
+import type { TranslationStrings } from '../types';
+
+const nav: TranslationStrings = {
+ 'nav.trip': 'Поездка',
+ 'nav.share': 'Поделиться',
+ 'nav.settings': 'Настройки',
+ 'nav.admin': 'Админ',
+ 'nav.logout': 'Выйти',
+ 'nav.lightMode': 'Светлая тема',
+ 'nav.darkMode': 'Тёмная тема',
+ 'nav.autoMode': 'Авто',
+ 'nav.administrator': 'Администратор',
+ 'nav.myTrips': 'Мои поездки',
+ 'nav.profile': 'Профиль',
+ 'nav.bottomSettings': 'Настройки',
+ 'nav.bottomAdmin': 'Администрирование',
+ 'nav.bottomLogout': 'Выйти',
+ 'nav.bottomAdminBadge': 'Админ',
+};
+export default nav;
diff --git a/shared/src/i18n/ru/notif.ts b/shared/src/i18n/ru/notif.ts
new file mode 100644
index 0000000000000000000000000000000000000000..33d89d7695ac34b88b4706414efb8dd81280830f
--- /dev/null
+++ b/shared/src/i18n/ru/notif.ts
@@ -0,0 +1,41 @@
+import type { TranslationStrings } from '../types';
+
+const notif: TranslationStrings = {
+ 'notif.test.title': '[Тест] Уведомление',
+ 'notif.test.simple.text': 'Это простое тестовое уведомление.',
+ 'notif.test.boolean.text': 'Вы принимаете это тестовое уведомление?',
+ 'notif.test.navigate.text': 'Нажмите ниже для перехода на панель управления.',
+ 'notif.trip_invite.title': 'Приглашение в поездку',
+ 'notif.trip_invite.text': '{actor} пригласил вас в {trip}',
+ 'notif.booking_change.title': 'Бронирование обновлено',
+ 'notif.booking_change.text': '{actor} обновил бронирование в {trip}',
+ 'notif.trip_reminder.title': 'Напоминание о поездке',
+ 'notif.trip_reminder.text': 'Ваша поездка {trip} скоро начнётся!',
+ 'notif.todo_due.title': 'Задача к сроку',
+ 'notif.todo_due.text': '{todo} в {trip} — срок {due}',
+ 'notif.vacay_invite.title': 'Приглашение Vacay Fusion',
+ 'notif.vacay_invite.text': '{actor} приглашает вас объединить планы отпуска',
+ 'notif.photos_shared.title': 'Фото опубликованы',
+ 'notif.photos_shared.text': '{actor} поделился {count} фото в {trip}',
+ 'notif.collab_message.title': 'Новое сообщение',
+ 'notif.collab_message.text': '{actor} отправил сообщение в {trip}',
+ 'notif.packing_tagged.title': 'Задание для упаковки',
+ 'notif.packing_tagged.text': '{actor} назначил вас в {category} в {trip}',
+ 'notif.version_available.title': 'Доступна новая версия',
+ 'notif.version_available.text': 'TREK {version} теперь доступен',
+ 'notif.action.view_trip': 'Открыть поездку',
+ 'notif.action.view_collab': 'Открыть сообщения',
+ 'notif.action.view_packing': 'Открыть упаковку',
+ 'notif.action.view_photos': 'Открыть фото',
+ 'notif.action.view_vacay': 'Открыть Vacay',
+ 'notif.action.view_admin': 'Перейти в админ',
+ 'notif.action.view': 'Открыть',
+ 'notif.action.accept': 'Принять',
+ 'notif.action.decline': 'Отклонить',
+ 'notif.generic.title': 'Уведомление',
+ 'notif.generic.text': 'У вас новое уведомление',
+ 'notif.dev.unknown_event.title': '[DEV] Неизвестное событие',
+ 'notif.dev.unknown_event.text':
+ 'Тип события "{event}" не зарегистрирован в EVENT_NOTIFICATION_CONFIG',
+};
+export default notif;
diff --git a/shared/src/i18n/ru/notifications.ts b/shared/src/i18n/ru/notifications.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c9ed124def9bc2f0fecb2c7f4866f0538c64edb2
--- /dev/null
+++ b/shared/src/i18n/ru/notifications.ts
@@ -0,0 +1,37 @@
+import type { TranslationStrings } from '../types';
+
+const notifications: TranslationStrings = {
+ 'notifications.title': 'Уведомления',
+ 'notifications.markAllRead': 'Отметить все прочитанными',
+ 'notifications.deleteAll': 'Удалить все',
+ 'notifications.showAll': 'Показать все уведомления',
+ 'notifications.empty': 'Нет уведомлений',
+ 'notifications.emptyDescription': 'Вы в курсе всех событий!',
+ 'notifications.all': 'Все',
+ 'notifications.unreadOnly': 'Непрочитанные',
+ 'notifications.markRead': 'Отметить как прочитанное',
+ 'notifications.markUnread': 'Отметить как непрочитанное',
+ 'notifications.delete': 'Удалить',
+ 'notifications.system': 'Система',
+ 'notifications.synologySessionCleared.title': 'Synology Photos отключено',
+ 'notifications.synologySessionCleared.text':
+ 'Ваш сервер или аккаунт изменился — перейдите в Настройки, чтобы проверить соединение снова.',
+ '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}".',
+ 'notifications.versionAvailable.title': 'Доступно обновление',
+ 'notifications.versionAvailable.text': 'TREK {version} теперь доступен.',
+ 'notifications.versionAvailable.button': 'Подробнее',
+};
+export default notifications;
diff --git a/shared/src/i18n/ru/oauth.ts b/shared/src/i18n/ru/oauth.ts
new file mode 100644
index 0000000000000000000000000000000000000000..af2860ec97bda4e4f4d2febd28049bf7cfb9e565
--- /dev/null
+++ b/shared/src/i18n/ru/oauth.ts
@@ -0,0 +1,123 @@
+import type { TranslationStrings } from '../types';
+
+const oauth: TranslationStrings = {
+ 'oauth.scope.group.trips': 'Поездки',
+ 'oauth.scope.group.places': 'Места',
+ 'oauth.scope.group.atlas': 'Atlas',
+ 'oauth.scope.group.packing': 'Вещи',
+ 'oauth.scope.group.todos': 'Задачи',
+ 'oauth.scope.group.budget': 'Бюджет',
+ 'oauth.scope.group.reservations': 'Бронирования',
+ 'oauth.scope.group.collab': 'Сотрудничество',
+ 'oauth.scope.group.notifications': 'Уведомления',
+ 'oauth.scope.group.vacay': 'Отпуск',
+ 'oauth.scope.group.geo': 'Geo',
+ 'oauth.scope.group.weather': 'Погода',
+ 'oauth.scope.group.journey': 'Путешествия',
+ 'oauth.scope.trips:read.label': 'Просмотр поездок и маршрутов',
+ 'oauth.scope.trips:read.description':
+ 'Чтение поездок, дней, заметок и участников',
+ 'oauth.scope.trips:write.label': 'Редактирование поездок и маршрутов',
+ 'oauth.scope.trips:write.description':
+ 'Создание и обновление поездок, дней, заметок и управление участниками',
+ 'oauth.scope.trips:delete.label': 'Удаление поездок',
+ 'oauth.scope.trips:delete.description':
+ 'Безвозвратное удаление поездок — это действие необратимо',
+ 'oauth.scope.trips:share.label': 'Управление ссылками на совместный доступ',
+ 'oauth.scope.trips:share.description':
+ 'Создание, обновление и отзыв публичных ссылок на поездки',
+ 'oauth.scope.places:read.label': 'Просмотр мест и данных карты',
+ 'oauth.scope.places:read.description':
+ 'Чтение мест, назначений по дням, тегов и категорий',
+ 'oauth.scope.places:write.label': 'Управление местами',
+ 'oauth.scope.places:write.description':
+ 'Создание, обновление и удаление мест, назначений и тегов',
+ 'oauth.scope.atlas:read.label': 'Просмотр Atlas',
+ 'oauth.scope.atlas:read.description':
+ 'Чтение посещённых стран, регионов и списка желаний',
+ 'oauth.scope.atlas:write.label': 'Управление Atlas',
+ 'oauth.scope.atlas:write.description':
+ 'Отмечать посещённые страны и регионы, управлять списком желаний',
+ 'oauth.scope.packing:read.label': 'Просмотр списков вещей',
+ 'oauth.scope.packing:read.description':
+ 'Чтение вещей, сумок и назначений категорий',
+ 'oauth.scope.packing:write.label': 'Управление списками вещей',
+ 'oauth.scope.packing:write.description':
+ 'Добавление, обновление, удаление, отметка и переупорядочивание вещей и сумок',
+ 'oauth.scope.todos:read.label': 'Просмотр списков задач',
+ 'oauth.scope.todos:read.description':
+ 'Чтение задач поездки и назначений категорий',
+ 'oauth.scope.todos:write.label': 'Управление списками задач',
+ 'oauth.scope.todos:write.description':
+ 'Создание, обновление, отметка, удаление и переупорядочивание задач',
+ 'oauth.scope.budget:read.label': 'Просмотр бюджета',
+ 'oauth.scope.budget:read.description':
+ 'Чтение статей бюджета и разбивки расходов',
+ 'oauth.scope.budget:write.label': 'Управление бюджетом',
+ 'oauth.scope.budget:write.description':
+ 'Создание, обновление и удаление статей бюджета',
+ 'oauth.scope.reservations:read.label': 'Просмотр бронирований',
+ 'oauth.scope.reservations:read.description':
+ 'Чтение бронирований и сведений о проживании',
+ 'oauth.scope.reservations:write.label': 'Управление бронированиями',
+ 'oauth.scope.reservations:write.description':
+ 'Создание, обновление, удаление и переупорядочивание бронирований',
+ 'oauth.scope.collab:read.label': 'Просмотр совместной работы',
+ 'oauth.scope.collab:read.description':
+ 'Чтение совместных заметок, опросов и сообщений',
+ 'oauth.scope.collab:write.label': 'Управление совместной работой',
+ 'oauth.scope.collab:write.description':
+ 'Создание, обновление и удаление заметок, опросов и сообщений',
+ 'oauth.scope.notifications:read.label': 'Просмотр уведомлений',
+ 'oauth.scope.notifications:read.description':
+ 'Чтение уведомлений в приложении и количества непрочитанных',
+ 'oauth.scope.notifications:write.label': 'Управление уведомлениями',
+ 'oauth.scope.notifications:write.description':
+ 'Отмечать уведомления как прочитанные и отвечать на них',
+ 'oauth.scope.vacay:read.label': 'Просмотр планов отпуска',
+ 'oauth.scope.vacay:read.description':
+ 'Чтение данных планирования отпуска, записей и статистики',
+ 'oauth.scope.vacay:write.label': 'Управление планами отпуска',
+ 'oauth.scope.vacay:write.description':
+ 'Создание и управление записями отпуска, праздниками и командными планами',
+ 'oauth.scope.geo:read.label': 'Карты и геокодирование',
+ 'oauth.scope.geo:read.description':
+ 'Поиск мест, разрешение URL карт и обратное геокодирование координат',
+ 'oauth.scope.weather:read.label': 'Прогнозы погоды',
+ 'oauth.scope.weather:read.description':
+ 'Получение прогнозов погоды для мест и дат поездки',
+ 'oauth.scope.journey:read.label': 'Просмотр путешествий',
+ 'oauth.scope.journey:read.description':
+ 'Чтение путешествий, записей и списка участников',
+ 'oauth.scope.journey:write.label': 'Управление путешествиями',
+ 'oauth.scope.journey:write.description':
+ 'Создание, обновление и удаление путешествий и их записей',
+ 'oauth.scope.journey:share.label': 'Управление ссылками на путешествия',
+ 'oauth.scope.journey:share.description':
+ 'Создание, обновление и отзыв публичных ссылок для путешествий',
+ 'oauth.authorize.authorizing': 'Authorizing…', // en-fallback
+ 'oauth.authorize.loading': 'Loading…', // en-fallback
+ 'oauth.authorize.errorTitle': 'Authorization Error', // en-fallback
+ 'oauth.authorize.loginTitle': 'Sign in to continue', // en-fallback
+ 'oauth.authorize.loginDescription':
+ '{client} wants access to your TREK account. Please sign in first.', // en-fallback
+ 'oauth.authorize.loginButton': 'Sign in to TREK', // en-fallback
+ 'oauth.authorize.requestLabel': 'Authorization Request', // en-fallback
+ 'oauth.authorize.requestDescription':
+ 'This application is requesting access to your TREK account.', // en-fallback
+ 'oauth.authorize.trustNote':
+ 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
+ 'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
+ 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
+ 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
+ 'oauth.authorize.deny': 'Deny', // en-fallback
+ 'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
+ 'oauth.authorize.permissionsRequested': 'Permissions requested', // en-fallback
+ 'oauth.authorize.alwaysIncluded': 'Always included', // en-fallback
+ 'oauth.authorize.alwaysTool.listTrips':
+ 'List your trips so the AI can discover trip IDs', // en-fallback
+ 'oauth.authorize.alwaysTool.getTripSummary':
+ 'Read a trip overview needed to use any other tool', // en-fallback
+};
+export default oauth;