1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-08-03 20:25:18 +02:00

Add Lodging to Map, Sanitize MD, Optional Password Disable

This commit is contained in:
Sean Morley 2025-03-17 15:20:44 -04:00 committed by GitHub
commit 6eb57d1d6e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 733 additions and 403 deletions

View file

@ -71,7 +71,7 @@ class CustomUserAdmin(UserAdmin):
readonly_fields = ('uuid',) readonly_fields = ('uuid',)
search_fields = ('username',) search_fields = ('username',)
fieldsets = UserAdmin.fieldsets + ( fieldsets = UserAdmin.fieldsets + (
(None, {'fields': ('profile_pic', 'uuid', 'public_profile')}), (None, {'fields': ('profile_pic', 'uuid', 'public_profile', 'disable_password')}),
) )
def image_display(self, obj): def image_display(self, obj):
if obj.profile_pic: if obj.profile_pic:

View file

@ -0,0 +1,19 @@
# Generated by Django 5.0.8 on 2025-03-17 01:15
import adventures.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('adventures', '0023_lodging_delete_hotel'),
]
operations = [
migrations.AlterField(
model_name='attachment',
name='file',
field=models.FileField(upload_to=adventures.models.PathAndRename('attachments/'), validators=[adventures.models.validate_file_extension]),
),
]

View file

@ -1,4 +1,4 @@
from collections.abc import Collection from django.core.exceptions import ValidationError
import os import os
from typing import Iterable from typing import Iterable
import uuid import uuid
@ -10,6 +10,13 @@ from django.contrib.postgres.fields import ArrayField
from django.forms import ValidationError from django.forms import ValidationError
from django_resized import ResizedImageField from django_resized import ResizedImageField
def validate_file_extension(value):
import os
from django.core.exceptions import ValidationError
ext = os.path.splitext(value.name)[1] # [0] returns path+filename
valid_extensions = ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.txt', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.mp4', '.mov', '.avi', '.mkv', '.mp3', '.wav', '.flac', '.ogg', '.m4a', '.wma', '.aac', '.opus', '.zip', '.rar', '.7z', '.tar', '.gz', '.bz2', '.xz', '.zst', '.lz4', '.lzma', '.lzo', '.z', '.tar.gz', '.tar.bz2', '.tar.xz', '.tar.zst', '.tar.lz4', '.tar.lzma', '.tar.lzo', '.tar.z', 'gpx', 'md', 'pdf']
if not ext.lower() in valid_extensions:
raise ValidationError('Unsupported file extension.')
ADVENTURE_TYPES = [ ADVENTURE_TYPES = [
('general', 'General 🌍'), ('general', 'General 🌍'),
@ -306,7 +313,7 @@ class Attachment(models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey( user_id = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user_id)
file = models.FileField(upload_to=PathAndRename('attachments/')) file = models.FileField(upload_to=PathAndRename('attachments/'),validators=[validate_file_extension])
adventure = models.ForeignKey(Adventure, related_name='attachments', on_delete=models.CASCADE) adventure = models.ForeignKey(Adventure, related_name='attachments', on_delete=models.CASCADE)
name = models.CharField(max_length=200, null=True, blank=True) name = models.CharField(max_length=200, null=True, blank=True)

View file

@ -227,6 +227,10 @@ HEADLESS_FRONTEND_URLS = {
"socialaccount_login_error": f"{FRONTEND_URL}/account/provider/callback", "socialaccount_login_error": f"{FRONTEND_URL}/account/provider/callback",
} }
AUTHENTICATION_BACKENDS = [
'users.backends.NoPasswordAuthBackend',
]
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
SITE_ID = 1 SITE_ID = 1
ACCOUNT_EMAIL_REQUIRED = True ACCOUNT_EMAIL_REQUIRED = True

View file

@ -1,7 +1,7 @@
from django.urls import include, re_path, path from django.urls import include, re_path, path
from django.contrib import admin from django.contrib import admin
from django.views.generic import RedirectView, TemplateView from django.views.generic import RedirectView, TemplateView
from users.views import IsRegistrationDisabled, PublicUserListView, PublicUserDetailView, UserMetadataView, UpdateUserMetadataView, EnabledSocialProvidersView from users.views import IsRegistrationDisabled, PublicUserListView, PublicUserDetailView, UserMetadataView, UpdateUserMetadataView, EnabledSocialProvidersView, DisablePasswordAuthenticationView
from .views import get_csrf_token, get_public_url, serve_protected_media from .views import get_csrf_token, get_public_url, serve_protected_media
from drf_yasg.views import get_schema_view from drf_yasg.views import get_schema_view
from drf_yasg import openapi from drf_yasg import openapi
@ -29,6 +29,8 @@ urlpatterns = [
path('auth/social-providers/', EnabledSocialProvidersView.as_view(), name='enabled-social-providers'), path('auth/social-providers/', EnabledSocialProvidersView.as_view(), name='enabled-social-providers'),
path('auth/disable-password/', DisablePasswordAuthenticationView.as_view(), name='disable-password-authentication'),
path('csrf/', get_csrf_token, name='get_csrf_token'), path('csrf/', get_csrf_token, name='get_csrf_token'),
path('public-url/', get_public_url, name='get_public_url'), path('public-url/', get_public_url, name='get_public_url'),

View file

@ -0,0 +1,16 @@
from django.contrib.auth.backends import ModelBackend
from allauth.socialaccount.models import SocialAccount
class NoPasswordAuthBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
print("NoPasswordAuthBackend")
# First, attempt normal authentication
user = super().authenticate(request, username=username, password=password, **kwargs)
if user is None:
return None
if SocialAccount.objects.filter(user=user).exists() and user.disable_password:
# If yes, disable login via password
return None
return user

View file

@ -0,0 +1,18 @@
# Generated by Django 5.0.8 on 2025-03-17 01:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0003_alter_customuser_email'),
]
operations = [
migrations.AddField(
model_name='customuser',
name='disable_password',
field=models.BooleanField(default=False),
),
]

View file

@ -8,6 +8,7 @@ class CustomUser(AbstractUser):
profile_pic = ResizedImageField(force_format="WEBP", quality=75, null=True, blank=True, upload_to='profile-pics/') profile_pic = ResizedImageField(force_format="WEBP", quality=75, null=True, blank=True, upload_to='profile-pics/')
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
public_profile = models.BooleanField(default=False) public_profile = models.BooleanField(default=False)
disable_password = models.BooleanField(default=False)
def __str__(self): def __str__(self):
return self.username return self.username

View file

@ -64,9 +64,11 @@ class UserDetailsSerializer(serializers.ModelSerializer):
extra_fields.append('date_joined') extra_fields.append('date_joined')
if hasattr(UserModel, 'is_staff'): if hasattr(UserModel, 'is_staff'):
extra_fields.append('is_staff') extra_fields.append('is_staff')
if hasattr(UserModel, 'disable_password'):
extra_fields.append('disable_password')
fields = ['pk', *extra_fields] fields = ['pk', *extra_fields]
read_only_fields = ('email', 'date_joined', 'is_staff', 'is_superuser', 'is_active', 'pk') read_only_fields = ('email', 'date_joined', 'is_staff', 'is_superuser', 'is_active', 'pk', 'disable_password')
def handle_public_profile_change(self, instance, validated_data): def handle_public_profile_change(self, instance, validated_data):
""" """
@ -94,8 +96,8 @@ class CustomUserDetailsSerializer(UserDetailsSerializer):
class Meta(UserDetailsSerializer.Meta): class Meta(UserDetailsSerializer.Meta):
model = CustomUser model = CustomUser
fields = UserDetailsSerializer.Meta.fields + ['profile_pic', 'uuid', 'public_profile', 'has_password'] fields = UserDetailsSerializer.Meta.fields + ['profile_pic', 'uuid', 'public_profile', 'has_password', 'disable_password']
read_only_fields = UserDetailsSerializer.Meta.read_only_fields + ('uuid', 'has_password') read_only_fields = UserDetailsSerializer.Meta.read_only_fields + ('uuid', 'has_password', 'disable_password')
@staticmethod @staticmethod
def get_has_password(instance): def get_has_password(instance):
@ -120,5 +122,5 @@ class CustomUserDetailsSerializer(UserDetailsSerializer):
representation.pop('pk', None) representation.pop('pk', None)
# Remove the email field # Remove the email field
representation.pop('email', None) representation.pop('email', None)
return representation return representation

View file

@ -13,6 +13,7 @@ from .serializers import CustomUserDetailsSerializer as PublicUserSerializer
from allauth.socialaccount.models import SocialApp from allauth.socialaccount.models import SocialApp
from adventures.serializers import AdventureSerializer, CollectionSerializer from adventures.serializers import AdventureSerializer, CollectionSerializer
from adventures.models import Adventure, Collection from adventures.models import Adventure, Collection
from allauth.socialaccount.models import SocialAccount
User = get_user_model() User = get_user_model()
@ -71,6 +72,7 @@ class PublicUserListView(APIView):
# for every user, remove the field has_password # for every user, remove the field has_password
for user in serializer.data: for user in serializer.data:
user.pop('has_password', None) user.pop('has_password', None)
user.pop('disable_password', None)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
class PublicUserDetailView(APIView): class PublicUserDetailView(APIView):
@ -171,4 +173,35 @@ class EnabledSocialProvidersView(APIView):
'url': f"{getenv('PUBLIC_URL')}/accounts/{new_provider}/login/", 'url': f"{getenv('PUBLIC_URL')}/accounts/{new_provider}/login/",
'name': provider.name 'name': provider.name
}) })
return Response(providers, status=status.HTTP_200_OK) return Response(providers, status=status.HTTP_200_OK)
class DisablePasswordAuthenticationView(APIView):
"""
Disable password authentication for a user. This is used when a user signs up with a social provider.
"""
# Allows the user to set the disable_password field to True if they have a social account linked
permission_classes = [IsAuthenticated]
@swagger_auto_schema(
responses={
200: openapi.Response('Password authentication disabled'),
400: 'Bad Request'
},
operation_description="Disable password authentication."
)
def post(self, request):
user = request.user
if SocialAccount.objects.filter(user=user).exists():
user.disable_password = True
user.save()
return Response({"detail": "Password authentication disabled."}, status=status.HTTP_200_OK)
return Response({"detail": "No social account linked."}, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request):
user = request.user
user.disable_password = False
user.save()
return Response({"detail": "Password authentication enabled."}, status=status.HTTP_200_OK)

View file

@ -34,19 +34,23 @@ class CountrySerializer(serializers.ModelSerializer):
class RegionSerializer(serializers.ModelSerializer): class RegionSerializer(serializers.ModelSerializer):
num_cities = serializers.SerializerMethodField() num_cities = serializers.SerializerMethodField()
country_name = serializers.CharField(source='country.name', read_only=True)
class Meta: class Meta:
model = Region model = Region
fields = '__all__' fields = '__all__'
read_only_fields = ['id', 'name', 'country', 'longitude', 'latitude', 'num_cities'] read_only_fields = ['id', 'name', 'country', 'longitude', 'latitude', 'num_cities', 'country_name']
def get_num_cities(self, obj): def get_num_cities(self, obj):
return City.objects.filter(region=obj).count() return City.objects.filter(region=obj).count()
class CitySerializer(serializers.ModelSerializer): class CitySerializer(serializers.ModelSerializer):
region_name = serializers.CharField(source='region.name', read_only=True)
country_name = serializers.CharField(source='region.country.name', read_only=True
)
class Meta: class Meta:
model = City model = City
fields = '__all__' fields = '__all__'
read_only_fields = ['id', 'name', 'region', 'longitude', 'latitude'] read_only_fields = ['id', 'name', 'region', 'longitude', 'latitude', 'region_name', 'country_name']
class VisitedRegionSerializer(CustomModelSerializer): class VisitedRegionSerializer(CustomModelSerializer):
longitude = serializers.DecimalField(source='region.longitude', max_digits=9, decimal_places=6, read_only=True) longitude = serializers.DecimalField(source='region.longitude', max_digits=9, decimal_places=6, read_only=True)

View file

@ -40,6 +40,7 @@
"dependencies": { "dependencies": {
"@lukulent/svelte-umami": "^0.0.3", "@lukulent/svelte-umami": "^0.0.3",
"@mapbox/togeojson": "^0.16.2", "@mapbox/togeojson": "^0.16.2",
"dompurify": "^3.2.4",
"emoji-picker-element": "^1.26.0", "emoji-picker-element": "^1.26.0",
"gsap": "^3.12.7", "gsap": "^3.12.7",
"marked": "^15.0.4", "marked": "^15.0.4",

View file

@ -14,6 +14,9 @@ importers:
'@mapbox/togeojson': '@mapbox/togeojson':
specifier: ^0.16.2 specifier: ^0.16.2
version: 0.16.2 version: 0.16.2
dompurify:
specifier: ^3.2.4
version: 3.2.4
emoji-picker-element: emoji-picker-element:
specifier: ^1.26.0 specifier: ^1.26.0
version: 1.26.0 version: 1.26.0
@ -816,6 +819,9 @@ packages:
'@types/supercluster@7.1.3': '@types/supercluster@7.1.3':
resolution: {integrity: sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==} resolution: {integrity: sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==}
'@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
'@vercel/nft@0.27.2': '@vercel/nft@0.27.2':
resolution: {integrity: sha512-7LeioS1yE5hwPpQfD3DdH04tuugKjo5KrJk3yK5kAI3Lh76iSsK/ezoFQfzuT08X3ZASQOd1y9ePjLNI9+TxTQ==} resolution: {integrity: sha512-7LeioS1yE5hwPpQfD3DdH04tuugKjo5KrJk3yK5kAI3Lh76iSsK/ezoFQfzuT08X3ZASQOd1y9ePjLNI9+TxTQ==}
engines: {node: '>=16'} engines: {node: '>=16'}
@ -1093,6 +1099,9 @@ packages:
dlv@1.1.3: dlv@1.1.3:
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
dompurify@3.2.4:
resolution: {integrity: sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==}
earcut@2.2.4: earcut@2.2.4:
resolution: {integrity: sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==} resolution: {integrity: sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==}
@ -2828,6 +2837,9 @@ snapshots:
dependencies: dependencies:
'@types/geojson': 7946.0.14 '@types/geojson': 7946.0.14
'@types/trusted-types@2.0.7':
optional: true
'@vercel/nft@0.27.2': '@vercel/nft@0.27.2':
dependencies: dependencies:
'@mapbox/node-pre-gyp': 1.0.11 '@mapbox/node-pre-gyp': 1.0.11
@ -3094,6 +3106,10 @@ snapshots:
dlv@1.1.3: {} dlv@1.1.3: {}
dompurify@3.2.4:
optionalDependencies:
'@types/trusted-types': 2.0.7
earcut@2.2.4: {} earcut@2.2.4: {}
eastasianwidth@0.2.0: {} eastasianwidth@0.2.0: {}

View file

@ -16,6 +16,7 @@ declare global {
uuid: string; uuid: string;
public_profile: boolean; public_profile: boolean;
has_password: boolean; has_password: boolean;
disable_password: boolean;
} | null; } | null;
locale: string; locale: string;
} }

