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

feat: Add "all day" localization strings for multiple languages and enhance transportation date handling

This commit is contained in:
Sean Morley 2025-03-18 17:40:32 -04:00
parent 9fd2a142cb
commit 1dc8e10758
16 changed files with 118 additions and 48 deletions

View file

@ -8,7 +8,7 @@
let fullStartDate: string = ''; let fullStartDate: string = '';
let fullEndDate: string = ''; let fullEndDate: string = '';
let allDay: boolean = false; let allDay: boolean = true;
// Set full start and end dates from collection // Set full start and end dates from collection
if (collection && collection.start_date && collection.end_date) { if (collection && collection.start_date && collection.end_date) {
@ -698,13 +698,13 @@
on:change={() => (constrainDates = !constrainDates)} on:change={() => (constrainDates = !constrainDates)}
/> />
{/if} {/if}
<span class="label-text">All Day</span> <span class="label-text">{$t('adventures.all_day')}</span>
<input <input
type="checkbox" type="checkbox"
class="toggle toggle-primary" class="toggle toggle-primary"
id="constrain_dates" id="constrain_dates"
name="constrain_dates" name="constrain_dates"
on:change={() => (allDay = !allDay)} bind:checked={allDay}
/> />
</label> </label>
<div class="flex gap-2 mb-1"> <div class="flex gap-2 mb-1">
@ -782,30 +782,31 @@
}} }}
></textarea> ></textarea>
</div> </div>
{#if !allDay}
<div role="alert" class="alert shadow-lg bg-neutral mt-2 mb-2"> <div role="alert" class="alert shadow-lg bg-neutral mt-2 mb-2">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
class="stroke-info h-6 w-6 shrink-0" class="stroke-info h-6 w-6 shrink-0"
> >
<path <path
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
stroke-width="2" stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
></path> ></path>
</svg> </svg>
<span> <span>
{$t('lodging.current_timezone')}: {$t('lodging.current_timezone')}:
{(() => { {(() => {
const tz = new Intl.DateTimeFormat().resolvedOptions().timeZone; const tz = new Intl.DateTimeFormat().resolvedOptions().timeZone;
const [continent, city] = tz.split('/'); const [continent, city] = tz.split('/');
return `${continent} (${city.replace('_', ' ')})`; return `${continent} (${city.replace('_', ' ')})`;
})()} })()}
</span> </span>
</div> </div>
{/if}
<div class="flex gap-2"> <div class="flex gap-2">
<button type="button" class="btn btn-neutral" on:click={addNewVisit} <button type="button" class="btn btn-neutral" on:click={addNewVisit}

View file

@ -124,7 +124,7 @@
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="font-medium text-sm">{$t('adventures.dates')}:</span> <span class="font-medium text-sm">{$t('adventures.dates')}:</span>
<p> <p>
{new Date(lodging.check_in).toLocaleString('en-US', { {new Date(lodging.check_in).toLocaleString(undefined, {
month: 'short', month: 'short',
day: 'numeric', day: 'numeric',
year: 'numeric', year: 'numeric',
@ -132,7 +132,7 @@
minute: 'numeric' minute: 'numeric'
})} })}
- -
{new Date(lodging.check_out).toLocaleString('en-US', { {new Date(lodging.check_out).toLocaleString(undefined, {
month: 'short', month: 'short',
day: 'numeric', day: 'numeric',
year: 'numeric', year: 'numeric',

View file

@ -7,7 +7,15 @@
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
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';
function getTransportationIcon(type: string) {
if (type in TRANSPORTATION_TYPES_ICONS) {
return TRANSPORTATION_TYPES_ICONS[type as keyof typeof TRANSPORTATION_TYPES_ICONS];
} else {
return '🚗';
}
}
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let transportation: Transportation; export let transportation: Transportation;
@ -106,7 +114,9 @@
<h2 class="card-title text-lg font-semibold truncate">{transportation.name}</h2> <h2 class="card-title text-lg font-semibold truncate">{transportation.name}</h2>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="badge badge-secondary"> <div class="badge badge-secondary">
{$t(`transportation.modes.${transportation.type}`)} {$t(`transportation.modes.${transportation.type}`) +
' ' +
getTransportationIcon(transportation.type)}
</div> </div>
{#if transportation.type == 'plane' && transportation.flight_number} {#if transportation.type == 'plane' && transportation.flight_number}
<div class="badge badge-neutral-200">{transportation.flight_number}</div> <div class="badge badge-neutral-200">{transportation.flight_number}</div>
@ -128,7 +138,7 @@
{#if transportation.date} {#if transportation.date}
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="font-medium text-sm">{$t('adventures.start')}:</span> <span class="font-medium text-sm">{$t('adventures.start')}:</span>
<p>{new Date(transportation.date).toLocaleString(undefined, { timeZone: 'UTC' })}</p> <p>{new Date(transportation.date).toLocaleString()}</p>
</div> </div>
{/if} {/if}
</div> </div>
@ -146,7 +156,7 @@
{#if transportation.end_date} {#if transportation.end_date}
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="font-medium text-sm">{$t('adventures.end')}:</span> <span class="font-medium text-sm">{$t('adventures.end')}:</span>
<p>{new Date(transportation.end_date).toLocaleString(undefined, { timeZone: 'UTC' })}</p> <p>{new Date(transportation.end_date).toLocaleString()}</p>
</div> </div>
{/if} {/if}
</div> </div>

View file

@ -16,10 +16,15 @@
let constrainDates: boolean = false; let constrainDates: boolean = false;
// Format date as local datetime
// Convert an ISO date to a datetime-local value in local time.
function toLocalDatetime(value: string | null): string { function toLocalDatetime(value: string | null): string {
if (!value) return ''; if (!value) return '';
const date = new Date(value); const date = new Date(value);
return date.toISOString().slice(0, 16); // Format: YYYY-MM-DDTHH:mm // Adjust the time by subtracting the timezone offset.
date.setMinutes(date.getMinutes() - date.getTimezoneOffset());
// Return format YYYY-MM-DDTHH:mm
return date.toISOString().slice(0, 16);
} }
let transportation: Transportation = { let transportation: Transportation = {
@ -185,6 +190,14 @@
return; return;
} }
// Convert local dates to UTC
if (transportation.date && !transportation.date.includes('Z')) {
transportation.date = new Date(transportation.date).toISOString();
}
if (transportation.end_date && !transportation.end_date.includes('Z')) {
transportation.end_date = new Date(transportation.end_date).toISOString();
}
if (transportation.type != 'plane') { if (transportation.type != 'plane') {
transportation.flight_number = ''; transportation.flight_number = '';
} }
@ -422,6 +435,29 @@
</div> </div>
</div> </div>
{/if} {/if}
<div role="alert" class="alert shadow-lg bg-neutral mt-4">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="stroke-info h-6 w-6 shrink-0"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
<span>
{$t('lodging.current_timezone')}:
{(() => {
const tz = new Intl.DateTimeFormat().resolvedOptions().timeZone;
const [continent, city] = tz.split('/');
return `${continent} (${city.replace('_', ' ')})`;
})()}
</span>
</div>
</div> </div>
</div> </div>

View file

@ -338,6 +338,17 @@ export let LODGING_TYPES_ICONS = {
other: '❓' other: '❓'
}; };
export let TRANSPORTATION_TYPES_ICONS = {
car: '🚗',
plane: '✈️',
train: '🚆',
bus: '🚌',
boat: '⛵',
bike: '🚲',
walking: '🚶',
other: '❓'
};
// Helper to check if a given date string represents midnight (all-day) // Helper to check if a given date string represents midnight (all-day)
export function isAllDay(dateStr: string | string[]) { export function isAllDay(dateStr: string | string[]) {
// Checks for the pattern "T00:00:00.000Z" // Checks for the pattern "T00:00:00.000Z"

View file

@ -247,7 +247,8 @@
"price": "Preis", "price": "Preis",
"reservation_number": "Reservierungsnummer", "reservation_number": "Reservierungsnummer",
"welcome_map_info": "Frei zugängliche Abenteuer auf diesem Server", "welcome_map_info": "Frei zugängliche Abenteuer auf diesem Server",
"open_in_maps": "In Karten öffnen" "open_in_maps": "In Karten öffnen",
"all_day": "Den ganzen Tag"
}, },
"home": { "home": {
"desc_1": "Entdecken, planen und erkunden Sie mühelos", "desc_1": "Entdecken, planen und erkunden Sie mühelos",

View file

@ -250,6 +250,7 @@
"show_map": "Show Map", "show_map": "Show Map",
"emoji_picker": "Emoji Picker", "emoji_picker": "Emoji Picker",
"download_calendar": "Download Calendar", "download_calendar": "Download Calendar",
"all_day": "All Day",
"date_information": "Date Information", "date_information": "Date Information",
"flight_information": "Flight Information", "flight_information": "Flight Information",
"out_of_range": "Not in itinerary date range", "out_of_range": "Not in itinerary date range",

View file

@ -295,7 +295,8 @@
"region": "Región", "region": "Región",
"reservation_number": "Número de reserva", "reservation_number": "Número de reserva",
"welcome_map_info": "Aventuras públicas en este servidor", "welcome_map_info": "Aventuras públicas en este servidor",
"open_in_maps": "Abrir en mapas" "open_in_maps": "Abrir en mapas",
"all_day": "Todo el día"
}, },
"worldtravel": { "worldtravel": {
"all": "Todo", "all": "Todo",

View file

@ -247,7 +247,8 @@
"region": "Région", "region": "Région",
"reservation_number": "Numéro de réservation", "reservation_number": "Numéro de réservation",
"welcome_map_info": "Aventures publiques sur ce serveur", "welcome_map_info": "Aventures publiques sur ce serveur",
"open_in_maps": "Ouvert dans les cartes" "open_in_maps": "Ouvert dans les cartes",
"all_day": "Toute la journée"
}, },
"home": { "home": {
"desc_1": "Découvrez, planifiez et explorez en toute simplicité", "desc_1": "Découvrez, planifiez et explorez en toute simplicité",

View file

@ -247,7 +247,8 @@
"region": "Regione", "region": "Regione",
"welcome_map_info": "Avventure pubbliche su questo server", "welcome_map_info": "Avventure pubbliche su questo server",
"reservation_number": "Numero di prenotazione", "reservation_number": "Numero di prenotazione",
"open_in_maps": "Aperto in mappe" "open_in_maps": "Aperto in mappe",
"all_day": "Tutto il giorno"
}, },
"home": { "home": {
"desc_1": "Scopri, pianifica ed esplora con facilità", "desc_1": "Scopri, pianifica ed esplora con facilità",

View file

@ -247,7 +247,8 @@
"region": "지역", "region": "지역",
"reservation_number": "예약 번호", "reservation_number": "예약 번호",
"welcome_map_info": "이 서버의 공개 모험", "welcome_map_info": "이 서버의 공개 모험",
"open_in_maps": "지도에서 열립니다" "open_in_maps": "지도에서 열립니다",
"all_day": "하루 종일"
}, },
"auth": { "auth": {
"both_passwords_required": "두 암호 모두 필요합니다", "both_passwords_required": "두 암호 모두 필요합니다",

View file

@ -247,7 +247,8 @@
"lodging_information": "Informatie overliggen", "lodging_information": "Informatie overliggen",
"price": "Prijs", "price": "Prijs",
"region": "Regio", "region": "Regio",
"open_in_maps": "Open in kaarten" "open_in_maps": "Open in kaarten",
"all_day": "De hele dag"
}, },
"home": { "home": {
"desc_1": "Ontdek, plan en verken met gemak", "desc_1": "Ontdek, plan en verken met gemak",

View file

@ -295,7 +295,8 @@
"region": "Region", "region": "Region",
"reservation_number": "Numer rezerwacji", "reservation_number": "Numer rezerwacji",
"welcome_map_info": "Publiczne przygody na tym serwerze", "welcome_map_info": "Publiczne przygody na tym serwerze",
"open_in_maps": "Otwarte w mapach" "open_in_maps": "Otwarte w mapach",
"all_day": "Cały dzień"
}, },
"worldtravel": { "worldtravel": {
"country_list": "Lista krajów", "country_list": "Lista krajów",

View file

@ -247,7 +247,8 @@
"price": "Pris", "price": "Pris",
"region": "Område", "region": "Område",
"reservation_number": "Bokningsnummer", "reservation_number": "Bokningsnummer",
"open_in_maps": "Kappas in" "open_in_maps": "Kappas in",
"all_day": "Hela dagen"
}, },
"home": { "home": {
"desc_1": "Upptäck, planera och utforska med lätthet", "desc_1": "Upptäck, planera och utforska med lätthet",

View file

@ -295,7 +295,8 @@
"lodging_information": "住宿信息", "lodging_information": "住宿信息",
"price": "价格", "price": "价格",
"reservation_number": "预订号", "reservation_number": "预订号",
"open_in_maps": "在地图上打开" "open_in_maps": "在地图上打开",
"all_day": "整天"
}, },
"auth": { "auth": {
"forgot_password": "忘记密码?", "forgot_password": "忘记密码?",

View file

@ -456,12 +456,14 @@
</div> </div>
</div> </div>
{/if} {/if}
<a {#if adventure.longitude && adventure.latitude}
class="btn btn-neutral btn-sm max-w-32" <a
href={`https://maps.apple.com/?q=${adventure.latitude},${adventure.longitude}`} class="btn btn-neutral btn-sm max-w-32"
target="_blank" href={`https://maps.apple.com/?q=${adventure.latitude},${adventure.longitude}`}
rel="noopener noreferrer">{$t('adventures.open_in_maps')}</a target="_blank"
> rel="noopener noreferrer">{$t('adventures.open_in_maps')}</a
>
{/if}
<MapLibre <MapLibre
style="https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json" style="https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
class="flex items-center self-center justify-center aspect-[9/16] max-h-[70vh] sm:aspect-video sm:max-h-full w-10/12 rounded-lg" class="flex items-center self-center justify-center aspect-[9/16] max-h-[70vh] sm:aspect-video sm:max-h-full w-10/12 rounded-lg"