Fix Wikipedia API with User-Agent (#822)

* refactor(serializers): remove unused gpxpy and geojson imports

* fix(generate_description): improve error handling and response validation for Wikipedia API calls

* Potential fix for code scanning alert no. 42: Information exposure through an exception

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* fix(generate_description): improve error logging for Wikipedia API data fetch failures

* chore(deps): bump devalue (#823)

Bumps the npm_and_yarn group with 1 update in the /frontend directory: [devalue](https://github.com/sveltejs/devalue).


Updates `devalue` from 5.1.1 to 5.3.2
- [Release notes](https://github.com/sveltejs/devalue/releases)
- [Changelog](https://github.com/sveltejs/devalue/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/devalue/compare/v5.1.1...v5.3.2)

---
updated-dependencies:
- dependency-name: devalue
  dependency-version: 5.3.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com>

* Refactor help documentation link in settings page

- Updated the condition to display the help documentation link based on the `wandererEnabled` flag.
- Removed the conditional rendering for staff users and Strava integration status.
- Changed the documentation link to point to the Immich integration documentation.

* fix(locations): update include_collections parameter handling for default behavior

* Update backend/server/adventures/views/generate_description_view.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Sean Morley
2025-09-01 10:06:44 -04:00
committed by GitHub
parent a3f0eda63f
commit cb431f7d26
7 changed files with 1175 additions and 1069 deletions

View File

@@ -7,8 +7,6 @@ from worldtravel.serializers import CountrySerializer, RegionSerializer, CitySer
from geopy.distance import geodesic from geopy.distance import geodesic
from integrations.models import ImmichIntegration from integrations.models import ImmichIntegration
from adventures.utils.geojson import gpx_to_geojson from adventures.utils.geojson import gpx_to_geojson
import gpxpy
import geojson
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -3,42 +3,137 @@ from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
import requests import requests
from django.conf import settings
import urllib.parse
import logging
logger = logging.getLogger(__name__)
class GenerateDescription(viewsets.ViewSet): class GenerateDescription(viewsets.ViewSet):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
@action(detail=False, methods=['get'],) # User-Agent header required by Wikipedia API
HEADERS = {
'User-Agent': f'AdventureLog/{getattr(settings, "ADVENTURELOG_RELEASE_VERSION", "unknown")}'
}
@action(detail=False, methods=['get'])
def desc(self, request): def desc(self, request):
name = self.request.query_params.get('name', '') name = self.request.query_params.get('name', '')
# un url encode the name if not name:
name = name.replace('%20', ' ') return Response({"error": "Name parameter is required"}, status=400)
name = self.get_search_term(name)
url = 'https://en.wikipedia.org/w/api.php?origin=*&action=query&prop=extracts&exintro&explaintext&format=json&titles=%s' % name # Properly URL decode the name
response = requests.get(url) name = urllib.parse.unquote(name)
search_term = self.get_search_term(name)
if not search_term:
return Response({"error": "No matching Wikipedia article found"}, status=404)
# Properly URL encode the search term for the API
encoded_term = urllib.parse.quote(search_term)
url = f'https://en.wikipedia.org/w/api.php?origin=*&action=query&prop=extracts&exintro&explaintext&format=json&titles={encoded_term}'
try:
response = requests.get(url, headers=self.HEADERS, timeout=10)
response.raise_for_status()
data = response.json() data = response.json()
data = response.json()
page_id = next(iter(data["query"]["pages"])) pages = data.get("query", {}).get("pages", {})
extract = data["query"]["pages"][page_id] if not pages:
if extract.get('extract') is None: return Response({"error": "No page data found"}, status=404)
return Response({"error": "No description found"}, status=400)
return Response(extract) page_id = next(iter(pages))
@action(detail=False, methods=['get'],) page_data = pages[page_id]
# Check if page exists (page_id of -1 means page doesn't exist)
if page_id == "-1":
return Response({"error": "Wikipedia page not found"}, status=404)
if not page_data.get('extract'):
return Response({"error": "No description found"}, status=404)
return Response(page_data)
except requests.exceptions.RequestException as e:
logger.exception("Failed to fetch data from Wikipedia")
return Response({"error": "Failed to fetch data from Wikipedia."}, status=500)
except ValueError as e: # JSON decode error
return Response({"error": "Invalid response from Wikipedia API"}, status=500)
@action(detail=False, methods=['get'])
def img(self, request): def img(self, request):
name = self.request.query_params.get('name', '') name = self.request.query_params.get('name', '')
# un url encode the name if not name:
name = name.replace('%20', ' ') return Response({"error": "Name parameter is required"}, status=400)
name = self.get_search_term(name)
url = 'https://en.wikipedia.org/w/api.php?origin=*&action=query&prop=pageimages&format=json&piprop=original&titles=%s' % name # Properly URL decode the name
response = requests.get(url) name = urllib.parse.unquote(name)
search_term = self.get_search_term(name)
if not search_term:
return Response({"error": "No matching Wikipedia article found"}, status=404)
# Properly URL encode the search term for the API
encoded_term = urllib.parse.quote(search_term)
url = f'https://en.wikipedia.org/w/api.php?origin=*&action=query&prop=pageimages&format=json&piprop=original&titles={encoded_term}'
try:
response = requests.get(url, headers=self.HEADERS, timeout=10)
response.raise_for_status()
data = response.json() data = response.json()
page_id = next(iter(data["query"]["pages"]))
extract = data["query"]["pages"][page_id] pages = data.get("query", {}).get("pages", {})
if extract.get('original') is None: if not pages:
return Response({"error": "No image found"}, status=400) return Response({"error": "No page data found"}, status=404)
return Response(extract["original"])
page_id = next(iter(pages))
page_data = pages[page_id]
# Check if page exists
if page_id == "-1":
return Response({"error": "Wikipedia page not found"}, status=404)
original_image = page_data.get('original')
if not original_image:
return Response({"error": "No image found"}, status=404)
return Response(original_image)
except requests.exceptions.RequestException as e:
logger.exception("Failed to fetch data from Wikipedia")
return Response({"error": "Failed to fetch data from Wikipedia."}, status=500)
except ValueError as e: # JSON decode error
return Response({"error": "Invalid response from Wikipedia API"}, status=500)
def get_search_term(self, term): def get_search_term(self, term):
response = requests.get(f'https://en.wikipedia.org/w/api.php?action=opensearch&search={term}&limit=10&namespace=0&format=json') if not term:
return None
# Properly URL encode the search term
encoded_term = urllib.parse.quote(term)
url = f'https://en.wikipedia.org/w/api.php?action=opensearch&search={encoded_term}&limit=10&namespace=0&format=json'
try:
response = requests.get(url, headers=self.HEADERS, timeout=10)
response.raise_for_status()
# Check if response is empty
if not response.text.strip():
return None
data = response.json() data = response.json()
if data[1] and len(data[1]) > 0:
return data[1][0] # OpenSearch API returns an array with 4 elements:
# [search_term, [titles], [descriptions], [urls]]
if len(data) >= 2 and data[1] and len(data[1]) > 0:
return data[1][0] # Return the first title match
return None
except requests.exceptions.RequestException:
# If search fails, return the original term as fallback
return term
except ValueError: # JSON decode error
# If JSON parsing fails, return the original term as fallback
return term

View File

@@ -853,8 +853,8 @@ packages:
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
engines: {node: '>=8'} engines: {node: '>=8'}
devalue@5.1.1: devalue@5.3.2:
resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} resolution: {integrity: sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw==}
didyoumean@1.2.2: didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
@@ -2235,7 +2235,7 @@ snapshots:
'@sveltejs/vite-plugin-svelte': 3.1.2(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2)) '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@4.2.19)(vite@5.4.19(@types/node@22.15.2))
'@types/cookie': 0.6.0 '@types/cookie': 0.6.0
cookie: 0.6.0 cookie: 0.6.0
devalue: 5.1.1 devalue: 5.3.2
esm-env: 1.2.2 esm-env: 1.2.2
import-meta-resolve: 4.1.0 import-meta-resolve: 4.1.0
kleur: 4.1.5 kleur: 4.1.5
@@ -2535,7 +2535,7 @@ snapshots:
detect-libc@2.0.4: {} detect-libc@2.0.4: {}
devalue@5.1.1: {} devalue@5.3.2: {}
didyoumean@1.2.2: {} didyoumean@1.2.2: {}

View File

@@ -141,7 +141,7 @@
"end_date": "Дата окончания", "end_date": "Дата окончания",
"start_date": "Дата начала", "start_date": "Дата начала",
"remove": "Удалить", "remove": "Удалить",
"location": "Местоположение", "location": "Локация",
"search_for_location": "Поиск местоположения", "search_for_location": "Поиск местоположения",
"clear_map": "Очистить карту", "clear_map": "Очистить карту",
"search_results": "Результаты поиска", "search_results": "Результаты поиска",
@@ -156,7 +156,7 @@
"links": "Ссылки", "links": "Ссылки",
"description": "Описание", "description": "Описание",
"sources": "Источники", "sources": "Источники",
"collection_adventures": "Включить приключения коллекции", "collection_adventures": "Включить приключения из коллекций",
"filter": "Фильтр", "filter": "Фильтр",
"category_filter": "Фильтр категории", "category_filter": "Фильтр категории",
"category": "Категория", "category": "Категория",
@@ -167,7 +167,7 @@
"share": "Поделиться", "share": "Поделиться",
"private": "Приватное", "private": "Приватное",
"public": "Публичное", "public": "Публичное",
"archived": "Архивное", "archived": "В архиве",
"edit_collection": "Редактировать коллекцию", "edit_collection": "Редактировать коллекцию",
"unarchive": "Разархивировать", "unarchive": "Разархивировать",
"archive": "Архивировать", "archive": "Архивировать",
@@ -192,7 +192,7 @@
"actions": "Действия", "actions": "Действия",
"see_adventures": "Посмотреть приключения", "see_adventures": "Посмотреть приключения",
"image_fetch_failed": "Не удалось получить изображение", "image_fetch_failed": "Не удалось получить изображение",
"no_location": "Пожалуйста, введите местоположение", "no_location": "Пожалуйста, введите локацию",
"no_description_found": "Описание не найдено", "no_description_found": "Описание не найдено",
"lodging": "Жильё", "lodging": "Жильё",
"set_to_pin": "Установить как булавку", "set_to_pin": "Установить как булавку",
@@ -231,12 +231,12 @@
"copy_failed": "Копирование не удалось", "copy_failed": "Копирование не удалось",
"show": "Показать", "show": "Показать",
"hide": "Скрыть", "hide": "Скрыть",
"clear_location": "Очистить местоположение", "clear_location": "Очистить локацию",
"starting_airport": "Аэропорт отправления", "starting_airport": "Аэропорт отправления",
"view_profile": "Просмотреть профиль", "view_profile": "Просмотреть профиль",
"joined": "Присоединился", "joined": "Присоединился",
"ending_airport": "Аэропорт прибытия", "ending_airport": "Аэропорт прибытия",
"no_location_found": "Местоположение не найдено", "no_location_found": "Локация не найдена",
"from": "От", "from": "От",
"to": "До", "to": "До",
"start": "Начало", "start": "Начало",
@@ -281,62 +281,62 @@
"collections_linked": "Коллекции связаны", "collections_linked": "Коллекции связаны",
"create_collection_first": "Сначала создайте коллекцию, чтобы организовать ваши приключения и воспоминания.", "create_collection_first": "Сначала создайте коллекцию, чтобы организовать ваши приключения и воспоминания.",
"delete_collection_warning": "Вы уверены, что хотите удалить эту коллекцию? \nЭто действие не может быть отменено.", "delete_collection_warning": "Вы уверены, что хотите удалить эту коллекцию? \nЭто действие не может быть отменено.",
"done": "Сделанный", "done": "Готово",
"loading_adventures": "Загрузка приключений ...", "loading_adventures": "Загрузка приключений ...",
"name_location": "имя, местоположение", "name_location": "имя, локация",
"collection_contents": "Содержание коллекции", "collection_contents": "Содержание коллекции",
"check_in": "Регистрироваться", "check_in": "Регистрация",
"check_out": "Проверить", "check_out": "Выезд",
"collection_link_location_error": "Ошибка связывания местоположения с сбором", "collection_link_location_error": "Ошибка связывания локации с коллекцией",
"collection_link_location_success": "Местоположение, связанное с коллекцией успешно!", "collection_link_location_success": "Локация успешно привязана к коллекции!",
"collection_locations": "Включите места для сбора", "collection_locations": "Добавить локации из коллекций",
"collection_remove_location_error": "Ошибка удаления местоположения из сбора", "collection_remove_location_error": "Ошибка удаления локации из коллекции",
"collection_remove_location_success": "Место удалено из коллекции успешно!", "collection_remove_location_success": "Локация успешно удалена из коллекции!",
"create_location": "Создать местоположение", "create_location": "Создать локацию",
"delete_location": "Удалить местоположение", "delete_location": "Удалить локацию",
"edit_location": "Редактировать местоположение", "edit_location": "Редактировать локацию",
"location_create_error": "Не удалось создать местоположение", "location_create_error": "Не удалось создать локацию",
"location_created": "Место создано", "location_created": "Локация создана",
"location_delete_confirm": "Вы уверены, что хотите удалить это место? \nЭто действие не может быть отменено.", "location_delete_confirm": "Вы уверены, что хотите удалить это место? \nЭто действие не может быть отменено.",
"location_delete_success": "Место удалено успешно!", "location_delete_success": "Локация успешно удалена!",
"location_not_found": "Местоположение не найдено", "location_not_found": "Локация не найдена",
"location_not_found_desc": "Место, которое вы искали, не было найдено. \nПожалуйста, попробуйте другое место или проверьте позже.", "location_not_found_desc": "Локация, которую вы искали, не найдена. \nПожалуйста, попробуйте другую локацию или попробуйте позже.",
"location_update_error": "Не удалось обновить местоположение", "location_update_error": "Не удалось обновить локацию",
"location_updated": "Место обновлено", "location_updated": "Локация обновлена",
"new_location": "Новое место", "new_location": "Новая локация",
"no_collections_to_add_location": "Коллекции не обнаружили, чтобы добавить это место.", "no_collections_to_add_location": "Коллекции для добавления этой локации не нейдены.",
"no_locations_to_recommendations": "Никаких мест не найдено. \nДобавьте хотя бы одно место, чтобы получить рекомендации.", "no_locations_to_recommendations": "Локаций не найдено. \nДобавьте хотя бы одну локацию, чтобы получить рекомендации.",
"public_location": "Общественное местоположение", "public_location": "Общественная локация",
"share_location": "Поделитесь этим расположением!", "share_location": "Поделитесь этой локацией!",
"visit_calendar": "Посетите календарь", "visit_calendar": "Календарь посещений",
"wiki_location_desc": "Вытягивает отрывок из статьи Википедии, соответствующей названию места.", "wiki_location_desc": "Вытягивает отрывок из статьи Википедии, соответствующей названию места.",
"will_be_marked_location": "будет отмечен по посещению после сохранения местоположения.", "will_be_marked_location": "будет отмечен как посещенынй при сохранении.",
"no_locations_found": "Никаких мест не найдено", "no_locations_found": "Локации не найдены",
"image_modal_navigate": "Используйте клавиши со стрелками или нажмите, чтобы перемещаться", "image_modal_navigate": "Используйте клавиши со стрелками или нажмите, чтобы перемещаться",
"details": "Подробности", "details": "Подробности",
"leave": "Оставлять", "leave": "Покинуть",
"leave_collection": "Оставить коллекцию", "leave_collection": "Оставить коллекцию",
"leave_collection_warning": "Вы уверены, что хотите покинуть эту коллекцию? \nЛюбые места, которые вы добавили, будут не связаны и останутся в вашей учетной записи.", "leave_collection_warning": "Вы уверены, что хотите покинуть эту коллекцию? \nЛюбые локации, которые вы добавили, будут не связаны и останутся в вашей учетной записи.",
"left_collection_message": "Успешно оставил коллекцию", "left_collection_message": "Успешно оставил коллекцию",
"loading_collections": "Загрузка коллекций ...", "loading_collections": "Загрузка коллекций ...",
"quick_start": "Быстрый старт", "quick_start": "Быстрый старт",
"achievements": "Достижения", "achievements": "Достижения",
"active_duration": "Активная продолжительность", "active_duration": "Активная продолжительность",
"activities_name": "Деятельность", "activities_name": "Активности",
"activity_name": "Имя деятельности", "activity_name": "Название активности",
"activity_name_placeholder": "Утренняя пробежка", "activity_name_placeholder": "Утренняя пробежка",
"activity_name_required": "Требуется имя деятельности", "activity_name_required": "Требуется имя активности",
"activity_options": "Варианты деятельности", "activity_options": "Варианты активностей",
"activity_statistics": "Статистика деятельности", "activity_statistics": "Статистика активности",
"activity_statistics_description": "Ваши достижения в области фитнеса и активности", "activity_statistics_description": "Ваши достижения в области фитнеса и активности",
"add_activity": "Добавить деятельность", "add_activity": "Добавить активность",
"add_first_trail": "Добавьте свой первый след, используя кнопку выше", "add_first_trail": "Добавьте свой первый след, используя кнопку выше",
"add_new_activity": "Добавить новую деятельность", "add_new_activity": "Добавить новую активность",
"add_new_trail": "Добавить новую тропу", "add_new_trail": "Добавить новый маршрут",
"add_trail": "Добавить след", "add_trail": "Добавить маршрут",
"add_visit": "Добавить визит", "add_visit": "Добавить посещение",
"add_wanderer_trail": "Добавить тропу Странника", "add_wanderer_trail": "Добавить маршрут Wanderer",
"attachment_management": "Управление привязанностью", "attachment_management": "Управление вложениями",
"attachment_name_required": "Требуется имя вложения", "attachment_name_required": "Требуется имя вложения",
"attachment_remove_error": "Произошла ошибка при удалении вложения", "attachment_remove_error": "Произошла ошибка при удалении вложения",
"attachment_removed": "Приложение удалено успешно", "attachment_removed": "Приложение удалено успешно",
@@ -347,96 +347,96 @@
"back": "Назад", "back": "Назад",
"cadence": "Каденция", "cadence": "Каденция",
"calories": "Калории", "calories": "Калории",
"click_map": "Нажмите на карту, чтобы выбрать местоположение", "click_map": "Нажмите на карту, чтобы выбрать локацию",
"click_on_map": "Нажмите на карту, чтобы выбрать местоположение", "click_on_map": "Нажмите на карту, чтобы выбрать локацию",
"complete_import": "Полный импорт", "complete_import": "Полный импорт",
"complete_strava_import": "Полный импорт Strava", "complete_strava_import": "Полный импорт Strava",
"confirm_delete_activity": "Вы уверены, что хотите удалить это занятие?", "confirm_delete_activity": "Вы уверены, что хотите удалить это занятие?",
"connect_to_wanderer": "Подключитесь к Wanderer", "connect_to_wanderer": "Подключитесь к Wanderer",
"continue": "Продолжать", "continue": "Далее",
"create_new_location": "Создайте новое место", "create_new_location": "Создайте новую локацию",
"create_trail": "Создать след", "create_trail": "Создать след",
"created": "Созданный", "created": "Созданный",
"current_attachments": "Текущие вложения", "current_attachments": "Текущие вложения",
"date_selection": "Выбор даты", "date_selection": "Выбор даты",
"download_gpx": "Скачать gpx", "download_gpx": "Скачать gpx",
"edit_visit": "Редактировать посещение", "edit_visit": "Редактировать посещение",
"elapsed_time": "Прошло время", "elapsed_time": "Длительность",
"elevation": "Возвышение", "elevation": "Возвышение",
"elevation_gain": "Увеличение возвышения", "elevation_gain": "Набор высоты",
"elevation_high": "Высота высота", "elevation_high": "Высшая точка",
"elevation_loss": "Потеря высоты", "elevation_loss": "Потеря высоты",
"elevation_low": "Высота", "elevation_low": "Низшая точка",
"end_lat": "Конец широты", "end_lat": "Широта финиша",
"end_lng": "Конец долготы", "end_lng": "Долгота финиша",
"export_gpx": "Экспорт GPX", "export_gpx": "Экспорт GPX",
"export_original": "Экспорт оригинал", "export_original": "Экспорт оригинал",
"external_link": "Внешняя ссылка", "external_link": "Внешняя ссылка",
"gain": "прирост", "gain": "прирост",
"getting_location_details": "Получение деталей местоположения", "getting_location_details": "Получение деталей локации",
"gpx_file": "Файл gpx", "gpx_file": "Файл gpx",
"gpx_file_downloaded": "Файл gpx загружен. \nПожалуйста, загрузите его ниже, чтобы завершить импорт.", "gpx_file_downloaded": "Файл gpx загружен. \nПожалуйста, загрузите его ниже, чтобы завершить импорт.",
"gpx_file_required": "Требуется файл GPX", "gpx_file_required": "Требуется файл GPX",
"image_management": "Управление изображением", "image_management": "Управление изображениями",
"import_activity": "Импортная деятельность", "import_activity": "Загрузить активность",
"importing": "Импорт", "importing": "Импорт",
"likes": "Лайки", "likes": "Лайки",
"loading_activities": "Погрузка", "loading_activities": "Погрузка",
"location_display_name": "Отображение местоположения", "location_display_name": "Отображение локации",
"location_map": "Расположение", "location_map": "Расположение",
"location_selected": "Местоположение выбрано", "location_selected": "Локация выбрана",
"max_speed": "Максимальная скорость", "max_speed": "Максимальная скорость",
"moving_time": "Время движения", "moving_time": "Время движения",
"next_image": "Следующее изображение", "next_image": "Следующее изображение",
"no_attachments_uploaded_yet": "Пока не загружены вложения", "no_attachments_uploaded_yet": "Вложения еще не загружены",
"no_external_link": "Внешнее внешнее ссылка", "no_external_link": "Внешнее внешнее ссылка",
"no_file_selected": "Файл не выбран", "no_file_selected": "Файл не выбран",
"no_images_uploaded_yet": "Пока не загружены изображения", "no_images_uploaded_yet": "Изображения еще не загружены",
"no_strava_activities": "Во время этого визита не было найдено мероприятий Strava", "no_strava_activities": "Во время этого посещения не было найдено мероприятий Strava",
"no_trails_added": "Пока не добавили тропы", "no_trails_added": "Пока не добавили маршруты",
"no_trails_available": "Нет тропинок", "no_trails_available": "Нет маршрутов",
"no_trails_found_matching": "Трейлей не найдено подходящими", "no_trails_found_matching": "Не найдено подходящих маршрутов",
"no_visits_description": "Создайте свой первый посещение, выбрав даты выше", "no_visits_description": "Создайте свое первое посещение, выбрав даты выше",
"notes_placeholder": "Добавить заметки об этом визите", "notes_placeholder": "Добавить заметки об этом посещении",
"or": "ИЛИ", "or": "ИЛИ",
"pace": "Шаг", "pace": "Шаг",
"photos": "Фото", "photos": "Фото",
"previous_image": "Предыдущее изображение", "previous_image": "Предыдущее изображение",
"processing": "Обработка", "processing": "Обработка",
"public_location_description": "Сделайте это место видимым другим пользователям", "public_location_description": "Сделайте эту локацию видимой другим пользователям",
"remove_visit": "Удалить посещение", "remove_visit": "Удалить посещение",
"rest_time": "Время отдыха", "rest_time": "Время отдыха",
"saved_activities": "Спасенные действия", "saved_activities": "Сохраненные активности",
"search_location": "Поиск места", "search_location": "Поиск локации",
"search_placeholder": "Войдите в город, место или достопримечательность ...", "search_placeholder": "Введите город, локацию или достопримечательность ...",
"search_trails_placeholder": "Поисковые следы по имени", "search_trails_placeholder": "Поиск маршрутов по имени",
"searching": "Идет поиск", "searching": "Идет поиск",
"select_on_map": "Выберите на карте", "select_on_map": "Выберите на карте",
"select_wanderer_trail": "Выберите след из своей учетной записи Wanderer", "select_wanderer_trail": "Выберите маршрут из своей учетной записи Wanderer",
"sport_type": "Спортивный тип", "sport_type": "Вид спорта",
"sport_type_placeholder": "Тропа бег", "sport_type_placeholder": "Бег по пересечённой местности",
"start_lat": "Запустить широту", "start_lat": "Широта старта",
"start_lng": "Начните долготу", "start_lng": "Долгота старта",
"strava_activities_during_visit": "Мероприятия Strava во время визита", "strava_activities_during_visit": "Мероприятия Strava во время посещения",
"strava_activity_ready": "Страва готова", "strava_activity_ready": "Strava готова",
"time": "Время", "time": "Время",
"total_covered": "Общее покрытие", "total_covered": "Всего охвачено",
"total_recorded": "Всего записано", "total_recorded": "Всего записано",
"trail": "Тащить", "trail": "Маршрут",
"trail_created_successfully": "Тропа создала успешно", "trail_created_successfully": "Маршрут успешно создан",
"trail_creation_failed": "Не удалось создать след", "trail_creation_failed": "Не удалось создать маршрут",
"trail_fetch_failed": "Не удалось принести тропы Странника", "trail_fetch_failed": "Не удалось получить маршрут Wanderer",
"trail_link_required": "Требуется ссылка на след", "trail_link_required": "Требуется ссылка на маршрут",
"trail_name": "Название тропы", "trail_name": "Название маршрута",
"trail_removal_failed": "Не удалось снять след", "trail_removal_failed": "Не удалось удалить маршрут",
"trail_removed_successfully": "Тропа успешно удален", "trail_removed_successfully": "Маршрут успешно удален",
"trail_update_failed": "Не удалось обновить след", "trail_update_failed": "Не удалось обновить маршрут",
"trail_updated_successfully": "Тропа обновлен успешно", "trail_updated_successfully": "Маршрут успешно обновлен",
"trails": "Тропы", "trails": "Маршруты",
"trails_found_for": "Тропы найдены для", "trails_found_for": "Маршруты найдены для",
"trails_management": "Управление тропами", "trails_management": "Управление маршрутами",
"trails_management_description": "Управление трассами, связанными с этим местом. \nСледы могут быть связаны с внешними услугами, такими как Alltrails или ссылка на Trails Wanderer.", "trails_management_description": "Управляйте маршрутами, связанными с этой локацией. \nИх можно связать с внешними сервисами, например, AllTrails или с маршрутами Wanderer.",
"update_location_details": "Обновление сведений о местоположении", "update_location_details": "Обновление сведений о локации",
"update_visit": "Обновить посещение", "update_visit": "Обновить посещение",
"upload_activity": "Загрузка активности", "upload_activity": "Загрузка активности",
"upload_attachment": "Загрузите вложение", "upload_attachment": "Загрузите вложение",
@@ -450,15 +450,15 @@
"view_gpx": "Посмотреть GPX", "view_gpx": "Посмотреть GPX",
"view_on": "Посмотреть на", "view_on": "Посмотреть на",
"view_strava_activities": "Посмотреть мероприятия Strava", "view_strava_activities": "Посмотреть мероприятия Strava",
"view_trail": "Посмотреть след", "view_trail": "Посмотреть маршрут",
"wanderer_integration_error": "Интеграция Wanderer не включена или истек.", "wanderer_integration_error": "Интеграция Wanderer не включена или срок ее действия истек.",
"wikipedia_error": "Не удалось получить описание из Википедии", "wikipedia_error": "Не удалось получить описание из Википедии",
"high": "Высокий", "high": "Высокий",
"low": "Низкий", "low": "Низкий",
"rest": "Отдых", "rest": "Осталось",
"total": "Общий", "total": "Всего",
"attachment_removed_error": "Ошибка удаления вложения", "attachment_removed_error": "Ошибка удаления вложения",
"attachment_removed_success": "Приложение удалено успешно", "attachment_removed_success": "Вложение успешно удалено",
"attachments_upload_info": "Вложения будут загружены после сохранения", "attachments_upload_info": "Вложения будут загружены после сохранения",
"image_upload_info": "Изображения будут загружены после сохранения", "image_upload_info": "Изображения будут загружены после сохранения",
"linked_locations": "Связанные локации", "linked_locations": "Связанные локации",
@@ -470,12 +470,12 @@
"route_map": "Маршрутная карта", "route_map": "Маршрутная карта",
"selected_attachments": "Выбранные вложения", "selected_attachments": "Выбранные вложения",
"selected_images": "Выбранные изображения", "selected_images": "Выбранные изображения",
"activities_text": "деятельность", "activities_text": "активность",
"activity_breakdown_by_category": "Распад деятельности по категории", "activity_breakdown_by_category": "Распределение активностей по категориям",
"distance_covered": "Расстояние", "distance_covered": "Расстояние",
"recorded_sessions": "Записанные сеансы", "recorded_sessions": "Записанные сеансы",
"total_activities": "Общая деятельность", "total_activities": "Всего активностей",
"total_climbed": "Всего поднялось", "total_climbed": "Общий набор высоты",
"total_distance": "Общее расстояние" "total_distance": "Общее расстояние"
}, },
"worldtravel": { "worldtravel": {
@@ -525,15 +525,15 @@
"remaining": "Оставшийся", "remaining": "Оставшийся",
"show_map": "Показать карту", "show_map": "Показать карту",
"show_map_labels": "Показать этикетки карты", "show_map_labels": "Показать этикетки карты",
"total_cities": "Общие города", "total_cities": "Всего город",
"total_countries": "Всего стран", "total_countries": "Всего стран",
"total_regions": "Общие регионы", "total_regions": "Всего регионов",
"newest_first": "Новейший первый", "newest_first": "Сначала новые",
"oldest_first": "Сначала старейший", "oldest_first": "Сначала старые",
"unvisited_first": "Не заселяется первым", "unvisited_first": "Сначала не посещенные",
"visited_first": "Посетил первым", "visited_first": "Сначала посещенные",
"total_items": "Общие предметы", "total_items": "Общие предметы",
"getting_location_details": "Получение деталей местоположения" "getting_location_details": "Получение деталей локации"
}, },
"auth": { "auth": {
"username": "Имя пользователя", "username": "Имя пользователя",
@@ -554,8 +554,8 @@
"or_3rd_party": "Или войти через сторонний сервис", "or_3rd_party": "Или войти через сторонний сервис",
"no_public_collections": "Публичные коллекции не найдены", "no_public_collections": "Публичные коллекции не найдены",
"user_collections": "Коллекции пользователя", "user_collections": "Коллекции пользователя",
"no_public_locations": "Общественных мест не найдено", "no_public_locations": "Публичные локации не найдены",
"user_locations": "Пользовательские местоположения", "user_locations": "Локации пользователя",
"enter_password": "Введите свой пароль", "enter_password": "Введите свой пароль",
"enter_username": "Введите свое имя пользователя", "enter_username": "Введите свое имя пользователя",
"logging_in": "Вход в систему", "logging_in": "Вход в систему",
@@ -695,19 +695,19 @@
"verify_setup": "Проверьте настройку", "verify_setup": "Проверьте настройку",
"whats_included": "Что включено", "whats_included": "Что включено",
"backup_your_data": "Резервную копию ваших данных", "backup_your_data": "Резервную копию ваших данных",
"backup_your_data_desc": "Загрузите полное резервное копирование данных вашей учетной записи, включая местоположения, коллекции, медиа и посещения.", "backup_your_data_desc": "Загрузите полное резервное копирование данных вашей учетной записи, включая локации, коллекции, медиа и посещения.",
"data_override_acknowledge": "Я признаю, что это переопределит все мои существующие данные", "data_override_acknowledge": "Я признаю, что это переопределит все мои существующие данные",
"data_override_acknowledge_desc": "Это действие необратимо и заменит все местоположения, коллекции и посещения в вашем аккаунте.", "data_override_acknowledge_desc": "Это действие необратимо и заменит все локации, коллекции и посещения в вашем аккаунте.",
"data_override_warning": "Предупреждение о переопределении данных", "data_override_warning": "Предупреждение о переопределении данных",
"data_override_warning_desc": "Восстановление данных полностью заменит все существующие данные (которые включены в резервную копию) в вашу учетную запись. \nЭто действие не может быть отменено.", "data_override_warning_desc": "Восстановление данных полностью заменит все существующие данные (которые включены в резервную копию) в вашу учетную запись. \nЭто действие не может быть отменено.",
"integrations_settings": "Настройки интеграции", "integrations_settings": "Настройки интеграции",
"media": "СМИ", "media": "Файлы",
"restore_data": "Восстановить данные", "restore_data": "Восстановить данные",
"restore_data_desc": "Загрузите файл резервного копирования, чтобы восстановить ваши данные.", "restore_data_desc": "Загрузите файл резервного копирования, чтобы восстановить ваши данные.",
"select_backup_file": "Выберите файл резервного копирования", "select_backup_file": "Выберите файл резервного копирования",
"world_travel_visits": "Всемирные поездки", "world_travel_visits": "Всемирные поездки",
"activities": "Деятельность", "activities": "Активности",
"trails": "Тропы", "trails": "Маршруты",
"use_imperial": "Используйте имперские подразделения", "use_imperial": "Используйте имперские подразделения",
"use_imperial_desc": "Используйте имперские единицы (ноги, дюймы, фунты) вместо метрических единиц" "use_imperial_desc": "Используйте имперские единицы (ноги, дюймы, фунты) вместо метрических единиц"
}, },
@@ -795,10 +795,10 @@
}, },
"edit_transportation": "Редактировать транспорт", "edit_transportation": "Редактировать транспорт",
"create_new_transportation": "Новый транспорт", "create_new_transportation": "Новый транспорт",
"enter_flight_number": "Введите номер полета", "enter_flight_number": "Введите номер рейса",
"enter_from_location": "Введите из местоположения", "enter_from_location": "Укажите место отправления",
"enter_link": "Введите ссылку", "enter_link": "Введите ссылку",
"enter_to_location": "Введите в местоположение", "enter_to_location": "Укажите место прибытия",
"enter_transportation_name": "Введите название транспорта", "enter_transportation_name": "Введите название транспорта",
"select_type": "Выберите тип", "select_type": "Выберите тип",
"update_transportation_details": "Обновите детали транспорта" "update_transportation_details": "Обновите детали транспорта"
@@ -834,7 +834,7 @@
"found": "найденный", "found": "найденный",
"result": "Результат", "result": "Результат",
"results": "Результаты", "results": "Результаты",
"try_searching_desc": "Попробуйте искать приключения, коллекции, страны, регионы, города или пользователей." "try_searching_desc": "Попробуйте искать локации, коллекции, страны, регионы, города или пользователей."
}, },
"map": { "map": {
"view_details": "Подробности", "view_details": "Подробности",
@@ -850,29 +850,31 @@
"map_controls": "Карта управления", "map_controls": "Карта управления",
"marker_placed_on_map": "Маркер размещен на карте", "marker_placed_on_map": "Маркер размещен на карте",
"regions": "Регионы", "regions": "Регионы",
"add_location": "Добавить новое место", "add_location": "Добавить новую локацию",
"add_location_at_marker": "Добавить новое место в маркере", "add_location_at_marker": "Добавить новую локацию в маркере",
"location_map": "Карта местоположения", "location_map": "Карта местоположения",
"locations_shown": "Места показаны", "locations_shown": "Места показаны",
"place_marker_desc_location": "Нажмите на карту, чтобы разместить маркер.", "place_marker_desc_location": "Нажмите на карту, чтобы разместить маркер.",
"show_activities": "Показать деятельность", "show_activities": "Показать активности",
"show_visited_cities": "Посещены города" "show_visited_cities": "Посещенные города"
}, },
"share": { "share": {
"shared": "Поделено", "shared": "Поделился",
"with": "с", "with": "с",
"unshared": "Не поделено", "unshared": "Не поделено",
"share_desc": "Поделитесь этой коллекцией с другими пользователями.", "share_desc": "Поделитесь этой коллекцией с другими пользователями.",
"shared_with": "Поделено с", "shared_with": "Поделился с",
"no_users_shared": "Ни с кем не поделено", "no_users_shared": "Ни с кем не поделился",
"not_shared_with": "Не поделено с", "not_shared_with": "Не поделился с",
"no_shared_found": "Не найдено коллекций, которыми с вами поделились.", "no_shared_found": "Не найдено коллекций, которыми с вами поделились.",
"set_public": "Чтобы пользователи могли делиться с вами, вам нужно сделать ваш профиль публичным.", "set_public": "Чтобы пользователи могли делиться с вами, вам нужно сделать ваш профиль публичным.",
"go_to_settings": "Перейти к настройкам", "go_to_settings": "Перейти к настройкам",
"available": "Доступный", "available": "Доступный",
"pending": "В ожидании", "pending": "В ожидании",
"revoke_invite": "Отменить приглашение", "revoke_invite": "Отменить приглашение",
"send_invite": "Отправить приглашение" "send_invite": "Отправить приглашение",
"available_users": "Поделиться с",
"no_available_users": "Нет доступных пользователей"
}, },
"languages": {}, "languages": {},
"profile": { "profile": {
@@ -881,14 +883,14 @@
"visited_countries": "Посещенные страны", "visited_countries": "Посещенные страны",
"visited_regions": "Посещенные регионы", "visited_regions": "Посещенные регионы",
"visited_cities": "Посещенные города", "visited_cities": "Посещенные города",
"discovered": "обнаруженный", "discovered": "обнаруженно",
"explored": "исследован", "explored": "исследованно",
"no_shared_adventures": "Этот пользователь еще не поделился публичными приключениями.", "no_shared_adventures": "Этот пользователь еще не поделился публичными приключениями.",
"no_shared_collections": "Этот пользователь еще не поделился публичными коллекциями.", "no_shared_collections": "Этот пользователь еще не поделился публичными коллекциями.",
"planned_trips": "Запланированные поездки", "planned_trips": "Запланированные поездки",
"travel_statistics": "Статистика путешествий", "travel_statistics": "Статистика путешествий",
"your_journey_at_a_glance": "Ваше приключенческое путешествие с первого взгляда", "your_journey_at_a_glance": "Ваше приключенческое путешествие с первого взгляда",
"public_location_experiences": "Общественное местоположение" "public_location_experiences": "Общественная локация"
}, },
"categories": { "categories": {
"manage_categories": "Управление категориями", "manage_categories": "Управление категориями",
@@ -899,7 +901,8 @@
"category_name": "Название категории", "category_name": "Название категории",
"add_new_category": "Добавить новую категорию", "add_new_category": "Добавить новую категорию",
"name_required": "Требуется название категории", "name_required": "Требуется название категории",
"location_update_after_refresh": "Карты местоположения будут обновлены после обновления страницы." "location_update_after_refresh": "Карты местоположения будут обновлены после обновления страницы.",
"no_categories_yet": "Нет категорий"
}, },
"dashboard": { "dashboard": {
"welcome_back": "Добро пожаловать обратно", "welcome_back": "Добро пожаловать обратно",
@@ -977,24 +980,24 @@
"week": "Неделя" "week": "Неделя"
}, },
"locations": { "locations": {
"location": "Расположение", "location": "Локация",
"locations": "Локации", "locations": "Локации",
"my_locations": "Мои локации" "my_locations": "Мои локации"
}, },
"settings_download_backup": "Скачать резервную копию", "settings_download_backup": "Скачать резервную копию",
"invites": { "invites": {
"accept": "Принимать", "accept": "Принять",
"accept_failed": "Не удалось принять приглашение", "accept_failed": "Не удалось принять приглашение",
"accepted": "Пригласить принятый", "accepted": "Приглашение принято",
"by": "к", "by": "к",
"decline": "Отклонить", "decline": "Отклонить",
"decline_failed": "Не удалось отказаться пригласить", "decline_failed": "Не удалось отклонить приглашение",
"declined": "Пригласить отклонено", "declined": "Приглашение отклонено",
"invited_on": "Приглашен", "invited_on": "Приглашен",
"no_invites": "Нет приглашений", "no_invites": "Нет приглашений",
"no_invites_desc": "Убедитесь, что ваш профиль является общедоступным, чтобы пользователи могли пригласить вас.", "no_invites_desc": "Убедитесь, что ваш профиль является общедоступным, чтобы пользователи могли пригласить вас.",
"pending_invites": "В ожидании приглашений", "pending_invites": "В ожидании приглашений",
"title": "Приглашает" "title": "Приглашения"
}, },
"strava": { "strava": {
"account_connected": "Учетная запись подключена", "account_connected": "Учетная запись подключена",
@@ -1013,6 +1016,6 @@
"wanderer": { "wanderer": {
"connected": "Успешно связанный с Wanderer", "connected": "Успешно связанный с Wanderer",
"connection_error": "Ошибка подключения к Wanderer", "connection_error": "Ошибка подключения к Wanderer",
"wanderer_integration_desc": "Подключитесь к Wanderer, чтобы легко импортировать и просмотреть ваши следы в местах" "wanderer_integration_desc": "Подключитесь к Wanderer, чтобы легко импортировать и просмотреть ваши маршруты в локациях"
} }
} }