View file

@ -14,6 +14,57 @@
let categories: Category[] = []; let categories: Category[] = [];
const allowedFileTypes = [
'.pdf',
'.doc',
'.docx',
'.xls',
'.xlsx',
'.ppt',
'.pptx',
'.txt',
'.png',
'.jpg',
'.jpeg',
'.gif',
'.webp',
'.mp4',
'.mov',
'.avi',
'.mkv',
'.mp3',
'.wav',
'.flac',
'.ogg',
'.m4a',
'.wma',
'.aac',
'.opus',
'.zip',
'.rar',
'.7z',
'.tar',
'.gz',
'.bz2',
'.xz',
'.zst',
'.lz4',
'.lzma',
'.lzo',
'.z',
'.tar.gz',
'.tar.bz2',
'.tar.xz',
'.tar.zst',
'.tar.lz4',
'.tar.lzma',
'.tar.lzo',
'.tar.z',
'gpx',
'md',
'pdf'
];
export let initialLatLng: { lat: number; lng: number } | null = null; // Used to pass the location from the map selection to the modal export let initialLatLng: { lat: number; lng: number } | null = null; // Used to pass the location from the map selection to the modal
let fileInput: HTMLInputElement; let fileInput: HTMLInputElement;
@ -783,7 +834,7 @@
type="file" type="file"
id="fileInput" id="fileInput"
class="file-input file-input-bordered w-full max-w-xs" class="file-input file-input-bordered w-full max-w-xs"
accept="image/*,video/*,audio/*,application/pdf,.gpx" accept={allowedFileTypes.join(',')}
on:change={handleFileChange} on:change={handleFileChange}
/> />

View file

