mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-08-03 20:25:18 +02:00
Merge pull request #356 from seanmorley15/development
AdventureLog v0.7.1
This commit is contained in:
commit
0ea948c62c
117 changed files with 5707 additions and 253190 deletions
28
.vscode/settings.json
vendored
28
.vscode/settings.json
vendored
|
@ -1,3 +1,29 @@
|
|||
{
|
||||
"git.ignoreLimitWarning": true
|
||||
"git.ignoreLimitWarning": true,
|
||||
"i18n-ally.localesPaths": [
|
||||
"frontend/src/locales",
|
||||
"backend/server/backend/lib/python3.12/site-packages/allauth/locale",
|
||||
"backend/server/backend/lib/python3.12/site-packages/dj_rest_auth/locale",
|
||||
"backend/server/backend/lib/python3.12/site-packages/rest_framework/locale",
|
||||
"backend/server/backend/lib/python3.12/site-packages/rest_framework_simplejwt/locale",
|
||||
"backend/server/backend/lib/python3.12/site-packages/django/conf/locale",
|
||||
"backend/server/backend/lib/python3.12/site-packages/django/contrib/messages",
|
||||
"backend/server/backend/lib/python3.12/site-packages/allauth/templates/account/messages",
|
||||
"backend/server/backend/lib/python3.12/site-packages/allauth/templates/mfa/messages",
|
||||
"backend/server/backend/lib/python3.12/site-packages/allauth/templates/socialaccount/messages",
|
||||
"backend/server/backend/lib/python3.12/site-packages/allauth/templates/usersessions/messages",
|
||||
"backend/server/backend/lib/python3.12/site-packages/django/contrib/admindocs/locale",
|
||||
"backend/server/backend/lib/python3.12/site-packages/django/contrib/auth/locale",
|
||||
"backend/server/backend/lib/python3.12/site-packages/django/contrib/admin/locale",
|
||||
"backend/server/backend/lib/python3.12/site-packages/django/contrib/contenttypes/locale",
|
||||
"backend/server/backend/lib/python3.12/site-packages/django/contrib/flatpages/locale",
|
||||
"backend/server/backend/lib/python3.12/site-packages/django/contrib/humanize/locale",
|
||||
"backend/server/backend/lib/python3.12/site-packages/django/contrib/gis/locale",
|
||||
"backend/server/backend/lib/python3.12/site-packages/django/contrib/redirects/locale",
|
||||
"backend/server/backend/lib/python3.12/site-packages/django/contrib/postgres/locale",
|
||||
"backend/server/backend/lib/python3.12/site-packages/django/contrib/sessions/locale",
|
||||
"backend/server/backend/lib/python3.12/site-packages/django/contrib/sites/locale",
|
||||
"backend/server/backend/lib/python3.12/site-packages/rest_framework/templates/rest_framework/docs/langs"
|
||||
],
|
||||
"i18n-ally.keystyle": "nested"
|
||||
}
|
||||
|
|
38
README.md
38
README.md
|
@ -53,13 +53,13 @@ Here is a summary of the configuration options available in the `docker-compose.
|
|||
| Name | Required | Description | Default Value |
|
||||
| ------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
|
||||
| `PUBLIC_SERVER_URL` | Yes | What the frontend SSR server uses to connect to the backend. | http://server:8000 |
|
||||
| `ORIGIN` | Sometimes | Not needed if using HTTPS. If not, set it to the domain of what you will acess the app from. | http://localhost:8080 |
|
||||
| `ORIGIN` | Sometimes | Not needed if using HTTPS. If not, set it to the domain of what you will acess the app from. | http://localhost:8015 |
|
||||
| `BODY_SIZE_LIMIT` | Yes | Used to set the maximum upload size to the server. Should be changed to prevent someone from uploading too much! Custom values must be set in **kiliobytes**. | Infinity |
|
||||
|
||||
### Backend Container (server)
|
||||
|
||||
| Name | Required | Description | Default Value |
|
||||
| ----------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
|
||||
| ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
|
||||
| `PGHOST` | Yes | Databse host. | db |
|
||||
| `PGDATABASE` | Yes | Database. | database |
|
||||
| `PGUSER` | Yes | Database user. | adventure |
|
||||
|
@ -67,28 +67,9 @@ Here is a summary of the configuration options available in the `docker-compose.
|
|||
| `DJANGO_ADMIN_USERNAME` | Yes | Default username. | admin |
|
||||
| `DJANGO_ADMIN_PASSWORD` | Yes | Default password, change after inital login. | admin |
|
||||
| `DJANGO_ADMIN_EMAIL` | Yes | Default user's email. | admin@example.com |
|
||||
| `PUBLIC_URL` | Yes | This is the publically accessible url to the **nginx** container. You should be able to acess nginx from this url where you access your app. | http://127.0.0.1:81 |
|
||||
| `CSRF_TRUSTED_ORIGINS` | Yes | Need to be changed to the orgins where you use your backend server and frontend. These values are comma seperated. | Needs to be changed. |
|
||||
| `FRONTEND_URL` | Yes | This is the publically accessible url to the **frontend** container. This link should be accessable for all users. Used for email generation. | http://localhost:3000 |
|
||||
|
||||
### Proxy Container (nginx) Configuration
|
||||
|
||||
In order to use media files in a production environment, you need to configure the `nginx` container to serve the media files. The container is already in the docker compose file but you need to do a few things to make it work.
|
||||
|
||||
1. Create a directory called `proxy` in the same directory as the `docker-compose.yml` file.
|
||||
2. Create a file called `nginx.conf` in the `proxy` directory.
|
||||
3. Add the following configuration to the `nginx.conf` file:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
location /media/ {
|
||||
alias /app/media/;
|
||||
}
|
||||
}
|
||||
```
|
||||
| `PUBLIC_URL` | Yes | This needs to match the outward port of the server and be accessible from where the app is used. It is used for the creation of image urls. | 'http://localhost:8016' |
|
||||
| `CSRF_TRUSTED_ORIGINS` | Yes | Need to be changed to the orgins where you use your backend server and frontend. These values are comma seperated. | http://localhost:8016 |
|
||||
| `FRONTEND_URL` | Yes | This is the publicly accessible url to the **frontend** container. This link should be accessible for all users. Used for email generation. | 'http://localhost:8015' |
|
||||
|
||||
## Running the Containers
|
||||
|
||||
|
@ -119,8 +100,6 @@ View all of your adventures on a map, with the ability to filter by visit status
|
|||
|
||||

|
||||
|
||||
️
|
||||
|
||||
# About AdventureLog
|
||||
|
||||
AdventureLog is a Svelte Kit and Django application that utilizes a PostgreSQL database. Users can log the adventures they have experienced, as well as plan future ones. Key features include:
|
||||
|
@ -152,10 +131,3 @@ AdventureLog is licensed under the GNU General Public License v3.0.
|
|||
|
||||
- Logo Design by [redtechtiger](https://github.com/redtechtiger)
|
||||
- WorldTravel Dataset [dr5hn/countries-states-cities-database](https://github.com/dr5hn/countries-states-cities-database)
|
||||
- [Mexico GEOJSON](https://cartographyvectors.com/map/784-mexico-with-states)
|
||||
- [Japan GEOJSON](https://cartographyvectors.com/map/361-japan)
|
||||
- [Ireland GEOJSON](https://cartographyvectors.com/map/1399-ireland-provinces)
|
||||
- [Sweden GEOJSON](https://cartographyvectors.com/map/1521-sweden-with-regions)
|
||||
- [Switzerland GEOJSON](https://cartographyvectors.com/map/1522-switzerland-with-regions)
|
||||
- [Iceland GEOJSON](https://cartographyvectors.com/map/1453-iceland-with-regions)
|
||||
- [Austria GEOJSON](https://github.com/codeforgermany/click_that_hood/blob/main/public/data/austria-states.geojson)
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
http://github.com/iMerica/dj-rest-auth/contributors
|
|
@ -1,5 +1,4 @@
|
|||
# Dockerfile
|
||||
|
||||
# Use the official Python slim image as the base image
|
||||
FROM python:3.10-slim
|
||||
|
||||
LABEL Developers="Sean Morley"
|
||||
|
@ -11,23 +10,36 @@ ENV PYTHONUNBUFFERED 1
|
|||
# Set the working directory
|
||||
WORKDIR /code
|
||||
|
||||
# Install system dependencies
|
||||
# Install system dependencies (Nginx included)
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y git postgresql-client gdal-bin libgdal-dev \
|
||||
&& apt-get clean
|
||||
&& apt-get install -y git postgresql-client gdal-bin libgdal-dev nginx \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Python dependencies
|
||||
COPY ./server/requirements.txt /code/
|
||||
RUN pip install --upgrade pip
|
||||
RUN pip install -r requirements.txt
|
||||
RUN pip install --upgrade pip \
|
||||
&& pip install -r requirements.txt
|
||||
|
||||
# Create necessary directories
|
||||
RUN mkdir -p /code/static /code/media
|
||||
# RUN mkdir -p /code/staticfiles /code/media
|
||||
|
||||
# Copy the Django project code into the Docker image
|
||||
COPY ./server /code/
|
||||
|
||||
# Copy Nginx configuration
|
||||
COPY ./nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
# Collect static files
|
||||
RUN python3 manage.py collectstatic --noinput --verbosity 2
|
||||
|
||||
# Set the entrypoint script
|
||||
COPY ./entrypoint.sh /code/entrypoint.sh
|
||||
RUN chmod +x /code/entrypoint.sh
|
||||
ENTRYPOINT ["/code/entrypoint.sh"]
|
||||
|
||||
# Expose ports for NGINX and Gunicorn
|
||||
EXPOSE 80 8000
|
||||
|
||||
# Command to start Nginx and Gunicorn
|
||||
CMD ["bash", "-c", "service nginx start && /code/entrypoint.sh"]
|
|
@ -1,4 +1,10 @@
|
|||
The MIT License (MIT)
|
||||
# Preface
|
||||
|
||||
AdventureLog uses DjRestAuth, a Django REST Framework authentication backend for Django Rest Framework. DjRestAuth is licensed under the MIT License.
|
||||
|
||||
---
|
||||
|
||||
## The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 iMerica https://github.com/iMerica/
|
||||
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
include AUTHORS
|
||||
include LICENSE
|
||||
include MANIFEST.in
|
||||
include README.md
|
||||
graft dj_rest_auth
|
|
@ -36,5 +36,7 @@ fi
|
|||
# Sync the countries and world travel regions
|
||||
python manage.py download-countries
|
||||
|
||||
# Start Django server
|
||||
python manage.py runserver 0.0.0.0:8000
|
||||
cat /code/adventurelog.txt
|
||||
|
||||
# Start gunicorn
|
||||
gunicorn main.wsgi:application --bind 0.0.0.0:8000
|
38
backend/nginx.conf
Normal file
38
backend/nginx.conf
Normal file
|
@ -0,0 +1,38 @@
|
|||
worker_processes 1;
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
upstream django {
|
||||
server server:8000; # Use the internal Docker networking
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80; # NGINX always listens on port 80 inside the container
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
proxy_pass http://server:8000; # Explicitly forward to Django service
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
|
||||
location /static/ {
|
||||
alias /code/staticfiles/; # Serve static files directly
|
||||
}
|
||||
|
||||
location /media/ {
|
||||
alias /code/media/; # Serve media files directly
|
||||
}
|
||||
}
|
||||
}
|
7
backend/server/adventurelog.txt
Normal file
7
backend/server/adventurelog.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
█████╗ ██████╗ ██╗ ██╗███████╗███╗ ██╗████████╗██╗ ██╗██████╗ ███████╗██╗ ██████╗ ██████╗
|
||||
██╔══██╗██╔══██╗██║ ██║██╔════╝████╗ ██║╚══██╔══╝██║ ██║██╔══██╗██╔════╝██║ ██╔═══██╗██╔════╝
|
||||
███████║██║ ██║██║ ██║█████╗ ██╔██╗ ██║ ██║ ██║ ██║██████╔╝█████╗ ██║ ██║ ██║██║ ███╗
|
||||
██╔══██║██║ ██║╚██╗ ██╔╝██╔══╝ ██║╚██╗██║ ██║ ██║ ██║██╔══██╗██╔══╝ ██║ ██║ ██║██║ ██║
|
||||
██║ ██║██████╔╝ ╚████╔╝ ███████╗██║ ╚████║ ██║ ╚██████╔╝██║ ██║███████╗███████╗╚██████╔╝╚██████╔╝
|
||||
╚═╝ ╚═╝╚═════╝ ╚═══╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚══════╝ ╚═════╝ ╚═════╝
|
||||
“The world is full of wonderful things you haven't seen yet. Don't ever give up on the chance of seeing them.” - J.K. Rowling
|
|
@ -90,10 +90,6 @@ class Adventure(models.Model):
|
|||
# end_date = models.DateField(blank=True, null=True)
|
||||
|
||||
def clean(self):
|
||||
if self.date and self.end_date and self.date > self.end_date:
|
||||
raise ValidationError('The start date must be before the end date. Start date: ' + str(self.date) + ' End date: ' + str(self.end_date))
|
||||
if self.end_date and not self.date:
|
||||
raise ValidationError('Adventures must have an end date. Adventure: ' + self.name)
|
||||
if self.collection:
|
||||
if self.collection.is_public and not self.is_public:
|
||||
raise ValidationError('Adventures associated with a public collection must be public. Collection: ' + self.trip.name + ' Adventure: ' + self.name)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from django.utils import timezone
|
||||
import os
|
||||
from .models import Adventure, AdventureImage, ChecklistItem, Collection, Note, Transportation, Checklist, Visit
|
||||
from rest_framework import serializers
|
||||
|
@ -19,6 +20,7 @@ class AdventureImageSerializer(serializers.ModelSerializer):
|
|||
return representation
|
||||
|
||||
class VisitSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Visit
|
||||
fields = ['id', 'start_date', 'end_date', 'notes']
|
||||
|
@ -27,10 +29,21 @@ class VisitSerializer(serializers.ModelSerializer):
|
|||
class AdventureSerializer(serializers.ModelSerializer):
|
||||
images = AdventureImageSerializer(many=True, read_only=True)
|
||||
visits = VisitSerializer(many=True, read_only=False)
|
||||
is_visited = serializers.SerializerMethodField()
|
||||
class Meta:
|
||||
model = Adventure
|
||||
fields = ['id', 'user_id', 'name', 'description', 'rating', 'activity_types', 'location', 'is_public', 'collection', 'created_at', 'updated_at', 'images', 'link', 'type', 'longitude', 'latitude', 'visits']
|
||||
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id']
|
||||
fields = ['id', 'user_id', 'name', 'description', 'rating', 'activity_types', 'location', 'is_public', 'collection', 'created_at', 'updated_at', 'images', 'link', 'type', 'longitude', 'latitude', 'visits', 'is_visited']
|
||||
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id', 'is_visited']
|
||||
|
||||
def get_is_visited(self, obj):
|
||||
current_date = timezone.now().date()
|
||||
for visit in obj.visits.all():
|
||||
if visit.start_date and visit.end_date and (visit.start_date <= current_date):
|
||||
return True
|
||||
elif visit.start_date and not visit.end_date and (visit.start_date <= current_date):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def to_representation(self, instance):
|
||||
representation = super().to_representation(instance)
|
||||
|
@ -187,3 +200,4 @@ class CollectionSerializer(serializers.ModelSerializer):
|
|||
shared_uuids.append(str(user.uuid))
|
||||
representation['shared_with'] = shared_uuids
|
||||
return representation
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
from django.urls import include, path
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import AdventureViewSet, ChecklistViewSet, CollectionViewSet, NoteViewSet, StatsViewSet, GenerateDescription, ActivityTypesView, TransportationViewSet, AdventureImageViewSet
|
||||
from .views import AdventureViewSet, ChecklistViewSet, CollectionViewSet, NoteViewSet, StatsViewSet, GenerateDescription, ActivityTypesView, TransportationViewSet, AdventureImageViewSet, ReverseGeocodeViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'adventures', AdventureViewSet, basename='adventures')
|
||||
|
@ -12,6 +12,7 @@ router.register(r'transportations', TransportationViewSet, basename='transportat
|
|||
router.register(r'notes', NoteViewSet, basename='notes')
|
||||
router.register(r'checklists', ChecklistViewSet, basename='checklists')
|
||||
router.register(r'images', AdventureImageViewSet, basename='images')
|
||||
router.register(r'reverse-geocode', ReverseGeocodeViewSet, basename='reverse-geocode')
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import json
|
||||
import uuid
|
||||
import requests
|
||||
from django.db import transaction
|
||||
|
@ -40,7 +41,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
|||
order_direction = self.request.query_params.get('order_direction', 'asc')
|
||||
include_collections = self.request.query_params.get('include_collections', 'true')
|
||||
|
||||
valid_order_by = ['name', 'type', 'date', 'rating', 'updated_at']
|
||||
valid_order_by = ['name', 'type', 'start_date', 'rating', 'updated_at']
|
||||
if order_by not in valid_order_by:
|
||||
order_by = 'name'
|
||||
|
||||
|
@ -104,7 +105,9 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
|||
@action(detail=False, methods=['get'])
|
||||
def filtered(self, request):
|
||||
types = request.query_params.get('types', '').split(',')
|
||||
# handle case where types is all
|
||||
is_visited = request.query_params.get('is_visited', 'all')
|
||||
|
||||
# Handle case where types is all
|
||||
if 'all' in types:
|
||||
types = [t[0] for t in ADVENTURE_TYPES]
|
||||
valid_types = [t[0] for t in ADVENTURE_TYPES]
|
||||
|
@ -113,14 +116,32 @@ class AdventureViewSet(viewsets.ModelViewSet):
|
|||
if not types:
|
||||
return Response({"error": "No valid types provided"}, status=400)
|
||||
|
||||
queryset = Adventure.objects.none()
|
||||
queryset = Adventure.objects.filter(
|
||||
type__in=types,
|
||||
user_id=request.user.id
|
||||
)
|
||||
|
||||
for adventure_type in types:
|
||||
if adventure_type in valid_types:
|
||||
queryset |= Adventure.objects.filter(
|
||||
type=adventure_type, user_id=request.user.id)
|
||||
# Handle is_visited filtering
|
||||
if is_visited.lower() == 'true':
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
filtered_ids = [
|
||||
adventure.id for adventure, serialized_adventure in zip(queryset, serializer.data)
|
||||
if serialized_adventure['is_visited']
|
||||
]
|
||||
queryset = queryset.filter(id__in=filtered_ids)
|
||||
elif is_visited.lower() == 'false':
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
filtered_ids = [
|
||||
adventure.id for adventure, serialized_adventure in zip(queryset, serializer.data)
|
||||
if not serialized_adventure['is_visited']
|
||||
]
|
||||
queryset = queryset.filter(id__in=filtered_ids)
|
||||
# If is_visited is 'all' or any other value, we don't apply additional filtering
|
||||
|
||||
# Apply sorting
|
||||
queryset = self.apply_sorting(queryset)
|
||||
|
||||
# Paginate and respond
|
||||
adventures = self.paginate_and_respond(queryset, request)
|
||||
return adventures
|
||||
|
||||
|
@ -1053,3 +1074,91 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
|
|||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(user_id=self.request.user)
|
||||
|
||||
class ReverseGeocodeViewSet(viewsets.ViewSet):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def extractIsoCode(self, data):
|
||||
"""
|
||||
Extract the ISO code from the response data.
|
||||
Returns a dictionary containing the region name, country name, and ISO code if found.
|
||||
"""
|
||||
iso_code = None
|
||||
town = None
|
||||
city = None
|
||||
county = None
|
||||
display_name = None
|
||||
country_code = None
|
||||
if 'address' in data.keys():
|
||||
keys = data['address'].keys()
|
||||
for key in keys:
|
||||
if key.find("ISO") != -1:
|
||||
iso_code = data['address'][key]
|
||||
if 'town' in keys:
|
||||
town = data['address']['town']
|
||||
if 'county' in keys:
|
||||
county = data['address']['county']
|
||||
if 'city' in keys:
|
||||
city = data['address']['city']
|
||||
if not iso_code:
|
||||
return {"error": "No region found"}
|
||||
region = Region.objects.filter(id=iso_code).first()
|
||||
visited_region = VisitedRegion.objects.filter(region=region).first()
|
||||
is_visited = False
|
||||
print(iso_code)
|
||||
country_code = iso_code[:2]
|
||||
|
||||
if city:
|
||||
display_name = f"{city}, {region.name}, {country_code}"
|
||||
elif town and region.name:
|
||||
display_name = f"{town}, {region.name}, {country_code}"
|
||||
elif county and region.name:
|
||||
display_name = f"{county}, {region.name}, {country_code}"
|
||||
|
||||
if visited_region:
|
||||
is_visited = True
|
||||
if region:
|
||||
return {"id": iso_code, "region": region.name, "country": region.country.name, "is_visited": is_visited, "display_name": display_name}
|
||||
return {"error": "No region found"}
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def reverse_geocode(self, request):
|
||||
lat = request.query_params.get('lat', '')
|
||||
lon = request.query_params.get('lon', '')
|
||||
url = f"https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat={lat}&lon={lon}"
|
||||
headers = {'User-Agent': 'AdventureLog Server'}
|
||||
response = requests.get(url, headers=headers)
|
||||
try:
|
||||
data = response.json()
|
||||
except requests.exceptions.JSONDecodeError:
|
||||
return Response({"error": "Invalid response from geocoding service"}, status=400)
|
||||
return Response(self.extractIsoCode(data))
|
||||
|
||||
@action(detail=False, methods=['post'])
|
||||
def mark_visited_region(self, request):
|
||||
# searches through all of the users adventures, if the serialized data is_visited, is true, runs reverse geocode on the adventures and if a region is found, marks it as visited. Use the extractIsoCode function to get the region
|
||||
new_region_count = 0
|
||||
new_regions = {}
|
||||
adventures = Adventure.objects.filter(user_id=self.request.user)
|
||||
serializer = AdventureSerializer(adventures, many=True)
|
||||
for adventure, serialized_adventure in zip(adventures, serializer.data):
|
||||
if serialized_adventure['is_visited'] == True:
|
||||
lat = adventure.latitude
|
||||
lon = adventure.longitude
|
||||
url = f"https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat={lat}&lon={lon}"
|
||||
headers = {'User-Agent': 'AdventureLog Server'}
|
||||
response = requests.get(url, headers=headers)
|
||||
try:
|
||||
data = response.json()
|
||||
except requests.exceptions.JSONDecodeError:
|
||||
return Response({"error": "Invalid response from geocoding service"}, status=400)
|
||||
region = self.extractIsoCode(data)
|
||||
if 'error' not in region:
|
||||
region = Region.objects.filter(id=region['id']).first()
|
||||
visited_region = VisitedRegion.objects.filter(region=region, user_id=self.request.user).first()
|
||||
if not visited_region:
|
||||
visited_region = VisitedRegion(region=region, user_id=self.request.user)
|
||||
visited_region.save()
|
||||
new_region_count += 1
|
||||
new_regions[region.id] = region.name
|
||||
return Response({"new_regions": new_region_count, "regions": new_regions})
|
|
@ -136,9 +136,8 @@ STATIC_ROOT = BASE_DIR / "staticfiles"
|
|||
STATIC_URL = '/static/'
|
||||
|
||||
MEDIA_URL = '/media/'
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
|
||||
|
||||
MEDIA_ROOT = BASE_DIR / 'media'
|
||||
STATICFILES_DIRS = [BASE_DIR / 'static']
|
||||
|
||||
# TEMPLATE_DIRS = [os.path.join(BASE_DIR, 'templates')]
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import os
|
|||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings")
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "main.settings")
|
||||
|
||||
application = get_wsgi_application()
|
||||
# add this vercel variable
|
||||
|
|
|
@ -13,3 +13,4 @@ whitenoise
|
|||
django-resized
|
||||
django-geojson
|
||||
setuptools
|
||||
gunicorn==23.0.0
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -7,15 +7,25 @@ class CountrySerializer(serializers.ModelSerializer):
|
|||
return os.environ.get('PUBLIC_URL', 'http://127.0.0.1:8000').rstrip('/').replace("'", "")
|
||||
|
||||
flag_url = serializers.SerializerMethodField()
|
||||
num_regions = serializers.SerializerMethodField()
|
||||
num_visits = serializers.SerializerMethodField()
|
||||
|
||||
def get_flag_url(self, obj):
|
||||
public_url = self.get_public_url(obj)
|
||||
return public_url + '/media/' + 'flags/' + obj.country_code.lower() + '.png'
|
||||
|
||||
def get_num_regions(self, obj):
|
||||
# get the number of regions in the country
|
||||
return Region.objects.filter(country=obj).count()
|
||||
|
||||
def get_num_visits(self, obj):
|
||||
return VisitedRegion.objects.filter(region__country=obj).count()
|
||||
|
||||
class Meta:
|
||||
model = Country
|
||||
fields = '__all__'
|
||||
read_only_fields = ['id', 'name', 'country_code', 'subregion', 'flag_url']
|
||||
read_only_fields = ['id', 'name', 'country_code', 'subregion', 'flag_url', 'num_regions', 'num_visits']
|
||||
|
||||
|
||||
class RegionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
from django.urls import include, path
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import CountryViewSet, RegionViewSet, VisitedRegionViewSet, regions_by_country, visits_by_country, GeoJSONView
|
||||
from .views import CountryViewSet, RegionViewSet, VisitedRegionViewSet, regions_by_country, visits_by_country
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'countries', CountryViewSet, basename='countries')
|
||||
|
@ -12,6 +12,5 @@ router.register(r'visitedregion', VisitedRegionViewSet, basename='visitedregion'
|
|||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
path('<str:country_code>/regions/', regions_by_country, name='regions-by-country'),
|
||||
path('<str:country_code>/visits/', visits_by_country, name='visits-by-country'),
|
||||
path('geojson/', GeoJSONView.as_view({'get': 'list'}), name='geojson'),
|
||||
path('<str:country_code>/visits/', visits_by_country, name='visits-by-country')
|
||||
]
|
||||
|
|
|
@ -95,38 +95,3 @@ class VisitedRegionViewSet(viewsets.ModelViewSet):
|
|||
self.perform_create(serializer)
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||
|
||||
|
||||
class GeoJSONView(viewsets.ViewSet):
|
||||
"""
|
||||
Combine all GeoJSON data from .json files in static/data into a single GeoJSON object.
|
||||
"""
|
||||
def list(self, request):
|
||||
combined_geojson = {
|
||||
"type": "FeatureCollection",
|
||||
"features": []
|
||||
}
|
||||
|
||||
# Use Django's static file finder to locate the 'data' directory
|
||||
data_dir = finders.find('data')
|
||||
|
||||
if not data_dir or not os.path.isdir(data_dir):
|
||||
return Response({"error": "Data directory does not exist."}, status=404)
|
||||
|
||||
for filename in os.listdir(data_dir):
|
||||
if filename.endswith('.json'):
|
||||
file_path = os.path.join(data_dir, filename)
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
json_data = json.load(f)
|
||||
# Check if the JSON data is GeoJSON
|
||||
if isinstance(json_data, dict) and "type" in json_data:
|
||||
if json_data["type"] == "FeatureCollection":
|
||||
combined_geojson["features"].extend(json_data.get("features", []))
|
||||
elif json_data["type"] == "Feature":
|
||||
combined_geojson["features"].append(json_data)
|
||||
# You can add more conditions here for other GeoJSON types if needed
|
||||
except (IOError, json.JSONDecodeError) as e:
|
||||
return Response({"error": f"Error reading file {filename}: {str(e)}"}, status=500)
|
||||
|
||||
return Response(combined_geojson)
|
|
@ -43,20 +43,6 @@ services:
|
|||
- "traefik.http.routers.adventurelog.tls=true"
|
||||
- "traefik.http.routers.adventurelog.tls.certresolver=letsencrypt"
|
||||
|
||||
nginx:
|
||||
image: nginx:latest
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.nginx.entrypoints=websecure"
|
||||
- "traefik.http.routers.nginx.rule=Host(`yourdomain.com`) && PathPrefix(`/media`)" # Replace with your domain
|
||||
- "traefik.http.routers.nginx.tls=true"
|
||||
- "traefik.http.routers.nginx.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.middlewares.nginx-stripprefix.stripprefix.prefixes=/media"
|
||||
- "traefik.http.routers.nginx.middlewares=nginx-stripprefix"
|
||||
volumes:
|
||||
- adventurelog-media:/usr/share/nginx/html
|
||||
|
||||
server:
|
||||
image: ghcr.io/seanmorley15/adventurelog-backend:latest
|
||||
restart: unless-stopped
|
||||
|
|
|
@ -5,11 +5,11 @@ services:
|
|||
container_name: adventurelog-frontend
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PUBLIC_SERVER_URL=http://server:8000 # MOST DOCKER USERS WILL NEVER NEED TO CHANGE THIS, EVEN IF YOU CHANGE THE PORTS
|
||||
- ORIGIN=http://localhost:8080
|
||||
- BODY_SIZE_LIMIT=Infinity # This is measured in bytes
|
||||
- PUBLIC_SERVER_URL=http://server:8000 # MOST DOCKER USERS WILL NOT NEED TO CHANGE THIS EVER EVEN IF YOU CHANGE THE OUTWARD PORT
|
||||
- ORIGIN=http://localhost:8015
|
||||
- BODY_SIZE_LIMIT=Infinity
|
||||
ports:
|
||||
- "8080:3000"
|
||||
- "8015:3000"
|
||||
depends_on:
|
||||
- server
|
||||
|
||||
|
@ -33,35 +33,22 @@ services:
|
|||
- PGHOST=db
|
||||
- PGDATABASE=database
|
||||
- PGUSER=adventure
|
||||
- PGPASSWORD=changeme123 # This should be the same as the POSTGRES_PASSWORD in the db service
|
||||
- PGPASSWORD=changeme123
|
||||
- SECRET_KEY=changeme123
|
||||
- DJANGO_ADMIN_USERNAME=admin
|
||||
- DJANGO_ADMIN_PASSWORD=admin
|
||||
- DJANGO_ADMIN_EMAIL=admin@example.com
|
||||
- PUBLIC_URL='http://localhost:81' # NOTE: THIS IS THE PUBLIC URL TO THE **NGINX** SERVER USED FOR MEDIA FILES!
|
||||
- CSRF_TRUSTED_ORIGINS=https://api.adventurelog.app,https://adventurelog.app # This is a comma separated list of trusted origins for CSRF, this should include where your frontend is hosted.
|
||||
- PUBLIC_URL='http://localhost:8016' # Match the outward port, used for the creation of image urls
|
||||
- CSRF_TRUSTED_ORIGINS=http://localhost:8016 # Comma separated list of trusted origins for CSRF
|
||||
- DEBUG=False
|
||||
- FRONTEND_URL='http://localhost:8080' # This is the URL of the frontend server
|
||||
#- DISABLE_REGISTRATION=True
|
||||
- FRONTEND_URL='http://localhost:8015' # Used for email generation. This should be the url of the frontend
|
||||
ports:
|
||||
- "8000:8000"
|
||||
- "8016:80"
|
||||
depends_on:
|
||||
- db
|
||||
volumes:
|
||||
- adventurelog_media:/code/media/
|
||||
|
||||
nginx:
|
||||
image: nginx:latest
|
||||
container_name: adventurelog-nginx
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "81:80"
|
||||
volumes:
|
||||
- adventurelog_media:/app/media
|
||||
- ./proxy/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
depends_on:
|
||||
- server
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
adventurelog_media:
|
||||
|
|
28
documentation/docs/Configuration/email_backend.md
Normal file
28
documentation/docs/Configuration/email_backend.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Change Email Backend
|
||||
|
||||
To change the email backend, you can set the following variable in your docker-compose.yml under the server service:
|
||||
|
||||
## Using Console (default)
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- EMAIL_BACKEND='console'
|
||||
```
|
||||
|
||||
## With SMTP
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- EMAIL_BACKEND='email'
|
||||
- EMAIL_HOST='smtp.gmail.com'
|
||||
- EMAIL_USE_TLS=False
|
||||
- EMAIL_PORT=587
|
||||
- EMAIL_USE_SSL=True
|
||||
- EMAIL_HOST_USER='user'
|
||||
- EMAIL_HOST_PASSWORD='password'
|
||||
- DEFAULT_FROM_EMAIL='user@example.com'
|
||||
```
|
14
documentation/docs/Configuration/umami_analytics.md
Normal file
14
documentation/docs/Configuration/umami_analytics.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Umami Analytics (optional)
|
||||
|
||||
Umami Analytics is a free, open-source, and privacy-focused web analytics tool that can be used as an alternative to Google Analytics. Learn more about Umami Analytics [here](https://umami.is/).
|
||||
|
||||
To enable Umami Analytics for your AdventureLog instance, you can set the following variables in your `docker-compose.yml` under the `web` service:
|
||||
|
||||
```yaml
|
||||
PUBLIC_UMAMI_SRC=https://cloud.umami.is/script.js # If you are using the hosted version of Umami
|
||||
PUBLIC_UMAMI_WEBSITE_ID=
|
||||
```
|
8
documentation/docs/Guides/_category_.json
Normal file
8
documentation/docs/Guides/_category_.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"label": "Guides 📚",
|
||||
"position": 4,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Guides for AdventureLog."
|
||||
}
|
||||
}
|
37
documentation/docs/Guides/nginx_migration.md
Normal file
37
documentation/docs/Guides/nginx_migration.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# AdventureLog v0.7.1 Migration
|
||||
|
||||
In order to make installation easier, the AdventureLog v0.7.1 release has **removed the need for a seperate nginx container** and cofig to serve the media files. Instead, the media files are now served by an instance of nginx running in the same container as the Django application.
|
||||
|
||||
## Docker Compose Changes
|
||||
|
||||
:::note
|
||||
|
||||
You can also just use the new `docker-compose.yml` file in the repository and change the environment variables to match your setup.
|
||||
|
||||
:::
|
||||
|
||||
1. Remove the `nginx` service from your `docker-compose.yml` file.
|
||||
2. Update the `PUBLIC_URL` environment variable in the `server` service (backend) to match the address of your **server**, instead of the previous nginx instance. For example, if your server is exposed to `http://localhost:8000`, set `PUBLIC_URL` to `http://localhost:8000`. If you are using a domain name, set `PUBLIC_URL` to `https://api.yourdomain.com` as an example.
|
||||
3. Change port mapping for the `server` service. Right now it probably looks like this:
|
||||
```yaml
|
||||
ports:
|
||||
- "your-exposed-port:8000"
|
||||
```
|
||||
Change it to:
|
||||
```yaml
|
||||
ports:
|
||||
- "your-exposed-port:80"
|
||||
```
|
||||
This is because the nginx instance in the container is now serving the Django application on port 80. The port on the left side of the colon is the port on your host machine and this can be changed to whatever you want. The port on the right side of the colon is the port the Django application is running on in the container and should not be changed.
|
||||
|
||||
That's it! You should now be able to run the application with the new configuration! This update also includes some performance enhancements so there should be a slight speed increase as well, especially with multiple users.
|
||||
|
||||
Enjoy the new version of AdventureLog! 🎉
|
||||
|
||||
View the full changelog [here](https://github.com/seanmorley15/AdventureLog/releases/tag/v0.7.1)
|
||||
|
||||
Report any bugs [GitHub repository](https://github.com/seanmorley15/adventurelog) or ask for help in the [Discord server](https://discord.gg/wRbQ9Egr8C).
|
|
@ -30,13 +30,13 @@ Here is a summary of the configuration options available in the `docker-compose.
|
|||
| Name | Required | Description | Default Value |
|
||||
| ------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
|
||||
| `PUBLIC_SERVER_URL` | Yes | What the frontend SSR server uses to connect to the backend. | http://server:8000 |
|
||||
| `ORIGIN` | Sometimes | Not needed if using HTTPS. If not, set it to the domain of what you will acess the app from. | http://localhost:8080 |
|
||||
| `ORIGIN` | Sometimes | Not needed if using HTTPS. If not, set it to the domain of what you will acess the app from. | http://localhost:8015 |
|
||||
| `BODY_SIZE_LIMIT` | Yes | Used to set the maximum upload size to the server. Should be changed to prevent someone from uploading too much! Custom values must be set in **kiliobytes**. | Infinity |
|
||||
|
||||
### Backend Container (server)
|
||||
|
||||
| Name | Required | Description | Default Value |
|
||||
| ----------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
|
||||
| ----------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
|
||||
| `PGHOST` | Yes | Databse host. | db |
|
||||
| `PGDATABASE` | Yes | Database. | database |
|
||||
| `PGUSER` | Yes | Database user. | adventure |
|
||||
|
@ -44,28 +44,9 @@ Here is a summary of the configuration options available in the `docker-compose.
|
|||
| `DJANGO_ADMIN_USERNAME` | Yes | Default username. | admin |
|
||||
| `DJANGO_ADMIN_PASSWORD` | Yes | Default password, change after inital login. | admin |
|
||||
| `DJANGO_ADMIN_EMAIL` | Yes | Default user's email. | admin@example.com |
|
||||
| `PUBLIC_URL` | Yes | This is the publically accessible url to the **nginx** container. You should be able to acess nginx from this url where you access your app. | http://127.0.0.1:81 |
|
||||
| `CSRF_TRUSTED_ORIGINS` | Yes | Need to be changed to the orgins where you use your backend server and frontend. These values are comma seperated. | Needs to be changed. |
|
||||
| `FRONTEND_URL` | Yes | This is the publically accessible url to the **frontend** container. This link should be accessable for all users. Used for email generation. | http://localhost:3000 |
|
||||
|
||||
### Proxy Container (nginx) Configuration
|
||||
|
||||
In order to use media files in a production environment, you need to configure the `nginx` container to serve the media files. The container is already in the docker compose file but you need to do a few things to make it work.
|
||||
|
||||
1. Create a directory called `proxy` in the same directory as the `docker-compose.yml` file.
|
||||
2. Create a file called `nginx.conf` in the `proxy` directory.
|
||||
3. Add the following configuration to the `nginx.conf` file:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
location /media/ {
|
||||
alias /app/media/;
|
||||
}
|
||||
}
|
||||
```
|
||||
| `PUBLIC_URL` | Yes | This needs to match the outward port of the server and be accessible from where the app is used. It is used for the creation of image urls. | 'http://localhost:8016' |
|
||||
| `CSRF_TRUSTED_ORIGINS` | Yes | Need to be changed to the orgins where you use your backend server and frontend. These values are comma seperated. | http://localhost:8016 |
|
||||
| `FRONTEND_URL` | Yes | This is the publically accessible url to the **frontend** container. This link should be accessable for all users. Used for email generation. | 'http://localhost:8015' |
|
||||
|
||||
## Running the Containers
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { Config } from "@docusaurus/types";
|
|||
import type * as Preset from "@docusaurus/preset-classic";
|
||||
|
||||
const config: Config = {
|
||||
title: "Adventure Log",
|
||||
title: "AdventureLog",
|
||||
tagline: "Embark, Explore, Remember. 🗺️",
|
||||
favicon: "img/favicon.png",
|
||||
|
||||
|
@ -15,8 +15,8 @@ const config: Config = {
|
|||
|
||||
// GitHub pages deployment config.
|
||||
// If you aren't using GitHub pages, you don't need these.
|
||||
organizationName: "facebook", // Usually your GitHub org/user name.
|
||||
projectName: "docusaurus", // Usually your repo name.
|
||||
organizationName: "seanmorley", // Usually your GitHub org/user name.
|
||||
projectName: "adventurelog", // Usually your repo name.
|
||||
|
||||
onBrokenLinks: "throw",
|
||||
onBrokenMarkdownLinks: "warn",
|
||||
|
@ -40,7 +40,7 @@ const config: Config = {
|
|||
// Please change this to your repo.
|
||||
// Remove this to remove the "edit this page" links.
|
||||
editUrl:
|
||||
"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/",
|
||||
"https://github.com/seanmorley15/AdventureLog/tree/main/documentation",
|
||||
},
|
||||
// blog: {
|
||||
// showReadingTime: true,
|
||||
|
@ -65,9 +65,9 @@ const config: Config = {
|
|||
// Replace with your project's social card
|
||||
image: "img/docusaurus-social-card.jpg",
|
||||
navbar: {
|
||||
title: "Adventure Log Docs",
|
||||
title: "AdventureLog Docs",
|
||||
logo: {
|
||||
alt: "My Site Logo",
|
||||
alt: "AdventureLog Logo",
|
||||
src: "img/favicon.png",
|
||||
},
|
||||
items: [
|
||||
|
@ -77,12 +77,22 @@ const config: Config = {
|
|||
position: "left",
|
||||
label: "Documentation",
|
||||
},
|
||||
{
|
||||
to: "https://www.buymeacoffee.com/seanmorley15",
|
||||
label: "Sponsor 💖",
|
||||
position: "right",
|
||||
},
|
||||
// { to: "/blog", label: "Blog", position: "left" },
|
||||
{
|
||||
href: "https://github.com/seanmorley15/adventurelog",
|
||||
to: "https://github.com/seanmorley15/adventurelog",
|
||||
label: "GitHub",
|
||||
position: "right",
|
||||
},
|
||||
{
|
||||
to: "https://discord.gg/wRbQ9Egr8C",
|
||||
label: "Discord",
|
||||
position: "right",
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
|
@ -108,6 +118,10 @@ const config: Config = {
|
|||
label: "GitHub",
|
||||
href: "https://github.com/seanmorley15/adventurelog",
|
||||
},
|
||||
{
|
||||
label: "Discord",
|
||||
href: "https://discord.gg/wRbQ9Egr8C",
|
||||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "my-website",
|
||||
"version": "0.0.0",
|
||||
"name": "adventurelog-docs",
|
||||
"version": "0.7.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
|
||||
import type { SidebarsConfig } from "@docusaurus/plugin-content-docs";
|
||||
|
||||
/**
|
||||
* Creating a sidebar enables you to:
|
||||
|
@ -12,7 +12,7 @@ import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
|
|||
*/
|
||||
const sidebars: SidebarsConfig = {
|
||||
// By default, Docusaurus generates a sidebar from the docs folder structure
|
||||
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
|
||||
tutorialSidebar: [{ type: "autogenerated", dirName: "." }],
|
||||
|
||||
// But you can create a sidebar manually
|
||||
/*
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 87 KiB |
|
@ -1,2 +1,7 @@
|
|||
PUBLIC_SERVER_URL=http://127.0.0.1:8000
|
||||
BODY_SIZE_LIMIT=Infinity
|
||||
|
||||
|
||||
# OPTIONAL VARIABLES FOR UMAMI ANALYTICS
|
||||
PUBLIC_UMAMI_SRC=
|
||||
PUBLIC_UMAMI_WEBSITE_ID=
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "adventurelog-frontend",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.1",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"django": "cd .. && cd backend/server && python3 manage.py runserver",
|
||||
|
@ -35,6 +35,8 @@
|
|||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@lukulent/svelte-umami": "^0.0.3",
|
||||
"svelte-i18n": "^4.0.1",
|
||||
"svelte-maplibre": "^0.9.8"
|
||||
}
|
||||
}
|
||||
|
|
453
frontend/pnpm-lock.yaml
generated
453
frontend/pnpm-lock.yaml
generated
|
@ -8,6 +8,12 @@ importers:
|
|||
|
||||
.:
|
||||
dependencies:
|
||||
'@lukulent/svelte-umami':
|
||||
specifier: ^0.0.3
|
||||
version: 0.0.3(svelte@4.2.19)
|
||||
svelte-i18n:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1(svelte@4.2.19)
|
||||
svelte-maplibre:
|
||||
specifier: ^0.9.8
|
||||
version: 0.9.8(svelte@4.2.19)
|
||||
|
@ -92,144 +98,297 @@ packages:
|
|||
'@antfu/utils@0.7.8':
|
||||
resolution: {integrity: sha512-rWQkqXRESdjXtc+7NRfK9lASQjpXJu1ayp7qi1d23zZorY+wBHVLHHoVcMsEnkqEBWTFqbztO7/QdJFzyEcLTg==}
|
||||
|
||||
'@esbuild/aix-ppc64@0.19.12':
|
||||
resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/aix-ppc64@0.21.5':
|
||||
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/android-arm64@0.19.12':
|
||||
resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.19.12':
|
||||
resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.21.5':
|
||||
resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.19.12':
|
||||
resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.21.5':
|
||||
resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/darwin-arm64@0.19.12':
|
||||
resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.19.12':
|
||||
resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.21.5':
|
||||
resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.19.12':
|
||||
resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.19.12':
|
||||
resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.21.5':
|
||||
resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/linux-arm64@0.19.12':
|
||||
resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.19.12':
|
||||
resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.21.5':
|
||||
resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.19.12':
|
||||
resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.21.5':
|
||||
resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.19.12':
|
||||
resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.21.5':
|
||||
resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.19.12':
|
||||
resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.21.5':
|
||||
resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.19.12':
|
||||
resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.21.5':
|
||||
resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.19.12':
|
||||
resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.21.5':
|
||||
resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.19.12':
|
||||
resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.21.5':
|
||||
resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.19.12':
|
||||
resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.21.5':
|
||||
resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/netbsd-x64@0.19.12':
|
||||
resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-x64@0.21.5':
|
||||
resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.19.12':
|
||||
resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.21.5':
|
||||
resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/sunos-x64@0.19.12':
|
||||
resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/sunos-x64@0.21.5':
|
||||
resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/win32-arm64@0.19.12':
|
||||
resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.19.12':
|
||||
resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.21.5':
|
||||
resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.19.12':
|
||||
resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.21.5':
|
||||
resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@formatjs/ecma402-abstract@2.2.1':
|
||||
resolution: {integrity: sha512-O4ywpkdJybrjFc9zyL8qK5aklleIAi5O4nYhBVJaOFtCkNrnU+lKFeJOFC48zpsZQmR8Aok2V79hGpHnzbmFpg==}
|
||||
|
||||
'@formatjs/fast-memoize@2.2.2':
|
||||
resolution: {integrity: sha512-mzxZcS0g1pOzwZTslJOBTmLzDXseMLLvnh25ymRilCm8QLMObsQ7x/rj9GNrH0iUhZMlFisVOD6J1n6WQqpKPQ==}
|
||||
|
||||
'@formatjs/icu-messageformat-parser@2.9.1':
|
||||
resolution: {integrity: sha512-7AYk4tjnLi5wBkxst2w7qFj38JLMJoqzj7BhdEl7oTlsWMlqwgx4p9oMmmvpXWTSDGNwOKBRc1SfwMh5MOHeNg==}
|
||||
|
||||
'@formatjs/icu-skeleton-parser@1.8.5':
|
||||
resolution: {integrity: sha512-zRZ/e3B5qY2+JCLs7puTzWS1Jb+t/K+8Jur/gEZpA2EjWeLDE17nsx8thyo9P48Mno7UmafnPupV2NCJXX17Dg==}
|
||||
|
||||
'@formatjs/intl-localematcher@0.5.6':
|
||||
resolution: {integrity: sha512-roz1+Ba5e23AHX6KUAWmLEyTRZegM5YDuxuvkHCyK3RJddf/UXB2f+s7pOMm9ktfPGla0g+mQXOn5vsuYirnaA==}
|
||||
|
||||
'@iconify-json/mdi@1.1.67':
|
||||
resolution: {integrity: sha512-00nllHES8hyACwIfgySlQgAE6MKgpr2wsKfpifMiZWZ9aXC5l4Jb0lR3lJSWwXgOW6kzAOdzC3T+2VOfBBZ13A==}
|
||||
|
||||
|
@ -265,6 +424,11 @@ packages:
|
|||
resolution: {integrity: sha512-f5DRIOZf7wxogefH03RjMPMdBF7ADTWUMoOs9kaJo06EfwF+aFhMZMDZxHg/Xe12hptN9xoZjGso2fdjapBRIA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@lukulent/svelte-umami@0.0.3':
|
||||
resolution: {integrity: sha512-4pL0sJapfy14yDj6CyZgewbRDadRoBJtk/dLqCJh7/tQuX7HO4hviBzhrVa4Osxaq2kcGEKdpkhAKAoaNdlNSA==}
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
|
||||
'@mapbox/geojson-rewind@0.5.2':
|
||||
resolution: {integrity: sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==}
|
||||
hasBin: true
|
||||
|
@ -660,6 +824,10 @@ packages:
|
|||
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
cli-color@2.0.4:
|
||||
resolution: {integrity: sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
||||
code-red@1.0.4:
|
||||
resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==}
|
||||
|
||||
|
@ -722,6 +890,10 @@ packages:
|
|||
resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
d@1.0.2:
|
||||
resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
daisyui@4.12.6:
|
||||
resolution: {integrity: sha512-Tz/rvi2ws7+7uh51JgGpsRqnASwI13t6Sz53ePaGkhLzhr4SQI4wwNxSypE8lj/d4gl/+lbHK1phIKUo+d2YNw==}
|
||||
engines: {node: '>=16.9.0'}
|
||||
|
@ -778,9 +950,28 @@ packages:
|
|||
emoji-regex@9.2.2:
|
||||
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
||||
|
||||
es5-ext@0.10.64:
|
||||
resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
||||
es6-iterator@2.0.3:
|
||||
resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==}
|
||||
|
||||
es6-promise@3.3.1:
|
||||
resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==}
|
||||
|
||||
es6-symbol@3.1.4:
|
||||
resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
es6-weak-map@2.0.3:
|
||||
resolution: {integrity: sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==}
|
||||
|
||||
esbuild@0.19.12:
|
||||
resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==}
|
||||
engines: {node: '>=12'}
|
||||
hasBin: true
|
||||
|
||||
esbuild@0.21.5:
|
||||
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
|
||||
engines: {node: '>=12'}
|
||||
|
@ -793,16 +984,26 @@ packages:
|
|||
esm-env@1.0.0:
|
||||
resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==}
|
||||
|
||||
esniff@2.0.1:
|
||||
resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
||||
estree-walker@2.0.2:
|
||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||
|
||||
estree-walker@3.0.3:
|
||||
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||
|
||||
event-emitter@0.3.5:
|
||||
resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==}
|
||||
|
||||
execa@5.1.1:
|
||||
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
ext@1.7.0:
|
||||
resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==}
|
||||
|
||||
extend-shallow@2.0.1:
|
||||
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -945,6 +1146,9 @@ packages:
|
|||
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
intl-messageformat@10.7.3:
|
||||
resolution: {integrity: sha512-AAo/3oyh7ROfPhDuh7DxTTydh97OC+lv7h1Eq5LuHWuLsUMKOhtzTYuyXlUReuwZ9vANDHo4CS1bGRrn7TZRtg==}
|
||||
|
||||
is-binary-path@2.1.0:
|
||||
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -988,6 +1192,9 @@ packages:
|
|||
resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-promise@2.2.2:
|
||||
resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==}
|
||||
|
||||
is-reference@1.2.1:
|
||||
resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
|
||||
|
||||
|
@ -1071,6 +1278,9 @@ packages:
|
|||
resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==}
|
||||
engines: {node: 14 || >=16.14}
|
||||
|
||||
lru-queue@0.1.0:
|
||||
resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==}
|
||||
|
||||
magic-string@0.30.10:
|
||||
resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==}
|
||||
|
||||
|
@ -1085,6 +1295,10 @@ packages:
|
|||
mdn-data@2.0.30:
|
||||
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
|
||||
|
||||
memoizee@0.4.17:
|
||||
resolution: {integrity: sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
merge-stream@2.0.0:
|
||||
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
||||
|
||||
|
@ -1164,6 +1378,9 @@ packages:
|
|||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
next-tick@1.1.0:
|
||||
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
|
||||
|
||||
node-fetch@2.7.0:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
|
@ -1538,6 +1755,13 @@ packages:
|
|||
peerDependencies:
|
||||
svelte: ^3.19.0 || ^4.0.0
|
||||
|
||||
svelte-i18n@4.0.1:
|
||||
resolution: {integrity: sha512-jaykGlGT5PUaaq04JWbJREvivlCnALtT+m87Kbm0fxyYHynkQaxQMnIKHLm2WeIuBRoljzwgyvz0Z6/CMwfdmQ==}
|
||||
engines: {node: '>= 16'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
svelte: ^3 || ^4 || ^5
|
||||
|
||||
svelte-maplibre@0.9.8:
|
||||
resolution: {integrity: sha512-z6YyJv1sT8AHJuzuzd+30M9PQMllFnGBpHvSJ5BlwFQF/yP4xdJY9+ynF9ziywJIzGMjuvTiCeEXZSY0fhsTAA==}
|
||||
peerDependencies:
|
||||
|
@ -1610,6 +1834,10 @@ packages:
|
|||
thenify@3.3.1:
|
||||
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
|
||||
|
||||
timers-ext@0.1.8:
|
||||
resolution: {integrity: sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
tiny-glob@0.2.9:
|
||||
resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==}
|
||||
|
||||
|
@ -1637,6 +1865,9 @@ packages:
|
|||
resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
type@2.7.3:
|
||||
resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
|
||||
|
||||
typescript@5.5.2:
|
||||
resolution: {integrity: sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==}
|
||||
engines: {node: '>=14.17'}
|
||||
|
@ -1798,75 +2029,169 @@ snapshots:
|
|||
|
||||
'@antfu/utils@0.7.8': {}
|
||||
|
||||
'@esbuild/aix-ppc64@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/aix-ppc64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.19.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.21.5':
|
||||
optional: true
|
||||
|
||||
'@formatjs/ecma402-abstract@2.2.1':
|
||||
dependencies:
|
||||
'@formatjs/fast-memoize': 2.2.2
|
||||
'@formatjs/intl-localematcher': 0.5.6
|
||||
tslib: 2.6.3
|
||||
|
||||
'@formatjs/fast-memoize@2.2.2':
|
||||
dependencies:
|
||||
tslib: 2.6.3
|
||||
|
||||
'@formatjs/icu-messageformat-parser@2.9.1':
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 2.2.1
|
||||
'@formatjs/icu-skeleton-parser': 1.8.5
|
||||
tslib: 2.6.3
|
||||
|
||||
'@formatjs/icu-skeleton-parser@1.8.5':
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 2.2.1
|
||||
tslib: 2.6.3
|
||||
|
||||
'@formatjs/intl-localematcher@0.5.6':
|
||||
dependencies:
|
||||
tslib: 2.6.3
|
||||
|
||||
'@iconify-json/mdi@1.1.67':
|
||||
dependencies:
|
||||
'@iconify/types': 2.0.0
|
||||
|
@ -1918,6 +2243,10 @@ snapshots:
|
|||
string-argv: 0.3.2
|
||||
type-detect: 4.0.8
|
||||
|
||||
'@lukulent/svelte-umami@0.0.3(svelte@4.2.19)':
|
||||
dependencies:
|
||||
svelte: 4.2.19
|
||||
|
||||
'@mapbox/geojson-rewind@0.5.2':
|
||||
dependencies:
|
||||
get-stream: 6.0.1
|
||||
|
@ -2323,6 +2652,14 @@ snapshots:
|
|||
|
||||
chownr@2.0.0: {}
|
||||
|
||||
cli-color@2.0.4:
|
||||
dependencies:
|
||||
d: 1.0.2
|
||||
es5-ext: 0.10.64
|
||||
es6-iterator: 2.0.3
|
||||
memoizee: 0.4.17
|
||||
timers-ext: 0.1.8
|
||||
|
||||
code-red@1.0.4:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
|
@ -2379,6 +2716,11 @@ snapshots:
|
|||
dependencies:
|
||||
d3-array: 3.2.4
|
||||
|
||||
d@1.0.2:
|
||||
dependencies:
|
||||
es5-ext: 0.10.64
|
||||
type: 2.7.3
|
||||
|
||||
daisyui@4.12.6(postcss@8.4.38):
|
||||
dependencies:
|
||||
css-selector-tokenizer: 0.8.0
|
||||
|
@ -2418,8 +2760,59 @@ snapshots:
|
|||
|
||||
emoji-regex@9.2.2: {}
|
||||
|
||||
es5-ext@0.10.64:
|
||||
dependencies:
|
||||
es6-iterator: 2.0.3
|
||||
es6-symbol: 3.1.4
|
||||
esniff: 2.0.1
|
||||
next-tick: 1.1.0
|
||||
|
||||
es6-iterator@2.0.3:
|
||||
dependencies:
|
||||
d: 1.0.2
|
||||
es5-ext: 0.10.64
|
||||
es6-symbol: 3.1.4
|
||||
|
||||
es6-promise@3.3.1: {}
|
||||
|
||||
es6-symbol@3.1.4:
|
||||
dependencies:
|
||||
d: 1.0.2
|
||||
ext: 1.7.0
|
||||
|
||||
es6-weak-map@2.0.3:
|
||||
dependencies:
|
||||
d: 1.0.2
|
||||
es5-ext: 0.10.64
|
||||
es6-iterator: 2.0.3
|
||||
es6-symbol: 3.1.4
|
||||
|
||||
esbuild@0.19.12:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.19.12
|
||||
'@esbuild/android-arm': 0.19.12
|
||||
'@esbuild/android-arm64': 0.19.12
|
||||
'@esbuild/android-x64': 0.19.12
|
||||
'@esbuild/darwin-arm64': 0.19.12
|
||||
'@esbuild/darwin-x64': 0.19.12
|
||||
'@esbuild/freebsd-arm64': 0.19.12
|
||||
'@esbuild/freebsd-x64': 0.19.12
|
||||
'@esbuild/linux-arm': 0.19.12
|
||||
'@esbuild/linux-arm64': 0.19.12
|
||||
'@esbuild/linux-ia32': 0.19.12
|
||||
'@esbuild/linux-loong64': 0.19.12
|
||||
'@esbuild/linux-mips64el': 0.19.12
|
||||
'@esbuild/linux-ppc64': 0.19.12
|
||||
'@esbuild/linux-riscv64': 0.19.12
|
||||
'@esbuild/linux-s390x': 0.19.12
|
||||
'@esbuild/linux-x64': 0.19.12
|
||||
'@esbuild/netbsd-x64': 0.19.12
|
||||
'@esbuild/openbsd-x64': 0.19.12
|
||||
'@esbuild/sunos-x64': 0.19.12
|
||||
'@esbuild/win32-arm64': 0.19.12
|
||||
'@esbuild/win32-ia32': 0.19.12
|
||||
'@esbuild/win32-x64': 0.19.12
|
||||
|
||||
esbuild@0.21.5:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.21.5
|
||||
|
@ -2450,12 +2843,24 @@ snapshots:
|
|||
|
||||
esm-env@1.0.0: {}
|
||||
|
||||
esniff@2.0.1:
|
||||
dependencies:
|
||||
d: 1.0.2
|
||||
es5-ext: 0.10.64
|
||||
event-emitter: 0.3.5
|
||||
type: 2.7.3
|
||||
|
||||
estree-walker@2.0.2: {}
|
||||
|
||||
estree-walker@3.0.3:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.6
|
||||
|
||||
event-emitter@0.3.5:
|
||||
dependencies:
|
||||
d: 1.0.2
|
||||
es5-ext: 0.10.64
|
||||
|
||||
execa@5.1.1:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.3
|
||||
|
@ -2468,6 +2873,10 @@ snapshots:
|
|||
signal-exit: 3.0.7
|
||||
strip-final-newline: 2.0.0
|
||||
|
||||
ext@1.7.0:
|
||||
dependencies:
|
||||
type: 2.7.3
|
||||
|
||||
extend-shallow@2.0.1:
|
||||
dependencies:
|
||||
is-extendable: 0.1.1
|
||||
|
@ -2615,6 +3024,13 @@ snapshots:
|
|||
|
||||
internmap@2.0.3: {}
|
||||
|
||||
intl-messageformat@10.7.3:
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 2.2.1
|
||||
'@formatjs/fast-memoize': 2.2.2
|
||||
'@formatjs/icu-messageformat-parser': 2.9.1
|
||||
tslib: 2.6.3
|
||||
|
||||
is-binary-path@2.1.0:
|
||||
dependencies:
|
||||
binary-extensions: 2.3.0
|
||||
|
@ -2649,6 +3065,8 @@ snapshots:
|
|||
dependencies:
|
||||
isobject: 3.0.1
|
||||
|
||||
is-promise@2.2.2: {}
|
||||
|
||||
is-reference@1.2.1:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.6
|
||||
|
@ -2710,6 +3128,10 @@ snapshots:
|
|||
|
||||
lru-cache@10.2.2: {}
|
||||
|
||||
lru-queue@0.1.0:
|
||||
dependencies:
|
||||
es5-ext: 0.10.64
|
||||
|
||||
magic-string@0.30.10:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
|
@ -2750,6 +3172,17 @@ snapshots:
|
|||
|
||||
mdn-data@2.0.30: {}
|
||||
|
||||
memoizee@0.4.17:
|
||||
dependencies:
|
||||
d: 1.0.2
|
||||
es5-ext: 0.10.64
|
||||
es6-weak-map: 2.0.3
|
||||
event-emitter: 0.3.5
|
||||
is-promise: 2.2.2
|
||||
lru-queue: 0.1.0
|
||||
next-tick: 1.1.0
|
||||
timers-ext: 0.1.8
|
||||
|
||||
merge-stream@2.0.0: {}
|
||||
|
||||
merge2@1.4.1: {}
|
||||
|
@ -2815,6 +3248,8 @@ snapshots:
|
|||
|
||||
nanoid@3.3.7: {}
|
||||
|
||||
next-tick@1.1.0: {}
|
||||
|
||||
node-fetch@2.7.0:
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
|
@ -3190,6 +3625,17 @@ snapshots:
|
|||
dependencies:
|
||||
svelte: 4.2.19
|
||||
|
||||
svelte-i18n@4.0.1(svelte@4.2.19):
|
||||
dependencies:
|
||||
cli-color: 2.0.4
|
||||
deepmerge: 4.3.1
|
||||
esbuild: 0.19.12
|
||||
estree-walker: 2.0.2
|
||||
intl-messageformat: 10.7.3
|
||||
sade: 1.8.1
|
||||
svelte: 4.2.19
|
||||
tiny-glob: 0.2.9
|
||||
|
||||
svelte-maplibre@0.9.8(svelte@4.2.19):
|
||||
dependencies:
|
||||
d3-geo: 3.1.1
|
||||
|
@ -3273,6 +3719,11 @@ snapshots:
|
|||
dependencies:
|
||||
any-promise: 1.3.0
|
||||
|
||||
timers-ext@0.1.8:
|
||||
dependencies:
|
||||
es5-ext: 0.10.64
|
||||
next-tick: 1.1.0
|
||||
|
||||
tiny-glob@0.2.9:
|
||||
dependencies:
|
||||
globalyzer: 0.1.0
|
||||
|
@ -3294,6 +3745,8 @@ snapshots:
|
|||
|
||||
type-detect@4.0.8: {}
|
||||
|
||||
type@2.7.3: {}
|
||||
|
||||
typescript@5.5.2: {}
|
||||
|
||||
typewise-core@1.2.0: {}
|
||||
|
|
1
frontend/src/app.d.ts
vendored
1
frontend/src/app.d.ts
vendored
|
@ -16,6 +16,7 @@ declare global {
|
|||
uuid: string;
|
||||
public_profile: boolean;
|
||||
} | null;
|
||||
locale: string;
|
||||
}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
|
|
|
@ -107,4 +107,14 @@ export const themeHook: Handle = async ({ event, resolve }) => {
|
|||
return await resolve(event);
|
||||
};
|
||||
|
||||
export const handle = sequence(authHook, themeHook);
|
||||
// hook to get the langauge cookie and set the locale
|
||||
export const i18nHook: Handle = async ({ event, resolve }) => {
|
||||
let locale = event.cookies.get('locale');
|
||||
if (!locale) {
|
||||
return await resolve(event);
|
||||
}
|
||||
event.locals.locale = locale; // Store the locale in locals
|
||||
return await resolve(event);
|
||||
};
|
||||
|
||||
export const handle = sequence(authHook, themeHook, i18nHook);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
const dispatch = createEventDispatcher();
|
||||
import { onMount } from 'svelte';
|
||||
let modal: HTMLDialogElement;
|
||||
import { t } from 'svelte-i18n';
|
||||
import { appVersion, copyrightYear, versionChangelog } from '$lib/config';
|
||||
|
||||
onMount(() => {
|
||||
|
@ -28,7 +29,7 @@
|
|||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div class="modal-box" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
||||
<h3 class="font-bold text-lg">
|
||||
About AdventureLog<span class=" inline-block"
|
||||
{$t('about.about')} AdventureLog<span class=" inline-block"
|
||||
><img src="/favicon.png" alt="Map Logo" class="w-10 -mb-3 ml-2" /></span
|
||||
>
|
||||
</h3>
|
||||
|
@ -43,34 +44,36 @@
|
|||
<p class="py-1">
|
||||
© {copyrightYear}
|
||||
<a
|
||||
href="https://github.com/seanmorley15"
|
||||
href="https://seanmorley.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary-500 underline">Sean Morley</a
|
||||
>
|
||||
</p>
|
||||
<p class="py-1">Licensed under the GPL-3.0 License.</p>
|
||||
<p class="py-1">{$t('about.license')}</p>
|
||||
<p class="py-1">
|
||||
<a
|
||||
href="https://github.com/seanmorley15/AdventureLog"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary-500 underline">Source Code</a
|
||||
class="text-primary-500 underline">{$t('about.source_code')}</a
|
||||
>
|
||||
</p>
|
||||
<p class="py-1">Made with ❤️ in the United States.</p>
|
||||
<p class="py-1">{$t('about.message')}</p>
|
||||
<div class="divider"></div>
|
||||
<h3 class="font-bold text-md">Open Source Attributions</h3>
|
||||
<h3 class="font-bold text-md">{$t('about.oss_attributions')}</h3>
|
||||
<p class="py-1 mb-4">
|
||||
Location Search and Geocoding is provided by <a
|
||||
{$t('about.nominatim_1')}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary-500 underline"
|
||||
href="https://operations.osmfoundation.org/policies/nominatim/">OpenStreepMap</a
|
||||
>. Their data is liscensed under the ODbL license.
|
||||
<br /> Additional attributions can be found in the README file.
|
||||
>. {$t('about.nominatim_2')}
|
||||
<br />
|
||||
{$t('about.other_attributions')}
|
||||
</p>
|
||||
|
||||
<button class="btn btn-primary" on:click={close}>Close</button>
|
||||
<button class="btn btn-primary" on:click={close}>{$t('about.close')}</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let activities: string[] | undefined | null;
|
||||
|
||||
|
@ -53,7 +54,7 @@
|
|||
<input
|
||||
type="text"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Add an activity"
|
||||
placeholder={$t('adventures.add_a_tag')}
|
||||
bind:value={inputVal}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
|
@ -62,7 +63,9 @@
|
|||
}
|
||||
}}
|
||||
/>
|
||||
<button type="button" class="btn btn-neutral" on:click={addActivity}>Add</button>
|
||||
<button type="button" class="btn btn-neutral" on:click={addActivity}
|
||||
>{$t('adventures.add')}</button
|
||||
>
|
||||
</div>
|
||||
{#if inputVal && filteredItems.length > 0}
|
||||
<ul class="absolute z-10 w-full bg-base-100 shadow-lg max-h-60 overflow-auto">
|
||||
|
@ -95,7 +98,7 @@
|
|||
class="btn btn-sm btn-error"
|
||||
on:click={() => removeActivity(activity)}
|
||||
>
|
||||
Remove
|
||||
{$t('adventures.remove')}
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
|
|
|
@ -11,15 +11,13 @@
|
|||
import MapMarker from '~icons/mdi/map-marker';
|
||||
import { addToast } from '$lib/toasts';
|
||||
import Link from '~icons/mdi/link-variant';
|
||||
import CheckBold from '~icons/mdi/check-bold';
|
||||
import FormatListBulletedSquare from '~icons/mdi/format-list-bulleted-square';
|
||||
import LinkVariantRemove from '~icons/mdi/link-variant-remove';
|
||||
import Plus from '~icons/mdi/plus';
|
||||
import CollectionLink from './CollectionLink.svelte';
|
||||
import DotsHorizontal from '~icons/mdi/dots-horizontal';
|
||||
import DeleteWarning from './DeleteWarning.svelte';
|
||||
import { isAdventureVisited, typeToString } from '$lib';
|
||||
import CardCarousel from './CardCarousel.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let type: string;
|
||||
export let user: User | null;
|
||||
|
@ -29,7 +27,6 @@
|
|||
let isWarningModalOpen: boolean = false;
|
||||
|
||||
export let adventure: Adventure;
|
||||
|
||||
let activityTypes: string[] = [];
|
||||
// makes it reactivty to changes so it updates automatically
|
||||
$: {
|
||||
|
@ -51,8 +48,7 @@
|
|||
}
|
||||
});
|
||||
if (res.ok) {
|
||||
console.log('Adventure deleted');
|
||||
addToast('info', 'Adventure deleted successfully!');
|
||||
addToast('info', $t('adventures.adventure_delete_success'));
|
||||
dispatch('delete', adventure.id);
|
||||
} else {
|
||||
console.log('Error deleting adventure');
|
||||
|
@ -68,34 +64,13 @@
|
|||
body: JSON.stringify({ collection: null })
|
||||
});
|
||||
if (res.ok) {
|
||||
console.log('Adventure removed from collection');
|
||||
addToast('info', 'Adventure removed from collection successfully!');
|
||||
addToast('info', `${$t('adventures.collection_remove_success')}`);
|
||||
dispatch('delete', adventure.id);
|
||||
} else {
|
||||
console.log('Error removing adventure from collection');
|
||||
addToast('error', `${$t('adventures.collection_remove_error')}`);
|
||||
}
|
||||
}
|
||||
|
||||
function changeType(newType: string) {
|
||||
return async () => {
|
||||
let res = await fetch(`/api/adventures/${adventure.id}/`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ type: newType })
|
||||
});
|
||||
if (res.ok) {
|
||||
console.log('Adventure type changed');
|
||||
dispatch('typeChange', adventure.id);
|
||||
addToast('info', 'Adventure type changed successfully!');
|
||||
adventure.type = newType;
|
||||
} else {
|
||||
console.log('Error changing adventure type');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function linkCollection(event: CustomEvent<number>) {
|
||||
let collectionId = event.detail;
|
||||
let res = await fetch(`/api/adventures/${adventure.id}`, {
|
||||
|
@ -107,11 +82,11 @@
|
|||
});
|
||||
if (res.ok) {
|
||||
console.log('Adventure linked to collection');
|
||||
addToast('info', 'Adventure linked to collection successfully!');
|
||||
addToast('info', `${$t('adventures.collection_link_success')}`);
|
||||
isCollectionModalOpen = false;
|
||||
dispatch('delete', adventure.id);
|
||||
} else {
|
||||
console.log('Error linking adventure to collection');
|
||||
addToast('error', `${$t('adventures.collection_link_error')}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,9 +105,9 @@
|
|||
|
||||
{#if isWarningModalOpen}
|
||||
<DeleteWarning
|
||||
title="Delete Adventure"
|
||||
title={$t('adventures.delete_adventure')}
|
||||
button_text="Delete"
|
||||
description="Are you sure you want to delete this adventure? This action cannot be undone."
|
||||
description={$t('adventures.adventure_delete_confirm')}
|
||||
is_warning={false}
|
||||
on:close={() => (isWarningModalOpen = false)}
|
||||
on:confirm={deleteAdventure}
|
||||
|
@ -154,9 +129,13 @@
|
|||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div class="badge badge-primary">{typeToString(adventure.type)}</div>
|
||||
<div class="badge badge-success">{isAdventureVisited(adventure) ? 'Visited' : 'Planned'}</div>
|
||||
<div class="badge badge-secondary">{adventure.is_public ? 'Public' : 'Private'}</div>
|
||||
<div class="badge badge-primary">{$t(`adventures.activities.${adventure.type}`)}</div>
|
||||
<div class="badge badge-success">
|
||||
{adventure.is_visited ? $t('adventures.visited') : $t('adventures.planned')}
|
||||
</div>
|
||||
<div class="badge badge-secondary">
|
||||
{adventure.is_public ? $t('adventures.public') : $t('adventures.private')}
|
||||
</div>
|
||||
</div>
|
||||
{#if adventure.location && adventure.location !== ''}
|
||||
<div class="inline-flex items-center">
|
||||
|
@ -170,7 +149,7 @@
|
|||
<Calendar class="w-5 h-5 mr-1" />
|
||||
<p class="ml-.5">
|
||||
{adventure.visits.length}
|
||||
{adventure.visits.length > 1 ? 'visits' : 'visit'}
|
||||
{adventure.visits.length > 1 ? $t('adventures.visits') : $t('adventures.visit')}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -199,30 +178,24 @@
|
|||
<button
|
||||
class="btn btn-neutral mb-2"
|
||||
on:click={() => goto(`/adventures/${adventure.id}`)}
|
||||
><Launch class="w-6 h-6" />Open Details</button
|
||||
><Launch class="w-6 h-6" />{$t('adventures.open_details')}</button
|
||||
>
|
||||
<button class="btn btn-neutral mb-2" on:click={editAdventure}>
|
||||
<FileDocumentEdit class="w-6 h-6" />Edit Adventure
|
||||
<FileDocumentEdit class="w-6 h-6" />
|
||||
{$t('adventures.edit_adventure')}
|
||||
</button>
|
||||
{#if adventure.type == 'visited' && user?.pk == adventure.user_id}
|
||||
<button class="btn btn-neutral mb-2" on:click={changeType('planned')}
|
||||
><FormatListBulletedSquare class="w-6 h-6" />Change to Plan</button
|
||||
>
|
||||
{/if}
|
||||
{#if adventure.type == 'planned' && user?.pk == adventure.user_id}
|
||||
<button class="btn btn-neutral mb-2" on:click={changeType('visited')}
|
||||
><CheckBold class="w-6 h-6" />Mark Visited</button
|
||||
>
|
||||
{/if}
|
||||
|
||||
<!-- remove from collection -->
|
||||
{#if adventure.collection && user?.pk == adventure.user_id}
|
||||
<button class="btn btn-neutral mb-2" on:click={removeFromCollection}
|
||||
><LinkVariantRemove class="w-6 h-6" />Remove from Collection</button
|
||||
><LinkVariantRemove class="w-6 h-6" />{$t(
|
||||
'adventures.remove_from_collection'
|
||||
)}</button
|
||||
>
|
||||
{/if}
|
||||
{#if !adventure.collection}
|
||||
<button class="btn btn-neutral mb-2" on:click={() => (isCollectionModalOpen = true)}
|
||||
><Plus class="w-6 h-6" />Add to Collection</button
|
||||
><Plus class="w-6 h-6" />{$t('adventures.add_to_collection')}</button
|
||||
>
|
||||
{/if}
|
||||
<button
|
||||
|
@ -230,7 +203,7 @@
|
|||
data-umami-event="Delete Adventure"
|
||||
class="btn btn-warning"
|
||||
on:click={() => (isWarningModalOpen = true)}
|
||||
><TrashCan class="w-6 h-6" />Delete</button
|
||||
><TrashCan class="w-6 h-6" />{$t('adventures.delete')}</button
|
||||
>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import type { Adventure, User } from '$lib/types';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
import type { ActionResult } from '@sveltejs/kit';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { onMount } from 'svelte';
|
||||
import AdventureCard from './AdventureCard.svelte';
|
||||
let modal: HTMLDialogElement;
|
||||
|
@ -51,7 +51,7 @@
|
|||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div class="modal-box w-11/12 max-w-5xl" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
||||
<h1 class="text-center font-bold text-4xl mb-6">My Adventures</h1>
|
||||
<h1 class="text-center font-bold text-4xl mb-6">{$t('adventures.my_adventures')}</h1>
|
||||
{#if isLoading}
|
||||
<div class="flex justify-center items-center w-full mt-16">
|
||||
<span class="loading loading-spinner w-24 h-24"></span>
|
||||
|
@ -63,10 +63,10 @@
|
|||
{/each}
|
||||
{#if adventures.length === 0 && !isLoading}
|
||||
<p class="text-center text-lg">
|
||||
No adventures found that can be linked to this collection.
|
||||
{$t('adventures.no_linkable_adventures')}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
<button class="btn btn-primary" on:click={close}>Close</button>
|
||||
<button class="btn btn-primary" on:click={close}>{$t('about.close')}</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import type { Adventure, Collection, OpenStreetMapPlace, Point } from '$lib/types';
|
||||
import type {
|
||||
Adventure,
|
||||
Collection,
|
||||
OpenStreetMapPlace,
|
||||
Point,
|
||||
ReverseGeocode
|
||||
} from '$lib/types';
|
||||
import { onMount } from 'svelte';
|
||||
import { enhance } from '$app/forms';
|
||||
import { addToast } from '$lib/toasts';
|
||||
import { deserialize } from '$app/forms';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let longitude: number | null = null;
|
||||
export let latitude: number | null = null;
|
||||
export let collection: Collection | null = null;
|
||||
|
||||
import { DefaultMarker, MapEvents, MapLibre } from 'svelte-maplibre';
|
||||
import { DefaultMarker, FillLayer, MapEvents, MapLibre } from 'svelte-maplibre';
|
||||
|
||||
let query: string = '';
|
||||
let places: OpenStreetMapPlace[] = [];
|
||||
|
@ -26,6 +33,10 @@
|
|||
|
||||
let noPlaces: boolean = false;
|
||||
|
||||
let is_custom_location: boolean = false;
|
||||
|
||||
let reverseGeocodePlace: ReverseGeocode | null = null;
|
||||
|
||||
let adventure: Adventure = {
|
||||
id: '',
|
||||
name: '',
|
||||
|
@ -61,7 +72,8 @@
|
|||
images: adventureToEdit?.images || [],
|
||||
user_id: adventureToEdit?.user_id || null,
|
||||
collection: adventureToEdit?.collection || collection?.id || null,
|
||||
visits: adventureToEdit?.visits || []
|
||||
visits: adventureToEdit?.visits || [],
|
||||
is_visited: adventureToEdit?.is_visited || false
|
||||
};
|
||||
|
||||
let markers: Point[] = [];
|
||||
|
@ -70,8 +82,20 @@
|
|||
let imageError: string = '';
|
||||
let wikiImageError: string = '';
|
||||
|
||||
let old_display_name: string = '';
|
||||
|
||||
images = adventure.images || [];
|
||||
|
||||
if (longitude && latitude) {
|
||||
adventure.latitude = latitude;
|
||||
adventure.longitude = longitude;
|
||||
reverseGeocode(true);
|
||||
}
|
||||
|
||||
$: {
|
||||
is_custom_location = adventure.location != reverseGeocodePlace?.display_name;
|
||||
}
|
||||
|
||||
if (adventure.longitude && adventure.latitude) {
|
||||
markers = [];
|
||||
markers = [
|
||||
|
@ -84,12 +108,6 @@
|
|||
];
|
||||
}
|
||||
|
||||
if (longitude && latitude) {
|
||||
adventure.latitude = latitude;
|
||||
adventure.longitude = longitude;
|
||||
reverseGeocode();
|
||||
}
|
||||
|
||||
$: {
|
||||
if (!adventure.rating) {
|
||||
adventure.rating = NaN;
|
||||
|
@ -111,9 +129,9 @@
|
|||
images = images.filter((image) => image.id !== id);
|
||||
adventure.images = images;
|
||||
console.log(images);
|
||||
addToast('success', 'Image removed');
|
||||
addToast('success', $t('adventures.image_removed_success'));
|
||||
} else {
|
||||
addToast('error', 'Failed to remove image');
|
||||
addToast('error', $t('adventures.image_removed_error'));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,12 +142,19 @@
|
|||
close();
|
||||
}
|
||||
|
||||
let previousCoords: { lat: number; lng: number } | null = null;
|
||||
|
||||
$: if (markers.length > 0) {
|
||||
adventure.latitude = Math.round(markers[0].lngLat.lat * 1e6) / 1e6;
|
||||
adventure.longitude = Math.round(markers[0].lngLat.lng * 1e6) / 1e6;
|
||||
if (!adventure.location) {
|
||||
adventure.location = markers[0].location;
|
||||
const newLat = Math.round(markers[0].lngLat.lat * 1e6) / 1e6;
|
||||
const newLng = Math.round(markers[0].lngLat.lng * 1e6) / 1e6;
|
||||
|
||||
if (!previousCoords || previousCoords.lat !== newLat || previousCoords.lng !== newLng) {
|
||||
adventure.latitude = newLat;
|
||||
adventure.longitude = newLng;
|
||||
previousCoords = { lat: newLat, lng: newLng };
|
||||
reverseGeocode();
|
||||
}
|
||||
|
||||
if (!adventure.name) {
|
||||
adventure.name = markers[0].name;
|
||||
}
|
||||
|
@ -139,7 +164,7 @@
|
|||
let res = await fetch(url);
|
||||
let data = await res.blob();
|
||||
if (!data) {
|
||||
imageError = 'No image found at that URL.';
|
||||
imageError = $t('adventures.no_image_url');
|
||||
return;
|
||||
}
|
||||
let file = new File([data], 'image.jpg', { type: 'image/jpeg' });
|
||||
|
@ -155,9 +180,9 @@
|
|||
if (data2.type === 'success') {
|
||||
images = [...images, data2];
|
||||
adventure.images = images;
|
||||
addToast('success', 'Image uploaded');
|
||||
addToast('success', $t('adventures.image_upload_success'));
|
||||
} else {
|
||||
addToast('error', 'Failed to upload image');
|
||||
addToast('error', $t('adventures.image_upload_error'));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,7 +190,7 @@
|
|||
let res = await fetch(`/api/generate/img/?name=${imageSearch}`);
|
||||
let data = await res.json();
|
||||
if (!res.ok) {
|
||||
wikiImageError = 'Failed to fetch image';
|
||||
wikiImageError = $t('adventures.image_fetch_failed');
|
||||
return;
|
||||
}
|
||||
if (data.source) {
|
||||
|
@ -187,10 +212,10 @@
|
|||
console.log(newImage);
|
||||
images = [...images, newImage];
|
||||
adventure.images = images;
|
||||
addToast('success', 'Image uploaded');
|
||||
addToast('success', $t('adventures.image_upload_success'));
|
||||
} else {
|
||||
addToast('error', 'Failed to upload image');
|
||||
wikiImageError = 'Failed to upload image';
|
||||
addToast('error', $t('adventures.image_upload_error'));
|
||||
wikiImageError = $t('adventures.wiki_image_error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -199,7 +224,7 @@
|
|||
e.preventDefault();
|
||||
}
|
||||
if (!query) {
|
||||
alert('Please enter a location');
|
||||
alert($t('adventures.no_location'));
|
||||
return;
|
||||
}
|
||||
let res = await fetch(`https://nominatim.openstreetmap.org/search?q=${query}&format=jsonv2`, {
|
||||
|
@ -225,15 +250,11 @@
|
|||
new_end_date = new_start_date;
|
||||
}
|
||||
if (new_start_date > new_end_date) {
|
||||
addToast('error', 'Start date must be before end date');
|
||||
return;
|
||||
}
|
||||
if (new_start_date === '' || new_end_date === '') {
|
||||
addToast('error', 'Please enter a start and end date');
|
||||
addToast('error', $t('adventures.start_before_end_error'));
|
||||
return;
|
||||
}
|
||||
if (new_end_date && !new_start_date) {
|
||||
addToast('error', 'Please enter a start date');
|
||||
addToast('error', $t('adventures.no_start_date'));
|
||||
return;
|
||||
}
|
||||
adventure.visits = [
|
||||
|
@ -250,30 +271,44 @@
|
|||
new_notes = '';
|
||||
}
|
||||
|
||||
async function reverseGeocode() {
|
||||
async function markVisited() {
|
||||
console.log(reverseGeocodePlace);
|
||||
if (reverseGeocodePlace) {
|
||||
let res = await fetch(`/worldtravel?/markVisited`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ regionId: reverseGeocodePlace.id })
|
||||
});
|
||||
if (res.ok) {
|
||||
reverseGeocodePlace.is_visited = true;
|
||||
addToast('success', `Visit to ${reverseGeocodePlace.region} marked`);
|
||||
} else {
|
||||
addToast('error', `Failed to mark visit to ${reverseGeocodePlace.region}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function reverseGeocode(force_update: boolean = false) {
|
||||
let res = await fetch(
|
||||
`https://nominatim.openstreetmap.org/search?q=${adventure.latitude},${adventure.longitude}&format=jsonv2`,
|
||||
{
|
||||
headers: {
|
||||
'User-Agent': `AdventureLog / ${appVersion} `
|
||||
}
|
||||
}
|
||||
`/api/reverse-geocode/reverse_geocode/?lat=${adventure.latitude}&lon=${adventure.longitude}`
|
||||
);
|
||||
let data = (await res.json()) as OpenStreetMapPlace[];
|
||||
if (data.length > 0) {
|
||||
adventure.name = data[0]?.name || '';
|
||||
adventure.activity_types?.push(data[0]?.type || '');
|
||||
adventure.location = data[0]?.display_name || '';
|
||||
if (longitude && latitude) {
|
||||
markers = [
|
||||
{
|
||||
lngLat: { lng: longitude, lat: latitude },
|
||||
location: data[0]?.display_name || '',
|
||||
name: data[0]?.name || '',
|
||||
activity_type: data[0]?.type || ''
|
||||
}
|
||||
];
|
||||
let data = await res.json();
|
||||
if (data.error) {
|
||||
console.log(data.error);
|
||||
reverseGeocodePlace = null;
|
||||
return;
|
||||
}
|
||||
reverseGeocodePlace = data;
|
||||
|
||||
console.log(reverseGeocodePlace);
|
||||
console.log(is_custom_location);
|
||||
|
||||
if (
|
||||
reverseGeocodePlace &&
|
||||
reverseGeocodePlace.display_name &&
|
||||
(!is_custom_location || force_update)
|
||||
) {
|
||||
old_display_name = reverseGeocodePlace.display_name;
|
||||
adventure.location = reverseGeocodePlace.display_name;
|
||||
}
|
||||
console.log(data);
|
||||
}
|
||||
|
@ -306,7 +341,7 @@
|
|||
adventure.description = data.extract;
|
||||
wikiError = '';
|
||||
} else {
|
||||
wikiError = 'No description found';
|
||||
wikiError = $t('adventures.no_description_found');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -330,12 +365,12 @@
|
|||
if (result.data.id && result.data.image) {
|
||||
adventure.images = [...adventure.images, result.data];
|
||||
images = [...images, result.data];
|
||||
addToast('success', 'Image uploaded');
|
||||
addToast('success', $t('adventures.image_upload_success'));
|
||||
|
||||
fileInput.value = '';
|
||||
console.log(adventure);
|
||||
} else {
|
||||
addToast('error', result.data.error || 'Failed to upload image');
|
||||
addToast('error', result.data.error || $t('adventures.image_upload_error'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -357,10 +392,10 @@
|
|||
adventure = data as Adventure;
|
||||
isDetails = false;
|
||||
warningMessage = '';
|
||||
addToast('success', 'Adventure created');
|
||||
addToast('success', $t('adventures.adventure_created'));
|
||||
} else {
|
||||
warningMessage = Object.values(data)[0] as string;
|
||||
addToast('error', 'Failed to create adventure');
|
||||
addToast('error', $t('adventures.adventure_create_error'));
|
||||
}
|
||||
} else {
|
||||
let res = await fetch(`/api/adventures/${adventure.id}`, {
|
||||
|
@ -375,10 +410,10 @@
|
|||
adventure = data as Adventure;
|
||||
isDetails = false;
|
||||
warningMessage = '';
|
||||
addToast('success', 'Adventure updated');
|
||||
addToast('success', $t('adventures.adventure_updated'));
|
||||
} else {
|
||||
warningMessage = Object.values(data)[0] as string;
|
||||
addToast('error', 'Failed to update adventure');
|
||||
addToast('error', $t('adventures.adventure_update_error'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -390,7 +425,7 @@
|
|||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<div class="modal-box w-11/12 max-w-3xl" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
||||
<h3 class="font-bold text-2xl">
|
||||
{adventureToEdit ? 'Edit Adventure' : 'New Adventure'}
|
||||
{adventureToEdit ? $t('adventures.edit_adventure') : $t('adventures.new_adventure')}
|
||||
</h3>
|
||||
{#if adventure.id === '' || isDetails}
|
||||
<div class="modal-action items-center">
|
||||
|
@ -399,10 +434,12 @@
|
|||
<!-- <div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3"> -->
|
||||
<div class="collapse collapse-plus bg-base-200 mb-4">
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title text-xl font-medium">Basic Information</div>
|
||||
<div class="collapse-title text-xl font-medium">
|
||||
{$t('adventures.basic_information')}
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
<div>
|
||||
<label for="name">Name</label><br />
|
||||
<label for="name">{$t('adventures.name')}</label><br />
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
|
@ -413,16 +450,16 @@
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="link">Category</label><br />
|
||||
<label for="link">{$t('adventures.category')}</label><br />
|
||||
<select class="select select-bordered w-full max-w-xs" bind:value={adventure.type}>
|
||||
<option disabled selected>Select Adventure Type</option>
|
||||
<option disabled selected>{$t('adventures.select_adventure_category')}</option>
|
||||
{#each ADVENTURE_TYPES as type}
|
||||
<option value={type.type}>{type.label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="rating">Rating</label><br />
|
||||
<label for="rating">{$t('adventures.rating')}</label><br />
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
|
@ -481,14 +518,14 @@
|
|||
class="btn btn-sm btn-error ml-2"
|
||||
on:click={() => (adventure.rating = NaN)}
|
||||
>
|
||||
Remove
|
||||
{$t('adventures.remove')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<label for="link">Link</label><br />
|
||||
<label for="link">{$t('adventures.link')}</label><br />
|
||||
<input
|
||||
type="text"
|
||||
id="link"
|
||||
|
@ -499,7 +536,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="description">Description</label><br />
|
||||
<label for="description">{$t('adventures.description')}</label><br />
|
||||
<textarea
|
||||
id="description"
|
||||
name="description"
|
||||
|
@ -507,12 +544,9 @@
|
|||
class="textarea textarea-bordered w-full h-32"
|
||||
></textarea>
|
||||
<div class="mt-2">
|
||||
<div
|
||||
class="tooltip tooltip-right"
|
||||
data-tip="Pulls excerpt from Wikipedia article matching the name of the adventure."
|
||||
>
|
||||
<div class="tooltip tooltip-right" data-tip={$t('adventures.wiki_desc')}>
|
||||
<button type="button" class="btn btn-neutral" on:click={generateDesc}
|
||||
>Generate Description</button
|
||||
>{$t('adventures.generate_desc')}</button
|
||||
>
|
||||
</div>
|
||||
<p class="text-red-500">{wikiError}</p>
|
||||
|
@ -522,7 +556,7 @@
|
|||
<div>
|
||||
<div class="form-control flex items-start mt-1">
|
||||
<label class="label cursor-pointer flex items-start space-x-2">
|
||||
<span class="label-text">Public Adventure</span>
|
||||
<span class="label-text">{$t('adventures.public_adventure')}</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle toggle-primary"
|
||||
|
@ -539,11 +573,14 @@
|
|||
|
||||
<div class="collapse collapse-plus bg-base-200 mb-4">
|
||||
<input type="checkbox" />
|
||||
<div class="collapse-title text-xl font-medium">Location Information</div>
|
||||
<div class="collapse-title text-xl font-medium">
|
||||
{$t('adventures.location_information')}
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
<!-- <div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3"> -->
|
||||
<div>
|
||||
<label for="latitude">Location</label><br />
|
||||
<label for="latitude">{$t('adventures.location')}</label><br />
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
type="text"
|
||||
id="location"
|
||||
|
@ -551,26 +588,36 @@
|
|||
bind:value={adventure.location}
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
{#if is_custom_location}
|
||||
<button
|
||||
class="btn btn-primary ml-2"
|
||||
type="button"
|
||||
on:click={() => (adventure.location = reverseGeocodePlace?.display_name)}
|
||||
>{$t('adventures.set_to_pin')}</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<form on:submit={geocode} class="mt-2">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Seach for a location"
|
||||
placeholder={$t('adventures.search_for_location')}
|
||||
class="input input-bordered w-full max-w-xs mb-2"
|
||||
id="search"
|
||||
name="search"
|
||||
bind:value={query}
|
||||
/>
|
||||
<button class="btn btn-neutral -mt-1" type="submit">Search</button>
|
||||
<button class="btn btn-neutral -mt-1" type="submit">{$t('navbar.search')}</button>
|
||||
<button class="btn btn-neutral -mt-1" type="button" on:click={clearMap}
|
||||
>Clear Map</button
|
||||
>{$t('adventures.clear_map')}</button
|
||||
>
|
||||
</form>
|
||||
</div>
|
||||
{#if places.length > 0}
|
||||
<div class="mt-4 max-w-full">
|
||||
<h3 class="font-bold text-lg mb-4">Search Results</h3>
|
||||
<h3 class="font-bold text-lg mb-4">{$t('adventures.search_results')}</h3>
|
||||
|
||||
<div class="flex flex-wrap">
|
||||
{#each places as place}
|
||||
|
@ -594,12 +641,12 @@
|
|||
</div>
|
||||
</div>
|
||||
{:else if noPlaces}
|
||||
<p class="text-error text-lg">No results found</p>
|
||||
<p class="text-error text-lg">{$t('adventures.no_results')}</p>
|
||||
{/if}
|
||||
<!-- </div> -->
|
||||
<div>
|
||||
<MapLibre
|
||||
style="https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
|
||||
style="https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
|
||||
class="relative aspect-[9/16] max-h-[70vh] w-full sm:aspect-video sm:max-h-full"
|
||||
standardControls
|
||||
>
|
||||
|
@ -612,6 +659,44 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
|||
<DefaultMarker lngLat={marker.lngLat} />
|
||||
{/each}
|
||||
</MapLibre>
|
||||
{#if reverseGeocodePlace}
|
||||
<div class="mt-2">
|
||||
<p>{reverseGeocodePlace.region}, {reverseGeocodePlace.country}</p>
|
||||
<p>
|
||||
{reverseGeocodePlace.is_visited
|
||||
? $t('adventures.visited')
|
||||
: $t('adventures.not_visited')}
|
||||
</p>
|
||||
</div>
|
||||
{#if !reverseGeocodePlace.is_visited}
|
||||
<div role="alert" class="alert alert-info mt-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
class="h-6 w-6 shrink-0 stroke-current"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
></path>
|
||||
</svg>
|
||||
<span
|
||||
>{$t('adventures.mark_region_as_visited', {
|
||||
values: {
|
||||
region: reverseGeocodePlace.region,
|
||||
country: reverseGeocodePlace.country
|
||||
}
|
||||
})}</span
|
||||
>
|
||||
<button type="button" class="btn btn-neutral" on:click={markVisited}>
|
||||
{$t('adventures.mark_visited')}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -619,7 +704,7 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
|||
<div class="collapse collapse-plus bg-base-200 mb-4 overflow-visible">
|
||||
<input type="checkbox" />
|
||||
<div class="collapse-title text-xl font-medium">
|
||||
Activity Types ({adventure.activity_types?.length || 0})
|
||||
{$t('adventures.tags')} ({adventure.activity_types?.length || 0})
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
<input
|
||||
|
@ -637,12 +722,12 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
|||
<div class="collapse collapse-plus bg-base-200 mb-4">
|
||||
<input type="checkbox" />
|
||||
<div class="collapse-title text-xl font-medium">
|
||||
Visits ({adventure.visits.length})
|
||||
{$t('adventures.visits')} ({adventure.visits.length})
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
<label class="label cursor-pointer flex items-start space-x-2">
|
||||
{#if adventure.collection && collection && collection.start_date && collection.end_date}
|
||||
<span class="label-text">Constrain to collection dates</span>
|
||||
<span class="label-text">{$t('adventures.date_constrain')}</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle toggle-primary"
|
||||
|
@ -669,7 +754,7 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
|||
<input
|
||||
type="date"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="End Date"
|
||||
placeholder={$t('adventures.end_date')}
|
||||
bind:value={new_end_date}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
|
@ -682,7 +767,7 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
|||
<input
|
||||
type="date"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="Start Date"
|
||||
placeholder={$t('adventures.start_date')}
|
||||
min={collection?.start_date}
|
||||
max={collection?.end_date}
|
||||
bind:value={new_start_date}
|
||||
|
@ -696,7 +781,7 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
|||
<input
|
||||
type="date"
|
||||
class="input input-bordered w-full"
|
||||
placeholder="End Date"
|
||||
placeholder={$t('adventures.end_date')}
|
||||
bind:value={new_end_date}
|
||||
min={collection?.start_date}
|
||||
max={collection?.end_date}
|
||||
|
@ -713,7 +798,7 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
|||
<!-- textarea for notes -->
|
||||
<textarea
|
||||
class="textarea textarea-bordered w-full"
|
||||
placeholder="Add notes"
|
||||
placeholder={$t('adventures.add_notes')}
|
||||
bind:value={new_notes}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
|
@ -725,11 +810,13 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
|||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button type="button" class="btn btn-neutral" on:click={addNewVisit}>Add</button>
|
||||
<button type="button" class="btn btn-neutral" on:click={addNewVisit}
|
||||
>{$t('adventures.add')}</button
|
||||
>
|
||||
</div>
|
||||
|
||||
{#if adventure.visits.length > 0}
|
||||
<h2 class=" font-bold text-xl mt-2">My Visits</h2>
|
||||
<h2 class=" font-bold text-xl mt-2">{$t('adventures.my_visits')}</h2>
|
||||
{#each adventure.visits as visit}
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex gap-2">
|
||||
|
@ -754,7 +841,7 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
|||
adventure.visits = adventure.visits.filter((v) => v !== visit);
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
{$t('adventures.remove')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -782,20 +869,20 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
|||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
</svg>
|
||||
<span>Warning: {warningMessage}</span>
|
||||
<span>{$t('adventures.warning')}: {warningMessage}</span>
|
||||
</div>
|
||||
{/if}
|
||||
<button type="submit" class="btn btn-primary">Save & Next</button>
|
||||
<button type="button" class="btn" on:click={close}>Close</button>
|
||||
<button type="submit" class="btn btn-primary">{$t('adventures.save_next')}</button>
|
||||
<button type="button" class="btn" on:click={close}>{$t('about.close')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{:else}
|
||||
<p>Upload images here</p>
|
||||
<p>{$t('adventures.upload_images_here')}</p>
|
||||
<!-- <p>{adventureToEdit.id}</p> -->
|
||||
<div class="mb-2">
|
||||
<label for="image">Image </label><br />
|
||||
<label for="image">{$t('adventures.image')} </label><br />
|
||||
<div class="flex">
|
||||
<form
|
||||
method="POST"
|
||||
|
@ -812,11 +899,13 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
|||
id="image"
|
||||
/>
|
||||
<input type="hidden" name="adventure" value={adventure.id} id="adventure" />
|
||||
<button class="btn btn-neutral mt-2 mb-2" type="submit">Upload Image</button>
|
||||
<button class="btn btn-neutral mt-2 mb-2" type="submit"
|
||||
>{$t('adventures.upload_image')}</button
|
||||
>
|
||||
</form>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<label for="url">URL</label><br />
|
||||
<label for="url">{$t('adventures.url')}</label><br />
|
||||
<input
|
||||
type="text"
|
||||
id="url"
|
||||
|
@ -825,11 +914,11 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
|||
class="input input-bordered w-full"
|
||||
/>
|
||||
<button class="btn btn-neutral mt-2" type="button" on:click={fetchImage}
|
||||
>Fetch Image</button
|
||||
>{$t('adventures.fetch_image')}</button
|
||||
>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<label for="name">Wikipedia</label><br />
|
||||
<label for="name">{$t('adventures.wikipedia')}</label><br />
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
|
@ -838,14 +927,14 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
|||
class="input input-bordered w-full"
|
||||
/>
|
||||
<button class="btn btn-neutral mt-2" type="button" on:click={fetchWikiImage}
|
||||
>Fetch Image</button
|
||||
>{$t('adventures.fetch_image')}</button
|
||||
>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
{#if images.length > 0}
|
||||
<h1 class="font-semibold text-xl">My Images</h1>
|
||||
<h1 class="font-semibold text-xl">{$t('adventures.my_images')}</h1>
|
||||
{:else}
|
||||
<h1 class="font-semibold text-xl">No Images</h1>
|
||||
<h1 class="font-semibold text-xl">{$t('adventures.no_images')}</h1>
|
||||
{/if}
|
||||
<div class="flex flex-wrap gap-2 mt-2">
|
||||
{#each images as image}
|
||||
|
@ -863,12 +952,14 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
|||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<button type="button" class="btn btn-primary" on:click={saveAndClose}>Close</button>
|
||||
<button type="button" class="btn btn-primary" on:click={saveAndClose}
|
||||
>{$t('about.close')}</button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
{#if adventure.is_public && adventure.id}
|
||||
<div class="bg-neutral p-4 mt-2 rounded-md shadow-sm">
|
||||
<p class=" font-semibold">Share this Adventure!</p>
|
||||
<p class=" font-semibold">{$t('adventures.share_adventure')}</p>
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-card-foreground font-mono">
|
||||
{window.location.origin}/adventures/{adventure.id}
|
||||
|
@ -880,7 +971,7 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
|||
}}
|
||||
class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 h-10 px-4 py-2"
|
||||
>
|
||||
Copy Link
|
||||
{$t('adventures.copy_link')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let user: any;
|
||||
|
||||
|
@ -14,7 +15,7 @@
|
|||
<div class="avatar placeholder">
|
||||
<div class="bg-neutral rounded-full text-neutral-200 w-10 ml-4">
|
||||
{#if user.profile_pic}
|
||||
<img src={user.profile_pic} alt="User Profile" />
|
||||
<img src={user.profile_pic} alt={$t('navbar.profile')} />
|
||||
{:else}
|
||||
<span class="text-2xl -mt-1">{letter}</span>
|
||||
{/if}
|
||||
|
@ -28,14 +29,17 @@
|
|||
>
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
<p class="text-lg ml-4 font-bold">Hi, {user.first_name} {user.last_name}</p>
|
||||
<li><button on:click={() => goto('/profile')}>Profile</button></li>
|
||||
<li><button on:click={() => goto('/adventures')}>My Adventures</button></li>
|
||||
<li><button on:click={() => goto('/activities')}>My Activities</button></li>
|
||||
<li><button on:click={() => goto('/shared')}>Shared With Me</button></li>
|
||||
<li><button on:click={() => goto('/settings')}>User Settings</button></li>
|
||||
<p class="text-lg ml-4 font-bold">
|
||||
{$t('navbar.greeting')}, {user.first_name}
|
||||
{user.last_name}
|
||||
</p>
|
||||
<li><button on:click={() => goto('/profile')}>{$t('navbar.profile')}</button></li>
|
||||
<li><button on:click={() => goto('/adventures')}>{$t('navbar.my_adventures')}</button></li>
|
||||
<li><button on:click={() => goto('/activities')}>{$t('navbar.my_tags')}</button></li>
|
||||
<li><button on:click={() => goto('/shared')}>{$t('navbar.shared_with_me')}</button></li>
|
||||
<li><button on:click={() => goto('/settings')}>{$t('navbar.settings')}</button></li>
|
||||
<form method="post">
|
||||
<li><button formaction="/?/logout">Logout</button></li>
|
||||
<li><button formaction="/?/logout">{$t('navbar.logout')}</button></li>
|
||||
</form>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import type { Adventure } from '$lib/types';
|
||||
import ImageDisplayModal from './ImageDisplayModal.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let adventures: Adventure[] = [];
|
||||
|
||||
|
@ -79,7 +80,7 @@
|
|||
{:else}
|
||||
<!-- svelte-ignore a11y-img-redundant-alt -->
|
||||
<img
|
||||
src={'https://placehold.co/300?text=No%20Image%20Found&font=roboto'}
|
||||
src={`https://placehold.co/300?text=${$t('adventures.no_image_found')}&font=roboto`}
|
||||
alt="No image available"
|
||||
class="w-full h-48 object-cover"
|
||||
/>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { ADVENTURE_TYPES } from '$lib';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
let types_arr: string[] = [];
|
||||
export let types: string;
|
||||
|
@ -29,9 +30,13 @@
|
|||
|
||||
<div class="collapse collapse-plus mb-4">
|
||||
<input type="checkbox" />
|
||||
<div class="collapse-title text-xl bg-base-300 font-medium">Category Filter</div>
|
||||
<div class="collapse-title text-xl bg-base-300 font-medium">
|
||||
{$t('adventures.category_filter')}
|
||||
</div>
|
||||
<div class="collapse-content bg-base-300">
|
||||
<button class="btn btn-wide btn-neutral-300" on:click={clearTypes}>Clear</button>
|
||||
<button class="btn btn-wide btn-neutral-300" on:click={clearTypes}
|
||||
>{$t(`adventures.clear`)}</button
|
||||
>
|
||||
{#each ADVENTURE_TYPES as type}
|
||||
<li>
|
||||
<label class="cursor-pointer">
|
||||
|
@ -41,7 +46,7 @@
|
|||
on:change={() => toggleSelect(type.type)}
|
||||
checked={types.indexOf(type.type) > -1}
|
||||
/>
|
||||
<span>{type.label}</span>
|
||||
<span>{$t(`adventures.activities.${type.type}`)}</span>
|
||||
</label>
|
||||
</li>
|
||||
{/each}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import type { Checklist, Collection, User } from '$lib/types';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
import Launch from '~icons/mdi/launch';
|
||||
import TrashCan from '~icons/mdi/trash-can';
|
||||
|
@ -21,10 +22,10 @@
|
|||
method: 'DELETE'
|
||||
});
|
||||
if (res.ok) {
|
||||
addToast('success', 'Checklist deleted successfully');
|
||||
addToast('success', $t('checklist.checklist_deleted'));
|
||||
dispatch('delete', checklist.id);
|
||||
} else {
|
||||
addToast('Failed to delete checklist', 'error');
|
||||
addToast($t('checklist.checklist_delete_error'), 'error');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -38,9 +39,12 @@
|
|||
{checklist.name}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="badge badge-primary">Checklist</div>
|
||||
<div class="badge badge-primary">{$t('adventures.checklist')}</div>
|
||||
{#if checklist.items.length > 0}
|
||||
<p>{checklist.items.length} {checklist.items.length > 1 ? 'Items' : 'Item'}</p>
|
||||
<p>
|
||||
{checklist.items.length}
|
||||
{checklist.items.length > 1 ? $t('checklist.items') : $t('checklist.item')}
|
||||
</p>
|
||||
{/if}
|
||||
{#if checklist.date && checklist.date !== ''}
|
||||
<div class="inline-flex items-center">
|
||||
|
@ -49,11 +53,8 @@
|
|||
</div>
|
||||
{/if}
|
||||
<div class="card-actions justify-end">
|
||||
<!-- <button class="btn btn-neutral mb-2" on:click={() => goto(`/notes/${note.id}`)}
|
||||
><Launch class="w-6 h-6" />Open Details</button
|
||||
> -->
|
||||
<button class="btn btn-neutral-200 mb-2" on:click={editChecklist}>
|
||||
<Launch class="w-6 h-6" />Open
|
||||
<Launch class="w-6 h-6" />{$t('notes.open')}
|
||||
</button>
|
||||
{#if checklist.user_id == user?.pk || (collection && user && collection.shared_with.includes(user.uuid))}
|
||||
<button
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<script lang="ts">
|
||||
import { isValidUrl } from '$lib';
|
||||
import type { Collection, Checklist, User, ChecklistItem } from '$lib/types';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
import { onMount } from 'svelte';
|
||||
let modal: HTMLDialogElement;
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let checklist: Checklist | null = null;
|
||||
export let collection: Collection;
|
||||
|
@ -21,11 +21,11 @@
|
|||
|
||||
function addItem() {
|
||||
if (newItem.trim() == '') {
|
||||
warning = 'Item cannot be empty';
|
||||
warning = $t('checklist.item_cannot_be_empty');
|
||||
return;
|
||||
}
|
||||
if (newChecklist.items.find((item) => item.name.trim() == newItem)) {
|
||||
warning = 'Item already exists';
|
||||
warning = $t('checklist.item_already_exists');
|
||||
return;
|
||||
}
|
||||
items = [
|
||||
|
@ -130,15 +130,15 @@
|
|||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div class="modal-box" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
||||
<h3 class="font-bold text-lg mb-2">Checklist Editor</h3>
|
||||
<h3 class="font-bold text-lg mb-2">{$t('checklist.checklist_editor')}</h3>
|
||||
{#if initialName}
|
||||
<p class="font-semibold text-md mb-2">Editing checklist {initialName}</p>
|
||||
<p class="font-semibold text-md mb-2">{$t('checklist.editing_checklist')} {initialName}</p>
|
||||
{/if}
|
||||
|
||||
{#if (checklist && user?.pk == checklist?.user_id) || (user && collection && collection.shared_with.includes(user.uuid)) || !checklist}
|
||||
<form on:submit|preventDefault>
|
||||
<div class="form-control mb-2">
|
||||
<label for="name">Name</label>
|
||||
<label for="name">{$t('adventures.name')}</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
|
@ -147,7 +147,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="form-control mb-2">
|
||||
<label for="content">Date</label>
|
||||
<label for="content">{$t('adventures.date')}</label>
|
||||
<input
|
||||
type="date"
|
||||
id="date"
|
||||
|
@ -163,7 +163,7 @@
|
|||
<input
|
||||
type="text"
|
||||
id="new_item"
|
||||
placeholder="New Item"
|
||||
placeholder={$t('checklist.new_item')}
|
||||
name="new_item"
|
||||
bind:value={newItem}
|
||||
class="input input-bordered w-full max-w-xs mt-1"
|
||||
|
@ -179,12 +179,12 @@
|
|||
class="btn btn-sm btn-primary absolute right-0 mt-2.5 mr-4"
|
||||
on:click={addItem}
|
||||
>
|
||||
Add
|
||||
{$t('adventures.add')}
|
||||
</button>
|
||||
</div>
|
||||
{#if items.length > 0}
|
||||
<div class="divider"></div>
|
||||
<h2 class=" text-xl font-semibold mb-4 -mt-3">Items</h2>
|
||||
<h2 class=" text-xl font-semibold mb-4 -mt-3">{$t('checklist.items')}</h2>
|
||||
{/if}
|
||||
|
||||
{#each items as item, i}
|
||||
|
@ -202,7 +202,7 @@
|
|||
class="btn btn-sm btn-error absolute right-0 mt-2.5 mr-4"
|
||||
on:click={() => removeItem(i)}
|
||||
>
|
||||
Remove
|
||||
{$t('adventures.remove')}
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
|
@ -226,8 +226,8 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
<button class="btn btn-primary mr-1" on:click={save}>Save</button>
|
||||
<button class="btn btn-neutral" on:click={close}>Close</button>
|
||||
<button class="btn btn-primary mr-1" on:click={save}>{$t('notes.save')}</button>
|
||||
<button class="btn btn-neutral" on:click={close}>{$t('about.close')}</button>
|
||||
|
||||
{#if collection.is_public}
|
||||
<div role="alert" class="alert mt-4">
|
||||
|
@ -244,14 +244,14 @@
|
|||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
></path>
|
||||
</svg>
|
||||
<span>This checklist is public because it is in a public collection.</span>
|
||||
<span>{$t('checklist.checklist_public')}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</form>
|
||||
{:else}
|
||||
<form>
|
||||
<div class="form-control mb-2">
|
||||
<label for="name">Name</label>
|
||||
<label for="name">{$t('adventures.name')}</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
|
@ -261,7 +261,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="form-control mb-2">
|
||||
<label for="content">Date</label>
|
||||
<label for="content">{$t('adventures.date')}</label>
|
||||
<input
|
||||
type="date"
|
||||
id="date"
|
||||
|
@ -276,7 +276,7 @@
|
|||
|
||||
{#if items.length > 0}
|
||||
<div class="divider"></div>
|
||||
<h2 class=" text-xl font-semibold mb-4 -mt-3">Items</h2>
|
||||
<h2 class=" text-xl font-semibold mb-4 -mt-3">{$t('checklist.items')}</h2>
|
||||
{/if}
|
||||
|
||||
{#each items as item, i}
|
||||
|
@ -299,7 +299,7 @@
|
|||
</div>
|
||||
{/each}
|
||||
|
||||
<button class="btn btn-neutral" on:click={close}>Close</button>
|
||||
<button class="btn btn-neutral" on:click={close}>{$t('about.close')}</button>
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
import Launch from '~icons/mdi/launch';
|
||||
import TrashCanOutline from '~icons/mdi/trash-can-outline';
|
||||
|
||||
import FileDocumentEdit from '~icons/mdi/file-document-edit';
|
||||
import ArchiveArrowDown from '~icons/mdi/archive-arrow-down';
|
||||
|
@ -11,6 +10,7 @@
|
|||
import { goto } from '$app/navigation';
|
||||
import type { Adventure, Collection } from '$lib/types';
|
||||
import { addToast } from '$lib/toasts';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
import Plus from '~icons/mdi/plus';
|
||||
import DotsHorizontal from '~icons/mdi/dots-horizontal';
|
||||
|
@ -39,8 +39,11 @@
|
|||
body: JSON.stringify({ is_archived: is_archived })
|
||||
});
|
||||
if (res.ok) {
|
||||
console.log(`Collection ${is_archived ? 'archived' : 'unarchived'}`);
|
||||
addToast('info', `Collection ${is_archived ? 'archived' : 'unarchived'} successfully!`);
|
||||
if (is_archived) {
|
||||
addToast('info', $t('adventures.archived_collection_message'));
|
||||
} else {
|
||||
addToast('info', $t('adventures.unarchived_collection_message'));
|
||||
}
|
||||
dispatch('delete', collection.id);
|
||||
} else {
|
||||
console.log('Error archiving collection');
|
||||
|
@ -57,8 +60,7 @@
|
|||
}
|
||||
});
|
||||
if (res.ok) {
|
||||
console.log('Collection deleted');
|
||||
addToast('info', 'Collection deleted successfully!');
|
||||
addToast('info', $t('adventures.delete_collection_success'));
|
||||
dispatch('delete', collection.id);
|
||||
} else {
|
||||
console.log('Error deleting collection');
|
||||
|
@ -70,9 +72,9 @@
|
|||
|
||||
{#if isWarningModalOpen}
|
||||
<DeleteWarning
|
||||
title="Delete Collection"
|
||||
button_text="Delete"
|
||||
description="Are you sure you want to delete this collection? This will also delete all of the linked adventures. This action cannot be undone."
|
||||
title={$t('adventures.delete_collection')}
|
||||
button_text={$t('adventures.delete')}
|
||||
description={$t('adventures.delete_collection_warning')}
|
||||
is_warning={true}
|
||||
on:close={() => (isWarningModalOpen = false)}
|
||||
on:confirm={deleteCollection}
|
||||
|
@ -97,20 +99,24 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="inline-flex gap-2 mb-2">
|
||||
<div class="badge badge-secondary">{collection.is_public ? 'Public' : 'Private'}</div>
|
||||
<div class="badge badge-secondary">
|
||||
{collection.is_public ? $t('adventures.public') : $t('adventures.private')}
|
||||
</div>
|
||||
{#if collection.is_archived}
|
||||
<div class="badge badge-warning">Archived</div>
|
||||
<div class="badge badge-warning">{$t('adventures.archived')}</div>
|
||||
{/if}
|
||||
</div>
|
||||
<p>{collection.adventures.length} Adventures</p>
|
||||
<p>{collection.adventures.length} {$t('navbar.adventures')}</p>
|
||||
{#if collection.start_date && collection.end_date}
|
||||
<p>
|
||||
Dates: {new Date(collection.start_date).toLocaleDateString(undefined, { timeZone: 'UTC' })} -
|
||||
{$t('adventures.dates')}: {new Date(collection.start_date).toLocaleDateString(undefined, {
|
||||
timeZone: 'UTC'
|
||||
})} -
|
||||
{new Date(collection.end_date).toLocaleDateString(undefined, { timeZone: 'UTC' })}
|
||||
</p>
|
||||
<!-- display the duration in days -->
|
||||
<p>
|
||||
Duration: {Math.floor(
|
||||
{$t('adventures.duration')}: {Math.floor(
|
||||
(new Date(collection.end_date).getTime() - new Date(collection.start_date).getTime()) /
|
||||
(1000 * 60 * 60 * 24)
|
||||
) + 1}{' '}
|
||||
|
@ -136,23 +142,23 @@
|
|||
<button
|
||||
class="btn btn-neutral mb-2"
|
||||
on:click={() => goto(`/collections/${collection.id}`)}
|
||||
><Launch class="w-5 h-5 mr-1" />Open Details</button
|
||||
><Launch class="w-5 h-5 mr-1" />{$t('adventures.open_details')}</button
|
||||
>
|
||||
{#if !collection.is_archived}
|
||||
<button class="btn btn-neutral mb-2" on:click={editAdventure}>
|
||||
<FileDocumentEdit class="w-6 h-6" />Edit Collection
|
||||
<FileDocumentEdit class="w-6 h-6" />{$t('adventures.edit_collection')}
|
||||
</button>
|
||||
<button class="btn btn-neutral mb-2" on:click={() => (isShareModalOpen = true)}>
|
||||
<FileDocumentEdit class="w-6 h-6" />Share
|
||||
<FileDocumentEdit class="w-6 h-6" />{$t('adventures.share')}
|
||||
</button>
|
||||
{/if}
|
||||
{#if collection.is_archived}
|
||||
<button class="btn btn-neutral mb-2" on:click={() => archiveCollection(false)}>
|
||||
<ArchiveArrowUp class="w-6 h-6 mr-1" />Unarchive
|
||||
<ArchiveArrowUp class="w-6 h-6 mr-1" />{$t('adventures.unarchive')}
|
||||
</button>
|
||||
{:else}
|
||||
<button class="btn btn-neutral mb-2" on:click={() => archiveCollection(true)}>
|
||||
<ArchiveArrowDown class="w-6 h-6 mr" />Archive
|
||||
<ArchiveArrowDown class="w-6 h-6 mr" />{$t('adventures.archive')}
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
|
@ -160,14 +166,14 @@
|
|||
data-umami-event="Delete Adventure"
|
||||
class="btn btn-warning"
|
||||
on:click={() => (isWarningModalOpen = true)}
|
||||
><TrashCan class="w-6 h-6" />Delete</button
|
||||
><TrashCan class="w-6 h-6" />{$t('adventures.delete')}</button
|
||||
>
|
||||
{/if}
|
||||
{#if type == 'viewonly'}
|
||||
<button
|
||||
class="btn btn-neutral mb-2"
|
||||
on:click={() => goto(`/collections/${collection.id}`)}
|
||||
><Launch class="w-5 h-5 mr-1" />Open Details</button
|
||||
><Launch class="w-5 h-5 mr-1" />{$t('adventures.open_details')}</button
|
||||
>
|
||||
{/if}
|
||||
</ul>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import { onMount } from 'svelte';
|
||||
import CollectionCard from './CollectionCard.svelte';
|
||||
let modal: HTMLDialogElement;
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
let collections: Collection[] = [];
|
||||
|
||||
|
@ -44,15 +45,15 @@
|
|||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div class="modal-box w-11/12 max-w-5xl" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
||||
<h1 class="text-center font-bold text-4xl mb-6">My Collections</h1>
|
||||
<h1 class="text-center font-bold text-4xl mb-6">{$t('adventures.my_collections')}</h1>
|
||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||
{#each collections as collection}
|
||||
<CollectionCard {collection} type="link" on:link={link} />
|
||||
{/each}
|
||||
{#if collections.length === 0}
|
||||
<p class="text-center text-lg">No collections found to add this adventure to.</p>
|
||||
<p class="text-center text-lg">{$t('adventures.no_collections_found')}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<button class="btn btn-primary" on:click={close}>Close</button>
|
||||
<button class="btn btn-primary" on:click={close}>{$t('about.close')}</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
|
|
@ -23,12 +23,26 @@
|
|||
</figure>
|
||||
<div class="card-body">
|
||||
<h2 class="card-title overflow-ellipsis">{country.name}</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#if country.subregion}
|
||||
<div class="badge badge-primary">{country.subregion}</div>
|
||||
{/if}
|
||||
{#if country.capital}
|
||||
<div class="badge badge-secondary"><MapMarkerStar class="-ml-1 mr-1" />{country.capital}</div>
|
||||
<div class="badge badge-secondary">
|
||||
<MapMarkerStar class="-ml-1 mr-1" />{country.capital}
|
||||
</div>
|
||||
{/if}
|
||||
{#if country.num_visits > 0 && country.num_visits != country.num_regions}
|
||||
<div class="badge badge-accent">
|
||||
Visited {country.num_visits} Region{country.num_visits > 1 ? 's' : ''}
|
||||
</div>
|
||||
{:else if country.num_visits > 0 && country.num_visits === country.num_regions}
|
||||
<div class="badge badge-success">Completed</div>
|
||||
{:else}
|
||||
<div class="badge badge-error">Not Visited</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="card-actions justify-end">
|
||||
<!-- <button class="btn btn-info" on:click={moreInfo}>More Info</button> -->
|
||||
<button class="btn btn-primary" on:click={nav}>Open</button>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { createEventDispatcher } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
let modal: HTMLDialogElement;
|
||||
|
||||
export let title: string;
|
||||
|
@ -41,6 +42,6 @@
|
|||
<button class="btn btn-{is_warning ? 'warning' : 'primary'} mr-2" on:click={confirm}
|
||||
>{button_text}</button
|
||||
>
|
||||
<button class="btn btn-neutral" on:click={close}>Cancel</button>
|
||||
<button class="btn btn-neutral" on:click={close}>{$t('adventures.cancel')}</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<script lang="ts">
|
||||
export let collectionToEdit: Collection;
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import type { Adventure, Collection } from '$lib/types';
|
||||
import type { Collection } from '$lib/types';
|
||||
import { t } from 'svelte-i18n';
|
||||
const dispatch = createEventDispatcher();
|
||||
import { onMount } from 'svelte';
|
||||
import { addToast } from '$lib/toasts';
|
||||
|
@ -41,17 +42,17 @@
|
|||
|
||||
if (collectionToEdit.end_date && collectionToEdit.start_date) {
|
||||
if (new Date(collectionToEdit.start_date) > new Date(collectionToEdit.end_date)) {
|
||||
addToast('error', 'Start date must be before end date');
|
||||
addToast('error', $t('adventures.start_before_end_error'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (collectionToEdit.end_date && !collectionToEdit.start_date) {
|
||||
addToast('error', 'Please provide a start date');
|
||||
addToast('error', $t('adventures.no_start_date'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (collectionToEdit.start_date && !collectionToEdit.end_date) {
|
||||
addToast('error', 'Please provide an end date');
|
||||
addToast('error', $t('adventures.no_end_date'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -66,12 +67,12 @@
|
|||
console.log(data);
|
||||
|
||||
if (data) {
|
||||
addToast('success', 'Adventure edited successfully!');
|
||||
addToast('success', $t('collection.collection_edit_success'));
|
||||
dispatch('saveEdit', collectionToEdit);
|
||||
close();
|
||||
} else {
|
||||
addToast('warning', 'Error editing adventure');
|
||||
console.log('Error editing adventure');
|
||||
addToast('warning', $t('collection.error_editing_collection'));
|
||||
console.log('Error editing collection');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +82,7 @@
|
|||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div class="modal-box" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
||||
<h3 class="font-bold text-lg">Edit Collection: {originalName}</h3>
|
||||
<h3 class="font-bold text-lg">{$t('adventures.edit_collection')}: {originalName}</h3>
|
||||
<div
|
||||
class="modal-action items-center"
|
||||
style="display: flex; flex-direction: column; align-items: center; width: 100%;"
|
||||
|
@ -97,7 +98,7 @@
|
|||
bind:value={collectionToEdit.id}
|
||||
class="input input-bordered w-full max-w-xs mt-1"
|
||||
/>
|
||||
<label for="name">Name</label><br />
|
||||
<label for="name">{$t('adventures.name')}</label><br />
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
|
@ -107,7 +108,9 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="date">Description <Notebook class="inline-block -mt-1 mb-1 w-6 h-6" /></label
|
||||
<label for="date"
|
||||
>{$t('adventures.description')}
|
||||
<Notebook class="inline-block -mt-1 mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<div class="flex">
|
||||
<input
|
||||
|
@ -127,7 +130,8 @@
|
|||
> -->
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="start_date">Start Date <Calendar class="inline-block mb-1 w-6 h-6" /></label
|
||||
<label for="start_date"
|
||||
>{$t('adventures.start_date')} <Calendar class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<input
|
||||
type="date"
|
||||
|
@ -138,8 +142,9 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="end_date">End Date <Calendar class="inline-block mb-1 w-6 h-6" /></label><br
|
||||
/>
|
||||
<label for="end_date"
|
||||
>{$t('adventures.end_date')} <Calendar class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<input
|
||||
type="date"
|
||||
id="end_date"
|
||||
|
@ -149,7 +154,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="end_date">Link </label><br />
|
||||
<label for="end_date">{$t('adventures.link')} </label><br />
|
||||
<input
|
||||
type="url"
|
||||
id="link"
|
||||
|
@ -160,8 +165,9 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="is_public">Public <Earth class="inline-block -mt-1 mb-1 w-6 h-6" /></label><br
|
||||
/>
|
||||
<label for="is_public"
|
||||
>{$t('adventures.public')} <Earth class="inline-block -mt-1 mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle toggle-primary"
|
||||
|
@ -173,7 +179,7 @@
|
|||
|
||||
{#if collectionToEdit.is_public}
|
||||
<div class="bg-neutral p-4 rounded-md shadow-sm">
|
||||
<p class=" font-semibold">Share this Adventure!</p>
|
||||
<p class=" font-semibold">{$t('adventures.share_adventure')}</p>
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-card-foreground font-mono">
|
||||
{window.location.origin}/collections/{collectionToEdit.id}
|
||||
|
@ -187,7 +193,7 @@
|
|||
}}
|
||||
class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 h-10 px-4 py-2"
|
||||
>
|
||||
Copy Link
|
||||
{$t('adventures.copy_link')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -195,7 +201,7 @@
|
|||
|
||||
<button type="submit" class="btn btn-primary mr-4 mt-4" on:click={submit}>Edit</button>
|
||||
<!-- if there is a button in form, it will close the modal -->
|
||||
<button class="btn mt-4" on:click={close}>Close</button>
|
||||
<button class="btn mt-4" on:click={close}>{$t('about.close')}</button>
|
||||
</form>
|
||||
<div class="flex items-center justify-center flex-wrap gap-4 mt-4"></div>
|
||||
</div>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import { onMount } from 'svelte';
|
||||
import { addToast } from '$lib/toasts';
|
||||
let modal: HTMLDialogElement;
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
console.log(transportationToEdit.id);
|
||||
|
||||
|
@ -64,7 +65,7 @@
|
|||
transportationToEdit.date &&
|
||||
transportationToEdit.date > transportationToEdit.end_date
|
||||
) {
|
||||
addToast('error', 'End date cannot be before start date');
|
||||
addToast('error', $t('adventures.start_before_end_error'));
|
||||
return;
|
||||
}
|
||||
// make sure end_date has a start_date
|
||||
|
@ -83,11 +84,11 @@
|
|||
|
||||
transportationToEdit = result;
|
||||
|
||||
addToast('success', 'Transportation edited successfully!');
|
||||
addToast('success', $t('transportation.transportation_edit_success'));
|
||||
dispatch('saveEdit', transportationToEdit);
|
||||
close();
|
||||
} else {
|
||||
addToast('error', 'Error editing transportaion');
|
||||
addToast('error', $t('transportation.error_editing_transportation'));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -96,7 +97,7 @@
|
|||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div class="modal-box" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
||||
<h3 class="font-bold text-lg">Edit Transportation: {originalName}</h3>
|
||||
<h3 class="font-bold text-lg">{$t('transportation.edit_transportation')}: {originalName}</h3>
|
||||
<div
|
||||
class="modal-action items-center"
|
||||
style="display: flex; flex-direction: column; align-items: center; width: 100%;"
|
||||
|
@ -122,26 +123,28 @@
|
|||
class="input input-bordered w-full max-w-xs mt-1"
|
||||
/>
|
||||
<div class="mb-2">
|
||||
<label for="type">Type <PlaneCar class="inline-block mb-1 w-6 h-6" /></label><br />
|
||||
<label for="type"
|
||||
>{$t('transportation.type')} <PlaneCar class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<select
|
||||
class="select select-bordered w-full max-w-xs"
|
||||
name="type"
|
||||
id="type"
|
||||
bind:value={transportationToEdit.type}
|
||||
>
|
||||
<option disabled selected>Transport Type</option>
|
||||
<option value="car">Car</option>
|
||||
<option value="plane">Plane</option>
|
||||
<option value="train">Train</option>
|
||||
<option value="bus">Bus</option>
|
||||
<option value="boat">Boat</option>
|
||||
<option value="bike">Bike</option>
|
||||
<option value="walking">Walking</option>
|
||||
<option value="other">Other</option>
|
||||
<option disabled selected>{$t('transportation.type')}</option>
|
||||
<option value="car">{$t('transportation.modes.car')}</option>
|
||||
<option value="plane">{$t('transportation.modes.plane')}</option>
|
||||
<option value="train">{$t('transportation.modes.train')}</option>
|
||||
<option value="bus">{$t('transportation.modes.bus')}</option>
|
||||
<option value="boat">{$t('transportation.modes.boat')}</option>
|
||||
<option value="bike">{$t('transportation.modes.bike')}</option>
|
||||
<option value="walking">{$t('transportation.modes.walking')}</option>
|
||||
<option value="other">{$t('transportation.modes.other')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<label for="name">Name</label><br />
|
||||
<label for="name">{$t('adventures.name')}</label><br />
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
|
@ -151,7 +154,9 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="date">Description <Notebook class="inline-block -mt-1 mb-1 w-6 h-6" /></label
|
||||
<label for="date"
|
||||
>{$t('adventures.description')}
|
||||
<Notebook class="inline-block -mt-1 mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<div class="flex">
|
||||
<input
|
||||
|
@ -164,9 +169,10 @@
|
|||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="start_date"
|
||||
>{transportationToEdit.date ? 'Start ' : ''}Date & Time <Calendar
|
||||
class="inline-block mb-1 w-6 h-6"
|
||||
/></label
|
||||
>{transportationToEdit.date ? `${$t('transportation.start')} ` : ''}{$t(
|
||||
'transportation.date_and_time'
|
||||
)}
|
||||
<Calendar class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<input
|
||||
type="datetime-local"
|
||||
|
@ -181,7 +187,8 @@
|
|||
{#if transportationToEdit.date}
|
||||
<div class="mb-2">
|
||||
<label for="end_date"
|
||||
>End Date & Time <Calendar class="inline-block mb-1 w-6 h-6" /></label
|
||||
>{$t('transportation.end_date_time')}
|
||||
<Calendar class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<input
|
||||
type="datetime-local"
|
||||
|
@ -195,7 +202,9 @@
|
|||
</div>
|
||||
{/if}
|
||||
<div class="mb-2">
|
||||
<label for="rating">Rating <Star class="inline-block mb-1 w-6 h-6" /></label><br />
|
||||
<label for="rating"
|
||||
>{$t('adventures.rating')} <Star class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<input
|
||||
type="number"
|
||||
max="5"
|
||||
|
@ -207,7 +216,9 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="rating">Link <LinkVariant class="inline-block mb-1 w-6 h-6" /></label><br />
|
||||
<label for="rating"
|
||||
>{$t('adventures.link')} <LinkVariant class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<input
|
||||
type="url"
|
||||
id="link"
|
||||
|
@ -220,7 +231,8 @@
|
|||
{#if transportationToEdit.type == 'plane'}
|
||||
<div class="mb-2">
|
||||
<label for="flight_number"
|
||||
>Flight Number <Airplane class="inline-block mb-1 w-6 h-6" /></label
|
||||
>{$t('transportation.flight_number')}
|
||||
<Airplane class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<input
|
||||
type="text"
|
||||
|
@ -232,7 +244,9 @@
|
|||
</div>
|
||||
{/if}
|
||||
<div class="mb-2">
|
||||
<label for="rating">From Location <MapMarker class="inline-block mb-1 w-6 h-6" /></label
|
||||
<label for="rating"
|
||||
>{$t('transportation.from_location')}
|
||||
<MapMarker class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<input
|
||||
type="text"
|
||||
|
@ -243,7 +257,9 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="rating">To Location <MapMarker class="inline-block mb-1 w-6 h-6" /></label
|
||||
<label for="rating"
|
||||
>{$t('transportation.to_location')}
|
||||
<MapMarker class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<input
|
||||
type="text"
|
||||
|
@ -255,9 +271,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary mr-4 mt-4">Edit</button>
|
||||
<button type="submit" class="btn btn-primary mr-4 mt-4">{$t('transportation.edit')}</button>
|
||||
<!-- if there is a button in form, it will close the modal -->
|
||||
<button class="btn mt-4" on:click={close}>Close</button>
|
||||
<button class="btn mt-4" on:click={close}>{$t('about.close')}</button>
|
||||
</form>
|
||||
<div class="flex items-center justify-center flex-wrap gap-4 mt-4"></div>
|
||||
</div>
|
||||
|
|
56
frontend/src/lib/components/ImageInfoModal.svelte
Normal file
56
frontend/src/lib/components/ImageInfoModal.svelte
Normal file
|
@ -0,0 +1,56 @@
|
|||
<script lang="ts">
|
||||
import type { Background } from '$lib/types';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
import { onMount } from 'svelte';
|
||||
let modal: HTMLDialogElement;
|
||||
export let background: Background;
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
onMount(() => {
|
||||
modal = document.getElementById('my_modal_1') as HTMLDialogElement;
|
||||
if (modal) {
|
||||
modal.showModal();
|
||||
}
|
||||
});
|
||||
|
||||
function close() {
|
||||
dispatch('close');
|
||||
}
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Escape') {
|
||||
dispatch('close');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<dialog id="my_modal_1" class="modal">
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div class="modal-box" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
||||
<h3 class="font-bold text-lg">
|
||||
{$t('settings.about_this_background')}<span class=" inline-block"></span>
|
||||
</h3>
|
||||
<div class="flex flex-col items-center">
|
||||
{#if background.author != ''}
|
||||
<p class="text-center mt-2">{$t('settings.photo_by')} {background.author}</p>
|
||||
{/if}
|
||||
{#if background.location != ''}
|
||||
<p class="text-center">{$t('adventures.location')}: {background.location}</p>
|
||||
{/if}
|
||||
<p class="text-center mt-4">
|
||||
<a
|
||||
href="https://discord.gg/wRbQ9Egr8C"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-blue-500 hover:underline"
|
||||
>
|
||||
{$t('settings.join_discord')}
|
||||
</a>
|
||||
{$t('settings.join_discord_desc')}
|
||||
</p>
|
||||
</div>
|
||||
<button class="btn btn-primary" on:click={close}>{$t('about.close')}</button>
|
||||
</div>
|
||||
</dialog>
|
|
@ -8,18 +8,25 @@
|
|||
import WeatherSunny from '~icons/mdi/weather-sunny';
|
||||
import WeatherNight from '~icons/mdi/weather-night';
|
||||
import Forest from '~icons/mdi/forest';
|
||||
import Flower from '~icons/mdi/flower';
|
||||
import Water from '~icons/mdi/water';
|
||||
import AboutModal from './AboutModal.svelte';
|
||||
import AccountMultiple from '~icons/mdi/account-multiple';
|
||||
import Avatar from './Avatar.svelte';
|
||||
import PaletteOutline from '~icons/mdi/palette-outline';
|
||||
import { page } from '$app/stores';
|
||||
import { t, locale, locales } from 'svelte-i18n';
|
||||
|
||||
let query: string = '';
|
||||
|
||||
let isAboutModalOpen: boolean = false;
|
||||
|
||||
const submitLocaleChange = (event: Event) => {
|
||||
const select = event.target as HTMLSelectElement;
|
||||
const newLocale = select.value;
|
||||
document.cookie = `locale=${newLocale}; path=/`;
|
||||
locale.set(newLocale);
|
||||
window.location.reload();
|
||||
};
|
||||
const submitUpdateTheme: SubmitFunction = ({ action }) => {
|
||||
const theme = action.searchParams.get('theme');
|
||||
console.log('theme', theme);
|
||||
|
@ -72,34 +79,38 @@
|
|||
>
|
||||
{#if data.user}
|
||||
<li>
|
||||
<button on:click={() => goto('/adventures')}>Adventures</button>
|
||||
<button on:click={() => goto('/adventures')}>{$t('navbar.adventures')}</button>
|
||||
</li>
|
||||
<li>
|
||||
<button on:click={() => goto('/collections')}>Collections</button>
|
||||
<button on:click={() => goto('/collections')}>{$t('navbar.collections')}</button>
|
||||
</li>
|
||||
<li>
|
||||
<button on:click={() => goto('/worldtravel')}>World Travel</button>
|
||||
<button on:click={() => goto('/worldtravel')}>{$t('navbar.worldtravel')}</button>
|
||||
</li>
|
||||
<li>
|
||||
<button on:click={() => goto('/map')}>Map</button>
|
||||
<button on:click={() => goto('/map')}>{$t('navbar.map')}</button>
|
||||
</li>
|
||||
<li>
|
||||
<button on:click={() => goto('/users')}>Users</button>
|
||||
<button on:click={() => goto('/users')}>{$t('navbar.users')}</button>
|
||||
</li>
|
||||
{/if}
|
||||
|
||||
{#if !data.user}
|
||||
<li>
|
||||
<button class="btn btn-primary" on:click={() => goto('/login')}>Login</button>
|
||||
<button class="btn btn-primary" on:click={() => goto('/login')}
|
||||
>{$t('auth.login')}</button
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-primary" on:click={() => goto('/signup')}>Signup</button>
|
||||
<button class="btn btn-primary" on:click={() => goto('/signup')}
|
||||
>{$t('auth.signup')}</button
|
||||
>
|
||||
</li>
|
||||
{/if}
|
||||
|
||||
<form class="flex gap-2">
|
||||
<label class="input input-bordered flex items-center gap-2">
|
||||
<input type="text" bind:value={query} class="grow" placeholder="Search" />
|
||||
<input type="text" bind:value={query} class="grow" placeholder={$t('navbar.search')} />
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
@ -114,7 +125,9 @@
|
|||
/>
|
||||
</svg>
|
||||
</label>
|
||||
<button on:click={searchGo} type="submit" class="btn btn-primary">Search</button>
|
||||
<button on:click={searchGo} type="submit" class="btn btn-primary"
|
||||
>{$t('navbar.search')}</button
|
||||
>
|
||||
</form>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -126,17 +139,22 @@
|
|||
<ul class="menu menu-horizontal px-1 gap-2">
|
||||
{#if data.user}
|
||||
<li>
|
||||
<button class="btn btn-neutral" on:click={() => goto('/adventures')}>Adventures</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-neutral" on:click={() => goto('/collections')}>Collections</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-neutral" on:click={() => goto('/worldtravel')}>World Travel</button
|
||||
<button class="btn btn-neutral" on:click={() => goto('/adventures')}
|
||||
>{$t('navbar.adventures')}</button
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-neutral" on:click={() => goto('/map')}>Map</button>
|
||||
<button class="btn btn-neutral" on:click={() => goto('/collections')}
|
||||
>{$t('navbar.collections')}</button
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-neutral" on:click={() => goto('/worldtravel')}
|
||||
>{$t('navbar.worldtravel')}</button
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-neutral" on:click={() => goto('/map')}>{$t('navbar.map')}</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-neutral" on:click={() => goto('/users')}
|
||||
|
@ -147,16 +165,19 @@
|
|||
|
||||
{#if !data.user}
|
||||
<li>
|
||||
<button class="btn btn-primary" on:click={() => goto('/login')}>Login</button>
|
||||
<button class="btn btn-primary" on:click={() => goto('/login')}>{$t('auth.login')}</button
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-primary" on:click={() => goto('/signup')}>Signup</button>
|
||||
<button class="btn btn-primary" on:click={() => goto('/signup')}
|
||||
>{$t('auth.signup')}</button
|
||||
>
|
||||
</li>
|
||||
{/if}
|
||||
|
||||
<form class="flex gap-2">
|
||||
<label class="input input-bordered flex items-center gap-2">
|
||||
<input type="text" bind:value={query} class="grow" placeholder="Search" />
|
||||
<input type="text" bind:value={query} class="grow" placeholder={$t('navbar.search')} />
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
@ -171,7 +192,9 @@
|
|||
/>
|
||||
</svg>
|
||||
</label>
|
||||
<button on:click={searchGo} type="submit" class="btn btn-neutral">Search</button>
|
||||
<button on:click={searchGo} type="submit" class="btn btn-neutral"
|
||||
>{$t('navbar.search')}</button
|
||||
>
|
||||
</form>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -188,42 +211,61 @@
|
|||
tabindex="0"
|
||||
class="dropdown-content bg-neutral text-neutral-content z-[1] menu p-2 shadow rounded-box w-52"
|
||||
>
|
||||
<button class="btn" on:click={() => (isAboutModalOpen = true)}>About AdventureLog</button>
|
||||
<button class="btn" on:click={() => (isAboutModalOpen = true)}>{$t('navbar.about')}</button>
|
||||
<button
|
||||
class="btn btn-sm mt-2"
|
||||
on:click={() => (window.location.href = 'https://docs.adventurelog.app/')}
|
||||
>Documentation</button
|
||||
>{$t('navbar.documentation')}</button
|
||||
>
|
||||
<button
|
||||
class="btn btn-sm mt-2"
|
||||
on:click={() => (window.location.href = 'https://discord.gg/wRbQ9Egr8C')}>Discord</button
|
||||
on:click={() => (window.location.href = 'https://discord.gg/wRbQ9Egr8C')}
|
||||
>{$t('navbar.discord')}</button
|
||||
>
|
||||
<p class="font-bold m-4 text-lg">Theme Selection</p>
|
||||
<p class="font-bold m-4 text-lg text-center">{$t('navbar.theme_selection')}</p>
|
||||
<form method="POST" use:enhance={submitUpdateTheme}>
|
||||
<li>
|
||||
<button formaction="/?/setTheme&theme=light"
|
||||
>Light<WeatherSunny class="w-6 h-6" />
|
||||
>{$t('navbar.themes.light')}<WeatherSunny class="w-6 h-6" />
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button formaction="/?/setTheme&theme=dark">Dark<WeatherNight class="w-6 h-6" /></button
|
||||
<button formaction="/?/setTheme&theme=dark"
|
||||
>{$t('navbar.themes.dark')}<WeatherNight class="w-6 h-6" /></button
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<button formaction="/?/setTheme&theme=night"
|
||||
>Night<WeatherNight class="w-6 h-6" /></button
|
||||
>{$t('navbar.themes.night')}<WeatherNight class="w-6 h-6" /></button
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<button formaction="/?/setTheme&theme=forest">Forest<Forest class="w-6 h-6" /></button>
|
||||
<button formaction="/?/setTheme&theme=forest"
|
||||
>{$t('navbar.themes.forest')}<Forest class="w-6 h-6" /></button
|
||||
>
|
||||
<button formaction="/?/setTheme&theme=aestheticLight"
|
||||
>Aesthetic Light<PaletteOutline class="w-6 h-6" /></button
|
||||
>{$t('navbar.themes.aestetic-light')}<PaletteOutline class="w-6 h-6" /></button
|
||||
>
|
||||
<button formaction="/?/setTheme&theme=aestheticDark"
|
||||
>Aesthetic Dark<PaletteOutline class="w-6 h-6" /></button
|
||||
>{$t('navbar.themes.aestetic-dark')}<PaletteOutline class="w-6 h-6" /></button
|
||||
>
|
||||
<button formaction="/?/setTheme&theme=aqua"
|
||||
>{$t('navbar.themes.aqua')}<Water class="w-6 h-6" /></button
|
||||
>
|
||||
<button formaction="/?/setTheme&theme=aqua">Aqua<Water class="w-6 h-6" /></button>
|
||||
</li>
|
||||
<p class="font-bold m-4 text-lg text-center">{$t('navbar.language_selection')}</p>
|
||||
<form method="POST" use:enhance>
|
||||
<select
|
||||
class="select select-bordered w-full max-w-xs bg-base-100 text-base-content"
|
||||
on:change={submitLocaleChange}
|
||||
bind:value={$locale}
|
||||
>
|
||||
{#each $locales as loc}
|
||||
<option value={loc} class="text-base-content">{$t(`languages.${loc}`)}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<input type="hidden" name="locale" value={$locale} />
|
||||
</form>
|
||||
</form>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { createEventDispatcher } from 'svelte';
|
||||
import type { Adventure, Collection } from '$lib/types';
|
||||
import { onMount } from 'svelte';
|
||||
import { enhance } from '$app/forms';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { addToast } from '$lib/toasts';
|
||||
|
||||
import Calendar from '~icons/mdi/calendar';
|
||||
|
@ -45,18 +45,18 @@
|
|||
|
||||
// make sure that start_date is before end_date
|
||||
if (new Date(newCollection.start_date ?? '') > new Date(newCollection.end_date ?? '')) {
|
||||
addToast('error', 'Start date must be before end date');
|
||||
addToast('error', $t('adventures.start_before_end_error'));
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure end date has a start date
|
||||
if (newCollection.end_date && !newCollection.start_date) {
|
||||
addToast('error', 'Please provide a start date');
|
||||
addToast('error', $t('adventures.no_start_date'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (newCollection.start_date && !newCollection.end_date) {
|
||||
addToast('error', 'Please provide an end date');
|
||||
addToast('error', $t('adventures.no_end_date'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -80,10 +80,10 @@
|
|||
newCollection.user_id = user_id;
|
||||
console.log(newCollection);
|
||||
dispatch('create', newCollection);
|
||||
addToast('success', 'Collection created successfully!');
|
||||
addToast('success', $t('collection.collection_created'));
|
||||
close();
|
||||
} else {
|
||||
addToast('error', 'Error creating collection');
|
||||
addToast('error', $t('collection.error_creating_collection'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +95,7 @@
|
|||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<div class="modal-box" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
||||
<h3 class="font-bold text-lg">New Collection</h3>
|
||||
<h3 class="font-bold text-lg">{$t('collection.new_collection')}</h3>
|
||||
<div
|
||||
class="modal-action items-center"
|
||||
style="display: flex; flex-direction: column; align-items: center; width: 100%;"
|
||||
|
@ -107,7 +107,7 @@
|
|||
action="/collections?/create"
|
||||
>
|
||||
<div class="mb-2">
|
||||
<label for="name">Name</label><br />
|
||||
<label for="name">{$t('adventures.name')}</label><br />
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
|
@ -119,7 +119,9 @@
|
|||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="description"
|
||||
>Description<iconify-icon icon="mdi:notebook" class="text-lg ml-1 -mb-0.5"
|
||||
>{$t('adventures.description')}<iconify-icon
|
||||
icon="mdi:notebook"
|
||||
class="text-lg ml-1 -mb-0.5"
|
||||
></iconify-icon></label
|
||||
><br />
|
||||
<div class="flex">
|
||||
|
@ -132,7 +134,8 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="start_date">Start Date <Calendar class="inline-block mb-1 w-6 h-6" /></label
|
||||
<label for="start_date"
|
||||
>{$t('adventures.start_date')} <Calendar class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<input
|
||||
type="date"
|
||||
|
@ -143,8 +146,9 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="end_date">End Date <Calendar class="inline-block mb-1 w-6 h-6" /></label><br
|
||||
/>
|
||||
<label for="end_date"
|
||||
>{$t('adventures.end_date')} <Calendar class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<input
|
||||
type="date"
|
||||
id="end_date"
|
||||
|
@ -154,7 +158,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="end_date">Link </label><br />
|
||||
<label for="end_date">{$t('adventures.link')} </label><br />
|
||||
<input
|
||||
type="url"
|
||||
id="link"
|
||||
|
@ -164,8 +168,10 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<button type="submit" class="btn btn-primary mr-4 mt-4">Create</button>
|
||||
<button type="button" class="btn mt-4" on:click={close}>Close</button>
|
||||
<button type="submit" class="btn btn-primary mr-4 mt-4">
|
||||
{$t('collection.create')}
|
||||
</button>
|
||||
<button type="button" class="btn mt-4" on:click={close}>{$t('about.close')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -1,27 +1,11 @@
|
|||
<script lang="ts">
|
||||
// let newTransportation: Transportation = {
|
||||
// id:NaN,
|
||||
// user_id: NaN,
|
||||
// type: '',
|
||||
// name: '',
|
||||
// description: null,
|
||||
// rating: NaN,
|
||||
// link: null,
|
||||
// date: null,
|
||||
// flight_number: null,
|
||||
// from_location: null,
|
||||
// to_location: null,
|
||||
// is_public: false,
|
||||
// collection: null,
|
||||
// created_at: '',
|
||||
// updated_at: ''
|
||||
// };
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import type { Collection, Transportation } from '$lib/types';
|
||||
const dispatch = createEventDispatcher();
|
||||
import { onMount } from 'svelte';
|
||||
import { addToast } from '$lib/toasts';
|
||||
let modal: HTMLDialogElement;
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let collection: Collection;
|
||||
|
||||
|
@ -74,7 +58,7 @@
|
|||
|
||||
// make sure there is a start date if there is an end date
|
||||
if (formData.get('end_date') && !formData.get('date')) {
|
||||
addToast('error', 'Please provide a start date');
|
||||
addToast('error', $t('transportation.provide_start_date'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -86,11 +70,11 @@
|
|||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
|
||||
addToast('success', 'Transportation added successfully!');
|
||||
addToast('success', $t('transportation.transportation_added'));
|
||||
dispatch('add', result);
|
||||
close();
|
||||
} else {
|
||||
addToast('error', 'Error editing transportation');
|
||||
addToast('error', $t('transportation.error_editing_transportation'));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -99,22 +83,13 @@
|
|||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div class="modal-box" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
||||
<h3 class="font-bold text-lg">New Transportation</h3>
|
||||
<h3 class="font-bold text-lg">{$t('transportation.new_transportation')}</h3>
|
||||
<div
|
||||
class="modal-action items-center"
|
||||
style="display: flex; flex-direction: column; align-items: center; width: 100%;"
|
||||
>
|
||||
<form method="post" style="width: 100%;" on:submit={handleSubmit}>
|
||||
<div class="mb-2">
|
||||
<!-- <input
|
||||
type="text"
|
||||
id="id"
|
||||
name="id"
|
||||
hidden
|
||||
readonly
|
||||
bind:value={newTransportation.id}
|
||||
class="input input-bordered w-full max-w-xs mt-1"
|
||||
/> -->
|
||||
<input
|
||||
type="text"
|
||||
id="collection"
|
||||
|
@ -134,26 +109,28 @@
|
|||
class="input input-bordered w-full max-w-xs mt-1"
|
||||
/>
|
||||
<div class="mb-2">
|
||||
<label for="type">Type <PlaneCar class="inline-block mb-1 w-6 h-6" /></label><br />
|
||||
<label for="type"
|
||||
>{$t('transportation.type')} <PlaneCar class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<select
|
||||
class="select select-bordered w-full max-w-xs"
|
||||
name="type"
|
||||
id="type"
|
||||
bind:value={type}
|
||||
>
|
||||
<option disabled selected>Transport Type</option>
|
||||
<option value="car">Car</option>
|
||||
<option value="plane">Plane</option>
|
||||
<option value="train">Train</option>
|
||||
<option value="bus">Bus</option>
|
||||
<option value="boat">Boat</option>
|
||||
<option value="bike">Bike</option>
|
||||
<option value="walking">Walking</option>
|
||||
<option value="other">Other</option>
|
||||
<option disabled selected>{$t('transportation.type')}</option>
|
||||
<option value="car">{$t('transportation.modes.car')}</option>
|
||||
<option value="plane">{$t('transportation.modes.plane')}</option>
|
||||
<option value="train">{$t('transportation.modes.train')}</option>
|
||||
<option value="bus">{$t('transportation.modes.bus')}</option>
|
||||
<option value="boat">{$t('transportation.modes.boat')}</option>
|
||||
<option value="bike">{$t('transportation.modes.bike')}</option>
|
||||
<option value="walking">{$t('transportation.modes.walking')}</option>
|
||||
<option value="other">{$t('transportation.modes.other')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<label for="name">Name</label><br />
|
||||
<label for="name">{$t('adventures.name')}</label><br />
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
|
@ -162,7 +139,9 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="date">Description <Notebook class="inline-block -mt-1 mb-1 w-6 h-6" /></label
|
||||
<label for="date"
|
||||
>{$t('adventures.description')}
|
||||
<Notebook class="inline-block -mt-1 mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<div class="flex">
|
||||
<input
|
||||
|
@ -174,7 +153,8 @@
|
|||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="start_date"
|
||||
>Start Date & Time <Calendar class="inline-block mb-1 w-6 h-6" /></label
|
||||
>{$t('transportation.date_time')}
|
||||
<Calendar class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<input
|
||||
type="datetime-local"
|
||||
|
@ -188,7 +168,8 @@
|
|||
|
||||
<div class="mb-2">
|
||||
<label for="end_date"
|
||||
>End Date & Time <Calendar class="inline-block mb-1 w-6 h-6" /></label
|
||||
>{$t('transportation.end_date_time')}
|
||||
<Calendar class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<input
|
||||
type="datetime-local"
|
||||
|
@ -201,7 +182,9 @@
|
|||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<label for="rating">Rating <Star class="inline-block mb-1 w-6 h-6" /></label><br />
|
||||
<label for="rating"
|
||||
>{$t('adventures.rating')} <Star class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<input
|
||||
type="number"
|
||||
max="5"
|
||||
|
@ -212,7 +195,9 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="rating">Link <LinkVariant class="inline-block mb-1 w-6 h-6" /></label><br />
|
||||
<label for="rating"
|
||||
>{$t('adventures.link')} <LinkVariant class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<input
|
||||
type="url"
|
||||
id="link"
|
||||
|
@ -224,7 +209,8 @@
|
|||
{#if type == 'plane'}
|
||||
<div class="mb-2">
|
||||
<label for="flight_number"
|
||||
>Flight Number <Airplane class="inline-block mb-1 w-6 h-6" /></label
|
||||
>{$t('transportation.flight_number')}
|
||||
<Airplane class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<input
|
||||
type="text"
|
||||
|
@ -235,7 +221,9 @@
|
|||
</div>
|
||||
{/if}
|
||||
<div class="mb-2">
|
||||
<label for="rating">From Location <MapMarker class="inline-block mb-1 w-6 h-6" /></label
|
||||
<label for="rating"
|
||||
>{$t('transportation.from_location')}
|
||||
<MapMarker class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<input
|
||||
type="text"
|
||||
|
@ -245,7 +233,9 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="rating">To Location <MapMarker class="inline-block mb-1 w-6 h-6" /></label
|
||||
<label for="rating"
|
||||
>{$t('transportation.to_location')}
|
||||
<MapMarker class="inline-block mb-1 w-6 h-6" /></label
|
||||
><br />
|
||||
<input
|
||||
type="text"
|
||||
|
@ -256,9 +246,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary mr-4 mt-4">Edit</button>
|
||||
<button type="submit" class="btn btn-primary mr-4 mt-4">{$t('transportation.edit')}</button>
|
||||
<!-- if there is a button in form, it will close the modal -->
|
||||
<button class="btn mt-4" on:click={close}>Close</button>
|
||||
<button class="btn mt-4" on:click={close}>{$t('about.close')}</button>
|
||||
</form>
|
||||
<div class="flex items-center justify-center flex-wrap gap-4 mt-4"></div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import Lost from '$lib/assets/undraw_lost.svg';
|
||||
export let error: string | undefined;
|
||||
import { t } from 'svelte-i18n';
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
@ -11,12 +12,11 @@
|
|||
<img src={Lost} alt="Lost" class="w-1/2" />
|
||||
</div>
|
||||
<h1 class="mt-4 text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
|
||||
No adventures found
|
||||
{$t('adventures.no_adventures_found')}
|
||||
</h1>
|
||||
{#if !error}
|
||||
<p class="mt-4 text-muted-foreground">
|
||||
There are no adventures to display. Add some using the plus button at the bottom right or
|
||||
try changing filters!
|
||||
{$t('adventures.adventure_not_found')}
|
||||
</p>
|
||||
{:else}
|
||||
<p class="text-error mt-2">{error}</p>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { addToast } from '$lib/toasts';
|
||||
import type { Collection, Note, User } from '$lib/types';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
@ -22,10 +22,10 @@
|
|||
method: 'DELETE'
|
||||
});
|
||||
if (res.ok) {
|
||||
addToast('success', 'Note deleted successfully');
|
||||
addToast('success', $t('notes.note_deleted'));
|
||||
dispatch('delete', note.id);
|
||||
} else {
|
||||
addToast('Failed to delete note', 'error');
|
||||
addToast($t('notes.note_delete_error'), 'error');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -39,9 +39,12 @@
|
|||
{note.name}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="badge badge-primary">Note</div>
|
||||
<div class="badge badge-primary">{$t('adventures.note')}</div>
|
||||
{#if note.links && note.links.length > 0}
|
||||
<p>{note.links.length} {note.links.length > 1 ? 'Links' : 'Link'}</p>
|
||||
<p>
|
||||
{note.links.length}
|
||||
{note.links.length > 1 ? $t('adventures.links') : $t('adventures.link')}
|
||||
</p>
|
||||
{/if}
|
||||
{#if note.date && note.date !== ''}
|
||||
<div class="inline-flex items-center">
|
||||
|
@ -54,7 +57,7 @@
|
|||
><Launch class="w-6 h-6" />Open Details</button
|
||||
> -->
|
||||
<button class="btn btn-neutral-200 mb-2" on:click={editNote}>
|
||||
<Launch class="w-6 h-6" />Open
|
||||
<Launch class="w-6 h-6" />{$t('notes.open')}
|
||||
</button>
|
||||
{#if note.user_id == user?.pk || (collection && user && collection.shared_with.includes(user.uuid))}
|
||||
<button
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import { createEventDispatcher } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
import { onMount } from 'svelte';
|
||||
import ShareModal from './ShareModal.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
let modal: HTMLDialogElement;
|
||||
|
||||
export let note: Note | null = null;
|
||||
|
@ -18,7 +18,7 @@
|
|||
function addLink() {
|
||||
// check to make it a valid URL
|
||||
if (!isValidUrl(newLink)) {
|
||||
warning = 'Invalid URL';
|
||||
warning = $t('notes.invalid_url');
|
||||
return;
|
||||
} else {
|
||||
warning = null;
|
||||
|
@ -98,8 +98,7 @@
|
|||
}
|
||||
} else {
|
||||
let data = await res.json();
|
||||
console.error('Failed to save note', data);
|
||||
console.error('Failed to save note');
|
||||
console.error($t('notes.failed_to_save'), data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -109,15 +108,15 @@
|
|||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div class="modal-box" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
||||
<h3 class="font-bold text-lg">Note Editor</h3>
|
||||
<h3 class="font-bold text-lg">{$t('notes.note_editor')}</h3>
|
||||
{#if initialName}
|
||||
<p class="font-semibold text-md mb-2">Editing note {initialName}</p>
|
||||
<p class="font-semibold text-md mb-2">{$t('notes.editing_note')} {initialName}</p>
|
||||
{/if}
|
||||
|
||||
{#if (note && user?.pk == note?.user_id) || (collection && user && collection.shared_with.includes(user.uuid)) || !note}
|
||||
<form on:submit|preventDefault>
|
||||
<div class="form-control mb-2">
|
||||
<label for="name">Name</label>
|
||||
<label for="name">{$t('adventures.name')}</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
|
@ -126,7 +125,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="form-control mb-2">
|
||||
<label for="content">Date</label>
|
||||
<label for="content">{$t('adventures.date')}</label>
|
||||
<input
|
||||
type="date"
|
||||
id="date"
|
||||
|
@ -138,7 +137,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="form-control mb-2">
|
||||
<label for="content">Content</label>
|
||||
<label for="content">{$t('notes.content')}</label>
|
||||
<textarea
|
||||
id="content"
|
||||
class="textarea textarea-bordered"
|
||||
|
@ -147,11 +146,11 @@
|
|||
></textarea>
|
||||
</div>
|
||||
<div class="form-control mb-2">
|
||||
<label for="content">Links</label>
|
||||
<label for="content">{$t('adventures.links')}</label>
|
||||
<input
|
||||
type="url"
|
||||
class="input input-bordered w-full mb-1"
|
||||
placeholder="Add a link (e.g. https://example.com)"
|
||||
placeholder="{$t('notes.add_a_link')} (e.g. https://example.com)"
|
||||
bind:value={newLink}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
|
@ -160,7 +159,9 @@
|
|||
}
|
||||
}}
|
||||
/>
|
||||
<button type="button" class="btn btn-sm btn-primary" on:click={addLink}>Add</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" on:click={addLink}
|
||||
>{$t('adventures.add')}</button
|
||||
>
|
||||
</div>
|
||||
{#if newNote.links.length > 0}
|
||||
<ul class="list-none">
|
||||
|
@ -174,7 +175,7 @@
|
|||
newNote.links = newNote.links.filter((_, index) => index !== i);
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
{$t('adventures.remove')}
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
|
@ -200,8 +201,8 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
<button class="btn btn-primary mr-1" on:click={save}>Save</button>
|
||||
<button class="btn btn-neutral" on:click={close}>Close</button>
|
||||
<button class="btn btn-primary mr-1" on:click={save}>{$t('notes.save')}</button>
|
||||
<button class="btn btn-neutral" on:click={close}>{$t('about.close')}</button>
|
||||
|
||||
{#if collection.is_public}
|
||||
<div role="alert" class="alert mt-4">
|
||||
|
@ -218,14 +219,14 @@
|
|||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
></path>
|
||||
</svg>
|
||||
<span>This note is public because it is in a public collection.</span>
|
||||
<span>{$t('notes.note_public')}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</form>
|
||||
{:else}
|
||||
<form>
|
||||
<div class="form-control mb-2">
|
||||
<label for="name">Name</label>
|
||||
<label for="name">{$t('adventures.public')}</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
|
@ -235,7 +236,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="form-control mb-2">
|
||||
<label for="content">Date</label>
|
||||
<label for="content">{$t('adventures.date')}</label>
|
||||
<input
|
||||
type="date"
|
||||
id="date"
|
||||
|
@ -248,7 +249,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="form-control mb-2">
|
||||
<label for="content">Content</label>
|
||||
<label for="content">{$t('notes.content')}</label>
|
||||
<textarea
|
||||
id="content"
|
||||
class="textarea textarea-bordered"
|
||||
|
@ -258,7 +259,7 @@
|
|||
></textarea>
|
||||
</div>
|
||||
<div class="form-control mb-2">
|
||||
<label for="content">Links</label>
|
||||
<label for="content">{$t('adventures.links')}</label>
|
||||
</div>
|
||||
{#if newNote.links.length > 0}
|
||||
<ul class="list-none">
|
||||
|
@ -270,7 +271,7 @@
|
|||
</ul>
|
||||
{/if}
|
||||
|
||||
<button class="btn btn-neutral" on:click={close}>Close</button>
|
||||
<button class="btn btn-neutral" on:click={close}>{$t('about.close')}</button>
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -110,7 +110,7 @@
|
|||
</form>
|
||||
<h3 class="font-bold text-lg mb-4">Choose a Point</h3>
|
||||
<MapLibre
|
||||
style="https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
|
||||
style="https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
|
||||
class="relative aspect-[9/16] max-h-[70vh] w-full sm:aspect-video sm:max-h-full"
|
||||
standardControls
|
||||
>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import UserCard from './UserCard.svelte';
|
||||
import { addToast } from '$lib/toasts';
|
||||
let modal: HTMLDialogElement;
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let collection: Collection;
|
||||
|
||||
|
@ -25,7 +26,10 @@
|
|||
sharedWithUsers = sharedWithUsers.concat(user);
|
||||
collection.shared_with.push(user.uuid);
|
||||
notSharedWithUsers = notSharedWithUsers.filter((u) => u.uuid !== user.uuid);
|
||||
addToast('success', `Shared ${collection.name} with ${user.first_name} ${user.last_name}`);
|
||||
addToast(
|
||||
'success',
|
||||
`${$t('share.shared')} ${collection.name} ${$t('share.with')} ${user.first_name} ${user.last_name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,7 +44,10 @@
|
|||
notSharedWithUsers = notSharedWithUsers.concat(user);
|
||||
collection.shared_with = collection.shared_with.filter((u) => u !== user.uuid);
|
||||
sharedWithUsers = sharedWithUsers.filter((u) => u.uuid !== user.uuid);
|
||||
addToast('success', `Unshared ${collection.name} with ${user.first_name} ${user.last_name}`);
|
||||
addToast(
|
||||
'success',
|
||||
`${$t('share.unshared')} ${collection.name} ${$t('share.with')} ${user.first_name} ${user.last_name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,10 +82,10 @@
|
|||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div class="modal-box w-11/12 max-w-5xl" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
||||
<h3 class="font-bold text-lg">Share {collection.name}</h3>
|
||||
<p class="py-1">Share this collection with other users.</p>
|
||||
<h3 class="font-bold text-lg">{$t('adventures.share')} {collection.name}</h3>
|
||||
<p class="py-1">{$t('share.share_desc')}</p>
|
||||
<div class="divider"></div>
|
||||
<h3 class="font-bold text-md">Shared With</h3>
|
||||
<h3 class="font-bold text-md">{$t('share.shared_with')}</h3>
|
||||
<ul>
|
||||
{#each sharedWithUsers as user}
|
||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||
|
@ -92,11 +99,11 @@
|
|||
</div>
|
||||
{/each}
|
||||
{#if sharedWithUsers.length === 0}
|
||||
<p class="text-neutral-content">No users shared with</p>
|
||||
<p class="text-neutral-content">{$t('share.no_users_shared')}</p>
|
||||
{/if}
|
||||
</ul>
|
||||
<div class="divider"></div>
|
||||
<h3 class="font-bold text-md">Not Shared With</h3>
|
||||
<h3 class="font-bold text-md">{$t('share.not_shared_with')}</h3>
|
||||
<ul>
|
||||
{#each notSharedWithUsers as user}
|
||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||
|
@ -110,9 +117,9 @@
|
|||
</div>
|
||||
{/each}
|
||||
{#if notSharedWithUsers.length === 0}
|
||||
<p class="text-neutral-content">No users not shared with</p>
|
||||
<p class="text-neutral-content">{$t('share.no_users_shared')}</p>
|
||||
{/if}
|
||||
</ul>
|
||||
<button class="btn btn-primary mt-4" on:click={close}>Close</button>
|
||||
<button class="btn btn-primary mt-4" on:click={close}>{$t('about.close')}</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import FileDocumentEdit from '~icons/mdi/file-document-edit';
|
||||
import type { Collection, Transportation, User } from '$lib/types';
|
||||
import { addToast } from '$lib/toasts';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
import ArrowDownThick from '~icons/mdi/arrow-down-thick';
|
||||
|
||||
|
@ -25,10 +26,9 @@
|
|||
}
|
||||
});
|
||||
if (!res.ok) {
|
||||
console.log('Error deleting transportation');
|
||||
console.log($t('transportation.transportation_delete_error'));
|
||||
} else {
|
||||
console.log('Collection deleted');
|
||||
addToast('info', 'Transportation deleted successfully!');
|
||||
addToast('info', $t('transportation.transportation_deleted'));
|
||||
dispatch('delete', transportation.id);
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@
|
|||
>
|
||||
<div class="card-body">
|
||||
<h2 class="card-title overflow-ellipsis">{transportation.name}</h2>
|
||||
<div class="badge badge-secondary">{transportation.type}</div>
|
||||
<div class="badge badge-secondary">{$t(`transportation.modes.${transportation.type}`)}</div>
|
||||
<div>
|
||||
{#if transportation.from_location}
|
||||
<p class="break-words text-wrap">{transportation.from_location}</p>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export let appVersion = 'Web v0.7.0';
|
||||
export let versionChangelog = 'https://github.com/seanmorley15/AdventureLog/releases/tag/v0.7.0';
|
||||
export let appVersion = 'Web v0.7.1';
|
||||
export let versionChangelog = 'https://github.com/seanmorley15/AdventureLog/releases/tag/v0.7.1';
|
||||
export let appTitle = 'AdventureLog';
|
||||
export let copyrightYear = '2024';
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
import inspirationalQuotes from './json/quotes.json';
|
||||
import type { Adventure, Checklist, Collection, Note, Transportation, User } from './types';
|
||||
import randomBackgrounds from './json/backgrounds.json';
|
||||
import type {
|
||||
Adventure,
|
||||
Background,
|
||||
Checklist,
|
||||
Collection,
|
||||
Note,
|
||||
Transportation,
|
||||
User
|
||||
} from './types';
|
||||
|
||||
export function getRandomQuote() {
|
||||
const quotes = inspirationalQuotes.quotes;
|
||||
const randomIndex = Math.floor(Math.random() * quotes.length);
|
||||
let quoteString = quotes[randomIndex].quote;
|
||||
let authorString = quotes[randomIndex].author;
|
||||
return '"' + quoteString + '" - ' + authorString;
|
||||
return { quote: quoteString, author: authorString };
|
||||
}
|
||||
|
||||
export function getFlag(size: number, country: string) {
|
||||
|
@ -244,33 +253,42 @@ export let ADVENTURE_TYPES = [
|
|||
{ type: 'other', label: 'Other' }
|
||||
];
|
||||
|
||||
export function typeToString(type: string) {
|
||||
const typeObj = ADVENTURE_TYPES.find((t) => t.type === type);
|
||||
if (typeObj) {
|
||||
return typeObj.label;
|
||||
// adventure type to icon mapping
|
||||
export let ADVENTURE_TYPE_ICONS = {
|
||||
general: '🌍',
|
||||
outdoor: '🏞️',
|
||||
lodging: '🛌',
|
||||
dining: '🍽️',
|
||||
activity: '🏄',
|
||||
attraction: '🎢',
|
||||
shopping: '🛍️',
|
||||
nightlife: '🌃',
|
||||
event: '🎉',
|
||||
transportation: '🚗',
|
||||
culture: '🎭',
|
||||
water_sports: '🚤',
|
||||
hiking: '🥾',
|
||||
wildlife: '🦒',
|
||||
historical_sites: '🏛️',
|
||||
music_concerts: '🎶',
|
||||
fitness: '🏋️',
|
||||
art_museums: '🎨',
|
||||
festivals: '🎪',
|
||||
spiritual_journeys: '🧘♀️',
|
||||
volunteer_work: '🤝',
|
||||
other: '❓'
|
||||
};
|
||||
|
||||
export function getAdventureTypeLabel(type: string) {
|
||||
// return the emoji ADVENTURE_TYPE_ICONS label for the given type if not found return ? emoji
|
||||
if (type in ADVENTURE_TYPE_ICONS) {
|
||||
return ADVENTURE_TYPE_ICONS[type as keyof typeof ADVENTURE_TYPE_ICONS];
|
||||
} else {
|
||||
return 'Unknown';
|
||||
return '❓';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an adventure has been visited.
|
||||
*
|
||||
* This function determines if the `adventure.visits` array contains at least one visit
|
||||
* with a `start_date` that is before the current date.
|
||||
*
|
||||
* @param adventure - The adventure object to check.
|
||||
* @returns `true` if the adventure has been visited, otherwise `false`.
|
||||
*/
|
||||
export function isAdventureVisited(adventure: Adventure) {
|
||||
const currentTime = Date.now();
|
||||
|
||||
// Check if any visit's start_date is before the current time.
|
||||
return (
|
||||
adventure.visits &&
|
||||
adventure.visits.some((visit) => {
|
||||
const visitStartTime = new Date(visit.start_date).getTime();
|
||||
return visit.start_date && visitStartTime <= currentTime;
|
||||
})
|
||||
);
|
||||
export function getRandomBackground() {
|
||||
const randomIndex = Math.floor(Math.random() * randomBackgrounds.backgrounds.length);
|
||||
return randomBackgrounds.backgrounds[randomIndex] as Background;
|
||||
}
|
||||
|
|
24
frontend/src/lib/json/backgrounds.json
Normal file
24
frontend/src/lib/json/backgrounds.json
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"backgrounds": [
|
||||
{
|
||||
"url": "backgrounds/adventurelog_showcase_1.webp",
|
||||
"author": "Sean Morley",
|
||||
"location": "Franconia Notch State Park, New Hampshire, USA"
|
||||
},
|
||||
{
|
||||
"url": "backgrounds/adventurelog_showcase_2.webp",
|
||||
"author": "Sean Morley",
|
||||
"location": "Tumbledown Mountain, Maine, USA"
|
||||
},
|
||||
{
|
||||
"url": "backgrounds/adventurelog_showcase_3.webp",
|
||||
"author": "Sean Morley",
|
||||
"location": "Philmont Scout Ranch, New Mexico, USA"
|
||||
},
|
||||
{
|
||||
"url": "backgrounds/adventurelog_showcase_4.webp",
|
||||
"author": "Sean Morley",
|
||||
"location": "Great Sand Dunes National Park, Colorado, USA"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -37,6 +37,7 @@ export type Adventure = {
|
|||
is_public: boolean;
|
||||
created_at?: string | null;
|
||||
updated_at?: string | null;
|
||||
is_visited?: boolean;
|
||||
};
|
||||
|
||||
export type Country = {
|
||||
|
@ -46,6 +47,8 @@ export type Country = {
|
|||
subregion: string;
|
||||
flag_url: string;
|
||||
capital: string;
|
||||
num_regions: number;
|
||||
num_visits: number;
|
||||
};
|
||||
|
||||
export type Region = {
|
||||
|
@ -60,6 +63,9 @@ export type VisitedRegion = {
|
|||
id: number;
|
||||
region: number;
|
||||
user_id: number;
|
||||
longitude: number;
|
||||
latitude: number;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type Point = {
|
||||
|
@ -160,3 +166,17 @@ export type ChecklistItem = {
|
|||
created_at: string; // ISO 8601 date string
|
||||
updated_at: string; // ISO 8601 date string
|
||||
};
|
||||
|
||||
export type Background = {
|
||||
url: string;
|
||||
author?: string;
|
||||
location?: string;
|
||||
};
|
||||
|
||||
export type ReverseGeocode = {
|
||||
id: string;
|
||||
region: string;
|
||||
country: string;
|
||||
is_visited: boolean;
|
||||
display_name: string;
|
||||
};
|
||||
|
|
403
frontend/src/locales/de.json
Normal file
403
frontend/src/locales/de.json
Normal file
|
@ -0,0 +1,403 @@
|
|||
{
|
||||
"about": {
|
||||
"about": "Um",
|
||||
"close": "Schließen",
|
||||
"license": "Lizenziert unter der GPL-3.0-Lizenz.",
|
||||
"message": "Hergestellt mit ❤️ in den Vereinigten Staaten.",
|
||||
"nominatim_1": "Standortsuche und Geokodierung werden bereitgestellt von",
|
||||
"nominatim_2": "Ihre Daten werden unter der ODbL-Lizenz lizenziert.",
|
||||
"oss_attributions": "Open-Source-Zuschreibungen",
|
||||
"other_attributions": "Weitere Hinweise finden Sie in der README-Datei.",
|
||||
"source_code": "Quellcode"
|
||||
},
|
||||
"adventures": {
|
||||
"activities": {
|
||||
"activity": "Aktivität 🏄",
|
||||
"art_museums": "Kunst",
|
||||
"attraction": "Attraktion 🎢",
|
||||
"culture": "Kultur 🎭",
|
||||
"dining": "Essen 🍽️",
|
||||
"event": "Veranstaltung 🎉",
|
||||
"festivals": "Feste 🎪",
|
||||
"fitness": "Fitness 🏋️",
|
||||
"general": "Allgemein 🌍",
|
||||
"hiking": "Wandern 🥾",
|
||||
"historical_sites": "Historische Stätten 🏛️",
|
||||
"lodging": "Unterkunft 🛌",
|
||||
"music_concerts": "Musik",
|
||||
"nightlife": "Nachtleben 🌃",
|
||||
"other": "Andere",
|
||||
"outdoor": "Draußen 🏞️",
|
||||
"shopping": "Einkaufen 🛍️",
|
||||
"spiritual_journeys": "Spirituelle Reisen 🧘♀️",
|
||||
"transportation": "Transport 🚗",
|
||||
"volunteer_work": "Freiwilligenarbeit 🤝",
|
||||
"water_sports": "Wassersport 🚤",
|
||||
"wildlife": "Wildtiere 🦒"
|
||||
},
|
||||
"add_to_collection": "Zur Sammlung hinzufügen",
|
||||
"adventure_delete_confirm": "Sind Sie sicher, dass Sie dieses Abenteuer löschen möchten? \nDiese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"collection_link_error": "Fehler beim Verknüpfen des Abenteuers mit der Sammlung",
|
||||
"collection_link_success": "Abenteuer erfolgreich mit Sammlung verknüpft!",
|
||||
"collection_remove_error": "Beim Entfernen des Abenteuers aus der Sammlung ist ein Fehler aufgetreten",
|
||||
"collection_remove_success": "Abenteuer erfolgreich aus der Sammlung entfernt!",
|
||||
"delete": "Löschen",
|
||||
"edit_adventure": "Abenteuer bearbeiten",
|
||||
"no_image_found": "Kein Bild gefunden",
|
||||
"open_details": "Details öffnen",
|
||||
"remove_from_collection": "Aus der Sammlung entfernen",
|
||||
"adventure": "Abenteuer",
|
||||
"adventure_delete_success": "Abenteuer erfolgreich gelöscht!",
|
||||
"adventure_details": "Abenteuerdetails",
|
||||
"adventure_type": "Abenteuertyp",
|
||||
"archive": "Archiv",
|
||||
"archived": "Archiviert",
|
||||
"archived_collection_message": "Sammlung erfolgreich archiviert!",
|
||||
"archived_collections": "Archivierte Sammlungen",
|
||||
"ascending": "Aufsteigend",
|
||||
"cancel": "Stornieren",
|
||||
"category_filter": "Kategoriefilter",
|
||||
"clear": "Klar",
|
||||
"close_filters": "Filter schließen",
|
||||
"collection": "Sammlung",
|
||||
"collection_adventures": "Schließen Sie Sammlungsabenteuer ein",
|
||||
"count_txt": "Ergebnisse, die Ihrer Suche entsprechen",
|
||||
"date": "Datum",
|
||||
"dates": "Termine",
|
||||
"delete_adventure": "Abenteuer löschen",
|
||||
"delete_collection": "Sammlung löschen",
|
||||
"delete_collection_success": "Sammlung erfolgreich gelöscht!",
|
||||
"delete_collection_warning": "Sind Sie sicher, dass Sie diese Sammlung löschen möchten? \nDadurch werden auch alle verknüpften Abenteuer gelöscht. \nDiese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"descending": "Absteigend",
|
||||
"duration": "Dauer",
|
||||
"edit_collection": "Sammlung bearbeiten",
|
||||
"filter": "Filter",
|
||||
"homepage": "Homepage",
|
||||
"image_removed_error": "Fehler beim Entfernen des Bildes",
|
||||
"image_removed_success": "Bild erfolgreich entfernt!",
|
||||
"image_upload_error": "Fehler beim Hochladen des Bildes",
|
||||
"image_upload_success": "Bild erfolgreich hochgeladen!",
|
||||
"latitude": "Breite",
|
||||
"longitude": "Länge",
|
||||
"my_collections": "Meine Sammlungen",
|
||||
"name": "Name",
|
||||
"no_image_url": "Unter dieser URL wurde kein Bild gefunden.",
|
||||
"not_found": "Abenteuer nicht gefunden",
|
||||
"not_found_desc": "Das von Ihnen gesuchte Abenteuer konnte nicht gefunden werden. \nBitte probieren Sie ein anderes Abenteuer aus oder schauen Sie später noch einmal vorbei.",
|
||||
"open_filters": "Öffnen Sie Filter",
|
||||
"order_by": "Bestellen nach",
|
||||
"order_direction": "Bestellrichtung",
|
||||
"planned": "Geplant",
|
||||
"private": "Privat",
|
||||
"public": "Öffentlich",
|
||||
"rating": "Bewertung",
|
||||
"share": "Aktie",
|
||||
"sort": "Sortieren",
|
||||
"sources": "Quellen",
|
||||
"start_before_end_error": "Das Startdatum muss vor dem Enddatum liegen",
|
||||
"unarchive": "Dearchivieren",
|
||||
"unarchived_collection_message": "Sammlung erfolgreich dearchiviert!",
|
||||
"updated": "Aktualisiert",
|
||||
"visit": "Besuchen",
|
||||
"visited": "Besucht",
|
||||
"visits": "Besuche",
|
||||
"wiki_image_error": "Fehler beim Abrufen des Bildes aus Wikipedia",
|
||||
"actions": "Aktionen",
|
||||
"activity": "Aktivität",
|
||||
"activity_types": "Aktivitätstypen",
|
||||
"add": "Hinzufügen",
|
||||
"add_an_activity": "Fügen Sie eine Aktivität hinzu",
|
||||
"add_notes": "Notizen hinzufügen",
|
||||
"adventure_create_error": "Das Abenteuer konnte nicht erstellt werden",
|
||||
"adventure_created": "Abenteuer geschaffen",
|
||||
"adventure_update_error": "Das Abenteuer konnte nicht aktualisiert werden",
|
||||
"adventure_updated": "Abenteuer aktualisiert",
|
||||
"basic_information": "Grundlegende Informationen",
|
||||
"category": "Kategorie",
|
||||
"clear_map": "Klare Karte",
|
||||
"copy_link": "Link kopieren",
|
||||
"create_new": "Neu erstellen...",
|
||||
"date_constrain": "Auf Abholtermine beschränken",
|
||||
"description": "Beschreibung",
|
||||
"end_date": "Enddatum",
|
||||
"fetch_image": "Bild abrufen",
|
||||
"generate_desc": "Beschreibung generieren",
|
||||
"image": "Bild",
|
||||
"image_fetch_failed": "Bild konnte nicht abgerufen werden",
|
||||
"link": "Link",
|
||||
"location": "Standort",
|
||||
"location_information": "Standortinformationen",
|
||||
"my_images": "Meine Bilder",
|
||||
"my_visits": "Meine Besuche",
|
||||
"new_adventure": "Neues Abenteuer",
|
||||
"no_description_found": "Keine Beschreibung gefunden",
|
||||
"no_images": "Keine Bilder",
|
||||
"no_location": "Bitte geben Sie einen Ort ein",
|
||||
"no_results": "Keine Ergebnisse gefunden",
|
||||
"no_start_date": "Bitte geben Sie ein Startdatum ein",
|
||||
"public_adventure": "Öffentliches Abenteuer",
|
||||
"remove": "Entfernen",
|
||||
"save_next": "Speichern",
|
||||
"search_for_location": "Suchen Sie nach einem Ort",
|
||||
"search_results": "Suchergebnisse",
|
||||
"see_adventures": "Siehe Abenteuer",
|
||||
"select_adventure_category": "Wählen Sie die Abenteuerkategorie",
|
||||
"share_adventure": "Teilen Sie dieses Abenteuer!",
|
||||
"start_date": "Startdatum",
|
||||
"upload_image": "Bild hochladen",
|
||||
"upload_images_here": "Laden Sie hier Bilder hoch",
|
||||
"url": "URL",
|
||||
"warning": "Warnung",
|
||||
"wiki_desc": "Ruft einen Auszug aus einem Wikipedia-Artikel ab, der zum Namen des Abenteuers passt.",
|
||||
"wikipedia": "Wikipedia",
|
||||
"adventure_not_found": "Es sind keine Abenteuer zum Anzeigen vorhanden. \nFügen Sie einige über die Plus-Schaltfläche unten rechts hinzu oder versuchen Sie, die Filter zu ändern!",
|
||||
"all": "Alle",
|
||||
"error_updating_regions": "Fehler beim Aktualisieren der Regionen",
|
||||
"mark_region_as_visited": "Region {region}, {country} als besucht markieren?",
|
||||
"mark_visited": "Mark besucht",
|
||||
"my_adventures": "Meine Abenteuer",
|
||||
"no_adventures_found": "Keine Abenteuer gefunden",
|
||||
"no_collections_found": "Es wurden keine Sammlungen gefunden, zu denen dieses Abenteuer hinzugefügt werden kann.",
|
||||
"no_linkable_adventures": "Es wurden keine Abenteuer gefunden, die mit dieser Sammlung verknüpft werden können.",
|
||||
"not_visited": "Nicht besucht",
|
||||
"regions_updated": "Regionen aktualisiert",
|
||||
"update_visited_regions": "Besuchte Regionen aktualisieren",
|
||||
"update_visited_regions_disclaimer": "Dies kann je nach Anzahl der Abenteuer, die Sie besucht haben, eine Weile dauern.",
|
||||
"visited_region_check": "Überprüfung der besuchten Region",
|
||||
"visited_region_check_desc": "Wenn Sie diese Option auswählen, überprüft der Server alle von Ihnen besuchten Abenteuer und markiert die Regionen, in denen sie sich befinden, als im Rahmen von Weltreisen besucht.",
|
||||
"add_new": "Neu hinzufügen...",
|
||||
"checklist": "Checkliste",
|
||||
"checklists": "Checklisten",
|
||||
"collection_completed": "Du hast diese Sammlung vervollständigt!",
|
||||
"collection_stats": "Sammlungsstatistiken",
|
||||
"days": "Tage",
|
||||
"itineary_by_date": "Reiseroute nach Datum",
|
||||
"keep_exploring": "Entdecken Sie weiter!",
|
||||
"link_new": "Link Neu...",
|
||||
"linked_adventures": "Verknüpfte Abenteuer",
|
||||
"links": "Links",
|
||||
"no_end_date": "Bitte geben Sie ein Enddatum ein",
|
||||
"note": "Notiz",
|
||||
"notes": "Notizen",
|
||||
"nothing_planned": "Für diesen Tag ist nichts geplant. \nGenieße die Reise!",
|
||||
"transportation": "Transport",
|
||||
"transportations": "Transporte",
|
||||
"visit_link": "Besuchen Sie den Link",
|
||||
"collection_archived": "Diese Sammlung wurde archiviert.",
|
||||
"day": "Tag",
|
||||
"add_a_tag": "Fügen Sie ein Tag hinzu",
|
||||
"tags": "Schlagworte",
|
||||
"set_to_pin": "Auf „Anpinnen“ setzen"
|
||||
},
|
||||
"home": {
|
||||
"desc_1": "Entdecken, planen und erkunden Sie mit Leichtigkeit",
|
||||
"desc_2": "AdventureLog wurde entwickelt, um Ihre Reise zu vereinfachen und Ihnen die Tools und Ressourcen zur Verfügung zu stellen, mit denen Sie Ihr nächstes unvergessliches Abenteuer planen, packen und navigieren können.",
|
||||
"feature_1": "Reisetagebuch",
|
||||
"feature_1_desc": "Behalten Sie den Überblick über Ihre Abenteuer mit einem personalisierten Reisetagebuch und teilen Sie Ihre Erlebnisse mit Freunden und Familie.",
|
||||
"feature_2": "Reiseplanung",
|
||||
"feature_3": "Reisekarte",
|
||||
"feature_3_desc": "Sehen Sie sich Ihre Reisen rund um die Welt mit einer interaktiven Karte an und erkunden Sie neue Reiseziele.",
|
||||
"go_to": "Gehen Sie zu AdventureLog",
|
||||
"hero_1": "Entdecken Sie die aufregendsten Abenteuer der Welt",
|
||||
"hero_2": "Entdecken und planen Sie Ihr nächstes Abenteuer mit AdventureLog. \nEntdecken Sie atemberaubende Reiseziele, erstellen Sie individuelle Reiserouten und bleiben Sie unterwegs in Verbindung.",
|
||||
"key_features": "Hauptmerkmale",
|
||||
"feature_2_desc": "Erstellen Sie ganz einfach individuelle Reiserouten und erhalten Sie eine tagesaktuelle Aufschlüsselung Ihrer Reise."
|
||||
},
|
||||
"navbar": {
|
||||
"about": "Über AdventureLog",
|
||||
"adventures": "Abenteuer",
|
||||
"collections": "Sammlungen",
|
||||
"discord": "Zwietracht",
|
||||
"documentation": "Dokumentation",
|
||||
"greeting": "Hallo",
|
||||
"logout": "Abmelden",
|
||||
"map": "Karte",
|
||||
"my_adventures": "Meine Abenteuer",
|
||||
"profile": "Profil",
|
||||
"search": "Suchen",
|
||||
"settings": "Einstellungen",
|
||||
"shared_with_me": "Mit mir geteilt",
|
||||
"theme_selection": "Themenauswahl",
|
||||
"themes": {
|
||||
"aestetic-dark": "Ästhetisches Dunkel",
|
||||
"aestetic-light": "Ästhetisches Licht",
|
||||
"aqua": "Aqua",
|
||||
"dark": "Dunkel",
|
||||
"forest": "Wald",
|
||||
"light": "Licht",
|
||||
"night": "Nacht"
|
||||
},
|
||||
"users": "Benutzer",
|
||||
"worldtravel": "Weltreisen",
|
||||
"my_tags": "Meine Tags",
|
||||
"tag": "Etikett",
|
||||
"language_selection": "Sprache"
|
||||
},
|
||||
"auth": {
|
||||
"confirm_password": "Passwort bestätigen",
|
||||
"email": "E-Mail",
|
||||
"first_name": "Vorname",
|
||||
"forgot_password": "Passwort vergessen?",
|
||||
"last_name": "Nachname",
|
||||
"login": "Login",
|
||||
"login_error": "Anmeldung mit den angegebenen Anmeldeinformationen nicht möglich.",
|
||||
"password": "Passwort",
|
||||
"registration_disabled": "Die Registrierung ist derzeit deaktiviert.",
|
||||
"signup": "Melden Sie sich an",
|
||||
"username": "Benutzername",
|
||||
"profile_picture": "Profilbild",
|
||||
"public_profile": "Öffentliches Profil",
|
||||
"public_tooltip": "Mit einem öffentlichen Profil können Benutzer Sammlungen mit Ihnen teilen und Ihr Profil auf der Benutzerseite anzeigen."
|
||||
},
|
||||
"users": {
|
||||
"no_users_found": "Keine Benutzer mit öffentlichen Profilen gefunden."
|
||||
},
|
||||
"worldtravel": {
|
||||
"all": "Alle",
|
||||
"all_subregions": "Alle Unterregionen",
|
||||
"clear_search": "Suche löschen",
|
||||
"completely_visited": "Vollständig besucht",
|
||||
"country_list": "Länderliste",
|
||||
"no_countries_found": "Keine Länder gefunden",
|
||||
"not_visited": "Nicht besucht",
|
||||
"num_countries": "Länder gefunden",
|
||||
"partially_visited": "Teilweise besucht"
|
||||
},
|
||||
"settings": {
|
||||
"account_settings": "Benutzerkontoeinstellungen",
|
||||
"current_email": "Aktuelle E-Mail",
|
||||
"email_change": "E-Mail ändern",
|
||||
"new_email": "Neue E-Mail",
|
||||
"new_password": "Neues Passwort",
|
||||
"no_email_set": "Keine E-Mail-Adresse festgelegt",
|
||||
"password_change": "Kennwort ändern",
|
||||
"settings_page": "Einstellungsseite",
|
||||
"update": "Aktualisieren",
|
||||
"update_error": "Fehler beim Aktualisieren der Einstellungen",
|
||||
"update_success": "Einstellungen erfolgreich aktualisiert!",
|
||||
"change_password": "Kennwort ändern",
|
||||
"confirm_new_password": "Bestätigen Sie das neue Passwort",
|
||||
"invalid_token": "Token ist ungültig oder abgelaufen",
|
||||
"login_redir": "Anschließend werden Sie zur Anmeldeseite weitergeleitet.",
|
||||
"missing_email": "Bitte geben Sie eine E-Mail-Adresse ein",
|
||||
"password_does_not_match": "Passwörter stimmen nicht überein",
|
||||
"password_is_required": "Passwort ist erforderlich",
|
||||
"possible_reset": "Wenn die von Ihnen angegebene E-Mail-Adresse mit einem Konto verknüpft ist, erhalten Sie eine E-Mail mit Anweisungen zum Zurücksetzen Ihres Passworts!",
|
||||
"reset_password": "Passwort zurücksetzen",
|
||||
"submit": "Einreichen",
|
||||
"token_required": "Zum Zurücksetzen des Passworts sind Token und UID erforderlich.",
|
||||
"about_this_background": "Über diesen Hintergrund",
|
||||
"join_discord": "Treten Sie dem Discord bei",
|
||||
"join_discord_desc": "um Ihre eigenen Fotos zu teilen. \nVeröffentlichen Sie sie im",
|
||||
"photo_by": "Foto von"
|
||||
},
|
||||
"checklist": {
|
||||
"add_item": "Artikel hinzufügen",
|
||||
"checklist_delete_error": "Fehler beim Löschen der Checkliste",
|
||||
"checklist_deleted": "Checkliste erfolgreich gelöscht!",
|
||||
"checklist_editor": "Checklisten-Editor",
|
||||
"checklist_public": "Diese Checkliste ist öffentlich, da sie sich in einer öffentlichen Sammlung befindet.",
|
||||
"editing_checklist": "Checkliste bearbeiten",
|
||||
"failed_to_save": "Checkliste konnte nicht gespeichert werden",
|
||||
"item": "Artikel",
|
||||
"item_already_exists": "Artikel existiert bereits",
|
||||
"item_cannot_be_empty": "Das Element darf nicht leer sein",
|
||||
"items": "Artikel",
|
||||
"new_item": "Neuer Artikel",
|
||||
"save": "Speichern"
|
||||
},
|
||||
"collection": {
|
||||
"collection_created": "Sammlung erfolgreich erstellt!",
|
||||
"collection_edit_success": "Sammlung erfolgreich bearbeitet!",
|
||||
"create": "Erstellen",
|
||||
"edit_collection": "Sammlung bearbeiten",
|
||||
"error_creating_collection": "Fehler beim Erstellen der Sammlung",
|
||||
"error_editing_collection": "Fehler beim Bearbeiten der Sammlung",
|
||||
"new_collection": "Neue Kollektion"
|
||||
},
|
||||
"notes": {
|
||||
"add_a_link": "Fügen Sie einen Link hinzu",
|
||||
"content": "Inhalt",
|
||||
"editing_note": "Bearbeitungsnotiz",
|
||||
"failed_to_save": "Notiz konnte nicht gespeichert werden",
|
||||
"note_delete_error": "Fehler beim Löschen der Notiz",
|
||||
"note_deleted": "Notiz erfolgreich gelöscht!",
|
||||
"note_editor": "Notizeditor",
|
||||
"note_public": "Diese Notiz ist öffentlich, da sie sich in einer öffentlichen Sammlung befindet.",
|
||||
"open": "Offen",
|
||||
"save": "Speichern",
|
||||
"invalid_url": "Ungültige URL"
|
||||
},
|
||||
"transportation": {
|
||||
"date_and_time": "Datum",
|
||||
"date_time": "Startdatum",
|
||||
"edit": "Bearbeiten",
|
||||
"edit_transportation": "Transport bearbeiten",
|
||||
"end_date_time": "Enddatum",
|
||||
"error_editing_transportation": "Fehler beim Bearbeiten des Transports",
|
||||
"flight_number": "Flugnummer",
|
||||
"from_location": "Vom Standort",
|
||||
"modes": {
|
||||
"bike": "Fahrrad",
|
||||
"boat": "Boot",
|
||||
"bus": "Bus",
|
||||
"walking": "Gehen",
|
||||
"car": "Auto",
|
||||
"other": "Andere",
|
||||
"plane": "Flugzeug",
|
||||
"train": "Zug"
|
||||
},
|
||||
"transportation_added": "Transport erfolgreich hinzugefügt!",
|
||||
"transportation_delete_error": "Fehler beim Löschen des Transports",
|
||||
"transportation_deleted": "Transport erfolgreich gelöscht!",
|
||||
"transportation_edit_success": "Transport erfolgreich bearbeitet!",
|
||||
"type": "Typ",
|
||||
"new_transportation": "Neue Transportmittel",
|
||||
"provide_start_date": "Bitte geben Sie ein Startdatum an",
|
||||
"start": "Start",
|
||||
"to_location": "Zum Standort",
|
||||
"transport_type": "Transporttyp"
|
||||
},
|
||||
"search": {
|
||||
"adventurelog_results": "AdventureLog-Ergebnisse",
|
||||
"online_results": "Online-Ergebnisse",
|
||||
"public_adventures": "Öffentliche Abenteuer"
|
||||
},
|
||||
"map": {
|
||||
"add_adventure": "Neues Abenteuer hinzufügen",
|
||||
"add_adventure_at_marker": "Neues Abenteuer bei Marker hinzufügen",
|
||||
"adventure_map": "Abenteuerkarte",
|
||||
"clear_marker": "Markierung löschen",
|
||||
"map_options": "Kartenoptionen",
|
||||
"show_visited_regions": "Besuchte Regionen anzeigen",
|
||||
"view_details": "Details anzeigen"
|
||||
},
|
||||
"languages": {
|
||||
"de": "Deutsch",
|
||||
"en": "Englisch",
|
||||
"es": "Spanisch",
|
||||
"fr": "Französisch",
|
||||
"it": "Italienisch",
|
||||
"nl": "Niederländisch",
|
||||
"sv": "Schwedisch",
|
||||
"zh": "chinesisch"
|
||||
},
|
||||
"share": {
|
||||
"no_users_shared": "Keine Benutzer geteilt mit",
|
||||
"not_shared_with": "Nicht geteilt mit",
|
||||
"share_desc": "Teilen Sie diese Sammlung mit anderen Benutzern.",
|
||||
"shared": "Geteilt",
|
||||
"shared_with": "Geteilt mit",
|
||||
"unshared": "Nicht geteilt",
|
||||
"with": "mit",
|
||||
"go_to_settings": "Gehen Sie zu den Einstellungen",
|
||||
"no_shared_found": "Es wurden keine Sammlungen gefunden, die mit Ihnen geteilt wurden.",
|
||||
"set_public": "Damit Benutzer Inhalte mit Ihnen teilen können, muss Ihr Profil auf „Öffentlich“ eingestellt sein."
|
||||
},
|
||||
"profile": {
|
||||
"member_since": "Mitglied seit",
|
||||
"user_stats": "Benutzerstatistiken",
|
||||
"visited_countries": "Besuchte Länder",
|
||||
"visited_regions": "Besuchte Regionen"
|
||||
}
|
||||
}
|
403
frontend/src/locales/en.json
Normal file
403
frontend/src/locales/en.json
Normal file
|
@ -0,0 +1,403 @@
|
|||
{
|
||||
"navbar": {
|
||||
"adventures": "Adventures",
|
||||
"collections": "Collections",
|
||||
"worldtravel": "World Travel",
|
||||
"map": "Map",
|
||||
"users": "Users",
|
||||
"search": "Search",
|
||||
"profile": "Profile",
|
||||
"greeting": "Hi",
|
||||
"my_adventures": "My Adventures",
|
||||
"my_tags": "My Tags",
|
||||
"tag": "Tag",
|
||||
"shared_with_me": "Shared With Me",
|
||||
"settings": "Settings",
|
||||
"logout": "Logout",
|
||||
"about": "About AdventureLog",
|
||||
"documentation": "Documentation",
|
||||
"discord": "Discord",
|
||||
"language_selection": "Language",
|
||||
"theme_selection": "Theme Selection",
|
||||
"themes": {
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"night": "Night",
|
||||
"forest": "Forest",
|
||||
"aestetic-dark": "Aestetic Dark",
|
||||
"aestetic-light": "Aestetic Light",
|
||||
"aqua": "Aqua"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"about": "About",
|
||||
"license": "Licensed under the GPL-3.0 License.",
|
||||
"source_code": "Source Code",
|
||||
"message": "Made with ❤️ in the United States.",
|
||||
"oss_attributions": "Open Source Attributions",
|
||||
"nominatim_1": "Location Search and Geocoding is provided by",
|
||||
"nominatim_2": "Their data is liscensed under the ODbL license.",
|
||||
"other_attributions": "Additional attributions can be found in the README file.",
|
||||
"close": "Close"
|
||||
},
|
||||
"home": {
|
||||
"hero_1": "Discover the World's Most Thrilling Adventures",
|
||||
"hero_2": "Discover and plan your next adventure with AdventureLog. Explore breathtaking destinations, create custom itineraries, and stay connected on the go.",
|
||||
"go_to": "Go To AdventureLog",
|
||||
"key_features": "Key Features",
|
||||
"desc_1": "Discover, Plan, and Explore with Ease",
|
||||
"desc_2": "AdventureLog is designed to simplify your journey, providing you with the tools and resources to plan, pack, and navigate your next unforgettable adventure.",
|
||||
"feature_1": "Travel Log",
|
||||
"feature_1_desc": "Keep track of your adventures with a personalized travel log and share your experiences with friends and family.",
|
||||
"feature_2": "Trip Planning",
|
||||
"feature_2_desc": "Easily create custom itineraries and get a day-by-day breakdown of your trip.",
|
||||
"feature_3": "Travel Map",
|
||||
"feature_3_desc": "View your travels throughout the world with an interactive map and explore new destinations."
|
||||
},
|
||||
"adventures": {
|
||||
"collection_remove_success": "Adventure removed from collection successfully!",
|
||||
"collection_remove_error": "Error removing adventure from collection",
|
||||
"collection_link_success": "Adventure linked to collection successfully!",
|
||||
"no_image_found": "No image found",
|
||||
"collection_link_error": "Error linking adventure to collection",
|
||||
"adventure_delete_confirm": "Are you sure you want to delete this adventure? This action cannot be undone.",
|
||||
"open_details": "Open Details",
|
||||
"edit_adventure": "Edit Adventure",
|
||||
"remove_from_collection": "Remove from Collection",
|
||||
"add_to_collection": "Add to Collection",
|
||||
"delete": "Delete",
|
||||
"not_found": "Adventure not found",
|
||||
"not_found_desc": "The adventure you were looking for could not be found. Please try a different adventure or check back later.",
|
||||
"homepage": "Homepage",
|
||||
"adventure_details": "Adventure Details",
|
||||
"collection": "Collection",
|
||||
"adventure_type": "Adventure Type",
|
||||
"longitude": "Longitude",
|
||||
"latitude": "Latitude",
|
||||
"visit": "Visit",
|
||||
"visits": "Visits",
|
||||
"create_new": "Create New...",
|
||||
"adventure": "Adventure",
|
||||
"count_txt": "results matching your search",
|
||||
"sort": "Sort",
|
||||
"order_by": "Order By",
|
||||
"order_direction": "Order Direction",
|
||||
"ascending": "Ascending",
|
||||
"descending": "Descending",
|
||||
"updated": "Updated",
|
||||
"name": "Name",
|
||||
"date": "Date",
|
||||
"activity_types": "Activity Types",
|
||||
"tags": "Tags",
|
||||
"add_a_tag": "Add a tag",
|
||||
"date_constrain": "Constrain to collection dates",
|
||||
"rating": "Rating",
|
||||
"my_images": "My Images",
|
||||
"add_an_activity": "Add an activity",
|
||||
"no_images": "No Images",
|
||||
"upload_images_here": "Upload images here",
|
||||
"share_adventure": "Share this Adventure!",
|
||||
"copy_link": "Copy Link",
|
||||
"image": "Image",
|
||||
"upload_image": "Upload Image",
|
||||
"url": "URL",
|
||||
"fetch_image": "Fetch Image",
|
||||
"wikipedia": "Wikipedia",
|
||||
"add_notes": "Add notes",
|
||||
"warning": "Warning",
|
||||
"my_adventures": "My Adventures",
|
||||
"no_linkable_adventures": "No adventures found that can be linked to this collection.",
|
||||
"add": "Add",
|
||||
"save_next": "Save & Next",
|
||||
"end_date": "End Date",
|
||||
"my_visits": "My Visits",
|
||||
"start_date": "Start Date",
|
||||
"remove": "Remove",
|
||||
"location": "Location",
|
||||
"search_for_location": "Search for a location",
|
||||
"clear_map": "Clear map",
|
||||
"search_results": "Searh results",
|
||||
"no_results": "No results found",
|
||||
"wiki_desc": "Pulls excerpt from Wikipedia article matching the name of the adventure.",
|
||||
"generate_desc": "Generate Description",
|
||||
"public_adventure": "Public Adventure",
|
||||
"location_information": "Location Information",
|
||||
"link": "Link",
|
||||
"links": "Links",
|
||||
"description": "Description",
|
||||
"sources": "Sources",
|
||||
"collection_adventures": "Include Collection Adventures",
|
||||
"filter": "Filter",
|
||||
"category_filter": "Category Filter",
|
||||
"category": "Category",
|
||||
"select_adventure_category": "Select Adventure Category",
|
||||
"clear": "Clear",
|
||||
"my_collections": "My Collections",
|
||||
"open_filters": "Open Filters",
|
||||
"close_filters": "Close Filters",
|
||||
"archived_collections": "Archived Collections",
|
||||
"share": "Share",
|
||||
"private": "Private",
|
||||
"public": "Public",
|
||||
"archived": "Archived",
|
||||
"edit_collection": "Edit Collection",
|
||||
"unarchive": "Unarchive",
|
||||
"archive": "Archive",
|
||||
"no_collections_found": "No collections found to add this adventure to.",
|
||||
"not_visited": "Not Visited",
|
||||
"archived_collection_message": "Collection archived successfully!",
|
||||
"unarchived_collection_message": "Collection unarchived successfully!",
|
||||
"delete_collection_success": "Collection deleted successfully!",
|
||||
"delete_collection_warning": "Are you sure you want to delete this collection? This will also delete all of the linked adventures. This action cannot be undone.",
|
||||
"cancel": "Cancel",
|
||||
"delete_collection": "Delete Collection",
|
||||
"delete_adventure": "Delete Adventure",
|
||||
"adventure_delete_success": "Adventure deleted successfully!",
|
||||
"visited": "Visited",
|
||||
"planned": "Planned",
|
||||
"duration": "Duration",
|
||||
"all": "All",
|
||||
"image_removed_success": "Image removed successfully!",
|
||||
"image_removed_error": "Error removing image",
|
||||
"no_image_url": "No image found at that URL.",
|
||||
"image_upload_success": "Image uploaded successfully!",
|
||||
"image_upload_error": "Error uploading image",
|
||||
"dates": "Dates",
|
||||
"wiki_image_error": "Error fetching image from Wikipedia",
|
||||
"start_before_end_error": "Start date must be before end date",
|
||||
"activity": "Activity",
|
||||
"actions": "Actions",
|
||||
"no_end_date": "Please enter an end date",
|
||||
"see_adventures": "See Adventures",
|
||||
"image_fetch_failed": "Failed to fetch image",
|
||||
"no_location": "Please enter a location",
|
||||
"no_start_date": "Please enter a start date",
|
||||
"no_description_found": "No description found",
|
||||
"adventure_created": "Adventure created",
|
||||
"adventure_create_error": "Failed to create adventure",
|
||||
"adventure_updated": "Adventure updated",
|
||||
"adventure_update_error": "Failed to update adventure",
|
||||
"set_to_pin": "Set to Pin",
|
||||
"new_adventure": "New Adventure",
|
||||
"basic_information": "Basic Information",
|
||||
"adventure_not_found": "There are no adventures to display. Add some using the plus button at the bottom right or try changing filters!",
|
||||
"no_adventures_found": "No adventures found",
|
||||
"mark_region_as_visited": "Mark region {region}, {country} as visited?",
|
||||
"mark_visited": "Mark Visited",
|
||||
"error_updating_regions": "Error updating regions",
|
||||
"regions_updated": "regions updated",
|
||||
"visited_region_check": "Visited Region Check",
|
||||
"visited_region_check_desc": "By selecting this, the server will check all of your visited adventures and mark the regions they are located in as visited in world travel.",
|
||||
"update_visited_regions": "Update Visited Regions",
|
||||
"update_visited_regions_disclaimer": "This may take a while depending on the number of adventures you have visited.",
|
||||
"link_new": "Link New...",
|
||||
"add_new": "Add New...",
|
||||
"transportation": "Transportation",
|
||||
"note": "Note",
|
||||
"checklist": "Checklist",
|
||||
"collection_archived": "This collection has been archived.",
|
||||
"visit_link": "Visit Link",
|
||||
"collection_completed": "You've completed this collection!",
|
||||
"collection_stats": "Collection Stats",
|
||||
"keep_exploring": "Keep Exploring!",
|
||||
"linked_adventures": "Linked Adventures",
|
||||
"notes": "Notes",
|
||||
"checklists": "Checklists",
|
||||
"transportations": "Transportations",
|
||||
"day": "Day",
|
||||
"itineary_by_date": "Itinerary by Date",
|
||||
"nothing_planned": "Nothing planned for this day. Enjoy the journey!",
|
||||
"days": "days",
|
||||
"activities": {
|
||||
"general": "General 🌍",
|
||||
"outdoor": "Outdoor 🏞️",
|
||||
"lodging": "Lodging 🛌",
|
||||
"dining": "Dining 🍽️",
|
||||
"activity": "Activity 🏄",
|
||||
"attraction": "Attraction 🎢",
|
||||
"shopping": "Shopping 🛍️",
|
||||
"nightlife": "Nightlife 🌃",
|
||||
"event": "Event 🎉",
|
||||
"transportation": "Transportation 🚗",
|
||||
"culture": "Culture 🎭",
|
||||
"water_sports": "Water Sports 🚤",
|
||||
"hiking": "Hiking 🥾",
|
||||
"wildlife": "Wildlife 🦒",
|
||||
"historical_sites": "Historical Sites 🏛️",
|
||||
"music_concerts": "Music & Concerts 🎶",
|
||||
"fitness": "Fitness 🏋️",
|
||||
"art_museums": "Art & Museums 🎨",
|
||||
"festivals": "Festivals 🎪",
|
||||
"spiritual_journeys": "Spiritual Journeys 🧘♀️",
|
||||
"volunteer_work": "Volunteer Work 🤝",
|
||||
"other": "Other"
|
||||
}
|
||||
},
|
||||
"worldtravel": {
|
||||
"country_list": "Country List",
|
||||
"num_countries": "countries found",
|
||||
"all": "All",
|
||||
"partially_visited": "Partially Visited",
|
||||
"not_visited": "Not Visited",
|
||||
"completely_visited": "Completely Visited",
|
||||
"all_subregions": "All Subregions",
|
||||
"clear_search": "Clear Search",
|
||||
"no_countries_found": "No countries found"
|
||||
},
|
||||
"auth": {
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"forgot_password": "Forgot Password?",
|
||||
"signup": "Signup",
|
||||
"login_error": "Unable to login with the provided credentials.",
|
||||
"login": "Login",
|
||||
"email": "Email",
|
||||
"first_name": "First Name",
|
||||
"last_name": "Last Name",
|
||||
"confirm_password": "Confirm Password",
|
||||
"registration_disabled": "Registration is currently disabled.",
|
||||
"profile_picture": "Profile Picture",
|
||||
"public_profile": "Public Profile",
|
||||
"public_tooltip": "With a public profile, users can share collections with you and view your profile on the users page."
|
||||
},
|
||||
"users": {
|
||||
"no_users_found": "No users found with public profiles."
|
||||
},
|
||||
"settings": {
|
||||
"update_error": "Error updating settings",
|
||||
"update_success": "Settings updated successfully!",
|
||||
"settings_page": "Settings Page",
|
||||
"account_settings": "User Account Settings",
|
||||
"update": "Update",
|
||||
"password_change": "Change Password",
|
||||
"new_password": "New Password",
|
||||
"confirm_new_password": "Confirm New Password",
|
||||
"email_change": "Change Email",
|
||||
"current_email": "Current Email",
|
||||
"no_email_set": "No email set",
|
||||
"new_email": "New Email",
|
||||
"change_password": "Change Password",
|
||||
"login_redir": "You will then be redirected to the login page.",
|
||||
"token_required": "Token and UID are required for password reset.",
|
||||
"reset_password": "Reset Password",
|
||||
"possible_reset": "If the email address you provided is associated with an account, you will receive an email with instructions to reset your password!",
|
||||
"missing_email": "Please enter an email address",
|
||||
"submit": "Submit",
|
||||
"password_does_not_match": "Passwords do not match",
|
||||
"password_is_required": "Password is required",
|
||||
"invalid_token": "Token is invalid or has expired",
|
||||
"about_this_background": "About this background",
|
||||
"photo_by": "Photo by",
|
||||
"join_discord": "Join the Discord",
|
||||
"join_discord_desc": "to share your own photos. Post them in the #travel-share channel."
|
||||
},
|
||||
"collection": {
|
||||
"collection_created": "Collection created successfully!",
|
||||
"error_creating_collection": "Error creating collection",
|
||||
"new_collection": "New Collection",
|
||||
"create": "Create",
|
||||
"collection_edit_success": "Collection edited successfully!",
|
||||
"error_editing_collection": "Error editing collection",
|
||||
"edit_collection": "Edit Collection"
|
||||
},
|
||||
"notes": {
|
||||
"note_deleted": "Note deleted successfully!",
|
||||
"note_delete_error": "Error deleting note",
|
||||
"open": "Open",
|
||||
"failed_to_save": "Failed to save note",
|
||||
"note_editor": "Note Editor",
|
||||
"editing_note": "Editing note",
|
||||
"content": "Content",
|
||||
"save": "Save",
|
||||
"note_public": "This note is public because it is in a public collection.",
|
||||
"add_a_link": "Add a link",
|
||||
"invalid_url": "Invalid URL"
|
||||
},
|
||||
"checklist": {
|
||||
"checklist_deleted": "Checklist deleted successfully!",
|
||||
"checklist_delete_error": "Error deleting checklist",
|
||||
"failed_to_save": "Failed to save checklist",
|
||||
"checklist_editor": "Checklist Editor",
|
||||
"editing_checklist": "Editing checklist",
|
||||
"item": "Item",
|
||||
"items": "Items",
|
||||
"add_item": "Add Item",
|
||||
"new_item": "New Item",
|
||||
"save": "Save",
|
||||
"checklist_public": "This checklist is public because it is in a public collection.",
|
||||
"item_cannot_be_empty": "Item cannot be empty",
|
||||
"item_already_exists": "Item already exists"
|
||||
},
|
||||
"transportation": {
|
||||
"transportation_deleted": "Transportation deleted successfully!",
|
||||
"transportation_delete_error": "Error deleting transportation",
|
||||
"provide_start_date": "Please provide a start date",
|
||||
"transport_type": "Transport Type",
|
||||
"type": "Type",
|
||||
"transportation_added": "Transportation added successfully!",
|
||||
"error_editing_transportation": "Error editing transportation",
|
||||
"new_transportation": "New Transportation",
|
||||
"date_time": "Start Date & Time",
|
||||
"end_date_time": "End Date & Time",
|
||||
"flight_number": "Flight Number",
|
||||
"from_location": "From Location",
|
||||
"to_location": "To Location",
|
||||
"edit": "Edit",
|
||||
"modes": {
|
||||
"car": "Car",
|
||||
"plane": "Plane",
|
||||
"train": "Train",
|
||||
"bus": "Bus",
|
||||
"boat": "Boat",
|
||||
"bike": "Bike",
|
||||
"walking": "Walking",
|
||||
"other": "Other"
|
||||
},
|
||||
"transportation_edit_success": "Transportation edited successfully!",
|
||||
"edit_transportation": "Edit Transportation",
|
||||
"start": "Start",
|
||||
"date_and_time": "Date & Time"
|
||||
},
|
||||
"search": {
|
||||
"adventurelog_results": "AdventureLog Results",
|
||||
"public_adventures": "Public Adventures",
|
||||
"online_results": "Online Results"
|
||||
},
|
||||
"map": {
|
||||
"view_details": "View Details",
|
||||
"adventure_map": "Adventure Map",
|
||||
"map_options": "Map Options",
|
||||
"show_visited_regions": "Show Visited Regions",
|
||||
"add_adventure_at_marker": "Add New Adventure at Marker",
|
||||
"clear_marker": "Clear Marker",
|
||||
"add_adventure": "Add New Adventure"
|
||||
},
|
||||
"share": {
|
||||
"shared": "Shared",
|
||||
"with": "with",
|
||||
"unshared": "Unshared",
|
||||
"share_desc": "Share this collection with other users.",
|
||||
"shared_with": "Shared With",
|
||||
"no_users_shared": "No users shared with",
|
||||
"not_shared_with": "Not Shared With",
|
||||
"no_shared_found": "No collections found that are shared with you.",
|
||||
"set_public": "In order to allow users to share with you, you need your profile set to public.",
|
||||
"go_to_settings": "Go to settings"
|
||||
},
|
||||
"languages": {
|
||||
"en": "English",
|
||||
"de": "German",
|
||||
"es": "Spanish",
|
||||
"fr": "French",
|
||||
"it": "Italian",
|
||||
"nl": "Dutch",
|
||||
"sv": "Swedish",
|
||||
"zh": "Chinese"
|
||||
},
|
||||
"profile": {
|
||||
"member_since": "Member since",
|
||||
"user_stats": "User Stats",
|
||||
"visited_countries": "Visited Countries",
|
||||
"visited_regions": "Visited Regions"
|
||||
}
|
||||
}
|
403
frontend/src/locales/es.json
Normal file
403
frontend/src/locales/es.json
Normal file
|
@ -0,0 +1,403 @@
|
|||
{
|
||||
"navbar": {
|
||||
"adventures": "Aventuras",
|
||||
"collections": "Colecciones",
|
||||
"worldtravel": "Viajar por el Mundo",
|
||||
"map": "Mapa",
|
||||
"users": "Usuarios",
|
||||
"search": "Buscar",
|
||||
"profile": "Perfil",
|
||||
"greeting": "Hola",
|
||||
"my_adventures": "Mis Aventuras",
|
||||
"shared_with_me": "Compartido Conmigo",
|
||||
"settings": "Configuraciones",
|
||||
"logout": "Cerrar Sesión",
|
||||
"about": "Acerca de AdventureLog",
|
||||
"documentation": "Documentación",
|
||||
"discord": "Discord",
|
||||
"theme_selection": "Selección de Tema",
|
||||
"themes": {
|
||||
"light": "Claro",
|
||||
"dark": "Oscuro",
|
||||
"night": "Noche",
|
||||
"forest": "Bosque",
|
||||
"aestetic-dark": "Estético Oscuro",
|
||||
"aestetic-light": "Estético Claro",
|
||||
"aqua": "Aqua"
|
||||
},
|
||||
"my_tags": "Mis etiquetas",
|
||||
"tag": "Etiqueta",
|
||||
"language_selection": "Idioma"
|
||||
},
|
||||
"about": {
|
||||
"about": "Acerca de",
|
||||
"license": "Licenciado bajo la Licencia GPL-3.0.",
|
||||
"source_code": "Código Fuente",
|
||||
"message": "Hecho con ❤️ en los Estados Unidos.",
|
||||
"oss_attributions": "Atribuciones de Código Abierto",
|
||||
"nominatim_1": "La búsqueda de ubicaciones y geocodificación es proporcionada por",
|
||||
"nominatim_2": "Sus datos están licenciados bajo la licencia ODbL.",
|
||||
"other_attributions": "Atribuciones adicionales se pueden encontrar en el archivo README.",
|
||||
"close": "Cerrar"
|
||||
},
|
||||
"home": {
|
||||
"hero_1": "Descubre las Aventuras Más Emocionantes del Mundo",
|
||||
"hero_2": "Descubre y planifica tu próxima aventura con AdventureLog. Explora destinos impresionantes, crea itinerarios personalizados y mantente conectado en todo momento.",
|
||||
"go_to": "Ir a AdventureLog",
|
||||
"key_features": "Características Clave",
|
||||
"desc_1": "Descubre, Planifica y Explora Fácilmente",
|
||||
"desc_2": "AdventureLog está diseñado para simplificar tu viaje, brindándote las herramientas y recursos para planificar, empacar y navegar tu próxima aventura inolvidable.",
|
||||
"feature_1": "Registro de Viajes",
|
||||
"feature_1_desc": "Mantén un registro de tus aventuras con un diario de viaje personalizado y comparte tus experiencias con amigos y familiares.",
|
||||
"feature_2": "Planificación de Viajes",
|
||||
"feature_2_desc": "Crea fácilmente itinerarios personalizados y obtén un desglose diario de tu viaje.",
|
||||
"feature_3": "Mapa de Viaje",
|
||||
"feature_3_desc": "Visualiza tus viajes por el mundo con un mapa interactivo y explora nuevos destinos."
|
||||
},
|
||||
"adventures": {
|
||||
"collection_remove_success": "¡Aventura eliminada de la colección con éxito!",
|
||||
"collection_remove_error": "Error al eliminar la aventura de la colección",
|
||||
"collection_link_success": "¡Aventura vinculada a la colección con éxito!",
|
||||
"collection_link_error": "Error al vincular la aventura a la colección",
|
||||
"adventure_delete_confirm": "¿Estás seguro de que quieres eliminar esta aventura? Esta acción no se puede deshacer.",
|
||||
"open_details": "Abrir Detalles",
|
||||
"edit_adventure": "Editar Aventura",
|
||||
"remove_from_collection": "Eliminar de la Colección",
|
||||
"add_to_collection": "Añadir a la Colección",
|
||||
"delete": "Eliminar",
|
||||
"activities": {
|
||||
"activity": "Actividad 🏄",
|
||||
"art_museums": "Arte",
|
||||
"attraction": "Atracción 🎢",
|
||||
"culture": "Cultura 🎭",
|
||||
"dining": "Cenar 🍽️",
|
||||
"event": "Evento 🎉",
|
||||
"festivals": "Festivales 🎪",
|
||||
"fitness": "Fitness 🏋️",
|
||||
"general": "Generales 🌍",
|
||||
"hiking": "Senderismo 🥾",
|
||||
"historical_sites": "Sitios Históricos 🏛️",
|
||||
"lodging": "Alojamiento 🛌",
|
||||
"music_concerts": "Música",
|
||||
"nightlife": "Vida nocturna 🌃",
|
||||
"other": "Otro",
|
||||
"outdoor": "Al aire libre 🏞️",
|
||||
"shopping": "Compras 🛍️",
|
||||
"spiritual_journeys": "Viajes espirituales 🧘♀️",
|
||||
"transportation": "Transporte 🚗",
|
||||
"volunteer_work": "Trabajo voluntario 🤝",
|
||||
"water_sports": "Deportes acuáticos 🚤",
|
||||
"wildlife": "Vida silvestre 🦒"
|
||||
},
|
||||
"no_image_found": "No se encontró ninguna imagen",
|
||||
"adventure_details": "Detalles de la aventura",
|
||||
"adventure_type": "Tipo de aventura",
|
||||
"collection": "Recopilación",
|
||||
"homepage": "Página principal",
|
||||
"latitude": "Latitud",
|
||||
"longitude": "Longitud",
|
||||
"not_found": "Aventura no encontrada",
|
||||
"not_found_desc": "La aventura que buscabas no se pudo encontrar. \nPruebe una aventura diferente o vuelva a consultar más tarde.",
|
||||
"visit": "Visita",
|
||||
"visits": "Visitas",
|
||||
"adventure": "Aventura",
|
||||
"count_txt": "resultados que coinciden con su búsqueda",
|
||||
"create_new": "Crear nuevo...",
|
||||
"ascending": "Ascendente",
|
||||
"collection_adventures": "Incluir aventuras de colección",
|
||||
"date": "Fecha",
|
||||
"descending": "Descendente",
|
||||
"filter": "Filtrar",
|
||||
"name": "Nombre",
|
||||
"order_by": "Ordenar por",
|
||||
"order_direction": "Dirección del pedido",
|
||||
"rating": "Clasificación",
|
||||
"sort": "Clasificar",
|
||||
"sources": "Fuentes",
|
||||
"updated": "Actualizado",
|
||||
"category_filter": "Filtro de categoría",
|
||||
"clear": "Claro",
|
||||
"archived_collections": "Colecciones archivadas",
|
||||
"close_filters": "Cerrar filtros",
|
||||
"my_collections": "Mis colecciones",
|
||||
"open_filters": "Abrir filtros",
|
||||
"private": "Privado",
|
||||
"public": "Público",
|
||||
"archived_collection_message": "¡Colección archivada exitosamente!",
|
||||
"delete_collection": "Eliminar colección",
|
||||
"delete_collection_success": "¡Colección eliminada exitosamente!",
|
||||
"delete_collection_warning": "¿Estás seguro de que deseas eliminar esta colección? \nEsto también eliminará todas las aventuras vinculadas. \nEsta acción no se puede deshacer.",
|
||||
"unarchived_collection_message": "¡Colección desarchivada exitosamente!",
|
||||
"archive": "Archivo",
|
||||
"archived": "Archivado",
|
||||
"edit_collection": "Editar colección",
|
||||
"share": "Compartir",
|
||||
"unarchive": "Desarchivar",
|
||||
"cancel": "Cancelar",
|
||||
"adventure_delete_success": "¡Aventura eliminada con éxito!",
|
||||
"delete_adventure": "Eliminar aventura",
|
||||
"planned": "Planificado",
|
||||
"visited": "Visitado",
|
||||
"dates": "Fechas",
|
||||
"duration": "Duración",
|
||||
"image_removed_error": "Error al eliminar la imagen",
|
||||
"image_removed_success": "¡Imagen eliminada exitosamente!",
|
||||
"image_upload_error": "Error al subir la imagen",
|
||||
"image_upload_success": "¡Imagen cargada exitosamente!",
|
||||
"no_image_url": "No se encontró ninguna imagen en esa URL.",
|
||||
"start_before_end_error": "La fecha de inicio debe ser anterior a la fecha de finalización.",
|
||||
"wiki_image_error": "Error al obtener la imagen de Wikipedia",
|
||||
"actions": "Comportamiento",
|
||||
"activity": "Actividad",
|
||||
"see_adventures": "Ver Aventuras",
|
||||
"activity_types": "Tipos de actividad",
|
||||
"add": "Agregar",
|
||||
"add_notes": "Agregar notas",
|
||||
"adventure_create_error": "No se pudo crear la aventura",
|
||||
"adventure_created": "Aventura creada",
|
||||
"adventure_update_error": "No se pudo actualizar la aventura",
|
||||
"adventure_updated": "Aventura actualizada",
|
||||
"basic_information": "Información básica",
|
||||
"category": "Categoría",
|
||||
"clear_map": "Borrar mapa",
|
||||
"copy_link": "Copiar enlace",
|
||||
"date_constrain": "Restringir a las fechas de recolección",
|
||||
"description": "Descripción",
|
||||
"end_date": "Fecha de finalización",
|
||||
"fetch_image": "Obtener imagen",
|
||||
"generate_desc": "Generar descripción",
|
||||
"image": "Imagen",
|
||||
"image_fetch_failed": "No se pudo recuperar la imagen",
|
||||
"link": "Enlace",
|
||||
"location": "Ubicación",
|
||||
"location_information": "Información de ubicación",
|
||||
"my_images": "Mis imágenes",
|
||||
"my_visits": "Mis visitas",
|
||||
"new_adventure": "Nueva aventura",
|
||||
"no_description_found": "No se encontró ninguna descripción",
|
||||
"no_images": "Sin imágenes",
|
||||
"no_location": "Por favor ingresa una ubicación",
|
||||
"no_results": "No se encontraron resultados",
|
||||
"no_start_date": "Por favor ingrese una fecha de inicio",
|
||||
"public_adventure": "Aventura pública",
|
||||
"remove": "Eliminar",
|
||||
"save_next": "Ahorrar",
|
||||
"search_for_location": "Buscar una ubicación",
|
||||
"search_results": "Resultados de búsqueda",
|
||||
"select_adventure_category": "Seleccionar categoría de aventura",
|
||||
"share_adventure": "¡Comparte esta aventura!",
|
||||
"start_date": "Fecha de inicio",
|
||||
"upload_image": "Subir imagen",
|
||||
"upload_images_here": "Sube imágenes aquí",
|
||||
"url": "URL",
|
||||
"warning": "Advertencia",
|
||||
"wiki_desc": "Extrae un extracto de un artículo de Wikipedia que coincide con el nombre de la aventura.",
|
||||
"wikipedia": "Wikipedia",
|
||||
"add_an_activity": "Agregar una actividad",
|
||||
"adventure_not_found": "No hay aventuras que mostrar. \n¡Agregue algunos usando el botón más en la parte inferior derecha o intente cambiar los filtros!",
|
||||
"no_adventures_found": "No se encontraron aventuras",
|
||||
"no_collections_found": "No se encontraron colecciones para agregar esta aventura.",
|
||||
"my_adventures": "mis aventuras",
|
||||
"no_linkable_adventures": "No se encontraron aventuras que puedan vincularse a esta colección.",
|
||||
"mark_region_as_visited": "¿Marcar región {region}, {country} como visitada?",
|
||||
"mark_visited": "Marcos visitó",
|
||||
"not_visited": "No visitado",
|
||||
"all": "Todo",
|
||||
"error_updating_regions": "Error al actualizar regiones",
|
||||
"regions_updated": "regiones actualizadas",
|
||||
"update_visited_regions": "Actualizar regiones visitadas",
|
||||
"update_visited_regions_disclaimer": "Esto puede llevar un tiempo dependiendo de la cantidad de aventuras que hayas visitado.",
|
||||
"visited_region_check": "Verificación de región visitada",
|
||||
"visited_region_check_desc": "Al seleccionar esto, el servidor verificará todas sus aventuras visitadas y marcará las regiones en las que se encuentran como visitadas en viajes mundiales.",
|
||||
"add_new": "Agregar nuevo...",
|
||||
"checklist": "Lista de verificación",
|
||||
"checklists": "Listas de verificación",
|
||||
"collection_archived": "Esta colección ha sido archivada.",
|
||||
"collection_completed": "¡Has completado esta colección!",
|
||||
"collection_stats": "Estadísticas de colección",
|
||||
"days": "días",
|
||||
"itineary_by_date": "Itinerario por fecha",
|
||||
"keep_exploring": "¡Sigue explorando!",
|
||||
"link_new": "Enlace nuevo...",
|
||||
"linked_adventures": "Aventuras vinculadas",
|
||||
"links": "Campo de golf",
|
||||
"no_end_date": "Por favor ingresa una fecha de finalización",
|
||||
"note": "Nota",
|
||||
"notes": "Notas",
|
||||
"nothing_planned": "Nada planeado para este día. \n¡Disfruta el viaje!",
|
||||
"transportation": "Transporte",
|
||||
"transportations": "Transportes",
|
||||
"visit_link": "Visitar enlace",
|
||||
"day": "Día",
|
||||
"add_a_tag": "Agregar una etiqueta",
|
||||
"tags": "Etiquetas",
|
||||
"set_to_pin": "Establecer en Fijar"
|
||||
},
|
||||
"worldtravel": {
|
||||
"all": "Todo",
|
||||
"all_subregions": "Todas las subregiones",
|
||||
"clear_search": "Borrar búsqueda",
|
||||
"completely_visited": "Completamente visitado",
|
||||
"no_countries_found": "No se encontraron países",
|
||||
"not_visited": "No visitado",
|
||||
"num_countries": "países encontrados",
|
||||
"partially_visited": "Parcialmente visitado",
|
||||
"country_list": "Lista de países"
|
||||
},
|
||||
"auth": {
|
||||
"forgot_password": "¿Has olvidado tu contraseña?",
|
||||
"login": "Acceso",
|
||||
"login_error": "No se puede iniciar sesión con las credenciales proporcionadas.",
|
||||
"password": "Contraseña",
|
||||
"signup": "Inscribirse",
|
||||
"username": "Nombre de usuario",
|
||||
"confirm_password": "confirmar Contraseña",
|
||||
"email": "Correo electrónico",
|
||||
"first_name": "Nombre de pila",
|
||||
"last_name": "Apellido",
|
||||
"registration_disabled": "El registro está actualmente deshabilitado.",
|
||||
"profile_picture": "Foto de perfil",
|
||||
"public_profile": "Perfil público",
|
||||
"public_tooltip": "Con un perfil público, los usuarios pueden compartir colecciones con usted y ver su perfil en la página de usuarios."
|
||||
},
|
||||
"users": {
|
||||
"no_users_found": "No se encontraron usuarios con perfiles públicos."
|
||||
},
|
||||
"settings": {
|
||||
"account_settings": "Configuración de cuenta de usuario",
|
||||
"confirm_new_password": "Confirmar nueva contraseña",
|
||||
"current_email": "Correo electrónico actual",
|
||||
"email_change": "Cambiar correo electrónico",
|
||||
"new_email": "Nuevo correo electrónico",
|
||||
"new_password": "Nueva contraseña",
|
||||
"no_email_set": "No hay correo electrónico configurado",
|
||||
"password_change": "Cambiar la contraseña",
|
||||
"settings_page": "Página de configuración",
|
||||
"update": "Actualizar",
|
||||
"update_error": "Error al actualizar la configuración",
|
||||
"update_success": "¡La configuración se actualizó correctamente!",
|
||||
"change_password": "Cambiar la contraseña",
|
||||
"login_redir": "Luego será redirigido a la página de inicio de sesión.",
|
||||
"missing_email": "Por favor ingrese una dirección de correo electrónico",
|
||||
"possible_reset": "Si la dirección de correo electrónico que proporcionó está asociada con una cuenta, recibirá un correo electrónico con instrucciones para restablecer su contraseña.",
|
||||
"reset_password": "Restablecer contraseña",
|
||||
"token_required": "Se requieren token y UID para restablecer la contraseña.",
|
||||
"password_does_not_match": "Las contraseñas no coinciden",
|
||||
"password_is_required": "Se requiere contraseña",
|
||||
"submit": "Entregar",
|
||||
"invalid_token": "El token no es válido o ha caducado",
|
||||
"about_this_background": "Sobre este trasfondo",
|
||||
"join_discord": "Únete a la discordia",
|
||||
"join_discord_desc": "para compartir tus propias fotos. \nPublicarlos en el",
|
||||
"photo_by": "Foto por"
|
||||
},
|
||||
"checklist": {
|
||||
"add_item": "Agregar artículo",
|
||||
"checklist_delete_error": "Error al eliminar la lista de verificación",
|
||||
"checklist_deleted": "¡Lista de verificación eliminada exitosamente!",
|
||||
"checklist_editor": "Editor de lista de verificación",
|
||||
"checklist_public": "Esta lista de verificación es pública porque se encuentra en una colección pública.",
|
||||
"editing_checklist": "Lista de verificación de edición",
|
||||
"failed_to_save": "No se pudo guardar la lista de verificación",
|
||||
"item": "Artículo",
|
||||
"item_already_exists": "El artículo ya existe",
|
||||
"item_cannot_be_empty": "El artículo no puede estar vacío",
|
||||
"items": "Elementos",
|
||||
"new_item": "Nuevo artículo",
|
||||
"save": "Ahorrar"
|
||||
},
|
||||
"collection": {
|
||||
"collection_created": "¡Colección creada con éxito!",
|
||||
"collection_edit_success": "¡Colección editada con éxito!",
|
||||
"create": "Crear",
|
||||
"edit_collection": "Editar colección",
|
||||
"error_creating_collection": "Error al crear la colección",
|
||||
"error_editing_collection": "Error al editar la colección",
|
||||
"new_collection": "Nueva colección"
|
||||
},
|
||||
"notes": {
|
||||
"add_a_link": "Agregar un enlace",
|
||||
"content": "Contenido",
|
||||
"editing_note": "Nota de edición",
|
||||
"failed_to_save": "No se pudo guardar la nota",
|
||||
"note_delete_error": "Error al eliminar la nota",
|
||||
"note_deleted": "¡Nota eliminada exitosamente!",
|
||||
"note_editor": "Editor de notas",
|
||||
"note_public": "Esta nota es pública porque está en una colección pública.",
|
||||
"open": "Abierto",
|
||||
"save": "Ahorrar",
|
||||
"invalid_url": "URL no válida"
|
||||
},
|
||||
"transportation": {
|
||||
"date_and_time": "Fecha",
|
||||
"error_editing_transportation": "Error al editar el transporte",
|
||||
"modes": {
|
||||
"bus": "Autobús",
|
||||
"bike": "Bicicleta",
|
||||
"boat": "Bote",
|
||||
"car": "Auto",
|
||||
"other": "Otro",
|
||||
"plane": "Avión",
|
||||
"train": "Tren",
|
||||
"walking": "Caminando"
|
||||
},
|
||||
"new_transportation": "Nuevo transporte",
|
||||
"provide_start_date": "Por favor proporcione una fecha de inicio",
|
||||
"start": "Comenzar",
|
||||
"to_location": "A la ubicación",
|
||||
"transport_type": "Tipo de transporte",
|
||||
"transportation_deleted": "¡Transporte eliminado exitosamente!",
|
||||
"transportation_edit_success": "Transporte editado exitosamente!",
|
||||
"type": "Tipo",
|
||||
"date_time": "Fecha de inicio",
|
||||
"edit": "Editar",
|
||||
"edit_transportation": "Editar transporte",
|
||||
"end_date_time": "Fecha de finalización",
|
||||
"flight_number": "Número de vuelo",
|
||||
"from_location": "Desde la ubicación",
|
||||
"transportation_added": "¡Transporte agregado exitosamente!",
|
||||
"transportation_delete_error": "Error al eliminar el transporte"
|
||||
},
|
||||
"search": {
|
||||
"adventurelog_results": "Resultados del registro de aventuras",
|
||||
"online_results": "Resultados en línea",
|
||||
"public_adventures": "Aventuras públicas"
|
||||
},
|
||||
"map": {
|
||||
"add_adventure": "Agregar nueva aventura",
|
||||
"add_adventure_at_marker": "Agregar nueva aventura en Marker",
|
||||
"adventure_map": "Mapa de aventuras",
|
||||
"clear_marker": "Borrar marcador",
|
||||
"map_options": "Opciones de mapa",
|
||||
"show_visited_regions": "Mostrar regiones visitadas",
|
||||
"view_details": "Ver detalles"
|
||||
},
|
||||
"share": {
|
||||
"no_users_shared": "Ningún usuario compartió con",
|
||||
"not_shared_with": "No compartido con",
|
||||
"share_desc": "Comparte esta colección con otros usuarios.",
|
||||
"shared": "Compartido",
|
||||
"shared_with": "Compartido con",
|
||||
"unshared": "Incompartible",
|
||||
"with": "con",
|
||||
"go_to_settings": "Ir a configuración",
|
||||
"no_shared_found": "No se encontraron colecciones que se compartan contigo.",
|
||||
"set_public": "Para permitir que los usuarios compartan contenido con usted, necesita que su perfil esté configurado como público."
|
||||
},
|
||||
"languages": {
|
||||
"de": "Alemán",
|
||||
"en": "Inglés",
|
||||
"es": "Español",
|
||||
"fr": "Francés",
|
||||
"it": "italiano",
|
||||
"nl": "Holandés",
|
||||
"sv": "sueco",
|
||||
"zh": "Chino"
|
||||
},
|
||||
"profile": {
|
||||
"member_since": "Miembro desde",
|
||||
"user_stats": "Estadísticas de usuario",
|
||||
"visited_countries": "Países visitados",
|
||||
"visited_regions": "Regiones visitadas"
|
||||
}
|
||||
}
|
403
frontend/src/locales/fr.json
Normal file
403
frontend/src/locales/fr.json
Normal file
|
@ -0,0 +1,403 @@
|
|||
{
|
||||
"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",
|
||||
"nominatim_2": "Leurs données sont sous licence ODbL.",
|
||||
"oss_attributions": "Attributions Open Source",
|
||||
"other_attributions": "Des attributions supplémentaires peuvent être trouvées dans le fichier README.",
|
||||
"source_code": "Code source"
|
||||
},
|
||||
"adventures": {
|
||||
"activities": {
|
||||
"activity": "Activité 🏄",
|
||||
"art_museums": "Art",
|
||||
"attraction": "Attraction 🎢",
|
||||
"culture": "Culturel 🎭",
|
||||
"dining": "Restauration 🍽️",
|
||||
"event": "Événement 🎉",
|
||||
"festivals": "Fêtes 🎪",
|
||||
"fitness": "Remise en forme 🏋️",
|
||||
"general": "Général 🌍",
|
||||
"hiking": "Randonnée 🥾",
|
||||
"historical_sites": "Sites historiques 🏛️",
|
||||
"lodging": "Hébergement 🛌",
|
||||
"music_concerts": "Musique",
|
||||
"nightlife": "Vie nocturne 🌃",
|
||||
"other": "Autre",
|
||||
"outdoor": "En plein air 🏞️",
|
||||
"shopping": "Shopping 🛍️",
|
||||
"spiritual_journeys": "Voyages spirituels 🧘♀️",
|
||||
"transportation": "Transport 🚗",
|
||||
"volunteer_work": "Travail bénévole 🤝",
|
||||
"water_sports": "Sports nautiques 🚤",
|
||||
"wildlife": "Faune 🦒"
|
||||
},
|
||||
"add_to_collection": "Ajouter à la collection",
|
||||
"adventure_delete_confirm": "Êtes-vous sûr de vouloir supprimer cette aventure ? \nCette action ne peut pas être annulée.",
|
||||
"collection_link_error": "Erreur lors de la liaison de l'aventure à la collection",
|
||||
"collection_link_success": "Aventure liée à la collection avec succès !",
|
||||
"collection_remove_error": "Erreur lors de la suppression de l'aventure de la collection",
|
||||
"collection_remove_success": "Aventure supprimée de la collection avec succès !",
|
||||
"delete": "Supprimer",
|
||||
"edit_adventure": "Modifier l'aventure",
|
||||
"no_image_found": "Aucune image trouvée",
|
||||
"open_details": "Ouvrir les détails",
|
||||
"remove_from_collection": "Supprimer de la collection",
|
||||
"adventure": "Aventure",
|
||||
"adventure_delete_success": "Aventure supprimée avec succès !",
|
||||
"adventure_details": "Détails de l'aventure",
|
||||
"adventure_type": "Type d'aventure",
|
||||
"archive": "Archive",
|
||||
"archived": "Archivé",
|
||||
"archived_collection_message": "Collection archivée avec succès !",
|
||||
"archived_collections": "Collections archivées",
|
||||
"ascending": "Ascendant",
|
||||
"cancel": "Annuler",
|
||||
"category_filter": "Filtre de catégorie",
|
||||
"clear": "Clair",
|
||||
"close_filters": "Fermer les filtres",
|
||||
"collection": "Collection",
|
||||
"collection_adventures": "Inclure les aventures de collection",
|
||||
"count_txt": "résultats correspondant à votre recherche",
|
||||
"create_new": "Créer un nouveau...",
|
||||
"date": "Date",
|
||||
"dates": "Dates",
|
||||
"delete_adventure": "Supprimer l'aventure",
|
||||
"delete_collection": "Supprimer la collection",
|
||||
"delete_collection_success": "Collection supprimée avec succès !",
|
||||
"delete_collection_warning": "Etes-vous sûr de vouloir supprimer cette collection ? \nCela supprimera également toutes les aventures liées. \nCette action ne peut pas être annulée.",
|
||||
"descending": "Descendant",
|
||||
"duration": "Durée",
|
||||
"edit_collection": "Modifier la collection",
|
||||
"filter": "Filtre",
|
||||
"homepage": "Page d'accueil",
|
||||
"image_removed_error": "Erreur lors de la suppression de l'image",
|
||||
"image_removed_success": "Image supprimée avec succès !",
|
||||
"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.",
|
||||
"not_found": "Aventure introuvable",
|
||||
"not_found_desc": "L'aventure que vous cherchiez est introuvable. \nVeuillez essayer une autre aventure ou revenez plus tard.",
|
||||
"open_filters": "Ouvrir les filtres",
|
||||
"order_by": "Commander par",
|
||||
"order_direction": "Direction de la commande",
|
||||
"planned": "Prévu",
|
||||
"private": "Privé",
|
||||
"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": "Mis à jour",
|
||||
"visit": "Visite",
|
||||
"visited": "Visité",
|
||||
"visits": "Visites",
|
||||
"wiki_image_error": "Erreur lors de la récupération de l'image depuis Wikipédia",
|
||||
"actions": "Actes",
|
||||
"activity": "Activité",
|
||||
"activity_types": "Types d'activités",
|
||||
"add": "Ajouter",
|
||||
"add_an_activity": "Ajouter une activité",
|
||||
"add_notes": "Ajouter des notes",
|
||||
"adventure_create_error": "Échec de la création de l'aventure",
|
||||
"adventure_created": "Aventure créée",
|
||||
"adventure_update_error": "Échec de la mise à jour de l'aventure",
|
||||
"adventure_updated": "Aventure mise à jour",
|
||||
"basic_information": "Informations de base",
|
||||
"category": "Catégorie",
|
||||
"clear_map": "Effacer la carte",
|
||||
"copy_link": "Copier le lien",
|
||||
"date_constrain": "Contraindre aux dates de collecte",
|
||||
"description": "Description",
|
||||
"end_date": "Date de fin",
|
||||
"fetch_image": "Récupérer une image",
|
||||
"generate_desc": "Générer une description",
|
||||
"image": "Image",
|
||||
"image_fetch_failed": "Échec de la récupération de l'image",
|
||||
"link": "Lien",
|
||||
"location": "Emplacement",
|
||||
"location_information": "Informations de localisation",
|
||||
"my_images": "Mes images",
|
||||
"my_visits": "Mes visites",
|
||||
"new_adventure": "Nouvelle aventure",
|
||||
"no_description_found": "Aucune description trouvée",
|
||||
"no_images": "Aucune image",
|
||||
"no_location": "Veuillez entrer un emplacement",
|
||||
"no_results": "Aucun résultat trouvé",
|
||||
"no_start_date": "Veuillez entrer une date de début",
|
||||
"public_adventure": "Aventure publique",
|
||||
"remove": "Retirer",
|
||||
"save_next": "Sauvegarder",
|
||||
"search_for_location": "Rechercher un emplacement",
|
||||
"search_results": "Résultats de la recherche",
|
||||
"see_adventures": "Voir les aventures",
|
||||
"select_adventure_category": "Sélectionnez la catégorie d'aventure",
|
||||
"share_adventure": "Partagez cette aventure !",
|
||||
"start_date": "Date de début",
|
||||
"upload_image": "Télécharger une image",
|
||||
"upload_images_here": "Téléchargez des images ici",
|
||||
"url": "URL",
|
||||
"warning": "Avertissement",
|
||||
"wiki_desc": "Extrait un extrait de l'article Wikipédia correspondant au nom de l'aventure.",
|
||||
"wikipedia": "Wikipédia",
|
||||
"adventure_not_found": "Il n'y a aucune aventure à afficher. \nAjoutez-en en utilisant le bouton plus en bas à droite ou essayez de changer les filtres !",
|
||||
"all": "Tous",
|
||||
"error_updating_regions": "Erreur lors de la mise à jour des régions",
|
||||
"mark_region_as_visited": "Marquer la région {region}, {country} comme visitée ?",
|
||||
"mark_visited": "Mark a visité",
|
||||
"my_adventures": "Mes aventures",
|
||||
"no_adventures_found": "Aucune aventure trouvée",
|
||||
"no_collections_found": "Aucune collection trouvée pour ajouter cette aventure.",
|
||||
"no_linkable_adventures": "Aucune aventure trouvée pouvant être liée à cette collection.",
|
||||
"not_visited": "Non visité",
|
||||
"regions_updated": "régions mises à jour",
|
||||
"update_visited_regions": "Mettre à jour les régions visitées",
|
||||
"update_visited_regions_disclaimer": "Cela peut prendre un certain temps en fonction du nombre d'aventures que vous avez visitées.",
|
||||
"visited_region_check": "Vérification de la région visitée",
|
||||
"visited_region_check_desc": "En sélectionnant cette option, le serveur vérifiera toutes vos aventures visitées et marquera les régions dans lesquelles elles se trouvent comme visitées lors des voyages dans le monde.",
|
||||
"add_new": "Ajouter un nouveau...",
|
||||
"checklists": "Listes de contrôle",
|
||||
"collection_archived": "Cette collection a été archivée.",
|
||||
"collection_completed": "Vous avez terminé cette collection !",
|
||||
"collection_stats": "Statistiques de collecte",
|
||||
"days": "jours",
|
||||
"itineary_by_date": "Itinéraire par date",
|
||||
"keep_exploring": "Continuez à explorer !",
|
||||
"link_new": "Lien Nouveau...",
|
||||
"linked_adventures": "Aventures liées",
|
||||
"links": "Links",
|
||||
"no_end_date": "Veuillez saisir une date de fin",
|
||||
"note": "Note",
|
||||
"notes": "Remarques",
|
||||
"nothing_planned": "Rien de prévu pour cette journée. \nBon voyage !",
|
||||
"transportation": "Transport",
|
||||
"transportations": "Transports",
|
||||
"visit_link": "Visitez le lien",
|
||||
"checklist": "Liste de contrôle",
|
||||
"day": "Jour",
|
||||
"add_a_tag": "Ajouter une balise",
|
||||
"tags": "Balises",
|
||||
"set_to_pin": "Définir sur Épingler"
|
||||
},
|
||||
"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. \nExplorez 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"
|
||||
},
|
||||
"navbar": {
|
||||
"about": "À propos de AdventureLog",
|
||||
"adventures": "Aventures",
|
||||
"collections": "Collections",
|
||||
"discord": "Discorde",
|
||||
"documentation": "Documentation",
|
||||
"greeting": "Salut",
|
||||
"logout": "Déconnexion",
|
||||
"map": "Carte",
|
||||
"my_adventures": "Mes aventures",
|
||||
"profile": "Profil",
|
||||
"search": "Recherche",
|
||||
"settings": "Paramètres",
|
||||
"shared_with_me": "Partagé avec moi",
|
||||
"theme_selection": "Sélection de thèmes",
|
||||
"themes": {
|
||||
"forest": "Forêt",
|
||||
"light": "Lumière",
|
||||
"night": "Nuit",
|
||||
"aestetic-dark": "Esthétique sombre",
|
||||
"aestetic-light": "Lumière esthétique",
|
||||
"aqua": "Aqua",
|
||||
"dark": "Sombre"
|
||||
},
|
||||
"users": "Utilisateurs",
|
||||
"worldtravel": "Voyage dans le monde",
|
||||
"my_tags": "Mes balises",
|
||||
"tag": "Étiqueter",
|
||||
"language_selection": "Langue"
|
||||
},
|
||||
"auth": {
|
||||
"confirm_password": "Confirmez 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",
|
||||
"public_tooltip": "Avec un profil public, les utilisateurs peuvent partager des collections avec vous et afficher votre profil sur la page des utilisateurs."
|
||||
},
|
||||
"users": {
|
||||
"no_users_found": "Aucun utilisateur trouvé avec des profils publics."
|
||||
},
|
||||
"worldtravel": {
|
||||
"all": "Tous",
|
||||
"all_subregions": "Toutes les sous-régions",
|
||||
"clear_search": "Effacer la recherche",
|
||||
"completely_visited": "Entièrement visité",
|
||||
"country_list": "Liste des pays",
|
||||
"no_countries_found": "Aucun pays trouvé",
|
||||
"not_visited": "Non visité",
|
||||
"num_countries": "pays trouvés",
|
||||
"partially_visited": "Partiellement visité"
|
||||
},
|
||||
"settings": {
|
||||
"account_settings": "Paramètres du compte utilisateur",
|
||||
"confirm_new_password": "Confirmer le nouveau mot de passe",
|
||||
"current_email": "Courriel actuel",
|
||||
"email_change": "Changer l'e-mail",
|
||||
"new_email": "Nouvel e-mail",
|
||||
"new_password": "Nouveau mot de passe",
|
||||
"no_email_set": "Aucune adresse e-mail définie",
|
||||
"password_change": "Changer le mot de passe",
|
||||
"settings_page": "Page Paramètres",
|
||||
"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",
|
||||
"invalid_token": "Le jeton n'est pas valide ou a expiré",
|
||||
"login_redir": "Vous serez alors redirigé vers la page de connexion.",
|
||||
"missing_email": "Veuillez entrer une adresse e-mail",
|
||||
"password_does_not_match": "Les mots de passe ne correspondent pas",
|
||||
"password_is_required": "Le mot de passe est requis",
|
||||
"possible_reset": "Si l'adresse e-mail que vous avez fournie est associée à un compte, vous recevrez un e-mail avec des instructions pour réinitialiser votre mot de passe !",
|
||||
"reset_password": "Réinitialiser le mot de passe",
|
||||
"submit": "Soumettre",
|
||||
"token_required": "Le jeton et l'UID sont requis pour la réinitialisation du mot de passe.",
|
||||
"about_this_background": "À propos de ce contexte",
|
||||
"join_discord": "Rejoignez le Discord",
|
||||
"join_discord_desc": "pour partager vos propres photos. \nPostez-les dans le",
|
||||
"photo_by": "Photo par"
|
||||
},
|
||||
"checklist": {
|
||||
"add_item": "Ajouter un article",
|
||||
"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.",
|
||||
"editing_checklist": "Liste de contrôle d'édition",
|
||||
"failed_to_save": "Échec de l'enregistrement de la liste de contrôle",
|
||||
"item": "Article",
|
||||
"item_already_exists": "L'article existe déjà",
|
||||
"item_cannot_be_empty": "L'élément ne peut pas être vide",
|
||||
"items": "Articles",
|
||||
"new_item": "Nouvel article",
|
||||
"save": "Sauvegarder"
|
||||
},
|
||||
"collection": {
|
||||
"collection_created": "Collection créée avec succès !",
|
||||
"collection_edit_success": "Collection modifiée avec succès !",
|
||||
"create": "Créer",
|
||||
"edit_collection": "Modifier la collection",
|
||||
"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"
|
||||
},
|
||||
"notes": {
|
||||
"add_a_link": "Ajouter un lien",
|
||||
"content": "Contenu",
|
||||
"editing_note": "Note d'édition",
|
||||
"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"
|
||||
},
|
||||
"transportation": {
|
||||
"date_time": "Date de début",
|
||||
"edit": "Modifier",
|
||||
"edit_transportation": "Modifier le transport",
|
||||
"end_date_time": "Date de fin",
|
||||
"error_editing_transportation": "Erreur lors de la modification du transport",
|
||||
"flight_number": "Numéro du vol",
|
||||
"from_location": "De l'emplacement",
|
||||
"modes": {
|
||||
"bike": "Vélo",
|
||||
"boat": "Bateau",
|
||||
"bus": "Bus",
|
||||
"car": "Voiture",
|
||||
"other": "Autre",
|
||||
"plane": "Avion",
|
||||
"train": "Former",
|
||||
"walking": "Marche"
|
||||
},
|
||||
"new_transportation": "Nouveau transport",
|
||||
"provide_start_date": "Veuillez fournir une date de début",
|
||||
"start": "Commencer",
|
||||
"to_location": "Vers l'emplacement",
|
||||
"transport_type": "Type de transport",
|
||||
"type": "Taper",
|
||||
"date_and_time": "Date",
|
||||
"transportation_added": "Transport ajouté avec succès !",
|
||||
"transportation_delete_error": "Erreur lors de la suppression du transport",
|
||||
"transportation_deleted": "Transport supprimé avec succès !",
|
||||
"transportation_edit_success": "Transport modifié avec succès !"
|
||||
},
|
||||
"search": {
|
||||
"adventurelog_results": "Résultats du journal d'aventure",
|
||||
"online_results": "Résultats en ligne",
|
||||
"public_adventures": "Aventures publiques"
|
||||
},
|
||||
"map": {
|
||||
"add_adventure": "Ajouter une nouvelle aventure",
|
||||
"add_adventure_at_marker": "Ajouter une nouvelle aventure au marqueur",
|
||||
"adventure_map": "Carte d'aventure",
|
||||
"clear_marker": "Effacer le marqueur",
|
||||
"map_options": "Options de la carte",
|
||||
"show_visited_regions": "Afficher les régions visitées",
|
||||
"view_details": "Afficher les détails"
|
||||
},
|
||||
"languages": {
|
||||
"de": "Allemand",
|
||||
"en": "Anglais",
|
||||
"es": "Espagnol",
|
||||
"fr": "Français",
|
||||
"it": "italien",
|
||||
"nl": "Néerlandais",
|
||||
"sv": "suédois",
|
||||
"zh": "Chinois"
|
||||
},
|
||||
"share": {
|
||||
"no_users_shared": "Aucun utilisateur partagé avec",
|
||||
"not_shared_with": "Non partagé avec",
|
||||
"share_desc": "Partagez cette collection avec d'autres utilisateurs.",
|
||||
"shared": "Commun",
|
||||
"shared_with": "Partagé avec",
|
||||
"unshared": "Non partagé",
|
||||
"with": "avec",
|
||||
"go_to_settings": "Allez dans les paramètres",
|
||||
"no_shared_found": "Aucune collection trouvée partagée avec vous.",
|
||||
"set_public": "Afin de permettre aux utilisateurs de partager avec vous, vous devez définir votre profil comme public."
|
||||
},
|
||||
"profile": {
|
||||
"member_since": "Membre depuis",
|
||||
"user_stats": "Statistiques des utilisateurs",
|
||||
"visited_countries": "Pays visités",
|
||||
"visited_regions": "Régions visitées"
|
||||
}
|
||||
}
|
403
frontend/src/locales/it.json
Normal file
403
frontend/src/locales/it.json
Normal file
|
@ -0,0 +1,403 @@
|
|||
{
|
||||
"about": {
|
||||
"about": "Di",
|
||||
"close": "Vicino",
|
||||
"license": "Concesso in licenza con la licenza GPL-3.0.",
|
||||
"message": "Realizzato con ❤️ negli Stati Uniti.",
|
||||
"nominatim_1": "La ricerca della posizione e la geocodifica sono fornite da",
|
||||
"nominatim_2": "I loro dati sono concessi in licenza con la licenza ODbL.",
|
||||
"oss_attributions": "Attribuzioni Open Source",
|
||||
"other_attributions": "Ulteriori attribuzioni possono essere trovate nel file README.",
|
||||
"source_code": "Codice sorgente"
|
||||
},
|
||||
"adventures": {
|
||||
"activities": {
|
||||
"activity": "Attività 🏄",
|
||||
"art_museums": "Arte",
|
||||
"attraction": "Attrazione 🎢",
|
||||
"culture": "Cultura 🎭",
|
||||
"dining": "Pranzo 🍽️",
|
||||
"event": "Evento 🎉",
|
||||
"festivals": "Festival 🎪",
|
||||
"fitness": "Forma fisica 🏋️",
|
||||
"general": "Generale 🌍",
|
||||
"hiking": "Escursionismo 🥾",
|
||||
"historical_sites": "Siti storici 🏛️",
|
||||
"lodging": "Alloggio 🛌",
|
||||
"music_concerts": "Musica",
|
||||
"nightlife": "Vita notturna 🌃",
|
||||
"other": "Altro",
|
||||
"outdoor": "All'aperto 🏞️",
|
||||
"shopping": "La spesa 🛍️",
|
||||
"spiritual_journeys": "Viaggi Spirituali 🧘♀️",
|
||||
"transportation": "Trasporti 🚗",
|
||||
"volunteer_work": "Lavoro volontario 🤝",
|
||||
"water_sports": "Sport acquatici 🚤",
|
||||
"wildlife": "Fauna selvatica 🦒"
|
||||
},
|
||||
"add_to_collection": "Aggiungi alla raccolta",
|
||||
"adventure": "Avventura",
|
||||
"adventure_delete_confirm": "Sei sicuro di voler eliminare questa avventura? \nQuesta azione non può essere annullata.",
|
||||
"adventure_details": "Dettagli dell'avventura",
|
||||
"adventure_type": "Tipo di avventura",
|
||||
"archive": "Archivio",
|
||||
"archived": "Archiviato",
|
||||
"archived_collection_message": "Raccolta archiviata con successo!",
|
||||
"archived_collections": "Collezioni archiviate",
|
||||
"ascending": "Ascendente",
|
||||
"cancel": "Cancellare",
|
||||
"category_filter": "Filtro categoria",
|
||||
"clear": "Chiaro",
|
||||
"close_filters": "Chiudi filtri",
|
||||
"collection": "Collezione",
|
||||
"collection_link_error": "Errore nel collegamento dell'avventura alla raccolta",
|
||||
"collection_remove_error": "Errore durante la rimozione dell'avventura dalla raccolta",
|
||||
"collection_remove_success": "Avventura rimossa con successo dalla raccolta!",
|
||||
"count_txt": "risultati corrispondenti alla tua ricerca",
|
||||
"create_new": "Crea nuovo...",
|
||||
"date": "Data",
|
||||
"delete": "Eliminare",
|
||||
"delete_collection": "Elimina raccolta",
|
||||
"delete_collection_success": "Raccolta eliminata con successo!",
|
||||
"delete_collection_warning": "Sei sicuro di voler eliminare questa raccolta? \nCiò eliminerà anche tutte le avventure collegate. \nQuesta azione non può essere annullata.",
|
||||
"descending": "Discendente",
|
||||
"edit_adventure": "Modifica Avventura",
|
||||
"edit_collection": "Modifica raccolta",
|
||||
"filter": "Filtro",
|
||||
"homepage": "Home page",
|
||||
"latitude": "Latitudine",
|
||||
"longitude": "Longitudine",
|
||||
"my_collections": "Le mie collezioni",
|
||||
"name": "Nome",
|
||||
"no_image_found": "Nessuna immagine trovata",
|
||||
"not_found": "Avventura non trovata",
|
||||
"not_found_desc": "L'avventura che stavi cercando non è stata trovata. \nProva un'avventura diversa o riprova più tardi.",
|
||||
"open_details": "Apri Dettagli",
|
||||
"open_filters": "Apri filtri",
|
||||
"order_by": "Ordina per",
|
||||
"order_direction": "Direzione dell'ordine",
|
||||
"private": "Privato",
|
||||
"public": "Pubblico",
|
||||
"rating": "Valutazione",
|
||||
"remove_from_collection": "Rimuovi dalla raccolta",
|
||||
"share": "Condividere",
|
||||
"sort": "Ordinare",
|
||||
"sources": "Fonti",
|
||||
"unarchive": "Annulla l'archiviazione",
|
||||
"unarchived_collection_message": "Raccolta annullata con successo!",
|
||||
"updated": "Aggiornato",
|
||||
"visit": "Visita",
|
||||
"visits": "Visite",
|
||||
"adventure_delete_success": "Avventura eliminata con successo!",
|
||||
"collection_adventures": "Includi avventure di raccolta",
|
||||
"collection_link_success": "Avventura collegata alla raccolta con successo!",
|
||||
"dates": "Date",
|
||||
"delete_adventure": "Elimina avventura",
|
||||
"duration": "Durata",
|
||||
"image_removed_error": "Errore durante la rimozione dell'immagine",
|
||||
"image_removed_success": "Immagine rimossa con successo!",
|
||||
"image_upload_error": "Errore durante il caricamento dell'immagine",
|
||||
"image_upload_success": "Immagine caricata con successo!",
|
||||
"no_image_url": "Nessuna immagine trovata a quell'URL.",
|
||||
"planned": "Pianificato",
|
||||
"start_before_end_error": "La data di inizio deve essere antecedente alla data di fine",
|
||||
"visited": "Visitato",
|
||||
"actions": "Azioni",
|
||||
"activity": "Attività",
|
||||
"activity_types": "Tipi di attività",
|
||||
"add": "Aggiungere",
|
||||
"add_an_activity": "Aggiungi un'attività",
|
||||
"add_notes": "Aggiungi note",
|
||||
"adventure_create_error": "Impossibile creare l'avventura",
|
||||
"adventure_created": "Avventura creata",
|
||||
"adventure_update_error": "Impossibile aggiornare l'avventura",
|
||||
"adventure_updated": "Avventura aggiornata",
|
||||
"basic_information": "Informazioni di base",
|
||||
"category": "Categoria",
|
||||
"clear_map": "Mappa chiara",
|
||||
"copy_link": "Copia collegamento",
|
||||
"date_constrain": "Vincolare alle date di raccolta",
|
||||
"description": "Descrizione",
|
||||
"end_date": "Data di fine",
|
||||
"fetch_image": "Recupera immagine",
|
||||
"generate_desc": "Genera descrizione",
|
||||
"image": "Immagine",
|
||||
"image_fetch_failed": "Impossibile recuperare l'immagine",
|
||||
"link": "Collegamento",
|
||||
"location": "Posizione",
|
||||
"location_information": "Informazioni sulla posizione",
|
||||
"my_images": "Le mie immagini",
|
||||
"my_visits": "Le mie visite",
|
||||
"new_adventure": "Nuova avventura",
|
||||
"no_description_found": "Nessuna descrizione trovata",
|
||||
"no_images": "Nessuna immagine",
|
||||
"no_location": "Inserisci una località",
|
||||
"no_results": "Nessun risultato trovato",
|
||||
"no_start_date": "Inserisci una data di inizio",
|
||||
"public_adventure": "Avventura pubblica",
|
||||
"remove": "Rimuovere",
|
||||
"save_next": "Salva",
|
||||
"search_for_location": "Cerca una posizione",
|
||||
"search_results": "Risultati della ricerca",
|
||||
"see_adventures": "Vedi Avventure",
|
||||
"select_adventure_category": "Seleziona la categoria Avventura",
|
||||
"share_adventure": "Condividi questa avventura!",
|
||||
"start_date": "Data di inizio",
|
||||
"upload_image": "Carica immagine",
|
||||
"upload_images_here": "Carica le immagini qui",
|
||||
"url": "URL",
|
||||
"warning": "Avvertimento",
|
||||
"wiki_desc": "Estrae un estratto dall'articolo di Wikipedia corrispondente al nome dell'avventura.",
|
||||
"wiki_image_error": "Errore durante il recupero dell'immagine da Wikipedia",
|
||||
"wikipedia": "Wikipedia",
|
||||
"adventure_not_found": "Non ci sono avventure da visualizzare. \nAggiungine alcuni utilizzando il pulsante più in basso a destra o prova a cambiare i filtri!",
|
||||
"all": "Tutto",
|
||||
"error_updating_regions": "Errore durante l'aggiornamento delle regioni",
|
||||
"mark_region_as_visited": "Contrassegnare la regione {regione}, {paese} come visitata?",
|
||||
"mark_visited": "Marco ha visitato",
|
||||
"my_adventures": "Le mie avventure",
|
||||
"no_adventures_found": "Nessuna avventura trovata",
|
||||
"no_collections_found": "Nessuna raccolta trovata a cui aggiungere questa avventura.",
|
||||
"no_linkable_adventures": "Non è stata trovata alcuna avventura che possa essere collegata a questa raccolta.",
|
||||
"not_visited": "Non visitato",
|
||||
"regions_updated": "regioni aggiornate",
|
||||
"update_visited_regions": "Aggiorna le regioni visitate",
|
||||
"update_visited_regions_disclaimer": "L'operazione potrebbe richiedere del tempo a seconda del numero di avventure che hai visitato.",
|
||||
"visited_region_check": "Controllo della regione visitata",
|
||||
"visited_region_check_desc": "Selezionando questa opzione, il server controllerà tutte le avventure che hai visitato e contrassegnerà le regioni in cui si trovano come visitate nei viaggi per il mondo.",
|
||||
"add_new": "Aggiungi nuovo...",
|
||||
"checklist": "Lista di controllo",
|
||||
"checklists": "Liste di controllo",
|
||||
"collection_archived": "Questa raccolta è stata archiviata.",
|
||||
"collection_completed": "Hai completato questa raccolta!",
|
||||
"collection_stats": "Statistiche della raccolta",
|
||||
"days": "giorni",
|
||||
"itineary_by_date": "Itinerario per data",
|
||||
"keep_exploring": "Continua a esplorare!",
|
||||
"link_new": "Collegamento Nuovo...",
|
||||
"linked_adventures": "Avventure collegate",
|
||||
"links": "Collegamenti",
|
||||
"no_end_date": "Inserisci una data di fine",
|
||||
"note": "Nota",
|
||||
"notes": "Note",
|
||||
"nothing_planned": "Niente in programma per questa giornata. \nBuon viaggio!",
|
||||
"transportation": "Trasporti",
|
||||
"transportations": "Trasporti",
|
||||
"visit_link": "Visita il collegamento",
|
||||
"day": "Giorno",
|
||||
"add_a_tag": "Aggiungi un'etichetta",
|
||||
"tags": "Tag",
|
||||
"set_to_pin": "Imposta su Blocca"
|
||||
},
|
||||
"home": {
|
||||
"desc_1": "Scopri, pianifica ed esplora con facilità",
|
||||
"desc_2": "AdventureLog è progettato per semplificare il tuo viaggio, fornendoti gli strumenti e le risorse per pianificare, preparare le valigie e affrontare la tua prossima avventura indimenticabile.",
|
||||
"feature_1": "Diario di viaggio",
|
||||
"feature_1_desc": "Tieni traccia delle tue avventure con un diario di viaggio personalizzato e condividi le tue esperienze con amici e familiari.",
|
||||
"feature_2": "Pianificazione del viaggio",
|
||||
"feature_2_desc": "Crea facilmente itinerari personalizzati e ottieni un riepilogo giorno per giorno del tuo viaggio.",
|
||||
"feature_3": "Mappa di viaggio",
|
||||
"feature_3_desc": "Visualizza i tuoi viaggi in tutto il mondo con una mappa interattiva ed esplora nuove destinazioni.",
|
||||
"go_to": "Vai a AdventureLog",
|
||||
"hero_1": "Scopri le avventure più emozionanti del mondo",
|
||||
"hero_2": "Scopri e pianifica la tua prossima avventura con AdventureLog. \nEsplora destinazioni mozzafiato, crea itinerari personalizzati e rimani connesso mentre sei in movimento.",
|
||||
"key_features": "Caratteristiche principali"
|
||||
},
|
||||
"navbar": {
|
||||
"about": "Informazioni su AdventureLog",
|
||||
"adventures": "Avventure",
|
||||
"collections": "Collezioni",
|
||||
"discord": "Discordia",
|
||||
"documentation": "Documentazione",
|
||||
"greeting": "CIAO",
|
||||
"logout": "Esci",
|
||||
"map": "Mappa",
|
||||
"my_adventures": "Le mie avventure",
|
||||
"profile": "Profilo",
|
||||
"search": "Ricerca",
|
||||
"settings": "Impostazioni",
|
||||
"shared_with_me": "Condiviso con me",
|
||||
"theme_selection": "Selezione del tema",
|
||||
"themes": {
|
||||
"aestetic-dark": "Oscuro estetico",
|
||||
"aestetic-light": "Luce estetica",
|
||||
"aqua": "Acqua",
|
||||
"dark": "Buio",
|
||||
"forest": "Foresta",
|
||||
"light": "Leggero",
|
||||
"night": "Notte"
|
||||
},
|
||||
"users": "Utenti",
|
||||
"worldtravel": "Viaggio nel mondo",
|
||||
"my_tags": "I miei tag",
|
||||
"tag": "Etichetta",
|
||||
"language_selection": "Lingua"
|
||||
},
|
||||
"auth": {
|
||||
"confirm_password": "Conferma password",
|
||||
"email": "E-mail",
|
||||
"first_name": "Nome di battesimo",
|
||||
"forgot_password": "Ha dimenticato la password?",
|
||||
"last_name": "Cognome",
|
||||
"login": "Login",
|
||||
"login_error": "Impossibile accedere con le credenziali fornite.",
|
||||
"password": "Password",
|
||||
"registration_disabled": "La registrazione è attualmente disabilitata.",
|
||||
"signup": "Iscrizione",
|
||||
"username": "Nome utente",
|
||||
"profile_picture": "Immagine del profilo",
|
||||
"public_profile": "Profilo pubblico",
|
||||
"public_tooltip": "Con un profilo pubblico, gli utenti possono condividere raccolte con te e visualizzare il tuo profilo nella pagina degli utenti."
|
||||
},
|
||||
"users": {
|
||||
"no_users_found": "Nessun utente trovato con profili pubblici."
|
||||
},
|
||||
"worldtravel": {
|
||||
"all": "Tutto",
|
||||
"all_subregions": "Tutte le sottoregioni",
|
||||
"clear_search": "Cancella ricerca",
|
||||
"completely_visited": "Completamente visitato",
|
||||
"country_list": "Elenco dei paesi",
|
||||
"no_countries_found": "Nessun paese trovato",
|
||||
"not_visited": "Non visitato",
|
||||
"num_countries": "paesi trovati",
|
||||
"partially_visited": "Parzialmente visitato"
|
||||
},
|
||||
"settings": {
|
||||
"account_settings": "Impostazioni dell'account utente",
|
||||
"confirm_new_password": "Conferma nuova password",
|
||||
"current_email": "E-mail corrente",
|
||||
"email_change": "Cambia e-mail",
|
||||
"new_email": "Nuova e-mail",
|
||||
"new_password": "Nuova parola d'ordine",
|
||||
"no_email_set": "Nessuna e-mail impostata",
|
||||
"password_change": "Cambiare la password",
|
||||
"settings_page": "Pagina Impostazioni",
|
||||
"update": "Aggiornamento",
|
||||
"update_error": "Errore durante l'aggiornamento delle impostazioni",
|
||||
"update_success": "Impostazioni aggiornate con successo!",
|
||||
"change_password": "Cambiare la password",
|
||||
"invalid_token": "Il token non è valido o è scaduto",
|
||||
"login_redir": "Verrai quindi reindirizzato alla pagina di accesso.",
|
||||
"missing_email": "Inserisci un indirizzo email",
|
||||
"password_does_not_match": "Le password non corrispondono",
|
||||
"password_is_required": "È richiesta la password",
|
||||
"possible_reset": "Se l'indirizzo email che hai fornito è associato a un account, riceverai un'email con le istruzioni per reimpostare la password!",
|
||||
"reset_password": "Reimposta password",
|
||||
"submit": "Invia",
|
||||
"token_required": "Token e UID sono necessari per la reimpostazione della password.",
|
||||
"about_this_background": "A proposito di questo contesto",
|
||||
"join_discord": "Unisciti alla Discordia",
|
||||
"join_discord_desc": "per condividere le tue foto. \nPubblicateli in",
|
||||
"photo_by": "Foto di"
|
||||
},
|
||||
"checklist": {
|
||||
"add_item": "Aggiungi articolo",
|
||||
"checklist_delete_error": "Errore durante l'eliminazione della lista di controllo",
|
||||
"checklist_deleted": "Lista di controllo eliminata con successo!",
|
||||
"checklist_editor": "Redattore della lista di controllo",
|
||||
"checklist_public": "Questa lista di controllo è pubblica perché è in una raccolta pubblica.",
|
||||
"editing_checklist": "Lista di controllo per la modifica",
|
||||
"failed_to_save": "Impossibile salvare la lista di controllo",
|
||||
"item": "Articolo",
|
||||
"item_already_exists": "L'articolo esiste già",
|
||||
"item_cannot_be_empty": "L'articolo non può essere vuoto",
|
||||
"items": "Elementi",
|
||||
"save": "Salva",
|
||||
"new_item": "Nuovo articolo"
|
||||
},
|
||||
"collection": {
|
||||
"edit_collection": "Modifica raccolta",
|
||||
"error_creating_collection": "Errore durante la creazione della raccolta",
|
||||
"error_editing_collection": "Errore durante la modifica della raccolta",
|
||||
"new_collection": "Nuova collezione",
|
||||
"collection_created": "Collezione creata con successo!",
|
||||
"collection_edit_success": "Raccolta modificata con successo!",
|
||||
"create": "Creare"
|
||||
},
|
||||
"notes": {
|
||||
"add_a_link": "Aggiungi un collegamento",
|
||||
"content": "Contenuto",
|
||||
"editing_note": "Nota di modifica",
|
||||
"failed_to_save": "Impossibile salvare la nota",
|
||||
"note_delete_error": "Errore durante l'eliminazione della nota",
|
||||
"note_deleted": "Nota eliminata con successo!",
|
||||
"note_editor": "Redattore della nota",
|
||||
"note_public": "Questa nota è pubblica perché è in una collezione pubblica.",
|
||||
"open": "Aprire",
|
||||
"save": "Salva",
|
||||
"invalid_url": "URL non valido"
|
||||
},
|
||||
"transportation": {
|
||||
"date_and_time": "Data",
|
||||
"date_time": "Data di inizio",
|
||||
"edit": "Modificare",
|
||||
"edit_transportation": "Modifica Trasporti",
|
||||
"end_date_time": "Data di fine",
|
||||
"error_editing_transportation": "Errore durante la modifica del trasporto",
|
||||
"flight_number": "Numero del volo",
|
||||
"from_location": "Dalla posizione",
|
||||
"modes": {
|
||||
"bike": "Bicicletta",
|
||||
"boat": "Barca",
|
||||
"bus": "Autobus",
|
||||
"car": "Auto",
|
||||
"other": "Altro",
|
||||
"plane": "Aereo",
|
||||
"train": "Treno",
|
||||
"walking": "A piedi"
|
||||
},
|
||||
"new_transportation": "Nuovi trasporti",
|
||||
"provide_start_date": "Si prega di fornire una data di inizio",
|
||||
"start": "Inizio",
|
||||
"to_location": "Alla posizione",
|
||||
"transport_type": "Tipo di trasporto",
|
||||
"transportation_added": "Trasporto aggiunto con successo!",
|
||||
"transportation_delete_error": "Errore durante l'eliminazione del trasporto",
|
||||
"transportation_deleted": "Trasporto eliminato con successo!",
|
||||
"transportation_edit_success": "Trasporti modificati con successo!",
|
||||
"type": "Tipo"
|
||||
},
|
||||
"search": {
|
||||
"adventurelog_results": "Risultati di AdventureLog",
|
||||
"online_results": "Risultati in linea",
|
||||
"public_adventures": "Avventure pubbliche"
|
||||
},
|
||||
"map": {
|
||||
"add_adventure": "Aggiungi nuova avventura",
|
||||
"add_adventure_at_marker": "Aggiungi nuova avventura a Marker",
|
||||
"adventure_map": "Mappa dell'avventura",
|
||||
"clear_marker": "Cancella indicatore",
|
||||
"map_options": "Opzioni della mappa",
|
||||
"show_visited_regions": "Mostra regioni visitate",
|
||||
"view_details": "Visualizza dettagli"
|
||||
},
|
||||
"languages": {
|
||||
"de": "tedesco",
|
||||
"en": "Inglese",
|
||||
"es": "spagnolo",
|
||||
"fr": "francese",
|
||||
"it": "Italiano",
|
||||
"nl": "Olandese",
|
||||
"sv": "svedese",
|
||||
"zh": "cinese"
|
||||
},
|
||||
"share": {
|
||||
"no_users_shared": "Nessun utente condiviso con",
|
||||
"not_shared_with": "Non condiviso con",
|
||||
"share_desc": "Condividi questa raccolta con altri utenti.",
|
||||
"shared": "Condiviso",
|
||||
"shared_with": "Condiviso con",
|
||||
"unshared": "Non condiviso",
|
||||
"with": "con",
|
||||
"go_to_settings": "Vai alle impostazioni",
|
||||
"no_shared_found": "Nessuna raccolta trovata condivisa con te.",
|
||||
"set_public": "Per consentire agli utenti di condividere con te, è necessario che il tuo profilo sia impostato su pubblico."
|
||||
},
|
||||
"profile": {
|
||||
"member_since": "Membro da allora",
|
||||
"user_stats": "Statistiche utente",
|
||||
"visited_countries": "Paesi visitati",
|
||||
"visited_regions": "Regioni visitate"
|
||||
}
|
||||
}
|
403
frontend/src/locales/nl.json
Normal file
403
frontend/src/locales/nl.json
Normal file
|
@ -0,0 +1,403 @@
|
|||
{
|
||||
"about": {
|
||||
"about": "Over",
|
||||
"close": "Dichtbij",
|
||||
"license": "Gelicentieerd onder de GPL-3.0-licentie.",
|
||||
"message": "Gemaakt met ❤️ in de Verenigde Staten.",
|
||||
"nominatim_1": "Locatie zoeken en geocodering wordt verzorgd door",
|
||||
"nominatim_2": "Hun gegevens zijn in licentie gegeven onder de ODbL-licentie.",
|
||||
"oss_attributions": "Open source-attributies",
|
||||
"other_attributions": "Aanvullende toeschrijvingen zijn te vinden in het README-bestand.",
|
||||
"source_code": "Broncode"
|
||||
},
|
||||
"adventures": {
|
||||
"activities": {
|
||||
"activity": "Activiteit 🏄",
|
||||
"art_museums": "Kunst",
|
||||
"attraction": "Attractie 🎢",
|
||||
"culture": "Cultuur 🎭",
|
||||
"dining": "Dineren 🍽️",
|
||||
"event": "Evenement 🎉",
|
||||
"festivals": "Festivals 🎪",
|
||||
"fitness": "Fitness🏋️",
|
||||
"general": "Algemeen 🌍",
|
||||
"hiking": "Wandelen 🥾",
|
||||
"historical_sites": "Historische locaties 🏛️",
|
||||
"lodging": "Accommodatie 🛌",
|
||||
"music_concerts": "Muziek",
|
||||
"nightlife": "Nachtleven 🌃",
|
||||
"other": "Ander",
|
||||
"outdoor": "Buiten 🏞️",
|
||||
"shopping": "Winkelen 🛍️",
|
||||
"spiritual_journeys": "Spirituele reizen 🧘♀️",
|
||||
"transportation": "Vervoer 🚗",
|
||||
"volunteer_work": "Vrijwilligerswerk 🤝",
|
||||
"water_sports": "Watersport 🚤",
|
||||
"wildlife": "Dieren in het wild 🦒"
|
||||
},
|
||||
"add_to_collection": "Toevoegen aan collectie",
|
||||
"adventure": "Avontuur",
|
||||
"adventure_delete_confirm": "Weet je zeker dat je dit avontuur wilt verwijderen? \nDeze actie kan niet ongedaan worden gemaakt.",
|
||||
"adventure_details": "Avontuurdetails",
|
||||
"adventure_type": "Avontuurtype",
|
||||
"archive": "Archief",
|
||||
"archived": "Gearchiveerd",
|
||||
"archived_collection_message": "Collectie succesvol gearchiveerd!",
|
||||
"archived_collections": "Gearchiveerde collecties",
|
||||
"ascending": "Oplopend",
|
||||
"cancel": "Annuleren",
|
||||
"category_filter": "Categoriefilter",
|
||||
"clear": "Duidelijk",
|
||||
"close_filters": "Sluit Filters",
|
||||
"collection": "Verzameling",
|
||||
"collection_adventures": "Inclusief collectie-avonturen",
|
||||
"collection_link_error": "Fout bij het koppelen van avontuur aan collectie",
|
||||
"collection_link_success": "Avontuur succesvol gekoppeld aan collectie!",
|
||||
"collection_remove_error": "Fout bij verwijderen van avontuur uit verzameling",
|
||||
"collection_remove_success": "Avontuur is succesvol uit de collectie verwijderd!",
|
||||
"count_txt": "resultaten die overeenkomen met uw zoekopdracht",
|
||||
"create_new": "Maak nieuwe...",
|
||||
"date": "Datum",
|
||||
"delete": "Verwijderen",
|
||||
"delete_collection": "Verzameling verwijderen",
|
||||
"delete_collection_success": "Collectie succesvol verwijderd!",
|
||||
"delete_collection_warning": "Weet u zeker dat u deze verzameling wilt verwijderen? \nHiermee worden ook alle gekoppelde avonturen verwijderd. \nDeze actie kan niet ongedaan worden gemaakt.",
|
||||
"descending": "Aflopend",
|
||||
"edit_adventure": "Avontuur bewerken",
|
||||
"edit_collection": "Verzameling bewerken",
|
||||
"filter": "Filter",
|
||||
"homepage": "Startpagina",
|
||||
"latitude": "Breedte",
|
||||
"longitude": "Lengte",
|
||||
"my_collections": "Mijn collecties",
|
||||
"name": "Naam",
|
||||
"no_image_found": "Geen afbeelding gevonden",
|
||||
"not_found": "Avontuur niet gevonden",
|
||||
"not_found_desc": "Het avontuur waar je naar op zoek was, kon niet gevonden worden. \nProbeer een ander avontuur of kom later nog eens terug.",
|
||||
"open_details": "Details openen",
|
||||
"open_filters": "Filters openen",
|
||||
"order_by": "Bestel per",
|
||||
"order_direction": "Bestelrichting",
|
||||
"private": "Privé",
|
||||
"public": "Openbaar",
|
||||
"rating": "Beoordeling",
|
||||
"remove_from_collection": "Verwijderen uit collectie",
|
||||
"share": "Deel",
|
||||
"sort": "Soort",
|
||||
"sources": "Bronnen",
|
||||
"unarchive": "Uit het archief halen",
|
||||
"unarchived_collection_message": "Collectie is succesvol gedearchiveerd!",
|
||||
"updated": "Bijgewerkt",
|
||||
"visit": "Bezoek",
|
||||
"visits": "Bezoeken",
|
||||
"adventure_delete_success": "Avontuur succesvol verwijderd!",
|
||||
"dates": "Datums",
|
||||
"delete_adventure": "Avontuur verwijderen",
|
||||
"duration": "Duur",
|
||||
"image_removed_error": "Fout bij verwijderen van afbeelding",
|
||||
"image_removed_success": "Afbeelding succesvol verwijderd!",
|
||||
"image_upload_error": "Fout bij het uploaden van afbeelding",
|
||||
"image_upload_success": "Afbeelding succesvol geüpload!",
|
||||
"no_image_url": "Er is geen afbeelding gevonden op die URL.",
|
||||
"planned": "Gepland",
|
||||
"start_before_end_error": "De startdatum moet vóór de einddatum liggen",
|
||||
"visited": "Bezocht",
|
||||
"wiki_image_error": "Fout bij het ophalen van afbeelding van Wikipedia",
|
||||
"actions": "Acties",
|
||||
"activity": "Activiteit",
|
||||
"activity_types": "Activiteitstypen",
|
||||
"add": "Toevoegen",
|
||||
"add_an_activity": "Voeg een activiteit toe",
|
||||
"add_notes": "Voeg notities toe",
|
||||
"adventure_create_error": "Kan geen avontuur creëren",
|
||||
"adventure_created": "Avontuur gecreëerd",
|
||||
"adventure_update_error": "Kan avontuur niet updaten",
|
||||
"adventure_updated": "Avontuur bijgewerkt",
|
||||
"basic_information": "Basisinformatie",
|
||||
"category": "Categorie",
|
||||
"clear_map": "Duidelijke kaart",
|
||||
"copy_link": "Kopieer link",
|
||||
"date_constrain": "Beperk u tot ophaaldata",
|
||||
"description": "Beschrijving",
|
||||
"end_date": "Einddatum",
|
||||
"fetch_image": "Afbeelding ophalen",
|
||||
"generate_desc": "Beschrijving genereren",
|
||||
"image": "Afbeelding",
|
||||
"image_fetch_failed": "Kan afbeelding niet ophalen",
|
||||
"link": "Link",
|
||||
"location": "Locatie",
|
||||
"location_information": "Locatie-informatie",
|
||||
"my_images": "Mijn afbeeldingen",
|
||||
"my_visits": "Mijn bezoeken",
|
||||
"new_adventure": "Nieuw avontuur",
|
||||
"no_description_found": "Geen beschrijving gevonden",
|
||||
"no_images": "Geen afbeeldingen",
|
||||
"no_location": "Voer een locatie in",
|
||||
"no_results": "Geen resultaten gevonden",
|
||||
"no_start_date": "Voer een startdatum in",
|
||||
"public_adventure": "Openbaar avontuur",
|
||||
"remove": "Verwijderen",
|
||||
"save_next": "Redden",
|
||||
"search_for_location": "Zoek een locatie",
|
||||
"search_results": "Zoekresultaten",
|
||||
"see_adventures": "Zie Avonturen",
|
||||
"select_adventure_category": "Selecteer Avontuurcategorie",
|
||||
"share_adventure": "Deel dit avontuur!",
|
||||
"start_date": "Startdatum",
|
||||
"upload_image": "Afbeelding uploaden",
|
||||
"upload_images_here": "Upload hier afbeeldingen",
|
||||
"url": "URL",
|
||||
"warning": "Waarschuwing",
|
||||
"wiki_desc": "Haalt een fragment uit een Wikipedia-artikel dat overeenkomt met de naam van het avontuur.",
|
||||
"wikipedia": "Wikipedia",
|
||||
"adventure_not_found": "Er zijn geen avonturen om weer te geven. \nVoeg er een paar toe via de plusknop rechtsonder of probeer de filters te wijzigen!",
|
||||
"all": "Alle",
|
||||
"error_updating_regions": "Fout bij updaten van regio's",
|
||||
"mark_visited": "Mark bezocht",
|
||||
"my_adventures": "Mijn avonturen",
|
||||
"no_adventures_found": "Geen avonturen gevonden",
|
||||
"no_collections_found": "Er zijn geen collecties gevonden waar dit avontuur aan kan worden toegevoegd.",
|
||||
"no_linkable_adventures": "Er zijn geen avonturen gevonden die aan deze collectie kunnen worden gekoppeld.",
|
||||
"not_visited": "Niet bezocht",
|
||||
"regions_updated": "regio's bijgewerkt",
|
||||
"update_visited_regions": "Update bezochte regio's",
|
||||
"update_visited_regions_disclaimer": "Dit kan even duren, afhankelijk van het aantal avonturen dat je hebt bezocht.",
|
||||
"visited_region_check": "Regiocheck bezocht",
|
||||
"visited_region_check_desc": "Door dit te selecteren, controleert de server al uw bezochte avonturen en markeert de regio's waarin ze zich bevinden als bezocht in de wereldreizen.",
|
||||
"add_new": "Nieuw toevoegen...",
|
||||
"checklist": "Controlelijst",
|
||||
"checklists": "Controlelijsten",
|
||||
"collection_archived": "Deze collectie is gearchiveerd.",
|
||||
"collection_completed": "Je hebt deze verzameling voltooid!",
|
||||
"collection_stats": "Verzamelstatistieken",
|
||||
"days": "dagen",
|
||||
"itineary_by_date": "Reisplan op datum",
|
||||
"keep_exploring": "Blijf verkennen!",
|
||||
"link_new": "Nieuwe link...",
|
||||
"linked_adventures": "Gekoppelde avonturen",
|
||||
"links": "Koppelingen",
|
||||
"mark_region_as_visited": "Regio {regio}, {country} markeren als bezocht?",
|
||||
"no_end_date": "Voer een einddatum in",
|
||||
"note": "Opmerking",
|
||||
"notes": "Opmerkingen",
|
||||
"nothing_planned": "Niets gepland voor deze dag. \nGeniet van de reis!",
|
||||
"transportation": "Vervoer",
|
||||
"transportations": "Transporten",
|
||||
"visit_link": "Bezoek Link",
|
||||
"day": "Dag",
|
||||
"add_a_tag": "Voeg een label toe",
|
||||
"tags": "Labels",
|
||||
"set_to_pin": "Stel in op Vastzetten"
|
||||
},
|
||||
"home": {
|
||||
"desc_1": "Ontdek, plan en verken met gemak",
|
||||
"desc_2": "AdventureLog is ontworpen om uw reis te vereenvoudigen en u de tools en middelen te bieden om uw volgende onvergetelijke avontuur te plannen, in te pakken en te navigeren.",
|
||||
"feature_1": "Reislogboek",
|
||||
"feature_1_desc": "Houd uw avonturen bij met een persoonlijk reislogboek en deel uw ervaringen met vrienden en familie.",
|
||||
"feature_2": "Reisplanning",
|
||||
"feature_2_desc": "Maak eenvoudig aangepaste reisroutes en krijg een overzicht van uw reis van dag tot dag.",
|
||||
"feature_3": "Reiskaart",
|
||||
"feature_3_desc": "Bekijk uw reizen over de hele wereld met een interactieve kaart en ontdek nieuwe bestemmingen.",
|
||||
"go_to": "Ga naar AdventureLog",
|
||||
"hero_2": "Ontdek en plan je volgende avontuur met AdventureLog. \nOntdek adembenemende bestemmingen, maak aangepaste reisroutes en blijf onderweg verbonden.",
|
||||
"hero_1": "Ontdek 's werelds meest opwindende avonturen",
|
||||
"key_features": "Belangrijkste kenmerken"
|
||||
},
|
||||
"navbar": {
|
||||
"about": "Over AdventureLog",
|
||||
"adventures": "Avonturen",
|
||||
"collections": "Collecties",
|
||||
"discord": "Meningsverschil",
|
||||
"documentation": "Documentatie",
|
||||
"greeting": "Hoi",
|
||||
"logout": "Uitloggen",
|
||||
"map": "Kaart",
|
||||
"my_adventures": "Mijn avonturen",
|
||||
"profile": "Profiel",
|
||||
"search": "Zoekopdracht",
|
||||
"settings": "Instellingen",
|
||||
"shared_with_me": "Gedeeld met mij",
|
||||
"theme_selection": "Thema Selectie",
|
||||
"themes": {
|
||||
"aestetic-dark": "Esthetisch donker",
|
||||
"aestetic-light": "Esthetisch licht",
|
||||
"aqua": "Aqua",
|
||||
"dark": "Donker",
|
||||
"forest": "Woud",
|
||||
"light": "Licht",
|
||||
"night": "Nacht"
|
||||
},
|
||||
"users": "Gebruikers",
|
||||
"worldtravel": "Wereldreizen",
|
||||
"my_tags": "Mijn tags",
|
||||
"tag": "Label",
|
||||
"language_selection": "Taal"
|
||||
},
|
||||
"auth": {
|
||||
"confirm_password": "Bevestig wachtwoord",
|
||||
"email": "E-mail",
|
||||
"first_name": "Voornaam",
|
||||
"forgot_password": "Wachtwoord vergeten?",
|
||||
"last_name": "Achternaam",
|
||||
"login": "Login",
|
||||
"login_error": "Kan niet inloggen met de opgegeven inloggegevens.",
|
||||
"password": "Wachtwoord",
|
||||
"registration_disabled": "Registratie is momenteel uitgeschakeld.",
|
||||
"signup": "Aanmelden",
|
||||
"username": "Gebruikersnaam",
|
||||
"profile_picture": "Profielfoto",
|
||||
"public_profile": "Openbaar profiel",
|
||||
"public_tooltip": "Met een openbaar profiel kunnen gebruikers collecties met u delen en uw profiel bekijken op de gebruikerspagina."
|
||||
},
|
||||
"users": {
|
||||
"no_users_found": "Er zijn geen gebruikers gevonden met openbare profielen."
|
||||
},
|
||||
"worldtravel": {
|
||||
"all": "Alle",
|
||||
"all_subregions": "Alle subregio's",
|
||||
"clear_search": "Zoekopdracht wissen",
|
||||
"completely_visited": "Volledig bezocht",
|
||||
"country_list": "Landenlijst",
|
||||
"no_countries_found": "Geen landen gevonden",
|
||||
"not_visited": "Niet bezocht",
|
||||
"num_countries": "landen gevonden",
|
||||
"partially_visited": "Gedeeltelijk bezocht"
|
||||
},
|
||||
"settings": {
|
||||
"account_settings": "Gebruikersaccountinstellingen",
|
||||
"confirm_new_password": "Bevestig nieuw wachtwoord",
|
||||
"current_email": "Huidige e-mail",
|
||||
"email_change": "Wijzig e-mailadres",
|
||||
"new_email": "Nieuwe e-mail",
|
||||
"new_password": "Nieuw wachtwoord",
|
||||
"no_email_set": "Geen e-mailadres ingesteld",
|
||||
"password_change": "Wachtwoord wijzigen",
|
||||
"settings_page": "Instellingenpagina",
|
||||
"update": "Update",
|
||||
"update_error": "Fout bij updaten van instellingen",
|
||||
"update_success": "Instellingen succesvol bijgewerkt!",
|
||||
"change_password": "Wachtwoord wijzigen",
|
||||
"invalid_token": "Token is ongeldig of verlopen",
|
||||
"login_redir": "Vervolgens wordt u doorgestuurd naar de inlogpagina.",
|
||||
"missing_email": "Voer een e-mailadres in",
|
||||
"password_does_not_match": "Wachtwoorden komen niet overeen",
|
||||
"password_is_required": "Wachtwoord is vereist",
|
||||
"possible_reset": "Als het door u opgegeven e-mailadres aan een account is gekoppeld, ontvangt u een e-mail met instructies om uw wachtwoord opnieuw in te stellen!",
|
||||
"reset_password": "Wachtwoord opnieuw instellen",
|
||||
"submit": "Indienen",
|
||||
"token_required": "Token en UID zijn vereist voor het opnieuw instellen van het wachtwoord.",
|
||||
"about_this_background": "Over deze achtergrond",
|
||||
"join_discord": "Sluit je aan bij de onenigheid",
|
||||
"join_discord_desc": "om uw eigen foto's te delen. \nPlaats ze in de",
|
||||
"photo_by": "Foto door"
|
||||
},
|
||||
"checklist": {
|
||||
"add_item": "Artikel toevoegen",
|
||||
"checklist_delete_error": "Fout bij verwijderen van checklist",
|
||||
"checklist_deleted": "Controlelijst succesvol verwijderd!",
|
||||
"checklist_editor": "Controlelijst-editor",
|
||||
"checklist_public": "Deze checklist is openbaar omdat deze zich in een openbare collectie bevindt.",
|
||||
"editing_checklist": "Controlelijst bewerken",
|
||||
"failed_to_save": "Kan checklist niet opslaan",
|
||||
"item": "Item",
|
||||
"item_already_exists": "Artikel bestaat al",
|
||||
"item_cannot_be_empty": "Artikel mag niet leeg zijn",
|
||||
"items": "Artikelen",
|
||||
"new_item": "Nieuw artikel",
|
||||
"save": "Redden"
|
||||
},
|
||||
"collection": {
|
||||
"collection_created": "Collectie succesvol aangemaakt!",
|
||||
"collection_edit_success": "Collectie succesvol bewerkt!",
|
||||
"create": "Creëren",
|
||||
"edit_collection": "Verzameling bewerken",
|
||||
"error_creating_collection": "Fout bij maken collectie",
|
||||
"error_editing_collection": "Fout bij bewerken collectie",
|
||||
"new_collection": "Nieuwe collectie"
|
||||
},
|
||||
"notes": {
|
||||
"add_a_link": "Voeg een link toe",
|
||||
"content": "Inhoud",
|
||||
"editing_note": "Notitie bewerken",
|
||||
"failed_to_save": "Kan notitie niet opslaan",
|
||||
"note_delete_error": "Fout bij verwijderen van notitie",
|
||||
"note_deleted": "Opmerking succesvol verwijderd!",
|
||||
"note_editor": "Notitie-editor",
|
||||
"note_public": "Deze notitie is openbaar omdat deze zich in een openbare collectie bevindt.",
|
||||
"open": "Open",
|
||||
"save": "Redden",
|
||||
"invalid_url": "Ongeldige URL"
|
||||
},
|
||||
"transportation": {
|
||||
"date_and_time": "Datum",
|
||||
"date_time": "Startdatum",
|
||||
"edit": "Bewerking",
|
||||
"edit_transportation": "Transport bewerken",
|
||||
"end_date_time": "Einddatum",
|
||||
"error_editing_transportation": "Fout bij bewerken van transport",
|
||||
"flight_number": "Vluchtnummer",
|
||||
"from_location": "Van locatie",
|
||||
"modes": {
|
||||
"bike": "Fiets",
|
||||
"boat": "Boot",
|
||||
"train": "Trein",
|
||||
"bus": "Bus",
|
||||
"car": "Auto",
|
||||
"other": "Ander",
|
||||
"plane": "Vliegtuig",
|
||||
"walking": "Lopen"
|
||||
},
|
||||
"to_location": "Naar locatie",
|
||||
"transportation_edit_success": "Transport succesvol bewerkt!",
|
||||
"type": "Type",
|
||||
"new_transportation": "Nieuw transport",
|
||||
"provide_start_date": "Geef een startdatum op",
|
||||
"start": "Begin",
|
||||
"transport_type": "Transporttype",
|
||||
"transportation_added": "Transport succesvol toegevoegd!",
|
||||
"transportation_delete_error": "Fout bij verwijderen transport",
|
||||
"transportation_deleted": "Transport succesvol verwijderd!"
|
||||
},
|
||||
"search": {
|
||||
"adventurelog_results": "AdventureLog-resultaten",
|
||||
"online_results": "Online resultaten",
|
||||
"public_adventures": "Openbare avonturen"
|
||||
},
|
||||
"map": {
|
||||
"add_adventure": "Voeg nieuw avontuur toe",
|
||||
"add_adventure_at_marker": "Voeg een nieuw avontuur toe bij Marker",
|
||||
"adventure_map": "Avonturenkaart",
|
||||
"clear_marker": "Duidelijke markering",
|
||||
"map_options": "Kaartopties",
|
||||
"show_visited_regions": "Toon bezochte regio's",
|
||||
"view_details": "Details bekijken"
|
||||
},
|
||||
"languages": {
|
||||
"de": "Duits",
|
||||
"en": "Engels",
|
||||
"es": "Spaans",
|
||||
"fr": "Frans",
|
||||
"it": "Italiaans",
|
||||
"nl": "Nederlands",
|
||||
"sv": "Zweeds",
|
||||
"zh": "Chinese"
|
||||
},
|
||||
"share": {
|
||||
"no_users_shared": "Er zijn geen gebruikers gedeeld",
|
||||
"not_shared_with": "Niet gedeeld met",
|
||||
"share_desc": "Deel deze verzameling met andere gebruikers.",
|
||||
"shared": "Gedeeld",
|
||||
"shared_with": "Gedeeld met",
|
||||
"unshared": "Niet gedeeld",
|
||||
"with": "met",
|
||||
"go_to_settings": "Ga naar instellingen",
|
||||
"no_shared_found": "Er zijn geen collecties gevonden die met u zijn gedeeld.",
|
||||
"set_public": "Om ervoor te zorgen dat gebruikers met u kunnen delen, moet uw profiel op openbaar zijn ingesteld."
|
||||
},
|
||||
"profile": {
|
||||
"member_since": "Lid sinds",
|
||||
"user_stats": "Gebruikersstatistieken",
|
||||
"visited_countries": "Bezochte landen",
|
||||
"visited_regions": "Bezochte regio's"
|
||||
}
|
||||
}
|
403
frontend/src/locales/sv.json
Normal file
403
frontend/src/locales/sv.json
Normal file
|
@ -0,0 +1,403 @@
|
|||
{
|
||||
"about": {
|
||||
"about": "Om",
|
||||
"close": "Nära",
|
||||
"license": "Licensierad under GPL-3.0-licensen.",
|
||||
"message": "Tillverkad med ❤️ i USA.",
|
||||
"nominatim_1": "Platssökning och geokodning tillhandahålls av",
|
||||
"nominatim_2": "Deras data är licensierad under ODbL-licensen.",
|
||||
"oss_attributions": "Tillskrivningar med öppen källkod",
|
||||
"other_attributions": "Ytterligare attributioner finns i README-filen.",
|
||||
"source_code": "Källkod"
|
||||
},
|
||||
"adventures": {
|
||||
"activities": {
|
||||
"activity": "Aktivitet 🏄",
|
||||
"art_museums": "Konst",
|
||||
"attraction": "Attraktion 🎢",
|
||||
"culture": "Kultur 🎭",
|
||||
"dining": "Mat 🍽️",
|
||||
"event": "Event 🎉",
|
||||
"festivals": "Festivaler 🎪",
|
||||
"fitness": "Fitness 🏋️",
|
||||
"general": "Allmänt 🌍",
|
||||
"hiking": "Vandring 🥾",
|
||||
"historical_sites": "Historiska platser 🏛️",
|
||||
"lodging": "Logi 🛌",
|
||||
"music_concerts": "Musik",
|
||||
"nightlife": "Nattliv 🌃",
|
||||
"other": "Andra",
|
||||
"outdoor": "Utomhus 🏞️",
|
||||
"shopping": "Shopping 🛍️",
|
||||
"spiritual_journeys": "Andliga resor 🧘♀️",
|
||||
"transportation": "Transport 🚗",
|
||||
"volunteer_work": "Volontärarbete 🤝",
|
||||
"water_sports": "Vattensporter 🚤",
|
||||
"wildlife": "Vilda djur 🦒"
|
||||
},
|
||||
"add_to_collection": "Lägg till i samlingen",
|
||||
"adventure": "Äventyr",
|
||||
"adventure_delete_confirm": "Är du säker på att du vill ta bort det här äventyret? \nDenna åtgärd kan inte ångras.",
|
||||
"adventure_delete_success": "Äventyret har raderats!",
|
||||
"adventure_details": "Äventyrsdetaljer",
|
||||
"adventure_type": "Äventyrstyp",
|
||||
"archive": "Arkiv",
|
||||
"archived": "Arkiverad",
|
||||
"archived_collection_message": "Samlingen har arkiverats!",
|
||||
"archived_collections": "Arkiverade samlingar",
|
||||
"ascending": "Stigande",
|
||||
"cancel": "Avboka",
|
||||
"category_filter": "Kategorifilter",
|
||||
"clear": "Rensa",
|
||||
"close_filters": "Stäng filter",
|
||||
"collection": "Samling",
|
||||
"collection_adventures": "Inkludera samlingsäventyr",
|
||||
"collection_link_error": "Det gick inte att länka äventyr till samling",
|
||||
"collection_link_success": "Äventyr kopplat till samling framgångsrikt!",
|
||||
"collection_remove_error": "Det gick inte att ta bort äventyr från samlingen",
|
||||
"collection_remove_success": "Äventyret har tagits bort från samlingen!",
|
||||
"count_txt": "resultat som matchar din sökning",
|
||||
"create_new": "Skapa nytt...",
|
||||
"date": "Datum",
|
||||
"dates": "Datum",
|
||||
"delete": "Radera",
|
||||
"delete_adventure": "Ta bort äventyr",
|
||||
"delete_collection": "Ta bort samling",
|
||||
"delete_collection_success": "Samlingen har raderats!",
|
||||
"delete_collection_warning": "Är du säker på att du vill ta bort den här samlingen? \nDetta tar också bort alla länkade äventyr. \nDenna åtgärd kan inte ångras.",
|
||||
"descending": "Fallande",
|
||||
"duration": "Varaktighet",
|
||||
"edit_adventure": "Redigera äventyr",
|
||||
"edit_collection": "Redigera samling",
|
||||
"filter": "Filtrera",
|
||||
"homepage": "Hemsida",
|
||||
"latitude": "Latitud",
|
||||
"longitude": "Longitud",
|
||||
"my_collections": "Mina samlingar",
|
||||
"name": "Namn",
|
||||
"no_image_found": "Ingen bild hittades",
|
||||
"not_found": "Äventyret hittades inte",
|
||||
"not_found_desc": "Äventyret du letade efter kunde inte hittas. \nProva ett annat äventyr eller kom tillbaka senare.",
|
||||
"open_details": "Öppna Detaljer",
|
||||
"open_filters": "Öppna filter",
|
||||
"order_by": "Beställ efter",
|
||||
"order_direction": "Beställ riktning",
|
||||
"planned": "Planerad",
|
||||
"private": "Privat",
|
||||
"public": "Offentlig",
|
||||
"rating": "Gradering",
|
||||
"remove_from_collection": "Ta bort från samlingen",
|
||||
"share": "Dela",
|
||||
"sort": "Sortera",
|
||||
"sources": "Källor",
|
||||
"unarchive": "Avarkivera",
|
||||
"unarchived_collection_message": "Samlingen har tagits bort från arkivet!",
|
||||
"visit": "Besök",
|
||||
"visited": "Besökte",
|
||||
"visits": "Besök",
|
||||
"image_removed_error": "Det gick inte att ta bort bilden",
|
||||
"image_removed_success": "Bilden har tagits bort!",
|
||||
"image_upload_error": "Det gick inte att ladda upp bilden",
|
||||
"image_upload_success": "Bilden har laddats upp!",
|
||||
"no_image_url": "Ingen bild hittades på den webbadressen.",
|
||||
"start_before_end_error": "Startdatumet måste vara före slutdatumet",
|
||||
"updated": "Uppdaterad",
|
||||
"wiki_image_error": "Det gick inte att hämta bilden från Wikipedia",
|
||||
"actions": "Åtgärder",
|
||||
"activity": "Aktivitet",
|
||||
"activity_types": "Aktivitetstyper",
|
||||
"add": "Tillägga",
|
||||
"add_an_activity": "Lägg till en aktivitet",
|
||||
"add_notes": "Lägg till anteckningar",
|
||||
"adventure_create_error": "Det gick inte att skapa äventyr",
|
||||
"adventure_created": "Äventyr skapat",
|
||||
"adventure_update_error": "Det gick inte att uppdatera äventyret",
|
||||
"adventure_updated": "Äventyr uppdaterat",
|
||||
"basic_information": "Grundläggande information",
|
||||
"category": "Kategori",
|
||||
"clear_map": "Rensa karta",
|
||||
"copy_link": "Kopiera länk",
|
||||
"date_constrain": "Begränsa till insamlingsdatum",
|
||||
"description": "Beskrivning",
|
||||
"end_date": "Slutdatum",
|
||||
"fetch_image": "Hämta bild",
|
||||
"generate_desc": "Skapa beskrivning",
|
||||
"image": "Bild",
|
||||
"image_fetch_failed": "Det gick inte att hämta bilden",
|
||||
"link": "Länk",
|
||||
"location": "Plats",
|
||||
"location_information": "Platsinformation",
|
||||
"my_images": "Mina bilder",
|
||||
"my_visits": "Mina besök",
|
||||
"new_adventure": "Nytt äventyr",
|
||||
"no_description_found": "Ingen beskrivning hittades",
|
||||
"no_images": "Inga bilder",
|
||||
"no_location": "Vänligen ange en plats",
|
||||
"no_results": "Inga resultat hittades",
|
||||
"no_start_date": "Ange ett startdatum",
|
||||
"public_adventure": "Offentligt äventyr",
|
||||
"remove": "Ta bort",
|
||||
"save_next": "Spara",
|
||||
"search_for_location": "Sök efter en plats",
|
||||
"search_results": "Sökresultat",
|
||||
"see_adventures": "Se äventyr",
|
||||
"select_adventure_category": "Välj äventyrskategori",
|
||||
"share_adventure": "Dela detta äventyr!",
|
||||
"start_date": "Startdatum",
|
||||
"upload_image": "Ladda upp bild",
|
||||
"upload_images_here": "Ladda upp bilder här",
|
||||
"url": "URL",
|
||||
"warning": "Varning",
|
||||
"wiki_desc": "Hämtar utdrag från Wikipedia-artikeln som matchar äventyrets namn.",
|
||||
"adventure_not_found": "Det finns inga äventyr att visa upp. \nLägg till några med hjälp av plusknappen längst ner till höger eller prova att byta filter!",
|
||||
"all": "Alla",
|
||||
"error_updating_regions": "Fel vid uppdatering av regioner",
|
||||
"mark_region_as_visited": "Markera region {region}, {country} som besökt?",
|
||||
"mark_visited": "Mark besökte",
|
||||
"my_adventures": "Mina äventyr",
|
||||
"no_adventures_found": "Inga äventyr hittades",
|
||||
"no_collections_found": "Inga samlingar hittades att lägga till detta äventyr till.",
|
||||
"no_linkable_adventures": "Inga äventyr hittades som kan kopplas till denna samling.",
|
||||
"not_visited": "Ej besökt",
|
||||
"regions_updated": "regioner uppdaterade",
|
||||
"update_visited_regions": "Uppdatera besökta regioner",
|
||||
"update_visited_regions_disclaimer": "Detta kan ta ett tag beroende på antalet äventyr du har besökt.",
|
||||
"visited_region_check": "Besökte Region Check",
|
||||
"visited_region_check_desc": "Genom att välja detta kommer servern att kontrollera alla dina besökta äventyr och markera de regioner de befinner sig i som besökta i världsresor.",
|
||||
"wikipedia": "Wikipedia",
|
||||
"add_new": "Lägg till ny...",
|
||||
"checklist": "Checklista",
|
||||
"checklists": "Checklistor",
|
||||
"collection_archived": "Denna samling har arkiverats.",
|
||||
"collection_completed": "Du har slutfört den här samlingen!",
|
||||
"collection_stats": "Insamlingsstatistik",
|
||||
"days": "dagar",
|
||||
"itineary_by_date": "Resplan efter datum",
|
||||
"keep_exploring": "Fortsätt utforska!",
|
||||
"link_new": "Länk Ny...",
|
||||
"linked_adventures": "Länkade äventyr",
|
||||
"links": "Länkar",
|
||||
"no_end_date": "Ange ett slutdatum",
|
||||
"note": "Notera",
|
||||
"notes": "Anteckningar",
|
||||
"nothing_planned": "Inget planerat för denna dag. \nNjut av resan!",
|
||||
"transportation": "Transport",
|
||||
"transportations": "Transporter",
|
||||
"visit_link": "Besök länken",
|
||||
"day": "Dag",
|
||||
"add_a_tag": "Lägg till en tagg",
|
||||
"tags": "Taggar",
|
||||
"set_to_pin": "Ställ in på Pin"
|
||||
},
|
||||
"home": {
|
||||
"desc_1": "Upptäck, planera och utforska med lätthet",
|
||||
"desc_2": "AdventureLog är designad för att förenkla din resa och förse dig med verktyg och resurser för att planera, packa och navigera i ditt nästa oförglömliga äventyr.",
|
||||
"feature_1": "Reselogg",
|
||||
"feature_1_desc": "Håll koll på dina äventyr med en personlig reselogg och dela dina upplevelser med vänner och familj.",
|
||||
"feature_2": "Reseplanering",
|
||||
"feature_2_desc": "Skapa enkelt anpassade resplaner och få en uppdelning av din resa dag för dag.",
|
||||
"feature_3": "Resekarta",
|
||||
"feature_3_desc": "Se dina resor över hela världen med en interaktiv karta och utforska nya destinationer.",
|
||||
"go_to": "Gå till AdventureLog",
|
||||
"hero_1": "Upptäck världens mest spännande äventyr",
|
||||
"hero_2": "Upptäck och planera ditt nästa äventyr med AdventureLog. \nUtforska hisnande destinationer, skapa anpassade resplaner och håll kontakten när du är på språng.",
|
||||
"key_features": "Nyckelfunktioner"
|
||||
},
|
||||
"navbar": {
|
||||
"about": "Om AdventureLog",
|
||||
"adventures": "Äventyr",
|
||||
"collections": "Samlingar",
|
||||
"discord": "Disharmoni",
|
||||
"documentation": "Dokumentation",
|
||||
"greeting": "Hej",
|
||||
"logout": "Utloggning",
|
||||
"map": "Karta",
|
||||
"my_adventures": "Mina äventyr",
|
||||
"profile": "Profil",
|
||||
"search": "Söka",
|
||||
"settings": "Inställningar",
|
||||
"shared_with_me": "Delade med mig",
|
||||
"theme_selection": "Temaval",
|
||||
"themes": {
|
||||
"aestetic-dark": "Estetisk mörk",
|
||||
"aestetic-light": "Estetiskt ljus",
|
||||
"aqua": "Aqua",
|
||||
"dark": "Mörk",
|
||||
"forest": "Skog",
|
||||
"light": "Ljus",
|
||||
"night": "Natt"
|
||||
},
|
||||
"users": "Användare",
|
||||
"worldtravel": "Världsresor",
|
||||
"my_tags": "Mina taggar",
|
||||
"tag": "Märka",
|
||||
"language_selection": "Språk"
|
||||
},
|
||||
"worldtravel": {
|
||||
"all": "Alla",
|
||||
"all_subregions": "Alla underregioner",
|
||||
"clear_search": "Rensa sökning",
|
||||
"completely_visited": "Helt besökt",
|
||||
"country_list": "Lista över länder",
|
||||
"no_countries_found": "Inga länder hittades",
|
||||
"not_visited": "Ej besökt",
|
||||
"num_countries": "hittade länder",
|
||||
"partially_visited": "Delvis besökt"
|
||||
},
|
||||
"auth": {
|
||||
"confirm_password": "Bekräfta lösenord",
|
||||
"email": "E-post",
|
||||
"first_name": "Förnamn",
|
||||
"forgot_password": "Glömt lösenordet?",
|
||||
"last_name": "Efternamn",
|
||||
"login": "Inloggning",
|
||||
"login_error": "Det går inte att logga in med de angivna uppgifterna.",
|
||||
"password": "Lösenord",
|
||||
"registration_disabled": "Registreringen är för närvarande inaktiverad.",
|
||||
"signup": "Registrera dig",
|
||||
"username": "Användarnamn",
|
||||
"public_tooltip": "Med en offentlig profil kan användare dela samlingar med dig och se din profil på användarsidan.",
|
||||
"profile_picture": "Profilbild",
|
||||
"public_profile": "Offentlig profil"
|
||||
},
|
||||
"users": {
|
||||
"no_users_found": "Inga användare hittades med offentliga profiler."
|
||||
},
|
||||
"settings": {
|
||||
"account_settings": "Användarkontoinställningar",
|
||||
"confirm_new_password": "Bekräfta nytt lösenord",
|
||||
"current_email": "Aktuell e-post",
|
||||
"email_change": "Ändra e-post",
|
||||
"new_email": "Ny e-post",
|
||||
"new_password": "Nytt lösenord",
|
||||
"no_email_set": "Ingen e-post inställd",
|
||||
"password_change": "Ändra lösenord",
|
||||
"settings_page": "Inställningssida",
|
||||
"update": "Uppdatera",
|
||||
"update_error": "Fel vid uppdatering av inställningar",
|
||||
"update_success": "Inställningarna har uppdaterats!",
|
||||
"change_password": "Ändra lösenord",
|
||||
"invalid_token": "Token är ogiltig eller har gått ut",
|
||||
"login_redir": "Du kommer då att omdirigeras till inloggningssidan.",
|
||||
"missing_email": "Vänligen ange en e-postadress",
|
||||
"password_does_not_match": "Lösenord stämmer inte överens",
|
||||
"password_is_required": "Lösenord krävs",
|
||||
"possible_reset": "Om e-postadressen du angav är kopplad till ett konto kommer du att få ett e-postmeddelande med instruktioner för att återställa ditt lösenord!",
|
||||
"reset_password": "Återställ lösenord",
|
||||
"submit": "Överlämna",
|
||||
"token_required": "Token och UID krävs för lösenordsåterställning.",
|
||||
"about_this_background": "Om denna bakgrund",
|
||||
"join_discord": "Gå med i Discord",
|
||||
"join_discord_desc": "för att dela dina egna foton. \nLägg upp dem i",
|
||||
"photo_by": "Foto av"
|
||||
},
|
||||
"checklist": {
|
||||
"add_item": "Lägg till objekt",
|
||||
"checklist_delete_error": "Fel vid borttagning av checklista",
|
||||
"checklist_deleted": "Checklistan har raderats!",
|
||||
"checklist_editor": "Checklista Editor",
|
||||
"checklist_public": "Den här checklistan är offentlig eftersom den finns i en offentlig samling.",
|
||||
"editing_checklist": "Redigeringschecklista",
|
||||
"failed_to_save": "Det gick inte att spara checklistan",
|
||||
"item": "Punkt",
|
||||
"item_already_exists": "Objektet finns redan",
|
||||
"item_cannot_be_empty": "Objektet får inte vara tomt",
|
||||
"items": "Föremål",
|
||||
"new_item": "Nytt föremål",
|
||||
"save": "Spara"
|
||||
},
|
||||
"collection": {
|
||||
"collection_created": "Samlingen har skapats!",
|
||||
"collection_edit_success": "Samlingen har redigerats!",
|
||||
"create": "Skapa",
|
||||
"edit_collection": "Redigera samling",
|
||||
"error_creating_collection": "Det gick inte att skapa samlingen",
|
||||
"error_editing_collection": "Fel vid redigering av samling",
|
||||
"new_collection": "Ny samling"
|
||||
},
|
||||
"notes": {
|
||||
"add_a_link": "Lägg till en länk",
|
||||
"content": "Innehåll",
|
||||
"editing_note": "Redigeringsanteckning",
|
||||
"failed_to_save": "Det gick inte att spara anteckningen",
|
||||
"note_delete_error": "Det gick inte att ta bort anteckningen",
|
||||
"note_deleted": "Anteckningen har raderats!",
|
||||
"note_editor": "Note Editor",
|
||||
"note_public": "Den här anteckningen är offentlig eftersom den finns i en offentlig samling.",
|
||||
"open": "Öppna",
|
||||
"save": "Spara",
|
||||
"invalid_url": "Ogiltig URL"
|
||||
},
|
||||
"transportation": {
|
||||
"date_and_time": "Datum",
|
||||
"date_time": "Startdatum",
|
||||
"edit": "Redigera",
|
||||
"edit_transportation": "Redigera transport",
|
||||
"end_date_time": "Slutdatum",
|
||||
"error_editing_transportation": "Fel vid redigering av transport",
|
||||
"flight_number": "Flygnummer",
|
||||
"from_location": "Från plats",
|
||||
"modes": {
|
||||
"bike": "Cykel",
|
||||
"boat": "Båt",
|
||||
"bus": "Buss",
|
||||
"car": "Bil",
|
||||
"other": "Andra",
|
||||
"plane": "Plan",
|
||||
"train": "Tåg",
|
||||
"walking": "Gående"
|
||||
},
|
||||
"new_transportation": "Nya transporter",
|
||||
"provide_start_date": "Ange ett startdatum",
|
||||
"start": "Start",
|
||||
"to_location": "Till Plats",
|
||||
"transport_type": "Transporttyp",
|
||||
"transportation_added": "Transport har lagts till!",
|
||||
"transportation_delete_error": "Det gick inte att ta bort transport",
|
||||
"transportation_deleted": "Transporten har raderats!",
|
||||
"transportation_edit_success": "Transporten har redigerats!",
|
||||
"type": "Typ"
|
||||
},
|
||||
"search": {
|
||||
"adventurelog_results": "AdventureLog-resultat",
|
||||
"online_results": "Online resultat",
|
||||
"public_adventures": "Offentliga äventyr"
|
||||
},
|
||||
"map": {
|
||||
"add_adventure": "Lägg till nytt äventyr",
|
||||
"add_adventure_at_marker": "Lägg till nytt äventyr vid Marker",
|
||||
"adventure_map": "Äventyrskarta",
|
||||
"clear_marker": "Rensa markör",
|
||||
"map_options": "Kartalternativ",
|
||||
"show_visited_regions": "Visa besökta regioner",
|
||||
"view_details": "Visa detaljer"
|
||||
},
|
||||
"languages": {
|
||||
"de": "tyska",
|
||||
"en": "engelska",
|
||||
"es": "spanska",
|
||||
"fr": "franska",
|
||||
"it": "italienska",
|
||||
"nl": "holländska",
|
||||
"sv": "svenska",
|
||||
"zh": "kinesiska"
|
||||
},
|
||||
"share": {
|
||||
"no_users_shared": "Inga användare delas med",
|
||||
"not_shared_with": "Inte delad med",
|
||||
"share_desc": "Dela den här samlingen med andra användare.",
|
||||
"shared": "Delad",
|
||||
"shared_with": "Delas med",
|
||||
"unshared": "Odelat",
|
||||
"with": "med",
|
||||
"go_to_settings": "Gå till inställningar",
|
||||
"no_shared_found": "Inga samlingar hittades som delas med dig.",
|
||||
"set_public": "För att tillåta användare att dela med dig måste du ha din profil inställd på offentlig."
|
||||
},
|
||||
"profile": {
|
||||
"member_since": "Medlem sedan dess",
|
||||
"user_stats": "Användarstatistik",
|
||||
"visited_countries": "Besökta länder",
|
||||
"visited_regions": "Besökte regioner"
|
||||
}
|
||||
}
|
403
frontend/src/locales/zh.json
Normal file
403
frontend/src/locales/zh.json
Normal file
|
@ -0,0 +1,403 @@
|
|||
{
|
||||
"about": {
|
||||
"about": "关于",
|
||||
"close": "关闭",
|
||||
"license": "根据 GPL-3.0 许可证获得许可。",
|
||||
"message": "由 ❤️ 在美国制造。",
|
||||
"nominatim_1": "位置搜索和地理编码由以下提供:",
|
||||
"nominatim_2": "他们的数据已获得 ODbL 许可证的许可。",
|
||||
"oss_attributions": "开源属性",
|
||||
"other_attributions": "其他属性可以在自述文件中找到。",
|
||||
"source_code": "源代码"
|
||||
},
|
||||
"adventures": {
|
||||
"activities": {
|
||||
"activity": "活动🏄",
|
||||
"art_museums": "艺术",
|
||||
"attraction": "景点🎢",
|
||||
"culture": "文化🎭",
|
||||
"dining": "餐饮🍽️",
|
||||
"event": "活动🎉",
|
||||
"festivals": "节日🎪",
|
||||
"fitness": "健身🏋️",
|
||||
"general": "一般🌍",
|
||||
"hiking": "徒步旅行🥾",
|
||||
"historical_sites": "历史古迹🏛️",
|
||||
"lodging": "住宿🛌",
|
||||
"music_concerts": "音乐",
|
||||
"nightlife": "夜生活🌃",
|
||||
"other": "其他",
|
||||
"outdoor": "户外🏞️",
|
||||
"shopping": "购物🛍️",
|
||||
"spiritual_journeys": "心灵之旅🧘♀️",
|
||||
"transportation": "交通🚗",
|
||||
"volunteer_work": "志愿工作🤝",
|
||||
"water_sports": "水上运动🚤",
|
||||
"wildlife": "野生动物🦒"
|
||||
},
|
||||
"add_to_collection": "添加到收藏",
|
||||
"adventure": "冒险",
|
||||
"adventure_delete_confirm": "您确定要删除此冒险吗?\n此操作无法撤消。",
|
||||
"adventure_details": "冒险详情",
|
||||
"adventure_type": "冒险类型",
|
||||
"archive": "档案",
|
||||
"archived": "已存档",
|
||||
"archived_collection_message": "收藏存档成功!",
|
||||
"archived_collections": "存档收藏",
|
||||
"ascending": "升序",
|
||||
"cancel": "取消",
|
||||
"category_filter": "类别过滤器",
|
||||
"clear": "清除",
|
||||
"close_filters": "关闭过滤器",
|
||||
"collection": "收藏",
|
||||
"collection_adventures": "包括收集冒险",
|
||||
"collection_link_error": "将冒险与收藏链接时出错",
|
||||
"collection_link_success": "冒险与收藏成功关联!",
|
||||
"collection_remove_error": "从集合中删除冒险时出错",
|
||||
"collection_remove_success": "冒险已成功从收藏中删除!",
|
||||
"count_txt": "与您的搜索匹配的结果",
|
||||
"create_new": "创建新...",
|
||||
"date": "日期",
|
||||
"delete": "删除",
|
||||
"delete_collection": "删除集合",
|
||||
"delete_collection_success": "收藏删除成功!",
|
||||
"delete_collection_warning": "您确定要删除该收藏吗?\n这也将删除所有链接的冒险。\n此操作无法撤消。",
|
||||
"descending": "降序",
|
||||
"edit_adventure": "编辑冒险",
|
||||
"edit_collection": "编辑收藏",
|
||||
"filter": "筛选",
|
||||
"homepage": "主页",
|
||||
"latitude": "纬度",
|
||||
"longitude": "经度",
|
||||
"my_collections": "我的收藏",
|
||||
"name": "姓名",
|
||||
"no_image_found": "没有找到图片",
|
||||
"not_found": "冒险未找到",
|
||||
"not_found_desc": "无法找到您正在寻找的冒险。\n请尝试不同的冒险或稍后回来查看。",
|
||||
"open_details": "打开详情",
|
||||
"open_filters": "打开过滤器",
|
||||
"order_by": "订购方式",
|
||||
"order_direction": "订单方向",
|
||||
"private": "私人的",
|
||||
"public": "民众",
|
||||
"rating": "等级",
|
||||
"remove_from_collection": "从集合中删除",
|
||||
"share": "分享",
|
||||
"sort": "种类",
|
||||
"sources": "来源",
|
||||
"unarchive": "取消归档",
|
||||
"unarchived_collection_message": "收藏解压成功!",
|
||||
"updated": "已更新",
|
||||
"visit": "访问",
|
||||
"visits": "访问量",
|
||||
"adventure_delete_success": "冒险删除成功!",
|
||||
"dates": "枣子",
|
||||
"delete_adventure": "删除冒险",
|
||||
"duration": "期间",
|
||||
"image_removed_error": "删除图像时出错",
|
||||
"image_removed_success": "图片删除成功!",
|
||||
"image_upload_error": "上传图片时出错",
|
||||
"image_upload_success": "图片上传成功!",
|
||||
"no_image_url": "在该 URL 中找不到图像。",
|
||||
"planned": "计划",
|
||||
"start_before_end_error": "开始日期必须早于结束日期",
|
||||
"visited": "访问过",
|
||||
"wiki_image_error": "从维基百科获取图像时出错",
|
||||
"actions": "行动",
|
||||
"activity": "活动",
|
||||
"activity_types": "活动类型",
|
||||
"add": "添加",
|
||||
"add_an_activity": "添加活动",
|
||||
"add_notes": "添加注释",
|
||||
"adventure_create_error": "未能创造冒险",
|
||||
"adventure_created": "冒险已创造",
|
||||
"adventure_update_error": "冒险更新失败",
|
||||
"adventure_updated": "冒险已更新",
|
||||
"basic_information": "基本信息",
|
||||
"category": "类别",
|
||||
"clear_map": "清晰的地图",
|
||||
"copy_link": "复制链接",
|
||||
"date_constrain": "限制收集日期",
|
||||
"description": "描述",
|
||||
"end_date": "结束日期",
|
||||
"fetch_image": "获取图像",
|
||||
"generate_desc": "生成描述",
|
||||
"image": "图像",
|
||||
"image_fetch_failed": "获取图像失败",
|
||||
"link": "关联",
|
||||
"location": "地点",
|
||||
"location_information": "位置信息",
|
||||
"my_images": "我的图片",
|
||||
"my_visits": "我的访问",
|
||||
"new_adventure": "新冒险",
|
||||
"no_description_found": "没有找到描述",
|
||||
"no_images": "没有图片",
|
||||
"no_location": "请输入地点",
|
||||
"no_results": "没有找到结果",
|
||||
"no_start_date": "请输入开始日期",
|
||||
"public_adventure": "公共冒险",
|
||||
"remove": "消除",
|
||||
"save_next": "节省",
|
||||
"search_for_location": "搜索地点",
|
||||
"search_results": "搜索结果",
|
||||
"see_adventures": "查看冒险",
|
||||
"select_adventure_category": "选择冒险类别",
|
||||
"share_adventure": "分享这个冒险!",
|
||||
"start_date": "开始日期",
|
||||
"upload_image": "上传图片",
|
||||
"upload_images_here": "在这里上传图片",
|
||||
"url": "网址",
|
||||
"warning": "警告",
|
||||
"wiki_desc": "从维基百科文章中提取与冒险名称匹配的摘录。",
|
||||
"wikipedia": "维基百科",
|
||||
"adventure_not_found": "没有任何冒险可以展示。\n使用右下角的加号按钮添加一些或尝试更改过滤器!",
|
||||
"all": "全部",
|
||||
"error_updating_regions": "更新区域时出错",
|
||||
"mark_region_as_visited": "将地区 {region}、{country} 标记为已访问?",
|
||||
"mark_visited": "马克访问过",
|
||||
"my_adventures": "我的冒险",
|
||||
"no_adventures_found": "没有发现冒险",
|
||||
"no_collections_found": "未找到可添加此冒险的集合。",
|
||||
"no_linkable_adventures": "没有发现任何冒险可以链接到这个集合。",
|
||||
"not_visited": "未访问过",
|
||||
"regions_updated": "地区已更新",
|
||||
"update_visited_regions": "更新访问过的地区",
|
||||
"update_visited_regions_disclaimer": "这可能需要一段时间,具体取决于您访问过的冒险活动的数量。",
|
||||
"visited_region_check": "访问地区检查",
|
||||
"visited_region_check_desc": "通过选择此选项,服务器将检查您访问过的所有冒险活动,并将它们所在的区域标记为在世界旅行中访问过的区域。",
|
||||
"add_new": "添加新...",
|
||||
"checklist": "清单",
|
||||
"checklists": "清单",
|
||||
"collection_archived": "该藏品已存档。",
|
||||
"collection_completed": "您已经完成了这个合集!",
|
||||
"collection_stats": "集合统计",
|
||||
"days": "天",
|
||||
"itineary_by_date": "行程(按日期)",
|
||||
"keep_exploring": "继续探索!",
|
||||
"link_new": "链接新...",
|
||||
"linked_adventures": "关联的冒险",
|
||||
"links": "链接",
|
||||
"no_end_date": "请输入结束日期",
|
||||
"note": "笔记",
|
||||
"notes": "笔记",
|
||||
"nothing_planned": "这一天没有什么计划。\n祝旅途愉快!",
|
||||
"transportation": "运输",
|
||||
"transportations": "交通",
|
||||
"visit_link": "访问链接",
|
||||
"day": "天",
|
||||
"add_a_tag": "添加标签",
|
||||
"tags": "标签",
|
||||
"set_to_pin": "设置为固定"
|
||||
},
|
||||
"home": {
|
||||
"desc_1": "轻松发现、规划和探索",
|
||||
"desc_2": "AdventureLog 旨在简化您的旅程,为您提供工具和资源来计划、打包和导航您的下一次难忘的冒险。",
|
||||
"feature_1": "旅行日志",
|
||||
"feature_1_desc": "通过个性化的旅行日志记录您的冒险经历,并与朋友和家人分享您的经历。",
|
||||
"feature_2": "旅行计划",
|
||||
"feature_2_desc": "轻松创建自定义行程并获取行程的每日详细信息。",
|
||||
"feature_3": "旅游地图",
|
||||
"feature_3_desc": "使用交互式地图查看您在世界各地的旅行并探索新的目的地。",
|
||||
"go_to": "前往冒险日志",
|
||||
"hero_1": "探索世界上最惊险的冒险",
|
||||
"hero_2": "使用 AdventureLog 发现并计划您的下一次冒险。\n探索令人惊叹的目的地、创建定制行程并随时随地保持联系。",
|
||||
"key_features": "主要特点"
|
||||
},
|
||||
"navbar": {
|
||||
"about": "关于冒险日志",
|
||||
"adventures": "冒险",
|
||||
"collections": "收藏",
|
||||
"discord": "不和谐",
|
||||
"documentation": "文档",
|
||||
"greeting": "你好",
|
||||
"logout": "退出",
|
||||
"map": "地图",
|
||||
"my_adventures": "我的冒险",
|
||||
"profile": "轮廓",
|
||||
"search": "搜索",
|
||||
"settings": "设置",
|
||||
"shared_with_me": "与我分享",
|
||||
"theme_selection": "主题选择",
|
||||
"themes": {
|
||||
"aestetic-dark": "审美黑暗",
|
||||
"aestetic-light": "审美之光",
|
||||
"aqua": "阿夸",
|
||||
"dark": "黑暗的",
|
||||
"forest": "森林",
|
||||
"light": "光",
|
||||
"night": "夜晚"
|
||||
},
|
||||
"users": "用户",
|
||||
"worldtravel": "环球旅行",
|
||||
"my_tags": "我的标签",
|
||||
"tag": "标签",
|
||||
"language_selection": "语言"
|
||||
},
|
||||
"auth": {
|
||||
"forgot_password": "忘记密码?",
|
||||
"login": "登录",
|
||||
"login_error": "无法使用提供的凭据登录。",
|
||||
"password": "密码",
|
||||
"signup": "报名",
|
||||
"username": "用户名",
|
||||
"confirm_password": "确认密码",
|
||||
"email": "电子邮件",
|
||||
"first_name": "名",
|
||||
"last_name": "姓",
|
||||
"registration_disabled": "目前已禁用注册。",
|
||||
"profile_picture": "个人资料图片",
|
||||
"public_profile": "公开资料",
|
||||
"public_tooltip": "通过公开个人资料,用户可以与您共享收藏并在用户页面上查看您的个人资料。"
|
||||
},
|
||||
"worldtravel": {
|
||||
"all": "全部",
|
||||
"all_subregions": "所有次区域",
|
||||
"clear_search": "清除搜索",
|
||||
"completely_visited": "已完全访问",
|
||||
"country_list": "国家列表",
|
||||
"no_countries_found": "没有找到国家",
|
||||
"not_visited": "未访问过",
|
||||
"num_countries": "找到的国家",
|
||||
"partially_visited": "部分访问"
|
||||
},
|
||||
"users": {
|
||||
"no_users_found": "未找到具有公开个人资料的用户。"
|
||||
},
|
||||
"settings": {
|
||||
"account_settings": "用户帐户设置",
|
||||
"confirm_new_password": "确认新密码",
|
||||
"current_email": "当前电子邮件",
|
||||
"email_change": "更改电子邮件",
|
||||
"new_email": "新电子邮件",
|
||||
"new_password": "新密码",
|
||||
"no_email_set": "没有设置电子邮件",
|
||||
"password_change": "更改密码",
|
||||
"settings_page": "设置页面",
|
||||
"update": "更新",
|
||||
"update_error": "更新设置时出错",
|
||||
"update_success": "设置更新成功!",
|
||||
"change_password": "更改密码",
|
||||
"invalid_token": "令牌无效或已过期",
|
||||
"login_redir": "然后您将被重定向到登录页面。",
|
||||
"missing_email": "请输入电子邮件地址",
|
||||
"password_does_not_match": "密码不匹配",
|
||||
"password_is_required": "需要密码",
|
||||
"possible_reset": "如果您提供的电子邮件地址与帐户关联,您将收到一封电子邮件,其中包含重置密码的说明!",
|
||||
"reset_password": "重置密码",
|
||||
"submit": "提交",
|
||||
"token_required": "重置密码需要令牌和 UID。",
|
||||
"about_this_background": "关于这个背景",
|
||||
"join_discord": "加入不和谐",
|
||||
"join_discord_desc": "分享您自己的照片。\n将它们张贴在",
|
||||
"photo_by": "摄影:"
|
||||
},
|
||||
"checklist": {
|
||||
"add_item": "添加项目",
|
||||
"checklist_delete_error": "删除清单时出错",
|
||||
"checklist_deleted": "清单删除成功!",
|
||||
"checklist_editor": "清单编辑器",
|
||||
"checklist_public": "该清单是公开的,因为它属于公共收藏。",
|
||||
"editing_checklist": "编辑清单",
|
||||
"failed_to_save": "保存清单失败",
|
||||
"item": "物品",
|
||||
"item_already_exists": "项目已存在",
|
||||
"item_cannot_be_empty": "项目不能为空",
|
||||
"items": "项目",
|
||||
"new_item": "新商品",
|
||||
"save": "节省"
|
||||
},
|
||||
"collection": {
|
||||
"collection_created": "收藏创建成功!",
|
||||
"collection_edit_success": "合集编辑成功!",
|
||||
"create": "创造",
|
||||
"edit_collection": "编辑收藏",
|
||||
"error_creating_collection": "创建集合时出错",
|
||||
"error_editing_collection": "编辑集合时出错",
|
||||
"new_collection": "新系列"
|
||||
},
|
||||
"notes": {
|
||||
"add_a_link": "添加链接",
|
||||
"content": "内容",
|
||||
"editing_note": "编辑注释",
|
||||
"failed_to_save": "保存笔记失败",
|
||||
"note_delete_error": "删除笔记时出错",
|
||||
"note_deleted": "备注删除成功!",
|
||||
"note_editor": "笔记编辑器",
|
||||
"note_public": "该笔记是公开的,因为它属于公共收藏。",
|
||||
"open": "打开",
|
||||
"save": "节省",
|
||||
"invalid_url": "无效网址"
|
||||
},
|
||||
"transportation": {
|
||||
"date_and_time": "日期",
|
||||
"date_time": "开始日期",
|
||||
"edit": "编辑",
|
||||
"edit_transportation": "编辑交通",
|
||||
"end_date_time": "结束日期",
|
||||
"error_editing_transportation": "编辑交通时出错",
|
||||
"flight_number": "航班",
|
||||
"from_location": "出发地点",
|
||||
"modes": {
|
||||
"bike": "自行车",
|
||||
"boat": "船",
|
||||
"bus": "公共汽车",
|
||||
"car": "车",
|
||||
"other": "其他",
|
||||
"plane": "飞机",
|
||||
"train": "火车",
|
||||
"walking": "步行"
|
||||
},
|
||||
"new_transportation": "新交通",
|
||||
"provide_start_date": "请提供开始日期",
|
||||
"start": "开始",
|
||||
"to_location": "前往地点",
|
||||
"transport_type": "运输类型",
|
||||
"transportation_added": "交通添加成功!",
|
||||
"transportation_delete_error": "删除交通时出错",
|
||||
"transportation_deleted": "交通删除成功!",
|
||||
"transportation_edit_success": "交通编辑成功!",
|
||||
"type": "类型"
|
||||
},
|
||||
"search": {
|
||||
"adventurelog_results": "冒险日志结果",
|
||||
"online_results": "在线结果",
|
||||
"public_adventures": "公共冒险"
|
||||
},
|
||||
"map": {
|
||||
"add_adventure": "添加新冒险",
|
||||
"add_adventure_at_marker": "在标记处添加新冒险",
|
||||
"adventure_map": "冒险地图",
|
||||
"clear_marker": "清除标记",
|
||||
"map_options": "地图选项",
|
||||
"show_visited_regions": "显示访问过的地区",
|
||||
"view_details": "查看详情"
|
||||
},
|
||||
"languages": {
|
||||
"de": "德语",
|
||||
"en": "英语",
|
||||
"es": "西班牙语",
|
||||
"fr": "法语",
|
||||
"it": "意大利语",
|
||||
"nl": "荷兰语",
|
||||
"sv": "瑞典",
|
||||
"zh": "中国人"
|
||||
},
|
||||
"share": {
|
||||
"no_users_shared": "没有与之共享的用户",
|
||||
"not_shared_with": "不与共享",
|
||||
"share_desc": "与其他用户分享此收藏。",
|
||||
"shared": "共享",
|
||||
"shared_with": "共享对象",
|
||||
"unshared": "未共享",
|
||||
"with": "和",
|
||||
"go_to_settings": "前往设置",
|
||||
"no_shared_found": "未找到与您共享的集合。",
|
||||
"set_public": "为了允许用户与您共享,您需要将您的个人资料设置为公开。"
|
||||
},
|
||||
"profile": {
|
||||
"member_since": "会员自",
|
||||
"user_stats": "用户统计",
|
||||
"visited_countries": "访问过的国家",
|
||||
"visited_regions": "访问地区"
|
||||
}
|
||||
}
|
|
@ -1,12 +1,15 @@
|
|||
import { locale } from 'svelte-i18n';
|
||||
import type { LayoutServerLoad } from './$types';
|
||||
|
||||
export const load: LayoutServerLoad = async (event) => {
|
||||
if (event.locals.user) {
|
||||
return {
|
||||
user: event.locals.user
|
||||
user: event.locals.user,
|
||||
locale: event.locals.locale
|
||||
};
|
||||
}
|
||||
return {
|
||||
user: null
|
||||
user: null,
|
||||
locale: event.locals.locale
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,11 +1,56 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/environment';
|
||||
import { register, init, locale, waitLocale } from 'svelte-i18n';
|
||||
import { UmamiAnalyticsEnv } from '@lukulent/svelte-umami';
|
||||
export let data;
|
||||
|
||||
// Register your translations for each locale
|
||||
register('en', () => import('../locales/en.json'));
|
||||
register('es', () => import('../locales/es.json'));
|
||||
register('fr', () => import('../locales/fr.json'));
|
||||
register('de', () => import('../locales/de.json'));
|
||||
register('it', () => import('../locales/it.json'));
|
||||
register('zh', () => import('../locales/zh.json'));
|
||||
register('nl', () => import('../locales/nl.json'));
|
||||
register('sv', () => import('../locales/sv.json'));
|
||||
|
||||
if (browser) {
|
||||
init({
|
||||
fallbackLocale: navigator.language.split('-')[0],
|
||||
initialLocale: data.locale
|
||||
});
|
||||
// get the locale cookie if it exists and set it as the initial locale if it exists
|
||||
const localeCookie = document.cookie
|
||||
.split(';')
|
||||
.find((cookie) => cookie.trim().startsWith('locale='));
|
||||
if (localeCookie) {
|
||||
const localeValue = localeCookie.split('=')[1];
|
||||
locale.set(localeValue);
|
||||
}
|
||||
}
|
||||
|
||||
import Navbar from '$lib/components/Navbar.svelte';
|
||||
import Toast from '$lib/components/Toast.svelte';
|
||||
import 'tailwindcss/tailwind.css';
|
||||
export let data;
|
||||
|
||||
// Create a promise that resolves when the locale is ready
|
||||
export const localeLoaded = browser ? waitLocale() : Promise.resolve();
|
||||
</script>
|
||||
|
||||
<Navbar {data} />
|
||||
<Toast />
|
||||
{#await localeLoaded}
|
||||
<!-- You can add a loading indicator here if needed -->
|
||||
{:then}
|
||||
<Navbar {data} />
|
||||
<Toast />
|
||||
<slot />
|
||||
{/await}
|
||||
|
||||
<slot></slot>
|
||||
<UmamiAnalyticsEnv />
|
||||
|
||||
<svelte:head>
|
||||
<title>AdventureLog</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Embark, explore, remember with AdventureLog. AdventureLog is the ultimate travel companion."
|
||||
/>
|
||||
</svelte:head>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
|
||||
import { redirect, type Actions } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { getRandomBackground } from '$lib';
|
||||
|
||||
const serverEndpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
|
||||
|
||||
|
@ -49,5 +51,15 @@ export const actions: Actions = {
|
|||
} else {
|
||||
return redirect(302, '/');
|
||||
}
|
||||
},
|
||||
setLocale: async ({ url, cookies }) => {
|
||||
const locale = url.searchParams.get('locale');
|
||||
// change the theme only if it is one of the allowed themes
|
||||
if (locale && ['en', 'es'].includes(locale)) {
|
||||
cookies.set('locale', locale, {
|
||||
path: '/',
|
||||
maxAge: 60 * 60 * 24 * 365
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import AdventureOverlook from '$lib/assets/AdventureOverlook.webp';
|
||||
import MapWithPins from '$lib/assets/MapWithPins.webp';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data;
|
||||
</script>
|
||||
|
@ -18,35 +19,38 @@
|
|||
class="text-3xl font-bold tracking-tighter sm:text-5xl xl:text-6xl/none bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent pb-4"
|
||||
>
|
||||
{data.user.first_name.charAt(0).toUpperCase() + data.user.first_name.slice(1)},
|
||||
Discover the World's Most Thrilling Adventures
|
||||
{$t('home.hero_1')}
|
||||
</h1>
|
||||
{:else}
|
||||
<h1
|
||||
class="text-3xl font-bold tracking-tighter sm:text-5xl xl:text-6xl/none bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent pb-4"
|
||||
>
|
||||
Discover the World's Most Thrilling Adventures
|
||||
{$t('home.hero_1')}
|
||||
</h1>
|
||||
{/if}
|
||||
{:else}
|
||||
<h1
|
||||
class="text-3xl font-bold tracking-tighter sm:text-5xl xl:text-6xl/none bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent pb-4"
|
||||
>
|
||||
Discover the World's Most Thrilling Adventures
|
||||
{$t('home.hero_1')}
|
||||
</h1>
|
||||
{/if}
|
||||
<p class="max-w-[600px] text-gray-500 md:text-xl dark:text-gray-400">
|
||||
Discover and plan your next adventure with AdventureLog. Explore breathtaking
|
||||
destinations, create custom itineraries, and stay connected on the go.
|
||||
{$t('home.hero_2')}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 min-[400px]:flex-row">
|
||||
{#if data.user}
|
||||
<button on:click={() => goto('/adventures')} class="btn btn-primary">
|
||||
Go To AdventureLog
|
||||
{$t('home.go_to')}
|
||||
</button>
|
||||
{:else}
|
||||
<button on:click={() => goto('/login')} class="btn btn-primary"> Log In </button>
|
||||
<button on:click={() => goto('/signup')} class="btn btn-neutral"> Sign Up </button>
|
||||
<button on:click={() => goto('/login')} class="btn btn-primary">
|
||||
{$t('auth.login')}
|
||||
</button>
|
||||
<button on:click={() => goto('/signup')} class="btn btn-neutral">
|
||||
{$t('auth.signup')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -69,18 +73,17 @@
|
|||
<div
|
||||
class="inline-block rounded-lg bg-gray-100 px-3 py-1 text-md dark:bg-gray-800 dark:text-gray-400"
|
||||
>
|
||||
Key Features
|
||||
{$t('home.key_features')}
|
||||
</div>
|
||||
<h2
|
||||
class="text-3xl font-bold tracking-tighter sm:text-5xl bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent"
|
||||
>
|
||||
Discover, Plan, and Explore with Ease
|
||||
{$t('home.desc_1')}
|
||||
</h2>
|
||||
<p
|
||||
class="max-w-[900px] text-gray-500 md:text-xl/relaxed lg:text-base/relaxed xl:text-xl/relaxed dark:text-gray-400"
|
||||
>
|
||||
AdventureLog is designed to simplify your journey, providing you with the tools and
|
||||
resources to plan, pack, and navigate your next unforgettable adventure.
|
||||
{$t('home.desc_2')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -97,27 +100,25 @@
|
|||
<ul class="grid gap-6">
|
||||
<li>
|
||||
<div class="grid gap-1">
|
||||
<h3 class="text-xl font-bold dark:text-gray-400">Travel Log</h3>
|
||||
<h3 class="text-xl font-bold dark:text-gray-400">{$t('home.feature_1')}</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400">
|
||||
Keep track of your adventures with a personalized travel log and share your
|
||||
experiences with friends and family.
|
||||
{$t('home.feature_1_desc')}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="grid gap-1">
|
||||
<h3 class="text-xl font-bold dark:text-gray-400">Trip Planning</h3>
|
||||
<h3 class="text-xl font-bold dark:text-gray-400">{$t('home.feature_2')}</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400">
|
||||
Easily create custom itineraries and get a day-by-day breakdown of your trip.
|
||||
{$t('home.feature_2_desc')}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="grid gap-1">
|
||||
<h3 class="text-xl font-bold dark:text-gray-400">Travel Map</h3>
|
||||
<h3 class="text-xl font-bold dark:text-gray-400">{$t('home.feature_3')}</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400">
|
||||
View your travels throughout the world with an interactive map and explore new
|
||||
destinations.
|
||||
{$t('home.feature_3_desc')}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import type { PageData } from './$types';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
|
@ -11,8 +12,8 @@
|
|||
<table class="table table-compact">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Activity</th>
|
||||
<th>Actions</th>
|
||||
<th>{$t('navbar.tag')}</th>
|
||||
<th>{$t('adventures.actions')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -23,7 +24,7 @@
|
|||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
on:click={() => goto(`/search?query=${activity}&property=activity_types`)}
|
||||
>See Adventures</button
|
||||
>{$t('adventures.see_adventures')}</button
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -32,6 +33,6 @@
|
|||
</table>
|
||||
|
||||
<svelte:head>
|
||||
<title>My Activities</title>
|
||||
<meta name="description" content="View my activity types." />
|
||||
<title>My Tags</title>
|
||||
<meta name="description" content="View my tags." />
|
||||
</svelte:head>
|
||||
|
|
|
@ -27,9 +27,10 @@ export const load = (async (event) => {
|
|||
const order_by = event.url.searchParams.get('order_by') || 'updated_at';
|
||||
const order_direction = event.url.searchParams.get('order_direction') || 'asc';
|
||||
const page = event.url.searchParams.get('page') || '1';
|
||||
const is_visited = event.url.searchParams.get('is_visited') || 'all';
|
||||
|
||||
let initialFetch = await fetch(
|
||||
`${serverEndpoint}/api/adventures/filtered?types=${typeString}&order_by=${order_by}&order_direction=${order_direction}&include_collections=${include_collections}&page=${page}`,
|
||||
`${serverEndpoint}/api/adventures/filtered?types=${typeString}&order_by=${order_by}&order_direction=${order_direction}&include_collections=${include_collections}&page=${page}&is_visited=${is_visited}`,
|
||||
{
|
||||
headers: {
|
||||
Cookie: `${event.cookies.get('auth')}`
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import CategoryFilterDropdown from '$lib/components/CategoryFilterDropdown.svelte';
|
||||
import NotFound from '$lib/components/NotFound.svelte';
|
||||
import type { Adventure } from '$lib/types';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
import Plus from '~icons/mdi/plus';
|
||||
|
||||
|
@ -20,19 +21,21 @@
|
|||
order: '',
|
||||
visited: true,
|
||||
planned: true,
|
||||
includeCollections: true
|
||||
includeCollections: true,
|
||||
is_visited: 'all'
|
||||
};
|
||||
|
||||
let resultsPerPage: number = 25;
|
||||
|
||||
let count = data.props.count || 0;
|
||||
|
||||
let totalPages = Math.ceil(count / resultsPerPage);
|
||||
let currentPage: number = 1;
|
||||
|
||||
let typeString: string = '';
|
||||
|
||||
$: {
|
||||
if (typeof window !== 'undefined') {
|
||||
if (typeof window !== 'undefined' && typeString) {
|
||||
let url = new URL(window.location.href);
|
||||
url.searchParams.set('types', typeString);
|
||||
goto(url.toString(), { invalidateAll: true, replaceState: true });
|
||||
|
@ -112,6 +115,10 @@
|
|||
currentSort.visited = true;
|
||||
currentSort.planned = true;
|
||||
}
|
||||
|
||||
if (url.searchParams.get('is_visited')) {
|
||||
currentSort.is_visited = url.searchParams.get('is_visited') || 'all';
|
||||
}
|
||||
}
|
||||
|
||||
let adventureToEdit: Adventure | null = null;
|
||||
|
@ -167,7 +174,7 @@
|
|||
tabindex="0"
|
||||
class="dropdown-content z-[1] menu p-4 shadow bg-base-300 text-base-content rounded-box w-52 gap-4"
|
||||
>
|
||||
<p class="text-center font-bold text-lg">Create new...</p>
|
||||
<p class="text-center font-bold text-lg">{$t('adventures.create_new')}</p>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
on:click={() => {
|
||||
|
@ -175,7 +182,7 @@
|
|||
adventureToEdit = null;
|
||||
}}
|
||||
>
|
||||
Adventure</button
|
||||
{$t('adventures.adventure')}</button
|
||||
>
|
||||
|
||||
<!-- <button
|
||||
|
@ -191,8 +198,8 @@
|
|||
<input id="my-drawer" type="checkbox" class="drawer-toggle" bind:checked={sidebarOpen} />
|
||||
<div class="drawer-content">
|
||||
<!-- Page content -->
|
||||
<h1 class="text-center font-bold text-4xl mb-6">My Adventures</h1>
|
||||
<p class="text-center">This search returned {count} results.</p>
|
||||
<h1 class="text-center font-bold text-4xl mb-6">{$t('navbar.my_adventures')}</h1>
|
||||
<p class="text-center">{count} {$t('adventures.count_txt')}</p>
|
||||
{#if adventures.length === 0}
|
||||
<NotFound error={undefined} />
|
||||
{/if}
|
||||
|
@ -201,7 +208,7 @@
|
|||
class="btn btn-primary drawer-button lg:hidden mb-4 fixed bottom-0 left-0 ml-2 z-[999]"
|
||||
on:click={toggleSidebar}
|
||||
>
|
||||
{sidebarOpen ? 'Close Filters' : 'Open Filters'}
|
||||
{sidebarOpen ? $t(`adventures.close_filters`) : $t(`adventures.open_filters`)}
|
||||
</button>
|
||||
|
||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||
|
@ -243,8 +250,8 @@
|
|||
<form method="get">
|
||||
<CategoryFilterDropdown bind:types={typeString} />
|
||||
<div class="divider"></div>
|
||||
<h3 class="text-center font-bold text-lg mb-4">Sort</h3>
|
||||
<p class="text-lg font-semibold mb-2">Order Direction</p>
|
||||
<h3 class="text-center font-bold text-lg mb-4">{$t('adventures.sort')}</h3>
|
||||
<p class="text-lg font-semibold mb-2">{$t('adventures.order_direction')}</p>
|
||||
<div class="join">
|
||||
<input
|
||||
class="join-item btn btn-neutral"
|
||||
|
@ -252,7 +259,7 @@
|
|||
name="order_direction"
|
||||
id="asc"
|
||||
value="asc"
|
||||
aria-label="Ascending"
|
||||
aria-label={$t('adventures.ascending')}
|
||||
checked={currentSort.order === 'asc'}
|
||||
/>
|
||||
<input
|
||||
|
@ -261,55 +268,88 @@
|
|||
name="order_direction"
|
||||
id="desc"
|
||||
value="desc"
|
||||
aria-label="Descending"
|
||||
aria-label={$t('adventures.descending')}
|
||||
checked={currentSort.order === 'desc'}
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<p class="text-lg font-semibold mt-2 mb-2">Order By</p>
|
||||
<div class="flex join overflow-auto">
|
||||
<p class="text-lg font-semibold mt-2 mb-2">{$t('adventures.order_by')}</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<input
|
||||
class="join-item btn btn-neutral"
|
||||
class="btn btn-neutral text-wrap"
|
||||
type="radio"
|
||||
name="order_by"
|
||||
id="updated_at"
|
||||
value="updated_at"
|
||||
aria-label="Updated"
|
||||
aria-label={$t('adventures.updated')}
|
||||
checked={currentSort.order_by === 'updated_at'}
|
||||
/>
|
||||
<input
|
||||
class="join-item btn btn-neutral"
|
||||
class="btn btn-neutral text-wrap"
|
||||
type="radio"
|
||||
name="order_by"
|
||||
id="name"
|
||||
aria-label="Name"
|
||||
aria-label={$t('adventures.name')}
|
||||
value="name"
|
||||
checked={currentSort.order_by === 'name'}
|
||||
/>
|
||||
<input
|
||||
class="join-item btn btn-neutral"
|
||||
class="btn btn-neutral text-wrap"
|
||||
type="radio"
|
||||
value="date"
|
||||
name="order_by"
|
||||
id="date"
|
||||
aria-label="Date"
|
||||
aria-label={$t('adventures.date')}
|
||||
checked={currentSort.order_by === 'date'}
|
||||
/>
|
||||
<input
|
||||
class="join-item btn btn-neutral"
|
||||
class="btn btn-neutral text-wrap"
|
||||
type="radio"
|
||||
name="order_by"
|
||||
id="rating"
|
||||
aria-label="Rating"
|
||||
aria-label={$t('adventures.rating')}
|
||||
value="rating"
|
||||
checked={currentSort.order_by === 'rating'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- is visited true false or all -->
|
||||
<p class="text-lg font-semibold mt-2 mb-2">{$t('adventures.visited')}</p>
|
||||
<div class="join">
|
||||
<input
|
||||
class="join-item btn btn-neutral"
|
||||
type="radio"
|
||||
name="is_visited"
|
||||
id="all"
|
||||
value="all"
|
||||
aria-label={$t('adventures.all')}
|
||||
checked={currentSort.is_visited === 'all'}
|
||||
/>
|
||||
<input
|
||||
class="join-item btn btn-neutral"
|
||||
type="radio"
|
||||
name="is_visited"
|
||||
id="true"
|
||||
value="true"
|
||||
aria-label={$t('adventures.visited')}
|
||||
checked={currentSort.is_visited === 'true'}
|
||||
/>
|
||||
<input
|
||||
class="join-item btn btn-neutral"
|
||||
type="radio"
|
||||
name="is_visited"
|
||||
id="false"
|
||||
value="false"
|
||||
aria-label={$t('adventures.not_visited')}
|
||||
checked={currentSort.is_visited === 'false'}
|
||||
/>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="form-control">
|
||||
<br />
|
||||
<p class="text-lg font-semibold mt-2 mb-2">Sources</p>
|
||||
<p class="text-lg font-semibold mt-2 mb-2">{$t('adventures.sources')}</p>
|
||||
<label class="label cursor-pointer">
|
||||
<span class="label-text">Include Collection Adventures</span>
|
||||
<span class="label-text">{$t('adventures.collection_adventures')}</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="include_collections"
|
||||
|
@ -318,7 +358,8 @@
|
|||
checked={currentSort.includeCollections}
|
||||
/>
|
||||
</label>
|
||||
<button type="submit" class="btn btn-success mt-4">Filter</button>
|
||||
<button type="submit" class="btn btn-success mt-4">{$t('adventures.filter')}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ul>
|
||||
|
@ -326,6 +367,6 @@
|
|||
</div>
|
||||
|
||||
<svelte:head>
|
||||
<title>Adventures</title>
|
||||
<title>{$t('navbar.adventures')}</title>
|
||||
<meta name="description" content="View your completed and planned adventures." />
|
||||
</svelte:head>
|
||||
|
|
|
@ -1,18 +1,3 @@
|
|||
<!-- <script lang="ts">
|
||||
import AdventureCard from '$lib/components/AdventureCard.svelte';
|
||||
import type { Adventure } from '$lib/types';
|
||||
|
||||
export let data;
|
||||
console.log(data);
|
||||
let adventure: Adventure | null = data.props.adventure;
|
||||
</script>
|
||||
|
||||
{#if !adventure}
|
||||
<p>Adventure not found</p>
|
||||
{:else}
|
||||
<AdventureCard {adventure} type={adventure.type} />
|
||||
{/if} -->
|
||||
|
||||
<script lang="ts">
|
||||
import type { Adventure } from '$lib/types';
|
||||
import { onMount } from 'svelte';
|
||||
|
@ -20,6 +5,7 @@
|
|||
import { goto } from '$app/navigation';
|
||||
import Lost from '$lib/assets/undraw_lost.svg';
|
||||
import { DefaultMarker, MapLibre, Popup } from 'svelte-maplibre';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: PageData;
|
||||
console.log(data);
|
||||
|
@ -37,10 +23,8 @@
|
|||
let image_url: string | null = null;
|
||||
|
||||
import ClipboardList from '~icons/mdi/clipboard-list';
|
||||
import EditAdventure from '$lib/components/AdventureModal.svelte';
|
||||
import AdventureModal from '$lib/components/AdventureModal.svelte';
|
||||
import ImageDisplayModal from '$lib/components/ImageDisplayModal.svelte';
|
||||
import { typeToString } from '$lib';
|
||||
|
||||
onMount(() => {
|
||||
if (data.props.adventure) {
|
||||
|
@ -65,14 +49,15 @@
|
|||
<img src={Lost} alt="Lost" class="w-1/2" />
|
||||
</div>
|
||||
<h1 class="mt-4 text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
|
||||
Adventure not Found
|
||||
{$t('adventures.not_found')}
|
||||
</h1>
|
||||
<p class="mt-4 text-muted-foreground">
|
||||
The adventure you were looking for could not be found. Please try a different adventure or
|
||||
check back later.
|
||||
{$t('adventures.not_found_desc')}
|
||||
</p>
|
||||
<div class="mt-6">
|
||||
<button class="btn btn-primary" on:click={() => goto('/')}>Homepage</button>
|
||||
<button class="btn btn-primary" on:click={() => goto('/')}
|
||||
>{$t('adventures.homepage')}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -186,37 +171,6 @@
|
|||
>{adventure.is_public ? 'Public' : 'Private'}</span
|
||||
>
|
||||
</div>
|
||||
<!-- {#if adventure.date}
|
||||
<div class="flex items-center gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="w-5 h-5 text-muted-foreground"
|
||||
>
|
||||
<path d="M8 2v4"></path>
|
||||
<path d="M16 2v4"></path>
|
||||
<rect width="18" height="18" x="3" y="4" rx="2"></rect>
|
||||
<path d="M3 10h18"></path>
|
||||
</svg>
|
||||
<span class="text-sm text-muted-foreground"
|
||||
>{new Date(adventure.date).toLocaleDateString(undefined, {
|
||||
timeZone: 'UTC'
|
||||
})}{adventure.end_date && adventure.end_date !== ''
|
||||
? ' - ' +
|
||||
new Date(adventure.end_date).toLocaleDateString(undefined, {
|
||||
timeZone: 'UTC'
|
||||
})
|
||||
: ''}</span
|
||||
>
|
||||
</div>
|
||||
{/if} -->
|
||||
{#if adventure.location}
|
||||
<div class="flex items-center gap-2">
|
||||
<svg
|
||||
|
@ -291,7 +245,7 @@
|
|||
</div>
|
||||
{#if adventure.description}
|
||||
<div class="grid gap-2">
|
||||
<p class="text-sm text-muted-foreground whitespace-pre-line">
|
||||
<p class="text-sm text-muted-foreground" style="white-space: pre-wrap;">
|
||||
{adventure.description}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -305,18 +259,18 @@
|
|||
></div>
|
||||
<div class="grid gap-8">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold mt-4">Adventure Details</h2>
|
||||
<h2 class="text-2xl font-bold mt-4">{$t('adventures.adventure_details')}</h2>
|
||||
<div class="grid gap-4 mt-4">
|
||||
<div class="grid md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p class="text-sm text-muted-foreground">Adventure Type</p>
|
||||
<p class="text-sm text-muted-foreground">{$t('adventures.adventure_type')}</p>
|
||||
<p class="text-base font-medium">
|
||||
{typeToString(adventure.type)}
|
||||
{$t(`adventures.activities.${adventure.type}`)}
|
||||
</p>
|
||||
</div>
|
||||
{#if data.props.collection}
|
||||
<div>
|
||||
<p class="text-sm text-muted-foreground">Collection</p>
|
||||
<p class="text-sm text-muted-foreground">{$t('adventures.collection')}</p>
|
||||
<a
|
||||
class="text-base font-medium link"
|
||||
href="/collections/{data.props.collection.id}">{data.props.collection.name}</a
|
||||
|
@ -328,7 +282,9 @@
|
|||
<p class="text-sm text-muted-foreground">Visits</p>
|
||||
<p class="text-base font-medium">
|
||||
{adventure.visits.length}
|
||||
{adventure.visits.length > 1 ? 'visits' : 'visit' + ':'}
|
||||
{adventure.visits.length > 1
|
||||
? $t('adventures.visits')
|
||||
: $t('adventures.visit') + ':'}
|
||||
</p>
|
||||
<!-- show each visit start and end date as well as notes -->
|
||||
{#each adventure.visits as visit}
|
||||
|
@ -357,16 +313,16 @@
|
|||
{#if adventure.longitude && adventure.latitude}
|
||||
<div class="grid md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p class="text-sm text-muted-foreground">Latitude</p>
|
||||
<p class="text-sm text-muted-foreground">{$t('adventures.latitude')}</p>
|
||||
<p class="text-base font-medium">{adventure.latitude}° N</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-muted-foreground">Longitude</p>
|
||||
<p class="text-sm text-muted-foreground">{$t('adventures.longitude')}</p>
|
||||
<p class="text-base font-medium">{adventure.longitude}° W</p>
|
||||
</div>
|
||||
</div>
|
||||
<MapLibre
|
||||
style="https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
|
||||
style="https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
|
||||
class="flex items-center self-center justify-center aspect-[9/16] max-h-[70vh] sm:aspect-video sm:max-h-full w-10/12"
|
||||
standardControls
|
||||
center={{ lng: adventure.longitude, lat: adventure.latitude }}
|
||||
|
@ -383,13 +339,26 @@
|
|||
<p class="font-semibold text-black text-md">
|
||||
{adventure.type.charAt(0).toUpperCase() + adventure.type.slice(1)}
|
||||
</p>
|
||||
<p>
|
||||
<!-- {adventure.date
|
||||
? new Date(adventure.date).toLocaleDateString(undefined, {
|
||||
{#if adventure.visits.length > 0}
|
||||
<p class="text-black text-sm">
|
||||
{#each adventure.visits as visit}
|
||||
{visit.start_date
|
||||
? new Date(visit.start_date).toLocaleDateString(undefined, {
|
||||
timeZone: 'UTC'
|
||||
})
|
||||
: ''} -->
|
||||
: ''}
|
||||
{visit.end_date &&
|
||||
visit.end_date !== '' &&
|
||||
visit.end_date !== visit.start_date
|
||||
? ' - ' +
|
||||
new Date(visit.end_date).toLocaleDateString(undefined, {
|
||||
timeZone: 'UTC'
|
||||
})
|
||||
: ''}
|
||||
<br />
|
||||
{/each}
|
||||
</p>
|
||||
{/if}
|
||||
</Popup>
|
||||
</DefaultMarker>
|
||||
</MapLibre>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import NewCollection from '$lib/components/NewCollection.svelte';
|
||||
import NotFound from '$lib/components/NotFound.svelte';
|
||||
import type { Collection } from '$lib/types';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
import Plus from '~icons/mdi/plus';
|
||||
|
||||
|
@ -27,6 +28,12 @@
|
|||
let totalPages = Math.ceil(count / resultsPerPage);
|
||||
let currentPage: number = 1;
|
||||
|
||||
$: {
|
||||
if (count != collections.length) {
|
||||
count = collections.length;
|
||||
}
|
||||
}
|
||||
|
||||
function handleChangePage() {
|
||||
return async ({ result }: any) => {
|
||||
if (result.type === 'success') {
|
||||
|
@ -136,7 +143,7 @@
|
|||
tabindex="0"
|
||||
class="dropdown-content z-[1] menu p-4 shadow bg-base-300 text-base-content rounded-box w-52 gap-4"
|
||||
>
|
||||
<p class="text-center font-bold text-lg">Create new...</p>
|
||||
<p class="text-center font-bold text-lg">{$t(`adventures.create_new`)}</p>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
on:click={() => {
|
||||
|
@ -144,7 +151,7 @@
|
|||
newType = 'visited';
|
||||
}}
|
||||
>
|
||||
Collection</button
|
||||
{$t(`adventures.collection`)}</button
|
||||
>
|
||||
|
||||
<!-- <button
|
||||
|
@ -160,8 +167,8 @@
|
|||
<input id="my-drawer" type="checkbox" class="drawer-toggle" bind:checked={sidebarOpen} />
|
||||
<div class="drawer-content">
|
||||
<!-- Page content -->
|
||||
<h1 class="text-center font-bold text-4xl mb-6">My Collections</h1>
|
||||
<p class="text-center">This search returned {count} results.</p>
|
||||
<h1 class="text-center font-bold text-4xl mb-6">{$t(`adventures.my_collections`)}</h1>
|
||||
<p class="text-center">{count} {$t(`adventures.count_txt`)}</p>
|
||||
{#if collections.length === 0}
|
||||
<NotFound error={undefined} />
|
||||
{/if}
|
||||
|
@ -170,7 +177,7 @@
|
|||
class="btn btn-primary drawer-button lg:hidden mb-4 fixed bottom-0 left-0 ml-2 z-[999]"
|
||||
on:click={toggleSidebar}
|
||||
>
|
||||
{sidebarOpen ? 'Close Filters' : 'Open Filters'}
|
||||
{sidebarOpen ? $t(`adventures.close_filters`) : $t(`adventures.open_filters`)}
|
||||
</button>
|
||||
|
||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||
|
@ -211,8 +218,8 @@
|
|||
<!-- Sidebar content here -->
|
||||
<div class="form-control">
|
||||
<form action="?/get" method="post" use:enhance={handleSubmit}>
|
||||
<h3 class="text-center font-semibold text-lg mb-4">Sort</h3>
|
||||
<p class="text-lg font-semibold mb-2">Order Direction</p>
|
||||
<h3 class="text-center font-semibold text-lg mb-4">{$t(`adventures.sort`)}</h3>
|
||||
<p class="text-lg font-semibold mb-2">{$t(`adventures.order_direction`)}</p>
|
||||
<div class="join">
|
||||
<input
|
||||
class="join-item btn btn-neutral"
|
||||
|
@ -220,7 +227,7 @@
|
|||
name="order_direction"
|
||||
id="asc"
|
||||
value="asc"
|
||||
aria-label="Ascending"
|
||||
aria-label={$t(`adventures.ascending`)}
|
||||
checked
|
||||
/>
|
||||
<input
|
||||
|
@ -229,7 +236,7 @@
|
|||
name="order_direction"
|
||||
id="desc"
|
||||
value="desc"
|
||||
aria-label="Descending"
|
||||
aria-label={$t(`adventures.descending`)}
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
|
@ -243,13 +250,16 @@
|
|||
value="name"
|
||||
hidden
|
||||
/>
|
||||
<button type="submit" class="btn btn-success btn-primary mt-4">Sort</button>
|
||||
<button type="submit" class="btn btn-success btn-primary mt-4"
|
||||
>{$t(`adventures.sort`)}</button
|
||||
>
|
||||
</form>
|
||||
<div class="divider"></div>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-neutral btn-primary mt-4"
|
||||
on:click={() => goto('/collections/archived')}>Archived Collections</button
|
||||
on:click={() => goto('/collections/archived')}
|
||||
>{$t(`adventures.archived_collections`)}</button
|
||||
>
|
||||
</div>
|
||||
</ul>
|
||||
|
@ -257,6 +267,6 @@
|
|||
</div>
|
||||
|
||||
<svelte:head>
|
||||
<title>Collections</title>
|
||||
<title>{$t(`navbar.collections`)}</title>
|
||||
<meta name="description" content="View your adventure collections." />
|
||||
</svelte:head>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import type { PageData } from './$types';
|
||||
import { goto } from '$app/navigation';
|
||||
import Lost from '$lib/assets/undraw_lost.svg';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
import Plus from '~icons/mdi/plus';
|
||||
import AdventureCard from '$lib/components/AdventureCard.svelte';
|
||||
|
@ -20,8 +21,7 @@
|
|||
groupAdventuresByDate,
|
||||
groupNotesByDate,
|
||||
groupTransportationsByDate,
|
||||
groupChecklistsByDate,
|
||||
isAdventureVisited
|
||||
groupChecklistsByDate
|
||||
} from '$lib';
|
||||
import ChecklistCard from '$lib/components/ChecklistCard.svelte';
|
||||
import ChecklistModal from '$lib/components/ChecklistModal.svelte';
|
||||
|
@ -45,7 +45,7 @@
|
|||
|
||||
$: {
|
||||
numAdventures = adventures.length;
|
||||
numVisited = adventures.filter(isAdventureVisited).length;
|
||||
numVisited = adventures.filter((adventure) => adventure.is_visited).length;
|
||||
}
|
||||
|
||||
let notFound: boolean = false;
|
||||
|
@ -107,19 +107,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
function changeType(event: CustomEvent<string>) {
|
||||
adventures = adventures.map((adventure) => {
|
||||
if (adventure.id == event.detail) {
|
||||
if (adventure.type == 'visited') {
|
||||
adventure.type = 'planned';
|
||||
} else {
|
||||
adventure.type = 'visited';
|
||||
}
|
||||
}
|
||||
return adventure;
|
||||
});
|
||||
}
|
||||
|
||||
let adventureToEdit: Adventure | null = null;
|
||||
let transportationToEdit: Transportation;
|
||||
let isAdventureModalOpen: boolean = false;
|
||||
|
@ -256,14 +243,15 @@
|
|||
<img src={Lost} alt="Lost" class="w-1/2" />
|
||||
</div>
|
||||
<h1 class="mt-4 text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
|
||||
Adventure not Found
|
||||
{$t('adventures.not_found')}
|
||||
</h1>
|
||||
<p class="mt-4 text-muted-foreground">
|
||||
The adventure you were looking for could not be found. Please try a different adventure or
|
||||
check back later.
|
||||
{$t('adventures.not_found_desc')}
|
||||
</p>
|
||||
<div class="mt-6">
|
||||
<button class="btn btn-primary" on:click={() => goto('/')}>Homepage</button>
|
||||
<button class="btn btn-primary" on:click={() => goto('/')}
|
||||
>{$t('adventures.homepage')}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -288,17 +276,17 @@
|
|||
class="dropdown-content z-[1] menu p-4 shadow bg-base-300 text-base-content rounded-box w-52 gap-4"
|
||||
>
|
||||
{#if collection.user_id === data.user.pk}
|
||||
<p class="text-center font-bold text-lg">Link new...</p>
|
||||
<p class="text-center font-bold text-lg">{$t('adventures.link_new')}</p>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
on:click={() => {
|
||||
isShowingLinkModal = true;
|
||||
}}
|
||||
>
|
||||
Adventure</button
|
||||
{$t('adventures.adventure')}</button
|
||||
>
|
||||
{/if}
|
||||
<p class="text-center font-bold text-lg">Add new...</p>
|
||||
<p class="text-center font-bold text-lg">{$t('adventures.add_new')}</p>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
on:click={() => {
|
||||
|
@ -306,7 +294,7 @@
|
|||
adventureToEdit = null;
|
||||
}}
|
||||
>
|
||||
Adventure</button
|
||||
{$t('adventures.adventure')}</button
|
||||
>
|
||||
|
||||
<button
|
||||
|
@ -316,7 +304,7 @@
|
|||
newType = '';
|
||||
}}
|
||||
>
|
||||
Transportation</button
|
||||
{$t('adventures.transportation')}</button
|
||||
>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
|
@ -326,7 +314,7 @@
|
|||
noteToEdit = null;
|
||||
}}
|
||||
>
|
||||
Note</button
|
||||
{$t('adventures.note')}</button
|
||||
>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
|
@ -336,7 +324,7 @@
|
|||
checklistToEdit = null;
|
||||
}}
|
||||
>
|
||||
Checklist</button
|
||||
{$t('adventures.checklist')}</button
|
||||
>
|
||||
|
||||
<!-- <button
|
||||
|
@ -364,7 +352,7 @@
|
|||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
</svg>
|
||||
<span>This collection has been archived.</span>
|
||||
<span>{$t('adventures.collection_archived')}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -374,7 +362,7 @@
|
|||
{#if collection.link}
|
||||
<div class="flex items-center justify-center mb-2">
|
||||
<a href={collection.link} target="_blank" rel="noopener noreferrer" class="btn btn-primary">
|
||||
Visit Link
|
||||
{$t('adventures.visit_link')}
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -386,12 +374,12 @@
|
|||
<div class="flex items-center justify-center mb-4">
|
||||
<div class="stats shadow bg-base-300">
|
||||
<div class="stat">
|
||||
<div class="stat-title">Collection Stats</div>
|
||||
<div class="stat-title">{$t('adventures.collection_stats')}</div>
|
||||
<div class="stat-value">{numVisited}/{numAdventures} Visited</div>
|
||||
{#if numAdventures === numVisited}
|
||||
<div class="stat-desc">You've completed this collection! 🎉!</div>
|
||||
<div class="stat-desc">{$t('adventures.collection_completed')}</div>
|
||||
{:else}
|
||||
<div class="stat-desc">Keep exploring!</div>
|
||||
<div class="stat-desc">{$t('adventures.keep_exploring')}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -402,7 +390,7 @@
|
|||
<NotFound error={undefined} />
|
||||
{/if}
|
||||
{#if adventures.length > 0}
|
||||
<h1 class="text-center font-bold text-4xl mt-4 mb-2">Linked Adventures</h1>
|
||||
<h1 class="text-center font-bold text-4xl mt-4 mb-2">{$t('adventures.linked_adventures')}</h1>
|
||||
|
||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||
{#each adventures as adventure}
|
||||
|
@ -412,7 +400,6 @@
|
|||
on:delete={deleteAdventure}
|
||||
type={adventure.type}
|
||||
{adventure}
|
||||
on:typeChange={changeType}
|
||||
{collection}
|
||||
/>
|
||||
{/each}
|
||||
|
@ -420,7 +407,7 @@
|
|||
{/if}
|
||||
|
||||
{#if transportations.length > 0}
|
||||
<h1 class="text-center font-bold text-4xl mt-4 mb-4">Transportation</h1>
|
||||
<h1 class="text-center font-bold text-4xl mt-4 mb-4">{$t('adventures.transportations')}</h1>
|
||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||
{#each transportations as transportation}
|
||||
<TransportationCard
|
||||
|
@ -440,7 +427,7 @@
|
|||
{/if}
|
||||
|
||||
{#if notes.length > 0}
|
||||
<h1 class="text-center font-bold text-4xl mt-4 mb-4">Notes</h1>
|
||||
<h1 class="text-center font-bold text-4xl mt-4 mb-4">{$t('adventures.notes')}</h1>
|
||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||
{#each notes as note}
|
||||
<NoteCard
|
||||
|
@ -460,7 +447,7 @@
|
|||
{/if}
|
||||
|
||||
{#if checklists.length > 0}
|
||||
<h1 class="text-center font-bold text-4xl mt-4 mb-4">Checklists</h1>
|
||||
<h1 class="text-center font-bold text-4xl mt-4 mb-4">{$t('adventures.checklists')}</h1>
|
||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||
{#each checklists as checklist}
|
||||
<ChecklistCard
|
||||
|
@ -481,9 +468,12 @@
|
|||
|
||||
{#if collection.start_date && collection.end_date}
|
||||
<div class="divider"></div>
|
||||
<h1 class="text-center font-bold text-4xl mt-4">Itinerary by Date</h1>
|
||||
<h1 class="text-center font-bold text-4xl mt-4">{$t('adventures.itineary_by_date')}</h1>
|
||||
{#if numberOfDays}
|
||||
<p class="text-center text-lg pl-16 pr-16">Duration: {numberOfDays} days</p>
|
||||
<p class="text-center text-lg pl-16 pr-16">
|
||||
{$t('adventures.duration')}: {numberOfDays}
|
||||
{$t('adventures.days')}
|
||||
</p>
|
||||
{/if}
|
||||
<p class="text-center text-lg pl-16 pr-16">
|
||||
Dates: {new Date(collection.start_date).toLocaleDateString(undefined, { timeZone: 'UTC' })} - {new Date(
|
||||
|
@ -517,9 +507,13 @@
|
|||
dateString
|
||||
] || []}
|
||||
|
||||
<h2 class="text-center font-semibold text-2xl mb-2 mt-4">
|
||||
Day {i + 1} - {adjustedDate.toLocaleDateString(undefined, { timeZone: 'UTC' })}
|
||||
<h2 class="text-center font-bold text-3xl mt-4">
|
||||
{$t('adventures.day')}
|
||||
{i + 1}
|
||||
</h2>
|
||||
<h3 class="text-center text-xl mb-2">
|
||||
{adjustedDate.toLocaleDateString(undefined, { timeZone: 'UTC' })}
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
|
||||
{#if dayAdventures.length > 0}
|
||||
{#each dayAdventures as adventure}
|
||||
|
@ -529,7 +523,6 @@
|
|||
on:delete={deleteAdventure}
|
||||
type={adventure.type}
|
||||
{adventure}
|
||||
on:typeChange={changeType}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
|
@ -580,13 +573,13 @@
|
|||
{/if}
|
||||
|
||||
{#if dayAdventures.length == 0 && dayTransportations.length == 0 && dayNotes.length == 0 && dayChecklists.length == 0}
|
||||
<p class="text-center text-lg mt-2">Nothing planned for this day. Enjoy the journey!</p>
|
||||
<p class="text-center text-lg mt-2">{$t('adventures.nothing_planned')}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<MapLibre
|
||||
style="https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
|
||||
style="https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
|
||||
class="flex items-center self-center justify-center aspect-[9/16] max-h-[70vh] sm:aspect-video sm:max-h-full w-10/12 mt-4"
|
||||
standardControls
|
||||
>
|
||||
|
@ -603,11 +596,6 @@
|
|||
<p class="font-semibold text-black text-md">
|
||||
{adventure.type.charAt(0).toUpperCase() + adventure.type.slice(1)}
|
||||
</p>
|
||||
<!-- <p>
|
||||
{adventure.
|
||||
? new Date(adventure.date).toLocaleDateString(undefined, { timeZone: 'UTC' })
|
||||
: ''}
|
||||
</p> -->
|
||||
</Popup>
|
||||
</DefaultMarker>
|
||||
{/if}
|
||||
|
@ -620,7 +608,7 @@
|
|||
<title
|
||||
>{data.props.adventure && data.props.adventure.name
|
||||
? `${data.props.adventure.name}`
|
||||
: 'Collection'}</title
|
||||
: $t('adventures.collection')}</title
|
||||
>
|
||||
<meta
|
||||
name="description"
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import CollectionCard from '$lib/components/CollectionCard.svelte';
|
||||
import NotFound from '$lib/components/NotFound.svelte';
|
||||
import type { Collection } from '$lib/types';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let data: any;
|
||||
console.log(data);
|
||||
|
@ -16,7 +17,7 @@
|
|||
<div class="drawer lg:drawer-open">
|
||||
<div class="drawer-content">
|
||||
<!-- Page content -->
|
||||
<h1 class="text-center font-bold text-4xl mb-6">Archived Collections</h1>
|
||||
<h1 class="text-center font-bold text-4xl mb-6">{$t('adventures.archived_collections')}</h1>
|
||||
{#if collections.length === 0}
|
||||
<NotFound error={undefined} />
|
||||
{/if}
|
||||
|
|
|
@ -1,11 +1,22 @@
|
|||
import { fail, redirect } from '@sveltejs/kit';
|
||||
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
import { getRandomBackground, getRandomQuote } from '$lib';
|
||||
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
if (event.locals.user) {
|
||||
return redirect(302, '/');
|
||||
} else {
|
||||
const quote = getRandomQuote();
|
||||
const background = getRandomBackground();
|
||||
|
||||
return {
|
||||
props: {
|
||||
quote,
|
||||
background
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,77 +1,93 @@
|
|||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { goto } from '$app/navigation';
|
||||
import { getRandomQuote } from '$lib';
|
||||
import { redirect, type SubmitFunction } from '@sveltejs/kit';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let data;
|
||||
console.log(data);
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
import FileImageBox from '~icons/mdi/file-image-box';
|
||||
|
||||
let isImageInfoModalOpen: boolean = false;
|
||||
|
||||
import { page } from '$app/stores';
|
||||
|
||||
let quote: string = '';
|
||||
let backgroundImageUrl =
|
||||
'https://images.unsplash.com/photo-1465056836041-7f43ac27dcb5?q=80&w=2942&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D';
|
||||
import ImageInfoModal from '$lib/components/ImageInfoModal.svelte';
|
||||
import type { Background } from '$lib/types.js';
|
||||
|
||||
onMount(async () => {
|
||||
quote = getRandomQuote();
|
||||
});
|
||||
let quote: { quote: string; author: string } = data.props.quote;
|
||||
|
||||
let background: Background = data.props.background;
|
||||
</script>
|
||||
|
||||
{#if isImageInfoModalOpen}
|
||||
<ImageInfoModal {background} on:close={() => (isImageInfoModalOpen = false)} />
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="min-h-screen bg-no-repeat bg-cover flex items-center justify-center"
|
||||
style="background-image: url('{backgroundImageUrl}')"
|
||||
style="background-image: url('{background.url}')"
|
||||
>
|
||||
<div class="card card-compact w-96 bg-base-100 shadow-xl p-6">
|
||||
<article class="text-center text-4xl font-extrabold">
|
||||
<h1>Sign in</h1>
|
||||
<div
|
||||
class="card card-compact m-12 w-full max-w-4xl bg-base-100 shadow-xl p-6 flex flex-col md:flex-row"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<h3 class="text-center">AdventureLog</h3>
|
||||
<article class="text-center text-4xl mb-4 font-extrabold">
|
||||
<h1>{$t('auth.login')}</h1>
|
||||
</article>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<form method="post" use:enhance class="w-full max-w-xs">
|
||||
<label for="username">Username</label>
|
||||
<label for="username">{$t('auth.username')}</label>
|
||||
<input
|
||||
name="username"
|
||||
id="username"
|
||||
class="block mb-2 input input-bordered w-full max-w-xs"
|
||||
class="block input input-bordered w-full max-w-xs"
|
||||
/><br />
|
||||
<label for="password">Password</label>
|
||||
<label for="password">{$t('auth.password')}</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
id="password"
|
||||
class="block mb-2 input input-bordered w-full max-w-xs"
|
||||
class="block input input-bordered w-full max-w-xs"
|
||||
/><br />
|
||||
<button class="py-2 px-4 btn btn-primary mr-2">Login</button>
|
||||
<button
|
||||
class="py-2 px-4 btn btn-neutral"
|
||||
type="button"
|
||||
on:click={() => goto('/settings/forgot-password')}>Forgot Password</button
|
||||
>
|
||||
<button class="py-2 px-4 btn btn-neutral" type="button" on:click={() => goto('/signup')}
|
||||
>Sign Up</button
|
||||
>
|
||||
<button class="py-2 px-4 btn btn-primary mr-2">{$t('auth.login')}</button>
|
||||
|
||||
<div class="flex justify-between mt-4">
|
||||
<p><a href="/signup" class="underline">{$t('auth.signup')}</a></p>
|
||||
<p>
|
||||
<a href="/settings/forgot-password" class="underline">{$t('auth.forgot_password')}</a>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{#if ($page.form?.message && $page.form?.message.length > 1) || $page.form?.type === 'error'}
|
||||
<div class="text-center text-error mt-4">
|
||||
{$page.form.message || 'Unable to login with the provided credentials.'}
|
||||
{$page.form.message || $t('auth.login_error')}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center mt-12 mr-25 ml-25">
|
||||
<blockquote class="w-80 text-center text-lg break-words">
|
||||
{#if quote != ''}
|
||||
{quote}
|
||||
<div class="flex-1 flex justify-center items-center mt-12 md:mt-0 md:ml-6">
|
||||
<blockquote class="w-80 text-center text-2xl font-semibold break-words">
|
||||
{#if quote != null}
|
||||
{quote.quote}
|
||||
{/if}
|
||||
<!-- <footer class="text-sm">- Steve Jobs</footer> -->
|
||||
<footer class="text-sm mt-1">{quote.author}</footer>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fixed bottom-4 right-4 z-[999]">
|
||||
<div class="dropdown dropdown-left dropdown-end">
|
||||
<button class="btn m-1 btn-circle btn-md" on:click={() => (isImageInfoModalOpen = true)}>
|
||||
<FileImageBox class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<svelte:head>
|
||||
<title>Login | AdventureLog</title>
|
||||
<meta
|
||||
|
|
|
@ -19,41 +19,21 @@ export const load = (async (event) => {
|
|||
Cookie: `${event.cookies.get('auth')}`
|
||||
}
|
||||
});
|
||||
let visitedRegions = (await visitedRegionsFetch.json()) as VisitedRegion[];
|
||||
|
||||
if (!visitedFetch.ok) {
|
||||
let visitedRegions = (await visitedRegionsFetch.json()) as VisitedRegion[];
|
||||
let adventures = (await visitedFetch.json()) as Adventure[];
|
||||
|
||||
if (!visitedRegionsFetch.ok) {
|
||||
console.error('Failed to fetch visited regions');
|
||||
return redirect(302, '/login');
|
||||
} else if (!visitedFetch.ok) {
|
||||
console.error('Failed to fetch visited adventures');
|
||||
return redirect(302, '/login');
|
||||
} else {
|
||||
let visited: Adventure[] = [];
|
||||
try {
|
||||
let api_result = await visitedFetch.json();
|
||||
visited = api_result as Adventure[];
|
||||
if (!Array.isArray(visited) || visited.length === 0 || !visited) {
|
||||
throw new Error('Visited adventures response is not an array');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing visited adventures:', error);
|
||||
return redirect(302, '/login');
|
||||
}
|
||||
|
||||
// make a long lat array like this { lngLat: [-20, 0], name: 'Adventure 1' },
|
||||
let markers = visited
|
||||
.filter((adventure) => adventure.latitude !== null && adventure.longitude !== null)
|
||||
.map((adventure) => {
|
||||
return {
|
||||
lngLat: [adventure.longitude, adventure.latitude],
|
||||
name: adventure.name,
|
||||
visits: adventure.visits
|
||||
};
|
||||
});
|
||||
|
||||
console.log('sent');
|
||||
|
||||
return {
|
||||
props: {
|
||||
markers,
|
||||
visitedRegions
|
||||
visitedRegions,
|
||||
adventures
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,128 +1,101 @@
|
|||
<script>
|
||||
// @ts-nocheck
|
||||
|
||||
import { isAdventureVisited } from '$lib';
|
||||
<script lang="ts">
|
||||
import AdventureModal from '$lib/components/AdventureModal.svelte';
|
||||
import {
|
||||
DefaultMarker,
|
||||
MapEvents,
|
||||
MapLibre,
|
||||
Popup,
|
||||
Marker,
|
||||
GeoJSON,
|
||||
LineLayer,
|
||||
FillLayer,
|
||||
SymbolLayer
|
||||
} from 'svelte-maplibre';
|
||||
import { DefaultMarker, MapEvents, MapLibre, Popup, Marker } from 'svelte-maplibre';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { Adventure, VisitedRegion } from '$lib/types.js';
|
||||
import { getAdventureTypeLabel } from '$lib';
|
||||
import CardCarousel from '$lib/components/CardCarousel.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
export let data;
|
||||
|
||||
let clickedName = '';
|
||||
let createModalOpen: boolean = false;
|
||||
let showGeo: boolean = false;
|
||||
|
||||
let visitedRegions: VisitedRegion[] = data.props.visitedRegions;
|
||||
let adventures: Adventure[] = data.props.adventures;
|
||||
|
||||
let filteredAdventures = adventures;
|
||||
|
||||
// Updates the filtered adventures based on the checkboxes
|
||||
$: {
|
||||
filteredAdventures = adventures.filter(
|
||||
(adventure) => (showVisited && adventure.is_visited) || (showPlanned && !adventure.is_visited)
|
||||
);
|
||||
}
|
||||
|
||||
// Reset the longitude and latitude when the newMarker is set to null so new adventures are not created at the wrong location
|
||||
$: {
|
||||
if (!newMarker) {
|
||||
newLongitude = null;
|
||||
newLatitude = null;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(data);
|
||||
|
||||
let showVisited = true;
|
||||
let showPlanned = true;
|
||||
let showVisited: boolean = true;
|
||||
let showPlanned: boolean = true;
|
||||
|
||||
$: filteredMarkers = markers.filter(
|
||||
(marker) =>
|
||||
(showVisited && isAdventureVisited(marker)) || (showPlanned && !isAdventureVisited(marker))
|
||||
);
|
||||
let newMarker: { lngLat: any } | null = null;
|
||||
|
||||
let newMarker = [];
|
||||
let newLongitude: number | null = null;
|
||||
let newLatitude: number | null = null;
|
||||
|
||||
let newLongitude = null;
|
||||
let newLatitude = null;
|
||||
let openPopupId: string | null = null; // Store the ID of the currently open popup
|
||||
|
||||
function addMarker(e) {
|
||||
newMarker = [];
|
||||
newMarker = [...newMarker, { lngLat: e.detail.lngLat, name: 'Marker 1' }];
|
||||
function addMarker(e: { detail: { lngLat: { lng: any; lat: any } } }) {
|
||||
newMarker = null;
|
||||
newMarker = { lngLat: e.detail.lngLat };
|
||||
newLongitude = e.detail.lngLat.lng;
|
||||
newLatitude = e.detail.lngLat.lat;
|
||||
}
|
||||
|
||||
let markers = [];
|
||||
|
||||
$: {
|
||||
markers = data.props.markers;
|
||||
}
|
||||
|
||||
function createNewAdventure(event) {
|
||||
let newMarker = {
|
||||
lngLat: [event.detail.longitude, event.detail.latitude],
|
||||
name: event.detail.name,
|
||||
type: event.detail.type,
|
||||
visits: event.detail.visits
|
||||
};
|
||||
markers = [...markers, newMarker];
|
||||
clearMarkers();
|
||||
function createNewAdventure(event: CustomEvent) {
|
||||
adventures = [...adventures, event.detail];
|
||||
newMarker = null;
|
||||
createModalOpen = false;
|
||||
}
|
||||
let visitedRegions = data.props.visitedRegions;
|
||||
|
||||
let allRegions = [];
|
||||
let isPopupOpen = false;
|
||||
|
||||
let visitArray = [];
|
||||
|
||||
// turns in into an array of the visits
|
||||
visitedRegions.forEach((el) => {
|
||||
visitArray.push(el.region);
|
||||
});
|
||||
|
||||
function clearMarkers() {
|
||||
newMarker = [];
|
||||
newLatitude = null;
|
||||
newLongitude = null;
|
||||
function togglePopup() {
|
||||
isPopupOpen = !isPopupOpen;
|
||||
}
|
||||
|
||||
// mapped to the checkbox
|
||||
let showGEO = false;
|
||||
$: {
|
||||
if (showGEO && allRegions.length === 0) {
|
||||
(async () => {
|
||||
allRegions = await fetch('/api/visitedregion/').then((res) => res.json());
|
||||
})();
|
||||
} else if (!showGEO) {
|
||||
allRegions = [];
|
||||
}
|
||||
}
|
||||
|
||||
let createModalOpen = false;
|
||||
</script>
|
||||
|
||||
<h1 class="text-center font-bold text-4xl">Adventure Map</h1>
|
||||
<h1 class="text-center font-bold text-4xl">{$t('map.adventure_map')}</h1>
|
||||
|
||||
<div class="m-2 flex flex-col items-center justify-center">
|
||||
<div class="gap-4 border-solid border-2 rounded-lg p-2 mb-4 border-neutral max-w-4xl">
|
||||
<p class="font-semibold text-center text-xl mb-2">Map Options</p>
|
||||
<p class="font-semibold text-center text-xl mb-2">{$t('map.map_options')}</p>
|
||||
<div class="flex flex-wrap items-center justify-center gap-4">
|
||||
<label class="label cursor-pointer">
|
||||
<span class="label-text mr-1">Visited</span>
|
||||
<span class="label-text mr-1">{$t('adventures.visited')}</span>
|
||||
<input type="checkbox" bind:checked={showVisited} class="checkbox checkbox-primary" />
|
||||
</label>
|
||||
<label class="label cursor-pointer">
|
||||
<span class="label-text mr-1">Planned</span>
|
||||
<span class="label-text mr-1">{$t('adventures.planned')}</span>
|
||||
<input type="checkbox" bind:checked={showPlanned} class="checkbox checkbox-primary" />
|
||||
</label>
|
||||
<!-- <div class="divider divider-horizontal"></div> -->
|
||||
<label for="show-geo">Show Visited Regions</label>
|
||||
<label for="show-geo">{$t('map.show_visited_regions')}</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="show-geo"
|
||||
name="show-geo"
|
||||
class="checkbox"
|
||||
bind:checked={showGEO}
|
||||
on:click={() => (showGeo = !showGeo)}
|
||||
/>
|
||||
<!-- <div class="divider divider-horizontal"></div> -->
|
||||
{#if newMarker.length > 0}
|
||||
<div class="divider divider-horizontal"></div>
|
||||
{#if newMarker}
|
||||
<button type="button" class="btn btn-primary mb-2" on:click={() => (createModalOpen = true)}
|
||||
>Add New Adventure at Marker</button
|
||||
>{$t('map.add_adventure_at_marker')}</button
|
||||
>
|
||||
<button type="button" class="btn btn-neutral mb-2" on:click={clearMarkers}
|
||||
>Clear Marker</button
|
||||
<button type="button" class="btn btn-neutral mb-2" on:click={() => (newMarker = null)}
|
||||
>{$t('map.clear_marker')}</button
|
||||
>
|
||||
{:else}
|
||||
<button type="button" class="btn btn-primary mb-2" on:click={() => (createModalOpen = true)}
|
||||
>Add New Adventure</button
|
||||
>{$t('map.add_adventure')}</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -143,59 +116,67 @@
|
|||
class="relative aspect-[9/16] max-h-[70vh] w-full sm:aspect-video sm:max-h-full"
|
||||
standardControls
|
||||
>
|
||||
{#each filteredMarkers as marker}
|
||||
{#if isAdventureVisited(marker)}
|
||||
{#each filteredAdventures as adventure}
|
||||
{#if adventure.latitude && adventure.longitude}
|
||||
<Marker
|
||||
lngLat={marker.lngLat}
|
||||
on:click={() => (clickedName = marker.name)}
|
||||
class="grid h-8 w-8 place-items-center rounded-full border border-gray-200 bg-red-300 text-black shadow-md"
|
||||
lngLat={[adventure.longitude, adventure.latitude]}
|
||||
class="grid h-8 w-8 place-items-center rounded-full border border-gray-200 {adventure.is_visited
|
||||
? 'bg-red-300'
|
||||
: 'bg-blue-300'} text-black focus:outline-6 focus:outline-black"
|
||||
on:click={togglePopup}
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
<span class="text-xl">
|
||||
{getAdventureTypeLabel(adventure.type)}
|
||||
</span>
|
||||
{#if isPopupOpen}
|
||||
<Popup openOn="click" offset={[0, -10]} on:close={() => (isPopupOpen = false)}>
|
||||
{#if adventure.images && adventure.images.length > 0}
|
||||
<CardCarousel adventures={[adventure]} />
|
||||
{/if}
|
||||
<div class="text-lg text-black font-bold">{adventure.name}</div>
|
||||
<p class="font-semibold text-black text-md">
|
||||
{adventure.is_visited ? $t('adventures.visited') : $t('adventures.planned')}
|
||||
</p>
|
||||
<p class="font-semibold text-black text-md">
|
||||
{$t(`adventures.activities.${adventure.type}`)}
|
||||
</p>
|
||||
{#if adventure.visits && adventure.visits.length > 0}
|
||||
<p class="text-black text-sm">
|
||||
{#each adventure.visits as visit}
|
||||
{visit.start_date
|
||||
? new Date(visit.start_date).toLocaleDateString(undefined, {
|
||||
timeZone: 'UTC'
|
||||
})
|
||||
: ''}
|
||||
{visit.end_date && visit.end_date !== '' && visit.end_date !== visit.start_date
|
||||
? ' - ' +
|
||||
new Date(visit.end_date).toLocaleDateString(undefined, {
|
||||
timeZone: 'UTC'
|
||||
})
|
||||
: ''}
|
||||
<br />
|
||||
{/each}
|
||||
</p>
|
||||
{/if}
|
||||
<button
|
||||
class="btn btn-neutral btn-wide btn-sm mt-4"
|
||||
on:click={() => goto(`/adventures/${adventure.id}`)}>{$t('map.view_details')}</button
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" stroke="red" stroke-width="2" fill="red" />
|
||||
</svg>
|
||||
<Popup openOn="click" offset={[0, -10]}>
|
||||
<div class="text-lg text-black font-bold">{marker.name}</div>
|
||||
<p class="font-semibold text-black text-md">Visited</p>
|
||||
</Popup>
|
||||
</Marker>
|
||||
{:else}
|
||||
<Marker
|
||||
lngLat={marker.lngLat}
|
||||
on:click={() => (clickedName = marker.name)}
|
||||
class="grid h-8 w-8 place-items-center rounded-full border border-gray-200 bg-blue-300 text-black shadow-2xl focus:outline-2 focus:outline-black"
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" stroke="blue" stroke-width="2" fill="blue" />
|
||||
</svg>
|
||||
<Popup openOn="click" offset={[0, -10]}>
|
||||
<div class="text-lg text-black font-bold">{marker.name}</div>
|
||||
<p class="font-semibold text-black text-md">Planned</p>
|
||||
</Popup>
|
||||
{/if}
|
||||
</Marker>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<MapEvents on:click={addMarker} />
|
||||
{#each newMarker as marker}
|
||||
<DefaultMarker lngLat={marker.lngLat} />
|
||||
{/each}
|
||||
{#if newMarker}
|
||||
<DefaultMarker lngLat={newMarker.lngLat} />
|
||||
{/if}
|
||||
|
||||
{#each allRegions as { longitude, latitude, name, region }}
|
||||
{#each visitedRegions as region}
|
||||
{#if showGeo}
|
||||
<Marker
|
||||
lngLat={[longitude, latitude]}
|
||||
on:click={() => (clickedName = name)}
|
||||
lngLat={[region.longitude, region.latitude]}
|
||||
class="grid h-8 w-8 place-items-center rounded-full border border-gray-200 bg-green-300 text-black shadow-md"
|
||||
>
|
||||
<svg
|
||||
|
@ -205,14 +186,14 @@
|
|||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<!-- green circle -->
|
||||
<circle cx="12" cy="12" r="10" stroke="green" stroke-width="2" fill="green" />
|
||||
</svg>
|
||||
<Popup openOn="click" offset={[0, -10]}>
|
||||
<div class="text-lg text-black font-bold">{name}</div>
|
||||
<p class="font-semibold text-black text-md">{region}</p>
|
||||
<div class="text-lg text-black font-bold">{region.name}</div>
|
||||
<p class="font-semibold text-black text-md">{region.region}</p>
|
||||
</Popup>
|
||||
</Marker>
|
||||
{/if}
|
||||
{/each}
|
||||
</MapLibre>
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue