Add "use my current location" button to Journey entry editor

- One-shot getCurrentPositionOnce() helper in useGeolocation with typed
  error codes (unsupported, insecure context, permission denied, timeout)
- Button inside the entry editor Location field fills coordinates from the
  device and reverse-geocodes the place name via /api/maps/reverse,
  falling back to formatted coordinates when no name is available
- New journey.editor.* i18n keys in all 23 locales
- Unit tests for the helper and component tests for the button flow
This commit is contained in:
Renzo Beux
2026-07-28 12:32:11 -03:00
committed by Maurice
parent 8a59c139c0
commit 3ec4b33dbb
27 changed files with 377 additions and 5 deletions
+5
View File
@@ -20,6 +20,11 @@ const journey: TranslationStrings = {
'journey.editor.addAnother': 'إضافة آخر',
'journey.editor.makeFirst': 'جعله الأول',
'journey.editor.searching': 'جارٍ البحث...',
'journey.editor.useCurrentLocation': 'استخدام موقعي الحالي',
'journey.editor.locationPermissionDenied': 'تم رفض الوصول إلى الموقع. اسمح به في إعدادات المتصفح وحاول مرة أخرى.',
'journey.editor.locationTimeout': 'انتهت مهلة تحديد موقعك. حاول مرة أخرى.',
'journey.editor.locationUnavailable': 'تعذر تحديد موقعك.',
'journey.editor.locationInsecureContext': 'يتطلب تحديد الموقع اتصالاً آمناً (HTTPS).',
'journey.share.copy': 'نسخ',
'journey.share.copied': 'تم النسخ!',
'journey.invite.role': 'الدور',
+6
View File
@@ -125,6 +125,12 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '1º',
'journey.editor.makeFirst': 'Tornar 1º',
'journey.editor.searching': 'Pesquisando...',
'journey.editor.useCurrentLocation': 'Usar minha localização atual',
'journey.editor.locationPermissionDenied':
'O acesso à localização foi negado. Permita nas configurações do navegador e tente novamente.',
'journey.editor.locationTimeout': 'Tempo esgotado ao obter sua localização. Tente novamente.',
'journey.editor.locationUnavailable': 'Não foi possível determinar sua localização.',
'journey.editor.locationInsecureContext': 'A localização requer uma conexão segura (HTTPS).',
'journey.mood.amazing': 'Incrível',
'journey.mood.good': 'Bom',
'journey.mood.neutral': 'Neutro',
+6
View File
@@ -125,6 +125,12 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '1r',
'journey.editor.makeFirst': 'Fes 1r',
'journey.editor.searching': 'Cercant...',
'journey.editor.useCurrentLocation': 'Utilitza la meva ubicació actual',
'journey.editor.locationPermissionDenied':
"S'ha denegat l'accés a la ubicació. Permet-lo a la configuració del navegador i torna-ho a provar.",
'journey.editor.locationTimeout': "S'ha esgotat el temps per obtenir la ubicació. Torna-ho a provar.",
'journey.editor.locationUnavailable': "No s'ha pogut determinar la teva ubicació.",
'journey.editor.locationInsecureContext': 'La ubicació requereix una connexió segura (HTTPS).',
'journey.mood.amazing': 'Increïble',
'journey.mood.good': 'Bé',
'journey.mood.neutral': 'Neutral',
+6
View File
@@ -125,6 +125,12 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '1.',
'journey.editor.makeFirst': 'Nastavit jako 1.',
'journey.editor.searching': 'Hledání...',
'journey.editor.useCurrentLocation': 'Použít moji aktuální polohu',
'journey.editor.locationPermissionDenied':
'Přístup k poloze byl odepřen. Povolte ho v nastavení prohlížeče a zkuste to znovu.',
'journey.editor.locationTimeout': 'Vypršel čas při zjišťování polohy. Zkuste to znovu.',
'journey.editor.locationUnavailable': 'Nepodařilo se zjistit vaši polohu.',
'journey.editor.locationInsecureContext': 'Poloha vyžaduje zabezpečené připojení (HTTPS).',
'journey.mood.amazing': 'Úžasný',
'journey.mood.good': 'Dobrý',
'journey.mood.neutral': 'Neutrální',
+6
View File
@@ -125,6 +125,12 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '1.',
'journey.editor.makeFirst': 'Als 1. setzen',
'journey.editor.searching': 'Suche...',
'journey.editor.useCurrentLocation': 'Meinen aktuellen Standort verwenden',
'journey.editor.locationPermissionDenied':
'Standortzugriff wurde verweigert. Erlaube ihn in den Browser-Einstellungen und versuche es erneut.',
'journey.editor.locationTimeout': 'Zeitüberschreitung beim Ermitteln deines Standorts. Versuche es erneut.',
'journey.editor.locationUnavailable': 'Dein Standort konnte nicht ermittelt werden.',
'journey.editor.locationInsecureContext': 'Der Standortzugriff erfordert eine sichere Verbindung (HTTPS).',
'journey.mood.amazing': 'Großartig',
'journey.mood.good': 'Gut',
'journey.mood.neutral': 'Neutral',
+6
View File
@@ -133,6 +133,12 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '1st',
'journey.editor.makeFirst': 'Make 1st',
'journey.editor.searching': 'Searching...',
'journey.editor.useCurrentLocation': 'Use my current location',
'journey.editor.locationPermissionDenied':
'Location access was denied. Allow it in your browser settings and try again.',
'journey.editor.locationTimeout': 'Timed out getting your location. Try again.',
'journey.editor.locationUnavailable': 'Could not determine your location.',
'journey.editor.locationInsecureContext': 'Location requires a secure (HTTPS) connection.',
'journey.mood.amazing': 'Amazing',
'journey.mood.good': 'Good',
'journey.mood.neutral': 'Neutral',
+6
View File
@@ -125,6 +125,12 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '1º',
'journey.editor.makeFirst': 'Hacer 1º',
'journey.editor.searching': 'Buscando...',
'journey.editor.useCurrentLocation': 'Usar mi ubicación actual',
'journey.editor.locationPermissionDenied':
'Se denegó el acceso a la ubicación. Permítelo en la configuración de tu navegador e inténtalo de nuevo.',
'journey.editor.locationTimeout': 'Se agotó el tiempo para obtener tu ubicación. Inténtalo de nuevo.',
'journey.editor.locationUnavailable': 'No se pudo determinar tu ubicación.',
'journey.editor.locationInsecureContext': 'La ubicación requiere una conexión segura (HTTPS).',
'journey.mood.amazing': 'Increíble',
'journey.mood.good': 'Bien',
'journey.mood.neutral': 'Neutral',
+6
View File
@@ -126,6 +126,12 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '1er',
'journey.editor.makeFirst': 'Mettre en 1er',
'journey.editor.searching': 'Recherche...',
'journey.editor.useCurrentLocation': 'Utiliser ma position actuelle',
'journey.editor.locationPermissionDenied':
"L'accès à la position a été refusé. Autorisez-le dans les paramètres de votre navigateur et réessayez.",
'journey.editor.locationTimeout': 'Délai dépassé pour obtenir votre position. Réessayez.',
'journey.editor.locationUnavailable': 'Impossible de déterminer votre position.',
'journey.editor.locationInsecureContext': 'La géolocalisation nécessite une connexion sécurisée (HTTPS).',
'journey.mood.amazing': 'Incroyable',
'journey.mood.good': 'Bien',
'journey.mood.neutral': 'Neutre',
+6
View File
@@ -128,6 +128,12 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '1η',
'journey.editor.makeFirst': 'Ορισμός ως 1η',
'journey.editor.searching': 'Αναζήτηση...',
'journey.editor.useCurrentLocation': 'Χρήση της τρέχουσας τοποθεσίας μου',
'journey.editor.locationPermissionDenied':
'Η πρόσβαση στην τοποθεσία απορρίφθηκε. Επιτρέψτε την στις ρυθμίσεις του προγράμματος περιήγησης και δοκιμάστε ξανά.',
'journey.editor.locationTimeout': 'Λήξη χρονικού ορίου κατά τον εντοπισμό της τοποθεσίας σας. Δοκιμάστε ξανά.',
'journey.editor.locationUnavailable': 'Δεν ήταν δυνατός ο εντοπισμός της τοποθεσίας σας.',
'journey.editor.locationInsecureContext': 'Η τοποθεσία απαιτεί ασφαλή σύνδεση (HTTPS).',
'journey.mood.amazing': 'Καταπληκτική',
'journey.mood.good': 'Καλή',
'journey.mood.neutral': 'Ουδέτερη',
+6
View File
@@ -125,6 +125,12 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '1.',
'journey.editor.makeFirst': 'Legyen az 1.',
'journey.editor.searching': 'Keresés...',
'journey.editor.useCurrentLocation': 'Jelenlegi helyzetem használata',
'journey.editor.locationPermissionDenied':
'A helymeghatározás engedélye megtagadva. Engedélyezd a böngésző beállításaiban, majd próbáld újra.',
'journey.editor.locationTimeout': 'Időtúllépés a helyzeted lekérése közben. Próbáld újra.',
'journey.editor.locationUnavailable': 'Nem sikerült meghatározni a helyzeted.',
'journey.editor.locationInsecureContext': 'A helymeghatározáshoz biztonságos (HTTPS) kapcsolat szükséges.',
'journey.mood.amazing': 'Fantasztikus',
'journey.mood.good': 'Jó',
'journey.mood.neutral': 'Semleges',
+5
View File
@@ -125,6 +125,11 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '1.',
'journey.editor.makeFirst': 'Jadikan ke-1',
'journey.editor.searching': 'Mencari...',
'journey.editor.useCurrentLocation': 'Gunakan lokasi saya saat ini',
'journey.editor.locationPermissionDenied': 'Akses lokasi ditolak. Izinkan di pengaturan browser lalu coba lagi.',
'journey.editor.locationTimeout': 'Waktu habis saat mengambil lokasi. Coba lagi.',
'journey.editor.locationUnavailable': 'Tidak dapat menentukan lokasi Anda.',
'journey.editor.locationInsecureContext': 'Lokasi memerlukan koneksi aman (HTTPS).',
'journey.mood.amazing': 'Luar biasa',
'journey.mood.good': 'Baik',
'journey.mood.neutral': 'Biasa',
+6
View File
@@ -125,6 +125,12 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '1°',
'journey.editor.makeFirst': 'Metti 1°',
'journey.editor.searching': 'Ricerca...',
'journey.editor.useCurrentLocation': 'Usa la mia posizione attuale',
'journey.editor.locationPermissionDenied':
"L'accesso alla posizione è stato negato. Consentilo nelle impostazioni del browser e riprova.",
'journey.editor.locationTimeout': 'Tempo scaduto durante il rilevamento della posizione. Riprova.',
'journey.editor.locationUnavailable': 'Impossibile determinare la tua posizione.',
'journey.editor.locationInsecureContext': 'La posizione richiede una connessione sicura (HTTPS).',
'journey.mood.amazing': 'Fantastico',
'journey.mood.good': 'Buono',
'journey.mood.neutral': 'Neutro',
+6
View File
@@ -123,6 +123,12 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '1番目',
'journey.editor.makeFirst': '1番目にする',
'journey.editor.searching': '検索中…',
'journey.editor.useCurrentLocation': '現在地を使用',
'journey.editor.locationPermissionDenied':
'位置情報へのアクセスが拒否されました。ブラウザの設定で許可して再試行してください。',
'journey.editor.locationTimeout': '位置情報の取得がタイムアウトしました。もう一度お試しください。',
'journey.editor.locationUnavailable': '現在地を特定できませんでした。',
'journey.editor.locationInsecureContext': '位置情報には安全な接続(HTTPS)が必要です。',
'journey.mood.amazing': '最高',
'journey.mood.good': '良い',
'journey.mood.neutral': '普通',
+5
View File
@@ -123,6 +123,11 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '1번째',
'journey.editor.makeFirst': '1번째로 설정',
'journey.editor.searching': '검색 중...',
'journey.editor.useCurrentLocation': '현재 위치 사용',
'journey.editor.locationPermissionDenied': '위치 접근이 거부되었습니다. 브라우저 설정에서 허용한 후 다시 시도하세요.',
'journey.editor.locationTimeout': '위치를 가져오는 데 시간이 초과되었습니다. 다시 시도하세요.',
'journey.editor.locationUnavailable': '위치를 확인할 수 없습니다.',
'journey.editor.locationInsecureContext': '위치 기능에는 보안 연결(HTTPS)이 필요합니다.',
'journey.mood.amazing': '최고!',
'journey.mood.good': '좋음',
'journey.mood.neutral': '보통',
+6
View File
@@ -126,6 +126,12 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '1e',
'journey.editor.makeFirst': 'Maak 1e',
'journey.editor.searching': 'Zoeken...',
'journey.editor.useCurrentLocation': 'Mijn huidige locatie gebruiken',
'journey.editor.locationPermissionDenied':
'Locatietoegang is geweigerd. Sta het toe in je browserinstellingen en probeer het opnieuw.',
'journey.editor.locationTimeout': 'Time-out bij het ophalen van je locatie. Probeer het opnieuw.',
'journey.editor.locationUnavailable': 'Je locatie kon niet worden bepaald.',
'journey.editor.locationInsecureContext': 'Locatie vereist een beveiligde (HTTPS-)verbinding.',
'journey.mood.amazing': 'Fantastisch',
'journey.mood.good': 'Goed',
'journey.mood.neutral': 'Neutraal',
+6
View File
@@ -125,6 +125,12 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '1.',
'journey.editor.makeFirst': 'Ustaw jako 1.',
'journey.editor.searching': 'Szukanie...',
'journey.editor.useCurrentLocation': 'Użyj mojej bieżącej lokalizacji',
'journey.editor.locationPermissionDenied':
'Dostęp do lokalizacji został odrzucony. Zezwól na niego w ustawieniach przeglądarki i spróbuj ponownie.',
'journey.editor.locationTimeout': 'Przekroczono limit czasu pobierania lokalizacji. Spróbuj ponownie.',
'journey.editor.locationUnavailable': 'Nie udało się ustalić Twojej lokalizacji.',
'journey.editor.locationInsecureContext': 'Lokalizacja wymaga bezpiecznego połączenia (HTTPS).',
'journey.mood.amazing': 'Niesamowity',
'journey.mood.good': 'Dobry',
'journey.mood.neutral': 'Neutralny',
+6
View File
@@ -125,6 +125,12 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '1-е',
'journey.editor.makeFirst': 'Сделать 1-м',
'journey.editor.searching': 'Поиск...',
'journey.editor.useCurrentLocation': 'Использовать моё текущее местоположение',
'journey.editor.locationPermissionDenied':
'Доступ к геолокации запрещён. Разрешите его в настройках браузера и попробуйте снова.',
'journey.editor.locationTimeout': 'Время ожидания определения местоположения истекло. Попробуйте снова.',
'journey.editor.locationUnavailable': 'Не удалось определить ваше местоположение.',
'journey.editor.locationInsecureContext': 'Для геолокации требуется защищённое соединение (HTTPS).',
'journey.mood.amazing': 'Потрясающе',
'journey.mood.good': 'Хорошо',
'journey.mood.neutral': 'Нейтрально',
+6
View File
@@ -126,6 +126,12 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '1:a',
'journey.editor.makeFirst': 'Gör 1:a',
'journey.editor.searching': 'Söker...',
'journey.editor.useCurrentLocation': 'Använd min nuvarande plats',
'journey.editor.locationPermissionDenied':
'Platsåtkomst nekades. Tillåt den i webbläsarens inställningar och försök igen.',
'journey.editor.locationTimeout': 'Tidsgränsen för att hämta din plats överskreds. Försök igen.',
'journey.editor.locationUnavailable': 'Din plats kunde inte fastställas.',
'journey.editor.locationInsecureContext': 'Plats kräver en säker anslutning (HTTPS).',
'journey.mood.amazing': 'Fantastiskt',
'journey.mood.good': 'Bra',
'journey.mood.neutral': 'Neutral',
+6
View File
@@ -123,6 +123,12 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '1.',
'journey.editor.makeFirst': '1. yap',
'journey.editor.searching': 'Aranıyor...',
'journey.editor.useCurrentLocation': 'Mevcut konumumu kullan',
'journey.editor.locationPermissionDenied':
'Konum erişimi reddedildi. Tarayıcı ayarlarından izin verip tekrar deneyin.',
'journey.editor.locationTimeout': 'Konum alınırken zaman aşımı oluştu. Tekrar deneyin.',
'journey.editor.locationUnavailable': 'Konumunuz belirlenemedi.',
'journey.editor.locationInsecureContext': 'Konum için güvenli (HTTPS) bir bağlantı gerekir.',
'journey.mood.amazing': 'Muhteşem',
'journey.mood.good': 'İyi',
'journey.mood.neutral': 'Nötr',
+6
View File
@@ -123,6 +123,12 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '1-е',
'journey.editor.makeFirst': 'Зробити першим',
'journey.editor.searching': 'Пошук...',
'journey.editor.useCurrentLocation': 'Використати моє поточне місцезнаходження',
'journey.editor.locationPermissionDenied':
'Доступ до геолокації заборонено. Дозвольте його в налаштуваннях браузера та спробуйте ще раз.',
'journey.editor.locationTimeout': 'Час очікування визначення місцезнаходження вичерпано. Спробуйте ще раз.',
'journey.editor.locationUnavailable': 'Не вдалося визначити ваше місцезнаходження.',
'journey.editor.locationInsecureContext': "Для геолокації потрібне захищене з'єднання (HTTPS).",
'journey.mood.amazing': 'Неймовірно',
'journey.mood.good': 'Добре',
'journey.mood.neutral': 'Нейтрально',
+6
View File
@@ -126,6 +126,12 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': 'thứ nhất',
'journey.editor.makeFirst': 'làm số 1',
'journey.editor.searching': 'Đang tìm kiếm...',
'journey.editor.useCurrentLocation': 'Dùng vị trí hiện tại của tôi',
'journey.editor.locationPermissionDenied':
'Quyền truy cập vị trí bị từ chối. Hãy cho phép trong cài đặt trình duyệt rồi thử lại.',
'journey.editor.locationTimeout': 'Hết thời gian lấy vị trí. Hãy thử lại.',
'journey.editor.locationUnavailable': 'Không thể xác định vị trí của bạn.',
'journey.editor.locationInsecureContext': 'Vị trí yêu cầu kết nối an toàn (HTTPS).',
'journey.mood.amazing': 'Tuyệt vời',
'journey.mood.good': 'Tốt',
'journey.mood.neutral': 'Trung lập',
+5
View File
@@ -125,6 +125,11 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '第1張',
'journey.editor.makeFirst': '設為第1張',
'journey.editor.searching': '搜尋中...',
'journey.editor.useCurrentLocation': '使用我的目前位置',
'journey.editor.locationPermissionDenied': '位置存取被拒絕。請在瀏覽器設定中允許後重試。',
'journey.editor.locationTimeout': '取得位置逾時,請重試。',
'journey.editor.locationUnavailable': '無法判斷您的位置。',
'journey.editor.locationInsecureContext': '定位需要安全連線(HTTPS)。',
'journey.mood.amazing': '太棒了',
'journey.mood.good': '不錯',
'journey.mood.neutral': '一般',
+5
View File
@@ -125,6 +125,11 @@ const journey: TranslationStrings = {
'journey.editor.photoFirst': '第1张',
'journey.editor.makeFirst': '设为第1张',
'journey.editor.searching': '搜索中...',
'journey.editor.useCurrentLocation': '使用我的当前位置',
'journey.editor.locationPermissionDenied': '位置访问被拒绝。请在浏览器设置中允许后重试。',
'journey.editor.locationTimeout': '获取位置超时,请重试。',
'journey.editor.locationUnavailable': '无法确定您的位置。',
'journey.editor.locationInsecureContext': '定位需要安全连接(HTTPS)。',
'journey.mood.amazing': '太棒了',
'journey.mood.good': '不错',
'journey.mood.neutral': '一般',