@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation';
import { addToast } from '$lib/toasts'; import { addToast } from '$lib/toasts';
import type { City } from '$lib/types'; import type { City } from '$lib/types';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
@ -45,7 +46,10 @@
<div class="card-body"> <div class="card-body">
<h2 class="card-title overflow-ellipsis">{city.name}</h2> <h2 class="card-title overflow-ellipsis">{city.name}</h2>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<div class="badge badge-neutral-300">{city.id}</div> <div class="badge badge-primary">
{city.region_name}, {city.country_name}
</div>
<div class="badge badge-neutral-300">{city.region}</div>
</div> </div>
<div class="card-actions justify-end"> <div class="card-actions justify-end">
{#if !visited} {#if !visited}

View file

@ -6,9 +6,18 @@
import { addToast } from '$lib/toasts'; import { addToast } from '$lib/toasts';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import DeleteWarning from './DeleteWarning.svelte'; import DeleteWarning from './DeleteWarning.svelte';
import { LODGING_TYPES_ICONS } from '$lib';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
function getLodgingIcon(type: string) {
if (type in LODGING_TYPES_ICONS) {
return LODGING_TYPES_ICONS[type as keyof typeof LODGING_TYPES_ICONS];
} else {
return '🏨';
}
}
export let lodging: Lodging; export let lodging: Lodging;
export let user: User | null = null; export let user: User | null = null;
export let collection: Collection | null = null; export let collection: Collection | null = null;
@ -91,7 +100,7 @@
<h2 class="card-title text-lg font-semibold truncate">{lodging.name}</h2> <h2 class="card-title text-lg font-semibold truncate">{lodging.name}</h2>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="badge badge-secondary"> <div class="badge badge-secondary">
{$t(`lodging.${lodging.type}`)} {$t(`lodging.${lodging.type}`) + ' ' + getLodgingIcon(lodging.type)}
</div> </div>
<!-- {#if hotel.type == 'plane' && hotel.flight_number} <!-- {#if hotel.type == 'plane' && hotel.flight_number}
<div class="badge badge-neutral-200">{hotel.flight_number}</div> <div class="badge badge-neutral-200">{hotel.flight_number}</div>

View file

@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { marked } from 'marked'; // Import the markdown parser import { marked } from 'marked'; // Import the markdown parser
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import DOMPurify from 'dompurify'; // Import DOMPurify to sanitize HTML
export let text: string | null | undefined = ''; // Markdown text export let text: string | null | undefined = ''; // Markdown text
export let editor_height: string = 'h-64'; // Editor height export let editor_height: string = 'h-64'; // Editor height
@ -8,7 +9,7 @@
// Function to parse markdown to HTML // Function to parse markdown to HTML
const renderMarkdown = (markdown: string) => { const renderMarkdown = (markdown: string) => {
return marked(markdown); return marked(markdown) as string;
}; };
// References for scroll syncing // References for scroll syncing
@ -61,7 +62,7 @@
class="prose overflow-auto h-96 max-w-full w-full p-4 border border-base-300 rounded-lg bg-base-300" class="prose overflow-auto h-96 max-w-full w-full p-4 border border-base-300 rounded-lg bg-base-300"
bind:this={previewRef} bind:this={previewRef}
> >
{@html renderMarkdown(text || '')} {@html DOMPurify.sanitize(renderMarkdown(text || ''))}
</article> </article>
{/if} {/if}
</div> </div>

View file

@ -56,11 +56,14 @@
<h2 class="card-title overflow-ellipsis">{region.name}</h2> <h2 class="card-title overflow-ellipsis">{region.name}</h2>
<div> <div>
<div class="badge badge-primary"> <div class="badge badge-primary">
<p>{region.id}</p> <p>{region.country_name}</p>
</div> </div>
<div class="badge badge-neutral-300"> <div class="badge badge-neutral-300">
<p>{region.num_cities} {$t('worldtravel.cities')}</p> <p>{region.num_cities} {$t('worldtravel.cities')}</p>
</div> </div>
<div class="badge badge-neutral-300">
<p>{region.id}</p>
</div>
</div> </div>
<div class="card-actions justify-end"> <div class="card-actions justify-end">
<!-- <button class="btn btn-info" on:click={moreInfo}>More Info</button> --> <!-- <button class="btn btn-info" on:click={moreInfo}>More Info</button> -->

View file

@ -324,6 +324,20 @@ export let ADVENTURE_TYPE_ICONS = {
other: '❓' other: '❓'
}; };
export let LODGING_TYPES_ICONS = {
hotel: '🏨',
hostel: '🛏️',
resort: '🏝️',
bnb: '🍳',
campground: '🏕️',
cabin: '🏚️',
apartment: '🏢',
house: '🏠',
villa: '🏡',
motel: '🚗🏨',
other: '❓'
};
export function getAdventureTypeLabel(type: string) { export function getAdventureTypeLabel(type: string) {
// return the emoji ADVENTURE_TYPE_ICONS label for the given type if not found return ? emoji // return the emoji ADVENTURE_TYPE_ICONS label for the given type if not found return ? emoji
if (type in ADVENTURE_TYPE_ICONS) { if (type in ADVENTURE_TYPE_ICONS) {

View file

@ -9,6 +9,7 @@ export type User = {
uuid: string; uuid: string;
public_profile: boolean; public_profile: boolean;
has_password: boolean; has_password: boolean;
disable_password: boolean;
}; };
export type Adventure = { export type Adventure = {
@ -63,6 +64,7 @@ export type Region = {
latitude: number; latitude: number;
longitude: number; longitude: number;
num_cities: number; num_cities: number;
country_name: string;
}; };
export type City = { export type City = {
@ -71,6 +73,8 @@ export type City = {
latitude: number | null; latitude: number | null;
longitude: number | null; longitude: number | null;
region: string; region: string;
region_name: string;
country_name: string;
}; };
export type VisitedRegion = { export type VisitedRegion = {

View file

@ -6,32 +6,32 @@
"message": "Hergestellt mit ❤️ in den Vereinigten Staaten.", "message": "Hergestellt mit ❤️ in den Vereinigten Staaten.",
"nominatim_1": "Standortsuche und Geokodierung werden bereitgestellt von", "nominatim_1": "Standortsuche und Geokodierung werden bereitgestellt von",
"nominatim_2": "Deren Daten sind unter der ODbL-Lizenz lizenziert.", "nominatim_2": "Deren Daten sind unter der ODbL-Lizenz lizenziert.",
"oss_attributions": "Open-Source-Zuschreibungen", "oss_attributions": "Open Source Quellenangaben",
"other_attributions": "Weitere Hinweise finden Sie in der README-Datei.", "other_attributions": "Weitere Hinweise finden Sie in der README-Datei.",
"source_code": "Quellcode" "source_code": "Quellcode"
}, },
"adventures": { "adventures": {
"activities": { "activities": {
"activity": "Aktivität 🏄", "activity": "Aktivität 🏄",
"art_museums": "Kunst", "art_museums": "Kunst & Museen",
"attraction": "Attraktion 🎢", "attraction": "Sehenswürdigkeit 🎢",
"culture": "Kultur 🎭", "culture": "Kultur 🎭",
"dining": "Essen 🍽️", "dining": "Essen 🍽️",
"event": "Veranstaltung 🎉", "event": "Veranstaltung 🎉",
"festivals": "Feste 🎪", "festivals": "Festivals 🎪",
"fitness": "Fitness 🏋️", "fitness": "Fitness 🏋️",
"general": "Allgemein 🌍", "general": "Allgemein 🌍",
"hiking": "Wandern 🥾", "hiking": "Wandern 🥾",
"historical_sites": "Historische Stätten 🏛️", "historical_sites": "Historische Denkmäler 🏛️",
"lodging": "Unterkunft 🛌", "lodging": "Herberge 🛌",
"music_concerts": "Musik", "music_concerts": "Musik & Konzerte",
"nightlife": "Nachtleben 🌃", "nightlife": "Nachtleben 🌃",
"other": "Andere", "other": "Sonstiges",
"outdoor": "Draußen 🏞️", "outdoor": "Outdoor 🏞️",
"shopping": "Einkaufen 🛍️", "shopping": "Einkaufen 🛍️",
"spiritual_journeys": "Spirituelle Reisen 🧘‍♀️", "spiritual_journeys": "Spirituelle Reisen 🧘‍♀️",
"transportation": "Transport 🚗", "transportation": "Transport 🚗",
"volunteer_work": "Freiwilligenarbeit 🤝", "volunteer_work": "Ehrenamt 🤝",
"water_sports": "Wassersport 🚤", "water_sports": "Wassersport 🚤",
"wildlife": "Wildtiere 🦒" "wildlife": "Wildtiere 🦒"
}, },
@ -44,8 +44,8 @@
"delete": "Löschen", "delete": "Löschen",
"edit_adventure": "Abenteuer bearbeiten", "edit_adventure": "Abenteuer bearbeiten",
"no_image_found": "Kein Bild gefunden", "no_image_found": "Kein Bild gefunden",
"open_details": "Details öffnen", "open_details": "Details",
"remove_from_collection": "Aus der Sammlung entfernen", "remove_from_collection": "Aus Sammlung entfernen",
"adventure": "Abenteuer", "adventure": "Abenteuer",
"adventure_delete_success": "Abenteuer erfolgreich gelöscht!", "adventure_delete_success": "Abenteuer erfolgreich gelöscht!",
"adventure_details": "Abenteuerdetails", "adventure_details": "Abenteuerdetails",
@ -60,8 +60,8 @@
"clear": "zurücksetzen", "clear": "zurücksetzen",
"close_filters": "Filter schließen", "close_filters": "Filter schließen",
"collection": "Sammlung", "collection": "Sammlung",
"collection_adventures": "Sammlungsabenteuer berücksichtigen", "collection_adventures": "Abenteuer aus Sammlung berücksichtigen",
"count_txt": "Ergebnisse, die Ihrer Suche entsprechen", "count_txt": "Suchergebnisse",
"date": "Datum", "date": "Datum",
"dates": "Termine", "dates": "Termine",
"delete_adventure": "Abenteuer löschen", "delete_adventure": "Abenteuer löschen",
@ -77,14 +77,14 @@
"image_removed_success": "Bild erfolgreich entfernt!", "image_removed_success": "Bild erfolgreich entfernt!",
"image_upload_error": "Fehler beim Hochladen des Bildes", "image_upload_error": "Fehler beim Hochladen des Bildes",
"image_upload_success": "Bild erfolgreich hochgeladen!", "image_upload_success": "Bild erfolgreich hochgeladen!",
"latitude": "Breite", "latitude": "Breitengrad",
"longitude": "Länge", "longitude": "Längengrad",
"my_collections": "Meine Sammlungen", "my_collections": "Meine Sammlungen",
"name": "Name", "name": "Name",
"no_image_url": "Unter dieser URL wurde kein Bild gefunden.", "no_image_url": "Unter dieser URL wurde kein Bild gefunden.",
"not_found": "Abenteuer nicht 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.", "not_found_desc": "Das von Ihnen gesuchte Abenteuer konnte nicht gefunden werden. \nBitte versuchen Sie ein anderes Abenteuer aus oder schauen Sie später noch mal vorbei.",
"open_filters": "Öffnen Sie Filter", "open_filters": "Filter öffnen",
"order_by": "Sortieren nach", "order_by": "Sortieren nach",
"order_direction": "Sortierreihenfolge", "order_direction": "Sortierreihenfolge",
"planned": "Geplant", "planned": "Geplant",
@ -94,7 +94,7 @@
"share": "Teilen", "share": "Teilen",
"sort": "Sortieren", "sort": "Sortieren",
"sources": "Quellen", "sources": "Quellen",
"start_before_end_error": "Das Startdatum muss vor dem Enddatum liegen", "start_before_end_error": "Das Start- muss vor dem Enddatum liegen",
"unarchive": "Dearchivieren", "unarchive": "Dearchivieren",
"unarchived_collection_message": "Sammlung erfolgreich dearchiviert!", "unarchived_collection_message": "Sammlung erfolgreich dearchiviert!",
"updated": "Aktualisiert", "updated": "Aktualisiert",
@ -106,18 +106,18 @@
"activity": "Aktivität", "activity": "Aktivität",
"activity_types": "Aktivitätstypen", "activity_types": "Aktivitätstypen",
"add": "Hinzufügen", "add": "Hinzufügen",
"add_an_activity": "Fügen Sie eine Aktivität hinzu", "add_an_activity": "Aktivität hinzufügen",
"add_notes": "Notizen hinzufügen", "add_notes": "Notizen hinzufügen",
"adventure_create_error": "Das Abenteuer konnte nicht erstellt werden", "adventure_create_error": "Das Abenteuer konnte nicht erstellt werden",
"adventure_created": "Abenteuer erstellt", "adventure_created": "Abenteuer erstellt",
"adventure_update_error": "Das Abenteuer konnte nicht aktualisiert werden", "adventure_update_error": "Das Abenteuer konnte nicht aktualisiert werden",
"adventure_updated": "Abenteuer aktualisiert", "adventure_updated": "Abenteuer aktualisiert",
"basic_information": "Grundlegende Informationen", "basic_information": "Basisdaten",
"category": "Kategorie", "category": "Kategorie",
"clear_map": "Karte leeren", "clear_map": "Karte leeren",
"copy_link": "Link kopieren", "copy_link": "Link kopieren",
"create_new": "Neu erstellen...", "create_new": "Neu erstellen...",
"date_constrain": "Auf Abholtermine beschränken", "date_constrain": "Beschränke auf Sammlungstermine",
"description": "Beschreibung", "description": "Beschreibung",
"end_date": "Enddatum", "end_date": "Enddatum",
"fetch_image": "Bild abrufen", "fetch_image": "Bild abrufen",
@ -138,7 +138,7 @@
"public_adventure": "Öffentliches Abenteuer", "public_adventure": "Öffentliches Abenteuer",
"remove": "Entfernen", "remove": "Entfernen",
"save_next": "Speichern & weiter", "save_next": "Speichern & weiter",
"search_for_location": "Suchen Sie nach einem Ort", "search_for_location": "Nach einem Ort suchen",
"search_results": "Suchergebnisse", "search_results": "Suchergebnisse",
"see_adventures": "Siehe Abenteuer", "see_adventures": "Siehe Abenteuer",
"select_adventure_category": "Wählen Sie die Abenteuerkategorie", "select_adventure_category": "Wählen Sie die Abenteuerkategorie",
@ -150,14 +150,14 @@
"warning": "Warnung", "warning": "Warnung",
"wiki_desc": "Ruft einen Auszug aus einem Wikipedia-Artikel ab, der zum Namen des Abenteuers passt.", "wiki_desc": "Ruft einen Auszug aus einem Wikipedia-Artikel ab, der zum Namen des Abenteuers passt.",
"wikipedia": "Wikipedia", "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!", "adventure_not_found": "Keine Abenteuer vorhanden. \nFügen Sie welche über die Plus-Schaltfläche unten rechts hinzu oder versuchen Sie, die Filter zu ändern!",
"all": "Alle", "all": "Alle",
"error_updating_regions": "Fehler beim Aktualisieren der Regionen", "error_updating_regions": "Fehler beim Aktualisieren der Regionen",
"mark_region_as_visited": "Region {region}, {country} als besucht markieren?", "mark_region_as_visited": "Region {region}, {country} als besucht markieren?",
"mark_visited": "als besucht markieren", "mark_visited": "als besucht markieren",
"my_adventures": "Meine Abenteuer", "my_adventures": "Meine Abenteuer",
"no_adventures_found": "Keine Abenteuer gefunden", "no_adventures_found": "Keine Abenteuer gefunden",
"no_collections_found": "Es wurden keine Sammlungen gefunden, zu denen dieses Abenteuer hinzugefügt werden kann.", "no_collections_found": "Es wurden keine Sammlungen gefunden, die zu diesem Abenteuer hinzugefügt werden können.",
"no_linkable_adventures": "Es wurden keine Abenteuer gefunden, die mit dieser Sammlung verknüpft werden können.", "no_linkable_adventures": "Es wurden keine Abenteuer gefunden, die mit dieser Sammlung verknüpft werden können.",
"not_visited": "Nicht besucht", "not_visited": "Nicht besucht",
"regions_updated": "Regionen aktualisiert", "regions_updated": "Regionen aktualisiert",
@ -165,65 +165,65 @@
"update_visited_regions_disclaimer": "Dies kann je nach Anzahl der Abenteuer, die Sie besucht haben, eine Weile dauern.", "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": "Ü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, im Bereich Weltreisen als besucht.", "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, im Bereich Weltreisen als besucht.",
"add_new": "Neu hinzufügen...", "add_new": "Neu...",
"checklist": "Checkliste", "checklist": "Checkliste",
"checklists": "Checklisten", "checklists": "Checklisten",
"collection_completed": "Du hast diese Sammlung vervollständigt!", "collection_completed": "Du hast die Sammlung vervollständigt!",
"collection_stats": "Sammlungsstatistiken", "collection_stats": "Sammlungsstatistiken",
"days": "Tage", "days": "Tage",
"itineary_by_date": "Reiseroute nach Datum", "itineary_by_date": "Reiseroute nach Datum",
"keep_exploring": "Entdecken Sie weiter!", "keep_exploring": "Weiter erkunden!",
"link_new": "Link Neu...", "link_new": "Neuer Link...",
"linked_adventures": "Verknüpfte Abenteuer", "linked_adventures": "Verknüpfte Abenteuer",
"links": "Links", "links": "Links",
"no_end_date": "Bitte geben Sie ein Enddatum ein", "no_end_date": "Bitte ein Enddatum eingeben",
"note": "Notiz", "note": "Notiz",
"notes": "Notizen", "notes": "Notizen",
"nothing_planned": "Für diesen Tag ist nichts geplant. \nGenieße die Reise!", "nothing_planned": "Für heute ist nichts geplant. \nGenieße die Reise!",
"transportation": "Transport", "transportation": "Transport",
"transportations": "Transporte", "transportations": "Transporte",
"visit_link": "Besuchen Sie den Link", "visit_link": "Besuche Link",
"collection_archived": "Diese Sammlung wurde archiviert.", "collection_archived": "Die Sammlung wurde archiviert.",
"day": "Tag", "day": "Tag",
"add_a_tag": "Fügen Sie einen Tag hinzu", "add_a_tag": "Fügen Sie ein Schlagwort hinzu",
"tags": "Schlagworte", "tags": "Schlagworte",
"set_to_pin": "Auf „Anpinnen“ setzen", "set_to_pin": "Auf „Anpinnen“ setzen",
"category_fetch_error": "Fehler beim Abrufen der Kategorien", "category_fetch_error": "Fehler beim Abrufen der Kategorien",
"copied_to_clipboard": "In die Zwischenablage kopiert!", "copied_to_clipboard": "In die Zwischenablage kopiert!",
"copy_failed": "Das Kopieren ist fehlgeschlagen", "copy_failed": "Das Kopieren ist fehlgeschlagen",
"adventure_calendar": "Abenteuerkalender", "adventure_calendar": "Abenteuerkalender",
"emoji_picker": "Emoji-Picker", "emoji_picker": "Emoji-Wähler",
"hide": "Verstecken", "hide": "Verstecken",
"show": "Zeigen", "show": "Zeigen",
"download_calendar": "Kalender herunterladen", "download_calendar": "Kalender herunterladen",
"md_instructions": "Schreiben Sie hier Ihren Markdowntext...", "md_instructions": "Hier den Markdowntext schreiben...",
"preview": "Vorschau", "preview": "Vorschau",
"checklist_delete_confirm": "Sind Sie sicher, dass Sie diese Checkliste löschen möchten? \nDiese Aktion kann nicht rückgängig gemacht werden.", "checklist_delete_confirm": "Sind Sie sicher, dass Sie diese Checkliste löschen möchten? \nDies kann nicht rückgängig gemacht werden.",
"clear_location": "Standort löschen", "clear_location": "Standort löschen",
"date_information": "Datumsinformationen", "date_information": "Datumsinformationen",
"delete_checklist": "Checkliste löschen", "delete_checklist": "Checkliste löschen",
"delete_note": "Notiz löschen", "delete_note": "Notiz löschen",
"delete_transportation": "Transport löschen", "delete_transportation": "Transport löschen",
"end": "Ende", "end": "Ende",
"ending_airport": "Endflughafen", "ending_airport": "Zielflughafen",
"flight_information": "Fluginformationen", "flight_information": "Fluginformationen",
"from": "Von", "from": "Von",
"no_location_found": "Kein Standort gefunden", "no_location_found": "Keinen Standort gefunden",
"note_delete_confirm": "Sind Sie sicher, dass Sie diese Notiz löschen möchten? \nDiese Aktion kann nicht rückgängig gemacht werden.", "note_delete_confirm": "Sind Sie sicher, dass Sie diese Notiz löschen möchten? \nDies kann nicht rückgängig gemacht werden!",
"out_of_range": "Nicht im Datumsbereich der Reiseroute", "out_of_range": "Außerhalb des geplanten Reisezeitraums",
"show_region_labels": "Regionsbeschriftungen anzeigen", "show_region_labels": "Regionsbeschriftungen anzeigen",
"start": "Start", "start": "Start",
"starting_airport": "Startflughafen", "starting_airport": "Startflughafen",
"to": "Nach", "to": "Nach",
"transportation_delete_confirm": "Sind Sie sicher, dass Sie diesen Transport löschen möchten? \nDiese Aktion kann nicht rückgängig gemacht werden.", "transportation_delete_confirm": "Sind Sie sicher, dass Sie diesen Transport löschen möchten? \nDies lässt sich nicht rückgängig machen.",
"show_map": "Karte anzeigen", "show_map": "Karte anzeigen",
"will_be_marked": "wird als besucht markiert, sobald das Abenteuer gespeichert ist.", "will_be_marked": "wird als besucht markiert, sobald das Abenteuer gespeichert wird.",
"cities_updated": "Städte aktualisiert", "cities_updated": "Städte aktualisiert",
"create_adventure": "Erstelle Abenteuer", "create_adventure": "Erstelle Abenteuer",
"no_adventures_to_recommendations": "Keine Abenteuer gefunden. \nFügen Sie mindestens ein Abenteuer hinzu, um Empfehlungen zu erhalten.", "no_adventures_to_recommendations": "Keine Abenteuer gefunden. \nFügen Sie mindestens ein Abenteuer hinzu, um Empfehlungen zu erhalten.",
"finding_recommendations": "Entdecken Sie verborgene Schätze für Ihr nächstes Abenteuer", "finding_recommendations": "Entdecken Sie verborgene Schätze für Ihr nächstes Abenteuer",
"attachment": "Anhang", "attachment": "Anhang",
"attachment_delete_success": "Anhang erfolgreich gelöscht!", "attachment_delete_success": "Anhang gelöscht!",
"attachment_name": "Anhangsname", "attachment_name": "Anhangsname",
"attachment_update_error": "Fehler beim Aktualisieren des Anhangs", "attachment_update_error": "Fehler beim Aktualisieren des Anhangs",
"attachment_update_success": "Anhang erfolgreich aktualisiert!", "attachment_update_success": "Anhang erfolgreich aktualisiert!",
@ -242,26 +242,26 @@
"lodging": "Unterkunft", "lodging": "Unterkunft",
"region": "Region", "region": "Region",
"delete_lodging": "Unterkunft löschen", "delete_lodging": "Unterkunft löschen",
"lodging_delete_confirm": "Sind Sie sicher, dass Sie diesen Standort löschen möchten? \nDiese Aktion kann nicht rückgängig gemacht werden.", "lodging_delete_confirm": "Sind Sie sicher, dass Sie diese Unterkunft löschen möchten? \nDies lässt sich nicht rückgängig machen!",
"lodging_information": "Unterkunftsinformationen", "lodging_information": "Informationen zur Unterkunft",
"price": "Preis", "price": "Preis",
"reservation_number": "Reservierungsnummer", "reservation_number": "Reservierungsnummer",
"welcome_map_info": "Öffentliche Abenteuer auf diesem Server", "welcome_map_info": "Frei zugängliche Abenteuer auf diesem Server",
"open_in_maps": "In Karten geöffnet" "open_in_maps": "In Karten öffnen"
}, },
"home": { "home": {
"desc_1": "Entdecken, planen und erkunden Sie mit Leichtigkeit", "desc_1": "Entdecken, planen und erkunden Sie mühelos",
"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.", "desc_2": "AdventureLog wurde entwickelt, um Ihre Reise zu vereinfachen und stellt Ihnen alle nötigen Werkzeuge und Ressourcen zur Verfügung, mit denen Sie Ihr nächstes unvergessliches Abenteuer planen, packen und erleben können.",
"feature_1": "Reisetagebuch", "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_1_desc": "Dokumentieren Sie Ihre Abenteuer mit einem persönlichen Reisetagebuch und teilen Sie Ihre Erlebnisse mit Freunden und Familie.",
"feature_2": "Reiseplanung", "feature_2": "Reiseplanung",
"feature_3": "Reisekarte", "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.", "feature_3_desc": "Betrachten Sie Ihre Reisen rund um die Welt auf einer interaktiven Karte und entdecken Sie neue Ziele.",
"go_to": "Gehen Sie zu AdventureLog", "go_to": "AdventureLog öffnen",
"hero_1": "Entdecken Sie die aufregendsten Abenteuer der Welt", "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.", "hero_2": "Entdecken und planen Sie Ihr nächstes Abenteuer mit AdventureLog. Erkunden Sie atemberaubende Reiseziele, erstellen Sie individuelle Reisepläne und bleiben Sie unterwegs stets verbunden.",
"key_features": "Hauptmerkmale", "key_features": "Hauptmerkmale",
"feature_2_desc": "Erstellen Sie ganz einfach individuelle Reiserouten und erhalten Sie eine tagesaktuelle Aufschlüsselung Ihrer Reise." "feature_2_desc": "Erstellen Sie mühelos individuelle Reisepläne und erhalten Sie eine detaillierte Tagesübersicht Ihrer Reise."
}, },
"navbar": { "navbar": {
"about": "Über AdventureLog", "about": "Über AdventureLog",
@ -277,7 +277,7 @@
"search": "Suchen", "search": "Suchen",
"settings": "Einstellungen", "settings": "Einstellungen",
"shared_with_me": "Mit mir geteilt", "shared_with_me": "Mit mir geteilt",
"theme_selection": "Themenauswahl", "theme_selection": "Design",
"themes": { "themes": {
"aqua": "Aqua", "aqua": "Aqua",
"dark": "Dunkel", "dark": "Dunkel",
@ -285,17 +285,17 @@
"light": "Hell", "light": "Hell",
"night": "Nacht", "night": "Nacht",
"aestheticDark": "Ästhetisches Dunkel", "aestheticDark": "Ästhetisches Dunkel",
"aestheticLight": "Ästhetisches Licht", "aestheticLight": "Ästhetisches Hell",
"northernLights": "Nordlicht" "northernLights": "Nordlichter"
}, },
"users": "Benutzer", "users": "Benutzer",
"worldtravel": "Weltreisen", "worldtravel": "Weltreisen",
"my_tags": "Meine Tags", "my_tags": "Meine Schlagworte",
"tag": "Etikett", "tag": "Schlagwort",
"language_selection": "Sprache", "language_selection": "Sprachauswahl",
"support": "Unterstützung", "support": "Unterstützung",
"calendar": "Kalender", "calendar": "Kalender",
"admin_panel": "Admin -Panel" "admin_panel": "Administration"
}, },
"auth": { "auth": {
"confirm_password": "Passwort bestätigen", "confirm_password": "Passwort bestätigen",
@ -304,7 +304,7 @@
"forgot_password": "Passwort vergessen?", "forgot_password": "Passwort vergessen?",
"last_name": "Nachname", "last_name": "Nachname",
"login": "Login", "login": "Login",
"login_error": "Anmeldung mit den angegebenen Anmeldeinformationen nicht möglich.", "login_error": "Die Anmeldung ist mit den angegebenen Anmeldeinformationen nicht möglich.",
"password": "Passwort", "password": "Passwort",
"registration_disabled": "Die Registrierung ist derzeit deaktiviert.", "registration_disabled": "Die Registrierung ist derzeit deaktiviert.",
"signup": "Melden Sie sich an", "signup": "Melden Sie sich an",
@ -323,7 +323,7 @@
"user_collections": "Benutzersammlungen" "user_collections": "Benutzersammlungen"
}, },
"users": { "users": {
"no_users_found": "Keine Benutzer mit öffentlichen Profilen gefunden." "no_users_found": "Keine Benutzer mit öffentlichem Profil gefunden."
}, },
"worldtravel": { "worldtravel": {
"all": "Alle", "all": "Alle",
@ -337,34 +337,34 @@
"partially_visited": "Teilweise besucht", "partially_visited": "Teilweise besucht",
"all_visited": "Sie haben alle Regionen besucht in", "all_visited": "Sie haben alle Regionen besucht in",
"cities": "Städte", "cities": "Städte",
"failed_to_mark_visit": "Der Besuch konnte nicht markiert werden", "failed_to_mark_visit": "Fehler beim Markieren des Besuchs von",
"failed_to_remove_visit": "Der Besuch von konnte nicht entfernt werden", "failed_to_remove_visit": "Fehler beim Entfernen des Besuchs",
"marked_visited": "als besucht markiert", "marked_visited": "als besucht markiert",
"no_cities_found": "Keine Städte gefunden", "no_cities_found": "Keine Städte gefunden",
"region_failed_visited": "Die Region konnte nicht als besucht markiert werden", "region_failed_visited": "Die Region konnte nicht als besucht markiert werden",
"region_stats": "Regionsstatistiken", "region_stats": "Regionsstatistiken",
"regions_in": "Regionen in", "regions_in": "Regionen in",
"removed": "ENTFERNT", "removed": "entfernt",
"view_cities": "Städte anzeigen", "view_cities": "Städte anzeigen",
"visit_remove_failed": "Der Besuch konnte nicht entfernt werden", "visit_remove_failed": "Der Besuch konnte nicht entfernt werden",
"visit_to": "Besuch bei" "visit_to": "Besuch von"
}, },
"settings": { "settings": {
"account_settings": "Benutzerkontoeinstellungen", "account_settings": "Benutzerkonto",
"current_email": "Aktuelle E-Mail", "current_email": "Bisherige E-Mail",
"email_change": "E-Mail ändern", "email_change": "E-Mail ändern",
"new_email": "Neue E-Mail", "new_email": "Neue E-Mail",
"new_password": "Neues Passwort", "new_password": "Neues Passwort",
"no_email_set": "Keine E-Mail-Adresse festgelegt", "no_email_set": "Keine E-Mail-Adresse festgelegt",
"password_change": "Kennwort ändern", "password_change": "Kennwort ändern",
"settings_page": "Einstellungsseite", "settings_page": "Einstellungen",
"update": "Aktualisieren", "update": "Aktualisieren",
"update_error": "Fehler beim Aktualisieren der Einstellungen", "update_error": "Fehler beim Aktualisieren der Einstellungen",
"update_success": "Einstellungen erfolgreich aktualisiert!", "update_success": "Einstellungen erfolgreich aktualisiert!",
"change_password": "Kennwort ändern", "change_password": "Kennwort ändern",
"confirm_new_password": "Bestätigen Sie das neue Passwort", "confirm_new_password": "Bestätigen Sie das neue Passwort",
"invalid_token": "Token ist ungültig oder abgelaufen", "invalid_token": "Das Token ist ungültig oder abgelaufen",
"login_redir": "Anschließend werden Sie zur Anmeldeseite weitergeleitet.", "login_redir": "Anschließend erfolgt eine Weiterleitung zur Anmeldeseite.",
"missing_email": "Bitte geben Sie eine E-Mail-Adresse ein", "missing_email": "Bitte geben Sie eine E-Mail-Adresse ein",
"password_does_not_match": "Passwörter stimmen nicht überein", "password_does_not_match": "Passwörter stimmen nicht überein",
"password_is_required": "Passwort ist erforderlich", "password_is_required": "Passwort ist erforderlich",
@ -375,74 +375,82 @@
"about_this_background": "Über diesen Hintergrund", "about_this_background": "Über diesen Hintergrund",
"join_discord": "Treten Sie dem Discord bei", "join_discord": "Treten Sie dem Discord bei",
"join_discord_desc": "um Ihre eigenen Fotos zu teilen. \nVeröffentlichen Sie sie im", "join_discord_desc": "um Ihre eigenen Fotos zu teilen. \nVeröffentlichen Sie sie im",
"photo_by": "Foto von", "photo_by": "Foto aufgenommen von",
"change_password_error": "Passwort kann nicht geändert werden. \nUngültiges aktuelles Passwort oder ungültiges neues Passwort.", "change_password_error": "Das Passwort kann nicht geändert werden. \nUngültiges aktuelles oder neues Passwort.",
"current_password": "Aktuelles Passwort", "current_password": "Aktuelles Passwort",
"password_change_lopout_warning": "Nach der Passwortänderung werden Sie abgemeldet.", "password_change_lopout_warning": "Nach der Passwortänderung werden Sie abgemeldet.",
"authenticator_code": "Authentifikatorcode", "authenticator_code": "Authentifizierungscode",
"copy": "Kopie", "copy": "Kopie",
"disable_mfa": "Deaktivieren Sie MFA", "disable_mfa": "Deaktivieren Sie MFA",
"email_added": "E-Mail erfolgreich hinzugefügt!", "email_added": "E-Mail hinzugefügt!",
"email_added_error": "Fehler beim Hinzufügen der E-Mail", "email_added_error": "Fehler beim Hinzufügen der E-Mail",
"email_removed": "E-Mail erfolgreich entfernt!", "email_removed": "E-Mail entfernt!",
"email_removed_error": "Fehler beim Entfernen der E-Mail", "email_removed_error": "Fehler beim Entfernen der E-Mail",
"email_set_primary": "E-Mail erfolgreich als primäre E-Mail-Adresse festgelegt!", "email_set_primary": "E-Mail als primäre E-Mail-Adresse festgelegt!",
"email_set_primary_error": "Fehler beim Festlegen der E-Mail-Adresse als primär", "email_set_primary_error": "Die E-Mail-Adresse konnte nicht als primäre Adresse festgelegt werden",
"email_verified": "E-Mail erfolgreich bestätigt!", "email_verified": "E-Mail bestätigt!",
"email_verified_erorr_desc": "Ihre E-Mail-Adresse konnte nicht bestätigt werden. \nBitte versuchen Sie es erneut.", "email_verified_erorr_desc": "Ihre E-Mail-Adresse konnte nicht bestätigt werden. \nBitte versuchen Sie es erneut.",
"email_verified_error": "Fehler bei der E-Mail-Bestätigung", "email_verified_error": "Fehler bei der Verifizierung der E-Mail-Adresse",
"email_verified_success": "Ihre E-Mail-Adresse wurde bestätigt. \nSie können sich jetzt anmelden.", "email_verified_success": "Ihre E-Mail-Adresse wurde bestätigt. \nSie können sich jetzt anmelden.",
"enable_mfa": "Aktivieren Sie MFA", "enable_mfa": "Aktivieren Sie MFA",
"error_change_password": "Fehler beim Ändern des Passworts. \nBitte überprüfen Sie Ihr aktuelles Passwort und versuchen Sie es erneut.", "error_change_password": "Fehler beim Ändern des Passworts. \nBitte überprüfen Sie Ihr aktuelles Passwort und versuchen Sie es erneut.",
"generic_error": "Bei der Bearbeitung Ihrer Anfrage ist ein Fehler aufgetreten.", "generic_error": "Bei der Bearbeitung Ihrer Anfrage ist ein Fehler aufgetreten.",
"invalid_code": "Ungültiger MFA-Code", "invalid_code": "Ungültiger MFA-Code",
"invalid_credentials": "Ungültiger Benutzername oder Passwort", "invalid_credentials": "Ungültiger Benutzername oder Passwort",
"make_primary": "Zum bevorzugten machen", "make_primary": "Als primär festlegen",
"mfa_disabled": "Multi-Faktor-Authentifizierung erfolgreich deaktiviert!", "mfa_disabled": "MFA-Authentifizierung deaktiviert!",
"mfa_enabled": "Multi-Faktor-Authentifizierung erfolgreich aktiviert!", "mfa_enabled": "MFA-Authentifizierung aktiviert!",
"mfa_not_enabled": "MFA ist nicht aktiviert", "mfa_not_enabled": "MFA nicht aktiviert",
"mfa_page_title": "Multi-Faktor-Authentifizierung", "mfa_page_title": "Multi-Faktor-Authentifizierung (MFA)",
"mfa_required": "Eine Multi-Faktor-Authentifizierung ist erforderlich", "mfa_required": "MFA erforderlich",
"no_emai_set": "Keine E-Mail-Adresse festgelegt", "no_emai_set": "Keine E-Mail-Adresse festgelegt",
"not_verified": "Nicht verifiziert", "not_verified": "Nicht verifiziert",
"primary": "Primär", "primary": "Primär",
"recovery_codes": "Wiederherstellungscodes", "recovery_codes": "Wiederherstellungscodes",
"recovery_codes_desc": "Dies sind Ihre Wiederherstellungscodes. \nBewahren Sie sie sicher auf. \nSie werden sie nicht mehr sehen können.", "recovery_codes_desc": "Dies sind Ihre Wiederherstellungscodes. \nBewahren Sie sie sicher auf. \nSie werden nicht erneut angezeigt.",
"reset_session_error": "Bitte melden Sie sich ab und wieder an, um Ihre Sitzung zu aktualisieren, und versuchen Sie es erneut.", "reset_session_error": "Bitte melden Sie sich ab und wieder an, um Ihre Sitzung zu aktualisieren, und versuchen Sie es erneut.",
"verified": "Verifiziert", "verified": "Verifiziert",
"verify": "Verifizieren", "verify": "Verifizieren",
"verify_email_error": "Fehler bei der E-Mail-Bestätigung. \nVersuchen Sie es in ein paar Minuten noch einmal.", "verify_email_error": "Fehler bei der E-Mail-Bestätigung. \nVersuchen Sie es in ein paar Minuten noch einmal.",
"verify_email_success": "E-Mail-Bestätigung erfolgreich gesendet!", "verify_email_success": "E-Mail-Bestätigung gesendet!",
"add_email_blocked": "Sie können keine E-Mail-Adresse zu einem Konto hinzufügen, das durch die Zwei-Faktor-Authentifizierung geschützt ist.", "add_email_blocked": "Sie können keine E-Mail-Adresse zu einem Konto hinzufügen, das durch die Zwei-Faktor-Authentifizierung (MFA) geschützt ist.",
"required": "Dieses Feld ist erforderlich", "required": "Dieses Feld ist erforderlich",
"csrf_failed": "CSRF-Token konnte nicht abgerufen werden", "csrf_failed": "Fehler beim Abrufen des CSRF-Tokens",
"duplicate_email": "Diese E-Mail-Adresse wird bereits verwendet.", "duplicate_email": "Diese E-Mail-Adresse wird bereits verwendet.",
"email_taken": "Diese E-Mail-Adresse wird bereits verwendet.", "email_taken": "Diese E-Mail-Adresse wird bereits verwendet.",
"username_taken": "Dieser Benutzername wird bereits verwendet.", "username_taken": "Dieser Benutzername wird bereits verwendet.",
"administration_settings": "Verwaltungseinstellungen", "administration_settings": "Administrationseinstellungen",
"documentation_link": "Dokumentationslink", "documentation_link": "Dokumentation",
"launch_account_connections": "Kontoverbindungen starten", "launch_account_connections": "Kontoverbindungen starten",
"launch_administration_panel": "Starten Sie das Administrationspanel", "launch_administration_panel": "Administrationseinstellungen öffnen",
"no_verified_email_warning": "Sie müssen über eine verifizierte E-Mail-Adresse verfügen, um die Zwei-Faktor-Authentifizierung zu aktivieren.", "no_verified_email_warning": "Sie müssen über eine verifizierte E-Mail-Adresse verfügen, um die Zwei-Faktor-Authentifizierung zu aktivieren.",
"social_auth_desc": "Aktivieren oder deaktivieren Sie soziale und OIDC-Authentifizierungsanbieter für Ihr Konto. \nMit diesen Verbindungen können Sie sich bei selbst gehosteten Authentifizierungsidentitätsanbietern wie Authentik oder Drittanbietern wie GitHub anmelden.", "social_auth_desc": "Aktivieren oder deaktivieren Sie soziale und OIDC-Authentifizierungsanbieter für Ihr Konto. \nMit diesen Verbindungen können Sie sich bei selbst gehosteten Authentifizierungsidentitätsanbietern wie Authentik oder Drittanbietern wie GitHub anmelden.",
"social_auth_desc_2": "Diese Einstellungen werden auf dem AdventureLog-Server verwaltet und müssen vom Administrator manuell aktiviert werden.", "social_auth_desc_2": "Diese Einstellungen werden auf dem AdventureLog-Server verwaltet und müssen vom Administrator manuell aktiviert werden.",
"social_oidc_auth": "Soziale und OIDC-Authentifizierung", "social_oidc_auth": "Soziale und OIDC-Authentifizierung",
"add_email": "E-Mail hinzufügen", "add_email": "E-Mail hinzufügen",
"password_too_short": "Das Passwort muss mindestens 6 Zeichen lang sein" "password_too_short": "Das Passwort muss mindestens 6 Zeichen lang sein",
"disable_password": "Passwort deaktivieren",
"password_disable": "Deaktivieren Sie die Passwortauthentifizierung",
"password_disable_desc": "Durch Deaktivieren der Kennwortauthentifizierung werden Sie daran hindern, sich mit einem Kennwort anzumelden. \nSie müssen einen sozialen oder OIDC-Anbieter verwenden, um sich anzumelden. Sollte Ihr sozialer Anbieter nicht verknüpft werden, wird die Kennwortauthentifizierung automatisch wieder aufgenommen, auch wenn diese Einstellung deaktiviert ist.",
"password_disable_warning": "Derzeit ist die Kennwortauthentifizierung deaktiviert. \nAnmelden Sie über einen sozialen oder OIDC -Anbieter erforderlich.",
"password_disabled": "Kennwortauthentifizierung deaktiviert",
"password_disabled_error": "Fehler beim Deaktivieren der Kennwortauthentifizierung. \nStellen Sie sicher, dass ein sozialer oder OIDC -Anbieter mit Ihrem Konto verknüpft ist.",
"password_enabled": "Kennwortauthentifizierung aktiviert",
"password_enabled_error": "Fehler zur Kennwortauthentifizierung."
}, },
"checklist": { "checklist": {
"add_item": "Artikel hinzufügen", "add_item": "Eintrag hinzufügen",
"checklist_delete_error": "Fehler beim Löschen der Checkliste", "checklist_delete_error": "Fehler beim Löschen der Checkliste",
"checklist_deleted": "Checkliste erfolgreich gelöscht!", "checklist_deleted": "Checkliste gelöscht!",
"checklist_editor": "Checklisten-Editor", "checklist_editor": "Checklisten-Editor",
"checklist_public": "Diese Checkliste ist öffentlich, da sie sich in einer öffentlichen Sammlung befindet.", "checklist_public": "Diese Checkliste ist öffentlich, da sie sich in einer öffentlichen Sammlung befindet.",
"editing_checklist": "Checkliste bearbeiten", "editing_checklist": "Checkliste bearbeiten",
"failed_to_save": "Checkliste konnte nicht gespeichert werden", "failed_to_save": "Checkliste konnte nicht gespeichert werden",
"item": "Artikel", "item": "Eintrag",
"item_already_exists": "Artikel existiert bereits", "item_already_exists": "Dieser Eintrag existiert bereits",
"item_cannot_be_empty": "Das Element darf nicht leer sein", "item_cannot_be_empty": "Der Eintrag darf nicht leer sein",
"items": "Artikel", "items": "Einträge",
"new_item": "Neuer Artikel", "new_item": "Neuer Eintrag",
"save": "Speichern", "save": "Speichern",
"checklist_viewer": "Checklisten-Viewer", "checklist_viewer": "Checklisten-Viewer",
"new_checklist": "Neue Checkliste" "new_checklist": "Neue Checkliste"
@ -454,7 +462,7 @@
"edit_collection": "Sammlung bearbeiten", "edit_collection": "Sammlung bearbeiten",
"error_creating_collection": "Fehler beim Erstellen der Sammlung", "error_creating_collection": "Fehler beim Erstellen der Sammlung",
"error_editing_collection": "Fehler beim Bearbeiten der Sammlung", "error_editing_collection": "Fehler beim Bearbeiten der Sammlung",
"new_collection": "Neue Kollektion", "new_collection": "Neue Sammlung",
"public_collection": "Öffentliche Sammlung" "public_collection": "Öffentliche Sammlung"
}, },
"notes": { "notes": {
@ -466,7 +474,7 @@
"note_deleted": "Notiz erfolgreich gelöscht!", "note_deleted": "Notiz erfolgreich gelöscht!",
"note_editor": "Notizeditor", "note_editor": "Notizeditor",
"note_public": "Diese Notiz ist öffentlich, da sie sich in einer öffentlichen Sammlung befindet.", "note_public": "Diese Notiz ist öffentlich, da sie sich in einer öffentlichen Sammlung befindet.",
"open": "Offen", "open": "Öffnen",
"save": "Speichern", "save": "Speichern",
"invalid_url": "Ungültige URL", "invalid_url": "Ungültige URL",
"note_viewer": "Notizenbetrachter" "note_viewer": "Notizenbetrachter"
@ -475,9 +483,9 @@
"date_and_time": "Datum", "date_and_time": "Datum",
"date_time": "Startdatum", "date_time": "Startdatum",
"edit": "Bearbeiten", "edit": "Bearbeiten",
"edit_transportation": "Transport bearbeiten", "edit_transportation": "Verkehrsmittel bearbeiten",
"end_date_time": "Enddatum", "end_date_time": "Enddatum",
"error_editing_transportation": "Fehler beim Bearbeiten des Transports", "error_editing_transportation": "Fehler beim Bearbeiten des Verkehrsmittels",
"flight_number": "Flugnummer", "flight_number": "Flugnummer",
"from_location": "Vom Standort", "from_location": "Vom Standort",
"modes": { "modes": {
@ -490,19 +498,19 @@
"plane": "Flugzeug", "plane": "Flugzeug",
"train": "Zug" "train": "Zug"
}, },
"transportation_added": "Transport erfolgreich hinzugefügt!", "transportation_added": "Verkehrsmittel erfolgreich hinzugefügt!",
"transportation_delete_error": "Fehler beim Löschen des Transports", "transportation_delete_error": "Fehler beim Löschen des Verkehrsmittels",
"transportation_deleted": "Transport erfolgreich gelöscht!", "transportation_deleted": "Verkehrsmittel erfolgreich gelöscht!",
"transportation_edit_success": "Transport erfolgreich bearbeitet!", "transportation_edit_success": "Verkehrsmittel erfolgreich bearbeitet!",
"type": "Typ", "type": "Typ",
"new_transportation": "Neue Transportmittel", "new_transportation": "Neues Verkehrsmittel",
"provide_start_date": "Bitte geben Sie ein Startdatum an", "provide_start_date": "Bitte geben Sie ein Startdatum an",
"start": "Start", "start": "Start",
"to_location": "Zum Standort", "to_location": "Zum Standort",
"transport_type": "Transporttyp", "transport_type": "Verkehrsmittel",
"ending_airport_desc": "Geben Sie den Ending Airport Code ein (z. B. lax)", "ending_airport_desc": "Geben Sie den Flughafencode des Zielflughafens ein (z. B. LAX)",
"fetch_location_information": "Standortinformationen abrufen", "fetch_location_information": "Standortinformationen abrufen",
"starting_airport_desc": "Geben Sie den Start -Flughafencode ein (z. B. JFK)" "starting_airport_desc": "Geben Sie den Flughafencode des Startflughafens ein (z. B. JFK)"
}, },
"search": { "search": {
"adventurelog_results": "AdventureLog-Ergebnisse", "adventurelog_results": "AdventureLog-Ergebnisse",
@ -522,14 +530,14 @@
"share": { "share": {
"no_users_shared": "Keine Benutzer geteilt mit", "no_users_shared": "Keine Benutzer geteilt mit",
"not_shared_with": "Nicht geteilt mit", "not_shared_with": "Nicht geteilt mit",
"share_desc": "Teilen Sie diese Sammlung mit anderen Benutzern.", "share_desc": "Sammlung mit anderen Benutzern teilen.",
"shared": "Geteilt", "shared": "Geteilt",
"shared_with": "Geteilt mit", "shared_with": "Geteilt mit",
"unshared": "Nicht geteilt", "unshared": "Nicht geteilt",
"with": "mit", "with": "mit",
"go_to_settings": "Gehen Sie zu den Einstellungen", "go_to_settings": "Gehe zu Einstellungen",
"no_shared_found": "Es wurden keine Sammlungen gefunden, die mit Ihnen geteilt wurden.", "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." "set_public": "Damit Benutzer Inhalte mit Ihnen teilen können, muss Ihr Profil auf „Öffentlich“ gesetzt sein."
}, },
"profile": { "profile": {
"member_since": "Mitglied seit", "member_since": "Mitglied seit",
@ -544,11 +552,11 @@
"icon": "Symbol", "icon": "Symbol",
"manage_categories": "Kategorien verwalten", "manage_categories": "Kategorien verwalten",
"no_categories_found": "Keine Kategorien gefunden.", "no_categories_found": "Keine Kategorien gefunden.",
"select_category": "Kategorie auswählen", "select_category": "Kategorie wählen",
"update_after_refresh": "Die Abenteuerkarten werden aktualisiert, sobald Sie die Seite aktualisieren." "update_after_refresh": "Die Abenteuerkarten werden aktualisiert, sobald Sie die Seite aktualisieren."
}, },
"dashboard": { "dashboard": {
"add_some": "Warum beginnen Sie nicht mit der Planung Ihres nächsten Abenteuers? \nSie können ein neues Abenteuer hinzufügen, indem Sie auf die Schaltfläche unten klicken.", "add_some": "Warum nicht gleich Ihr nächstes Abenteuer planen? Sie können ein neues Abenteuer hinzufügen, indem Sie auf den Button unten klicken.",
"countries_visited": "Besuchte Länder", "countries_visited": "Besuchte Länder",
"no_recent_adventures": "Keine aktuellen Abenteuer?", "no_recent_adventures": "Keine aktuellen Abenteuer?",
"recent_adventures": "Aktuelle Abenteuer", "recent_adventures": "Aktuelle Abenteuer",
@ -565,21 +573,21 @@
"imageid_required": "Bild-ID ist erforderlich", "imageid_required": "Bild-ID ist erforderlich",
"immich": "Immich", "immich": "Immich",
"immich_desc": "Integrieren Sie Ihr Immich-Konto mit AdventureLog, damit Sie Ihre Fotobibliothek durchsuchen und Fotos für Ihre Abenteuer importieren können.", "immich_desc": "Integrieren Sie Ihr Immich-Konto mit AdventureLog, damit Sie Ihre Fotobibliothek durchsuchen und Fotos für Ihre Abenteuer importieren können.",
"immich_disabled": "Immich-Integration erfolgreich deaktiviert!", "immich_disabled": "Immich-Integration deaktiviert!",
"immich_enabled": "Immich-Integration erfolgreich aktiviert!", "immich_enabled": "Immich-Integration aktiviert!",
"immich_error": "Fehler beim Aktualisieren der Immich-Integration", "immich_error": "Fehler beim aktualisieren der Immich-Integration",
"immich_updated": "Immich-Einstellungen erfolgreich aktualisiert!", "immich_updated": "Immich-Einstellungen erfolgreich aktualisiert!",
"integration_enabled": "Integration aktiviert", "integration_enabled": "Integration aktiviert",
"integration_fetch_error": "Fehler beim Abrufen der Daten aus der Immich-Integration", "integration_fetch_error": "Fehler beim Abrufen der Daten aus der Immich-Integration",
"integration_missing": "Im Backend fehlt die Immich-Integration", "integration_missing": "Im Backend fehlt die Immich-Integration",
"load_more": "Mehr laden", "load_more": "Mehr laden",
"no_items_found": "Keine Artikel gefunden", "no_items_found": "Keine Artikel gefunden",
"query_required": "Abfrage ist erforderlich", "query_required": "Bitte geben Sie eine Suchanfrage ein",
"server_down": "Der Immich-Server ist derzeit ausgefallen oder nicht erreichbar", "server_down": "Der Immich-Server ist derzeit nicht erreichbar",
"server_url": "Immich-Server-URL", "server_url": "Immich-Server-URL",
"update_integration": "Update-Integration", "update_integration": "Integration updaten",
"immich_integration": "Immich-Integration", "immich_integration": "Immich-Integration",
"documentation": "Immich-Integrationsdokumentation", "documentation": "Dokumentation zur Immich-Integration",
"localhost_note": "Hinweis: localhost wird höchstwahrscheinlich nicht funktionieren, es sei denn, Sie haben Docker-Netzwerke entsprechend eingerichtet. \nEs wird empfohlen, die IP-Adresse des Servers oder den Domänennamen zu verwenden." "localhost_note": "Hinweis: localhost wird höchstwahrscheinlich nicht funktionieren, es sei denn, Sie haben Docker-Netzwerke entsprechend eingerichtet. \nEs wird empfohlen, die IP-Adresse des Servers oder den Domänennamen zu verwenden."
}, },
"recomendations": { "recomendations": {
@ -591,29 +599,29 @@
}, },
"lodging": { "lodging": {
"apartment": "Wohnung", "apartment": "Wohnung",
"bnb": "Übernachtung mit Frühstück", "bnb": "Bed & Breakfast",
"cabin": "Kabine", "cabin": "Hütte",
"campground": "Campingplatz", "campground": "Campingplatz",
"check_in": "Einchecken", "check_in": "Check-in",
"check_out": "Kasse", "check_out": "Check-out",
"date_and_time": "Datum", "date_and_time": "Datum und Uhrzeit",
"edit": "Bearbeiten", "edit": "Bearbeiten",
"edit_lodging": "Unterkunft bearbeiten", "edit_lodging": "Unterkunft bearbeiten",
"error_editing_lodging": "Fehlerbearbeitung", "error_editing_lodging": "Fehler beim Bearbeiten der Unterkunft",
"hostel": "Herberge", "hostel": "Hostel",
"hotel": "Hotel", "hotel": "Hotel",
"house": "Haus", "house": "Haus",
"lodging_added": "Unterkunft erfolgreich hinzugefügt!", "lodging_added": "Unterkunft erfolgreich hinzugefügt!",
"lodging_delete_error": "Fehler beim Löschen von Unterkünften", "lodging_delete_error": "Fehler beim Löschen der Unterkunft",
"lodging_deleted": "Unterkunft erfolgreich gelöscht!", "lodging_deleted": "Unterkunft gelöscht!",
"lodging_edit_success": "Unterbringung erfolgreich bearbeitet!", "lodging_edit_success": "Unterbringung bearbeitet!",
"lodging_type": "Unterkunftstyp", "lodging_type": "Unterkunftstyp",
"motel": "Motel", "motel": "Motel",
"new_lodging": "Neue Unterkunft", "new_lodging": "Neue Unterkunft",
"other": "Andere", "other": "Sonstige",
"provide_start_date": "Bitte geben Sie einen Startdatum an", "provide_start_date": "Bitte geben Sie einen Startdatum an",
"reservation_number": "Reservierungsnummer", "reservation_number": "Reservierungsnummer",
"resort": "Resort", "resort": "Ferienanlage",
"start": "Start", "start": "Start",
"type": "Typ", "type": "Typ",
"villa": "Villa", "villa": "Villa",

View file

@ -203,7 +203,7 @@
"category_fetch_error": "Error fetching categories", "category_fetch_error": "Error fetching categories",
"new_adventure": "New Adventure", "new_adventure": "New Adventure",
"basic_information": "Basic Information", "basic_information": "Basic Information",
"no_adventures_to_recommendations": "No adventures found. Add at leat one adventure to get recommendations.", "no_adventures_to_recommendations": "No adventures found. Add at least one adventure to get recommendations.",
"display_name": "Display Name", "display_name": "Display Name",
"adventure_not_found": "There are no adventures to display. Add some using the plus button at the bottom right or try changing filters!", "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", "no_adventures_found": "No adventures found",
@ -428,7 +428,15 @@
"documentation_link": "Documentation Link", "documentation_link": "Documentation Link",
"launch_account_connections": "Launch Account Connections", "launch_account_connections": "Launch Account Connections",
"password_too_short": "Password must be at least 6 characters", "password_too_short": "Password must be at least 6 characters",
"add_email": "Add Email" "add_email": "Add Email",
"password_disable": "Disable Password Authentication",
"password_disable_desc": "Disabling password authentication will prevent you from logging in with a password. You will need to use a social or OIDC provider to log in. Should your social provider be unlinked, password authentication will be automatically re-enabled even if this setting is disabled.",
"disable_password": "Disable Password",
"password_enabled": "Password authentication enabled",
"password_disabled": "Password authentication disabled",
"password_disable_warning": "Currently, password authentication is disabled. Login via a social or OIDC provider is required.",
"password_disabled_error": "Error disabling password authentication. Make sure a social or OIDC provider is linked to your account.",
"password_enabled_error": "Error enabling password authentication."
}, },
"collection": { "collection": {
"collection_created": "Collection created successfully!", "collection_created": "Collection created successfully!",

View file

@ -428,7 +428,15 @@
"social_auth_desc_2": "Estas configuraciones se administran en el servidor AdventureLog y el administrador debe habilitarlas manualmente.", "social_auth_desc_2": "Estas configuraciones se administran en el servidor AdventureLog y el administrador debe habilitarlas manualmente.",
"social_oidc_auth": "Autenticación social y OIDC", "social_oidc_auth": "Autenticación social y OIDC",
"add_email": "Agregar correo electrónico", "add_email": "Agregar correo electrónico",
"password_too_short": "La contraseña debe tener al menos 6 caracteres." "password_too_short": "La contraseña debe tener al menos 6 caracteres.",
"disable_password": "Desactivar contraseña",
"password_disable": "Desactivar la autenticación de contraseña",
"password_disable_desc": "Desactivar la autenticación de contraseña le impedirá iniciar sesión con una contraseña. \nDeberá utilizar un proveedor social o OIDC para iniciar sesión. Si su proveedor social no está vinculado, la autenticación de contraseña se volverá a habilitar automáticamente incluso si esta configuración está deshabilitada.",
"password_disable_warning": "Actualmente, la autenticación de contraseña está deshabilitada. \nIniciar sesión a través de un proveedor social o OIDC se requiere.",
"password_disabled": "Autenticación de contraseña deshabilitada",
"password_disabled_error": "Error a deshabilitar la autenticación de contraseña. \nAsegúrese de que un proveedor social o OIDC esté vinculado a su cuenta.",
"password_enabled": "Autenticación de contraseña habilitada",
"password_enabled_error": "Error al habilitar la autenticación de contraseña."
}, },
"checklist": { "checklist": {
"add_item": "Agregar artículo", "add_item": "Agregar artículo",

View file

@ -428,7 +428,15 @@
"social_auth_desc_2": "Ces paramètres sont gérés sur le serveur AdventureLog et doivent être activés manuellement par l'administrateur.", "social_auth_desc_2": "Ces paramètres sont gérés sur le serveur AdventureLog et doivent être activés manuellement par l'administrateur.",
"social_oidc_auth": "Authentification sociale et OIDC", "social_oidc_auth": "Authentification sociale et OIDC",
"add_email": "Ajouter un e-mail", "add_email": "Ajouter un e-mail",
"password_too_short": "Le mot de passe doit contenir au moins 6 caractères" "password_too_short": "Le mot de passe doit contenir au moins 6 caractères",
"disable_password": "Désactiver le mot de passe",
"password_disable": "Désactiver l'authentification du mot de passe",
"password_disable_desc": "La désactivation de l'authentification du mot de passe vous empêchera de vous connecter avec un mot de passe. \nVous devrez utiliser un fournisseur social ou OIDC pour vous connecter. Si votre fournisseur social est non lié, l'authentification du mot de passe sera automatiquement réactivé même si ce paramètre est désactivé.",
"password_disable_warning": "Actuellement, l'authentification du mot de passe est désactivée. \nLa connexion via un fournisseur social ou OIDC est requise.",
"password_disabled": "Authentification du mot de passe désactivé",
"password_disabled_error": "Erreur de désactivation de l'authentification du mot de passe. \nAssurez-vous qu'un fournisseur social ou OIDC est lié à votre compte.",
"password_enabled": "Authentification du mot de passe activé",
"password_enabled_error": "Erreur permettant l'authentification du mot de passe."
}, },
"checklist": { "checklist": {
"add_item": "Ajouter un article", "add_item": "Ajouter un article",

View file

@ -428,7 +428,15 @@
"social_auth_desc_2": "Queste impostazioni sono gestite nel server AdventureLog e devono essere abilitate manualmente dall'amministratore.", "social_auth_desc_2": "Queste impostazioni sono gestite nel server AdventureLog e devono essere abilitate manualmente dall'amministratore.",
"social_oidc_auth": "Autenticazione sociale e OIDC", "social_oidc_auth": "Autenticazione sociale e OIDC",
"add_email": "Aggiungi e-mail", "add_email": "Aggiungi e-mail",
"password_too_short": "La password deve contenere almeno 6 caratteri" "password_too_short": "La password deve contenere almeno 6 caratteri",
"disable_password": "Disabilita la password",
"password_disable": "Disabilita l'autenticazione della password",
"password_disable_desc": "La disabilitazione dell'autenticazione della password ti impedirà di accedere con una password. \nDovrai utilizzare un provider di social o OIDC per accedere. Se il tuo fornitore social non fosse collegato, l'autenticazione password verrà riabilitata automaticamente anche se questa impostazione è disabilitata.",
"password_disable_warning": "Attualmente, l'autenticazione della password è disabilitata. \nÈ richiesto l'accesso tramite un fornitore sociale o OIDC.",
"password_disabled": "Autenticazione password disabilitata",
"password_disabled_error": "Errore di disabilitazione dell'autenticazione della password. \nAssicurati che un fornitore sociale o OIDC sia collegato al tuo account.",
"password_enabled": "Autenticazione password abilitata",
"password_enabled_error": "Errore che abilita l'autenticazione della password."
}, },
"checklist": { "checklist": {
"add_item": "Aggiungi articolo", "add_item": "Aggiungi articolo",

View file

@ -514,7 +514,15 @@
"documentation_link": "문서화 링크", "documentation_link": "문서화 링크",
"email_set_primary": "기본 이메일 세트로 설정했습니다!", "email_set_primary": "기본 이메일 세트로 설정했습니다!",
"email_set_primary_error": "기본 이메일 설정 오류", "email_set_primary_error": "기본 이메일 설정 오류",
"email_taken": "이 이메일 주소는 이미 사용 중입니다." "email_taken": "이 이메일 주소는 이미 사용 중입니다.",
"disable_password": "비밀번호를 비활성화합니다",
"password_disable": "비밀번호 인증을 비활성화합니다",
"password_disable_desc": "비밀번호 인증을 비활성화하면 비밀번호로 로그인하지 못하게됩니다. \n소셜 또는 OIDC 제공 업체를 사용하여 로그인해야합니다. 소셜 제공 업체가 끊어지지 않으면이 설정이 비활성화 된 경우에도 비밀번호 인증이 자동으로 다시 활성화됩니다.",
"password_disable_warning": "현재 비밀번호 인증이 비활성화되어 있습니다. \n소셜 또는 OIDC 제공 업체를 통해 로그인해야합니다.",
"password_disabled": "비밀번호 인증 비활성화",
"password_disabled_error": "비밀번호 인증 오류. \n소셜 또는 OIDC 제공 업체가 귀하의 계정에 연결되어 있는지 확인하십시오.",
"password_enabled": "비밀번호 인증 활성화",
"password_enabled_error": "비밀번호 인증을 활성화하는 오류."
}, },
"share": { "share": {
"go_to_settings": "설정으로 이동", "go_to_settings": "설정으로 이동",

View file

@ -428,7 +428,15 @@
"social_auth_desc_2": "Deze instellingen worden beheerd op de AdventureLog-server en moeten handmatig worden ingeschakeld door de beheerder.", "social_auth_desc_2": "Deze instellingen worden beheerd op de AdventureLog-server en moeten handmatig worden ingeschakeld door de beheerder.",
"social_oidc_auth": "Sociale en OIDC-authenticatie", "social_oidc_auth": "Sociale en OIDC-authenticatie",
"add_email": "E-mail toevoegen", "add_email": "E-mail toevoegen",
"password_too_short": "Wachtwoord moet minimaal 6 tekens lang zijn" "password_too_short": "Wachtwoord moet minimaal 6 tekens lang zijn",
"disable_password": "Schakel het wachtwoord uit",
"password_disable": "Schakel wachtwoordverificatie uit",
"password_disable_desc": "Het uitschakelen van wachtwoordverificatie zal voorkomen dat u zich aanmeldt met een wachtwoord. \nU moet een sociale of OIDC-provider gebruiken om in te loggen. Als uw sociale provider niet wordt gekoppeld, wordt wachtwoordverificatie automatisch opnieuw ingeschakeld, zelfs als deze instelling is uitgeschakeld.",
"password_disable_warning": "Momenteel is wachtwoordverificatie uitgeschakeld. \nLogin via een sociale of OIDC -provider is vereist.",
"password_disabled": "Wachtwoordverificatie uitgeschakeld",
"password_disabled_error": "Fout het uitschakelen van wachtwoordverificatie. \nZorg ervoor dat een sociale of OIDC -provider is gekoppeld aan uw account.",
"password_enabled": "Wachtwoordverificatie ingeschakeld",
"password_enabled_error": "Fout bij het inschakelen van wachtwoordverificatie."
}, },
"checklist": { "checklist": {
"add_item": "Artikel toevoegen", "add_item": "Artikel toevoegen",

View file

@ -428,7 +428,15 @@
"social_auth_desc_2": "Ustawienia te są zarządzane na serwerze AdventureLog i muszą zostać włączone ręcznie przez administratora.", "social_auth_desc_2": "Ustawienia te są zarządzane na serwerze AdventureLog i muszą zostać włączone ręcznie przez administratora.",
"social_oidc_auth": "Uwierzytelnianie społecznościowe i OIDC", "social_oidc_auth": "Uwierzytelnianie społecznościowe i OIDC",
"add_email": "Dodaj e-mail", "add_email": "Dodaj e-mail",
"password_too_short": "Hasło musi mieć co najmniej 6 znaków" "password_too_short": "Hasło musi mieć co najmniej 6 znaków",
"disable_password": "Wyłącz hasło",
"password_disable": "Wyłącz uwierzytelnianie hasła",
"password_disable_desc": "Wyłączenie uwierzytelniania hasła uniemożliwia logowanie hasłem. \nAby się zalogować, będziesz musiał użyć dostawcy społeczności lub OIDC. Jeśli dostawca społecznościowy zostanie niezbędny, uwierzytelnianie hasła zostanie automatycznie ponowne odnowione, nawet jeśli to ustawienie zostanie wyłączone.",
"password_disable_warning": "Obecnie uwierzytelnianie hasła jest wyłączone. \nWymagane jest zalogowanie się za pośrednictwem dostawcy społeczności lub OIDC.",
"password_disabled": "Uwierzytelnianie hasła wyłączone",
"password_disabled_error": "Błąd wyłączający uwierzytelnianie hasła. \nUpewnij się, że dostawca społecznościowy lub OIDC jest powiązany z Twoim kontem.",
"password_enabled": "Włączone uwierzytelnianie hasła",
"password_enabled_error": "Błąd umożliwiający uwierzytelnianie hasła."
}, },
"collection": { "collection": {
"collection_created": "Kolekcja została pomyślnie utworzona!", "collection_created": "Kolekcja została pomyślnie utworzona!",

View file

@ -428,7 +428,15 @@
"social_auth_desc_2": "Dessa inställningar hanteras i AdventureLog-servern och måste aktiveras manuellt av administratören.", "social_auth_desc_2": "Dessa inställningar hanteras i AdventureLog-servern och måste aktiveras manuellt av administratören.",
"social_oidc_auth": "Social och OIDC-autentisering", "social_oidc_auth": "Social och OIDC-autentisering",
"add_email": "Lägg till e-post", "add_email": "Lägg till e-post",
"password_too_short": "Lösenordet måste bestå av minst 6 tecken" "password_too_short": "Lösenordet måste bestå av minst 6 tecken",
"disable_password": "Inaktivera lösenord",
"password_disable": "Inaktivera lösenordsautentisering",
"password_disable_desc": "Inaktivering av lösenordsautentisering förhindrar dig att logga in med ett lösenord. \nDu måste använda en social eller OIDC-leverantör för att logga in. Om din sociala leverantör lossnar kommer lösenordsautentisering automatiskt att aktiveras även om den här inställningen är inaktiverad.",
"password_disable_warning": "För närvarande är lösenordsautentisering inaktiverad. \nLogga in via en social eller OIDC -leverantör krävs.",
"password_disabled": "Lösenordsautentisering inaktiverad",
"password_disabled_error": "Fel Inaktivera lösenordsautentisering. \nSe till att en social eller OIDC -leverantör är länkad till ditt konto.",
"password_enabled": "Lösenordsautentisering aktiverad",
"password_enabled_error": "Fel som aktiverar lösenordsautentisering."
}, },
"checklist": { "checklist": {
"add_item": "Lägg till objekt", "add_item": "Lägg till objekt",

View file

@ -1,37 +1,37 @@
{ {
"navbar": { "navbar": {
"about": "关于冒险日志",
"adventures": "冒险", "adventures": "冒险",
"collections": "合集", "collections": "收藏",
"worldtravel": "环游世界", "discord": "不和谐",
"map": "地图", "documentation": "文档",
"users": "用户",
"search": "搜索",
"profile": "个人资料",
"greeting": "你好", "greeting": "你好",
"logout": "退出",
"map": "地图",
"my_adventures": "我的冒险", "my_adventures": "我的冒险",
"profile": "轮廓",
"search": "搜索",
"settings": "设置",
"shared_with_me": "与我分享",
"theme_selection": "主题选择",
"themes": {
"aqua": "阿夸",
"dark": "黑暗的",
"forest": "森林",
"light": "光",
"night": "夜晚",
"aestheticDark": "审美黑暗",
"aestheticLight": "美学之光",
"northernLights": "北极光"
},
"users": "用户",
"worldtravel": "环球旅行",
"my_tags": "我的标签", "my_tags": "我的标签",
"tag": "标签", "tag": "标签",
"shared_with_me": "与我共享",
"settings": "设置",
"logout": "登出",
"about": "关于AdventureLog",
"documentation": "文档",
"discord": "Discord",
"language_selection": "语言", "language_selection": "语言",
"support": "支持", "support": "支持",
"calendar": "日历", "calendar": "日历",
"theme_selection": "主题选择", "admin_panel": "管理面板"
"admin_panel": "管理面板",
"themes": {
"light": "明亮",
"dark": "黑暗",
"night": "夜晚",
"forest": "森林",
"aestheticLight": "美学明亮",
"aestheticDark": "美学黑暗",
"aqua": "水蓝",
"northernLights": "北极光"
}
}, },
"about": { "about": {
"about": "关于", "about": "关于",
@ -45,18 +45,18 @@
"close": "关闭" "close": "关闭"
}, },
"home": { "home": {
"hero_1": "发现世界上最刺激的冒险",
"hero_2": "使用 AdventureLog 发现并计划您的下一个冒险。\n探索令人叹为观止的目的地创建自定义行程并随时随地保持联系。",
"go_to": "前往AdventureLog",
"key_features": "主要功能",
"desc_1": "轻松发现、规划和探索", "desc_1": "轻松发现、规划和探索",
"desc_2": "AdventureLog 旨在简化您的旅程,为您提供计划、打包和导航下一个难忘冒险的工具和资源。", "desc_2": "AdventureLog 旨在简化您的旅程,为您提供工具和资源来计划、打包和导航您的下一次难忘的冒险。",
"feature_1": "旅行日志", "feature_1": "旅行日志",
"feature_1_desc": "通过个性化的旅行日志记录你的冒险,并与朋友和家人分享你的经历。", "feature_1_desc": "通过个性化的旅行日志记录您的冒险经历,并与朋友和家人分享您的经历。",
"feature_2": "行程规划", "feature_2": "旅行计划",
"feature_2_desc": "轻松创建自定义行程,并获取每日行程明细。", "feature_2_desc": "轻松创建自定义行程并获取行程的每日详细信息。",
"feature_3": "旅行地图", "feature_3": "旅游地图",
"feature_3_desc": "通过交互式地图查看你在世界各地的旅行,并探索新的目的地。" "feature_3_desc": "使用交互式地图查看您在世界各地的旅行并探索新的目的地。",
"go_to": "前往冒险日志",
"hero_1": "探索世界上最惊险的冒险",
"hero_2": "使用 AdventureLog 发现并计划您的下一次冒险。\n探索令人惊叹的目的地、创建定制行程并随时随地保持联系。",
"key_features": "主要特点"
}, },
"adventures": { "adventures": {
"collection_remove_success": "成功从合集中移除冒险!", "collection_remove_success": "成功从合集中移除冒险!",
@ -266,7 +266,7 @@
"attachment_update_success": "附件更新成功!", "attachment_update_success": "附件更新成功!",
"attachment_name": "附件名称", "attachment_name": "附件名称",
"gpx_tip": "上传 GPX 文件到附件以便在地图上查看它们!", "gpx_tip": "上传 GPX 文件到附件以便在地图上查看它们!",
"welcome_map_info": "此服务器上的公开冒险", "welcome_map_info": "该服务器上的公共冒险",
"attachment_update_error": "更新附件时出错", "attachment_update_error": "更新附件时出错",
"activities": { "activities": {
"general": "通用 🌍", "general": "通用 🌍",
@ -294,108 +294,9 @@
}, },
"lodging_information": "住宿信息", "lodging_information": "住宿信息",
"price": "价格", "price": "价格",
"region": "地区",
"reservation_number": "预订号", "reservation_number": "预订号",
"welcome_map_info": "该服务器上的公共冒险",
"open_in_maps": "在地图上打开" "open_in_maps": "在地图上打开"
}, },
"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": {
"aqua": "阿夸",
"dark": "黑暗的",
"forest": "森林",
"light": "光",
"night": "夜晚",
"aestheticDark": "审美黑暗",
"aestheticLight": "美学之光",
"northernLights": "北极光"
},
"users": "用户",
"worldtravel": "环球旅行",
"my_tags": "我的标签",
"tag": "标签",
"language_selection": "语言",
"support": "支持",
"calendar": "日历",
"admin_panel": "管理面板"
},
"auth": {
"forgot_password": "忘记密码?",
"login": "登录",
"login_error": "无法使用提供的凭据登录。",
"password": "密码",
"signup": "报名",
"username": "用户名",
"confirm_password": "确认密码",
"email": "电子邮件",
"first_name": "名",
"last_name": "姓",
"registration_disabled": "目前已禁用注册。",
"profile_picture": "个人资料图片",
"public_profile": "公开资料",
"public_tooltip": "通过公开个人资料,用户可以与您共享收藏并在用户页面上查看您的个人资料。",
"email_required": "电子邮件为必填项",
"both_passwords_required": "两个密码都需要",
"new_password": "新密码",
"reset_failed": "重置密码失败",
"or_3rd_party": "或者使用第三方服务登录",
"no_public_adventures": "找不到公共冒险",
"no_public_collections": "找不到公共收藏",
"user_adventures": "用户冒险",
"user_collections": "用户收集"
},
"worldtravel": {
"all": "全部",
"all_subregions": "所有次区域",
"clear_search": "清除搜索",
"completely_visited": "已完全访问",
"country_list": "国家列表",
"no_countries_found": "没有找到国家",
"not_visited": "未访问过",
"num_countries": "找到的国家",
"partially_visited": "部分访问",
"all_visited": "您已访问过所有地区",
"cities": "城市",
"failed_to_mark_visit": "无法标记访问",
"failed_to_remove_visit": "无法删除对的访问",
"marked_visited": "标记为已访问",
"no_cities_found": "没有找到城市",
"region_failed_visited": "无法将区域标记为已访问",
"region_stats": "地区统计",
"regions_in": "地区位于",
"removed": "已删除",
"view_cities": "查看城市",
"visit_remove_failed": "删除访问失败",
"visit_to": "参观"
},
"auth": { "auth": {
"forgot_password": "忘记密码?", "forgot_password": "忘记密码?",
"login": "登录", "login": "登录",
@ -421,6 +322,30 @@
"user_adventures": "用户冒险", "user_adventures": "用户冒险",
"user_collections": "用户合集" "user_collections": "用户合集"
}, },
"worldtravel": {
"all": "全部",
"all_subregions": "所有次区域",
"clear_search": "清除搜索",
"completely_visited": "已完全访问",
"country_list": "国家列表",
"no_countries_found": "没有找到国家",
"not_visited": "未访问过",
"num_countries": "找到的国家",
"partially_visited": "部分访问",
"all_visited": "您已访问过所有地区",
"cities": "城市",
"failed_to_mark_visit": "无法标记访问",
"failed_to_remove_visit": "无法删除对的访问",
"marked_visited": "标记为已访问",
"no_cities_found": "没有找到城市",
"region_failed_visited": "无法将区域标记为已访问",
"region_stats": "地区统计",
"regions_in": "地区位于",
"removed": "已删除",
"view_cities": "查看城市",
"visit_remove_failed": "删除访问失败",
"visit_to": "参观"
},
"users": { "users": {
"no_users_found": "未找到已公开个人资料的用户。" "no_users_found": "未找到已公开个人资料的用户。"
}, },
@ -503,7 +428,15 @@
"social_auth_desc_2": "这些设置在 AdventureLog 服务器中进行管理,并且必须由管理员手动启用。", "social_auth_desc_2": "这些设置在 AdventureLog 服务器中进行管理,并且必须由管理员手动启用。",
"social_oidc_auth": "社交与 OIDC 认证", "social_oidc_auth": "社交与 OIDC 认证",
"add_email": "添加邮箱", "add_email": "添加邮箱",
"password_too_short": "密码必须至少为 6 个字符" "password_too_short": "密码必须至少为 6 个字符",
"disable_password": "禁用密码",
"password_disable": "禁用密码身份验证",
"password_disable_desc": "禁用密码身份验证将阻止您使用密码登录。\n您将需要使用社交或OIDC提供商登录。如果您的社交提供商未链接即使禁用了此设置密码身份验证也将自动重新启用。",
"password_disable_warning": "当前,密码身份验证已禁用。\n需要通过社交或OIDC提供商登录。",
"password_disabled": "密码身份验证禁用",
"password_disabled_error": "错误禁用密码身份验证。\n确保将社交或OIDC提供商链接到您的帐户。",
"password_enabled": "启用密码身份验证",
"password_enabled_error": "启用密码身份验证的错误。"
}, },
"checklist": { "checklist": {
"add_item": "添加项目", "add_item": "添加项目",
@ -694,4 +627,4 @@
"resort": "度假村", "resort": "度假村",
"current_timezone": "当前时区" "current_timezone": "当前时区"
} }
} }

View file

@ -7,7 +7,7 @@
import { DefaultMarker, MapLibre, Popup, GeoJSON, LineLayer } from 'svelte-maplibre'; import { DefaultMarker, MapLibre, Popup, GeoJSON, LineLayer } from 'svelte-maplibre';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import { marked } from 'marked'; // Import the markdown parser import { marked } from 'marked'; // Import the markdown parser
import DOMPurify from 'dompurify';
// @ts-ignore // @ts-ignore
import toGeoJSON from '@mapbox/togeojson'; import toGeoJSON from '@mapbox/togeojson';
@ -16,7 +16,7 @@
let geojson: any; let geojson: any;
const renderMarkdown = (markdown: string) => { const renderMarkdown = (markdown: string) => {
return marked(markdown); return marked(markdown) as string;
}; };
async function getGpxFiles() { async function getGpxFiles() {
@ -369,7 +369,7 @@
<article <article
class="prose overflow-auto h-full max-w-full p-4 border border-base-300 rounded-lg" class="prose overflow-auto h-full max-w-full p-4 border border-base-300 rounded-lg"
> >
{@html renderMarkdown(adventure.description)} {@html DOMPurify.sanitize(renderMarkdown(adventure.description))}
</article> </article>
{/if} {/if}
</div> </div>

View file

@ -6,7 +6,7 @@ import { json } from '@sveltejs/kit';
/** @type {import('./$types').RequestHandler} */ /** @type {import('./$types').RequestHandler} */
export async function GET(event) { export async function GET(event) {
const { url, params, request, fetch, cookies } = event; const { url, params, request, fetch, cookies } = event;
const searchParam = url.search ? `${url.search}&format=json` : '?format=json'; const searchParam = url.search ? `${url.search}` : '';
return handleRequest(url, params, request, fetch, cookies, searchParam); return handleRequest(url, params, request, fetch, cookies, searchParam);
} }
@ -17,13 +17,13 @@ export async function POST({ url, params, request, fetch, cookies }) {
} }
export async function PATCH({ url, params, request, fetch, cookies }) { export async function PATCH({ url, params, request, fetch, cookies }) {
const searchParam = url.search ? `${url.search}&format=json` : '?format=json'; const searchParam = url.search ? `${url.search}` : '';
return handleRequest(url, params, request, fetch, cookies, searchParam, true); return handleRequest(url, params, request, fetch, cookies, searchParam, false);
} }
export async function PUT({ url, params, request, fetch, cookies }) { export async function PUT({ url, params, request, fetch, cookies }) {
const searchParam = url.search ? `${url.search}&format=json` : '?format=json'; const searchParam = url.search ? `${url.search}` : '';
return handleRequest(url, params, request, fetch, cookies, searchParam, true); return handleRequest(url, params, request, fetch, cookies, searchParam, false);
} }
export async function DELETE({ url, params, request, fetch, cookies }) { export async function DELETE({ url, params, request, fetch, cookies }) {
@ -43,13 +43,15 @@ async function handleRequest(
const path = params.path; const path = params.path;
let targetUrl = `${endpoint}/auth/${path}`; let targetUrl = `${endpoint}/auth/${path}`;
const add_trailing_slash_list = ['disable-password'];
// Ensure the path ends with a trailing slash // Ensure the path ends with a trailing slash
if (requreTrailingSlash && !targetUrl.endsWith('/')) { if ((requreTrailingSlash && !targetUrl.endsWith('/')) || add_trailing_slash_list.includes(path)) {
targetUrl += '/'; targetUrl += '/';
} }
// Append query parameters to the path correctly // Append query parameters to the path correctly
targetUrl += searchParam; // This will add ?format=json or &format=json to the URL targetUrl += searchParam; // This will add or to the URL
const headers = new Headers(request.headers); const headers = new Headers(request.headers);

View file

@ -17,7 +17,7 @@
import AdventureCard from '$lib/components/AdventureCard.svelte'; import AdventureCard from '$lib/components/AdventureCard.svelte';
import AdventureLink from '$lib/components/AdventureLink.svelte'; import AdventureLink from '$lib/components/AdventureLink.svelte';
import NotFound from '$lib/components/NotFound.svelte'; import NotFound from '$lib/components/NotFound.svelte';
import { DefaultMarker, MapLibre, Marker, Popup } from 'svelte-maplibre'; import { DefaultMarker, MapLibre, Marker, Popup, LineLayer, GeoJSON } from 'svelte-maplibre';
import TransportationCard from '$lib/components/TransportationCard.svelte'; import TransportationCard from '$lib/components/TransportationCard.svelte';
import NoteCard from '$lib/components/NoteCard.svelte'; import NoteCard from '$lib/components/NoteCard.svelte';
import NoteModal from '$lib/components/NoteModal.svelte'; import NoteModal from '$lib/components/NoteModal.svelte';
@ -28,7 +28,8 @@
groupTransportationsByDate, groupTransportationsByDate,
groupChecklistsByDate, groupChecklistsByDate,
osmTagToEmoji, osmTagToEmoji,
groupLodgingByDate groupLodgingByDate,
LODGING_TYPES_ICONS
} from '$lib'; } from '$lib';
import ChecklistCard from '$lib/components/ChecklistCard.svelte'; import ChecklistCard from '$lib/components/ChecklistCard.svelte';
import ChecklistModal from '$lib/components/ChecklistModal.svelte'; import ChecklistModal from '$lib/components/ChecklistModal.svelte';
@ -46,6 +47,14 @@
return marked(markdown); return marked(markdown);
}; };
function getLodgingIcon(type: string) {
if (type in LODGING_TYPES_ICONS) {
return LODGING_TYPES_ICONS[type as keyof typeof LODGING_TYPES_ICONS];
} else {
return '🏨';
}
}
let collection: Collection; let collection: Collection;
// add christmas and new years // add christmas and new years
@ -604,10 +613,6 @@
</div> </div>
{/if} {/if}
{#if collection && !collection.start_date && adventures.length == 0 && transportations.length == 0 && notes.length == 0 && checklists.length == 0 && lodging.length == 0}
<NotFound error={undefined} />
{/if}
{#if collection.description} {#if collection.description}
<div class="flex justify-center mt-4 max-w-screen-lg mx-auto"> <div class="flex justify-center mt-4 max-w-screen-lg mx-auto">
<article <article
@ -990,14 +995,15 @@
{/if} {/if}
{/each} {/each}
{#each transportations as transportation} {#each transportations as transportation}
{#if transportation.destination_latitude && transportation.destination_longitude} {#if transportation.origin_latitude && transportation.origin_longitude && transportation.destination_latitude && transportation.destination_longitude}
<!-- Origin Marker -->
<Marker <Marker
lngLat={{ lngLat={{
lng: transportation.destination_longitude, lng: transportation.origin_longitude,
lat: transportation.destination_latitude lat: transportation.origin_latitude
}} }}
class="grid h-8 w-8 place-items-center rounded-full border border-gray-200 class="grid h-8 w-8 place-items-center rounded-full border border-gray-200
bg-red-300 text-black focus:outline-6 focus:outline-black" bg-green-300 text-black focus:outline-6 focus:outline-black"
> >
<span class="text-xl"> <span class="text-xl">
{getTransportationEmoji(transportation.type)} {getTransportationEmoji(transportation.type)}
@ -1009,15 +1015,15 @@
</p> </p>
</Popup> </Popup>
</Marker> </Marker>
{/if}
{#if transportation.origin_latitude && transportation.origin_longitude} <!-- Destination Marker -->
<Marker <Marker
lngLat={{ lngLat={{
lng: transportation.origin_longitude, lng: transportation.destination_longitude,
lat: transportation.origin_latitude lat: transportation.destination_latitude
}} }}
class="grid h-8 w-8 place-items-center rounded-full border border-gray-200 class="grid h-8 w-8 place-items-center rounded-full border border-gray-200
bg-green-300 text-black focus:outline-6 focus:outline-black" bg-red-300 text-black focus:outline-6 focus:outline-black"
> >
<span class="text-xl"> <span class="text-xl">
{getTransportationEmoji(transportation.type)} {getTransportationEmoji(transportation.type)}
@ -1029,6 +1035,57 @@
</p> </p>
</Popup> </Popup>
</Marker> </Marker>
<!-- Line connecting origin and destination -->
<GeoJSON
data={{
type: 'Feature',
properties: {
name: transportation.name,
type: transportation.type
},
geometry: {
type: 'LineString',
coordinates: [
[transportation.origin_longitude, transportation.origin_latitude],
[transportation.destination_longitude, transportation.destination_latitude]
]
}
}}
>
<LineLayer
layout={{ 'line-cap': 'round', 'line-join': 'round' }}
paint={{
'line-width': 3,
'line-color': '#898989', // customize your line color here
'line-opacity': 0.8
// 'line-dasharray': [5, 2]
}}
/>
</GeoJSON>
{/if}
{/each}
{#each lodging as hotel}
{#if hotel.longitude && hotel.latitude}
<Marker
lngLat={{
lng: hotel.longitude,
lat: hotel.latitude
}}
class="grid h-8 w-8 place-items-center rounded-full border border-gray-200
bg-yellow-300 text-black focus:outline-6 focus:outline-black"
>
<span class="text-xl">
{getLodgingIcon(hotel.type)}
</span>
<Popup openOn="click" offset={[0, -10]}>
<div class="text-lg text-black font-bold">{hotel.name}</div>
<p class="font-semibold text-black text-md">
{hotel.type}
</p>
</Popup>
</Marker>
{/if} {/if}
{/each} {/each}
</MapLibre> </MapLibre>
@ -1080,7 +1137,7 @@
</div> </div>
<div class="join flex items-center justify-center mt-4"> <div class="join flex items-center justify-center mt-4">
<input <input
class="join-item btn" class="join-item btn btn-neutral"
type="radio" type="radio"
name="options" name="options"
aria-label="Tourism" aria-label="Tourism"
@ -1088,7 +1145,7 @@
on:click={() => (recomendationType = 'tourism')} on:click={() => (recomendationType = 'tourism')}
/> />
<input <input
class="join-item btn" class="join-item btn btn-neutral"
type="radio" type="radio"
name="options" name="options"
aria-label="Food" aria-label="Food"
@ -1096,7 +1153,7 @@
on:click={() => (recomendationType = 'food')} on:click={() => (recomendationType = 'food')}
/> />
<input <input
class="join-item btn" class="join-item btn btn-neutral"
type="radio" type="radio"
name="options" name="options"
aria-label="Lodging" aria-label="Lodging"

View file

@ -63,6 +63,13 @@ export const load: PageServerLoad = async (event) => {
immichIntegration = await immichIntegrationsFetch.json(); immichIntegration = await immichIntegrationsFetch.json();
} }
let socialProvidersFetch = await fetch(`${endpoint}/auth/social-providers`, {
headers: {
Cookie: `sessionid=${sessionId}`
}
});
let socialProviders = await socialProvidersFetch.json();
let publicUrlFetch = await fetch(`${endpoint}/public-url/`); let publicUrlFetch = await fetch(`${endpoint}/public-url/`);
let publicUrl = ''; let publicUrl = '';
if (!publicUrlFetch.ok) { if (!publicUrlFetch.ok) {
@ -78,7 +85,8 @@ export const load: PageServerLoad = async (event) => {
emails, emails,
authenticators, authenticators,
immichIntegration, immichIntegration,
publicUrl publicUrl,
socialProviders
} }
}; };
}; };

View file

@ -9,7 +9,6 @@
import TotpModal from '$lib/components/TOTPModal.svelte'; import TotpModal from '$lib/components/TOTPModal.svelte';
import { appTitle, appVersion } from '$lib/config.js'; import { appTitle, appVersion } from '$lib/config.js';
import ImmichLogo from '$lib/assets/immich.svg'; import ImmichLogo from '$lib/assets/immich.svg';
import { goto } from '$app/navigation';
export let data; export let data;
console.log(data); console.log(data);
@ -20,6 +19,8 @@
emails = data.props.emails; emails = data.props.emails;
} }
let new_password_disable_setting: boolean = false;
let new_email: string = ''; let new_email: string = '';
let public_url: string = data.props.publicUrl; let public_url: string = data.props.publicUrl;
let immichIntegration = data.props.immichIntegration; let immichIntegration = data.props.immichIntegration;
@ -87,8 +88,38 @@
} }
} }
async function disablePassword() {
if (user.disable_password) {
let res = await fetch('/auth/disable-password/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
if (res.ok) {
addToast('success', $t('settings.password_disabled'));
} else {
addToast('error', $t('settings.password_disabled_error'));
user.disable_password = false;
}
} else {
let res = await fetch('/auth/disable-password/', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
});
if (res.ok) {
addToast('success', $t('settings.password_enabled'));
} else {
addToast('error', $t('settings.password_enabled_error'));
user.disable_password = true;
}
}
}
async function verifyEmail(email: { email: any; verified?: boolean; primary?: boolean }) { async function verifyEmail(email: { email: any; verified?: boolean; primary?: boolean }) {
let res = await fetch('/auth/browser/v1/account/email/', { let res = await fetch('/auth/browser/v1/account/email', {
method: 'PUT', method: 'PUT',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@ -103,7 +134,7 @@
} }
async function addEmail() { async function addEmail() {
let res = await fetch('/auth/browser/v1/account/email/', { let res = await fetch('/auth/browser/v1/account/email', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@ -122,7 +153,7 @@
} }
async function primaryEmail(email: { email: any; verified?: boolean; primary?: boolean }) { async function primaryEmail(email: { email: any; verified?: boolean; primary?: boolean }) {
let res = await fetch('/auth/browser/v1/account/email/', { let res = await fetch('/auth/browser/v1/account/email', {
method: 'PATCH', method: 'PATCH',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@ -490,6 +521,41 @@
href={`${public_url}/accounts/social/connections/`} href={`${public_url}/accounts/social/connections/`}
target="_blank">{$t('settings.launch_account_connections')}</a target="_blank">{$t('settings.launch_account_connections')}</a
> >
{#if data.props.socialProviders && data.props.socialProviders.length > 0}
<div class="mt-8">
<h2 class="text-2xl font-semibold text-center">{$t('settings.password_disable')}</h2>
<p>{$t('settings.password_disable_desc')}</p>
<div class="flex flex-col items-center mt-4">
<input
type="checkbox"
id="disable_password"
name="disable_password"
bind:checked={user.disable_password}
class="toggle toggle-primary"
on:change={disablePassword}
/>
<label for="disable_password" class="ml-2 text-sm text-neutral-content"
>{$t('settings.disable_password')}</label
>
<!-- <button class="btn btn-primary mt-4" on:click={disablePassword}
>{$t('settings.update')}</button
> -->
{#if user.disable_password}
<div class="badge badge-error mt-2">{$t('settings.password_disabled')}</div>
{/if}
{#if !user.disable_password}
<div class="badge badge-success mt-2">{$t('settings.password_enabled')}</div>
{/if}
{#if user.disable_password}
<div class="alert alert-warning mt-4">
{$t('settings.password_disable_warning')}
</div>
{/if}
</div>
</div>
{/if}
</div> </div>
</section> </section>

View file

@ -10,6 +10,21 @@
let regions: Region[] = data.props?.regions || []; let regions: Region[] = data.props?.regions || [];
let visitedRegions: VisitedRegion[] = data.props?.visitedRegions || []; let visitedRegions: VisitedRegion[] = data.props?.visitedRegions || [];
let filteredRegions: Region[] = [];
let searchQuery: string = '';
$: {
if (searchQuery === '') {
filteredRegions = regions;
} else {
// always filter from the original regions list
filteredRegions = regions.filter((region) =>
region.name.toLowerCase().includes(searchQuery.toLowerCase())
);
}
}
const country = data.props?.country || null; const country = data.props?.country || null;
console.log(data); console.log(data);
@ -88,8 +103,25 @@
</div> </div>
</div> </div>
<div class="flex items-center justify-center mb-4">
<input
type="text"
placeholder={$t('navbar.search')}
class="input input-bordered w-full max-w-xs"
bind:value={searchQuery}
/>
{#if searchQuery.length > 0}
<!-- clear button -->
<div class="flex items-center justify-center ml-4">
<button class="btn btn-neutral" on:click={() => (searchQuery = '')}>
{$t('worldtravel.clear_search')}
</button>
</div>
{/if}
</div>
<div class="flex flex-wrap gap-4 mr-4 ml-4 justify-center content-center"> <div class="flex flex-wrap gap-4 mr-4 ml-4 justify-center content-center">
{#each regions as region} {#each filteredRegions as region}
<RegionCard <RegionCard
{region} {region}
visited={visitedRegions.some((visitedRegion) => visitedRegion.region === region.id)} visited={visitedRegions.some((visitedRegion) => visitedRegion.region === region.id)}

View file

@ -20,7 +20,7 @@
filteredCities = allCities; filteredCities = allCities;
} else { } else {
// otherwise, filter countries by name // otherwise, filter countries by name
filteredCities = filteredCities.filter((country) => filteredCities = allCities.filter((country) =>
country.name.toLowerCase().includes(searchQuery.toLowerCase()) country.name.toLowerCase().includes(searchQuery.toLowerCase())
); );
} }
@ -97,74 +97,7 @@
</div> </div>
</div> </div>
<!-- <div class="flex items-center justify-center mb-4">
<div class="join">
<input
class="join-item btn"
type="radio"
name="filter"
aria-label={$t('worldtravel.all')}
checked
on:click={() => (filterOption = 'all')}
/>
<input
class="join-item btn"
type="radio"
name="filter"
aria-label={$t('worldtravel.partially_visited')}
on:click={() => (filterOption = 'partial')}
/>
<input
class="join-item btn"
type="radio"
name="filter"
aria-label={$t('worldtravel.completely_visited')}
on:click={() => (filterOption = 'complete')}
/>
<input
class="join-item btn"
type="radio"
name="filter"
aria-label={$t('worldtravel.not_visited')}
on:click={() => (filterOption = 'not')}
/>
</div>
<select class="select select-bordered w-full max-w-xs ml-4" bind:value={subRegionOption}>
<option value="">{$t('worldtravel.all_subregions')}</option>
{#each worldSubregions as subregion}
<option value={subregion}>{subregion}</option>
{/each}
</select>
<div class="flex items-center justify-center ml-4">
<input
type="checkbox"
class="checkbox checkbox-bordered"
bind:checked={showMap}
aria-label={$t('adventures.show_map')}
/>
<span class="ml-2">{$t('adventures.show_map')}</span>
</div>
</div> -->
{#if allCities.length > 0} {#if allCities.length > 0}
<div class="flex items-center justify-center mb-4">
<input
type="text"
placeholder={$t('navbar.search')}
class="input input-bordered w-full max-w-xs"
bind:value={searchQuery}
/>
{#if searchQuery.length > 0}
<!-- clear button -->
<div class="flex items-center justify-center ml-4">
<button class="btn btn-neutral" on:click={() => (searchQuery = '')}>
{$t('worldtravel.clear_search')}
</button>
</div>
{/if}
</div>
<div class="mt-4 mb-4 flex justify-center"> <div class="mt-4 mb-4 flex justify-center">
<!-- checkbox to toggle marker --> <!-- checkbox to toggle marker -->
@ -198,6 +131,23 @@
</div> </div>
{/if} {/if}
<div class="flex items-center justify-center mb-4">
<input
type="text"
placeholder={$t('navbar.search')}
class="input input-bordered w-full max-w-xs"
bind:value={searchQuery}
/>
{#if searchQuery.length > 0}
<!-- clear button -->
<div class="flex items-center justify-center ml-4">
<button class="btn btn-neutral" on:click={() => (searchQuery = '')}>
{$t('worldtravel.clear_search')}
</button>
</div>
{/if}
</div>
<div class="flex flex-wrap gap-4 mr-4 ml-4 justify-center content-center"> <div class="flex flex-wrap gap-4 mr-4 ml-4 justify-center content-center">
{#each filteredCities as city} {#each filteredCities as city}
<CityCard <CityCard