From bf063b06321d2733afbb62ac6d30873524ec09d3 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Wed, 8 Jul 2026 20:15:09 -0400 Subject: [PATCH] Refactor sunrise/sunset functionality and introduce caching - Renamed `ExternalSunTimesThrottle` to `ExternalSunriseSunsetThrottle` for clarity. - Added `SunriseSunsetAPI` view to handle sunrise and sunset data retrieval. - Implemented caching for sunrise/sunset data using `get_or_fetch_cached`. - Created new service functions for fetching sunrise/sunset data from the external API. - Updated related tests to ensure proper functionality and caching behavior. - Removed deprecated sun times handling from the `LocationViewSet` for cleaner code. --- .../adventures/providers/places/wikipedia.py | 33 +- .../adventures/providers/sun/__init__.py | 0 .../adventures/providers/sun/sunrisesunset.py | 48 + .../adventures/services/external_cache.py | 44 + .../server/adventures/services/sun/times.py | 39 - .../services/sunrise_sunset/__init__.py | 0 .../services/sunrise_sunset/times.py | 71 + .../services/wikipedia/description_service.py | 21 + .../adventures/tests/test_sunrise_sunset.py | 41 + .../adventures/tests/test_wikipedia_cache.py | 22 + backend/server/adventures/throttling.py | 4 +- backend/server/adventures/urls.py | 2 + .../adventures/utils/location_access.py | 18 + .../server/adventures/views/location_view.py | 31 +- .../adventures/views/sunrise_sunset_view.py | 49 + backend/server/main/settings.py | 7 +- .../configuration/advanced_configuration.md | 2 +- .../configuration/environment_variables.md | 2 +- frontend/src/lib/sunriseSunset.ts | 27 + frontend/src/lib/types.ts | 9 - frontend/src/locales/en.json | 4 +- frontend/src/locales/fr.json | 2800 ++++++++--------- .../src/routes/locations/[id]/+page.server.ts | 6 +- .../src/routes/locations/[id]/+page.svelte | 314 +- 24 files changed, 1983 insertions(+), 1611 deletions(-) create mode 100644 backend/server/adventures/providers/sun/__init__.py create mode 100644 backend/server/adventures/providers/sun/sunrisesunset.py create mode 100644 backend/server/adventures/services/external_cache.py delete mode 100644 backend/server/adventures/services/sun/times.py create mode 100644 backend/server/adventures/services/sunrise_sunset/__init__.py create mode 100644 backend/server/adventures/services/sunrise_sunset/times.py create mode 100644 backend/server/adventures/tests/test_sunrise_sunset.py create mode 100644 backend/server/adventures/tests/test_wikipedia_cache.py create mode 100644 backend/server/adventures/utils/location_access.py create mode 100644 backend/server/adventures/views/sunrise_sunset_view.py create mode 100644 frontend/src/lib/sunriseSunset.ts diff --git a/backend/server/adventures/providers/places/wikipedia.py b/backend/server/adventures/providers/places/wikipedia.py index d6a50885..822810cf 100644 --- a/backend/server/adventures/providers/places/wikipedia.py +++ b/backend/server/adventures/providers/places/wikipedia.py @@ -2,8 +2,13 @@ from typing import Optional from urllib.parse import quote import requests +from django.conf import settings from adventures.providers.base import ProviderResult +from adventures.services.external_cache import get_or_fetch_cached + +WIKIPEDIA_SUMMARY_CACHE_PREFIX = 'wikipedia_summary_v1' +WIKIPEDIA_SUMMARY_CACHE_TIMEOUT = getattr(settings, 'WIKIPEDIA_SUMMARY_CACHE_TIMEOUT', 60 * 60 * 24 * 7) def fetch_summary(query: str, language: str = "en") -> ProviderResult[Optional[str]]: @@ -11,9 +16,27 @@ def fetch_summary(query: str, language: str = "en") -> ProviderResult[Optional[s if not normalized_query: return ProviderResult(error="Missing query") - candidates = [normalized_query] - if "," in normalized_query: - head = normalized_query.split(",")[0].strip() + normalized_language = (language or "en").strip().lower() or "en" + + def fetch() -> Optional[str]: + return _fetch_summary_uncached(normalized_query, normalized_language) + + cached = get_or_fetch_cached( + WIKIPEDIA_SUMMARY_CACHE_PREFIX, + normalized_language, + normalized_query, + fetch_fn=fetch, + timeout=WIKIPEDIA_SUMMARY_CACHE_TIMEOUT, + ) + if cached: + return ProviderResult(data=cached) + return ProviderResult(data=None) + + +def _fetch_summary_uncached(query: str, language: str) -> Optional[str]: + candidates = [query] + if "," in query: + head = query.split(",")[0].strip() if head and head not in candidates: candidates.append(head) @@ -35,8 +58,8 @@ def fetch_summary(query: str, language: str = "en") -> ProviderResult[Optional[s extract = (data.get("extract") or "").strip() if len(extract) >= 120: - return ProviderResult(data=extract) + return extract except requests.exceptions.RequestException: continue - return ProviderResult(data=None) + return None diff --git a/backend/server/adventures/providers/sun/__init__.py b/backend/server/adventures/providers/sun/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/server/adventures/providers/sun/sunrisesunset.py b/backend/server/adventures/providers/sun/sunrisesunset.py new file mode 100644 index 00000000..398b7cbb --- /dev/null +++ b/backend/server/adventures/providers/sun/sunrisesunset.py @@ -0,0 +1,48 @@ +import logging +from typing import TypedDict + +import requests + +from adventures.providers.base import ProviderResult + +logger = logging.getLogger(__name__) + +API_BASE_URL = 'https://api.sunrisesunset.io/json' + + +class SunriseSunsetData(TypedDict): + date: str + sunrise: str + sunset: str + + +def fetch_sunrise_sunset(latitude: float, longitude: float, date: str) -> ProviderResult[SunriseSunsetData]: + """Fetch sunrise and sunset for a coordinate and calendar date (YYYY-MM-DD).""" + if latitude is None or longitude is None: + return ProviderResult(error='Missing coordinates') + if not date: + return ProviderResult(error='Missing date') + + api_url = f'{API_BASE_URL}?lat={latitude}&lng={longitude}&date={date}' + try: + response = requests.get(api_url, timeout=10) + except requests.RequestException as exc: + logger.warning('Sunrise/sunset API request failed for %s: %s', date, exc) + return ProviderResult(error='Sunrise/sunset API request failed') + + if response.status_code != 200: + logger.warning('Sunrise/sunset API returned status %s for %s', response.status_code, date) + return ProviderResult(error='Sunrise/sunset API returned an error') + + data = response.json() or {} + results = data.get('results', {}) + sunrise = results.get('sunrise') + sunset = results.get('sunset') + if not sunrise or not sunset: + return ProviderResult(error='Sunrise/sunset not available for this date') + + return ProviderResult(data={ + 'date': date, + 'sunrise': sunrise, + 'sunset': sunset, + }) diff --git a/backend/server/adventures/services/external_cache.py b/backend/server/adventures/services/external_cache.py new file mode 100644 index 00000000..92e40145 --- /dev/null +++ b/backend/server/adventures/services/external_cache.py @@ -0,0 +1,44 @@ +import hashlib +from typing import Callable, Optional, TypeVar + +from django.conf import settings +from django.core.cache import cache + +T = TypeVar('T') + + +def build_cache_key(prefix: str, *parts: str) -> str: + normalized = ':'.join((part or '').strip().lower() for part in parts) + if len(normalized) > 200: + digest = hashlib.sha256(normalized.encode()).hexdigest() + return f'{prefix}:{digest}' + return f'{prefix}:{normalized}' + + +def get_cached(prefix: str, *parts: str) -> Optional[T]: + return cache.get(build_cache_key(prefix, *parts)) + + +def set_cached(prefix: str, *parts: str, value: T, timeout: Optional[int] = None) -> T: + cache.set( + build_cache_key(prefix, *parts), + value, + timeout or getattr(settings, 'EXTERNAL_API_CACHE_TIMEOUT', 60 * 60 * 24), + ) + return value + + +def get_or_fetch_cached( + prefix: str, + *parts: str, + fetch_fn: Callable[[], Optional[T]], + timeout: Optional[int] = None, +) -> Optional[T]: + cached = get_cached(prefix, *parts) + if cached is not None: + return cached + + value = fetch_fn() + if value is not None: + set_cached(prefix, *parts, value=value, timeout=timeout) + return value diff --git a/backend/server/adventures/services/sun/times.py b/backend/server/adventures/services/sun/times.py deleted file mode 100644 index 72c5eead..00000000 --- a/backend/server/adventures/services/sun/times.py +++ /dev/null @@ -1,39 +0,0 @@ -import logging -from typing import List, Dict -import requests - -logger = logging.getLogger(__name__) - - -def get_sun_times_for_visits(latitude: float, longitude: float, visits: List[Dict]) -> List[Dict]: - """Return sunrise/sunset times for visits given lat/lon and visit date entries. - - visits: list of dicts with keys 'start_date' and 'id' - """ - sun_times = [] - for visit in visits: - date = visit.get('start_date') - if not (date and latitude and longitude): - continue - - api_url = ( - f'https://api.sunrisesunset.io/json?' - f'lat={latitude}&lng={longitude}&date={date}' - ) - try: - response = requests.get(api_url, timeout=10) - if response.status_code == 200: - data = response.json() - results = data.get('results', {}) - if results.get('sunrise') and results.get('sunset'): - sun_times.append({ - 'date': date, - 'visit_id': visit.get('id'), - 'sunrise': results.get('sunrise'), - 'sunset': results.get('sunset'), - }) - except requests.RequestException: - logger.debug('Sun times API request failed for date %s', date) - continue - - return sun_times diff --git a/backend/server/adventures/services/sunrise_sunset/__init__.py b/backend/server/adventures/services/sunrise_sunset/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/server/adventures/services/sunrise_sunset/times.py b/backend/server/adventures/services/sunrise_sunset/times.py new file mode 100644 index 00000000..222d22aa --- /dev/null +++ b/backend/server/adventures/services/sunrise_sunset/times.py @@ -0,0 +1,71 @@ +import logging +from datetime import timezone as dt_timezone +from typing import Optional + +from django.conf import settings +from django.utils.dateparse import parse_date, parse_datetime + +from adventures.providers.sun.sunrisesunset import SunriseSunsetData, fetch_sunrise_sunset +from adventures.services.external_cache import get_or_fetch_cached + +logger = logging.getLogger(__name__) + +SUNRISE_SUNSET_CACHE_PREFIX = 'sunrise_sunset_v1' +SUNRISE_SUNSET_CACHE_TIMEOUT = getattr(settings, 'SUNRISE_SUNSET_CACHE_TIMEOUT', 60 * 60 * 24 * 30) + + +def normalize_sunrise_sunset_date(value: str) -> Optional[str]: + """Normalize an ISO datetime or YYYY-MM-DD string to YYYY-MM-DD.""" + if not value: + return None + + value = value.strip() + if len(value) >= 10 and value[4:5] == '-' and value[7:8] == '-': + parsed_date = parse_date(value[:10]) + if parsed_date: + return parsed_date.isoformat() + + parsed_datetime = parse_datetime(value) + if parsed_datetime: + if parsed_datetime.tzinfo is not None: + parsed_datetime = parsed_datetime.astimezone(dt_timezone.utc) + return parsed_datetime.date().isoformat() + + return None + + +def _cache_key_parts(latitude: float, longitude: float, date_value: str) -> tuple[str, str, str]: + return ( + f'{latitude:.4f}', + f'{longitude:.4f}', + date_value, + ) + + +def get_sunrise_sunset_for_date( + latitude: float, + longitude: float, + date_value: str, +) -> Optional[SunriseSunsetData]: + """Return cached sunrise/sunset for a coordinate and calendar date.""" + normalized_date = normalize_sunrise_sunset_date(date_value) + if not normalized_date: + return None + + lat_part, lng_part, _ = _cache_key_parts(latitude, longitude, normalized_date) + + def fetch() -> Optional[SunriseSunsetData]: + result = fetch_sunrise_sunset(latitude, longitude, normalized_date) + if result.error or not result.data: + logger.debug('Sunrise/sunset unavailable for %s: %s', normalized_date, result.error) + return None + return result.data + + return get_or_fetch_cached( + SUNRISE_SUNSET_CACHE_PREFIX, + lat_part, + lng_part, + normalized_date, + fetch_fn=fetch, + timeout=SUNRISE_SUNSET_CACHE_TIMEOUT, + ) diff --git a/backend/server/adventures/services/wikipedia/description_service.py b/backend/server/adventures/services/wikipedia/description_service.py index d762d061..ea464abc 100644 --- a/backend/server/adventures/services/wikipedia/description_service.py +++ b/backend/server/adventures/services/wikipedia/description_service.py @@ -3,11 +3,16 @@ import re from typing import Dict, List, Optional from difflib import SequenceMatcher +from django.conf import settings +from adventures.services.external_cache import get_or_fetch_cached from adventures.services.wikipedia.search import WikipediaClient, is_valid_image_url logger = logging.getLogger(__name__) +WIKIPEDIA_DESC_CACHE_PREFIX = 'wikipedia_desc_v1' +WIKIPEDIA_DESC_CACHE_TIMEOUT = getattr(settings, 'WIKIPEDIA_DESC_CACHE_TIMEOUT', 60 * 60 * 24 * 7) + class WikipediaDescriptionService: LANGUAGE_PATTERN = re.compile(r"^[a-z0-9-]{2,12}$", re.IGNORECASE) @@ -47,6 +52,22 @@ class WikipediaDescriptionService: def get_description(self, term: str, lang: str) -> Optional[Dict]: if not term: return None + + normalized_term = term.strip() + normalized_lang = self.get_language(lang) + + def fetch() -> Optional[Dict]: + return self._fetch_description(normalized_term, normalized_lang) + + return get_or_fetch_cached( + WIKIPEDIA_DESC_CACHE_PREFIX, + normalized_lang, + normalized_term, + fetch_fn=fetch, + timeout=WIKIPEDIA_DESC_CACHE_TIMEOUT, + ) + + def _fetch_description(self, term: str, lang: str) -> Optional[Dict]: candidates = self.client.get_candidate_pages(term, lang, max_candidates=self.MAX_CANDIDATES) for candidate in candidates: try: diff --git a/backend/server/adventures/tests/test_sunrise_sunset.py b/backend/server/adventures/tests/test_sunrise_sunset.py new file mode 100644 index 00000000..7041e7d0 --- /dev/null +++ b/backend/server/adventures/tests/test_sunrise_sunset.py @@ -0,0 +1,41 @@ +from unittest.mock import patch + +from django.test import SimpleTestCase, override_settings + +from adventures.services.sunrise_sunset.times import ( + get_sunrise_sunset_for_date, + normalize_sunrise_sunset_date, +) + + +class NormalizeSunriseSunsetDateTests(SimpleTestCase): + def test_accepts_iso_date(self): + self.assertEqual(normalize_sunrise_sunset_date('2024-06-15'), '2024-06-15') + + def test_accepts_iso_datetime(self): + self.assertEqual(normalize_sunrise_sunset_date('2024-06-15T14:30:00Z'), '2024-06-15') + + def test_rejects_invalid_value(self): + self.assertIsNone(normalize_sunrise_sunset_date('not-a-date')) + + +@override_settings(SUNRISE_SUNSET_CACHE_TIMEOUT=3600) +class GetSunriseSunsetForDateTests(SimpleTestCase): + @patch('adventures.services.sunrise_sunset.times.get_or_fetch_cached') + def test_delegates_to_cache_helper(self, mock_get_or_fetch_cached): + mock_get_or_fetch_cached.return_value = { + 'date': '2024-06-15', + 'sunrise': '5:30 AM', + 'sunset': '8:15 PM', + } + + result = get_sunrise_sunset_for_date(40.7128, -74.0060, '2024-06-15T00:00:00Z') + + self.assertEqual(result['sunrise'], '5:30 AM') + mock_get_or_fetch_cached.assert_called_once() + args = mock_get_or_fetch_cached.call_args.args + self.assertEqual(args[0], 'sunrise_sunset_v1') + self.assertEqual(args[3], '2024-06-15') + + def test_returns_none_for_invalid_date(self): + self.assertIsNone(get_sunrise_sunset_for_date(40.7128, -74.0060, 'invalid')) diff --git a/backend/server/adventures/tests/test_wikipedia_cache.py b/backend/server/adventures/tests/test_wikipedia_cache.py new file mode 100644 index 00000000..2e92554b --- /dev/null +++ b/backend/server/adventures/tests/test_wikipedia_cache.py @@ -0,0 +1,22 @@ +from unittest.mock import patch + +from django.test import SimpleTestCase, override_settings + +from adventures.services.wikipedia.description_service import WikipediaDescriptionService + + +@override_settings(WIKIPEDIA_DESC_CACHE_TIMEOUT=3600) +class WikipediaDescriptionCacheTests(SimpleTestCase): + @patch('adventures.services.wikipedia.description_service.get_or_fetch_cached') + def test_get_description_uses_cache(self, mock_get_or_fetch_cached): + mock_get_or_fetch_cached.return_value = {'extract': 'A sample description', 'title': 'Paris'} + + service = WikipediaDescriptionService() + result = service.get_description('Paris', 'en') + + self.assertEqual(result['extract'], 'A sample description') + mock_get_or_fetch_cached.assert_called_once() + args = mock_get_or_fetch_cached.call_args.args + self.assertEqual(args[0], 'wikipedia_desc_v1') + self.assertEqual(args[1], 'en') + self.assertEqual(args[2], 'Paris') diff --git a/backend/server/adventures/throttling.py b/backend/server/adventures/throttling.py index 88a9e3b9..263f1260 100644 --- a/backend/server/adventures/throttling.py +++ b/backend/server/adventures/throttling.py @@ -29,5 +29,5 @@ class ExternalWikipediaThrottle(ConditionalUserRateThrottle): scope = "external_wikipedia" -class ExternalSunTimesThrottle(ConditionalUserRateThrottle): - scope = "external_sun_times" +class ExternalSunriseSunsetThrottle(ConditionalUserRateThrottle): + scope = "external_sunrise_sunset" diff --git a/backend/server/adventures/urls.py b/backend/server/adventures/urls.py index c1dbdc26..e566b7bb 100644 --- a/backend/server/adventures/urls.py +++ b/backend/server/adventures/urls.py @@ -2,6 +2,7 @@ from django.urls import include, path from rest_framework.routers import DefaultRouter from adventures.views import * from adventures.views.places_api_view import PlacesAPI +from adventures.views.sunrise_sunset_view import SunriseSunsetAPI router = DefaultRouter() router.register(r'locations', LocationViewSet, basename='locations') @@ -15,6 +16,7 @@ router.register(r'checklists', ChecklistViewSet, basename='checklists') router.register(r'images', ContentImageViewSet, basename='images') router.register(r'reverse-geocode', ReverseGeocodeViewSet, basename='reverse-geocode') router.register(r'places', PlacesAPI, basename='places') +router.register(r'sunrise-sunset', SunriseSunsetAPI, basename='sunrise-sunset') router.register(r'categories', CategoryViewSet, basename='categories') router.register(r'ics-calendar', IcsCalendarGeneratorViewSet, basename='ics-calendar') router.register(r'search', GlobalSearchView, basename='search') diff --git a/backend/server/adventures/utils/location_access.py b/backend/server/adventures/utils/location_access.py new file mode 100644 index 00000000..e87839c1 --- /dev/null +++ b/backend/server/adventures/utils/location_access.py @@ -0,0 +1,18 @@ +from adventures.models import Location + + +def user_can_access_location(location: Location, user) -> bool: + if location.is_public: + return True + + if user.is_authenticated and location.user == user: + return True + + if user.is_authenticated: + for collection in location.collections.all(): + if collection.user == user: + return True + if collection.shared_with.filter(uuid=user.uuid).exists(): + return True + + return False diff --git a/backend/server/adventures/views/location_view.py b/backend/server/adventures/views/location_view.py index ab4dd5bd..77488b39 100644 --- a/backend/server/adventures/views/location_view.py +++ b/backend/server/adventures/views/location_view.py @@ -20,8 +20,7 @@ from adventures.serializers import ( MapPinSerializer, ) from adventures.utils import pagination -from adventures.throttling import ExternalGeocodeThrottle, ExternalSunTimesThrottle -from adventures.utils.geo import point_to_lat_lon +from adventures.throttling import ExternalGeocodeThrottle from adventures.services.geocoding.reverse import reverse_geocode as reverse_geocode_service from adventures.services.share_image import ( build_share_image, @@ -393,32 +392,20 @@ class LocationViewSet(viewsets.ModelViewSet): serializer = CalendarLocationSerializer(queryset, many=True) return Response(serializer.data) - @action( - detail=True, - methods=['get'], - url_path='additional-info', - throttle_classes=[ExternalSunTimesThrottle], - ) + @action(detail=True, methods=['get'], url_path='additional-info') def additional_info(self, request, pk=None): - """Get adventure with additional sunrise/sunset information.""" + """Get adventure with extended details (kept for backwards compatibility).""" adventure = self.get_object() user = request.user - # Validate access permissions if not self._has_adventure_access(adventure, user): return Response( {"error": "User does not have permission to access this adventure"}, status=status.HTTP_403_FORBIDDEN ) - # Get base adventure data serializer = self.get_serializer(adventure) - response_data = serializer.data - - # Add sunrise/sunset data - response_data['sun_times'] = self._get_sun_times(adventure, response_data.get('visits', [])) - - return Response(response_data) + return Response(serializer.data) @action(detail=True, methods=['post']) def duplicate(self, request, pk=None): @@ -812,16 +799,6 @@ class LocationViewSet(viewsets.ModelViewSet): return False - def _get_sun_times(self, adventure, visits): - """Get sunrise/sunset times for adventure visits via service.""" - from adventures.services.sun.times import get_sun_times_for_visits - - try: - latitude, longitude = point_to_lat_lon(adventure.coordinates) - return get_sun_times_for_visits(latitude, longitude, visits) - except Exception: - return [] - def paginate_and_respond(self, queryset, request): """Paginate queryset and return response.""" paginator = self.pagination_class() diff --git a/backend/server/adventures/views/sunrise_sunset_view.py b/backend/server/adventures/views/sunrise_sunset_view.py new file mode 100644 index 00000000..d2418889 --- /dev/null +++ b/backend/server/adventures/views/sunrise_sunset_view.py @@ -0,0 +1,49 @@ +from rest_framework import viewsets +from rest_framework.decorators import action +from rest_framework.permissions import AllowAny +from rest_framework.response import Response + +from adventures.models import Location +from adventures.services.sunrise_sunset.times import get_sunrise_sunset_for_date +from adventures.throttling import ExternalSunriseSunsetThrottle +from adventures.utils.geo import point_to_lat_lon +from adventures.utils.location_access import user_can_access_location + + +class SunriseSunsetAPI(viewsets.ViewSet): + permission_classes = [AllowAny] + + @action(detail=False, methods=['get'], throttle_classes=[ExternalSunriseSunsetThrottle]) + def lookup(self, request): + location_id = request.query_params.get('location_id', '').strip() + date_value = request.query_params.get('date', '').strip() + + if not location_id: + return Response({'error': 'location_id parameter is required'}, status=400) + if not date_value: + return Response({'error': 'date parameter is required'}, status=400) + + try: + location = Location.objects.get(pk=location_id) + except Location.DoesNotExist: + return Response({'error': 'Location not found'}, status=404) + + if not user_can_access_location(location, request.user): + return Response( + {'error': 'User does not have permission to access this location'}, + status=403, + ) + + if not location.coordinates: + return Response({'error': 'Location has no coordinates'}, status=400) + + try: + latitude, longitude = point_to_lat_lon(location.coordinates) + except Exception: + return Response({'error': 'Location coordinates are invalid'}, status=400) + + sunrise_sunset = get_sunrise_sunset_for_date(latitude, longitude, date_value) + if not sunrise_sunset: + return Response({'error': 'Sunrise/sunset not available for this date'}, status=404) + + return Response(sunrise_sunset) diff --git a/backend/server/main/settings.py b/backend/server/main/settings.py index 0a00d5c4..7dda8919 100644 --- a/backend/server/main/settings.py +++ b/backend/server/main/settings.py @@ -333,9 +333,14 @@ RATE_LIMIT_RATES = { 'external_geocode': getenv('RATE_LIMIT_EXTERNAL_GEOCODE', '120/minute'), 'external_recommendations': getenv('RATE_LIMIT_EXTERNAL_RECOMMENDATIONS', '30/minute'), 'external_wikipedia': getenv('RATE_LIMIT_EXTERNAL_WIKIPEDIA', '60/minute'), - 'external_sun_times': getenv('RATE_LIMIT_EXTERNAL_SUN_TIMES', '30/minute'), + 'external_sunrise_sunset': getenv('RATE_LIMIT_EXTERNAL_SUNRISE_SUNSET', '30/minute'), } +SUNRISE_SUNSET_CACHE_TIMEOUT = int(getenv('SUNRISE_SUNSET_CACHE_TIMEOUT', str(60 * 60 * 24 * 30))) +WIKIPEDIA_DESC_CACHE_TIMEOUT = int(getenv('WIKIPEDIA_DESC_CACHE_TIMEOUT', str(60 * 60 * 24 * 7))) +WIKIPEDIA_SUMMARY_CACHE_TIMEOUT = int(getenv('WIKIPEDIA_SUMMARY_CACHE_TIMEOUT', str(60 * 60 * 24 * 7))) +EXTERNAL_API_CACHE_TIMEOUT = int(getenv('EXTERNAL_API_CACHE_TIMEOUT', str(60 * 60 * 24))) + FORCE_SOCIALACCOUNT_LOGIN = getenv('FORCE_SOCIALACCOUNT_LOGIN', 'false').lower() == 'true' # When true, only social login is allowed (no password login) and the login page will show only social providers or redirect directly to the first provider if only one is configured. if getenv('EMAIL_BACKEND', 'console') == 'console': diff --git a/documentation/docs/configuration/advanced_configuration.md b/documentation/docs/configuration/advanced_configuration.md index 47fc1342..30002af7 100644 --- a/documentation/docs/configuration/advanced_configuration.md +++ b/documentation/docs/configuration/advanced_configuration.md @@ -30,7 +30,7 @@ Per-endpoint overrides (all optional): | `RATE_LIMIT_EXTERNAL_GEOCODE` | `120/minute` | External geocoding | | `RATE_LIMIT_EXTERNAL_RECOMMENDATIONS` | `30/minute` | Recommendations | | `RATE_LIMIT_EXTERNAL_WIKIPEDIA` | `60/minute` | Wikipedia lookups | -| `RATE_LIMIT_EXTERNAL_SUN_TIMES` | `30/minute` | Sun times API | +| `RATE_LIMIT_EXTERNAL_SUNRISE_SUNSET` | `30/minute` | Sunrise/sunset API | ## Performance diff --git a/documentation/docs/configuration/environment_variables.md b/documentation/docs/configuration/environment_variables.md index b3ab1245..3ed94961 100644 --- a/documentation/docs/configuration/environment_variables.md +++ b/documentation/docs/configuration/environment_variables.md @@ -84,7 +84,7 @@ Related guides: [Social Auth](social_auth.md), [Disable Registration](disable_re | `RATE_LIMIT_EXTERNAL_GEOCODE` | No | External geocoding limit. | `120/minute` | | `RATE_LIMIT_EXTERNAL_RECOMMENDATIONS` | No | Recommendations API limit. | `30/minute` | | `RATE_LIMIT_EXTERNAL_WIKIPEDIA` | No | Wikipedia lookup limit. | `60/minute` | -| `RATE_LIMIT_EXTERNAL_SUN_TIMES` | No | Sun times API limit. | `30/minute` | +| `RATE_LIMIT_EXTERNAL_SUNRISE_SUNSET` | No | Sunrise/sunset API limit. | `30/minute` | See [Advanced Configuration](advanced_configuration.md) for usage notes. diff --git a/frontend/src/lib/sunriseSunset.ts b/frontend/src/lib/sunriseSunset.ts new file mode 100644 index 00000000..e0eb2676 --- /dev/null +++ b/frontend/src/lib/sunriseSunset.ts @@ -0,0 +1,27 @@ +export type SunriseSunset = { + date: string; + sunrise: string; + sunset: string; +}; + +export function visitDateKey(startDate: string | null | undefined): string | null { + if (!startDate) return null; + return startDate.split('T')[0] ?? null; +} + +export async function fetchSunriseSunset( + locationId: string, + date: string +): Promise { + const params = new URLSearchParams({ + location_id: locationId, + date + }); + + const response = await fetch(`/api/sunrise-sunset/lookup/?${params.toString()}`); + if (!response.ok) { + return null; + } + + return (await response.json()) as SunriseSunset; +} diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index f2129d42..c260f46e 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -100,15 +100,6 @@ export type Location = { trails: Trail[]; }; -export type AdditionalLocation = Location & { - sun_times: { - date: string; - visit_id: string; - sunrise: string; - sunset: string; - }[]; -}; - export type Country = { id: number; name: string; diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 3a7732a7..2805ad91 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -154,9 +154,11 @@ "distance": "Distance", "share_collection": "Share this Collection!", "copy_link": "Copy Link", - "sun_times": "Sun Times", "sunrise": "Sunrise", "sunset": "Sunset", + "show_sunrise_sunset": "Show sunrise & sunset", + "loading_sunrise_sunset": "Loading sunrise & sunset...", + "sunrise_sunset_load_error": "Could not load sunrise and sunset times", "open_in_maps": "Open in Maps", "fetch_image": "Fetch Image", "wikipedia": "Wikipedia", diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json index 25740d0e..d018249a 100644 --- a/frontend/src/locales/fr.json +++ b/frontend/src/locales/fr.json @@ -1,1402 +1,1402 @@ { - "about": { - "about": "À propos", - "close": "Fermer", - "license": "Sous licence GPL-3.0.", - "message": "Fabriqué avec ❤️ aux États-Unis.", - "nominatim_1": "La recherche de localisation et le géocodage sont fournis par", - "other_attributions": "Des attributions supplémentaires peuvent être trouvées dans le fichier README.", - "generic_attributions": "Connectez-vous à AdventureLog pour afficher les attributions pour les intégrations et services activés.", - "attributions": "Attributions", - "developer": "Développeur", - "license_info": "Licence", - "sponsor": "Parrainer", - "thank_you": "Merci d'utiliser AdventureLog !", - "version": "Version", - "view_changelog": "Voir le journal des modifications", - "view_license": "Afficher la licence", - "copy_version": "Copier la version" - }, - "adventures": { - "add_to_collection": "Ajouter à la collection", - "delete": "Supprimer", - "open_details": "Ouvrir les détails", - "remove_from_collection": "Supprimer de la collection", - "archive": "Archiver", - "archived": "Archivée", - "archived_collection_message": "Collection archivée avec succès !", - "archived_collections": "Collections archivées", - "ascending": "Ascendant", - "cancel": "Annuler", - "category_filter": "Filtres de catégorie", - "clear": "Réinitialiser", - "collection": "Collection", - "create_new": "Créer une nouvelle aventure", - "date": "Date", - "dates": "Dates", - "delete_collection": "Supprimer la collection", - "delete_collection_success": "Collection supprimée avec succès !", - "descending": "Descendant", - "duration": "Durée", - "edit_collection": "Modifier la collection", - "filter": "Filtrer", - "homepage": "Page d'accueil", - "image_upload_error": "Erreur lors du téléchargement de l'image", - "image_upload_success": "Image téléchargée avec succès !", - "latitude": "Latitude", - "longitude": "Longitude", - "my_collections": "Mes collections", - "name": "Nom", - "no_image_url": "Aucune image trouvée à cette URL.", - "order_by": "Trier par", - "order_direction": "Direction du tri", - "planned": "Prévue", - "private": "Privée", - "public": "Publique", - "rating": "Notation", - "share": "Partager", - "sort": "Trier", - "sources": "Sources", - "start_before_end_error": "La date de début doit être antérieure à la date de fin", - "unarchive": "Désarchiver", - "unarchived_collection_message": "Collection désarchivée avec succès !", - "updated": "Mise à jour", - "visit": "Visite", - "visited": "Visitée", - "visits": "Visites", - "wiki_image_error": "Erreur lors de la récupération de l'image depuis Wikipédia", - "add": "Ajouter", - "basic_information": "Informations de base", - "category": "Catégorie", - "copy_link": "Copier le lien", - "date_constrain": "Limiter aux dates de la collection", - "description": "Description", - "end_date": "Date de fin", - "fetch_image": "Récupérer une image", - "generate_desc": "Générer une description", - "image_fetch_failed": "Échec de la récupération de l'image", - "link": "Lien", - "location": "Lieu", - "no_results": "Aucun résultat trouvé", - "remove": "Retirer", - "search_for_location": "Rechercher un lieu", - "search_results": "Résultats de la recherche", - "start_date": "Date de début", - "wikipedia": "Wikipédia", - "adventure_not_found": "Il n'y a aucune aventure à afficher. Ajoutez-en en utilisant le bouton '+' en bas à droite ou essayez de changer les filtres !", - "all": "Tous", - "error_updating_regions": "Erreur lors de la mise à jour des régions", - "mark_visited": "Marquer comme visité", - "not_visited": "Non visitée", - "regions_updated": "régions mises à jour", - "update_visited_regions": "Mettre à jour les régions visitées", - "add_new": "Ajouter un nouveau...", - "checklists": "Listes de contrôle", - "days": "jours", - "link_new": "Ajouter un lien vers...", - "links": "Liens", - "note": "Note", - "notes": "Notes", - "transportation": "Déplacement", - "transportations": "Déplacements", - "checklist": "Liste de contrôle", - "day": "Jour", - "add_a_tag": "Ajouter une balise", - "tags": "Balises", - "copied_to_clipboard": "Copié dans le presse-papier !", - "copy_failed": "Échec de la copie", - "download_calendar": "Télécharger le calendrier", - "md_instructions": "Écrivez ici au format Markdown...", - "preview": "Aperçu", - "checklist_delete_confirm": "Êtes-vous sûr de vouloir supprimer cette liste de contrôle ? Cette action ne peut pas être annulée.", - "date_information": "Dates", - "delete_checklist": "Supprimer la liste de contrôle", - "delete_note": "Supprimer la note", - "delete_transportation": "Supprimer le déplacement", - "end": "Arrivée", - "from": "Depuis", - "note_delete_confirm": "Êtes-vous sûr de vouloir supprimer cette note ? Cette action ne peut pas être annulée.", - "out_of_range": "Pas dans la plage de dates de l'itinéraire", - "start": "Départ", - "to": "Vers", - "transportation_delete_confirm": "Etes-vous sûr de vouloir supprimer ce transport ? Cette action ne peut pas être annulée.", - "cities_updated": "villes mises à jour", - "attachment": "Pièce jointe", - "attachment_delete_success": "Pièce jointe supprimée avec succès !", - "attachment_name": "Nom de la pièce jointe", - "attachment_update_error": "Erreur lors de la mise à jour de la pièce jointe", - "attachment_upload_error": "Erreur lors du téléchargement de la pièce jointe", - "attachment_upload_success": "Pièce jointe téléchargée avec succès !", - "attachments": "Pièces jointes", - "gpx_tip": "Téléchargez des fichiers GPX en pièces jointes pour les afficher sur la carte !", - "images": "Images", - "upload": "Télécharger", - "city": "Ville", - "delete_lodging": "Supprimer l'hébergement", - "lodging": "Hébergement", - "lodging_delete_confirm": "Êtes-vous sûr de vouloir supprimer cet hébergement ? Cette action ne peut pas être annulée.", - "price": "Prix", - "region": "Région", - "open_in_maps": "Ouvert dans les cartes", - "all_day": "Journée complète", - "collection_no_start_end_date": "L'ajout d'une date de début et de fin à la collection débloquera les fonctionnalités de planification de l'itinéraire dans la page de collection.", - "invalid_date_range": "Plage de dates non valide", - "timezone": "Fuseau horaire", - "no_visits": "Pas de visites", - "coordinates": "Coordonnées", - "copy_coordinates": "Copier les coordonnées", - "sun_times": "Éphémérides", - "sunrise": "Lever du soleil", - "sunset": "Coucher de soleil", - "timed": "Chronométré", - "distance": "Distance", - "itinerary": "Itinéraire", - "joined": "Joint", - "view_profile": "Afficher le profil", - "share_collection": "Partagez cette collection !", - "filters_and_sort": "Filtres", - "filters_and_stats": "Filtres", - "no_adventures_message": "Commencez à documenter vos aventures et à en planifier de nouvelles. Chaque voyage a une histoire qui mérite d'être racontée.", - "travel_progress": "Progrès du voyage", - "collections_linked": "Collections liées", - "create_collection_first": "Créez d'abord une collection pour organiser vos aventures et vos souvenirs.", - "delete_collection_warning": "Êtes-vous sûr de vouloir supprimer cette collection ? Cette action ne peut pas être annulée.", - "done": "Fait", - "name_location": "nom, emplacement", - "check_in": "Enregistrement", - "check_out": "Départ", - "collection_link_location_error": "Erreur liant l'emplacement à la collection", - "collection_link_location_success": "Emplacement lié à la collection avec succès !", - "collection_locations": "Inclure les emplacements de collecte", - "collection_remove_location_error": "Erreur de suppression de l'emplacement de la collection", - "collection_remove_location_success": "Emplacement retiré de la collection avec succès !", - "create_location": "Créer un emplacement", - "delete_location": "Supprimer l'emplacement", - "edit_location": "Modifier l'emplacement", - "location_delete_confirm": "Êtes-vous sûr de vouloir supprimer cet emplacement ? Cette action ne peut pas être annulée.", - "location_delete_success": "Emplacement supprimé avec succès !", - "location_not_found": "Emplacement introuvable", - "location_not_found_desc": "L'emplacement que vous recherchiez n'a pas pu être trouvé. Veuillez essayer un autre emplacement ou revenir plus tard.", - "new_location": "Nouvel emplacement", - "no_collections_to_add_location": "Aucune collection n'a été trouvée pour ajouter cet emplacement à.", - "public_location": "Lieu public", - "visit_calendar": "Calendrier des visites", - "no_locations_found": "Aucun emplacement trouvé", - "image_modal_navigate": "Utilisez des touches flèches ou cliquez pour naviguer", - "details": "Détails", - "leave": "Partir", - "leave_collection": "Collecte de congé", - "leave_collection_warning": "Êtes-vous sûr de vouloir quitter cette collection ? Tous les emplacements que vous avez ajoutés seront non liés et resteront dans votre compte.", - "left_collection_message": "Collection de gauche avec succès", - "loading_collections": "Chargement des collections ...", - "quick_start": "Démarrage rapide", - "achievements": "Réalisations", - "active_duration": "Durée active", - "activity_name": "Nom d'activité", - "activity_name_placeholder": "Run du matin", - "activity_name_required": "Le nom d'activité est requis", - "activity_options": "Options d'activité", - "activity_statistics": "Statistiques d'activité", - "activity_statistics_description": "Vos réalisations de fitness et d'activité", - "add_activity": "Ajouter une activité", - "add_first_trail": "Ajoutez votre premier sentier en utilisant le bouton ci-dessus", - "add_new_activity": "Ajouter une nouvelle activité", - "add_new_trail": "Ajouter un nouveau sentier", - "add_trail": "Ajouter une piste", - "add_visit": "Ajouter la visite", - "add_wanderer_trail": "Ajouter le sentier Wanderer", - "attachment_management": "Gestion de l'attachement", - "attachment_name_required": "Nom de la pièce jointe requis", - "attachment_remove_error": "Une erreur s'est produite lors du retrait de la pièce jointe", - "attachment_removed": "L'attachement supprimé avec succès", - "attachment_updated": "Pièce jointe mise à jour avec succès", - "average_cadence": "Cadence moyenne", - "average_speed": "Vitesse moyenne", - "avg_speed": "Vitesse moyenne", - "back": "Retour", - "cadence": "Cadence", - "calories": "Calories", - "click_map": "Cliquez sur la carte pour sélectionner un emplacement", - "click_on_map": "Cliquez sur la carte pour sélectionner un emplacement", - "complete_import": "Importation complète", - "complete_strava_import": "Importation complète de Strava", - "confirm_delete_activity": "Êtes-vous sûr de vouloir supprimer cette activité ?", - "connect_to_wanderer": "Connectez-vous à Wanderer", - "continue": "Continuer", - "create_new_location": "Créer un nouvel emplacement", - "create_trail": "Créer une piste", - "created": "Créé", - "current_attachments": "Fichiers joints", - "date_selection": "Sélection de date", - "download_gpx": "Télécharger GPX", - "edit_visit": "Modifier la visite", - "elapsed_time": "Temps écoulé", - "elevation": "Élévation", - "elevation_gain": "Gain d'altitude", - "elevation_high": "Élévation élevée", - "elevation_loss": "Perte", - "elevation_low": "Élévation basse", - "end_lat": "Latitude finale", - "end_lng": "Fin de longitude", - "export_gpx": "Exporter gpx", - "export_original": "Exporter original", - "external_link": "Lien externe", - "gain": "gagner", - "getting_location_details": "Obtenir les détails de l'emplacement", - "gpx_file": "Fichier gpx", - "gpx_file_downloaded": "Fichier GPX téléchargé. Veuillez le télécharger ci-dessous pour terminer l'importation.", - "gpx_file_required": "Fichier GPX requis", - "image_management": "Gestion de l'image", - "import_activity": "Activité d'importation", - "importing": "Importation", - "likes": "Goûts", - "loading_activities": "Activités de chargement", - "location_display_name": "Nom de l'écran d'emplacement", - "location_map": "Emplacement", - "location_selected": "Emplacement sélectionné", - "max_speed": "Vitesse maximale", - "moving_time": "Temps de déménagement", - "next_image": "Image suivante", - "no_attachments_uploaded_yet": "Aucune pièce jointe encore téléchargée", - "no_external_link": "Aucun lien externe disponible", - "no_file_selected": "Aucun fichier sélectionné", - "no_images_uploaded_yet": "Aucune images encore téléchargées", - "no_strava_activities": "Aucune activité Strava trouvée lors de cette visite", - "no_trails_added": "Aucun sentier ajouté encore", - "no_trails_available": "Pas de sentiers disponibles", - "no_trails_found_matching": "Aucun sentier trouvé correspondant", - "no_visits_description": "Créez votre première visite en sélectionnant les dates ci-dessus", - "notes_placeholder": "Ajouter des notes sur cette visite", - "or": "OU", - "pace": "Rythme", - "photos": "Photos", - "previous_image": "Image précédente", - "processing": "Traitement", - "public_location_description": "Rendre cet emplacement visible pour les autres utilisateurs", - "remove_visit": "Supprimer la visite", - "rest_time": "Temps de repos", - "saved_activities": "Activités sauvées", - "search_location": "Rechercher un emplacement", - "search_placeholder": "Entrez la ville, l'emplacement ou le point de repère ...", - "search_trails_placeholder": "Recherchez les sentiers par nom", - "searching": "Recherche", - "select_on_map": "Sélectionner sur la carte", - "select_wanderer_trail": "Sélectionnez un sentier dans votre compte Wanderer", - "sport_type": "Type de sport", - "start_lat": "Latitude de départ", - "start_lng": "Longitude de départ", - "strava_activities_during_visit": "Activités de Strava lors de la visite", - "strava_activity_ready": "Activité Strava prête", - "time": "Temps", - "total_recorded": "Total enregistré", - "trail": "Piste", - "trail_created_successfully": "Trail créé avec succès", - "trail_creation_failed": "Échec de la création de sentiers", - "trail_fetch_failed": "Échec de la récupération des sentiers Wanderer", - "trail_link_required": "Une liaison de sentier est requise", - "trail_name": "Nom du sentier", - "trail_removal_failed": "Échec de la suppression du sentier", - "trail_removed_successfully": "Le sentier a été supprimé avec succès", - "trail_update_failed": "Échec de la mise à jour du sentier", - "trail_updated_successfully": "Trail mis à jour avec succès", - "trails": "Sentiers", - "trails_found_for": "sentiers trouvés pour", - "trails_management": "Gestion des sentiers", - "trails_management_description": "Gérez les sentiers associés à cet emplacement. Les sentiers peuvent être liés à des services externes comme Alltrails ou Wanderer Trails.", - "update_location_details": "Mettre à jour les détails de l'emplacement", - "update_visit": "Mettre à jour la visite", - "upload_activity": "Télécharger l'activité", - "upload_attachment": "Télécharger la pièce jointe", - "upload_first_attachment": "Téléchargez votre première pièce jointe à l'aide des options ci-dessus", - "upload_first_image": "Téléchargez votre première image en utilisant l'une des options ci-dessus", - "upload_from_device": "Télécharger à partir de l'appareil", - "upload_from_url": "Télécharger de l'URL", - "upload_gpx_file": "Téléchargez le fichier gpx qui vient d'être téléchargé pour terminer l'importation de Strava", - "uploading": "Téléchargement", - "use_current_location": "Utiliser l'emplacement actuel", - "view_gpx": "Afficher GPX", - "view_on": "Voir", - "view_strava_activities": "Voir les activités de Strava", - "view_trail": "Voir le sentier", - "wanderer_integration_error": "L'intégration de Wanderer n'est pas activée ou a expiré. Connectez le dans Paramètres → Intégrations.", - "wikipedia_error": "Échec de génération de description à partir de Wikipedia", - "high": "Haut", - "low": "Faible", - "total": "Total", - "rest": "Repos", - "lodging_save_error": "Économie d'erreur", - "activities_text": "activités", - "activity_breakdown_by_category": "Déchange d'activité par catégorie", - "distance_covered": "Distance couverte", - "recorded_sessions": "Sessions enregistrées", - "total_activities": "Activités totales", - "total_climbed": "Total grimpé", - "total_distance": "Distance totale", - "dates_not_saved": "Visitez non encore ajouté", - "dates_not_saved_description": "Cliquez sur Ajouter une visite pour enregistrer", - "active_days": "Journées actives", - "add_here": "Ajouter ici", - "airport_code_examples": "JFK, LAX, LHR...", - "airport_search_mode": "Mode de recherche d'aéroport", - "all_locations_already_linked": "Tous les emplacements sont déjà liés à cette collection.", - "arrival_airport": "Aéroport d'arrivée", - "avg_rating": "Note moyenne", - "burned": "brûlé", - "categories": "Catégories", - "cities": "Villes", - "clear_search": "Effacer la recherche", - "click_map_add_marker": "Cliquez sur la carte pour déposer un marqueur, puis ajoutez-le ici.", - "completed": "Complété", - "content_media": "Contenu", - "countries": "Pays", - "country": "pays", - "departure_airport": "Aéroport de départ", - "distance_traveled": "Distance parcourue", - "end_location": "Lieu d'arrivée", - "enter_location_display_name": "Entrez le nom d'affichage de l'emplacement", - "files": "Fichiers", - "flight": "Vol", - "folder": "Dossier", - "folder_view": "Vue des dossiers - affichant toutes les données", - "footprints": "Empreintes", - "gained": "gagné", - "geographic_breakdown": "Répartition géographique", - "gpx_routes": "Itinéraires GPX", - "hide_filters": "Masquer les filtres", - "images_captured": "Images capturées", - "in": "dans", - "in_progress": "En cours", - "items": "articles", - "itinerary_link_modal": { - "add_here": "Ajouter ici", - "add_here_keep_date": "Ajouter (conserver la date)", - "add_to_itinerary": "Ajouter à l'itinéraire", - "already_added": "Déjà ajouté", - "already_added_on_this_day": "Déjà ajouté ce jour", - "already_added_on_this_day_desc": "Ces éléments sont déjà programmés pour cette journée.", - "already_added_other_days": "Déjà ajouté les autres jours", - "already_added_other_days_desc": "Ces éléments sont programmés à des dates différentes. Les ajouter ici mettra à jour leur date ou les ajoutera tels quels.", - "items_available": "{count} éléments disponibles pour lier", - "items_on_other_days": "Articles les autres jours", - "items_on_other_days_desc": "Ces éléments ont des dates différentes. Vous pouvez les ajouter et éventuellement mettre à jour leur date en conséquence.", - "items_on_this_day": "Articles de ce jour", - "no_unscheduled_items": "Aucun élément non planifié disponible", - "no_unscheduled_items_desc": "Tous les éléments ont été ajoutés à l'itinéraire ou il n'y a aucun élément à ajouter.", - "title": "Lier les éléments à {date}" - }, - "link_copied": "Lien copié", - "lists": "Listes", - "location_search_mode": "Mode de recherche d'emplacement", - "locations_visited": "Lieux visités", - "lodging_not_found": "Hébergement introuvable", - "lodging_types": "Types d'hébergement", - "more_details": "Plus de détails", - "my_locations": "Mes emplacements", - "nights": "Nuits", - "no_linkable_locations": "Aucun emplacement trouvé pouvant être lié à cette collection.", - "of_locations": "des emplacements", - "on_this_trip": "Lors de ce voyage", - "overnight": "Du jour au lendemain", - "physical_activities": "Activités physiques", - "pins": "épingles", - "places": "Lieux", - "regions": "Régions", - "reservation": "Réservation", - "reset_filters": "Réinitialiser", - "route": "Itinéraire", - "route_selected": "Itinéraire sélectionné", - "routes_and_activities": "Itinéraires", - "saving": "Enregistrement", - "search_end_location": "Emplacement final de la recherche", - "search_start_end_locations": "Rechercher des emplacements de début et de fin", - "search_start_location": "Lieu de départ de la recherche", - "segments": "Segments", - "select": "Sélectionner", - "show_filters": "Afficher les filtres", - "start_location": "Lieu de départ", - "status_filter": "Filtre d'état", - "stays": "Séjours", - "tasks_done": "Tâches effectuées", - "total_days": "Nombre total de jours", - "total_visits": "Visites totales", - "transport_activity_paths": "Transport et chemin d'activité", - "transportation_gpx_tip": "Téléchargez les fichiers GPX ici pour les afficher sur la carte", - "traveled": "voyagé", - "travelers": "Voyageurs", - "trip_timeline": "Chronologie du voyage", - "trip_window": "Durée de voyage", - "upcoming": "Prochain", - "view": "Voir", - "wiki_results_found": "Résultats Wikipédia", - "with_activities": "Avec des activités", - "written": "Écrit", - "export_failed": "Échec de l'exportation", - "export_success": "Collection exportée", - "export_zip": "Exporter le code postal", - "import_failed": "Échec de l'importation", - "import_success": "Succès de l'importation", - "import_from_file": "Importer à partir d'un fichier", - "duplicate": "Dupliquer", - "duplicate_location": "Dupliquer le lieu", - "location_duplicate_success": "Emplacement dupliqué avec succès !", - "location_duplicate_error": "Échec de la duplication du lieu.", - "location_actions": "Actions du lieu", - "collection_duplicate_success": "Collection dupliquée avec succès ! Redirection...", - "collection_duplicate_error": "Échec de la duplication de la collection.", - "hide_strava_activities": "Masquer les activités Strava", - "show_strava_activities": "Afficher les activités Strava", - "add_details": "Ajouter des détails", - "export_pdf": "Télécharger le PDF", - "export_pdf_success": "PDF de la collection téléchargé", - "export_pdf_failed": "Echec du téléchargement du PDF de la collection", - "google_photos_available": "{count} Google photo(s) disponible", - "google_photos_import_all": "Tout importer depuis Google", - "google_photos_import_selected": "Importer la sélection", - "google_photos_importing": "Importation depuis Google photos…", - "google_photos_import_success": "Importation Google photos réalisée avec succès", - "google_photos_import_error": "Impossible d'importer Google photos", - "google_photos_select_hint": "Sélectionnez les photos que vous souhaitez importer, ou importez-les toutes en même temps." - }, - "home": { - "desc_1": "Découvrez, planifiez et explorez en toute simplicité", - "desc_2": "AdventureLog est conçu pour simplifier votre voyage, en vous fournissant les outils et les ressources nécessaires pour planifier, préparer et naviguer dans votre prochaine aventure inoubliable.", - "feature_1": "Carnet de voyage", - "feature_1_desc": "Gardez une trace de vos aventures avec un carnet de voyage personnalisé et partagez vos expériences avec vos amis et votre famille.", - "feature_2": "Planification du voyage", - "feature_2_desc": "Créez facilement des itinéraires personnalisés et obtenez un aperçu quotidien de votre voyage.", - "feature_3": "Carte de voyage", - "feature_3_desc": "Visualisez vos voyages à travers le monde avec une carte interactive et explorez de nouvelles destinations.", - "go_to": "Aller au journal d'aventure", - "hero_1": "Découvrez les aventures les plus palpitantes du monde", - "hero_2": "Découvrez et planifiez votre prochaine aventure avec AdventureLog. Explorez des destinations à couper le souffle, créez des itinéraires personnalisés et restez connecté lors de vos déplacements.", - "key_features": "Principales fonctionnalités", - "explore_world": "Explorer le monde", - "latest_travel_experiences": "Vos dernières expériences de voyage", - "of_world": "du monde", - "start_your_journey": "Commencez votre voyage" - }, - "navbar": { - "about": "À propos de AdventureLog", - "collections": "Collections", - "documentation": "Documentation", - "greeting": "Salut", - "logout": "Déconnexion", - "map": "Carte", - "profile": "Profil", - "search": "Recherche", - "settings": "Paramètres", - "shared_with_me": "Partagé avec moi", - "theme_selection": "Sélection de thèmes", - "themes": { - "aestheticDark": "Esthétique sombre", - "aestheticLight": "Lumière esthétique", - "aqua": "Aqua", - "dark": "Sombre", - "dim": "Faible", - "forest": "Forêt", - "light": "Lumière", - "night": "Nuit", - "northernLights": "Lumières du Nord" - }, - "users": "Utilisateurs", - "language_selection": "Langue", - "support": "Soutien", - "calendar": "Calendrier", - "admin_panel": "Panneau d'administration", - "navigation": "Navigation", - "worldtravel": "Voyage dans le monde", - "mobile_login": "Connexion mobile" - }, - "auth": { - "confirm_password": "Confirmer le mot de passe", - "email": "E-mail", - "first_name": "Prénom", - "forgot_password": "Mot de passe oublié ?", - "last_name": "Nom de famille", - "login": "Se connecter", - "login_error": "Impossible de se connecter avec les identifiants fournis.", - "password": "Mot de passe", - "registration_disabled": "L'inscription est actuellement désactivée.", - "signup": "S'inscrire", - "username": "Nom d'utilisateur", - "profile_picture": "Photo de profil", - "public_profile": "Profil public", - "new_password": "Nouveau mot de passe", - "or_3rd_party": "Ou connectez-vous avec un service tiers", - "no_public_collections": "Aucune collection publique trouvée", - "user_collections": "Collections de l'utilisateur", - "no_public_locations": "Aucun emplacement public trouvé", - "user_locations": "Emplacements des utilisateurs", - "enter_password": "Entrez votre mot de passe", - "enter_username": "Entrez votre nom d'utilisateur", - "logging_in": "Se connecter", - "totp": "Code à deux facteurs", - "user_email_verification_required": "Vérification par e-mail requise. Veuillez vérifier votre courrier électronique pour un lien de vérification." - }, - "users": { - "no_users_found": "Aucun utilisateur trouvé avec un profil public.", - "no_users_desc": "Lorsque d'autres voyageurs rendront leur profil public, celui-ci apparaîtra ici.", - "no_match": "Aucun utilisateur ne correspond à votre recherche", - "no_match_desc": "Essayez un autre nom ou effacez vos filtres.", - "page_description": "Parcourez les profils publics d'AdventureLog et découvrez d'autres voyageurs.", - "search_placeholder": "Recherche par nom ou nom d'utilisateur...", - "public_profiles": "Profils publics", - "public_profile": "Profil public", - "browse_options": "Options de navigation", - "overview": "Aperçu", - "sort_name": "Nom", - "sort_recent": "Nouveautés" - }, - "worldtravel": { - "country_list": "Liste des pays", - "no_countries_found": "Aucun pays trouvé", - "cities": "villes", - "failed_to_mark_visit": "Échec de la notation de la visite à", - "failed_to_remove_visit": "Échec de la suppression de la visite à", - "marked_visited": "marqué comme visité", - "no_cities_found": "Aucune ville trouvée", - "region_failed_visited": "Échec du marquage de la région comme visitée", - "regions_in": "Régions dans", - "removed": "supprimé", - "view_cities": "Voir les villes", - "visit_remove_failed": "Échec de la suppression de la visite", - "visit_to": "Visite à", - "all_regions": "Toutes les régions", - "available_to_explore": "Disponible pour explorer", - "cities_in": "Villes", - "clear_all": "Effacer tout", - "clear_all_filters": "Effacer tous les filtres", - "clear_filters": "Réinitialiser les filtres", - "complete": "Complet", - "countries": "pays", - "country_completed": "Pays achevé", - "filter_by": "Filtrer", - "filter_by_region": "Filtre par région", - "hide_map": "Carte de cachette", - "hide_map_labels": "Masquer les étiquettes de la carte", - "interactive_map": "Carte interactive", - "no_countries_found_desc": "Essayez d'ajuster vos termes de recherche ou vos filtres pour trouver les pays que vous recherchez.", - "no_country_data_available": "Aucune donnée de pays disponible", - "no_country_data_available_desc": "Veuillez vérifier la documentation pour la mise à jour des données régionales.", - "no_regions_found": "Aucune région trouvée", - "of": "de", - "partial": "Partiel", - "progress": "Progrès", - "progress_and_stats": "Progrès", - "region_completed": "Région terminée", - "remaining": "Restant", - "show_map": "Afficher la carte", - "show_map_labels": "Afficher les étiquettes de carte", - "total_cities": "Total des villes", - "total_countries": "Total des pays", - "total_regions": "Régions totales", - "getting_location_details": "Obtenir les détails de l'emplacement", - "cities_available": "Villes disponibles", - "destination_revealed": "Destination révélée !", - "dive_deeper": "Plonger plus profondément", - "exploration_progress": "Progrès de l'exploration", - "explore_country": "Explorer le pays", - "globe_spin_error_desc": "Erreur pour récupérer les données de spin globe", - "hide_globe_spin": "Masquer la rotation du globe", - "in": "dans", - "loading_globe_spin": "Chargement du globe Spin", - "no_globe_spin_data": "Pas de données de spin globe", - "show_globe_spin": "Montrer le spin au globe", - "spin_again": "Remonter", - "spinning_globe": "Globe de rotation", - "try_again": "Essayer à nouveau", - "your_random_adventure_awaits": "Votre aventure aléatoire vous attend !", - "about_country": "À propos du pays", - "about_region": "À propos de la région", - "show_less": "Afficher moins", - "show_more": "Afficher plus", - "all_locations_visited": "Tous les lieux visités !" - }, - "settings": { - "account_settings": "Paramètres du compte utilisateur", - "confirm_new_password": "Confirmer le nouveau mot de passe", - "email_change": "Changer l'adresse de courriel", - "new_email": "Nouvelle adresse de courriel", - "new_password": "Nouveau mot de passe", - "no_email_set": "Aucune adresse de courriel définie", - "password_change": "Changer le mot de passe", - "settings_page": "Page de paramétrage", - "update": "Mise à jour", - "update_error": "Erreur lors de la mise à jour des paramètres", - "update_success": "Paramètres mis à jour avec succès !", - "change_password": "Changer le mot de passe", - "possible_reset": "Si l'adresse de courriel que vous avez fournie est associée à un compte, vous recevrez un courriel avec des instructions pour réinitialiser votre mot de passe !", - "reset_password": "Réinitialiser le mot de passe", - "about_this_background": "À propos de cette photo", - "join_discord": "Rejoignez le Discord", - "join_discord_desc": "pour partager vos propres photos. Postez-les dans le #travel-share channel.", - "photo_by": "Photo par", - "current_password": "Mot de passe actuel", - "password_change_lopout_warning": "Vous serez déconnecté après avoir modifié votre mot de passe.", - "authenticator_code": "Code d'authentification", - "copy": "Copier", - "disable_mfa": "Désactiver l'authentification multi-facteurs", - "email_added": "Adresse de courriel ajoutée avec succès !", - "email_removed": "Adresse de courriel supprimée avec succès !", - "email_removed_error": "Erreur lors de la suppression de l'adresse de courriel", - "email_set_primary": "Adresse de courriel principale définie avec succès !", - "email_set_primary_error": "Erreur lors de la définition de l'adresse de courriel principale", - "email_verified": "Adresse de courriel vérifiée avec succès !", - "email_verified_erorr_desc": "Votre adresse de courriel n'a pas pu être vérifiée. Veuillez réessayer.", - "email_verified_error": "Erreur lors de la vérification de l'adresse de courriel", - "email_verified_success": "Votre adresse de courriel a été vérifiée. Vous pouvez maintenant vous connecter.", - "enable_mfa": "Activer l'authentification multi-facteurs", - "generic_error": "Une erreur s'est produite lors du traitement de votre demande.", - "make_primary": "Définir comme adresse de courriel principale", - "mfa_disabled": "Authentification multi-facteurs désactivée avec succès !", - "mfa_enabled": "Authentification multi-facteurs activée avec succès !", - "mfa_not_enabled": "L'authentification multi-facteurs n'est pas activée", - "mfa_page_title": "Authentification multi-facteurs", - "no_emai_set": "Aucune adresse de courriel définie", - "not_verified": "Non vérifiée", - "primary": "Principale", - "recovery_codes": "Codes de récupération", - "recovery_codes_desc": "Ce sont vos codes de récupération. Gardez-les en sécurité. Ils ne pourront plus vous être affichés.", - "reset_session_error": "Veuillez vous déconnecter, puis vous reconnecter pour actualiser votre session et réessayer.", - "verified": "Vérifiée", - "verify": "Vérifier", - "verify_email_error": "Erreur lors de la vérification de l'adresse de courriel. Réessayez dans quelques minutes.", - "verify_email_success": "Vérification par courriel envoyée avec succès !", - "documentation_link": "Lien vers la documentation", - "launch_account_connections": "Lancer les connexions au compte", - "launch_administration_panel": "Lancer le panneau d'administration", - "no_verified_email_warning": "Vous devez disposer d'une adresse de courriel vérifiée pour activer l'authentification multi-facteurs.", - "social_auth_desc": "Activez ou désactivez les fournisseurs d'authentification sociale et OIDC pour votre compte. Ces connexions vous permettent de vous connecter avec des fournisseurs d'identité auto-hébergés comme Authentik ou des fournisseurs tiers comme GitHub.", - "social_auth_desc_2": "Ces paramètres sont gérés sur le serveur AdventureLog et doivent être activés manuellement par l'administrateur.", - "add_email": "Ajouter une adresse de courriel", - "password_disable_warning": "Actuellement, l'authentification par mot de passe est désactivée. La connexion via un fournisseur social ou OIDC est requise.", - "password_disabled": "Authentification par mot de passe désactivée", - "password_disabled_error": "Erreur de désactivation de l'authentification par mot de passe. Assurez-vous qu'un fournisseur social ou OIDC est lié à votre compte.", - "password_enabled": "Authentification par mot de passe activée", - "password_enabled_error": "Erreur permettant l'authentification par mot de passe.", - "admin_panel_desc": "Accéder à l'interface d'administration complète", - "administration": "Administration", - "advanced_settings": "Paramètres avancés", - "app_version": "Version de l'application", - "connected": "Connecté", - "email_management_desc": "Gérez vos adresses e-mail et votre statut de vérification", - "emails": "E-mails", - "enabled": "Activé", - "enter_current_password": "Entrez le mot de passe actuel", - "enter_first_name": "Entrez votre prénom", - "enter_new_email": "Entrez la nouvelle adresse e-mail", - "enter_new_password": "Entrez un nouveau mot de passe", - "enter_username": "Entrez votre nom d'utilisateur", - "integrations": "Intégrations", - "integrations_desc": "Connectez les services externes pour améliorer votre expérience", - "license": "Licence", - "mfa_desc": "Ajoutez une couche supplémentaire de sécurité à votre compte", - "mfa_is_enabled": "MFA est activé", - "pass_change_desc": "Mettez à jour le mot de passe de votre compte pour une meilleure sécurité", - "password_auth": "Authentification du mot de passe", - "password_login_disabled": "Login de mot de passe désactivé", - "password_login_enabled": "Connexion du mot de passe activé", - "profile_info": "Informations sur le profil", - "profile_info_desc": "Mettez à jour vos coordonnées personnelles et votre photo de profil", - "public_profile_desc": "Rendre votre profil visible pour les autres utilisateurs", - "quick_actions": "Actions rapides", - "region_updates": "Mises à jour de la région", - "region_updates_desc": "Mettre à jour les régions et les villes visitées", - "regular_user": "Utilisateur régulier", - "security": "Sécurité", - "settings_menu": "Menu des paramètres", - "social_auth": "Authentification sociale", - "social_auth_desc_1": "Gérer les options de connexion sociales et les paramètres de mot de passe", - "social_auth_setup": "Configuration d'authentification sociale", - "staff_status": "Statut du personnel", - "staff_user": "Utilisateur du personnel", - "access_restricted": "Accès restreint", - "access_restricted_desc": "Les fonctionnalités administratives ne sont disponibles que pour les membres du personnel.", - "add_new_email": "Ajouter un nouvel e-mail", - "add_new_email_address": "Ajouter une nouvelle adresse e-mail", - "admin": "Administrer", - "administration_desc": "Outils et paramètres administratifs", - "advanced": "Avancé", - "advanced_settings_desc": "Outils avancés de configuration et de développement", - "all_rights_reserved": "Tous droits réservés.", - "debug_information": "Débogage des informations", - "disabled": "Désactivé", - "disconnected": "Déconnecté", - "email_management": "Gestion des e-mails", - "enter_last_name": "Entrez votre nom de famille", - "invalid_credentials": "Des références non valides", - "backup_restore": "Sauvegarde", - "backup_restore_desc": "Enregistrez vos données ou restaurez-les à partir d'un fichier de sauvegarde précédent.", - "complete_setup_to_enable": "Configuration complète pour activer le MFA", - "copy_all": "Copier tout", - "enter_6_digit_code": "Entrez le code à 6 chiffres", - "enter_code_from_app": "Entrez le code à partir de l'application", - "error_occurred": "Une erreur s'est produite", - "important": "Important", - "manual_entry": "Entrée manuelle", - "mfa_already_enabled": "MFA déjà activé", - "mfa_required": "MFA requis", - "scan_qr_code": "Scanner le code QR", - "scan_with_authenticator_app": "Scanner avec l'application Authenticatrice", - "secure_your_account": "Sécuriser votre compte", - "setup_required": "Configuration requise", - "verify_setup": "Vérifiez la configuration", - "whats_included": "Ce qui est inclus", - "backup_your_data": "Sauvegardez vos données", - "backup_your_data_desc": "Téléchargez une sauvegarde complète des données de votre compte, y compris les emplacements, les collections, les médias et les visites.", - "data_override_acknowledge": "Je reconnais que cela remplacera toutes mes données existantes", - "data_override_acknowledge_desc": "Cette action est irréversible et remplacera tous les emplacements, collections et visites dans votre compte.", - "data_override_warning": "Avertissement de remplacement des données", - "data_override_warning_desc": "La restauration des données remplacera complètement toutes les données existantes (incluses dans la sauvegarde) dans votre compte. Cette action ne peut pas être annulée.", - "integrations_settings": "Paramètres des intégrations", - "media": "Médias", - "media_storage": "Stockage de médias", - "media_storage_desc": "Utilisation de l'espace de stockage pour vos images et pièces jointes téléchargées.", - "media_total_used": "Total utilisé", - "media_profile_pics": "Photos de profil", - "media_files": "files", - "restore_data": "Restaurer les données", - "restore_data_desc": "Téléchargez un fichier de sauvegarde pour restaurer vos données.", - "select_backup_file": "Sélectionnez le fichier de sauvegarde", - "world_travel_visits": "Visites de voyage dans le monde", - "activities": "Activités", - "trails": "Sentiers", - "use_imperial": "Utilisez des unités impériales", - "use_imperial_desc": "Utilisez des unités impériales (pieds, pouces, livres) au lieu d'unités métriques", - "map_style_desc": "Utilisé comme fond de carte par défaut sur les cartes.", - "preferred_currency_desc": "Cette devise pré-remplit les champs monétaires lors de l'ajout de nouveaux éléments.", - "public_profile_sharing_warning": "Vous êtes actuellement présent(e) dans {count} collection(s). Rendre votre profil privé vous retirera de ces collections.", - "public_profile_invite_warning": "Vous avez {count} invitations à rejoindre une collection en attente. Rendre votre profil privé les annulera.", - "public_profile_private_confirm": "Rendre votre profil privé vous retirera des collections partagées {shared} et annulera les {invites} invitations en attente. Continuer ?", - "public_profile_left_collections": "Votre profil est désormais privé. Vous avez été retiré(e) de {count} collections partagées.", - "public_profile_revoked_invites": "Annulation de {count} invitations à une collection en attente." - }, - "checklist": { - "checklist_delete_error": "Erreur lors de la suppression de la liste de contrôle", - "checklist_deleted": "Liste de contrôle supprimée avec succès !", - "checklist_editor": "Éditeur de liste de contrôle", - "checklist_public": "Cette liste de contrôle est publique car elle fait partie d’une collection publique.", - "item": "Élément", - "item_already_exists": "L'élément existe déjà", - "item_cannot_be_empty": "L'élément ne peut pas être vide", - "items": "Éléments", - "new_item": "Nouvel élément", - "new_checklist": "Nouvelle liste de contrôle", - "add_new_item": "Ajouter un nouvel article", - "add_your_first_item": "Ajoutez votre premier article", - "checklist_viewer": "Visionner de la liste de contrôle", - "completed": "Complété", - "current_items": "Éléments actuels", - "editing_checklist": "Liste de contrôle d'édition", - "enter_checklist_title": "Entrez le titre de la liste de contrôle", - "no_items_yet": "Pas encore d'articles", - "update_checklist_details": "Liste de contrôle d'édition", - "viewing_checklist": "Affichage de la liste de contrôle", - "more_items": "Plus d'éléments" - }, - "collection": { - "collection_created": "Collection créée avec succès !", - "collection_edit_success": "Collection modifiée avec succès !", - "create": "Créer", - "error_creating_collection": "Erreur lors de la création de la collection", - "error_editing_collection": "Erreur lors de la modification de la collection", - "new_collection": "Nouvelle collection", - "public_collection": "Collection publique", - "manage_collections": "Gérer les collections", - "archived_appear_here": "Les collections archivées apparaîtront ici.", - "create_first": "Créez votre première collection pour organiser vos aventures et vos souvenirs.", - "make_sure_public": "Assurez-vous que votre profil est public afin que les autres puissent partager avec vous.", - "no_archived_collections": "Aucune collection archivée.", - "no_collections_yet": "Pas encore de collections", - "no_shared_collections": "Pas de collections partagées.", - "shared_collections": "Collections partagées", - "available": "Disponible", - "linked": "Lié", - "try_different_search": "Essayez une recherche ou un filtre différent.", - "changing_date_title": "La modification des dates affectera les éléments de l'itinéraire", - "changing_date_warning": "Tous les éléments de l'itinéraire en dehors de la nouvelle plage de dates seront supprimés de l'itinéraire et réintégrés dans les éléments non datés de la collection.", - "clear_cover": "Couverture transparente", - "collaborators": "Collaborateurs", - "cover": "Couverture", - "cover_image": "Image de couverture", - "cover_image_hint": "Choisissez une couverture parmi les images de cette collection.", - "create_new_collection": "Créer une nouvelle collection", - "enter_collection_name": "Entrez le nom de la collection", - "location_primary": "Couverture d'emplacement", - "no_images_available": "Aucune image disponible à partir des emplacements liés pour le moment.", - "public_collection_description": "Autoriser toute personne disposant du lien à consulter", - "set_cover": "Définir la couverture", - "update_collection_details": "Mettre à jour les détails de la collection" - }, - "notes": { - "content": "Contenu", - "editing_note": "Modification de la note", - "failed_to_save": "Échec de l'enregistrement de la note", - "note_delete_error": "Erreur lors de la suppression de la note", - "note_deleted": "Note supprimée avec succès !", - "note_editor": "Éditeur de notes", - "note_public": "Cette note est publique car elle fait partie d'une collection publique.", - "open": "Ouvrir", - "save": "Sauvegarder", - "invalid_url": "URL invalide", - "note_viewer": "Visionneuse de notes", - "create_new_note": "Créer une nouvelle note", - "enter_note_title": "Entrez le titre de la note", - "update_note_details": "Note de montage", - "viewing_note": "Note d'observation" - }, - "transportation": { - "edit": "Modifier", - "edit_transportation": "Modifier le déplacement", - "flight_number": "Numéro du vol", - "from_location": "Du lieu", - "modes": { - "bike": "Vélo", - "boat": "Bateau", - "bus": "Bus", - "car": "Voiture", - "other": "Autre", - "plane": "Avion", - "train": "Train", - "walking": "Marche" - }, - "new_transportation": "Nouveau déplacement", - "to_location": "Vers le lieu", - "type": "Type", - "transportation_delete_error": "Erreur lors de la suppression du déplacement", - "transportation_deleted": "Déplacement supprimé avec succès !", - "create_new_transportation": "Nouveau transport", - "enter_flight_number": "Entrez le numéro de vol", - "enter_from_location": "Entrer de l'emplacement", - "enter_link": "Entrez le lien", - "enter_to_location": "Entrez à l'emplacement", - "enter_transportation_name": "Entrez le nom du transport", - "select_type": "Sélectionner le type", - "update_transportation_details": "Mettre à jour les détails du transport", - "arrival_code": "Code d'arrivée", - "arrival_date": "Date d'arrivée", - "arrival_timezone": "Fuseau horaire d'arrivée", - "departure_code": "Code de départ", - "departure_date": "Date de départ", - "departure_timezone": "Fuseau horaire de départ" - }, - "search": { - "cities": "Villes", - "countries": "Pays", - "found": "trouvé", - "result": "Résultat", - "results": "Résultats", - "try_searching_desc": "Essayez de rechercher des lieux, collections, notes, moyen de transport, pays, régions, villes ou des utilisateurs.", - "quick_actions": "Actions rapides", - "recent_searches": "Recherches récentes", - "results_heading": "Résultats", - "places_heading": "Lieux", - "places_min_chars": "Saisissez au moins 3 caractères pour rechercher des lieux.", - "no_actions": "Aucune action rapide correspondante.", - "see_all_results": "Voir tous les résultats", - "open": "Ouvrir", - "open_palette": "Ouvrir la recherche", - "close": "Fermer", - "navigate": "Naviguer", - "switch_tab": "Changer d'onglet", - "visited": "Visité", - "empty_prompt_title": "Rechercher votre AdventureLog", - "tab_places": "Lieux", - "retry": "Réssayer", - "load_more": "Charger plus", - "tab_adventures": "Mon Log", - "empty_prompt_desc": "Trouvez des lieux, des collections, des notes, des moyens de transport, des destinations de voyage dans le monde entier et des utilisateurs, le tout depuis un seul endroit.", - "filters": { - "all": "Tous", - "adventures": "Lieux", - "collections": "Collections", - "notes": "Notes", - "transport": "Transport", - "world": "Monde", - "users": "Utilisateurs" - }, - "types": { - "location": "Lieux", - "collection": "Collection", - "lodging": "Hébergement", - "transportation": "Transport", - "note": "Note", - "checklist": "Liste de contrôle", - "activity": "Activité", - "country": "Pays", - "region": "Région", - "city": "Ville", - "user": "Utilisateur" - }, - "actions": { - "locations": "Aller aux emplacements", - "locations_desc": "Consultez et gérez vos emplacements", - "collections": "Aller aux collections", - "collections_desc": "Ouvrez vos collections de voyage", - "map": "Aller à la carte", - "map_desc": "Explorez les lieux sur la carte", - "worldtravel": "Allez à Voyages autour du monde", - "worldtravel_desc": "Suivre les pays, les régions et les villes", - "calendar": "Aller au calendrier", - "calendar_desc": "Consultez votre calendrier de voyage", - "users": "Accéder aux utilisateurs", - "users_desc": "Parcourir les profils public", - "settings": "Aller aux paramètres", - "settings_desc": "Gérer mon compte et mes préférences" - } - }, - "map": { - "clear_marker": "Effacer le marqueur", - "view_details": "Afficher les détails", - "adventure_stats": "Statistiques d'aventure", - "completion": "Achèvement", - "display_options": "Options d'affichage", - "map_controls": "Contrôles de cartes", - "marker_placed_on_map": "Marqueur placé sur la carte", - "regions": "Régions", - "add_location": "Ajouter un nouvel emplacement", - "add_location_at_marker": "Ajouter un nouvel emplacement chez Marker", - "location_map": "Carte de localisation", - "locations_shown": "Emplacements montrés", - "place_marker_desc_location": "Cliquez sur la carte pour placer un marqueur.", - "show_visited_cities": "Villes visites", - "search_locations": "Rechercher vos lieux...", - "search_mode_my": "Mon", - "search_mode_places": "Lieux", - "search_mode_nearby": "Proche", - "search_places_placeholder": "Cherchez un endroit à ajouter ...", - "nearby_search_hint": "Déplacez la carte, puis appuyez sur « Rechercher dans cette zone » en haut de la carte.", - "search_this_area": "Rechercher dans cette zone", - "back_to_controls": "Retour aux commandes de la carte", - "select_on_map": "Sélectionnez une épingle ou un résultat de recherche pour afficher les détails.", - "loading_details": "Chargement des détails ...", - "quick_add": "Ajout rapide d'un lieu", - "add_with_details": "Ajoutez avec plus de détails", - "add_as_lodging": "Ajouter comme hébergement", - "powered_by": "Donnée de {provider}", - "visit_website": "Site internet", - "open_now": "Ouvrir maintenant", - "closed_now": "Fermer", - "hours": "Heures", - "last_visit": "Dernière visite", - "visits": "Visites", - "media": "Médias", - "searching": "Recherche...", - "searching_nearby": "Trouver des lieux à proximité …", - "show_more": "Afficher plus", - "show_less": "Afficher moins", - "no_results": "Aucun lieu trouvé", - "search_error": "La recherche a échoué. Veuillez réessayer plus tard.", - "clear_search": "Effacer la recherche", - "recommendations_nearby": "Lieux à proximité", - "recommendation_category": "Catégorie", - "recommendation_radius": "Rayon de recherche", - "recommendations_count": "{count} lieux trouvés", - "recommendations_error": "Impossible de charger les recommandations", - "recommendations_empty": "Aucune recommandation dans cette zone. Essayez un rayon plus large.", - "preview_panel": "Détails", - "nearby_controls": "Explorez les environs", - "zoom_in": "Zoomer", - "zoom_out": "Dézoomer", - "locate_me": "Trouver ma position", - "enter_fullscreen": "Passer en plein écran", - "exit_fullscreen": "Quitter le plein écran", - "geolocation_unavailable": "La géolocalisation n'est pas disponible dans ce navigateur.", - "geolocation_denied": "L'accès à la localisation a été refusé.", - "track_layers": "Couches de suivi", - "show_activities": "Afficher les pistes d'activité", - "hide_activities": "Masquer les pistes d'activité", - "show_trails": "Afficher les traces de sentier", - "hide_trails": "Masquer les traces de sentier", - "fullscreen_failed": "Impossible de passer en plein écran.", - "map_style": "Style de carte", - "no_locations_to_explore": "Aucun endroit sur la carte à explorer.", - "random_location": "Emplacement aléatoire", - "stats": "Statistiques" - }, - "share": { - "no_users_shared": "Aucun utilisateur", - "share_desc": "Partager cette collection avec d'autres utilisateurs.", - "shared": "Partagé", - "shared_with": "Déjà partagé avec", - "unshared": "Partage désactivé pour", - "with": "avec", - "available": "Disponible", - "pending": "En attente", - "revoke_invite": "Revoke Inviter", - "send_invite": "Envoyer l'invitation", - "available_users": "Utilisateurs disponibles", - "invite_failed": "L'invitation a échoué", - "invite_revoked": "Inviter révoqué", - "invite_sent": "Inviter envoyé", - "revoke_failed": "Revoke a échoué", - "unshare_failed": "Sans partage a échoué" - }, - "profile": { - "member_since": "Membre depuis", - "visited_countries": "Pays visités", - "visited_regions": "Régions visitées", - "visited_cities": "Villes visitées", - "discovered": "découvert", - "explored": "exploré", - "no_shared_adventures": "Cet utilisateur n'a encore partagé aucune aventure publique.", - "no_shared_collections": "Cet utilisateur n'a pas encore partagé de collections publiques.", - "planned_trips": "Voyages prévus", - "travel_statistics": "Statistiques de voyage", - "your_journey_at_a_glance": "Votre voyage d'aventure en un coup d'œil", - "public_location_experiences": "Expériences de localisation publique" - }, - "categories": { - "category_name": "Nom de la catégorie", - "edit_category": "Modifier la catégorie", - "icon": "Icône", - "manage_categories": "Gérer les catégories", - "no_categories_found": "Aucune catégorie trouvée.", - "select_category": "Sélectionnez une catégorie", - "add_new_category": "Ajouter une nouvelle catégorie", - "name_required": "Le nom de catégorie est requis", - "location_update_after_refresh": "Les cartes de localisation seront mises à jour une fois que vous avez actualisé la page." - }, - "dashboard": { - "countries_visited": "Pays visités", - "no_recent_adventures": "Pas d'aventure récente ?", - "recent_adventures": "Aventures récentes", - "total_visited_regions": "Total des régions visitées", - "welcome_back": "Content de te revoir", - "total_visited_cities": "Total des villes visitées", - "document_some_adventures": "Commencez à documenter vos voyages et construisez votre carte d'aventure personnelle !", - "view_all": "Afficher tous", - "welcome_text_1": "Tu as été sur", - "welcome_text_2": "Aventures jusqu'à présent", - "welcome_text_3": "Continuez à explorer et à documenter vos voyages !", - "greeting_morning": "Bonjour", - "greeting_afternoon": "Bon après-midi", - "greeting_evening": "Bonne soirée", - "greeting_night": "Bonne nuit", - "hero_active_trip": "Vous êtes en route — {name} est en cours", - "hero_next_trip": "{days} jours jusqu'à {name}", - "hero_countries": "Vous avez exploré {count} pays — où irez-vous ensuite ?", - "hero_locations_logged": "{count} emplacements sur votre carte — continuez à enrichir l'histoire", - "hero_location_logged": "1 lieu sur votre carte — continuez à enrichir l'histoire", - "hero_start_journey": "Votre carnet de voyage est vide — il est temps de planifier votre première étape", - "hero_headline": "Votre centre d'aventure", - "hero_headline_new": "Prêt à explorer ?", - "hero_active_subtitle": "{count} lieux visités lors de ce voyage — continuez à partager vos souvenirs", - "hero_next_subtitle": "Départ dans {days} jours — il est temps de s'enthousiasmer", - "hero_badge_countries": "{count} pays explorés", - "hero_badge_default": "AdventureLog", - "hero_trip_empty_title": "Aucun voyage en vue", - "hero_trip_empty_desc": "Constituez une collection et commencez à planifier votre prochaine évasion", - "hero_view_trip": "Voyage ouvert", - "hero_trip_on_the_road": "Sur la route", - "hero_trip_coming_up": "À venir", - "page_title": "Tableau de bord", - "page_description": "Votre maison de voyage — statistiques, voyages à venir et aventures récentes en un coup d'œil.", - "whats_next": "Et ensuite", - "upcoming_trips": "Voyages à venir", - "active_trip": "Actuellement en voyage", - "activity_highlights": "Points forts de l'activité", - "personal_record": "Meilleure distance personnelle", - "quick_links": "Liens rapides", - "recently_updated": "Mise à jour récemment", - "stats_error": "Impossible de charger les données de votre tableau de bord. Veuillez actualiser la page.", - "no_upcoming_trips": "Aucun voyage n'est prévu pour le moment.", - "no_active_trip": "Aucun voyage n'est en cours pour le moment.", - "no_recent_locations": "Aucun lieu visité pour le moment.", - "locations_visited": "Lieux visités", - "next_trip_in": "Prochain voyage dans {days} jours", - "pending_invites": "Invitations en attente", - "my_collections": "Mes collections", - "view_profile": "Voir le profil" - }, - "immich": { - "api_key": "Clé d'API Immich", - "api_note": "Remarque : il doit s'agir de l'URL de base de l'API Immich, elle se termine donc généralement par /api, sauf si vous disposez d'une configuration personnalisée.", - "disable": "Désactiver", - "immich": "Immich", - "immich_disabled": "Intégration Immich désactivée avec succès !", - "immich_error": "Erreur lors de la mise à jour de l'intégration Immich", - "integration_fetch_error": "Erreur lors de la récupération des données de l'intégration Immich", - "load_more": "Charger plus", - "no_items_found": "Aucun article trouvé", - "server_url": "URL du serveur Immich", - "update_integration": "Intégration des mises à jour", - "localhost_note": "Remarque : localhost ne fonctionnera probablement pas à moins que vous n'ayez configuré les réseaux Docker en conséquence. Il est recommandé d'utiliser l'adresse IP du serveur ou le nom de domaine.", - "api_key_placeholder": "Entrez votre clé API Immich", - "enable_integration": "Activer l'intégration", - "immich_integration_desc": "Connectez votre serveur de gestion de photos Immich", - "need_help": "Besoin d'aide pour la configurer ? Découvrez le", - "connection_error": "Erreur de connexion à Immich Server", - "copy_locally": "Copier les images localement", - "copy_locally_desc": "Copiez des images sur le serveur pour un accès hors ligne. Utilise plus d'espace disque.", - "error_saving_image": "Erreur d'enregistrement d'image", - "integration_already_exists": "Une intégration Immich existe déjà. Vous ne pouvez avoir qu'une seule intégration à la fois.", - "integration_not_found": "L'intégration d'immich n'est pas trouvée. Veuillez créer une nouvelle intégration.", - "network_error": "Erreur réseau lors de la connexion au serveur Immich. Veuillez vérifier votre connexion et réessayer.", - "validation_error": "Une erreur s'est produite lors de la validation de l'intégration d'Immich. Veuillez vérifier l'URL et la clé API de votre serveur.", - "by_album": "Par album", - "by_date": "Par date", - "error_no_object_id": "Aucun ID d'objet n'a été fourni", - "fetch_error": "Erreur récupérant les données de l'intégration d'Immich", - "image_search_placeholder": "Recherche à l'aide d'Immich", - "loading": "Chargement", - "loading_albums": "Chargement des albums", - "no_images": "Aucune image trouvée", - "select_album": "Sélectionner l'album", - "try_different_date": "Essayez une date différente" - }, - "recomendations": { - "recommendations": "Recommandations", - "food": "Nourriture", - "tourism": "Tourisme", - "any": "N'importe lequel", - "average_rating": "Note moyenne", - "discover_places": "Découvrez des lieux", - "lodging": "Hôtels", - "map_view": "Vue cartographique", - "minimum_rating": "Note minimale", - "minimum_reviews": "Examens minimaux", - "no_results_yet": "Aucun résultat pour l'instant", - "open_now_only": "Ouvert maintenant seulement", - "search_around_location": "Rechercher autour de l'emplacement", - "search_by_address": "Recherche par adresse", - "search_radius_label": "Rayon de recherche :", - "searching": "Recherche...", - "select_location_or_query": "Sélectionnez un emplacement ou saisissez une requête de recherche pour découvrir des endroits incroyables à proximité !", - "total_results": "Résultats totaux", - "use_search_instead": "Utilisez plutôt la recherche", - "add_location": "Ajouter un emplacement", - "add_lodging": "Ajouter un hébergement", - "away": "loin", - "hours": "Heures", - "open": "Ouvrir", - "your_location": "Votre emplacement" - }, - "lodging": { - "apartment": "Appartement", - "bnb": "Bed and Breakfast", - "cabin": "Châlet", - "campground": "Camping", - "edit": "Modifier", - "edit_lodging": "Modifier l'hébergement", - "hostel": "Auberge", - "hotel": "Hôtel", - "house": "Maison", - "motel": "Motel", - "new_lodging": "Nouvel hébergement", - "other": "Autre", - "reservation_number": "Numéro de réservation", - "resort": "Complexe touristique", - "villa": "Villa", - "create_new_lodging": "Nouveau logement", - "enter_lodging_name": "Entrez le nom de l'hébergement", - "enter_reservation_number": "Entrez le numéro de réservation", - "update_lodging_details": "Mettre à jour les détails de l'hébergement", - "invalid_link": "Veuillez saisir une URL valide (par exemple https://example.com).", - "save_failed": "Échec de l'enregistrement de l'hébergement. Veuillez réessayer." - }, - "google_maps": { - "google_maps_integration_desc": "Connectez votre compte Google Maps pour obtenir des résultats de recherche et recommandations de recherche de haute qualité.", - "google_maps_integration_desc_no_staff": "Cette intégration doit d'abord être activée par l'administrateur sur ce serveur." - }, - "calendar": { - "all_day_event": "Événement toute la journée", - "calendar_overview": "Aperçu du calendrier", - "day": "Jour", - "events_scheduled": "événements prévus", - "filtered_results": "Résultats filtrés", - "month": "Mois", - "today": "Aujourd'hui", - "total_events": "Événements totaux", - "week": "Semaine", - "event timezone": "Fuseau horaire de l'événement", - "your timezone": "Votre fuseau horaire", - "page_title": "Calendrier", - "agenda": "Agenda", - "event": "Événement", - "sidebar_title": "Filtres et options", - "timezone_display": "Affichage de l'heure", - "event_types": "Afficher les types d'événements", - "type_visit": "Visites", - "type_transportation": "Transport", - "type_lodging": "Hébergement", - "type_collection": "Voyages", - "type_note": "Notes", - "type_checklist": "Listes de contrôle", - "search_placeholder": "Rechercher des événements, des lieux, des collections...", - "upcoming": "Prochain", - "this_month": "Ce mois-ci", - "upcoming_agenda": "Événements à venir", - "no_upcoming_events": "Aucun événement à venir ne correspond à vos filtres.", - "no_events_in_range": "Aucun événement dans cette plage.", - "in_collection": "Collection", - "view_collection": "Voir la collection", - "load_error": "Impossible de charger les événements du calendrier. Veuillez réessayer.", - "export_success": "Calendrier exporté avec succès.", - "export_error": "Impossible d'exporter le calendrier.", - "more_events": "+{count} plus" - }, - "locations": { - "location": "Emplacement", - "locations": "Lieux", - "my_locations": "Mes emplacements", - "best_happened_at": "Le mieux s'est passé à" - }, - "settings_download_backup": "Télécharger la sauvegarde", - "invites": { - "accept_failed": "Échec de l'acceptation de l'invitation", - "accepted": "Inviter accepté", - "by": "par", - "decline": "Déclin", - "decline_failed": "Échec de refuser l'invitation", - "declined": "Invite a refusé", - "invited_on": "Invité", - "no_invites": "Aucune invitation", - "no_invites_desc": "Assurez-vous que votre profil est public afin que les utilisateurs puissent vous inviter.", - "pending_invites": "Invitations en attente", - "title": "Invitations", - "accept": "Accepter" - }, - "strava": { - "authorization_error": "Redirection d'erreur vers l'URL de l'autorisation Strava", - "connect_account": "Connecter le compte", - "disconnect": "Débrancher", - "disconnect_error": "Erreur de déconnexion de Strava", - "disconnected": "Déconnecté avec succès de Strava", - "strava_integration_desc": "Connectez-vous à Strava pour importer facilement vos activités dans des emplacements et des visites", - "gpx_required": "Veuillez télécharger le fichier gpx pour terminer l'importation de Strava", - "not_enabled": "L'intégration de Strava n'est pas activée sur cette instance." - }, - "wanderer": { - "connected": "Connecté avec succès à Wanderer", - "connection_error": "Erreur de connexion à Wanderer", - "wanderer_integration_desc": "Connectez-vous à Wanderer avec une clé API pour importer facilement et afficher vos sentiers dans les lieux", - "updated": "Intégration de Wanderer mise à jour", - "disconnected": "Wanderer déconnecté", - "disconnect_error": "Erreur de déconnexion de Wanderer", - "server_url": "URL du serveur", - "api_key": "Clé d'API", - "api_key_placeholder": "wanderer_key_...", - "localhost_note": "Le serveur Wanderer doit être accessible depuis le serveur AdventureLog (localhost peut ne pas fonctionner dans Docker).", - "update_integration": "Mise à jour de l'intégration" - }, - "collections": { - "all_items": "Tous les éléments", - "currencies": "Devises", - "currency": "Devise", - "event_timezone": "Fuseau horaire de l'événement", - "event_timezone_desc": "Le fuseau horaire de l'événement utilise le fuseau horaire de l'emplacement ou de l'élément lorsqu'il est disponible. Mon fuseau horaire utilise", - "events": "événements", - "local_timezone": "Mon fuseau horaire", - "no_calendar_events": "Aucune visite n'est encore prévue pour cette collection.", - "no_priced_items": "Ajoutez les prix des lieux, de l'hébergement ou du transport pour afficher les totaux des voyages par devise.", - "not_found": "Collection introuvable", - "statistics": "Statistiques", - "times_shown_in": "Horaires affichés dans", - "trip_costs": "Frais de voyage" - }, - "common": { - "show_less": "Masquer les détails", - "show_more": "Afficher plus" - }, - "currencies": { - "AED": "Dirham des Émirats Arabes Unis", - "AUD": "Dollar australien", - "BRL": "Réal brésilien", - "CAD": "Dollar canadien", - "CHF": "Franc suisse", - "CNY": "Yuan chinois", - "DKK": "Couronne danoise", - "EUR": "Euro", - "GBP": "Livre sterling", - "HKD": "Dollar de Hong Kong", - "INR": "Roupie indienne", - "JPY": "Yens japonais", - "MXN": "Peso mexicain", - "NOK": "Couronne norvégienne", - "NZD": "Dollar néo-zélandais", - "SEK": "Couronne suédoise", - "SGD": "Dollar de Singapour", - "TRY": "Lire turque", - "USD": "Dollar américain", - "ZAR": "Rand sud-africain", - "no_matches": "Aucune correspondance", - "search": "Devise de recherche", - "select_currency": "Sélectionnez la devise" - }, - "itinerary": { - "add_description": "Ajouter une description", - "add_to_day": "Ajouter au jour", - "add_to_trip_context": "Ajouter un contexte de voyage", - "added_to_trip_context": "Ajouté au contexte du voyage", - "auto_generate": "Génération automatique", - "auto_generate_itinerary": "Itinéraire généré automatiquement", - "auto_generate_itinerary_desc": "Cette collection contient des articles datés mais pas encore d'itinéraire. Souhaitez-vous les organiser automatiquement par date ?", - "change_day": "Changement de jour", - "drag_to_reorder": "Faites glisser pour réorganiser", - "failed_to_add_to_trip_context": "Échec de l'ajout d'un élément au contexte du voyage", - "failed_to_move_to_trip_context": "Échec du passage au contexte du voyage", - "generating": "Générateur", - "item_already_in_trip_context": "Éléments déjà dans le contexte du voyage", - "item_not_found": "Article introuvable", - "item_remove_error": "Erreur lors de la suppression de l'élément de l'itinéraire", - "item_remove_success": "Article supprimé de l'itinéraire", - "move_to_trip_context": "Passer au contexte du voyage", - "moved_to_trip_context": "Déplacé vers le contexte du voyage", - "multi_day": "Plusieurs jours", - "no_itinerary_yet": "Pas encore d'itinéraire", - "no_plans_for_day": "Aucun projet pour cette journée", - "no_trip_context_items": "Aucun élément de contexte de voyage pour l'instant.", - "remove_from_itinerary": "Supprimer du jour", - "remove_from_trip_context": "Supprimer du contexte", - "start_planning": "Commencez à planifier votre voyage en ajoutant des éléments à des jours spécifiques.", - "staying_overnight": "Passer la nuit", - "trip_context": "Contexte du voyage", - "trip_context_info": "Les éléments de contexte du voyage s'appliquent à l'ensemble du voyage, par exemple les lieux qui constituent la destination elle-même, les notes générales ou les listes de colisage importantes pour l'ensemble du voyage.", - "unscheduled_items": "Éléments non planifiés", - "unscheduled_items_desc": "Ces éléments sont liés à ce voyage mais n'ont pas encore été ajoutés à un jour spécifique.", - "link_existing_item": "Lier un élément existant" - }, - "api_keys": { - "copied": "Copié !", - "copy": "Copier la clé", - "create": "Créer une clé", - "create_error": "Échec de la création de la clé API.", - "created": "Créé", - "description": "Créez des clés API personnelles pour un accès par programmation. Les clés ne sont affichées qu'une seule fois au moment de la création.", - "dismiss": "Rejeter", - "key_created": "Clé API créée avec succès.", - "key_name_placeholder": "Nom de la clé (par exemple, Home Assistant)", - "key_revoked": "Clé API révoquée.", - "last_used": "Dernière utilisation", - "never_used": "Jamais utilisé", - "new_key_title": "Enregistrez votre nouvelle clé API", - "new_key_warning": "Cette clé ne sera plus affichée. Copiez-le et conservez-le dans un endroit sûr.", - "no_keys": "Aucune clé API pour l'instant.", - "revoke": "Révoquer", - "revoke_error": "Échec de la révocation de la clé API.", - "title": "Clés API", - "copy_error": "Erreur lors de la copie de la clé.", - "usage_middle": "en-tête ou comme", - "usage_prefix": "Utilisez cette clé dans le", - "delete_confirm": "Êtes-vous sûr de vouloir supprimer cette clé API mobile ?" - }, - "social_share": { - "share_externally": "Partager en externe", - "modal_desc": "Créez une image partageable sur les réseaux sociaux.", - "aspect_square": "Carré", - "aspect_story": "Histoire", - "aspect_landscape": "Paysage", - "download_image": "Télécharger l'image", - "copy_image": "Copier l'image", - "copy_image_success": "Image copiée dans le presse-papiers", - "copy_image_failed": "Impossible de copier l'image", - "share_native": "Partager …", - "share_native_failed": "Le partage a échoué", - "preview_loading": "Génération de l'aperçu …", - "share_image_failed": "Impossible de générer l'image de partage", - "download_success": "Image partagée téléchargée", - "private_notice_collection": "Cette collection n'est pas publique. Le code QR et le lien ne fonctionnent que pour vous et les personnes avec lesquelles vous avez partagé la collection.", - "private_notice_location": "Ce lieu n'est pas public. Le code QR et le lien ne fonctionnent que pour vous et les utilisateurs autorisés." - } + "about": { + "about": "À propos", + "close": "Fermer", + "license": "Sous licence GPL-3.0.", + "message": "Fabriqué avec ❤️ aux États-Unis.", + "nominatim_1": "La recherche de localisation et le géocodage sont fournis par", + "other_attributions": "Des attributions supplémentaires peuvent être trouvées dans le fichier README.", + "generic_attributions": "Connectez-vous à AdventureLog pour afficher les attributions pour les intégrations et services activés.", + "attributions": "Attributions", + "developer": "Développeur", + "license_info": "Licence", + "sponsor": "Parrainer", + "thank_you": "Merci d'utiliser AdventureLog !", + "version": "Version", + "view_changelog": "Voir le journal des modifications", + "view_license": "Afficher la licence", + "copy_version": "Copier la version" + }, + "adventures": { + "add_to_collection": "Ajouter à la collection", + "delete": "Supprimer", + "open_details": "Ouvrir les détails", + "remove_from_collection": "Supprimer de la collection", + "archive": "Archiver", + "archived": "Archivée", + "archived_collection_message": "Collection archivée avec succès !", + "archived_collections": "Collections archivées", + "ascending": "Ascendant", + "cancel": "Annuler", + "category_filter": "Filtres de catégorie", + "clear": "Réinitialiser", + "collection": "Collection", + "create_new": "Créer une nouvelle aventure", + "date": "Date", + "dates": "Dates", + "delete_collection": "Supprimer la collection", + "delete_collection_success": "Collection supprimée avec succès !", + "descending": "Descendant", + "duration": "Durée", + "edit_collection": "Modifier la collection", + "filter": "Filtrer", + "homepage": "Page d'accueil", + "image_upload_error": "Erreur lors du téléchargement de l'image", + "image_upload_success": "Image téléchargée avec succès !", + "latitude": "Latitude", + "longitude": "Longitude", + "my_collections": "Mes collections", + "name": "Nom", + "no_image_url": "Aucune image trouvée à cette URL.", + "order_by": "Trier par", + "order_direction": "Direction du tri", + "planned": "Prévue", + "private": "Privée", + "public": "Publique", + "rating": "Notation", + "share": "Partager", + "sort": "Trier", + "sources": "Sources", + "start_before_end_error": "La date de début doit être antérieure à la date de fin", + "unarchive": "Désarchiver", + "unarchived_collection_message": "Collection désarchivée avec succès !", + "updated": "Mise à jour", + "visit": "Visite", + "visited": "Visitée", + "visits": "Visites", + "wiki_image_error": "Erreur lors de la récupération de l'image depuis Wikipédia", + "add": "Ajouter", + "basic_information": "Informations de base", + "category": "Catégorie", + "copy_link": "Copier le lien", + "date_constrain": "Limiter aux dates de la collection", + "description": "Description", + "end_date": "Date de fin", + "fetch_image": "Récupérer une image", + "generate_desc": "Générer une description", + "image_fetch_failed": "Échec de la récupération de l'image", + "link": "Lien", + "location": "Lieu", + "no_results": "Aucun résultat trouvé", + "remove": "Retirer", + "search_for_location": "Rechercher un lieu", + "search_results": "Résultats de la recherche", + "start_date": "Date de début", + "wikipedia": "Wikipédia", + "adventure_not_found": "Il n'y a aucune aventure à afficher. Ajoutez-en en utilisant le bouton '+' en bas à droite ou essayez de changer les filtres !", + "all": "Tous", + "error_updating_regions": "Erreur lors de la mise à jour des régions", + "mark_visited": "Marquer comme visité", + "not_visited": "Non visitée", + "regions_updated": "régions mises à jour", + "update_visited_regions": "Mettre à jour les régions visitées", + "add_new": "Ajouter un nouveau...", + "checklists": "Listes de contrôle", + "days": "jours", + "link_new": "Ajouter un lien vers...", + "links": "Liens", + "note": "Note", + "notes": "Notes", + "transportation": "Déplacement", + "transportations": "Déplacements", + "checklist": "Liste de contrôle", + "day": "Jour", + "add_a_tag": "Ajouter une balise", + "tags": "Balises", + "copied_to_clipboard": "Copié dans le presse-papier !", + "copy_failed": "Échec de la copie", + "download_calendar": "Télécharger le calendrier", + "md_instructions": "Écrivez ici au format Markdown...", + "preview": "Aperçu", + "checklist_delete_confirm": "Êtes-vous sûr de vouloir supprimer cette liste de contrôle ? Cette action ne peut pas être annulée.", + "date_information": "Dates", + "delete_checklist": "Supprimer la liste de contrôle", + "delete_note": "Supprimer la note", + "delete_transportation": "Supprimer le déplacement", + "end": "Arrivée", + "from": "Depuis", + "note_delete_confirm": "Êtes-vous sûr de vouloir supprimer cette note ? Cette action ne peut pas être annulée.", + "out_of_range": "Pas dans la plage de dates de l'itinéraire", + "start": "Départ", + "to": "Vers", + "transportation_delete_confirm": "Etes-vous sûr de vouloir supprimer ce transport ? Cette action ne peut pas être annulée.", + "cities_updated": "villes mises à jour", + "attachment": "Pièce jointe", + "attachment_delete_success": "Pièce jointe supprimée avec succès !", + "attachment_name": "Nom de la pièce jointe", + "attachment_update_error": "Erreur lors de la mise à jour de la pièce jointe", + "attachment_upload_error": "Erreur lors du téléchargement de la pièce jointe", + "attachment_upload_success": "Pièce jointe téléchargée avec succès !", + "attachments": "Pièces jointes", + "gpx_tip": "Téléchargez des fichiers GPX en pièces jointes pour les afficher sur la carte !", + "images": "Images", + "upload": "Télécharger", + "city": "Ville", + "delete_lodging": "Supprimer l'hébergement", + "lodging": "Hébergement", + "lodging_delete_confirm": "Êtes-vous sûr de vouloir supprimer cet hébergement ? Cette action ne peut pas être annulée.", + "price": "Prix", + "region": "Région", + "open_in_maps": "Ouvert dans les cartes", + "all_day": "Journée complète", + "collection_no_start_end_date": "L'ajout d'une date de début et de fin à la collection débloquera les fonctionnalités de planification de l'itinéraire dans la page de collection.", + "invalid_date_range": "Plage de dates non valide", + "timezone": "Fuseau horaire", + "no_visits": "Pas de visites", + "coordinates": "Coordonnées", + "copy_coordinates": "Copier les coordonnées", + "sun_times": "Éphémérides", + "sunrise": "Lever du soleil", + "sunset": "Coucher de soleil", + "timed": "Chronométré", + "distance": "Distance", + "itinerary": "Itinéraire", + "joined": "Joint", + "view_profile": "Afficher le profil", + "share_collection": "Partagez cette collection !", + "filters_and_sort": "Filtres", + "filters_and_stats": "Filtres", + "no_adventures_message": "Commencez à documenter vos aventures et à en planifier de nouvelles. Chaque voyage a une histoire qui mérite d'être racontée.", + "travel_progress": "Progrès du voyage", + "collections_linked": "Collections liées", + "create_collection_first": "Créez d'abord une collection pour organiser vos aventures et vos souvenirs.", + "delete_collection_warning": "Êtes-vous sûr de vouloir supprimer cette collection ? Cette action ne peut pas être annulée.", + "done": "Fait", + "name_location": "nom, emplacement", + "check_in": "Enregistrement", + "check_out": "Départ", + "collection_link_location_error": "Erreur liant l'emplacement à la collection", + "collection_link_location_success": "Emplacement lié à la collection avec succès !", + "collection_locations": "Inclure les emplacements de collecte", + "collection_remove_location_error": "Erreur de suppression de l'emplacement de la collection", + "collection_remove_location_success": "Emplacement retiré de la collection avec succès !", + "create_location": "Créer un emplacement", + "delete_location": "Supprimer l'emplacement", + "edit_location": "Modifier l'emplacement", + "location_delete_confirm": "Êtes-vous sûr de vouloir supprimer cet emplacement ? Cette action ne peut pas être annulée.", + "location_delete_success": "Emplacement supprimé avec succès !", + "location_not_found": "Emplacement introuvable", + "location_not_found_desc": "L'emplacement que vous recherchiez n'a pas pu être trouvé. Veuillez essayer un autre emplacement ou revenir plus tard.", + "new_location": "Nouvel emplacement", + "no_collections_to_add_location": "Aucune collection n'a été trouvée pour ajouter cet emplacement à.", + "public_location": "Lieu public", + "visit_calendar": "Calendrier des visites", + "no_locations_found": "Aucun emplacement trouvé", + "image_modal_navigate": "Utilisez des touches flèches ou cliquez pour naviguer", + "details": "Détails", + "leave": "Partir", + "leave_collection": "Collecte de congé", + "leave_collection_warning": "Êtes-vous sûr de vouloir quitter cette collection ? Tous les emplacements que vous avez ajoutés seront non liés et resteront dans votre compte.", + "left_collection_message": "Collection de gauche avec succès", + "loading_collections": "Chargement des collections ...", + "quick_start": "Démarrage rapide", + "achievements": "Réalisations", + "active_duration": "Durée active", + "activity_name": "Nom d'activité", + "activity_name_placeholder": "Run du matin", + "activity_name_required": "Le nom d'activité est requis", + "activity_options": "Options d'activité", + "activity_statistics": "Statistiques d'activité", + "activity_statistics_description": "Vos réalisations de fitness et d'activité", + "add_activity": "Ajouter une activité", + "add_first_trail": "Ajoutez votre premier sentier en utilisant le bouton ci-dessus", + "add_new_activity": "Ajouter une nouvelle activité", + "add_new_trail": "Ajouter un nouveau sentier", + "add_trail": "Ajouter une piste", + "add_visit": "Ajouter la visite", + "add_wanderer_trail": "Ajouter le sentier Wanderer", + "attachment_management": "Gestion de l'attachement", + "attachment_name_required": "Nom de la pièce jointe requis", + "attachment_remove_error": "Une erreur s'est produite lors du retrait de la pièce jointe", + "attachment_removed": "L'attachement supprimé avec succès", + "attachment_updated": "Pièce jointe mise à jour avec succès", + "average_cadence": "Cadence moyenne", + "average_speed": "Vitesse moyenne", + "avg_speed": "Vitesse moyenne", + "back": "Retour", + "cadence": "Cadence", + "calories": "Calories", + "click_map": "Cliquez sur la carte pour sélectionner un emplacement", + "click_on_map": "Cliquez sur la carte pour sélectionner un emplacement", + "complete_import": "Importation complète", + "complete_strava_import": "Importation complète de Strava", + "confirm_delete_activity": "Êtes-vous sûr de vouloir supprimer cette activité ?", + "connect_to_wanderer": "Connectez-vous à Wanderer", + "continue": "Continuer", + "create_new_location": "Créer un nouvel emplacement", + "create_trail": "Créer une piste", + "created": "Créé", + "current_attachments": "Fichiers joints", + "date_selection": "Sélection de date", + "download_gpx": "Télécharger GPX", + "edit_visit": "Modifier la visite", + "elapsed_time": "Temps écoulé", + "elevation": "Élévation", + "elevation_gain": "Gain d'altitude", + "elevation_high": "Élévation élevée", + "elevation_loss": "Perte", + "elevation_low": "Élévation basse", + "end_lat": "Latitude finale", + "end_lng": "Fin de longitude", + "export_gpx": "Exporter gpx", + "export_original": "Exporter original", + "external_link": "Lien externe", + "gain": "gagner", + "getting_location_details": "Obtenir les détails de l'emplacement", + "gpx_file": "Fichier gpx", + "gpx_file_downloaded": "Fichier GPX téléchargé. Veuillez le télécharger ci-dessous pour terminer l'importation.", + "gpx_file_required": "Fichier GPX requis", + "image_management": "Gestion de l'image", + "import_activity": "Activité d'importation", + "importing": "Importation", + "likes": "Goûts", + "loading_activities": "Activités de chargement", + "location_display_name": "Nom de l'écran d'emplacement", + "location_map": "Emplacement", + "location_selected": "Emplacement sélectionné", + "max_speed": "Vitesse maximale", + "moving_time": "Temps de déménagement", + "next_image": "Image suivante", + "no_attachments_uploaded_yet": "Aucune pièce jointe encore téléchargée", + "no_external_link": "Aucun lien externe disponible", + "no_file_selected": "Aucun fichier sélectionné", + "no_images_uploaded_yet": "Aucune images encore téléchargées", + "no_strava_activities": "Aucune activité Strava trouvée lors de cette visite", + "no_trails_added": "Aucun sentier ajouté encore", + "no_trails_available": "Pas de sentiers disponibles", + "no_trails_found_matching": "Aucun sentier trouvé correspondant", + "no_visits_description": "Créez votre première visite en sélectionnant les dates ci-dessus", + "notes_placeholder": "Ajouter des notes sur cette visite", + "or": "OU", + "pace": "Rythme", + "photos": "Photos", + "previous_image": "Image précédente", + "processing": "Traitement", + "public_location_description": "Rendre cet emplacement visible pour les autres utilisateurs", + "remove_visit": "Supprimer la visite", + "rest_time": "Temps de repos", + "saved_activities": "Activités sauvées", + "search_location": "Rechercher un emplacement", + "search_placeholder": "Entrez la ville, l'emplacement ou le point de repère ...", + "search_trails_placeholder": "Recherchez les sentiers par nom", + "searching": "Recherche", + "select_on_map": "Sélectionner sur la carte", + "select_wanderer_trail": "Sélectionnez un sentier dans votre compte Wanderer", + "sport_type": "Type de sport", + "start_lat": "Latitude de départ", + "start_lng": "Longitude de départ", + "strava_activities_during_visit": "Activités de Strava lors de la visite", + "strava_activity_ready": "Activité Strava prête", + "time": "Temps", + "total_recorded": "Total enregistré", + "trail": "Piste", + "trail_created_successfully": "Trail créé avec succès", + "trail_creation_failed": "Échec de la création de sentiers", + "trail_fetch_failed": "Échec de la récupération des sentiers Wanderer", + "trail_link_required": "Une liaison de sentier est requise", + "trail_name": "Nom du sentier", + "trail_removal_failed": "Échec de la suppression du sentier", + "trail_removed_successfully": "Le sentier a été supprimé avec succès", + "trail_update_failed": "Échec de la mise à jour du sentier", + "trail_updated_successfully": "Trail mis à jour avec succès", + "trails": "Sentiers", + "trails_found_for": "sentiers trouvés pour", + "trails_management": "Gestion des sentiers", + "trails_management_description": "Gérez les sentiers associés à cet emplacement. Les sentiers peuvent être liés à des services externes comme Alltrails ou Wanderer Trails.", + "update_location_details": "Mettre à jour les détails de l'emplacement", + "update_visit": "Mettre à jour la visite", + "upload_activity": "Télécharger l'activité", + "upload_attachment": "Télécharger la pièce jointe", + "upload_first_attachment": "Téléchargez votre première pièce jointe à l'aide des options ci-dessus", + "upload_first_image": "Téléchargez votre première image en utilisant l'une des options ci-dessus", + "upload_from_device": "Télécharger à partir de l'appareil", + "upload_from_url": "Télécharger de l'URL", + "upload_gpx_file": "Téléchargez le fichier gpx qui vient d'être téléchargé pour terminer l'importation de Strava", + "uploading": "Téléchargement", + "use_current_location": "Utiliser l'emplacement actuel", + "view_gpx": "Afficher GPX", + "view_on": "Voir", + "view_strava_activities": "Voir les activités de Strava", + "view_trail": "Voir le sentier", + "wanderer_integration_error": "L'intégration de Wanderer n'est pas activée ou a expiré. Connectez le dans Paramètres → Intégrations.", + "wikipedia_error": "Échec de génération de description à partir de Wikipedia", + "high": "Haut", + "low": "Faible", + "total": "Total", + "rest": "Repos", + "lodging_save_error": "Économie d'erreur", + "activities_text": "activités", + "activity_breakdown_by_category": "Déchange d'activité par catégorie", + "distance_covered": "Distance couverte", + "recorded_sessions": "Sessions enregistrées", + "total_activities": "Activités totales", + "total_climbed": "Total grimpé", + "total_distance": "Distance totale", + "dates_not_saved": "Visitez non encore ajouté", + "dates_not_saved_description": "Cliquez sur Ajouter une visite pour enregistrer", + "active_days": "Journées actives", + "add_here": "Ajouter ici", + "airport_code_examples": "JFK, LAX, LHR...", + "airport_search_mode": "Mode de recherche d'aéroport", + "all_locations_already_linked": "Tous les emplacements sont déjà liés à cette collection.", + "arrival_airport": "Aéroport d'arrivée", + "avg_rating": "Note moyenne", + "burned": "brûlé", + "categories": "Catégories", + "cities": "Villes", + "clear_search": "Effacer la recherche", + "click_map_add_marker": "Cliquez sur la carte pour déposer un marqueur, puis ajoutez-le ici.", + "completed": "Complété", + "content_media": "Contenu", + "countries": "Pays", + "country": "pays", + "departure_airport": "Aéroport de départ", + "distance_traveled": "Distance parcourue", + "end_location": "Lieu d'arrivée", + "enter_location_display_name": "Entrez le nom d'affichage de l'emplacement", + "files": "Fichiers", + "flight": "Vol", + "folder": "Dossier", + "folder_view": "Vue des dossiers - affichant toutes les données", + "footprints": "Empreintes", + "gained": "gagné", + "geographic_breakdown": "Répartition géographique", + "gpx_routes": "Itinéraires GPX", + "hide_filters": "Masquer les filtres", + "images_captured": "Images capturées", + "in": "dans", + "in_progress": "En cours", + "items": "articles", + "itinerary_link_modal": { + "add_here": "Ajouter ici", + "add_here_keep_date": "Ajouter (conserver la date)", + "add_to_itinerary": "Ajouter à l'itinéraire", + "already_added": "Déjà ajouté", + "already_added_on_this_day": "Déjà ajouté ce jour", + "already_added_on_this_day_desc": "Ces éléments sont déjà programmés pour cette journée.", + "already_added_other_days": "Déjà ajouté les autres jours", + "already_added_other_days_desc": "Ces éléments sont programmés à des dates différentes. Les ajouter ici mettra à jour leur date ou les ajoutera tels quels.", + "items_available": "{count} éléments disponibles pour lier", + "items_on_other_days": "Articles les autres jours", + "items_on_other_days_desc": "Ces éléments ont des dates différentes. Vous pouvez les ajouter et éventuellement mettre à jour leur date en conséquence.", + "items_on_this_day": "Articles de ce jour", + "no_unscheduled_items": "Aucun élément non planifié disponible", + "no_unscheduled_items_desc": "Tous les éléments ont été ajoutés à l'itinéraire ou il n'y a aucun élément à ajouter.", + "title": "Lier les éléments à {date}" + }, + "link_copied": "Lien copié", + "lists": "Listes", + "location_search_mode": "Mode de recherche d'emplacement", + "locations_visited": "Lieux visités", + "lodging_not_found": "Hébergement introuvable", + "lodging_types": "Types d'hébergement", + "more_details": "Plus de détails", + "my_locations": "Mes emplacements", + "nights": "Nuits", + "no_linkable_locations": "Aucun emplacement trouvé pouvant être lié à cette collection.", + "of_locations": "des emplacements", + "on_this_trip": "Lors de ce voyage", + "overnight": "Du jour au lendemain", + "physical_activities": "Activités physiques", + "pins": "épingles", + "places": "Lieux", + "regions": "Régions", + "reservation": "Réservation", + "reset_filters": "Réinitialiser", + "route": "Itinéraire", + "route_selected": "Itinéraire sélectionné", + "routes_and_activities": "Itinéraires", + "saving": "Enregistrement", + "search_end_location": "Emplacement final de la recherche", + "search_start_end_locations": "Rechercher des emplacements de début et de fin", + "search_start_location": "Lieu de départ de la recherche", + "segments": "Segments", + "select": "Sélectionner", + "show_filters": "Afficher les filtres", + "start_location": "Lieu de départ", + "status_filter": "Filtre d'état", + "stays": "Séjours", + "tasks_done": "Tâches effectuées", + "total_days": "Nombre total de jours", + "total_visits": "Visites totales", + "transport_activity_paths": "Transport et chemin d'activité", + "transportation_gpx_tip": "Téléchargez les fichiers GPX ici pour les afficher sur la carte", + "traveled": "voyagé", + "travelers": "Voyageurs", + "trip_timeline": "Chronologie du voyage", + "trip_window": "Durée de voyage", + "upcoming": "Prochain", + "view": "Voir", + "wiki_results_found": "Résultats Wikipédia", + "with_activities": "Avec des activités", + "written": "Écrit", + "export_failed": "Échec de l'exportation", + "export_success": "Collection exportée", + "export_zip": "Exporter le code postal", + "import_failed": "Échec de l'importation", + "import_success": "Succès de l'importation", + "import_from_file": "Importer à partir d'un fichier", + "duplicate": "Dupliquer", + "duplicate_location": "Dupliquer le lieu", + "location_duplicate_success": "Emplacement dupliqué avec succès !", + "location_duplicate_error": "Échec de la duplication du lieu.", + "location_actions": "Actions du lieu", + "collection_duplicate_success": "Collection dupliquée avec succès ! Redirection...", + "collection_duplicate_error": "Échec de la duplication de la collection.", + "hide_strava_activities": "Masquer les activités Strava", + "show_strava_activities": "Afficher les activités Strava", + "add_details": "Ajouter des détails", + "export_pdf": "Télécharger le PDF", + "export_pdf_success": "PDF de la collection téléchargé", + "export_pdf_failed": "Echec du téléchargement du PDF de la collection", + "google_photos_available": "{count} Google photo(s) disponible", + "google_photos_import_all": "Tout importer depuis Google", + "google_photos_import_selected": "Importer la sélection", + "google_photos_importing": "Importation depuis Google photos…", + "google_photos_import_success": "Importation Google photos réalisée avec succès", + "google_photos_import_error": "Impossible d'importer Google photos", + "google_photos_select_hint": "Sélectionnez les photos que vous souhaitez importer, ou importez-les toutes en même temps." + }, + "home": { + "desc_1": "Découvrez, planifiez et explorez en toute simplicité", + "desc_2": "AdventureLog est conçu pour simplifier votre voyage, en vous fournissant les outils et les ressources nécessaires pour planifier, préparer et naviguer dans votre prochaine aventure inoubliable.", + "feature_1": "Carnet de voyage", + "feature_1_desc": "Gardez une trace de vos aventures avec un carnet de voyage personnalisé et partagez vos expériences avec vos amis et votre famille.", + "feature_2": "Planification du voyage", + "feature_2_desc": "Créez facilement des itinéraires personnalisés et obtenez un aperçu quotidien de votre voyage.", + "feature_3": "Carte de voyage", + "feature_3_desc": "Visualisez vos voyages à travers le monde avec une carte interactive et explorez de nouvelles destinations.", + "go_to": "Aller au journal d'aventure", + "hero_1": "Découvrez les aventures les plus palpitantes du monde", + "hero_2": "Découvrez et planifiez votre prochaine aventure avec AdventureLog. Explorez des destinations à couper le souffle, créez des itinéraires personnalisés et restez connecté lors de vos déplacements.", + "key_features": "Principales fonctionnalités", + "explore_world": "Explorer le monde", + "latest_travel_experiences": "Vos dernières expériences de voyage", + "of_world": "du monde", + "start_your_journey": "Commencez votre voyage" + }, + "navbar": { + "about": "À propos de AdventureLog", + "collections": "Collections", + "documentation": "Documentation", + "greeting": "Salut", + "logout": "Déconnexion", + "map": "Carte", + "profile": "Profil", + "search": "Recherche", + "settings": "Paramètres", + "shared_with_me": "Partagé avec moi", + "theme_selection": "Sélection de thèmes", + "themes": { + "aestheticDark": "Esthétique sombre", + "aestheticLight": "Lumière esthétique", + "aqua": "Aqua", + "dark": "Sombre", + "dim": "Faible", + "forest": "Forêt", + "light": "Lumière", + "night": "Nuit", + "northernLights": "Lumières du Nord" + }, + "users": "Utilisateurs", + "language_selection": "Langue", + "support": "Soutien", + "calendar": "Calendrier", + "admin_panel": "Panneau d'administration", + "navigation": "Navigation", + "worldtravel": "Voyage dans le monde", + "mobile_login": "Connexion mobile" + }, + "auth": { + "confirm_password": "Confirmer le mot de passe", + "email": "E-mail", + "first_name": "Prénom", + "forgot_password": "Mot de passe oublié ?", + "last_name": "Nom de famille", + "login": "Se connecter", + "login_error": "Impossible de se connecter avec les identifiants fournis.", + "password": "Mot de passe", + "registration_disabled": "L'inscription est actuellement désactivée.", + "signup": "S'inscrire", + "username": "Nom d'utilisateur", + "profile_picture": "Photo de profil", + "public_profile": "Profil public", + "new_password": "Nouveau mot de passe", + "or_3rd_party": "Ou connectez-vous avec un service tiers", + "no_public_collections": "Aucune collection publique trouvée", + "user_collections": "Collections de l'utilisateur", + "no_public_locations": "Aucun emplacement public trouvé", + "user_locations": "Emplacements des utilisateurs", + "enter_password": "Entrez votre mot de passe", + "enter_username": "Entrez votre nom d'utilisateur", + "logging_in": "Se connecter", + "totp": "Code à deux facteurs", + "user_email_verification_required": "Vérification par e-mail requise. Veuillez vérifier votre courrier électronique pour un lien de vérification." + }, + "users": { + "no_users_found": "Aucun utilisateur trouvé avec un profil public.", + "no_users_desc": "Lorsque d'autres voyageurs rendront leur profil public, celui-ci apparaîtra ici.", + "no_match": "Aucun utilisateur ne correspond à votre recherche", + "no_match_desc": "Essayez un autre nom ou effacez vos filtres.", + "page_description": "Parcourez les profils publics d'AdventureLog et découvrez d'autres voyageurs.", + "search_placeholder": "Recherche par nom ou nom d'utilisateur...", + "public_profiles": "Profils publics", + "public_profile": "Profil public", + "browse_options": "Options de navigation", + "overview": "Aperçu", + "sort_name": "Nom", + "sort_recent": "Nouveautés" + }, + "worldtravel": { + "country_list": "Liste des pays", + "no_countries_found": "Aucun pays trouvé", + "cities": "villes", + "failed_to_mark_visit": "Échec de la notation de la visite à", + "failed_to_remove_visit": "Échec de la suppression de la visite à", + "marked_visited": "marqué comme visité", + "no_cities_found": "Aucune ville trouvée", + "region_failed_visited": "Échec du marquage de la région comme visitée", + "regions_in": "Régions dans", + "removed": "supprimé", + "view_cities": "Voir les villes", + "visit_remove_failed": "Échec de la suppression de la visite", + "visit_to": "Visite à", + "all_regions": "Toutes les régions", + "available_to_explore": "Disponible pour explorer", + "cities_in": "Villes", + "clear_all": "Effacer tout", + "clear_all_filters": "Effacer tous les filtres", + "clear_filters": "Réinitialiser les filtres", + "complete": "Complet", + "countries": "pays", + "country_completed": "Pays achevé", + "filter_by": "Filtrer", + "filter_by_region": "Filtre par région", + "hide_map": "Carte de cachette", + "hide_map_labels": "Masquer les étiquettes de la carte", + "interactive_map": "Carte interactive", + "no_countries_found_desc": "Essayez d'ajuster vos termes de recherche ou vos filtres pour trouver les pays que vous recherchez.", + "no_country_data_available": "Aucune donnée de pays disponible", + "no_country_data_available_desc": "Veuillez vérifier la documentation pour la mise à jour des données régionales.", + "no_regions_found": "Aucune région trouvée", + "of": "de", + "partial": "Partiel", + "progress": "Progrès", + "progress_and_stats": "Progrès", + "region_completed": "Région terminée", + "remaining": "Restant", + "show_map": "Afficher la carte", + "show_map_labels": "Afficher les étiquettes de carte", + "total_cities": "Total des villes", + "total_countries": "Total des pays", + "total_regions": "Régions totales", + "getting_location_details": "Obtenir les détails de l'emplacement", + "cities_available": "Villes disponibles", + "destination_revealed": "Destination révélée !", + "dive_deeper": "Plonger plus profondément", + "exploration_progress": "Progrès de l'exploration", + "explore_country": "Explorer le pays", + "globe_spin_error_desc": "Erreur pour récupérer les données de spin globe", + "hide_globe_spin": "Masquer la rotation du globe", + "in": "dans", + "loading_globe_spin": "Chargement du globe Spin", + "no_globe_spin_data": "Pas de données de spin globe", + "show_globe_spin": "Montrer le spin au globe", + "spin_again": "Remonter", + "spinning_globe": "Globe de rotation", + "try_again": "Essayer à nouveau", + "your_random_adventure_awaits": "Votre aventure aléatoire vous attend !", + "about_country": "À propos du pays", + "about_region": "À propos de la région", + "show_less": "Afficher moins", + "show_more": "Afficher plus", + "all_locations_visited": "Tous les lieux visités !" + }, + "settings": { + "account_settings": "Paramètres du compte utilisateur", + "confirm_new_password": "Confirmer le nouveau mot de passe", + "email_change": "Changer l'adresse de courriel", + "new_email": "Nouvelle adresse de courriel", + "new_password": "Nouveau mot de passe", + "no_email_set": "Aucune adresse de courriel définie", + "password_change": "Changer le mot de passe", + "settings_page": "Page de paramétrage", + "update": "Mise à jour", + "update_error": "Erreur lors de la mise à jour des paramètres", + "update_success": "Paramètres mis à jour avec succès !", + "change_password": "Changer le mot de passe", + "possible_reset": "Si l'adresse de courriel que vous avez fournie est associée à un compte, vous recevrez un courriel avec des instructions pour réinitialiser votre mot de passe !", + "reset_password": "Réinitialiser le mot de passe", + "about_this_background": "À propos de cette photo", + "join_discord": "Rejoignez le Discord", + "join_discord_desc": "pour partager vos propres photos. Postez-les dans le #travel-share channel.", + "photo_by": "Photo par", + "current_password": "Mot de passe actuel", + "password_change_lopout_warning": "Vous serez déconnecté après avoir modifié votre mot de passe.", + "authenticator_code": "Code d'authentification", + "copy": "Copier", + "disable_mfa": "Désactiver l'authentification multi-facteurs", + "email_added": "Adresse de courriel ajoutée avec succès !", + "email_removed": "Adresse de courriel supprimée avec succès !", + "email_removed_error": "Erreur lors de la suppression de l'adresse de courriel", + "email_set_primary": "Adresse de courriel principale définie avec succès !", + "email_set_primary_error": "Erreur lors de la définition de l'adresse de courriel principale", + "email_verified": "Adresse de courriel vérifiée avec succès !", + "email_verified_erorr_desc": "Votre adresse de courriel n'a pas pu être vérifiée. Veuillez réessayer.", + "email_verified_error": "Erreur lors de la vérification de l'adresse de courriel", + "email_verified_success": "Votre adresse de courriel a été vérifiée. Vous pouvez maintenant vous connecter.", + "enable_mfa": "Activer l'authentification multi-facteurs", + "generic_error": "Une erreur s'est produite lors du traitement de votre demande.", + "make_primary": "Définir comme adresse de courriel principale", + "mfa_disabled": "Authentification multi-facteurs désactivée avec succès !", + "mfa_enabled": "Authentification multi-facteurs activée avec succès !", + "mfa_not_enabled": "L'authentification multi-facteurs n'est pas activée", + "mfa_page_title": "Authentification multi-facteurs", + "no_emai_set": "Aucune adresse de courriel définie", + "not_verified": "Non vérifiée", + "primary": "Principale", + "recovery_codes": "Codes de récupération", + "recovery_codes_desc": "Ce sont vos codes de récupération. Gardez-les en sécurité. Ils ne pourront plus vous être affichés.", + "reset_session_error": "Veuillez vous déconnecter, puis vous reconnecter pour actualiser votre session et réessayer.", + "verified": "Vérifiée", + "verify": "Vérifier", + "verify_email_error": "Erreur lors de la vérification de l'adresse de courriel. Réessayez dans quelques minutes.", + "verify_email_success": "Vérification par courriel envoyée avec succès !", + "documentation_link": "Lien vers la documentation", + "launch_account_connections": "Lancer les connexions au compte", + "launch_administration_panel": "Lancer le panneau d'administration", + "no_verified_email_warning": "Vous devez disposer d'une adresse de courriel vérifiée pour activer l'authentification multi-facteurs.", + "social_auth_desc": "Activez ou désactivez les fournisseurs d'authentification sociale et OIDC pour votre compte. Ces connexions vous permettent de vous connecter avec des fournisseurs d'identité auto-hébergés comme Authentik ou des fournisseurs tiers comme GitHub.", + "social_auth_desc_2": "Ces paramètres sont gérés sur le serveur AdventureLog et doivent être activés manuellement par l'administrateur.", + "add_email": "Ajouter une adresse de courriel", + "password_disable_warning": "Actuellement, l'authentification par mot de passe est désactivée. La connexion via un fournisseur social ou OIDC est requise.", + "password_disabled": "Authentification par mot de passe désactivée", + "password_disabled_error": "Erreur de désactivation de l'authentification par mot de passe. Assurez-vous qu'un fournisseur social ou OIDC est lié à votre compte.", + "password_enabled": "Authentification par mot de passe activée", + "password_enabled_error": "Erreur permettant l'authentification par mot de passe.", + "admin_panel_desc": "Accéder à l'interface d'administration complète", + "administration": "Administration", + "advanced_settings": "Paramètres avancés", + "app_version": "Version de l'application", + "connected": "Connecté", + "email_management_desc": "Gérez vos adresses e-mail et votre statut de vérification", + "emails": "E-mails", + "enabled": "Activé", + "enter_current_password": "Entrez le mot de passe actuel", + "enter_first_name": "Entrez votre prénom", + "enter_new_email": "Entrez la nouvelle adresse e-mail", + "enter_new_password": "Entrez un nouveau mot de passe", + "enter_username": "Entrez votre nom d'utilisateur", + "integrations": "Intégrations", + "integrations_desc": "Connectez les services externes pour améliorer votre expérience", + "license": "Licence", + "mfa_desc": "Ajoutez une couche supplémentaire de sécurité à votre compte", + "mfa_is_enabled": "MFA est activé", + "pass_change_desc": "Mettez à jour le mot de passe de votre compte pour une meilleure sécurité", + "password_auth": "Authentification du mot de passe", + "password_login_disabled": "Login de mot de passe désactivé", + "password_login_enabled": "Connexion du mot de passe activé", + "profile_info": "Informations sur le profil", + "profile_info_desc": "Mettez à jour vos coordonnées personnelles et votre photo de profil", + "public_profile_desc": "Rendre votre profil visible pour les autres utilisateurs", + "quick_actions": "Actions rapides", + "region_updates": "Mises à jour de la région", + "region_updates_desc": "Mettre à jour les régions et les villes visitées", + "regular_user": "Utilisateur régulier", + "security": "Sécurité", + "settings_menu": "Menu des paramètres", + "social_auth": "Authentification sociale", + "social_auth_desc_1": "Gérer les options de connexion sociales et les paramètres de mot de passe", + "social_auth_setup": "Configuration d'authentification sociale", + "staff_status": "Statut du personnel", + "staff_user": "Utilisateur du personnel", + "access_restricted": "Accès restreint", + "access_restricted_desc": "Les fonctionnalités administratives ne sont disponibles que pour les membres du personnel.", + "add_new_email": "Ajouter un nouvel e-mail", + "add_new_email_address": "Ajouter une nouvelle adresse e-mail", + "admin": "Administrer", + "administration_desc": "Outils et paramètres administratifs", + "advanced": "Avancé", + "advanced_settings_desc": "Outils avancés de configuration et de développement", + "all_rights_reserved": "Tous droits réservés.", + "debug_information": "Débogage des informations", + "disabled": "Désactivé", + "disconnected": "Déconnecté", + "email_management": "Gestion des e-mails", + "enter_last_name": "Entrez votre nom de famille", + "invalid_credentials": "Des références non valides", + "backup_restore": "Sauvegarde", + "backup_restore_desc": "Enregistrez vos données ou restaurez-les à partir d'un fichier de sauvegarde précédent.", + "complete_setup_to_enable": "Configuration complète pour activer le MFA", + "copy_all": "Copier tout", + "enter_6_digit_code": "Entrez le code à 6 chiffres", + "enter_code_from_app": "Entrez le code à partir de l'application", + "error_occurred": "Une erreur s'est produite", + "important": "Important", + "manual_entry": "Entrée manuelle", + "mfa_already_enabled": "MFA déjà activé", + "mfa_required": "MFA requis", + "scan_qr_code": "Scanner le code QR", + "scan_with_authenticator_app": "Scanner avec l'application Authenticatrice", + "secure_your_account": "Sécuriser votre compte", + "setup_required": "Configuration requise", + "verify_setup": "Vérifiez la configuration", + "whats_included": "Ce qui est inclus", + "backup_your_data": "Sauvegardez vos données", + "backup_your_data_desc": "Téléchargez une sauvegarde complète des données de votre compte, y compris les emplacements, les collections, les médias et les visites.", + "data_override_acknowledge": "Je reconnais que cela remplacera toutes mes données existantes", + "data_override_acknowledge_desc": "Cette action est irréversible et remplacera tous les emplacements, collections et visites dans votre compte.", + "data_override_warning": "Avertissement de remplacement des données", + "data_override_warning_desc": "La restauration des données remplacera complètement toutes les données existantes (incluses dans la sauvegarde) dans votre compte. Cette action ne peut pas être annulée.", + "integrations_settings": "Paramètres des intégrations", + "media": "Médias", + "media_storage": "Stockage de médias", + "media_storage_desc": "Utilisation de l'espace de stockage pour vos images et pièces jointes téléchargées.", + "media_total_used": "Total utilisé", + "media_profile_pics": "Photos de profil", + "media_files": "files", + "restore_data": "Restaurer les données", + "restore_data_desc": "Téléchargez un fichier de sauvegarde pour restaurer vos données.", + "select_backup_file": "Sélectionnez le fichier de sauvegarde", + "world_travel_visits": "Visites de voyage dans le monde", + "activities": "Activités", + "trails": "Sentiers", + "use_imperial": "Utilisez des unités impériales", + "use_imperial_desc": "Utilisez des unités impériales (pieds, pouces, livres) au lieu d'unités métriques", + "map_style_desc": "Utilisé comme fond de carte par défaut sur les cartes.", + "preferred_currency_desc": "Cette devise pré-remplit les champs monétaires lors de l'ajout de nouveaux éléments.", + "public_profile_sharing_warning": "Vous êtes actuellement présent(e) dans {count} collection(s). Rendre votre profil privé vous retirera de ces collections.", + "public_profile_invite_warning": "Vous avez {count} invitations à rejoindre une collection en attente. Rendre votre profil privé les annulera.", + "public_profile_private_confirm": "Rendre votre profil privé vous retirera des collections partagées {shared} et annulera les {invites} invitations en attente. Continuer ?", + "public_profile_left_collections": "Votre profil est désormais privé. Vous avez été retiré(e) de {count} collections partagées.", + "public_profile_revoked_invites": "Annulation de {count} invitations à une collection en attente." + }, + "checklist": { + "checklist_delete_error": "Erreur lors de la suppression de la liste de contrôle", + "checklist_deleted": "Liste de contrôle supprimée avec succès !", + "checklist_editor": "Éditeur de liste de contrôle", + "checklist_public": "Cette liste de contrôle est publique car elle fait partie d’une collection publique.", + "item": "Élément", + "item_already_exists": "L'élément existe déjà", + "item_cannot_be_empty": "L'élément ne peut pas être vide", + "items": "Éléments", + "new_item": "Nouvel élément", + "new_checklist": "Nouvelle liste de contrôle", + "add_new_item": "Ajouter un nouvel article", + "add_your_first_item": "Ajoutez votre premier article", + "checklist_viewer": "Visionner de la liste de contrôle", + "completed": "Complété", + "current_items": "Éléments actuels", + "editing_checklist": "Liste de contrôle d'édition", + "enter_checklist_title": "Entrez le titre de la liste de contrôle", + "no_items_yet": "Pas encore d'articles", + "update_checklist_details": "Liste de contrôle d'édition", + "viewing_checklist": "Affichage de la liste de contrôle", + "more_items": "Plus d'éléments" + }, + "collection": { + "collection_created": "Collection créée avec succès !", + "collection_edit_success": "Collection modifiée avec succès !", + "create": "Créer", + "error_creating_collection": "Erreur lors de la création de la collection", + "error_editing_collection": "Erreur lors de la modification de la collection", + "new_collection": "Nouvelle collection", + "public_collection": "Collection publique", + "manage_collections": "Gérer les collections", + "archived_appear_here": "Les collections archivées apparaîtront ici.", + "create_first": "Créez votre première collection pour organiser vos aventures et vos souvenirs.", + "make_sure_public": "Assurez-vous que votre profil est public afin que les autres puissent partager avec vous.", + "no_archived_collections": "Aucune collection archivée.", + "no_collections_yet": "Pas encore de collections", + "no_shared_collections": "Pas de collections partagées.", + "shared_collections": "Collections partagées", + "available": "Disponible", + "linked": "Lié", + "try_different_search": "Essayez une recherche ou un filtre différent.", + "changing_date_title": "La modification des dates affectera les éléments de l'itinéraire", + "changing_date_warning": "Tous les éléments de l'itinéraire en dehors de la nouvelle plage de dates seront supprimés de l'itinéraire et réintégrés dans les éléments non datés de la collection.", + "clear_cover": "Couverture transparente", + "collaborators": "Collaborateurs", + "cover": "Couverture", + "cover_image": "Image de couverture", + "cover_image_hint": "Choisissez une couverture parmi les images de cette collection.", + "create_new_collection": "Créer une nouvelle collection", + "enter_collection_name": "Entrez le nom de la collection", + "location_primary": "Couverture d'emplacement", + "no_images_available": "Aucune image disponible à partir des emplacements liés pour le moment.", + "public_collection_description": "Autoriser toute personne disposant du lien à consulter", + "set_cover": "Définir la couverture", + "update_collection_details": "Mettre à jour les détails de la collection" + }, + "notes": { + "content": "Contenu", + "editing_note": "Modification de la note", + "failed_to_save": "Échec de l'enregistrement de la note", + "note_delete_error": "Erreur lors de la suppression de la note", + "note_deleted": "Note supprimée avec succès !", + "note_editor": "Éditeur de notes", + "note_public": "Cette note est publique car elle fait partie d'une collection publique.", + "open": "Ouvrir", + "save": "Sauvegarder", + "invalid_url": "URL invalide", + "note_viewer": "Visionneuse de notes", + "create_new_note": "Créer une nouvelle note", + "enter_note_title": "Entrez le titre de la note", + "update_note_details": "Note de montage", + "viewing_note": "Note d'observation" + }, + "transportation": { + "edit": "Modifier", + "edit_transportation": "Modifier le déplacement", + "flight_number": "Numéro du vol", + "from_location": "Du lieu", + "modes": { + "bike": "Vélo", + "boat": "Bateau", + "bus": "Bus", + "car": "Voiture", + "other": "Autre", + "plane": "Avion", + "train": "Train", + "walking": "Marche" + }, + "new_transportation": "Nouveau déplacement", + "to_location": "Vers le lieu", + "type": "Type", + "transportation_delete_error": "Erreur lors de la suppression du déplacement", + "transportation_deleted": "Déplacement supprimé avec succès !", + "create_new_transportation": "Nouveau transport", + "enter_flight_number": "Entrez le numéro de vol", + "enter_from_location": "Entrer de l'emplacement", + "enter_link": "Entrez le lien", + "enter_to_location": "Entrez à l'emplacement", + "enter_transportation_name": "Entrez le nom du transport", + "select_type": "Sélectionner le type", + "update_transportation_details": "Mettre à jour les détails du transport", + "arrival_code": "Code d'arrivée", + "arrival_date": "Date d'arrivée", + "arrival_timezone": "Fuseau horaire d'arrivée", + "departure_code": "Code de départ", + "departure_date": "Date de départ", + "departure_timezone": "Fuseau horaire de départ" + }, + "search": { + "cities": "Villes", + "countries": "Pays", + "found": "trouvé", + "result": "Résultat", + "results": "Résultats", + "try_searching_desc": "Essayez de rechercher des lieux, collections, notes, moyen de transport, pays, régions, villes ou des utilisateurs.", + "quick_actions": "Actions rapides", + "recent_searches": "Recherches récentes", + "results_heading": "Résultats", + "places_heading": "Lieux", + "places_min_chars": "Saisissez au moins 3 caractères pour rechercher des lieux.", + "no_actions": "Aucune action rapide correspondante.", + "see_all_results": "Voir tous les résultats", + "open": "Ouvrir", + "open_palette": "Ouvrir la recherche", + "close": "Fermer", + "navigate": "Naviguer", + "switch_tab": "Changer d'onglet", + "visited": "Visité", + "empty_prompt_title": "Rechercher votre AdventureLog", + "tab_places": "Lieux", + "retry": "Réssayer", + "load_more": "Charger plus", + "tab_adventures": "Mon Log", + "empty_prompt_desc": "Trouvez des lieux, des collections, des notes, des moyens de transport, des destinations de voyage dans le monde entier et des utilisateurs, le tout depuis un seul endroit.", + "filters": { + "all": "Tous", + "adventures": "Lieux", + "collections": "Collections", + "notes": "Notes", + "transport": "Transport", + "world": "Monde", + "users": "Utilisateurs" + }, + "types": { + "location": "Lieux", + "collection": "Collection", + "lodging": "Hébergement", + "transportation": "Transport", + "note": "Note", + "checklist": "Liste de contrôle", + "activity": "Activité", + "country": "Pays", + "region": "Région", + "city": "Ville", + "user": "Utilisateur" + }, + "actions": { + "locations": "Aller aux emplacements", + "locations_desc": "Consultez et gérez vos emplacements", + "collections": "Aller aux collections", + "collections_desc": "Ouvrez vos collections de voyage", + "map": "Aller à la carte", + "map_desc": "Explorez les lieux sur la carte", + "worldtravel": "Allez à Voyages autour du monde", + "worldtravel_desc": "Suivre les pays, les régions et les villes", + "calendar": "Aller au calendrier", + "calendar_desc": "Consultez votre calendrier de voyage", + "users": "Accéder aux utilisateurs", + "users_desc": "Parcourir les profils public", + "settings": "Aller aux paramètres", + "settings_desc": "Gérer mon compte et mes préférences" + } + }, + "map": { + "clear_marker": "Effacer le marqueur", + "view_details": "Afficher les détails", + "adventure_stats": "Statistiques d'aventure", + "completion": "Achèvement", + "display_options": "Options d'affichage", + "map_controls": "Contrôles de cartes", + "marker_placed_on_map": "Marqueur placé sur la carte", + "regions": "Régions", + "add_location": "Ajouter un nouvel emplacement", + "add_location_at_marker": "Ajouter un nouvel emplacement chez Marker", + "location_map": "Carte de localisation", + "locations_shown": "Emplacements montrés", + "place_marker_desc_location": "Cliquez sur la carte pour placer un marqueur.", + "show_visited_cities": "Villes visites", + "search_locations": "Rechercher vos lieux...", + "search_mode_my": "Mon", + "search_mode_places": "Lieux", + "search_mode_nearby": "Proche", + "search_places_placeholder": "Cherchez un endroit à ajouter ...", + "nearby_search_hint": "Déplacez la carte, puis appuyez sur « Rechercher dans cette zone » en haut de la carte.", + "search_this_area": "Rechercher dans cette zone", + "back_to_controls": "Retour aux commandes de la carte", + "select_on_map": "Sélectionnez une épingle ou un résultat de recherche pour afficher les détails.", + "loading_details": "Chargement des détails ...", + "quick_add": "Ajout rapide d'un lieu", + "add_with_details": "Ajoutez avec plus de détails", + "add_as_lodging": "Ajouter comme hébergement", + "powered_by": "Donnée de {provider}", + "visit_website": "Site internet", + "open_now": "Ouvrir maintenant", + "closed_now": "Fermer", + "hours": "Heures", + "last_visit": "Dernière visite", + "visits": "Visites", + "media": "Médias", + "searching": "Recherche...", + "searching_nearby": "Trouver des lieux à proximité …", + "show_more": "Afficher plus", + "show_less": "Afficher moins", + "no_results": "Aucun lieu trouvé", + "search_error": "La recherche a échoué. Veuillez réessayer plus tard.", + "clear_search": "Effacer la recherche", + "recommendations_nearby": "Lieux à proximité", + "recommendation_category": "Catégorie", + "recommendation_radius": "Rayon de recherche", + "recommendations_count": "{count} lieux trouvés", + "recommendations_error": "Impossible de charger les recommandations", + "recommendations_empty": "Aucune recommandation dans cette zone. Essayez un rayon plus large.", + "preview_panel": "Détails", + "nearby_controls": "Explorez les environs", + "zoom_in": "Zoomer", + "zoom_out": "Dézoomer", + "locate_me": "Trouver ma position", + "enter_fullscreen": "Passer en plein écran", + "exit_fullscreen": "Quitter le plein écran", + "geolocation_unavailable": "La géolocalisation n'est pas disponible dans ce navigateur.", + "geolocation_denied": "L'accès à la localisation a été refusé.", + "track_layers": "Couches de suivi", + "show_activities": "Afficher les pistes d'activité", + "hide_activities": "Masquer les pistes d'activité", + "show_trails": "Afficher les traces de sentier", + "hide_trails": "Masquer les traces de sentier", + "fullscreen_failed": "Impossible de passer en plein écran.", + "map_style": "Style de carte", + "no_locations_to_explore": "Aucun endroit sur la carte à explorer.", + "random_location": "Emplacement aléatoire", + "stats": "Statistiques" + }, + "share": { + "no_users_shared": "Aucun utilisateur", + "share_desc": "Partager cette collection avec d'autres utilisateurs.", + "shared": "Partagé", + "shared_with": "Déjà partagé avec", + "unshared": "Partage désactivé pour", + "with": "avec", + "available": "Disponible", + "pending": "En attente", + "revoke_invite": "Revoke Inviter", + "send_invite": "Envoyer l'invitation", + "available_users": "Utilisateurs disponibles", + "invite_failed": "L'invitation a échoué", + "invite_revoked": "Inviter révoqué", + "invite_sent": "Inviter envoyé", + "revoke_failed": "Revoke a échoué", + "unshare_failed": "Sans partage a échoué" + }, + "profile": { + "member_since": "Membre depuis", + "visited_countries": "Pays visités", + "visited_regions": "Régions visitées", + "visited_cities": "Villes visitées", + "discovered": "découvert", + "explored": "exploré", + "no_shared_adventures": "Cet utilisateur n'a encore partagé aucune aventure publique.", + "no_shared_collections": "Cet utilisateur n'a pas encore partagé de collections publiques.", + "planned_trips": "Voyages prévus", + "travel_statistics": "Statistiques de voyage", + "your_journey_at_a_glance": "Votre voyage d'aventure en un coup d'œil", + "public_location_experiences": "Expériences de localisation publique" + }, + "categories": { + "category_name": "Nom de la catégorie", + "edit_category": "Modifier la catégorie", + "icon": "Icône", + "manage_categories": "Gérer les catégories", + "no_categories_found": "Aucune catégorie trouvée.", + "select_category": "Sélectionnez une catégorie", + "add_new_category": "Ajouter une nouvelle catégorie", + "name_required": "Le nom de catégorie est requis", + "location_update_after_refresh": "Les cartes de localisation seront mises à jour une fois que vous avez actualisé la page." + }, + "dashboard": { + "countries_visited": "Pays visités", + "no_recent_adventures": "Pas d'aventure récente ?", + "recent_adventures": "Aventures récentes", + "total_visited_regions": "Total des régions visitées", + "welcome_back": "Content de te revoir", + "total_visited_cities": "Total des villes visitées", + "document_some_adventures": "Commencez à documenter vos voyages et construisez votre carte d'aventure personnelle !", + "view_all": "Afficher tous", + "welcome_text_1": "Tu as été sur", + "welcome_text_2": "Aventures jusqu'à présent", + "welcome_text_3": "Continuez à explorer et à documenter vos voyages !", + "greeting_morning": "Bonjour", + "greeting_afternoon": "Bon après-midi", + "greeting_evening": "Bonne soirée", + "greeting_night": "Bonne nuit", + "hero_active_trip": "Vous êtes en route — {name} est en cours", + "hero_next_trip": "{days} jours jusqu'à {name}", + "hero_countries": "Vous avez exploré {count} pays — où irez-vous ensuite ?", + "hero_locations_logged": "{count} emplacements sur votre carte — continuez à enrichir l'histoire", + "hero_location_logged": "1 lieu sur votre carte — continuez à enrichir l'histoire", + "hero_start_journey": "Votre carnet de voyage est vide — il est temps de planifier votre première étape", + "hero_headline": "Votre centre d'aventure", + "hero_headline_new": "Prêt à explorer ?", + "hero_active_subtitle": "{count} lieux visités lors de ce voyage — continuez à partager vos souvenirs", + "hero_next_subtitle": "Départ dans {days} jours — il est temps de s'enthousiasmer", + "hero_badge_countries": "{count} pays explorés", + "hero_badge_default": "AdventureLog", + "hero_trip_empty_title": "Aucun voyage en vue", + "hero_trip_empty_desc": "Constituez une collection et commencez à planifier votre prochaine évasion", + "hero_view_trip": "Voyage ouvert", + "hero_trip_on_the_road": "Sur la route", + "hero_trip_coming_up": "À venir", + "page_title": "Tableau de bord", + "page_description": "Votre maison de voyage — statistiques, voyages à venir et aventures récentes en un coup d'œil.", + "whats_next": "Et ensuite", + "upcoming_trips": "Voyages à venir", + "active_trip": "Actuellement en voyage", + "activity_highlights": "Points forts de l'activité", + "personal_record": "Meilleure distance personnelle", + "quick_links": "Liens rapides", + "recently_updated": "Mise à jour récemment", + "stats_error": "Impossible de charger les données de votre tableau de bord. Veuillez actualiser la page.", + "no_upcoming_trips": "Aucun voyage n'est prévu pour le moment.", + "no_active_trip": "Aucun voyage n'est en cours pour le moment.", + "no_recent_locations": "Aucun lieu visité pour le moment.", + "locations_visited": "Lieux visités", + "next_trip_in": "Prochain voyage dans {days} jours", + "pending_invites": "Invitations en attente", + "my_collections": "Mes collections", + "view_profile": "Voir le profil" + }, + "immich": { + "api_key": "Clé d'API Immich", + "api_note": "Remarque : il doit s'agir de l'URL de base de l'API Immich, elle se termine donc généralement par /api, sauf si vous disposez d'une configuration personnalisée.", + "disable": "Désactiver", + "immich": "Immich", + "immich_disabled": "Intégration Immich désactivée avec succès !", + "immich_error": "Erreur lors de la mise à jour de l'intégration Immich", + "integration_fetch_error": "Erreur lors de la récupération des données de l'intégration Immich", + "load_more": "Charger plus", + "no_items_found": "Aucun article trouvé", + "server_url": "URL du serveur Immich", + "update_integration": "Intégration des mises à jour", + "localhost_note": "Remarque : localhost ne fonctionnera probablement pas à moins que vous n'ayez configuré les réseaux Docker en conséquence. Il est recommandé d'utiliser l'adresse IP du serveur ou le nom de domaine.", + "api_key_placeholder": "Entrez votre clé API Immich", + "enable_integration": "Activer l'intégration", + "immich_integration_desc": "Connectez votre serveur de gestion de photos Immich", + "need_help": "Besoin d'aide pour la configurer ? Découvrez le", + "connection_error": "Erreur de connexion à Immich Server", + "copy_locally": "Copier les images localement", + "copy_locally_desc": "Copiez des images sur le serveur pour un accès hors ligne. Utilise plus d'espace disque.", + "error_saving_image": "Erreur d'enregistrement d'image", + "integration_already_exists": "Une intégration Immich existe déjà. Vous ne pouvez avoir qu'une seule intégration à la fois.", + "integration_not_found": "L'intégration d'immich n'est pas trouvée. Veuillez créer une nouvelle intégration.", + "network_error": "Erreur réseau lors de la connexion au serveur Immich. Veuillez vérifier votre connexion et réessayer.", + "validation_error": "Une erreur s'est produite lors de la validation de l'intégration d'Immich. Veuillez vérifier l'URL et la clé API de votre serveur.", + "by_album": "Par album", + "by_date": "Par date", + "error_no_object_id": "Aucun ID d'objet n'a été fourni", + "fetch_error": "Erreur récupérant les données de l'intégration d'Immich", + "image_search_placeholder": "Recherche à l'aide d'Immich", + "loading": "Chargement", + "loading_albums": "Chargement des albums", + "no_images": "Aucune image trouvée", + "select_album": "Sélectionner l'album", + "try_different_date": "Essayez une date différente" + }, + "recomendations": { + "recommendations": "Recommandations", + "food": "Nourriture", + "tourism": "Tourisme", + "any": "N'importe lequel", + "average_rating": "Note moyenne", + "discover_places": "Découvrez des lieux", + "lodging": "Hôtels", + "map_view": "Vue cartographique", + "minimum_rating": "Note minimale", + "minimum_reviews": "Examens minimaux", + "no_results_yet": "Aucun résultat pour l'instant", + "open_now_only": "Ouvert maintenant seulement", + "search_around_location": "Rechercher autour de l'emplacement", + "search_by_address": "Recherche par adresse", + "search_radius_label": "Rayon de recherche :", + "searching": "Recherche...", + "select_location_or_query": "Sélectionnez un emplacement ou saisissez une requête de recherche pour découvrir des endroits incroyables à proximité !", + "total_results": "Résultats totaux", + "use_search_instead": "Utilisez plutôt la recherche", + "add_location": "Ajouter un emplacement", + "add_lodging": "Ajouter un hébergement", + "away": "loin", + "hours": "Heures", + "open": "Ouvrir", + "your_location": "Votre emplacement" + }, + "lodging": { + "apartment": "Appartement", + "bnb": "Bed and Breakfast", + "cabin": "Châlet", + "campground": "Camping", + "edit": "Modifier", + "edit_lodging": "Modifier l'hébergement", + "hostel": "Auberge", + "hotel": "Hôtel", + "house": "Maison", + "motel": "Motel", + "new_lodging": "Nouvel hébergement", + "other": "Autre", + "reservation_number": "Numéro de réservation", + "resort": "Complexe touristique", + "villa": "Villa", + "create_new_lodging": "Nouveau logement", + "enter_lodging_name": "Entrez le nom de l'hébergement", + "enter_reservation_number": "Entrez le numéro de réservation", + "update_lodging_details": "Mettre à jour les détails de l'hébergement", + "invalid_link": "Veuillez saisir une URL valide (par exemple https://example.com).", + "save_failed": "Échec de l'enregistrement de l'hébergement. Veuillez réessayer." + }, + "google_maps": { + "google_maps_integration_desc": "Connectez votre compte Google Maps pour obtenir des résultats de recherche et recommandations de recherche de haute qualité.", + "google_maps_integration_desc_no_staff": "Cette intégration doit d'abord être activée par l'administrateur sur ce serveur." + }, + "calendar": { + "all_day_event": "Événement toute la journée", + "calendar_overview": "Aperçu du calendrier", + "day": "Jour", + "events_scheduled": "événements prévus", + "filtered_results": "Résultats filtrés", + "month": "Mois", + "today": "Aujourd'hui", + "total_events": "Événements totaux", + "week": "Semaine", + "event timezone": "Fuseau horaire de l'événement", + "your timezone": "Votre fuseau horaire", + "page_title": "Calendrier", + "agenda": "Agenda", + "event": "Événement", + "sidebar_title": "Filtres et options", + "timezone_display": "Affichage de l'heure", + "event_types": "Afficher les types d'événements", + "type_visit": "Visites", + "type_transportation": "Transport", + "type_lodging": "Hébergement", + "type_collection": "Voyages", + "type_note": "Notes", + "type_checklist": "Listes de contrôle", + "search_placeholder": "Rechercher des événements, des lieux, des collections...", + "upcoming": "Prochain", + "this_month": "Ce mois-ci", + "upcoming_agenda": "Événements à venir", + "no_upcoming_events": "Aucun événement à venir ne correspond à vos filtres.", + "no_events_in_range": "Aucun événement dans cette plage.", + "in_collection": "Collection", + "view_collection": "Voir la collection", + "load_error": "Impossible de charger les événements du calendrier. Veuillez réessayer.", + "export_success": "Calendrier exporté avec succès.", + "export_error": "Impossible d'exporter le calendrier.", + "more_events": "+{count} plus" + }, + "locations": { + "location": "Emplacement", + "locations": "Lieux", + "my_locations": "Mes emplacements", + "best_happened_at": "Le mieux s'est passé à" + }, + "settings_download_backup": "Télécharger la sauvegarde", + "invites": { + "accept_failed": "Échec de l'acceptation de l'invitation", + "accepted": "Inviter accepté", + "by": "par", + "decline": "Déclin", + "decline_failed": "Échec de refuser l'invitation", + "declined": "Invite a refusé", + "invited_on": "Invité", + "no_invites": "Aucune invitation", + "no_invites_desc": "Assurez-vous que votre profil est public afin que les utilisateurs puissent vous inviter.", + "pending_invites": "Invitations en attente", + "title": "Invitations", + "accept": "Accepter" + }, + "strava": { + "authorization_error": "Redirection d'erreur vers l'URL de l'autorisation Strava", + "connect_account": "Connecter le compte", + "disconnect": "Débrancher", + "disconnect_error": "Erreur de déconnexion de Strava", + "disconnected": "Déconnecté avec succès de Strava", + "strava_integration_desc": "Connectez-vous à Strava pour importer facilement vos activités dans des emplacements et des visites", + "gpx_required": "Veuillez télécharger le fichier gpx pour terminer l'importation de Strava", + "not_enabled": "L'intégration de Strava n'est pas activée sur cette instance." + }, + "wanderer": { + "connected": "Connecté avec succès à Wanderer", + "connection_error": "Erreur de connexion à Wanderer", + "wanderer_integration_desc": "Connectez-vous à Wanderer avec une clé API pour importer facilement et afficher vos sentiers dans les lieux", + "updated": "Intégration de Wanderer mise à jour", + "disconnected": "Wanderer déconnecté", + "disconnect_error": "Erreur de déconnexion de Wanderer", + "server_url": "URL du serveur", + "api_key": "Clé d'API", + "api_key_placeholder": "wanderer_key_...", + "localhost_note": "Le serveur Wanderer doit être accessible depuis le serveur AdventureLog (localhost peut ne pas fonctionner dans Docker).", + "update_integration": "Mise à jour de l'intégration" + }, + "collections": { + "all_items": "Tous les éléments", + "currencies": "Devises", + "currency": "Devise", + "event_timezone": "Fuseau horaire de l'événement", + "event_timezone_desc": "Le fuseau horaire de l'événement utilise le fuseau horaire de l'emplacement ou de l'élément lorsqu'il est disponible. Mon fuseau horaire utilise", + "events": "événements", + "local_timezone": "Mon fuseau horaire", + "no_calendar_events": "Aucune visite n'est encore prévue pour cette collection.", + "no_priced_items": "Ajoutez les prix des lieux, de l'hébergement ou du transport pour afficher les totaux des voyages par devise.", + "not_found": "Collection introuvable", + "statistics": "Statistiques", + "times_shown_in": "Horaires affichés dans", + "trip_costs": "Frais de voyage" + }, + "common": { + "show_less": "Masquer les détails", + "show_more": "Afficher plus" + }, + "currencies": { + "AED": "Dirham des Émirats Arabes Unis", + "AUD": "Dollar australien", + "BRL": "Réal brésilien", + "CAD": "Dollar canadien", + "CHF": "Franc suisse", + "CNY": "Yuan chinois", + "DKK": "Couronne danoise", + "EUR": "Euro", + "GBP": "Livre sterling", + "HKD": "Dollar de Hong Kong", + "INR": "Roupie indienne", + "JPY": "Yens japonais", + "MXN": "Peso mexicain", + "NOK": "Couronne norvégienne", + "NZD": "Dollar néo-zélandais", + "SEK": "Couronne suédoise", + "SGD": "Dollar de Singapour", + "TRY": "Lire turque", + "USD": "Dollar américain", + "ZAR": "Rand sud-africain", + "no_matches": "Aucune correspondance", + "search": "Devise de recherche", + "select_currency": "Sélectionnez la devise" + }, + "itinerary": { + "add_description": "Ajouter une description", + "add_to_day": "Ajouter au jour", + "add_to_trip_context": "Ajouter un contexte de voyage", + "added_to_trip_context": "Ajouté au contexte du voyage", + "auto_generate": "Génération automatique", + "auto_generate_itinerary": "Itinéraire généré automatiquement", + "auto_generate_itinerary_desc": "Cette collection contient des articles datés mais pas encore d'itinéraire. Souhaitez-vous les organiser automatiquement par date ?", + "change_day": "Changement de jour", + "drag_to_reorder": "Faites glisser pour réorganiser", + "failed_to_add_to_trip_context": "Échec de l'ajout d'un élément au contexte du voyage", + "failed_to_move_to_trip_context": "Échec du passage au contexte du voyage", + "generating": "Générateur", + "item_already_in_trip_context": "Éléments déjà dans le contexte du voyage", + "item_not_found": "Article introuvable", + "item_remove_error": "Erreur lors de la suppression de l'élément de l'itinéraire", + "item_remove_success": "Article supprimé de l'itinéraire", + "move_to_trip_context": "Passer au contexte du voyage", + "moved_to_trip_context": "Déplacé vers le contexte du voyage", + "multi_day": "Plusieurs jours", + "no_itinerary_yet": "Pas encore d'itinéraire", + "no_plans_for_day": "Aucun projet pour cette journée", + "no_trip_context_items": "Aucun élément de contexte de voyage pour l'instant.", + "remove_from_itinerary": "Supprimer du jour", + "remove_from_trip_context": "Supprimer du contexte", + "start_planning": "Commencez à planifier votre voyage en ajoutant des éléments à des jours spécifiques.", + "staying_overnight": "Passer la nuit", + "trip_context": "Contexte du voyage", + "trip_context_info": "Les éléments de contexte du voyage s'appliquent à l'ensemble du voyage, par exemple les lieux qui constituent la destination elle-même, les notes générales ou les listes de colisage importantes pour l'ensemble du voyage.", + "unscheduled_items": "Éléments non planifiés", + "unscheduled_items_desc": "Ces éléments sont liés à ce voyage mais n'ont pas encore été ajoutés à un jour spécifique.", + "link_existing_item": "Lier un élément existant" + }, + "api_keys": { + "copied": "Copié !", + "copy": "Copier la clé", + "create": "Créer une clé", + "create_error": "Échec de la création de la clé API.", + "created": "Créé", + "description": "Créez des clés API personnelles pour un accès par programmation. Les clés ne sont affichées qu'une seule fois au moment de la création.", + "dismiss": "Rejeter", + "key_created": "Clé API créée avec succès.", + "key_name_placeholder": "Nom de la clé (par exemple, Home Assistant)", + "key_revoked": "Clé API révoquée.", + "last_used": "Dernière utilisation", + "never_used": "Jamais utilisé", + "new_key_title": "Enregistrez votre nouvelle clé API", + "new_key_warning": "Cette clé ne sera plus affichée. Copiez-le et conservez-le dans un endroit sûr.", + "no_keys": "Aucune clé API pour l'instant.", + "revoke": "Révoquer", + "revoke_error": "Échec de la révocation de la clé API.", + "title": "Clés API", + "copy_error": "Erreur lors de la copie de la clé.", + "usage_middle": "en-tête ou comme", + "usage_prefix": "Utilisez cette clé dans le", + "delete_confirm": "Êtes-vous sûr de vouloir supprimer cette clé API mobile ?" + }, + "social_share": { + "share_externally": "Partager en externe", + "modal_desc": "Créez une image partageable sur les réseaux sociaux.", + "aspect_square": "Carré", + "aspect_story": "Histoire", + "aspect_landscape": "Paysage", + "download_image": "Télécharger l'image", + "copy_image": "Copier l'image", + "copy_image_success": "Image copiée dans le presse-papiers", + "copy_image_failed": "Impossible de copier l'image", + "share_native": "Partager …", + "share_native_failed": "Le partage a échoué", + "preview_loading": "Génération de l'aperçu …", + "share_image_failed": "Impossible de générer l'image de partage", + "download_success": "Image partagée téléchargée", + "private_notice_collection": "Cette collection n'est pas publique. Le code QR et le lien ne fonctionnent que pour vous et les personnes avec lesquelles vous avez partagé la collection.", + "private_notice_location": "Ce lieu n'est pas public. Le code QR et le lien ne fonctionnent que pour vous et les utilisateurs autorisés." + } } diff --git a/frontend/src/routes/locations/[id]/+page.server.ts b/frontend/src/routes/locations/[id]/+page.server.ts index ee2c3dcf..8c25c553 100644 --- a/frontend/src/routes/locations/[id]/+page.server.ts +++ b/frontend/src/routes/locations/[id]/+page.server.ts @@ -1,12 +1,12 @@ import type { PageServerLoad } from './$types'; const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL']; -import type { AdditionalLocation, Location, Collection } from '$lib/types'; +import type { Location } from '$lib/types'; import { buildLocationShareMeta } from '$lib/shareMeta.server'; const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000'; export const load = (async (event) => { const id = event.params as { id: string }; - let request = await fetch(`${endpoint}/api/locations/${id.id}/additional-info/`, { + let request = await fetch(`${endpoint}/api/locations/${id.id}/`, { headers: { Cookie: `sessionid=${event.cookies.get('sessionid')}` }, @@ -21,7 +21,7 @@ export const load = (async (event) => { shareMeta: null }; } else { - let adventure = (await request.json()) as AdditionalLocation; + let adventure = (await request.json()) as Location; return { props: { diff --git a/frontend/src/routes/locations/[id]/+page.svelte b/frontend/src/routes/locations/[id]/+page.svelte index f35f5e61..682754e0 100644 --- a/frontend/src/routes/locations/[id]/+page.svelte +++ b/frontend/src/routes/locations/[id]/+page.svelte @@ -1,6 +1,6 @@