Date Fixes, Translations, Misc Bugs (#840)
* Translated using Weblate (Spanish) Currently translated at 100.0% (956 of 956 strings) Translation: AdventureLog/Web App Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/ * Added translation using Weblate (English (United States)) * Translated using Weblate (Norwegian Bokmål) Currently translated at 100.0% (956 of 956 strings) Translation: AdventureLog/Web App Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/nb_NO/ * Remove empty English (United States) locale file * Translated using Weblate (Spanish) Currently translated at 100.0% (956 of 956 strings) Translation: AdventureLog/Web App Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/ * [BUG]Ordered Itinerary includes visits that are outside itinerary date range Fixes #746 * [BUG] Server Error (500) when trying to access the API docs Fixes #712 * [BUG] Single day Collections will think location visits are out of date range Fixes #827 * Fixes #654 * Translated using Weblate (Spanish) Currently translated at 100.0% (956 of 956 strings) Translation: AdventureLog/Web App Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/ * Added Slovak translations (#815) * Created sk.json * Update Navbar.svelte * Update +layout.svelte --------- Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com> * Implement code changes to enhance functionality and improve performance --------- Co-authored-by: Nikolai Eidsheim <nikolai.eidsheim@gmail.com> Co-authored-by: Sergio <garcia.sergio@me.com> Co-authored-by: fantastron27 <fantastron27@gmail.com>
This commit is contained in:
@@ -36,4 +36,8 @@ EMAIL_BACKEND='console'
|
|||||||
# PGDATABASE='adventurelog'
|
# PGDATABASE='adventurelog'
|
||||||
# PGUSER='admin'
|
# PGUSER='admin'
|
||||||
# PGPASSWORD='admin'
|
# PGPASSWORD='admin'
|
||||||
|
|
||||||
|
# For Sean's use:
|
||||||
|
# re-sync the development branch with main after doing squash merges
|
||||||
|
# git fetch origin && git checkout development && git reset --hard origin/main && git push origin development --force
|
||||||
# ------------------- #
|
# ------------------- #
|
||||||
@@ -27,3 +27,4 @@ psutil==6.1.1
|
|||||||
geojson==3.2.0
|
geojson==3.2.0
|
||||||
gpxpy==1.6.2
|
gpxpy==1.6.2
|
||||||
pymemcache==4.0.0
|
pymemcache==4.0.0
|
||||||
|
legacy-cgi==2.6.3
|
||||||
@@ -9,29 +9,18 @@
|
|||||||
import TrashCan from '~icons/mdi/trash-can';
|
import TrashCan from '~icons/mdi/trash-can';
|
||||||
import Calendar from '~icons/mdi/calendar';
|
import Calendar from '~icons/mdi/calendar';
|
||||||
import DeleteWarning from './DeleteWarning.svelte';
|
import DeleteWarning from './DeleteWarning.svelte';
|
||||||
|
import { isEntityOutsideCollectionDateRange } from '$lib/dateUtils';
|
||||||
|
|
||||||
export let checklist: Checklist;
|
export let checklist: Checklist;
|
||||||
export let user: User | null = null;
|
export let user: User | null = null;
|
||||||
export let collection: Collection | null = null;
|
export let collection: Collection;
|
||||||
|
|
||||||
let isWarningModalOpen: boolean = false;
|
let isWarningModalOpen: boolean = false;
|
||||||
|
|
||||||
let unlinked: boolean = false;
|
let outsideCollectionRange: boolean = false;
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (collection?.start_date && collection.end_date) {
|
outsideCollectionRange = isEntityOutsideCollectionDateRange(checklist, collection);
|
||||||
const startOutsideRange =
|
|
||||||
checklist.date &&
|
|
||||||
collection.start_date < checklist.date &&
|
|
||||||
collection.end_date < checklist.date;
|
|
||||||
|
|
||||||
const endOutsideRange =
|
|
||||||
checklist.date &&
|
|
||||||
collection.start_date > checklist.date &&
|
|
||||||
collection.end_date > checklist.date;
|
|
||||||
|
|
||||||
unlinked = !!(startOutsideRange || endOutsideRange || !checklist.date);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function editChecklist() {
|
function editChecklist() {
|
||||||
@@ -71,7 +60,7 @@
|
|||||||
<h2 class="text-xl font-bold break-words">{checklist.name}</h2>
|
<h2 class="text-xl font-bold break-words">{checklist.name}</h2>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<div class="badge badge-primary">{$t('adventures.checklist')}</div>
|
<div class="badge badge-primary">{$t('adventures.checklist')}</div>
|
||||||
{#if unlinked}
|
{#if outsideCollectionRange}
|
||||||
<div class="badge badge-error">{$t('adventures.out_of_range')}</div>
|
<div class="badge badge-error">{$t('adventures.out_of_range')}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
let items: ChecklistItem[] = [];
|
let items: ChecklistItem[] = [];
|
||||||
|
|
||||||
let constrainDates: boolean = false;
|
let constrainDates: boolean = true;
|
||||||
|
|
||||||
items = checklist?.items || [];
|
items = checklist?.items || [];
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
import StarOutline from '~icons/mdi/star-outline';
|
import StarOutline from '~icons/mdi/star-outline';
|
||||||
import Eye from '~icons/mdi/eye';
|
import Eye from '~icons/mdi/eye';
|
||||||
import EyeOff from '~icons/mdi/eye-off';
|
import EyeOff from '~icons/mdi/eye-off';
|
||||||
|
import { isEntityOutsideCollectionDateRange } from '$lib/dateUtils';
|
||||||
|
|
||||||
export let type: string | null = null;
|
export let type: string | null = null;
|
||||||
export let user: User | null;
|
export let user: User | null;
|
||||||
@@ -48,17 +49,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let unlinked: boolean = false;
|
let outsideCollectionRange: boolean = false;
|
||||||
|
|
||||||
// Reactive block to update `unlinked` when dependencies change
|
|
||||||
$: {
|
$: {
|
||||||
if (collection && collection?.start_date && collection.end_date) {
|
if (collection) {
|
||||||
unlinked = adventure.visits.every((visit) => {
|
outsideCollectionRange = adventure.visits.every((visit) =>
|
||||||
if (!visit.start_date || !visit.end_date) return true;
|
isEntityOutsideCollectionDateRange(visit, collection)
|
||||||
const isBeforeVisit = collection.end_date && collection.end_date < visit.start_date;
|
);
|
||||||
const isAfterVisit = collection.start_date && collection.start_date > visit.end_date;
|
|
||||||
return isBeforeVisit || isAfterVisit;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,7 +196,7 @@
|
|||||||
>
|
>
|
||||||
{adventure.is_visited ? $t('adventures.visited') : $t('adventures.planned')}
|
{adventure.is_visited ? $t('adventures.visited') : $t('adventures.planned')}
|
||||||
</div>
|
</div>
|
||||||
{#if unlinked}
|
{#if outsideCollectionRange}
|
||||||
<div class="badge badge-sm badge-error shadow-lg">{$t('adventures.out_of_range')}</div>
|
<div class="badge badge-sm badge-error shadow-lg">{$t('adventures.out_of_range')}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
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';
|
import { LODGING_TYPES_ICONS } from '$lib';
|
||||||
import { formatDateInTimezone } from '$lib/dateUtils';
|
import { formatDateInTimezone, isEntityOutsideCollectionDateRange } from '$lib/dateUtils';
|
||||||
import { formatAllDayDate } from '$lib/dateUtils';
|
import { formatAllDayDate } from '$lib/dateUtils';
|
||||||
import { isAllDay } from '$lib';
|
import { isAllDay } from '$lib';
|
||||||
import CardCarousel from './CardCarousel.svelte';
|
import CardCarousel from './CardCarousel.svelte';
|
||||||
@@ -31,38 +31,11 @@
|
|||||||
dispatch('edit', lodging);
|
dispatch('edit', lodging);
|
||||||
}
|
}
|
||||||
|
|
||||||
let unlinked: boolean = false;
|
let outsideCollectionRange: boolean = false;
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (collection?.start_date && collection.end_date) {
|
if (collection) {
|
||||||
// Parse transportation dates
|
outsideCollectionRange = isEntityOutsideCollectionDateRange(lodging, collection);
|
||||||
let transportationStartDate = lodging.check_in
|
|
||||||
? new Date(lodging.check_in.split('T')[0]) // Ensure proper date parsing
|
|
||||||
: null;
|
|
||||||
let transportationEndDate = lodging.check_out
|
|
||||||
? new Date(lodging.check_out.split('T')[0])
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// Parse collection dates
|
|
||||||
let collectionStartDate = new Date(collection.start_date);
|
|
||||||
let collectionEndDate = new Date(collection.end_date);
|
|
||||||
|
|
||||||
// Check if the collection range is outside the transportation range
|
|
||||||
const startOutsideRange =
|
|
||||||
transportationStartDate &&
|
|
||||||
collectionStartDate < transportationStartDate &&
|
|
||||||
collectionEndDate < transportationStartDate;
|
|
||||||
|
|
||||||
const endOutsideRange =
|
|
||||||
transportationEndDate &&
|
|
||||||
collectionStartDate > transportationEndDate &&
|
|
||||||
collectionEndDate > transportationEndDate;
|
|
||||||
|
|
||||||
unlinked = !!(
|
|
||||||
startOutsideRange ||
|
|
||||||
endOutsideRange ||
|
|
||||||
(!transportationStartDate && !transportationEndDate)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +93,7 @@
|
|||||||
{$t(`lodging.${lodging.type}`)}
|
{$t(`lodging.${lodging.type}`)}
|
||||||
{getLodgingIcon(lodging.type)}
|
{getLodgingIcon(lodging.type)}
|
||||||
</div>
|
</div>
|
||||||
{#if unlinked}
|
{#if outsideCollectionRange}
|
||||||
<div class="badge badge-error">{$t('adventures.out_of_range')}</div>
|
<div class="badge badge-error">{$t('adventures.out_of_range')}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -63,7 +63,8 @@
|
|||||||
ru: 'Русский',
|
ru: 'Русский',
|
||||||
ja: '日本語',
|
ja: '日本語',
|
||||||
ar: 'العربية',
|
ar: 'العربية',
|
||||||
'pt-br': 'Português (Brasil)'
|
'pt-br': 'Português (Brasil)',
|
||||||
|
'sk': 'Slovenský'
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitLocaleChange = (event: Event) => {
|
const submitLocaleChange = (event: Event) => {
|
||||||
|
|||||||
@@ -15,23 +15,18 @@
|
|||||||
import TrashCan from '~icons/mdi/trash-can';
|
import TrashCan from '~icons/mdi/trash-can';
|
||||||
import Calendar from '~icons/mdi/calendar';
|
import Calendar from '~icons/mdi/calendar';
|
||||||
import DeleteWarning from './DeleteWarning.svelte';
|
import DeleteWarning from './DeleteWarning.svelte';
|
||||||
|
import { isEntityOutsideCollectionDateRange } from '$lib/dateUtils';
|
||||||
|
|
||||||
export let note: Note;
|
export let note: Note;
|
||||||
export let user: User | null = null;
|
export let user: User | null = null;
|
||||||
export let collection: Collection | null = null;
|
export let collection: Collection | null = null;
|
||||||
|
|
||||||
let isWarningModalOpen: boolean = false;
|
let isWarningModalOpen: boolean = false;
|
||||||
let unlinked: boolean = false;
|
let outsideCollectionRange: boolean = false;
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (collection?.start_date && collection.end_date) {
|
if (collection) {
|
||||||
const startOutsideRange =
|
outsideCollectionRange = isEntityOutsideCollectionDateRange(note, collection);
|
||||||
note.date && collection.start_date < note.date && collection.end_date < note.date;
|
|
||||||
|
|
||||||
const endOutsideRange =
|
|
||||||
note.date && collection.start_date > note.date && collection.end_date > note.date;
|
|
||||||
|
|
||||||
unlinked = !!(startOutsideRange || endOutsideRange || !note.date);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +68,7 @@
|
|||||||
<h2 class="text-xl font-bold break-words">{note.name}</h2>
|
<h2 class="text-xl font-bold break-words">{note.name}</h2>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<div class="badge badge-primary">{$t('adventures.note')}</div>
|
<div class="badge badge-primary">{$t('adventures.note')}</div>
|
||||||
{#if unlinked}
|
{#if outsideCollectionRange}
|
||||||
<div class="badge badge-error">{$t('adventures.out_of_range')}</div>
|
<div class="badge badge-error">{$t('adventures.out_of_range')}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
export let collection: Collection;
|
export let collection: Collection;
|
||||||
export let user: User | null = null;
|
export let user: User | null = null;
|
||||||
|
|
||||||
let constrainDates: boolean = false;
|
let constrainDates: boolean = true;
|
||||||
|
|
||||||
let isReadOnly =
|
let isReadOnly =
|
||||||
!(note && user?.uuid == note?.user) &&
|
!(note && user?.uuid == note?.user) &&
|
||||||
|
|||||||
@@ -8,7 +8,11 @@
|
|||||||
import DeleteWarning from './DeleteWarning.svelte';
|
import DeleteWarning from './DeleteWarning.svelte';
|
||||||
// import ArrowDownThick from '~icons/mdi/arrow-down-thick';
|
// import ArrowDownThick from '~icons/mdi/arrow-down-thick';
|
||||||
import { TRANSPORTATION_TYPES_ICONS } from '$lib';
|
import { TRANSPORTATION_TYPES_ICONS } from '$lib';
|
||||||
import { formatAllDayDate, formatDateInTimezone } from '$lib/dateUtils';
|
import {
|
||||||
|
formatAllDayDate,
|
||||||
|
formatDateInTimezone,
|
||||||
|
isEntityOutsideCollectionDateRange
|
||||||
|
} from '$lib/dateUtils';
|
||||||
import { isAllDay } from '$lib';
|
import { isAllDay } from '$lib';
|
||||||
import CardCarousel from './CardCarousel.svelte';
|
import CardCarousel from './CardCarousel.svelte';
|
||||||
|
|
||||||
@@ -36,52 +40,11 @@
|
|||||||
dispatch('edit', transportation);
|
dispatch('edit', transportation);
|
||||||
}
|
}
|
||||||
|
|
||||||
let unlinked: boolean = false;
|
let outsideCollectionRange: boolean = false;
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (collection?.start_date && collection.end_date) {
|
if (collection) {
|
||||||
// Parse transportation dates
|
outsideCollectionRange = isEntityOutsideCollectionDateRange(transportation, collection);
|
||||||
let transportationStartDate = transportation.date
|
|
||||||
? new Date(transportation.date.split('T')[0]) // Ensure proper date parsing
|
|
||||||
: null;
|
|
||||||
let transportationEndDate = transportation.end_date
|
|
||||||
? new Date(transportation.end_date.split('T')[0])
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// Parse collection dates
|
|
||||||
let collectionStartDate = new Date(collection.start_date);
|
|
||||||
let collectionEndDate = new Date(collection.end_date);
|
|
||||||
|
|
||||||
// // Debugging outputs
|
|
||||||
// console.log(
|
|
||||||
// 'Transportation Start Date:',
|
|
||||||
// transportationStartDate,
|
|
||||||
// 'Transportation End Date:',
|
|
||||||
// transportationEndDate
|
|
||||||
// );
|
|
||||||
// console.log(
|
|
||||||
// 'Collection Start Date:',
|
|
||||||
// collectionStartDate,
|
|
||||||
// 'Collection End Date:',
|
|
||||||
// collectionEndDate
|
|
||||||
// );
|
|
||||||
|
|
||||||
// Check if the collection range is outside the transportation range
|
|
||||||
const startOutsideRange =
|
|
||||||
transportationStartDate &&
|
|
||||||
collectionStartDate < transportationStartDate &&
|
|
||||||
collectionEndDate < transportationStartDate;
|
|
||||||
|
|
||||||
const endOutsideRange =
|
|
||||||
transportationEndDate &&
|
|
||||||
collectionStartDate > transportationEndDate &&
|
|
||||||
collectionEndDate > transportationEndDate;
|
|
||||||
|
|
||||||
unlinked = !!(
|
|
||||||
startOutsideRange ||
|
|
||||||
endOutsideRange ||
|
|
||||||
(!transportationStartDate && !transportationEndDate)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +128,7 @@
|
|||||||
{#if transportation.type === 'plane' && transportation.flight_number}
|
{#if transportation.type === 'plane' && transportation.flight_number}
|
||||||
<div class="badge badge-neutral">{transportation.flight_number}</div>
|
<div class="badge badge-neutral">{transportation.flight_number}</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if unlinked}
|
{#if outsideCollectionRange}
|
||||||
<div class="badge badge-error">{$t('adventures.out_of_range')}</div>
|
<div class="badge badge-error">{$t('adventures.out_of_range')}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -173,6 +173,10 @@
|
|||||||
Math.round(transportation.destination_longitude * 1e6) / 1e6;
|
Math.round(transportation.destination_longitude * 1e6) / 1e6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (transportation.date && !transportation.end_date) {
|
||||||
|
transportation.end_date = transportation.date;
|
||||||
|
}
|
||||||
|
|
||||||
if (!transportation.type) {
|
if (!transportation.type) {
|
||||||
transportation.type = 'other';
|
transportation.type = 'other';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
import ArrowLeftIcon from '~icons/mdi/arrow-left';
|
import ArrowLeftIcon from '~icons/mdi/arrow-left';
|
||||||
import RunFastIcon from '~icons/mdi/run-fast';
|
import RunFastIcon from '~icons/mdi/run-fast';
|
||||||
import LoadingIcon from '~icons/mdi/loading';
|
import LoadingIcon from '~icons/mdi/loading';
|
||||||
|
import InfoIcon from '~icons/mdi/information';
|
||||||
import UploadIcon from '~icons/mdi/upload';
|
import UploadIcon from '~icons/mdi/upload';
|
||||||
import FileIcon from '~icons/mdi/file';
|
import FileIcon from '~icons/mdi/file';
|
||||||
import CloseIcon from '~icons/mdi/close';
|
import CloseIcon from '~icons/mdi/close';
|
||||||
@@ -1535,6 +1536,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- if localStartDate and localEndDate are set, show a callout saying its not saved yet -->
|
||||||
|
{#if localStartDate || localEndDate}
|
||||||
|
<div class="alert alert-neutral">
|
||||||
|
<InfoIcon class="w-5 h-5" />
|
||||||
|
<div>
|
||||||
|
<div class="font-medium text-sm">{$t('adventures.dates_not_saved')}</div>
|
||||||
|
<div class="text-xs opacity-75">{$t('adventures.dates_not_saved_description')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="flex gap-3 justify-end pt-4">
|
<div class="flex gap-3 justify-end pt-4">
|
||||||
<button class="btn btn-neutral-200 gap-2" on:click={handleBack}>
|
<button class="btn btn-neutral-200 gap-2" on:click={handleBack}>
|
||||||
<ArrowLeftIcon class="w-5 h-5" />
|
<ArrowLeftIcon class="w-5 h-5" />
|
||||||
|
|||||||
@@ -1,34 +1,27 @@
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
import type { Checklist, Collection, Lodging, Note, Transportation, Visit } from './types';
|
||||||
|
import { isAllDay } from '$lib';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a UTC ISO date to a datetime-local value in the specified timezone
|
* Convert a UTC ISO date to a datetime-local value in the specified timezone
|
||||||
* @param utcDate - UTC date in ISO format or null
|
|
||||||
* @param timezone - Target timezone (defaults to browser timezone)
|
|
||||||
* @returns Formatted local datetime string for input fields (YYYY-MM-DDTHH:MM)
|
|
||||||
*/
|
*/
|
||||||
export function toLocalDatetime(
|
export function toLocalDatetime(
|
||||||
utcDate: string | null,
|
utcDate: string | null,
|
||||||
timezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone
|
timezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||||
): string {
|
): string {
|
||||||
if (!utcDate) return '';
|
if (!utcDate) return '';
|
||||||
|
|
||||||
const dt = DateTime.fromISO(utcDate, { zone: 'UTC' });
|
const dt = DateTime.fromISO(utcDate, { zone: 'UTC' });
|
||||||
if (!dt.isValid) return '';
|
if (!dt.isValid) return '';
|
||||||
|
|
||||||
const isoString = dt.setZone(timezone).toISO({
|
const isoString = dt.setZone(timezone).toISO({
|
||||||
suppressSeconds: true,
|
suppressSeconds: true,
|
||||||
includeOffset: false
|
includeOffset: false
|
||||||
});
|
});
|
||||||
|
|
||||||
return isoString ? isoString.slice(0, 16) : '';
|
return isoString ? isoString.slice(0, 16) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a local datetime to UTC
|
* Convert a local datetime to UTC
|
||||||
* @param localDate - Local datetime string in ISO format
|
|
||||||
* @param timezone - Source timezone (defaults to browser timezone)
|
|
||||||
* @returns UTC datetime in ISO format or null
|
|
||||||
*/
|
*/
|
||||||
export function toUTCDatetime(
|
export function toUTCDatetime(
|
||||||
localDate: string,
|
localDate: string,
|
||||||
@@ -36,15 +29,12 @@ export function toUTCDatetime(
|
|||||||
allDay: boolean = false
|
allDay: boolean = false
|
||||||
): string | null {
|
): string | null {
|
||||||
if (!localDate) return null;
|
if (!localDate) return null;
|
||||||
|
|
||||||
if (allDay) {
|
if (allDay) {
|
||||||
// Treat input as date-only, set UTC midnight manually
|
// Treat as date only, set UTC midnight
|
||||||
return DateTime.fromISO(localDate, { zone: 'UTC' })
|
return DateTime.fromISO(localDate, { zone: 'UTC' })
|
||||||
.startOf('day')
|
.startOf('day')
|
||||||
.toISO({ suppressMilliseconds: true });
|
.toISO({ suppressMilliseconds: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal timezone conversion for datetime-local input
|
|
||||||
return DateTime.fromISO(localDate, { zone: timezone })
|
return DateTime.fromISO(localDate, { zone: timezone })
|
||||||
.toUTC()
|
.toUTC()
|
||||||
.toISO({ suppressMilliseconds: true });
|
.toISO({ suppressMilliseconds: true });
|
||||||
@@ -52,8 +42,6 @@ export function toUTCDatetime(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates local datetime values based on UTC date and timezone
|
* Updates local datetime values based on UTC date and timezone
|
||||||
* @param params Object containing UTC date and timezone
|
|
||||||
* @returns Object with updated local datetime string
|
|
||||||
*/
|
*/
|
||||||
export function updateLocalDate({
|
export function updateLocalDate({
|
||||||
utcDate,
|
utcDate,
|
||||||
@@ -62,15 +50,11 @@ export function updateLocalDate({
|
|||||||
utcDate: string | null;
|
utcDate: string | null;
|
||||||
timezone: string;
|
timezone: string;
|
||||||
}) {
|
}) {
|
||||||
return {
|
return { localDate: toLocalDatetime(utcDate, timezone) };
|
||||||
localDate: toLocalDatetime(utcDate, timezone)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates UTC datetime values based on local datetime and timezone
|
* Updates UTC datetime values based on local datetime and timezone
|
||||||
* @param params Object containing local date and timezone
|
|
||||||
* @returns Object with updated UTC datetime string
|
|
||||||
*/
|
*/
|
||||||
export function updateUTCDate({
|
export function updateUTCDate({
|
||||||
localDate,
|
localDate,
|
||||||
@@ -81,40 +65,27 @@ export function updateUTCDate({
|
|||||||
timezone: string;
|
timezone: string;
|
||||||
allDay?: boolean;
|
allDay?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return {
|
return { utcDate: toUTCDatetime(localDate, timezone, allDay) };
|
||||||
utcDate: toUTCDatetime(localDate, timezone, allDay)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate date ranges using UTC comparison
|
* Validate date ranges using UTC comparison
|
||||||
* @param startDate - Start date string in UTC (ISO format)
|
|
||||||
* @param endDate - End date string in UTC (ISO format)
|
|
||||||
* @returns Object with validation result and optional error message
|
|
||||||
*/
|
*/
|
||||||
export function validateDateRange(
|
export function validateDateRange(
|
||||||
startDate: string,
|
startDate: string,
|
||||||
endDate: string
|
endDate: string
|
||||||
): { valid: boolean; error?: string } {
|
): { valid: boolean; error?: string } {
|
||||||
if (endDate && !startDate) {
|
if (endDate && !startDate) {
|
||||||
return {
|
return { valid: false, error: 'Start date is required when end date is provided' };
|
||||||
valid: false,
|
|
||||||
error: 'Start date is required when end date is provided'
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
startDate &&
|
startDate &&
|
||||||
endDate &&
|
endDate &&
|
||||||
DateTime.fromISO(startDate, { zone: 'utc' }).toMillis() >
|
DateTime.fromISO(startDate, { zone: 'utc' }).toMillis() >
|
||||||
DateTime.fromISO(endDate, { zone: 'utc' }).toMillis()
|
DateTime.fromISO(endDate, { zone: 'utc' }).toMillis()
|
||||||
) {
|
) {
|
||||||
return {
|
return { valid: false, error: 'Start date must be before end date (based on UTC)' };
|
||||||
valid: false,
|
|
||||||
error: 'Start date must be before end date (based on UTC)'
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { valid: true };
|
return { valid: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,11 +106,6 @@ export function formatDateInTimezone(utcDate: string, timezone: string | null):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Format UTC date for display
|
|
||||||
* @param utcDate - UTC date in ISO format
|
|
||||||
* @returns Formatted date string without seconds (YYYY-MM-DD HH:MM)
|
|
||||||
*/
|
|
||||||
export function formatUTCDate(utcDate: string | null): string {
|
export function formatUTCDate(utcDate: string | null): string {
|
||||||
if (!utcDate) return '';
|
if (!utcDate) return '';
|
||||||
const dateTime = DateTime.fromISO(utcDate);
|
const dateTime = DateTime.fromISO(utcDate);
|
||||||
@@ -147,18 +113,10 @@ export function formatUTCDate(utcDate: string | null): string {
|
|||||||
return dateTime.toISO()?.slice(0, 16).replace('T', ' ') || '';
|
return dateTime.toISO()?.slice(0, 16).replace('T', ' ') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Format all-day date for display without timezone conversion
|
|
||||||
* @param dateString - Date string in ISO format (YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS)
|
|
||||||
* @returns Formatted date string (e.g., "Jun 1, 2025")
|
|
||||||
*/
|
|
||||||
export function formatAllDayDate(dateString: string): string {
|
export function formatAllDayDate(dateString: string): string {
|
||||||
if (!dateString) return '';
|
if (!dateString) return '';
|
||||||
|
|
||||||
// Extract just the date part and add midday time to avoid timezone issues
|
|
||||||
const datePart = dateString.split('T')[0];
|
const datePart = dateString.split('T')[0];
|
||||||
const dateWithMidday = `${datePart}T12:00:00`;
|
const dateWithMidday = `${datePart}T12:00:00`;
|
||||||
|
|
||||||
return new Intl.DateTimeFormat('en-US', {
|
return new Intl.DateTimeFormat('en-US', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'short',
|
month: 'short',
|
||||||
@@ -166,6 +124,150 @@ export function formatAllDayDate(dateString: string): string {
|
|||||||
}).format(new Date(dateWithMidday));
|
}).format(new Date(dateWithMidday));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==== FIXED TIMEZONE-AWARE DATE RANGE LOGIC ====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts start and end dates from various entity types (Luxon DateTime)
|
||||||
|
* Returns also isAllDay flag for correct comparison logic
|
||||||
|
*/
|
||||||
|
function getEntityDateRange(entity: Visit | Transportation | Lodging | Note | Checklist): {
|
||||||
|
start: DateTime | null;
|
||||||
|
end: DateTime | null;
|
||||||
|
isAllDay: boolean;
|
||||||
|
} {
|
||||||
|
let start: DateTime | null = null;
|
||||||
|
let end: DateTime | null = null;
|
||||||
|
let isAllDayEvent = false;
|
||||||
|
try {
|
||||||
|
let timezone = (entity as Visit).timezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
|
if ('start_date' in entity && 'end_date' in entity) {
|
||||||
|
// Check if all-day (no time portion)
|
||||||
|
isAllDayEvent = isAllDay(entity.start_date) && isAllDay(entity.end_date);
|
||||||
|
console;
|
||||||
|
if (isAllDayEvent) {
|
||||||
|
start = entity.start_date
|
||||||
|
? DateTime.fromISO(entity.start_date.split('T')[0], { zone: 'UTC' }).startOf('day')
|
||||||
|
: null;
|
||||||
|
end = entity.end_date
|
||||||
|
? DateTime.fromISO(entity.end_date.split('T')[0], { zone: 'UTC' }).endOf('day')
|
||||||
|
: null;
|
||||||
|
} else {
|
||||||
|
start = DateTime.fromISO(entity.start_date, { zone: 'UTC' }).setZone(timezone);
|
||||||
|
end = DateTime.fromISO(entity.end_date, { zone: 'UTC' }).setZone(timezone);
|
||||||
|
}
|
||||||
|
} else if ('date' in entity && 'end_date' in entity) {
|
||||||
|
isAllDayEvent = !!(entity.date && entity.date.length === 10);
|
||||||
|
if (isAllDayEvent) {
|
||||||
|
start = DateTime.fromISO(entity.date, { zone: 'UTC' }).startOf('day');
|
||||||
|
end = DateTime.fromISO(entity.end_date, { zone: 'UTC' }).endOf('day');
|
||||||
|
} else {
|
||||||
|
start = DateTime.fromISO(entity.date, { zone: 'UTC' }).setZone(timezone);
|
||||||
|
end = DateTime.fromISO(entity.end_date, { zone: 'UTC' }).setZone(timezone);
|
||||||
|
}
|
||||||
|
} else if ('check_in' in entity && 'check_out' in entity) {
|
||||||
|
isAllDayEvent = !!(entity.check_in && entity.check_in.length === 10);
|
||||||
|
if (isAllDayEvent) {
|
||||||
|
start = DateTime.fromISO(entity.check_in, { zone: 'UTC' }).startOf('day');
|
||||||
|
end = DateTime.fromISO(entity.check_out, { zone: 'UTC' }).endOf('day');
|
||||||
|
} else {
|
||||||
|
start = DateTime.fromISO(entity.check_in, { zone: 'UTC' }).setZone(timezone);
|
||||||
|
end = DateTime.fromISO(entity.check_out, { zone: 'UTC' }).setZone(timezone);
|
||||||
|
}
|
||||||
|
} else if ('date' in entity) {
|
||||||
|
isAllDayEvent = !!(entity.date && entity.date.length === 10);
|
||||||
|
if (isAllDayEvent) {
|
||||||
|
start = DateTime.fromISO(entity.date, { zone: 'UTC' }).startOf('day');
|
||||||
|
end = start;
|
||||||
|
} else {
|
||||||
|
start = DateTime.fromISO(entity.date, { zone: 'UTC' }).setZone(timezone);
|
||||||
|
end = start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing entity dates:', error);
|
||||||
|
}
|
||||||
|
return { start, end, isAllDay: isAllDayEvent };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract collection start/end as Luxon DateTime w/allDay logic
|
||||||
|
*/
|
||||||
|
function getCollectionDateRange(collection: Collection): {
|
||||||
|
start: DateTime | null;
|
||||||
|
end: DateTime | null;
|
||||||
|
isAllDay: boolean;
|
||||||
|
} {
|
||||||
|
if (!collection.start_date || !collection.end_date) {
|
||||||
|
return { start: null, end: null, isAllDay: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume collection always uses full datetimes in ISO string
|
||||||
|
const isAllDay = collection.start_date.length === 10 && collection.end_date.length === 10;
|
||||||
|
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
|
const start = isAllDay
|
||||||
|
? DateTime.fromISO(collection.start_date, { zone: 'UTC' }).startOf('day')
|
||||||
|
: DateTime.fromISO(collection.start_date, { zone: 'UTC' }).setZone(timezone);
|
||||||
|
const end = isAllDay
|
||||||
|
? DateTime.fromISO(collection.end_date, { zone: 'UTC' }).endOf('day')
|
||||||
|
: DateTime.fromISO(collection.end_date, { zone: 'UTC' }).setZone(timezone);
|
||||||
|
return { start, end, isAllDay };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an entity falls within a collection's date range (timezone-safe, all-day-aware)
|
||||||
|
*/
|
||||||
|
export function isEntityInCollectionDateRange(
|
||||||
|
entity: Visit | Transportation | Lodging | Note | Checklist,
|
||||||
|
collection: Collection
|
||||||
|
): boolean {
|
||||||
|
if (!collection?.start_date || !collection.end_date) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { start: entityStart, end: entityEnd, isAllDay: entityAllDay } = getEntityDateRange(entity);
|
||||||
|
const {
|
||||||
|
start: collStart,
|
||||||
|
end: collEnd,
|
||||||
|
isAllDay: collAllDay
|
||||||
|
} = getCollectionDateRange(collection);
|
||||||
|
|
||||||
|
// If any dates are missing, don't match
|
||||||
|
if (!entityStart || !collStart) return false;
|
||||||
|
|
||||||
|
// If either side is all-day, use date comparison
|
||||||
|
if (entityAllDay || collAllDay) {
|
||||||
|
// Compare only date components
|
||||||
|
const entStartDate = entityStart.startOf('day');
|
||||||
|
const entEndDate = (entityEnd || entityStart).endOf('day');
|
||||||
|
const colStartDate = collStart.startOf('day');
|
||||||
|
const colEndDate = collEnd.endOf('day');
|
||||||
|
return entStartDate <= colEndDate && entEndDate >= colStartDate;
|
||||||
|
} else {
|
||||||
|
// Compare actual DateTimes
|
||||||
|
const entEnd = entityEnd || entityStart;
|
||||||
|
return entityStart <= collEnd && entEnd >= collStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isEntityOutsideCollectionDateRange(
|
||||||
|
entity: Visit | Transportation | Lodging | Note | Checklist,
|
||||||
|
collection: Collection
|
||||||
|
): boolean {
|
||||||
|
return !isEntityInCollectionDateRange(entity, collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEntitiesInDateRange<
|
||||||
|
T extends Visit | Transportation | Lodging | Note | Checklist
|
||||||
|
>(entities: T[], collection: Collection): T[] {
|
||||||
|
return entities.filter((entity) => isEntityInCollectionDateRange(entity, collection));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEntitiesOutsideDateRange<
|
||||||
|
T extends Visit | Transportation | Lodging | Note | Checklist
|
||||||
|
>(entities: T[], collection: Collection): T[] {
|
||||||
|
return entities.filter((entity) => isEntityOutsideCollectionDateRange(entity, collection));
|
||||||
|
}
|
||||||
|
|
||||||
export const VALID_TIMEZONES = [
|
export const VALID_TIMEZONES = [
|
||||||
'Africa/Abidjan',
|
'Africa/Abidjan',
|
||||||
'Africa/Accra',
|
'Africa/Accra',
|
||||||
|
|||||||
@@ -422,7 +422,9 @@
|
|||||||
"recorded_sessions": "جلسات مسجلة",
|
"recorded_sessions": "جلسات مسجلة",
|
||||||
"total_activities": "الأنشطة الكلية",
|
"total_activities": "الأنشطة الكلية",
|
||||||
"total_climbed": "مجموع تسلق",
|
"total_climbed": "مجموع تسلق",
|
||||||
"total_distance": "إجمالي المسافة"
|
"total_distance": "إجمالي المسافة",
|
||||||
|
"dates_not_saved": "زيارة لم تتم إضافتها بعد",
|
||||||
|
"dates_not_saved_description": "انقر فوق إضافة زيارة إلى حفظ"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"confirm_password": "تأكيد كلمة المرور",
|
"confirm_password": "تأكيد كلمة المرور",
|
||||||
|
|||||||
@@ -423,7 +423,9 @@
|
|||||||
"recorded_sessions": "Aufgenommene Sitzungen",
|
"recorded_sessions": "Aufgenommene Sitzungen",
|
||||||
"total_activities": "Gesamtaktivitäten",
|
"total_activities": "Gesamtaktivitäten",
|
||||||
"total_climbed": "Total bestiegen",
|
"total_climbed": "Total bestiegen",
|
||||||
"total_distance": "Gesamtabstand"
|
"total_distance": "Gesamtabstand",
|
||||||
|
"dates_not_saved": "Besuchen Sie noch nicht hinzugefügt",
|
||||||
|
"dates_not_saved_description": "Klicken Sie auf Besuchen Sie, um sie zu speichern"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"desc_1": "Entdecken, planen und erkunden Sie mühelos",
|
"desc_1": "Entdecken, planen und erkunden Sie mühelos",
|
||||||
|
|||||||
@@ -476,7 +476,9 @@
|
|||||||
"total_distance": "Total Distance",
|
"total_distance": "Total Distance",
|
||||||
"total_activities": "Total Activities",
|
"total_activities": "Total Activities",
|
||||||
"recorded_sessions": "Recorded sessions",
|
"recorded_sessions": "Recorded sessions",
|
||||||
"activity_breakdown_by_category": "Activity Breakdown by Category"
|
"activity_breakdown_by_category": "Activity Breakdown by Category",
|
||||||
|
"dates_not_saved": "Visit Not Added Yet",
|
||||||
|
"dates_not_saved_description": "Click add visit to save"
|
||||||
},
|
},
|
||||||
"worldtravel": {
|
"worldtravel": {
|
||||||
"country_list": "Country List",
|
"country_list": "Country List",
|
||||||
|
|||||||
@@ -126,7 +126,7 @@
|
|||||||
"image_upload_error": "Error al subir la imagen",
|
"image_upload_error": "Error al subir la imagen",
|
||||||
"image_upload_success": "¡Imagen cargada exitosamente!",
|
"image_upload_success": "¡Imagen cargada exitosamente!",
|
||||||
"no_image_url": "No se encontró ninguna imagen en esa URL.",
|
"no_image_url": "No se encontró ninguna imagen en esa URL.",
|
||||||
"start_before_end_error": "La fecha de inicio debe ser anterior a la fecha de finalización.",
|
"start_before_end_error": "La fecha de inicio debe ser anterior a la fecha de finalización",
|
||||||
"wiki_image_error": "Error al obtener la imagen de Wikipedia",
|
"wiki_image_error": "Error al obtener la imagen de Wikipedia",
|
||||||
"actions": "Acciones",
|
"actions": "Acciones",
|
||||||
"see_adventures": "Ver Aventuras",
|
"see_adventures": "Ver Aventuras",
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
"url": "URL",
|
"url": "URL",
|
||||||
"warning": "Advertencia",
|
"warning": "Advertencia",
|
||||||
"wikipedia": "Wikipedia",
|
"wikipedia": "Wikipedia",
|
||||||
"adventure_not_found": "No hay aventuras que mostrar. \n¡Agregue algunas usando el botón más en la parte inferior derecha o intente cambiar los filtros!",
|
"adventure_not_found": "No hay aventuras que mostrar. ¡Agregue algunas usando el botón más en la parte inferior derecha o intente cambiar los filtros!",
|
||||||
"no_adventures_found": "No se encontraron aventuras",
|
"no_adventures_found": "No se encontraron aventuras",
|
||||||
"my_adventures": "Mis aventuras",
|
"my_adventures": "Mis aventuras",
|
||||||
"no_linkable_adventures": "No se encontraron aventuras que puedan vincularse a esta colección.",
|
"no_linkable_adventures": "No se encontraron aventuras que puedan vincularse a esta colección.",
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"links": "Enlaces",
|
"links": "Enlaces",
|
||||||
"note": "Nota",
|
"note": "Nota",
|
||||||
"notes": "Notas",
|
"notes": "Notas",
|
||||||
"nothing_planned": "Nada planeado para este día. \n¡Disfruta el viaje!",
|
"nothing_planned": "Nada planeado para este día. ¡Disfruta el viaje!",
|
||||||
"transportation": "Transporte",
|
"transportation": "Transporte",
|
||||||
"transportations": "Transportes",
|
"transportations": "Transportes",
|
||||||
"visit_link": "Visitar enlace",
|
"visit_link": "Visitar enlace",
|
||||||
@@ -217,12 +217,12 @@
|
|||||||
"flight_information": "Información de vuelo",
|
"flight_information": "Información de vuelo",
|
||||||
"from": "De",
|
"from": "De",
|
||||||
"no_location_found": "No se encontró ninguna ubicación",
|
"no_location_found": "No se encontró ninguna ubicación",
|
||||||
"note_delete_confirm": "¿Estás seguro de que deseas eliminar esta nota? \nEsta acción no se puede deshacer.",
|
"note_delete_confirm": "¿Estás seguro de que deseas eliminar esta nota? Esta acción no se puede deshacer.",
|
||||||
"out_of_range": "No en el rango de fechas del itinerario",
|
"out_of_range": "No en el rango de fechas del itinerario",
|
||||||
"start": "Comenzar",
|
"start": "Comenzar",
|
||||||
"starting_airport": "Aeropuerto de inicio",
|
"starting_airport": "Aeropuerto de inicio",
|
||||||
"to": "A",
|
"to": "A",
|
||||||
"transportation_delete_confirm": "¿Está seguro de que desea eliminar este transporte? \nEsta acción no se puede deshacer.",
|
"transportation_delete_confirm": "¿Está seguro de que desea eliminar este transporte? Esta acción no se puede deshacer.",
|
||||||
"cities_updated": "ciudades actualizadas",
|
"cities_updated": "ciudades actualizadas",
|
||||||
"finding_recommendations": "Descubriendo gemas ocultas para tu próxima aventura",
|
"finding_recommendations": "Descubriendo gemas ocultas para tu próxima aventura",
|
||||||
"attachment": "Adjunto",
|
"attachment": "Adjunto",
|
||||||
@@ -241,7 +241,7 @@
|
|||||||
"display_name": "Nombre para mostrar",
|
"display_name": "Nombre para mostrar",
|
||||||
"location_details": "Detalles de la ubicación",
|
"location_details": "Detalles de la ubicación",
|
||||||
"lodging": "Alojamiento",
|
"lodging": "Alojamiento",
|
||||||
"lodging_delete_confirm": "¿Estás seguro de que quieres eliminar este lugar de alojamiento? \nEsta acción no se puede deshacer.",
|
"lodging_delete_confirm": "¿Estás seguro de que quieres eliminar este lugar de alojamiento? Esta acción no se puede deshacer.",
|
||||||
"lodging_information": "Información de alojamiento",
|
"lodging_information": "Información de alojamiento",
|
||||||
"price": "Precio",
|
"price": "Precio",
|
||||||
"region": "Región",
|
"region": "Región",
|
||||||
@@ -272,14 +272,14 @@
|
|||||||
"view_profile": "Ver perfil",
|
"view_profile": "Ver perfil",
|
||||||
"share_collection": "¡Comparte esta colección!",
|
"share_collection": "¡Comparte esta colección!",
|
||||||
"filters_and_sort": "Filtros",
|
"filters_and_sort": "Filtros",
|
||||||
"no_adventures_message": "Comience a documentar sus aventuras y planificar nuevas. \nCada viaje tiene una historia que vale la pena contar.",
|
"no_adventures_message": "Comience a documentar sus aventuras y planificar nuevas. Cada viaje tiene una historia que vale la pena contar.",
|
||||||
"filters_and_stats": "Filtros",
|
"filters_and_stats": "Filtros",
|
||||||
"travel_progress": "Progreso del viaje",
|
"travel_progress": "Progreso del viaje",
|
||||||
"adventures_available": "Aventuras disponibles",
|
"adventures_available": "Aventuras disponibles",
|
||||||
"all_adventures_already_linked": "Todas las aventuras ya están vinculadas a esta colección.",
|
"all_adventures_already_linked": "Todas las aventuras ya están vinculadas a esta colección.",
|
||||||
"collections_linked": "Colecciones vinculadas",
|
"collections_linked": "Colecciones vinculadas",
|
||||||
"create_collection_first": "Cree una colección primero para organizar sus aventuras y recuerdos.",
|
"create_collection_first": "Cree una colección primero para organizar sus aventuras y recuerdos.",
|
||||||
"delete_collection_warning": "¿Estás seguro de que quieres eliminar esta colección? \nEsta acción no se puede deshacer.",
|
"delete_collection_warning": "¿Estás seguro de que quieres eliminar esta colección? Esta acción no se puede deshacer.",
|
||||||
"done": "Hecho",
|
"done": "Hecho",
|
||||||
"loading_adventures": "Cargando aventuras ...",
|
"loading_adventures": "Cargando aventuras ...",
|
||||||
"name_location": "Nombre, ubicación",
|
"name_location": "Nombre, ubicación",
|
||||||
@@ -293,18 +293,18 @@
|
|||||||
"collection_remove_location_success": "¡Ubicación eliminada de la colección con éxito!",
|
"collection_remove_location_success": "¡Ubicación eliminada de la colección con éxito!",
|
||||||
"create_location": "Crear ubicación",
|
"create_location": "Crear ubicación",
|
||||||
"delete_location": "Eliminar la ubicación",
|
"delete_location": "Eliminar la ubicación",
|
||||||
"edit_location": "Ubicación de edición",
|
"edit_location": "Editar ubicación",
|
||||||
"location_create_error": "No se pudo crear la ubicación",
|
"location_create_error": "No se pudo crear la ubicación",
|
||||||
"location_created": "Ubicación creada",
|
"location_created": "Ubicación creada",
|
||||||
"location_delete_confirm": "¿Estás seguro de que quieres eliminar esta ubicación? Esta acción no se puede deshacer.",
|
"location_delete_confirm": "¿Estás seguro de que quieres eliminar esta ubicación? Esta acción no se puede deshacer.",
|
||||||
"location_delete_success": "Ubicación eliminada con éxito!",
|
"location_delete_success": "Ubicación eliminada con éxito!",
|
||||||
"location_not_found": "Ubicación no encontrada",
|
"location_not_found": "Ubicación no encontrada",
|
||||||
"location_not_found_desc": "No se podía encontrar la ubicación que estaba buscando. \nPruebe una ubicación diferente o vuelva a consultar más tarde.",
|
"location_not_found_desc": "No se ha encontrado la ubicación que está buscando. Pruebe con otra ubicación o vuelva a intentarlo más tarde.",
|
||||||
"location_update_error": "No se pudo actualizar la ubicación",
|
"location_update_error": "No se pudo actualizar la ubicación",
|
||||||
"location_updated": "Ubicación actualizada",
|
"location_updated": "Ubicación actualizada",
|
||||||
"new_location": "Nueva ubicación",
|
"new_location": "Nueva ubicación",
|
||||||
"no_collections_to_add_location": "No se encuentran colecciones para agregar esta ubicación a.",
|
"no_collections_to_add_location": "No se encuentran colecciones para agregar esta ubicación a.",
|
||||||
"no_locations_to_recommendations": "No se encontraron ubicaciones. \nAgregue al menos una ubicación para obtener recomendaciones.",
|
"no_locations_to_recommendations": "No se encontraron ubicaciones. Agregue al menos una ubicación para obtener recomendaciones.",
|
||||||
"public_location": "Ubicación pública",
|
"public_location": "Ubicación pública",
|
||||||
"share_location": "¡Comparte esta ubicación!",
|
"share_location": "¡Comparte esta ubicación!",
|
||||||
"visit_calendar": "Visitar el calendario",
|
"visit_calendar": "Visitar el calendario",
|
||||||
@@ -315,10 +315,10 @@
|
|||||||
"details": "Detalles",
|
"details": "Detalles",
|
||||||
"leave": "Dejar",
|
"leave": "Dejar",
|
||||||
"leave_collection": "Recolección de licencia",
|
"leave_collection": "Recolección de licencia",
|
||||||
"leave_collection_warning": "¿Estás seguro de que quieres dejar esta colección? \nCualquier ubicación que agregó no estará vinculado y permanecerá en su cuenta.",
|
"leave_collection_warning": "¿Estás seguro de que quieres dejar esta colección? Cualquier ubicación que agregó no estará vinculado y permanecerá en su cuenta.",
|
||||||
"left_collection_message": "Colección de izquierda con éxito",
|
"left_collection_message": "Colección de izquierda con éxito",
|
||||||
"loading_collections": "Cargando colecciones ...",
|
"loading_collections": "Cargando colecciones ...",
|
||||||
"quick_start": "Comienzo rápido",
|
"quick_start": "Inicio rápido",
|
||||||
"click_map": "Haga clic en el mapa para seleccionar una ubicación",
|
"click_map": "Haga clic en el mapa para seleccionar una ubicación",
|
||||||
"continue": "Continuar",
|
"continue": "Continuar",
|
||||||
"getting_location_details": "Obtener detalles de ubicación",
|
"getting_location_details": "Obtener detalles de ubicación",
|
||||||
@@ -346,13 +346,13 @@
|
|||||||
"attachment_remove_error": "Se produjo un error al eliminar el archivo adjunto",
|
"attachment_remove_error": "Se produjo un error al eliminar el archivo adjunto",
|
||||||
"attachment_removed": "Adjunto eliminado con éxito",
|
"attachment_removed": "Adjunto eliminado con éxito",
|
||||||
"attachment_updated": "Adjunto actualizado con éxito",
|
"attachment_updated": "Adjunto actualizado con éxito",
|
||||||
"create_trail": "Crear rastro",
|
"create_trail": "Crear ruta",
|
||||||
"image_management": "Gestión de imágenes",
|
"image_management": "Gestión de imágenes",
|
||||||
"no_attachments_uploaded_yet": "No hay archivos adjuntos cargados todavía",
|
"no_attachments_uploaded_yet": "No hay archivos adjuntos cargados todavía",
|
||||||
"no_external_link": "No hay enlace externo disponible",
|
"no_external_link": "No hay enlace externo disponible",
|
||||||
"no_file_selected": "No hay archivo seleccionado",
|
"no_file_selected": "No hay archivo seleccionado",
|
||||||
"no_images_uploaded_yet": "No hay imágenes cargadas todavía",
|
"no_images_uploaded_yet": "No hay imágenes cargadas todavía",
|
||||||
"no_trails_added": "Todavía no se agregan senderos",
|
"no_trails_added": "Aún no se han añadido rutas",
|
||||||
"no_trails_available": "No hay senderos disponibles",
|
"no_trails_available": "No hay senderos disponibles",
|
||||||
"no_trails_found_matching": "No se encontraron senderos coincidentes",
|
"no_trails_found_matching": "No se encontraron senderos coincidentes",
|
||||||
"select_wanderer_trail": "Seleccione un sendero de su cuenta de Wanderer",
|
"select_wanderer_trail": "Seleccione un sendero de su cuenta de Wanderer",
|
||||||
@@ -365,8 +365,8 @@
|
|||||||
"trail_update_failed": "No se pudo actualizar el sendero",
|
"trail_update_failed": "No se pudo actualizar el sendero",
|
||||||
"trail_updated_successfully": "Sendero actualizado con éxito",
|
"trail_updated_successfully": "Sendero actualizado con éxito",
|
||||||
"trails_found_for": "senderos encontrados para",
|
"trails_found_for": "senderos encontrados para",
|
||||||
"trails_management": "Gestión de senderos",
|
"trails_management": "Gestión de rutas",
|
||||||
"trails_management_description": "Administre los senderos asociados con esta ubicación. \nLos senderos se pueden vincular a servicios externos como Alltrails o Link a Wanderer Trails.",
|
"trails_management_description": "Administre los senderos asociados con esta ubicación. Los senderos se pueden vincular a servicios externos como Alltrails o Link a Wanderer Trails.",
|
||||||
"upload_attachment": "Subir el archivo adjunto",
|
"upload_attachment": "Subir el archivo adjunto",
|
||||||
"upload_first_attachment": "Cargue su primer archivo adjunto utilizando las opciones anteriores",
|
"upload_first_attachment": "Cargue su primer archivo adjunto utilizando las opciones anteriores",
|
||||||
"upload_first_image": "Sube tu primera imagen usando una de las opciones de arriba",
|
"upload_first_image": "Sube tu primera imagen usando una de las opciones de arriba",
|
||||||
@@ -376,7 +376,7 @@
|
|||||||
"external_link": "Enlace externo",
|
"external_link": "Enlace externo",
|
||||||
"search_trails_placeholder": "Búsqueda de senderos por nombre",
|
"search_trails_placeholder": "Búsqueda de senderos por nombre",
|
||||||
"trail_name": "Nombre de sendero",
|
"trail_name": "Nombre de sendero",
|
||||||
"add_trail": "Agregar rastro",
|
"add_trail": "Añadir ruta",
|
||||||
"achievements": "Logros",
|
"achievements": "Logros",
|
||||||
"activity_options": "Opciones de actividad",
|
"activity_options": "Opciones de actividad",
|
||||||
"avg_speed": "Velocidad promedio",
|
"avg_speed": "Velocidad promedio",
|
||||||
@@ -414,7 +414,7 @@
|
|||||||
"end_lat": "Final de la latitud",
|
"end_lat": "Final de la latitud",
|
||||||
"end_lng": "Longitud final",
|
"end_lng": "Longitud final",
|
||||||
"gpx_file": "Archivo gpx",
|
"gpx_file": "Archivo gpx",
|
||||||
"gpx_file_downloaded": "Archivo GPX descargado. \nCárguelo a continuación para completar la importación.",
|
"gpx_file_downloaded": "Archivo GPX descargado. Cárguelo a continuación para completar la importación.",
|
||||||
"gpx_file_required": "Requerido el archivo GPX",
|
"gpx_file_required": "Requerido el archivo GPX",
|
||||||
"importing": "Importador",
|
"importing": "Importador",
|
||||||
"loading_activities": "Actividades de carga",
|
"loading_activities": "Actividades de carga",
|
||||||
@@ -475,7 +475,9 @@
|
|||||||
"recorded_sessions": "Sesiones grabadas",
|
"recorded_sessions": "Sesiones grabadas",
|
||||||
"total_activities": "Actividades totales",
|
"total_activities": "Actividades totales",
|
||||||
"total_climbed": "Total escalado",
|
"total_climbed": "Total escalado",
|
||||||
"total_distance": "Distancia total"
|
"total_distance": "Distancia total",
|
||||||
|
"dates_not_saved": "Visita aún no agregada",
|
||||||
|
"dates_not_saved_description": "Haga clic en Agregar visita a Guardar"
|
||||||
},
|
},
|
||||||
"worldtravel": {
|
"worldtravel": {
|
||||||
"all": "Todo",
|
"all": "Todo",
|
||||||
@@ -527,7 +529,7 @@
|
|||||||
"total_regions": "Total de regiones",
|
"total_regions": "Total de regiones",
|
||||||
"region_completed": "Región completada",
|
"region_completed": "Región completada",
|
||||||
"total_cities": "Ciudades totales",
|
"total_cities": "Ciudades totales",
|
||||||
"newest_first": "El primero primero",
|
"newest_first": "Más reciente primero",
|
||||||
"oldest_first": "El más antiguo primero",
|
"oldest_first": "El más antiguo primero",
|
||||||
"unvisited_first": "Primero no visitado",
|
"unvisited_first": "Primero no visitado",
|
||||||
"visited_first": "Visitado primero",
|
"visited_first": "Visitado primero",
|
||||||
@@ -576,7 +578,7 @@
|
|||||||
"update_error": "Error al actualizar la configuración",
|
"update_error": "Error al actualizar la configuración",
|
||||||
"update_success": "¡La configuración se actualizó correctamente!",
|
"update_success": "¡La configuración se actualizó correctamente!",
|
||||||
"change_password": "Cambiar la contraseña",
|
"change_password": "Cambiar la contraseña",
|
||||||
"possible_reset": "Si la dirección de correo electrónico que proporcionó está asociada con una cuenta, recibirá un correo electrónico con instrucciones para restablecer su contraseña.",
|
"possible_reset": "Si la dirección de correo electrónico que proporcionó está asociada con una cuenta, ¡recibirá un correo electrónico con instrucciones para restablecer su contraseña!",
|
||||||
"reset_password": "Restablecer contraseña",
|
"reset_password": "Restablecer contraseña",
|
||||||
"about_this_background": "Sobre este fondo",
|
"about_this_background": "Sobre este fondo",
|
||||||
"join_discord": "Únete a Discord",
|
"join_discord": "Únete a Discord",
|
||||||
@@ -595,7 +597,7 @@
|
|||||||
"primary": "Principal",
|
"primary": "Principal",
|
||||||
"verified": "Verificado",
|
"verified": "Verificado",
|
||||||
"verify": "Verificar",
|
"verify": "Verificar",
|
||||||
"verify_email_error": "Error al verificar el correo electrónico. \nInténtalo de nuevo en unos minutos.",
|
"verify_email_error": "Error al verificar el correo electrónico. Inténtalo de nuevo en unos minutos.",
|
||||||
"verify_email_success": "¡La verificación por correo electrónico se envió correctamente!",
|
"verify_email_success": "¡La verificación por correo electrónico se envió correctamente!",
|
||||||
"disable_mfa": "Deshabilitar MFA",
|
"disable_mfa": "Deshabilitar MFA",
|
||||||
"enable_mfa": "Habilitar MFA",
|
"enable_mfa": "Habilitar MFA",
|
||||||
@@ -605,22 +607,22 @@
|
|||||||
"copy": "Copiar",
|
"copy": "Copiar",
|
||||||
"mfa_enabled": "¡La autenticación multifactor se habilitó correctamente!",
|
"mfa_enabled": "¡La autenticación multifactor se habilitó correctamente!",
|
||||||
"recovery_codes": "Códigos de recuperación",
|
"recovery_codes": "Códigos de recuperación",
|
||||||
"recovery_codes_desc": "Estos son tus códigos de recuperación. \nMantenlos a salvo. \nNo podrás volver a verlos.",
|
"recovery_codes_desc": "Estos son tus códigos de recuperación. Mantenlos a salvo. No podrás volver a verlos.",
|
||||||
"reset_session_error": "Por favor cierre sesión y vuelva a iniciarla para actualizar su sesión e inténtelo nuevamente.",
|
"reset_session_error": "Por favor cierre sesión y vuelva a iniciarla para actualizar su sesión e inténtelo nuevamente.",
|
||||||
"authenticator_code": "Código de autenticación",
|
"authenticator_code": "Código de autenticación",
|
||||||
"email_verified": "¡Correo electrónico verificado exitosamente!",
|
"email_verified": "¡Correo electrónico verificado exitosamente!",
|
||||||
"email_verified_error": "Error al verificar el correo electrónico",
|
"email_verified_error": "Error al verificar el correo electrónico",
|
||||||
"email_verified_success": "Su correo electrónico ha sido verificado. \nAhora puedes iniciar sesión.",
|
"email_verified_success": "Su correo electrónico ha sido verificado. Ahora puedes iniciar sesión.",
|
||||||
"documentation_link": "Enlace de documentación",
|
"documentation_link": "Enlace de documentación",
|
||||||
"launch_account_connections": "Iniciar conexiones de cuenta",
|
"launch_account_connections": "Iniciar conexiones de cuenta",
|
||||||
"launch_administration_panel": "Iniciar el panel de administración",
|
"launch_administration_panel": "Iniciar el panel de administración",
|
||||||
"no_verified_email_warning": "Debe tener una dirección de correo electrónico verificada para habilitar la autenticación de dos factores.",
|
"no_verified_email_warning": "Debe tener una dirección de correo electrónico verificada para habilitar la autenticación de dos factores.",
|
||||||
"social_auth_desc": "Habilite o deshabilite los proveedores de autenticación social y OIDC para su cuenta. \nEstas conexiones le permiten iniciar sesión con proveedores de identidad de autenticación autohospedados como Authentik o proveedores externos como GitHub.",
|
"social_auth_desc": "Habilite o deshabilite los proveedores de autenticación social y OIDC para su cuenta. Estas conexiones le permiten iniciar sesión con proveedores de identidad de autenticación autohospedados como Authentik o proveedores externos como GitHub.",
|
||||||
"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.",
|
||||||
"add_email": "Agregar correo electrónico",
|
"add_email": "Agregar correo electrónico",
|
||||||
"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_disable_warning": "Actualmente, la autenticación de contraseña está deshabilitada. Es necesario iniciar sesión a través de una red social o un proveedor OIDC.",
|
||||||
"password_disabled": "Autenticación de contraseña deshabilitada",
|
"password_disabled": "Autenticación de contraseña deshabilitada",
|
||||||
"password_disabled_error": "Error al deshabilitar la autenticación de contraseña. \nAsegúrese de que un proveedor social o OIDC esté vinculado a su cuenta.",
|
"password_disabled_error": "Error al deshabilitar la autenticación de contraseña. Asegúrese de que un proveedor social o OIDC esté vinculado a su cuenta.",
|
||||||
"password_enabled": "Autenticación de contraseña habilitada",
|
"password_enabled": "Autenticación de contraseña habilitada",
|
||||||
"password_enabled_error": "Error al habilitar la autenticación de contraseña.",
|
"password_enabled_error": "Error al habilitar la autenticación de contraseña.",
|
||||||
"admin": "Administración",
|
"admin": "Administración",
|
||||||
@@ -673,7 +675,7 @@
|
|||||||
"staff_user": "Usuario de personal",
|
"staff_user": "Usuario de personal",
|
||||||
"license": "Licencia",
|
"license": "Licencia",
|
||||||
"all_rights_reserved": "Reservados todos los derechos.",
|
"all_rights_reserved": "Reservados todos los derechos.",
|
||||||
"email_verified_erorr_desc": "Su correo electrónico no pudo ser verificado. \nPor favor intente de nuevo.",
|
"email_verified_erorr_desc": "Su correo electrónico no pudo ser verificado. Por favor inténtelo de nuevo.",
|
||||||
"no_emai_set": "Sin conjunto de correo electrónico",
|
"no_emai_set": "Sin conjunto de correo electrónico",
|
||||||
"invalid_credentials": "Credenciales no válidas",
|
"invalid_credentials": "Credenciales no válidas",
|
||||||
"backup_restore": "Respaldo",
|
"backup_restore": "Respaldo",
|
||||||
@@ -695,12 +697,12 @@
|
|||||||
"whats_included": "¿Qué está incluido?",
|
"whats_included": "¿Qué está incluido?",
|
||||||
"backup_your_data": "Haga una copia de seguridad de sus datos",
|
"backup_your_data": "Haga una copia de seguridad de sus datos",
|
||||||
"backup_your_data_desc": "Descargue una copia de seguridad completa de los datos de su cuenta, incluidas ubicaciones, colecciones, medios y visitas.",
|
"backup_your_data_desc": "Descargue una copia de seguridad completa de los datos de su cuenta, incluidas ubicaciones, colecciones, medios y visitas.",
|
||||||
"data_override_acknowledge": "Reconozco que esto anulará todos mis datos existentes.",
|
"data_override_acknowledge": "Reconozco que esto sobrescribirá todos mis datos existentes",
|
||||||
"data_override_acknowledge_desc": "Esta acción es irreversible y reemplazará todas las ubicaciones, colecciones y visitas en su cuenta.",
|
"data_override_acknowledge_desc": "Esta acción es irreversible y reemplazará todas las ubicaciones, colecciones y visitas en su cuenta.",
|
||||||
"data_override_warning": "Advertencia de anulación de datos",
|
"data_override_warning": "Advertencia de anulación de datos",
|
||||||
"data_override_warning_desc": "La restauración de datos reemplazará completamente todos los datos existentes (que se incluyen en la copia de seguridad) en su cuenta. \nEsta acción no se puede deshacer.",
|
"data_override_warning_desc": "La restauración de datos reemplazará completamente todos los datos existentes (que se incluyen en la copia de seguridad) en su cuenta. Esta acción no se puede deshacer.",
|
||||||
"integrations_settings": "Configuración de integraciones",
|
"integrations_settings": "Configuración de integraciones",
|
||||||
"media": "Medios de comunicación",
|
"media": "Medios",
|
||||||
"restore_data": "Restaurar datos",
|
"restore_data": "Restaurar datos",
|
||||||
"restore_data_desc": "Cargue un archivo de copia de seguridad para restaurar sus datos.",
|
"restore_data_desc": "Cargue un archivo de copia de seguridad para restaurar sus datos.",
|
||||||
"select_backup_file": "Seleccione el archivo de copia de seguridad",
|
"select_backup_file": "Seleccione el archivo de copia de seguridad",
|
||||||
@@ -878,7 +880,7 @@
|
|||||||
"location_update_after_refresh": "Las tarjetas de ubicación se actualizarán una vez que actualice la página."
|
"location_update_after_refresh": "Las tarjetas de ubicación se actualizarán una vez que actualice la página."
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"add_some": "¿Por qué no empezar a planificar tu próxima aventura? \nPuedes agregar una nueva aventura haciendo clic en el botón de abajo.",
|
"add_some": "¿Por qué no empezar a planificar tu próxima aventura? Puedes agregar una nueva aventura haciendo clic en el botón de abajo.",
|
||||||
"countries_visited": "Países visitados",
|
"countries_visited": "Países visitados",
|
||||||
"no_recent_adventures": "¿Sin aventuras recientes?",
|
"no_recent_adventures": "¿Sin aventuras recientes?",
|
||||||
"recent_adventures": "Aventuras recientes",
|
"recent_adventures": "Aventuras recientes",
|
||||||
@@ -904,19 +906,19 @@
|
|||||||
"no_items_found": "No se encontraron elementos",
|
"no_items_found": "No se encontraron elementos",
|
||||||
"server_url": "URL del servidor Immich",
|
"server_url": "URL del servidor Immich",
|
||||||
"update_integration": "Integración de actualización",
|
"update_integration": "Integración de actualización",
|
||||||
"localhost_note": "Nota: lo más probable es que localhost no funcione a menos que haya configurado las redes acoplables en consecuencia. \nSe recomienda utilizar la dirección IP del servidor o el nombre de dominio.",
|
"localhost_note": "Nota: lo más probable es que localhost no funcione a menos que haya configurado las redes acoplables en consecuencia. Se recomienda utilizar la dirección IP del servidor o el nombre de dominio.",
|
||||||
"api_key_placeholder": "Ingrese su clave de API Immich",
|
"api_key_placeholder": "Ingrese su clave de API Immich",
|
||||||
"enable_integration": "Habilitar la integración",
|
"enable_integration": "Habilitar la integración",
|
||||||
"immich_integration_desc": "Conecte su servidor de administración de fotos de Immich",
|
"immich_integration_desc": "Conecte su servidor de administración de fotos de Immich",
|
||||||
"need_help": "¿Necesita ayuda para configurar esto? \nMira el",
|
"need_help": "¿Necesita ayuda para configurar esto? Mira el",
|
||||||
"connection_error": "Error al conectarse al servidor Immich",
|
"connection_error": "Error al conectarse al servidor Immich",
|
||||||
"copy_locally": "Copiar imágenes localmente",
|
"copy_locally": "Copiar imágenes localmente",
|
||||||
"copy_locally_desc": "Copie imágenes al servidor para obtener acceso fuera de línea. \nUtiliza más espacio en disco.",
|
"copy_locally_desc": "Copie imágenes al servidor para obtener acceso fuera de línea. Utiliza más espacio en disco.",
|
||||||
"error_saving_image": "Error al guardar la imagen",
|
"error_saving_image": "Error al guardar la imagen",
|
||||||
"integration_already_exists": "Ya existe una integración Immich. \nSolo puedes tener una integración a la vez.",
|
"integration_already_exists": "Ya existe una integración Immich. Solo puedes tener una integración a la vez.",
|
||||||
"integration_not_found": "Integración Immich no encontrada. \nPor favor cree una nueva integración.",
|
"integration_not_found": "Integración Immich no encontrada. Por favor cree una nueva integración.",
|
||||||
"network_error": "Error de red mientras se conecta al servidor Immich. \nVerifique su conexión y vuelva a intentarlo.",
|
"network_error": "Error de red mientras se conecta al servidor Immich. Verifique su conexión y vuelva a intentarlo.",
|
||||||
"validation_error": "Se produjo un error al validar la integración de Immich. \nVerifique la URL y la tecla API de su servidor.",
|
"validation_error": "Se produjo un error al validar la integración de Immich. Verifique la URL y la tecla API de su servidor.",
|
||||||
"by_album": "Por álbum",
|
"by_album": "Por álbum",
|
||||||
"by_date": "Por fecha",
|
"by_date": "Por fecha",
|
||||||
"error_no_object_id": "No se proporcionó una identificación de objeto",
|
"error_no_object_id": "No se proporcionó una identificación de objeto",
|
||||||
|
|||||||
@@ -423,7 +423,9 @@
|
|||||||
"recorded_sessions": "Sessions enregistrées",
|
"recorded_sessions": "Sessions enregistrées",
|
||||||
"total_activities": "Activités totales",
|
"total_activities": "Activités totales",
|
||||||
"total_climbed": "Total grimpé",
|
"total_climbed": "Total grimpé",
|
||||||
"total_distance": "Distance totale"
|
"total_distance": "Distance totale",
|
||||||
|
"dates_not_saved": "Visitez non encore ajouté",
|
||||||
|
"dates_not_saved_description": "Cliquez sur Ajouter une visite pour enregistrer"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"desc_1": "Découvrez, planifiez et explorez en toute simplicité",
|
"desc_1": "Découvrez, planifiez et explorez en toute simplicité",
|
||||||
|
|||||||
@@ -423,7 +423,9 @@
|
|||||||
"recorded_sessions": "Sessioni registrate",
|
"recorded_sessions": "Sessioni registrate",
|
||||||
"total_activities": "Attività totali",
|
"total_activities": "Attività totali",
|
||||||
"total_climbed": "Totale scalato",
|
"total_climbed": "Totale scalato",
|
||||||
"total_distance": "Distanza totale"
|
"total_distance": "Distanza totale",
|
||||||
|
"dates_not_saved": "Visitare non ancora aggiunto",
|
||||||
|
"dates_not_saved_description": "Fai clic su Aggiungi visita per salvare"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"desc_1": "Scopri, pianifica ed esplora con facilità",
|
"desc_1": "Scopri, pianifica ed esplora con facilità",
|
||||||
|
|||||||
@@ -422,7 +422,9 @@
|
|||||||
"recorded_sessions": "録音されたセッション",
|
"recorded_sessions": "録音されたセッション",
|
||||||
"total_activities": "総アクティビティ",
|
"total_activities": "総アクティビティ",
|
||||||
"total_climbed": "総登り",
|
"total_climbed": "総登り",
|
||||||
"total_distance": "総距離"
|
"total_distance": "総距離",
|
||||||
|
"dates_not_saved": "まだ追加されていません",
|
||||||
|
"dates_not_saved_description": "[訪問の追加]をクリックして保存します"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"confirm_password": "パスワードを認証する",
|
"confirm_password": "パスワードを認証する",
|
||||||
|
|||||||
@@ -423,7 +423,9 @@
|
|||||||
"recorded_sessions": "기록 된 세션",
|
"recorded_sessions": "기록 된 세션",
|
||||||
"total_activities": "총 활동",
|
"total_activities": "총 활동",
|
||||||
"total_climbed": "총계가 올라 갔다",
|
"total_climbed": "총계가 올라 갔다",
|
||||||
"total_distance": "총 거리"
|
"total_distance": "총 거리",
|
||||||
|
"dates_not_saved": "아직 추가되지 않았습니다",
|
||||||
|
"dates_not_saved_description": "저장을 위해 방문 추가를 클릭하십시오"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"confirm_password": "비밀번호 확인",
|
"confirm_password": "비밀번호 확인",
|
||||||
|
|||||||
@@ -423,7 +423,9 @@
|
|||||||
"recorded_sessions": "Opgenomen sessies",
|
"recorded_sessions": "Opgenomen sessies",
|
||||||
"total_activities": "Totale activiteiten",
|
"total_activities": "Totale activiteiten",
|
||||||
"total_climbed": "Totaal geklommen",
|
"total_climbed": "Totaal geklommen",
|
||||||
"total_distance": "Totale afstand"
|
"total_distance": "Totale afstand",
|
||||||
|
"dates_not_saved": "Bezoek nog niet toegevoegd",
|
||||||
|
"dates_not_saved_description": "Klik op Bezoek toevoegen om op te slaan"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"desc_1": "Ontdek, plan en verken met gemak",
|
"desc_1": "Ontdek, plan en verken met gemak",
|
||||||
|
|||||||
@@ -475,7 +475,9 @@
|
|||||||
"recorded_sessions": "Innspilte økter",
|
"recorded_sessions": "Innspilte økter",
|
||||||
"total_activities": "Total aktiviteter",
|
"total_activities": "Total aktiviteter",
|
||||||
"total_climbed": "Totalt klatret",
|
"total_climbed": "Totalt klatret",
|
||||||
"total_distance": "Total avstand"
|
"total_distance": "Total avstand",
|
||||||
|
"dates_not_saved": "Besøk ikke lagt til ennå",
|
||||||
|
"dates_not_saved_description": "Klikk Legg til besøk for å lagre"
|
||||||
},
|
},
|
||||||
"worldtravel": {
|
"worldtravel": {
|
||||||
"country_list": "Liste over land",
|
"country_list": "Liste over land",
|
||||||
|
|||||||
@@ -476,7 +476,9 @@
|
|||||||
"recorded_sessions": "Nagrane sesje",
|
"recorded_sessions": "Nagrane sesje",
|
||||||
"total_activities": "Całkowite działania",
|
"total_activities": "Całkowite działania",
|
||||||
"total_climbed": "Całkowita wspinana",
|
"total_climbed": "Całkowita wspinana",
|
||||||
"total_distance": "Całkowita odległość"
|
"total_distance": "Całkowita odległość",
|
||||||
|
"dates_not_saved": "Wizyta jeszcze nie dodana",
|
||||||
|
"dates_not_saved_description": "Kliknij Dodaj wizytę, aby zapisać"
|
||||||
},
|
},
|
||||||
"worldtravel": {
|
"worldtravel": {
|
||||||
"country_list": "Lista krajów",
|
"country_list": "Lista krajów",
|
||||||
|
|||||||
@@ -422,7 +422,9 @@
|
|||||||
"recorded_sessions": "Sessões gravadas",
|
"recorded_sessions": "Sessões gravadas",
|
||||||
"total_activities": "Atividades totais",
|
"total_activities": "Atividades totais",
|
||||||
"total_climbed": "Total escalou",
|
"total_climbed": "Total escalou",
|
||||||
"total_distance": "Distância total"
|
"total_distance": "Distância total",
|
||||||
|
"dates_not_saved": "Visite ainda não adicionado",
|
||||||
|
"dates_not_saved_description": "Clique em Adicionar visita para salvar"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"confirm_password": "Confirme sua senha",
|
"confirm_password": "Confirme sua senha",
|
||||||
|
|||||||
@@ -476,7 +476,9 @@
|
|||||||
"recorded_sessions": "Записанные сеансы",
|
"recorded_sessions": "Записанные сеансы",
|
||||||
"total_activities": "Всего активностей",
|
"total_activities": "Всего активностей",
|
||||||
"total_climbed": "Общий набор высоты",
|
"total_climbed": "Общий набор высоты",
|
||||||
"total_distance": "Общее расстояние"
|
"total_distance": "Общее расстояние",
|
||||||
|
"dates_not_saved": "Посещение еще не добавлено",
|
||||||
|
"dates_not_saved_description": "Нажмите «Добавить посещение», чтобы сохранить"
|
||||||
},
|
},
|
||||||
"worldtravel": {
|
"worldtravel": {
|
||||||
"country_list": "Список стран",
|
"country_list": "Список стран",
|
||||||
@@ -872,9 +874,7 @@
|
|||||||
"available": "Доступный",
|
"available": "Доступный",
|
||||||
"pending": "В ожидании",
|
"pending": "В ожидании",
|
||||||
"revoke_invite": "Отменить приглашение",
|
"revoke_invite": "Отменить приглашение",
|
||||||
"send_invite": "Отправить приглашение",
|
"send_invite": "Отправить приглашение"
|
||||||
"available_users": "Поделиться с",
|
|
||||||
"no_available_users": "Нет доступных пользователей"
|
|
||||||
},
|
},
|
||||||
"languages": {},
|
"languages": {},
|
||||||
"profile": {
|
"profile": {
|
||||||
@@ -901,8 +901,7 @@
|
|||||||
"category_name": "Название категории",
|
"category_name": "Название категории",
|
||||||
"add_new_category": "Добавить новую категорию",
|
"add_new_category": "Добавить новую категорию",
|
||||||
"name_required": "Требуется название категории",
|
"name_required": "Требуется название категории",
|
||||||
"location_update_after_refresh": "Карты местоположения будут обновлены после обновления страницы.",
|
"location_update_after_refresh": "Карты местоположения будут обновлены после обновления страницы."
|
||||||
"no_categories_yet": "Нет категорий"
|
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"welcome_back": "Добро пожаловать обратно",
|
"welcome_back": "Добро пожаловать обратно",
|
||||||
|
|||||||
1016
frontend/src/locales/sk.json
Normal file
1016
frontend/src/locales/sk.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -423,7 +423,9 @@
|
|||||||
"recorded_sessions": "Inspelade sessioner",
|
"recorded_sessions": "Inspelade sessioner",
|
||||||
"total_activities": "Totala aktiviteter",
|
"total_activities": "Totala aktiviteter",
|
||||||
"total_climbed": "Total stigning",
|
"total_climbed": "Total stigning",
|
||||||
"total_distance": "Totalt avstånd"
|
"total_distance": "Totalt avstånd",
|
||||||
|
"dates_not_saved": "Besök ännu inte tillagd",
|
||||||
|
"dates_not_saved_description": "Klicka på Lägg till Besök för att spara"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"desc_1": "Upptäck, planera och utforska med lätthet",
|
"desc_1": "Upptäck, planera och utforska med lätthet",
|
||||||
|
|||||||
@@ -476,7 +476,9 @@
|
|||||||
"recorded_sessions": "记录的会议",
|
"recorded_sessions": "记录的会议",
|
||||||
"total_activities": "总活动",
|
"total_activities": "总活动",
|
||||||
"total_climbed": "总攀登",
|
"total_climbed": "总攀登",
|
||||||
"total_distance": "总距离"
|
"total_distance": "总距离",
|
||||||
|
"dates_not_saved": "访问尚未添加",
|
||||||
|
"dates_not_saved_description": "点击添加访问以保存"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"forgot_password": "忘记密码?",
|
"forgot_password": "忘记密码?",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
register('ja', () => import('../locales/ja.json'));
|
register('ja', () => import('../locales/ja.json'));
|
||||||
register('ar', () => import('../locales/ar.json'));
|
register('ar', () => import('../locales/ar.json'));
|
||||||
register('pt-br', () => import('../locales/pt-br.json'));
|
register('pt-br', () => import('../locales/pt-br.json'));
|
||||||
|
register('sk', () => import('../locales/sk.json'));
|
||||||
|
|
||||||
let locales = [
|
let locales = [
|
||||||
'en',
|
'en',
|
||||||
@@ -36,7 +37,8 @@
|
|||||||
'ru',
|
'ru',
|
||||||
'ja',
|
'ja',
|
||||||
'ar',
|
'ar',
|
||||||
'pt-br'
|
'pt-br',
|
||||||
|
'sk'
|
||||||
];
|
];
|
||||||
|
|
||||||
if (browser) {
|
if (browser) {
|
||||||
|
|||||||
@@ -389,6 +389,21 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: filteredOrderedItems = orderedItems.filter((item) => {
|
||||||
|
if (!collection?.start_date || !collection?.end_date) {
|
||||||
|
return true; // If no date range is set, show all items
|
||||||
|
}
|
||||||
|
|
||||||
|
const collectionStart = new Date(collection.start_date);
|
||||||
|
const collectionEnd = new Date(collection.end_date);
|
||||||
|
const itemStart = new Date(item.start);
|
||||||
|
const itemEnd = new Date(item.end);
|
||||||
|
|
||||||
|
// Check if item overlaps with collection date range
|
||||||
|
// Item is included if it starts before collection ends AND ends after collection starts
|
||||||
|
return itemStart <= collectionEnd && itemEnd >= collectionStart;
|
||||||
|
});
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
numAdventures = adventures.length;
|
numAdventures = adventures.length;
|
||||||
numVisited = adventures.filter((adventure) => adventure.is_visited).length;
|
numVisited = adventures.filter((adventure) => adventure.is_visited).length;
|
||||||
@@ -1176,11 +1191,11 @@
|
|||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<div class="w-full max-w-4xl relative">
|
<div class="w-full max-w-4xl relative">
|
||||||
<!-- Vertical timeline line that spans the entire height -->
|
<!-- Vertical timeline line that spans the entire height -->
|
||||||
{#if orderedItems.length > 0}
|
{#if filteredOrderedItems.length > 0}
|
||||||
<div class="absolute left-8 top-0 bottom-0 w-1 bg-primary"></div>
|
<div class="absolute left-8 top-0 bottom-0 w-1 bg-primary"></div>
|
||||||
{/if}
|
{/if}
|
||||||
<ul class="relative">
|
<ul class="relative">
|
||||||
{#each orderedItems as orderedItem, index}
|
{#each filteredOrderedItems as orderedItem, index}
|
||||||
<li class="relative pl-20 mb-8">
|
<li class="relative pl-20 mb-8">
|
||||||
<!-- Timeline Icon -->
|
<!-- Timeline Icon -->
|
||||||
<div
|
<div
|
||||||
@@ -1281,7 +1296,7 @@
|
|||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
{#if orderedItems.length === 0}
|
{#if filteredOrderedItems.length === 0}
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<p class="text-center text-lg">{$t('adventures.no_ordered_items')}</p>
|
<p class="text-center text-lg">{$t('adventures.no_ordered_items')}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -1360,7 +1375,7 @@
|
|||||||
</Marker>
|
</Marker>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{#if lineData}
|
{#if lineData && collection.start_date && collection.end_date}
|
||||||
<GeoJSON data={lineData}>
|
<GeoJSON data={lineData}>
|
||||||
<LineLayer
|
<LineLayer
|
||||||
layout={{ 'line-cap': 'round', 'line-join': 'round' }}
|
layout={{ 'line-cap': 'round', 'line-join': 'round' }}
|
||||||
|
|||||||
Reference in New Issue
Block a user