1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-19 12:59:36 +02:00

localization v2

This commit is contained in:
Sean Morley 2024-10-28 13:56:57 -04:00
parent 6cf62cfb82
commit 91c0ec8c07
18 changed files with 432 additions and 101 deletions

28
.vscode/settings.json vendored
View file

@ -1,3 +1,29 @@
{ {
"git.ignoreLimitWarning": true "git.ignoreLimitWarning": true,
"i18n-ally.localesPaths": [
"frontend/src/locales",
"backend/server/backend/lib/python3.12/site-packages/allauth/locale",
"backend/server/backend/lib/python3.12/site-packages/dj_rest_auth/locale",
"backend/server/backend/lib/python3.12/site-packages/rest_framework/locale",
"backend/server/backend/lib/python3.12/site-packages/rest_framework_simplejwt/locale",
"backend/server/backend/lib/python3.12/site-packages/django/conf/locale",
"backend/server/backend/lib/python3.12/site-packages/django/contrib/messages",
"backend/server/backend/lib/python3.12/site-packages/allauth/templates/account/messages",
"backend/server/backend/lib/python3.12/site-packages/allauth/templates/mfa/messages",
"backend/server/backend/lib/python3.12/site-packages/allauth/templates/socialaccount/messages",
"backend/server/backend/lib/python3.12/site-packages/allauth/templates/usersessions/messages",
"backend/server/backend/lib/python3.12/site-packages/django/contrib/admindocs/locale",
"backend/server/backend/lib/python3.12/site-packages/django/contrib/auth/locale",
"backend/server/backend/lib/python3.12/site-packages/django/contrib/admin/locale",
"backend/server/backend/lib/python3.12/site-packages/django/contrib/contenttypes/locale",
"backend/server/backend/lib/python3.12/site-packages/django/contrib/flatpages/locale",
"backend/server/backend/lib/python3.12/site-packages/django/contrib/humanize/locale",
"backend/server/backend/lib/python3.12/site-packages/django/contrib/gis/locale",
"backend/server/backend/lib/python3.12/site-packages/django/contrib/redirects/locale",
"backend/server/backend/lib/python3.12/site-packages/django/contrib/postgres/locale",
"backend/server/backend/lib/python3.12/site-packages/django/contrib/sessions/locale",
"backend/server/backend/lib/python3.12/site-packages/django/contrib/sites/locale",
"backend/server/backend/lib/python3.12/site-packages/rest_framework/templates/rest_framework/docs/langs"
],
"i18n-ally.keystyle": "nested"
} }

View file

@ -109,17 +109,11 @@ export const themeHook: Handle = async ({ event, resolve }) => {
// hook to get the langauge cookie and set the locale // hook to get the langauge cookie and set the locale
export const i18nHook: Handle = async ({ event, resolve }) => { export const i18nHook: Handle = async ({ event, resolve }) => {
let lang = event.cookies.get('lang'); let locale = event.cookies.get('locale');
if (!lang) { if (!locale) {
lang = ''; // Set default locale return await resolve(event);
event.cookies.set('lang', lang, {
httpOnly: true,
sameSite: 'lax',
expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), // 1 year
path: '/'
});
} }
event.locals.locale = lang; // Store the locale in locals event.locals.locale = locale; // Store the locale in locals
return await resolve(event); return await resolve(event);
}; };

View file