View File

@@ -22,7 +22,7 @@ export const load = (async (event) => {
typeString = 'all'; typeString = 'all';
} }
const include_collections = event.url.searchParams.get('include_collections') || 'false'; const include_collections = event.url.searchParams.get('include_collections') || 'true';
const order_by = event.url.searchParams.get('order_by') || 'updated_at'; const order_by = event.url.searchParams.get('order_by') || 'updated_at';
const order_direction = event.url.searchParams.get('order_direction') || 'asc'; const order_direction = event.url.searchParams.get('order_direction') || 'asc';
const page = event.url.searchParams.get('page') || '1'; const page = event.url.searchParams.get('page') || '1';

View File

@@ -114,10 +114,13 @@
} else { } else {
currentSort.visited = false; currentSort.visited = false;
} }
if (url.searchParams.get('include_collections') === 'on') { if (url.searchParams.get('include_collections') === 'true') {
currentSort.includeCollections = true; currentSort.includeCollections = true;
} else { } else if (url.searchParams.get('include_collections') === 'false') {
currentSort.includeCollections = false; currentSort.includeCollections = false;
} else {
// Default to true when no parameter is present (first visit)
currentSort.includeCollections = true;
} }
if (!currentSort.visited && !currentSort.planned) { if (!currentSort.visited && !currentSort.planned) {
@@ -469,6 +472,18 @@
id="include_collections" id="include_collections"
class="checkbox checkbox-primary" class="checkbox checkbox-primary"
checked={currentSort.includeCollections} checked={currentSort.includeCollections}
on:change={(e) => {
const target = e.currentTarget;
currentSort.includeCollections = target.checked;
// Immediately update the URL to reflect the change
let url = new URL(window.location.href);
if (target.checked) {
url.searchParams.set('include_collections', 'true');
} else {
url.searchParams.set('include_collections', 'false');
}
goto(url.toString(), { invalidateAll: true, replaceState: true });
}}
/> />
<span class="label-text">{$t('adventures.collection_locations')}</span> <span class="label-text">{$t('adventures.collection_locations')}</span>
</label> </label>

View File

@@ -1226,22 +1226,17 @@
{/if} {/if}
<!-- Help documentation link --> <!-- Help documentation link -->
{#if user.is_staff || !stravaGlobalEnabled}
{#if !wandererEnabled}
<div class="mt-4 p-4 bg-info/10 rounded-lg"> <div class="mt-4 p-4 bg-info/10 rounded-lg">
{#if user.is_staff}
<p class="text-sm"> <p class="text-sm">
📖 {$t('immich.need_help')} 📖 {$t('immich.need_help')}
<a <a
class="link link-primary" class="link link-primary"
href="https://adventurelog.app/docs/configuration/wanderer_integration.html" href="https://adventurelog.app/docs/configuration/immich_integration.html"
target="_blank">{$t('navbar.documentation')}</a target="_blank">{$t('navbar.documentation')}</a
> >
</p> </p>
{:else if !stravaGlobalEnabled}
<p class="text-sm">
{$t('google_maps.google_maps_integration_desc_no_staff')}
</p>
{/if}
</div> </div>
{/if} {/if}
</div> </div>