@ -11,15 +11,14 @@
import MapMarker from '~icons/mdi/map-marker'; import MapMarker from '~icons/mdi/map-marker';
import { addToast } from '$lib/toasts'; import { addToast } from '$lib/toasts';
import Link from '~icons/mdi/link-variant'; import Link from '~icons/mdi/link-variant';
import CheckBold from '~icons/mdi/check-bold';
import FormatListBulletedSquare from '~icons/mdi/format-list-bulleted-square';
import LinkVariantRemove from '~icons/mdi/link-variant-remove'; import LinkVariantRemove from '~icons/mdi/link-variant-remove';
import Plus from '~icons/mdi/plus'; import Plus from '~icons/mdi/plus';
import CollectionLink from './CollectionLink.svelte'; import CollectionLink from './CollectionLink.svelte';
import DotsHorizontal from '~icons/mdi/dots-horizontal'; import DotsHorizontal from '~icons/mdi/dots-horizontal';
import DeleteWarning from './DeleteWarning.svelte'; import DeleteWarning from './DeleteWarning.svelte';
import { isAdventureVisited, typeToString } from '$lib'; import { isAdventureVisited } from '$lib';
import CardCarousel from './CardCarousel.svelte'; import CardCarousel from './CardCarousel.svelte';
import { t } from 'svelte-i18n';
export let type: string; export let type: string;
export let user: User | null; export let user: User | null;
@ -67,34 +66,13 @@
body: JSON.stringify({ collection: null }) body: JSON.stringify({ collection: null })
}); });
if (res.ok) { if (res.ok) {
console.log('Adventure removed from collection'); addToast('info', `${$t('adventures.collection_remove_success')}`);
addToast('info', 'Adventure removed from collection successfully!');
dispatch('delete', adventure.id); dispatch('delete', adventure.id);
} else { } else {
console.log('Error removing adventure from collection'); addToast('error', `${$t('adventures.collection_remove_error')}`);
} }
} }
function changeType(newType: string) {
return async () => {
let res = await fetch(`/api/adventures/${adventure.id}/`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ type: newType })
});
if (res.ok) {
console.log('Adventure type changed');
dispatch('typeChange', adventure.id);
addToast('info', 'Adventure type changed successfully!');
adventure.type = newType;
} else {
console.log('Error changing adventure type');
}
};
}
async function linkCollection(event: CustomEvent<number>) { async function linkCollection(event: CustomEvent<number>) {
let collectionId = event.detail; let collectionId = event.detail;
let res = await fetch(`/api/adventures/${adventure.id}`, { let res = await fetch(`/api/adventures/${adventure.id}`, {
@ -106,11 +84,11 @@
}); });
if (res.ok) { if (res.ok) {
console.log('Adventure linked to collection'); console.log('Adventure linked to collection');
addToast('info', 'Adventure linked to collection successfully!'); addToast('info', `${$t('adventures.collection_link_success')}`);
isCollectionModalOpen = false; isCollectionModalOpen = false;
dispatch('delete', adventure.id); dispatch('delete', adventure.id);
} else { } else {
console.log('Error linking adventure to collection'); addToast('error', `${$t('adventures.collection_link_error')}`);
} }
} }
@ -131,7 +109,7 @@
<DeleteWarning <DeleteWarning
title="Delete Adventure" title="Delete Adventure"
button_text="Delete" button_text="Delete"
description="Are you sure you want to delete this adventure? This action cannot be undone." description={$t('adventures.adventure_delete_confirm')}
is_warning={false} is_warning={false}
on:close={() => (isWarningModalOpen = false)} on:close={() => (isWarningModalOpen = false)}
on:confirm={deleteAdventure} on:confirm={deleteAdventure}
@ -153,7 +131,7 @@
</button> </button>
</div> </div>
<div> <div>
<div class="badge badge-primary">{typeToString(adventure.type)}</div> <div class="badge badge-primary">{$t(`adventures.activities.${adventure.type}`)}</div>
<div class="badge badge-success">{isAdventureVisited(adventure) ? 'Visited' : 'Planned'}</div> <div class="badge badge-success">{isAdventureVisited(adventure) ? 'Visited' : 'Planned'}</div>
<div class="badge badge-secondary">{adventure.is_public ? 'Public' : 'Private'}</div> <div class="badge badge-secondary">{adventure.is_public ? 'Public' : 'Private'}</div>
</div> </div>
@ -198,30 +176,24 @@
<button <button
class="btn btn-neutral mb-2" class="btn btn-neutral mb-2"
on:click={() => goto(`/adventures/${adventure.id}`)} on:click={() => goto(`/adventures/${adventure.id}`)}
><Launch class="w-6 h-6" />Open Details</button ><Launch class="w-6 h-6" />{$t('adventures.open_details')}</button
> >
<button class="btn btn-neutral mb-2" on:click={editAdventure}> <button class="btn btn-neutral mb-2" on:click={editAdventure}>
<FileDocumentEdit class="w-6 h-6" />Edit Adventure <FileDocumentEdit class="w-6 h-6" />
{$t('adventures.edit_adventure')}
</button> </button>
{#if adventure.type == 'visited' && user?.pk == adventure.user_id}
<button class="btn btn-neutral mb-2" on:click={changeType('planned')}
><FormatListBulletedSquare class="w-6 h-6" />Change to Plan</button
>
{/if}
{#if adventure.type == 'planned' && user?.pk == adventure.user_id}
<button class="btn btn-neutral mb-2" on:click={changeType('visited')}
><CheckBold class="w-6 h-6" />Mark Visited</button
>
{/if}
<!-- remove from collection --> <!-- remove from collection -->
{#if adventure.collection && user?.pk == adventure.user_id} {#if adventure.collection && user?.pk == adventure.user_id}
<button class="btn btn-neutral mb-2" on:click={removeFromCollection} <button class="btn btn-neutral mb-2" on:click={removeFromCollection}
><LinkVariantRemove class="w-6 h-6" />Remove from Collection</button ><LinkVariantRemove class="w-6 h-6" />{$t(
'adventures.remove_from_collection'
)}</button
> >
{/if} {/if}
{#if !adventure.collection} {#if !adventure.collection}
<button class="btn btn-neutral mb-2" on:click={() => (isCollectionModalOpen = true)} <button class="btn btn-neutral mb-2" on:click={() => (isCollectionModalOpen = true)}
><Plus class="w-6 h-6" />Add to Collection</button ><Plus class="w-6 h-6" />{$t('adventures.add_to_collection')}</button
> >
{/if} {/if}
<button <button
@ -229,7 +201,7 @@
data-umami-event="Delete Adventure" data-umami-event="Delete Adventure"
class="btn btn-warning" class="btn btn-warning"
on:click={() => (isWarningModalOpen = true)} on:click={() => (isWarningModalOpen = true)}
><TrashCan class="w-6 h-6" />Delete</button ><TrashCan class="w-6 h-6" />{$t('adventures.delete')}</button
> >
</ul> </ul>
</div> </div>

View file

@ -15,7 +15,7 @@
<div class="avatar placeholder"> <div class="avatar placeholder">
<div class="bg-neutral rounded-full text-neutral-200 w-10 ml-4"> <div class="bg-neutral rounded-full text-neutral-200 w-10 ml-4">
{#if user.profile_pic} {#if user.profile_pic}
<img src={user.profile_pic} alt="User Profile" /> <img src={user.profile_pic} alt={$t('navbar.profile')} />
{:else} {:else}
<span class="text-2xl -mt-1">{letter}</span> <span class="text-2xl -mt-1">{letter}</span>
{/if} {/if}

View file

@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { Adventure } from '$lib/types'; import type { Adventure } from '$lib/types';
import ImageDisplayModal from './ImageDisplayModal.svelte'; import ImageDisplayModal from './ImageDisplayModal.svelte';
import { t } from 'svelte-i18n';
export let adventures: Adventure[] = []; export let adventures: Adventure[] = [];
@ -79,7 +80,7 @@
{:else} {:else}
<!-- svelte-ignore a11y-img-redundant-alt --> <!-- svelte-ignore a11y-img-redundant-alt -->
<img <img
src={'https://placehold.co/300?text=No%20Image%20Found&font=roboto'} src={`https://placehold.co/300?text=${$t('adventures.no_image_found')}&font=roboto`}
alt="No image available" alt="No image available"
class="w-full h-48 object-cover" class="w-full h-48 object-cover"
/> />

View file

@ -8,19 +8,25 @@
import WeatherSunny from '~icons/mdi/weather-sunny'; import WeatherSunny from '~icons/mdi/weather-sunny';
import WeatherNight from '~icons/mdi/weather-night'; import WeatherNight from '~icons/mdi/weather-night';
import Forest from '~icons/mdi/forest'; import Forest from '~icons/mdi/forest';
import Flower from '~icons/mdi/flower';
import Water from '~icons/mdi/water'; import Water from '~icons/mdi/water';
import AboutModal from './AboutModal.svelte'; import AboutModal from './AboutModal.svelte';
import AccountMultiple from '~icons/mdi/account-multiple'; import AccountMultiple from '~icons/mdi/account-multiple';
import Avatar from './Avatar.svelte'; import Avatar from './Avatar.svelte';
import PaletteOutline from '~icons/mdi/palette-outline'; import PaletteOutline from '~icons/mdi/palette-outline';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { t } from 'svelte-i18n'; import { t, locale, locales } from 'svelte-i18n';
let query: string = ''; let query: string = '';
let isAboutModalOpen: boolean = false; let isAboutModalOpen: boolean = false;
const submitLocaleChange = (event: Event) => {
const select = event.target as HTMLSelectElement;
const newLocale = select.value;
document.cookie = `locale=${newLocale}; path=/`;
locale.set(newLocale);
window.location.reload();
};
const submitUpdateTheme: SubmitFunction = ({ action }) => { const submitUpdateTheme: SubmitFunction = ({ action }) => {
const theme = action.searchParams.get('theme'); const theme = action.searchParams.get('theme');
console.log('theme', theme); console.log('theme', theme);
@ -104,7 +110,7 @@
<form class="flex gap-2"> <form class="flex gap-2">
<label class="input input-bordered flex items-center gap-2"> <label class="input input-bordered flex items-center gap-2">
<input type="text" bind:value={query} class="grow" placeholder="Search" /> <input type="text" bind:value={query} class="grow" placeholder={$t('navbar.search')} />
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -172,7 +178,7 @@
<form class="flex gap-2"> <form class="flex gap-2">
<label class="input input-bordered flex items-center gap-2"> <label class="input input-bordered flex items-center gap-2">
<input type="text" bind:value={query} class="grow" placeholder="Search" /> <input type="text" bind:value={query} class="grow" placeholder={$t('navbar.search')} />
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -206,41 +212,55 @@
tabindex="0" tabindex="0"
class="dropdown-content bg-neutral text-neutral-content z-[1] menu p-2 shadow rounded-box w-52" class="dropdown-content bg-neutral text-neutral-content z-[1] menu p-2 shadow rounded-box w-52"
> >
<button class="btn" on:click={() => (isAboutModalOpen = true)}>About AdventureLog</button> <button class="btn" on:click={() => (isAboutModalOpen = true)}>{$t('navbar.about')}</button>
<button <button
class="btn btn-sm mt-2" class="btn btn-sm mt-2"
on:click={() => (window.location.href = 'https://docs.adventurelog.app/')} on:click={() => (window.location.href = 'https://docs.adventurelog.app/')}
>Documentation</button >{$t('navbar.documentation')}</button
> >
<button <button
class="btn btn-sm mt-2" class="btn btn-sm mt-2"
on:click={() => (window.location.href = 'https://discord.gg/wRbQ9Egr8C')}>Discord</button on:click={() => (window.location.href = 'https://discord.gg/wRbQ9Egr8C')}
>{$t('navbar.discord')}</button
> >
<p class="font-bold m-4 text-lg">Theme Selection</p> <p class="font-bold m-4 text-lg">{$t('navbar.theme_selection')}</p>
<form method="POST" use:enhance={submitUpdateTheme}> <form method="POST" use:enhance={submitUpdateTheme}>
<li> <li>
<button formaction="/?/setTheme&theme=light" <button formaction="/?/setTheme&theme=light"
>Light<WeatherSunny class="w-6 h-6" /> >{$t('navbar.themes.light')}<WeatherSunny class="w-6 h-6" />
</button> </button>
</li> </li>
<li> <li>
<button formaction="/?/setTheme&theme=dark">Dark<WeatherNight class="w-6 h-6" /></button <button formaction="/?/setTheme&theme=dark"
>{$t('navbar.themes.dark')}<WeatherNight class="w-6 h-6" /></button
> >
</li> </li>
<li> <li>
<button formaction="/?/setTheme&theme=night" <button formaction="/?/setTheme&theme=night"
>Night<WeatherNight class="w-6 h-6" /></button >{$t('navbar.themes.night')}<WeatherNight class="w-6 h-6" /></button
> >
</li> </li>
<li> <li>
<button formaction="/?/setTheme&theme=forest">Forest<Forest class="w-6 h-6" /></button> <button formaction="/?/setTheme&theme=forest"
>{$t('navbar.themes.forest')}<Forest class="w-6 h-6" /></button
>
<button formaction="/?/setTheme&theme=aestheticLight" <button formaction="/?/setTheme&theme=aestheticLight"
>Aesthetic Light<PaletteOutline class="w-6 h-6" /></button >{$t('navbar.themes.aestetic-light')}<PaletteOutline class="w-6 h-6" /></button
> >
<button formaction="/?/setTheme&theme=aestheticDark" <button formaction="/?/setTheme&theme=aestheticDark"
>Aesthetic Dark<PaletteOutline class="w-6 h-6" /></button >{$t('navbar.themes.aestetic-dark')}<PaletteOutline class="w-6 h-6" /></button
> >
<button formaction="/?/setTheme&theme=aqua">Aqua<Water class="w-6 h-6" /></button> <button formaction="/?/setTheme&theme=aqua"
>{$t('navbar.themes.aqua')}<Water class="w-6 h-6" /></button
>
<form method="POST" use:enhance>
<select class="select" on:change={submitLocaleChange} bind:value={$locale}>
{#each $locales as loc}
<option value={loc}>{loc}</option>
{/each}
</select>
<input type="hidden" name="locale" value={$locale} />
</form>
</li> </li>
</form> </form>
</ul> </ul>

View file

@ -253,15 +253,6 @@ export let ADVENTURE_TYPES = [
{ type: 'other', label: 'Other' } { type: 'other', label: 'Other' }
]; ];
export function typeToString(type: string) {
const typeObj = ADVENTURE_TYPES.find((t) => t.type === type);
if (typeObj) {
return typeObj.label;
} else {
return 'Unknown';
}
}
/** /**
* Checks if an adventure has been visited. * Checks if an adventure has been visited.
* *

View file

View file

@ -0,0 +1,93 @@
{
"about": {
"about": "Um",
"close": "Schließen",
"license": "Lizenziert unter der GPL-3.0-Lizenz.",
"message": "Hergestellt mit ❤️ in den Vereinigten Staaten.",
"nominatim_1": "Standortsuche und Geokodierung werden bereitgestellt von",
"nominatim_2": "Ihre Daten werden unter der ODbL-Lizenz lizenziert.",
"oss_attributions": "Open-Source-Zuschreibungen",
"other_attributions": "Weitere Hinweise finden Sie in der README-Datei.",
"source_code": "Quellcode"
},
"adventures": {
"activities": {
"activity": "Aktivität 🏄",
"art_museums": "Kunst",
"attraction": "Attraktion 🎢",
"culture": "Kultur 🎭",
"dining": "Essen 🍽️",
"event": "Veranstaltung 🎉",
"festivals": "Feste 🎪",
"fitness": "Fitness 🏋️",
"general": "Allgemein 🌍",
"hiking": "Wandern 🥾",
"historical_sites": "Historische Stätten 🏛️",
"lodging": "Unterkunft 🛌",
"music_concerts": "Musik",
"nightlife": "Nachtleben 🌃",
"other": "Andere",
"outdoor": "Draußen 🏞️",
"shopping": "Einkaufen 🛍️",
"spiritual_journeys": "Spirituelle Reisen 🧘‍♀️",
"transportation": "Transport 🚗",
"volunteer_work": "Freiwilligenarbeit 🤝",
"water_sports": "Wassersport 🚤",
"wildlife": "Wildtiere 🦒"
},
"add_to_collection": "Zur Sammlung hinzufügen",
"adventure_delete_confirm": "Sind Sie sicher, dass Sie dieses Abenteuer löschen möchten? \nDiese Aktion kann nicht rückgängig gemacht werden.",
"collection_link_error": "Fehler beim Verknüpfen des Abenteuers mit der Sammlung",
"collection_link_success": "Abenteuer erfolgreich mit Sammlung verknüpft!",
"collection_remove_error": "Beim Entfernen des Abenteuers aus der Sammlung ist ein Fehler aufgetreten",
"collection_remove_success": "Abenteuer erfolgreich aus der Sammlung entfernt!",
"delete": "Löschen",
"edit_adventure": "Abenteuer bearbeiten",
"no_image_found": "Kein Bild gefunden",
"open_details": "Details öffnen",
"remove_from_collection": "Aus der Sammlung entfernen"
},
"home": {
"desc_1": "Entdecken, planen und erkunden Sie mit Leichtigkeit",
"desc_2": "AdventureLog wurde entwickelt, um Ihre Reise zu vereinfachen und Ihnen die Tools und Ressourcen zur Verfügung zu stellen, mit denen Sie Ihr nächstes unvergessliches Abenteuer planen, packen und navigieren können.",
"feature_1": "Reisetagebuch",
"feature_1_desc": "Behalten Sie den Überblick über Ihre Abenteuer mit einem personalisierten Reisetagebuch und teilen Sie Ihre Erlebnisse mit Freunden und Familie.",
"feature_2": "Reiseplanung",
"feature_3": "Reisekarte",
"feature_3_desc": "Sehen Sie sich Ihre Reisen rund um die Welt mit einer interaktiven Karte an und erkunden Sie neue Reiseziele.",
"go_to": "Gehen Sie zu AdventureLog",
"hero_1": "Entdecken Sie die aufregendsten Abenteuer der Welt",
"hero_2": "Entdecken und planen Sie Ihr nächstes Abenteuer mit AdventureLog. \nEntdecken Sie atemberaubende Reiseziele, erstellen Sie individuelle Reiserouten und bleiben Sie unterwegs in Verbindung.",
"key_features": "Hauptmerkmale"
},
"navbar": {
"about": "Über AdventureLog",
"adventures": "Abenteuer",
"collections": "Sammlungen",
"discord": "Zwietracht",
"documentation": "Dokumentation",
"greeting": "Hallo",
"login": "Login",
"logout": "Abmelden",
"map": "Karte",
"my_activities": "Meine Aktivitäten",
"my_adventures": "Meine Abenteuer",
"profile": "Profil",
"search": "Suchen",
"settings": "Einstellungen",
"shared_with_me": "Mit mir geteilt",
"signup": "Melden Sie sich an",
"theme_selection": "Themenauswahl",
"themes": {
"aestetic-dark": "Ästhetisches Dunkel",
"aestetic-light": "Ästhetisches Licht",
"aqua": "Aqua",
"dark": "Dunkel",
"forest": "Wald",
"light": "Licht",
"night": "Nacht"
},
"users": "Benutzer",
"worldtravel": "Weltreisen"
}
}

View file

@ -14,7 +14,20 @@
"my_activities": "My Activities", "my_activities": "My Activities",
"shared_with_me": "Shared With Me", "shared_with_me": "Shared With Me",
"settings": "Settings", "settings": "Settings",
"logout": "Logout" "logout": "Logout",
"about": "About AdventureLog",
"documentation": "Documentation",
"discord": "Discord",
"theme_selection": "Theme Selection",
"themes": {
"light": "Light",
"dark": "Dark",
"night": "Night",
"forest": "Forest",
"aestetic-dark": "Aestetic Dark",
"aestetic-light": "Aestetic Light",
"aqua": "Aqua"
}
}, },
"about": { "about": {
"about": "About", "about": "About",
@ -40,5 +53,42 @@
"feature_2_desc": "Easily create custom itineraries and get a day-by-day breakdown of your trip.", "feature_2_desc": "Easily create custom itineraries and get a day-by-day breakdown of your trip.",
"feature_3": "Travel Map", "feature_3": "Travel Map",
"feature_3_desc": "View your travels throughout the world with an interactive map and explore new destinations." "feature_3_desc": "View your travels throughout the world with an interactive map and explore new destinations."
},
"adventures": {
"collection_remove_success": "Adventure removed from collection successfully!",
"collection_remove_error": "Error removing adventure from collection",
"collection_link_success": "Adventure linked to collection successfully!",
"no_image_found": "No image found",
"collection_link_error": "Error linking adventure to collection",
"adventure_delete_confirm": "Are you sure you want to delete this adventure? This action cannot be undone.",
"open_details": "Open Details",
"edit_adventure": "Edit Adventure",
"remove_from_collection": "Remove from Collection",
"add_to_collection": "Add to Collection",
"delete": "Delete",
"activities": {
"general": "General 🌍",
"outdoor": "Outdoor 🏞️",
"lodging": "Lodging 🛌",
"dining": "Dining 🍽️",
"activity": "Activity 🏄",
"attraction": "Attraction 🎢",
"shopping": "Shopping 🛍️",
"nightlife": "Nightlife 🌃",
"event": "Event 🎉",
"transportation": "Transportation 🚗",
"culture": "Culture 🎭",
"water_sports": "Water Sports 🚤",
"hiking": "Hiking 🥾",
"wildlife": "Wildlife 🦒",
"historical_sites": "Historical Sites 🏛️",
"music_concerts": "Music & Concerts 🎶",
"fitness": "Fitness 🏋️",
"art_museums": "Art & Museums 🎨",
"festivals": "Festivals 🎪",
"spiritual_journeys": "Spiritual Journeys 🧘‍♀️",
"volunteer_work": "Volunteer Work 🤝",
"other": "Other"
}
} }
} }

View file

@ -2,12 +2,32 @@
"navbar": { "navbar": {
"adventures": "Aventuras", "adventures": "Aventuras",
"collections": "Colecciones", "collections": "Colecciones",
"worldtravel": "Viaje Mundial", "worldtravel": "Viajar por el Mundo",
"map": "Mapa", "map": "Mapa",
"users": "Usuarios", "users": "Usuarios",
"login": "Iniciar sesión", "login": "Iniciar Sesión",
"signup": "Registrarse", "signup": "Registrarse",
"search": "Buscar" "search": "Buscar",
"profile": "Perfil",
"greeting": "Hola",
"my_adventures": "Mis Aventuras",
"my_activities": "Mis Actividades",
"shared_with_me": "Compartido Conmigo",
"settings": "Configuraciones",
"logout": "Cerrar Sesión",
"about": "Acerca de AdventureLog",
"documentation": "Documentación",
"discord": "Discord",
"theme_selection": "Selección de Tema",
"themes": {
"light": "Claro",
"dark": "Oscuro",
"night": "Noche",
"forest": "Bosque",
"aestetic-dark": "Estético Oscuro",
"aestetic-light": "Estético Claro",
"aqua": "Aqua"
}
}, },
"about": { "about": {
"about": "Acerca de", "about": "Acerca de",
@ -15,23 +35,60 @@
"source_code": "Código Fuente", "source_code": "Código Fuente",
"message": "Hecho con ❤️ en los Estados Unidos.", "message": "Hecho con ❤️ en los Estados Unidos.",
"oss_attributions": "Atribuciones de Código Abierto", "oss_attributions": "Atribuciones de Código Abierto",
"nominatim_1": "La búsqueda de ubicación y la geocodificación son proporcionadas por", "nominatim_1": "La búsqueda de ubicaciones y geocodificación es proporcionada por",
"nominatim_2": "Sus datos están licenciados bajo la licencia ODbL.", "nominatim_2": "Sus datos están licenciados bajo la licencia ODbL.",
"other_attributions": "Atribuciones adicionales se pueden encontrar en el archivo README.", "other_attributions": "Atribuciones adicionales se pueden encontrar en el archivo README.",
"close": "Cerrar" "close": "Cerrar"
}, },
"home": { "home": {
"hero_1": "Descubre las Aventuras Más Emocionantes del Mundo", "hero_1": "Descubre las Aventuras Más Emocionantes del Mundo",
"hero_2": "Descubre y planifica tu próxima aventura con AdventureLog. Explora destinos impresionantes, crea itinerarios personalizados y mantente conectado sobre la marcha.", "hero_2": "Descubre y planifica tu próxima aventura con AdventureLog. Explora destinos impresionantes, crea itinerarios personalizados y mantente conectado en todo momento.",
"go_to": "Ir a AdventureLog", "go_to": "Ir a AdventureLog",
"key_features": "Características Clave", "key_features": "Características Clave",
"desc_1": "Descubre, Planifica y Explora con Facilidad", "desc_1": "Descubre, Planifica y Explora Fácilmente",
"desc_2": "AdventureLog está diseñado para simplificar tu viaje, proporcionándote las herramientas y recursos para planificar, empacar y navegar tu próxima aventura inolvidable.", "desc_2": "AdventureLog está diseñado para simplificar tu viaje, brindándote las herramientas y recursos para planificar, empacar y navegar tu próxima aventura inolvidable.",
"feature_1": "Diario de Viajes", "feature_1": "Registro de Viajes",
"feature_1_desc": "Lleva un registro de tus aventuras con un diario de viajes personalizado y comparte tus experiencias con amigos y familiares.", "feature_1_desc": "Mantén un registro de tus aventuras con un diario de viaje personalizado y comparte tus experiencias con amigos y familiares.",
"feature_2": "Planificación de Viajes", "feature_2": "Planificación de Viajes",
"feature_2_desc": "Crea fácilmente itinerarios personalizados y obtén un desglose diario de tu viaje.", "feature_2_desc": "Crea fácilmente itinerarios personalizados y obtén un desglose diario de tu viaje.",
"feature_3": "Mapa de Viajes", "feature_3": "Mapa de Viaje",
"feature_3_desc": "Ve tus viajes por todo el mundo con un mapa interactivo y explora nuevos destinos." "feature_3_desc": "Visualiza tus viajes por el mundo con un mapa interactivo y explora nuevos destinos."
},
"adventures": {
"collection_remove_success": "¡Aventura eliminada de la colección con éxito!",
"collection_remove_error": "Error al eliminar la aventura de la colección",
"collection_link_success": "¡Aventura vinculada a la colección con éxito!",
"collection_link_error": "Error al vincular la aventura a la colección",
"adventure_delete_confirm": "¿Estás seguro de que quieres eliminar esta aventura? Esta acción no se puede deshacer.",
"open_details": "Abrir Detalles",
"edit_adventure": "Editar Aventura",
"remove_from_collection": "Eliminar de la Colección",
"add_to_collection": "Añadir a la Colección",
"delete": "Eliminar",
"activities": {
"activity": "Actividad 🏄",
"art_museums": "Arte",
"attraction": "Atracción 🎢",
"culture": "Cultura 🎭",
"dining": "Cenar 🍽️",
"event": "Evento 🎉",
"festivals": "Festivales 🎪",
"fitness": "Fitness 🏋️",
"general": "Generales 🌍",
"hiking": "Senderismo 🥾",
"historical_sites": "Sitios Históricos 🏛️",
"lodging": "Alojamiento 🛌",
"music_concerts": "Música",
"nightlife": "Vida nocturna 🌃",
"other": "Otro",
"outdoor": "Al aire libre 🏞️",
"shopping": "Compras 🛍️",
"spiritual_journeys": "Viajes espirituales 🧘‍♀️",
"transportation": "Transporte 🚗",
"volunteer_work": "Trabajo voluntario 🤝",
"water_sports": "Deportes acuáticos 🚤",
"wildlife": "Vida silvestre 🦒"
},
"no_image_found": "No se encontró ninguna imagen"
} }
} }

View file

@ -0,0 +1,90 @@
{
"about": {
"about": "À propos",
"close": "Fermer",
"license": "Sous licence GPL-3.0.",
"message": "Fabriqué avec ❤️ aux États-Unis.",
"nominatim_1": "La recherche de localisation et le géocodage sont fournis par",
"nominatim_2": "Leurs données sont sous licence ODbL.",
"oss_attributions": "Attributions Open Source",
"other_attributions": "Des attributions supplémentaires peuvent être trouvées dans le fichier README.",
"source_code": "Code source"
},
"adventures": {
"activities": {
"activity": "Activité 🏄",
"art_museums": "Art",
"attraction": "Attraction 🎢",
"culture": "Culturel 🎭",
"dining": "Restauration 🍽️",
"event": "Événement 🎉",
"festivals": "Fêtes 🎪",
"fitness": "Remise en forme 🏋️",
"general": "Général 🌍",
"hiking": "Randonnée 🥾",
"historical_sites": "Sites historiques 🏛️",
"lodging": "Hébergement 🛌",
"music_concerts": "Musique",
"nightlife": "Vie nocturne 🌃",
"other": "Autre",
"outdoor": "En plein air 🏞️",
"shopping": "Shopping 🛍️",
"spiritual_journeys": "Voyages spirituels 🧘‍♀️",
"transportation": "Transport 🚗",
"volunteer_work": "Travail bénévole 🤝",
"water_sports": "Sports nautiques 🚤",
"wildlife": "Faune 🦒"
},
"add_to_collection": "Ajouter à la collection",
"adventure_delete_confirm": "Êtes-vous sûr de vouloir supprimer cette aventure ? \nCette action ne peut pas être annulée.",
"collection_link_error": "Erreur lors de la liaison de l'aventure à la collection",
"collection_link_success": "Aventure liée à la collection avec succès !",
"collection_remove_error": "Erreur lors de la suppression de l'aventure de la collection",
"collection_remove_success": "Aventure supprimée de la collection avec succès !",
"delete": "Supprimer",
"edit_adventure": "Modifier l'aventure",
"no_image_found": "Aucune image trouvée",
"open_details": "Ouvrir les détails",
"remove_from_collection": "Supprimer de la collection"
},
"home": {
"desc_1": "Découvrez, planifiez et explorez en toute simplicité",
"desc_2": "AdventureLog est conçu pour simplifier votre voyage, en vous fournissant les outils et les ressources nécessaires pour planifier, préparer et naviguer dans votre prochaine aventure inoubliable.",
"feature_1": "Carnet de voyage",
"feature_1_desc": "Gardez une trace de vos aventures avec un carnet de voyage personnalisé et partagez vos expériences avec vos amis et votre famille.",
"feature_2": "Planification du voyage",
"feature_2_desc": "Créez facilement des itinéraires personnalisés et obtenez un aperçu quotidien de votre voyage.",
"feature_3": "Carte de voyage",
"feature_3_desc": "Visualisez vos voyages à travers le monde avec une carte interactive et explorez de nouvelles destinations.",
"go_to": "Aller au journal d'aventure",
"hero_1": "Découvrez les aventures les plus palpitantes du monde",
"hero_2": "Découvrez et planifiez votre prochaine aventure avec AdventureLog. \nExplorez des destinations à couper le souffle, créez des itinéraires personnalisés et restez connecté lors de vos déplacements.",
"key_features": "Principales fonctionnalités"
},
"navbar": {
"about": "À propos de AdventureLog",
"adventures": "Aventures",
"collections": "Collections",
"discord": "Discorde",
"documentation": "Documentation",
"greeting": "Salut",
"login": "Se connecter",
"logout": "Déconnexion",
"map": "Carte",
"my_activities": "Mes activités",
"my_adventures": "Mes aventures",
"profile": "Profil",
"search": "Recherche",
"settings": "Paramètres",
"shared_with_me": "Partagé avec moi",
"signup": "S'inscrire",
"theme_selection": "Sélection de thèmes",
"themes": {
"forest": "Forêt",
"light": "Lumière",
"night": "Nuit"
},
"users": "Utilisateurs",
"worldtravel": "Voyage dans le monde"
}
}

View file

@ -6,6 +6,8 @@
// Register your translations for each locale // Register your translations for each locale
register('en', () => import('../locales/en.json')); register('en', () => import('../locales/en.json'));
register('es', () => import('../locales/es.json')); register('es', () => import('../locales/es.json'));
register('fr', () => import('../locales/fr.json'));
register('de', () => import('../locales/de.json'));
if (browser) { if (browser) {
init({ init({
@ -37,3 +39,11 @@
<Toast /> <Toast />
<slot /> <slot />
{/await} {/await}
<svelte:head>
<title>AdventureLog</title>
<meta
name="description"
content="Embark, explore, remember with AdventureLog. AdventureLog is the ultimate travel companion."
/>
</svelte:head>

View file

@ -49,5 +49,15 @@ export const actions: Actions = {
} else { } else {
return redirect(302, '/'); return redirect(302, '/');
} }
},
setLocale: async ({ url, cookies }) => {
const locale = url.searchParams.get('locale');
// change the theme only if it is one of the allowed themes
if (locale && ['en', 'es'].includes(locale)) {
cookies.set('locale', locale, {
path: '/',
maxAge: 60 * 60 * 24 * 365
});
}
} }
}; };

View file

@ -26,6 +26,13 @@
let resultsPerPage: number = 25; let resultsPerPage: number = 25;
let count = data.props.count || 0; let count = data.props.count || 0;
$: {
if (count != adventures.length) {
count = adventures.length;
}
}
let totalPages = Math.ceil(count / resultsPerPage); let totalPages = Math.ceil(count / resultsPerPage);
let currentPage: number = 1; let currentPage: number = 1;

View file

@ -5,6 +5,7 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import Lost from '$lib/assets/undraw_lost.svg'; import Lost from '$lib/assets/undraw_lost.svg';
import { DefaultMarker, MapLibre, Popup } from 'svelte-maplibre'; import { DefaultMarker, MapLibre, Popup } from 'svelte-maplibre';
import { t } from 'svelte-i18n';
export let data: PageData; export let data: PageData;
console.log(data); console.log(data);
@ -22,10 +23,8 @@
let image_url: string | null = null; let image_url: string | null = null;
import ClipboardList from '~icons/mdi/clipboard-list'; import ClipboardList from '~icons/mdi/clipboard-list';
import EditAdventure from '$lib/components/AdventureModal.svelte';
import AdventureModal from '$lib/components/AdventureModal.svelte'; import AdventureModal from '$lib/components/AdventureModal.svelte';
import ImageDisplayModal from '$lib/components/ImageDisplayModal.svelte'; import ImageDisplayModal from '$lib/components/ImageDisplayModal.svelte';
import { typeToString } from '$lib';
onMount(() => { onMount(() => {
if (data.props.adventure) { if (data.props.adventure) {
@ -265,7 +264,7 @@
<div> <div>
<p class="text-sm text-muted-foreground">Adventure Type</p> <p class="text-sm text-muted-foreground">Adventure Type</p>
<p class="text-base font-medium"> <p class="text-base font-medium">
{typeToString(adventure.type)} {$t(`adventures.activities.${adventure.type}`)}
</p> </p>
</div> </div>
{#if data.props.collection} {#if data.props.collection}

View file

@ -27,6 +27,12 @@
let totalPages = Math.ceil(count / resultsPerPage); let totalPages = Math.ceil(count / resultsPerPage);
let currentPage: number = 1; let currentPage: number = 1;
$: {
if (count != collections.length) {
count = collections.length;
}
}
function handleChangePage() { function handleChangePage() {
return async ({ result }: any) => { return async ({ result }: any) => {
if (result.type === 'success') { if (result.type === 'success') {

View file

@ -1,7 +1,7 @@
<script> <script>
// @ts-nocheck // @ts-nocheck
import { isAdventureVisited, typeToString } from '$lib'; import { isAdventureVisited } from '$lib';
import AdventureModal from '$lib/components/AdventureModal.svelte'; import AdventureModal from '$lib/components/AdventureModal.svelte';
import { import {
DefaultMarker, DefaultMarker,
@ -14,6 +14,7 @@
FillLayer, FillLayer,
SymbolLayer SymbolLayer
} from 'svelte-maplibre'; } from 'svelte-maplibre';
import { t } from 'svelte-i18n';
export let data; export let data;
let clickedName = ''; let clickedName = '';
@ -162,7 +163,9 @@
<Popup openOn="click" offset={[0, -10]}> <Popup openOn="click" offset={[0, -10]}>
<div class="text-lg text-black font-bold">{marker.name}</div> <div class="text-lg text-black font-bold">{marker.name}</div>
<p class="font-semibold text-black text-md">Visited</p> <p class="font-semibold text-black text-md">Visited</p>
<p class="font-semibold text-black text-md">{typeToString(marker.type)}</p> <p class="font-semibold text-black text-md">
{$t(`adventures.activities.${marker.type}`)}
</p>
{#if marker.visits && marker.visits.length > 0} {#if marker.visits && marker.visits.length > 0}
<p class="text-black text-sm"> <p class="text-black text-sm">
{#each marker.visits as visit} {#each marker.visits as visit}
@ -201,7 +204,9 @@
<Popup openOn="click" offset={[0, -10]}> <Popup openOn="click" offset={[0, -10]}>
<div class="text-lg text-black font-bold">{marker.name}</div> <div class="text-lg text-black font-bold">{marker.name}</div>
<p class="font-semibold text-black text-md">Planned</p> <p class="font-semibold text-black text-md">Planned</p>
<p class="font-semibold text-black text-md">{typeToString(marker.type)}</p> <p class="font-semibold text-black text-md">
{$t(`adventures.activities.${marker.type}`)}}
</p>
{#if marker.visits && marker.visits.length > 0} {#if marker.visits && marker.visits.length > 0}
<p class="text-black text-sm"> <p class="text-black text-sm">
{#each marker.visits as visit} {#each marker.visits as visit}