Skip to content

Commit f315f85

Browse files
committed
feat: enhance internationalization support in CollectionMap and CollectionStats components
- Added translation support for various labels and messages in CollectionMap.svelte and CollectionStats.svelte using svelte-i18n. - Updated English and Chinese locale files to include new translation keys for improved user experience. - Simplified the rendering of recommendation views in the collections page.
1 parent c53eb6b commit f315f85

15 files changed

Lines changed: 436 additions & 269 deletions

documentation/docs/install/getting_started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Perfect for Docker beginners.
2222

2323
## ⚙️ Advanced & Alternative Setups
2424

25-
- [Dev Container + WSL](dev_container_wsl.md) - Windows dev environment with WSL 2 + Dev Containers
2625
- [Nginx Proxy Manager](nginx_proxy_manager.md) - Easy reverse proxy config
2726
- [Traefik](traefik.md) — Dynamic reverse proxy with automation
2827
- [Caddy](caddy.md) — Automatic HTTPS with a clean config
28+
- [Dev Container + WSL](dev_container_wsl.md) - Windows dev environment with WSL 2 + Dev Containers

frontend/src/lib/components/CollectionModal.svelte

Lines changed: 71 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -413,78 +413,84 @@
413413
{/if}
414414

415415
<!-- Cover Image Selection -->
416-
<div class="card bg-base-100 border border-base-300 shadow-lg">
417-
<div class="card-body p-6 space-y-4">
418-
<div class="flex items-center gap-3">
419-
<div class="p-2 bg-primary/10 rounded-lg">
420-
<ImageIcon class="w-5 h-5 text-primary" />
421-
</div>
422-
<div>
423-
<h3 class="text-lg font-semibold">
424-
{$t('collection.cover_image') ?? 'Cover image'}
425-
</h3>
426-
<p class="text-sm text-base-content/60">
427-
{$t('collection.cover_image_hint') ??
428-
'Choose a cover from images in this collection.'}
429-
</p>
416+
{#if collection.id}
417+
<div class="card bg-base-100 border border-base-300 shadow-lg">
418+
<div class="card-body p-6 space-y-4">
419+
<div class="flex items-center gap-3">
420+
<div class="p-2 bg-primary/10 rounded-lg">
421+
<ImageIcon class="w-5 h-5 text-primary" />
422+
</div>
423+
<div>
424+
<h3 class="text-lg font-semibold">
425+
{$t('collection.cover_image') ?? 'Cover image'}
426+
</h3>
427+
<p class="text-sm text-base-content/60">
428+
{$t('collection.cover_image_hint') ??
429+
'Choose a cover from images in this collection.'}
430+
</p>
431+
</div>
430432
</div>
431-
</div>
432433

433-
{#if availableImages.length === 0}
434-
<div class="alert alert-info shadow-sm">
435-
<span>
436-
{$t('collection.no_images_available') ??
437-
'No images available from linked locations yet.'}
438-
</span>
439-
</div>
440-
{:else}
441-
<div class="grid sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
442-
{#each availableImages as image (image.id)}
434+
{#if availableImages.length === 0}
435+
<div class="alert alert-info shadow-sm">
436+
<span>
437+
{$t('collection.no_images_available') ??
438+
'No images available from linked locations yet.'}
439+
</span>
440+
</div>
441+
{:else}
442+
<div class="grid sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
443+
{#each availableImages as image (image.id)}
444+
<button
445+
type="button"
446+
class="relative group rounded-xl overflow-hidden border border-base-300 bg-base-200/30 hover:border-primary transition shadow-sm {coverImageId ===
447+
image.id
448+
? 'ring-2 ring-primary ring-offset-2 ring-offset-base-100'
449+
: ''}"
450+
on:click={() => selectCover(image.id)}
451+
aria-pressed={coverImageId === image.id}
452+
>
453+
<img
454+
src={image.image}
455+
alt="Cover candidate"
456+
class="w-full h-32 object-cover"
457+
/>
458+
<div
459+
class="absolute inset-0 bg-gradient-to-t from-base-300/60 to-transparent opacity-0 group-hover:opacity-100 transition"
460+
/>
461+
{#if coverImageId === image.id}
462+
<div class="absolute top-2 left-2 badge badge-primary gap-2 shadow">
463+
{$t('collection.cover') ?? 'Cover'}
464+
</div>
465+
{:else if image.is_primary}
466+
<div class="absolute top-2 left-2 badge badge-ghost shadow">
467+
{$t('collection.location_primary') ?? 'Location cover'}
468+
</div>
469+
{/if}
470+
<div
471+
class="absolute bottom-2 right-2 btn btn-xs btn-ghost bg-base-100/90 shadow"
472+
>
473+
{coverImageId === image.id
474+
? ($t('collection.cover') ?? 'Cover')
475+
: ($t('collection.set_cover') ?? 'Set cover')}
476+
</div>
477+
</button>
478+
{/each}
479+
</div>
480+
<div class="flex justify-end">
443481
<button
444482
type="button"
445-
class="relative group rounded-xl overflow-hidden border border-base-300 bg-base-200/30 hover:border-primary transition shadow-sm {coverImageId ===
446-
image.id
447-
? 'ring-2 ring-primary ring-offset-2 ring-offset-base-100'
448-
: ''}"
449-
on:click={() => selectCover(image.id)}
450-
aria-pressed={coverImageId === image.id}
483+
class="btn btn-ghost btn-sm"
484+
on:click={() => selectCover(null)}
451485
>
452-
<img src={image.image} alt="Cover candidate" class="w-full h-32 object-cover" />
453-
<div
454-
class="absolute inset-0 bg-gradient-to-t from-base-300/60 to-transparent opacity-0 group-hover:opacity-100 transition"
455-
/>
456-
{#if coverImageId === image.id}
457-
<div class="absolute top-2 left-2 badge badge-primary gap-2 shadow">
458-
{$t('collection.cover') ?? 'Cover'}
459-
</div>
460-
{:else if image.is_primary}
461-
<div class="absolute top-2 left-2 badge badge-ghost shadow">
462-
{$t('collection.location_primary') ?? 'Location cover'}
463-
</div>
464-
{/if}
465-
<div
466-
class="absolute bottom-2 right-2 btn btn-xs btn-ghost bg-base-100/90 shadow"
467-
>
468-
{coverImageId === image.id
469-
? ($t('collection.cover') ?? 'Cover')
470-
: ($t('collection.set_cover') ?? 'Set cover')}
471-
</div>
486+
<CloseIcon class="w-4 h-4" />
487+
<span>{$t('collection.clear_cover') ?? 'Clear cover'}</span>
472488
</button>
473-
{/each}
474-
</div>
475-
<div class="flex justify-end">
476-
<button
477-
type="button"
478-
class="btn btn-ghost btn-sm"
479-
on:click={() => selectCover(null)}
480-
>
481-
<CloseIcon class="w-4 h-4" />
482-
<span>{$t('collection.clear_cover') ?? 'Clear cover'}</span>
483-
</button>
484-
</div>
485-
{/if}
489+
</div>
490+
{/if}
491+
</div>
486492
</div>
487-
</div>
493+
{/if}
488494

489495
<!-- Share Link Section (only if public and has ID) -->
490496
{#if collection.is_public && collection.id}

frontend/src/lib/components/CollectionRecommendationView.svelte

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script lang="ts">
22
import type { Collection, User, ContentImage } from '$lib/types';
33
import { onMount } from 'svelte';
4+
import { t } from 'svelte-i18n';
45
import { DefaultMarker, MapLibre, Popup } from 'svelte-maplibre';
56
import { getBasemapUrl } from '$lib';
67
import MagnifyIcon from '~icons/mdi/magnify';
@@ -22,7 +23,6 @@
2223
export let collection: Collection;
2324
export let user: User | null;
2425
// Whether the current user can modify this collection (owner or shared user)
25-
export let canModify: boolean = false;
2626
2727
type RecommendationResult = {
2828
name: string;
@@ -269,7 +269,7 @@
269269
<div class="card-body">
270270
<h2 class="card-title text-2xl mb-4">
271271
<Compass class="w-8 h-8" />
272-
Discover Places
272+
{$t('recomendations.discover_places')}
273273
</h2>
274274

275275
<!-- Search Options -->
@@ -278,10 +278,12 @@
278278
{#if locationsWithCoords.length > 0}
279279
<div class="form-control">
280280
<label class="label">
281-
<span class="label-text font-semibold">Search Around Location</span>
281+
<span class="label-text font-semibold"
282+
>{$t('recomendations.search_around_location')}</span
283+
>
282284
</label>
283285
<select class="select select-bordered w-full" bind:value={selectedLocationId}>
284-
<option value={null}>Use search instead...</option>
286+
<option value={null}>{$t('recomendations.use_search_instead')}...</option>
285287
{#each locationsWithCoords as location}
286288
<option value={location.id}>{location.name}</option>
287289
{/each}
@@ -292,13 +294,11 @@
292294
<!-- Search Input -->
293295
<div class="form-control">
294296
<label class="label">
295-
<span class="label-text font-semibold">
296-
{locationsWithCoords.length > 0 ? 'Or Search by Address' : 'Search by Address'}
297-
</span>
297+
<span class="label-text font-semibold">{$t('recomendations.search_by_address')}</span>
298298
</label>
299299
<input
300300
type="text"
301-
placeholder="Enter city, address, or place..."
301+
placeholder={$t('adventures.search_placeholder')}
302302
class="input input-bordered w-full"
303303
bind:value={searchQuery}
304304
disabled={selectedLocationId !== null}
@@ -309,19 +309,21 @@
309309
<!-- Category Selector -->
310310
<div class="form-control">
311311
<label class="label">
312-
<span class="label-text font-semibold">Category</span>
312+
<span class="label-text font-semibold">{$t('adventures.category')}</span>
313313
</label>
314314
<select class="select select-bordered w-full" bind:value={selectedCategory}>
315-
<option value="tourism">🏛️ Tourist Attractions</option>
316-
<option value="lodging">🏨 Hotels & Lodging</option>
317-
<option value="food">🍴 Restaurants & Food</option>
315+
<option value="tourism">🏛️ {$t('recomendations.tourism')}</option>
316+
<option value="lodging">🏨 {$t('recomendations.lodging')}</option>
317+
<option value="food">🍴 {$t('recomendations.food')}</option>
318318
</select>
319319
</div>
320320

321321
<!-- Radius Selector -->
322322
<div class="form-control">
323323
<label class="label">
324-
<span class="label-text font-semibold">Search Radius: {radiusDisplay}</span>
324+
<span class="label-text font-semibold"
325+
>{$t('recomendations.search_radius_label')} {radiusDisplay}</span
326+
>
325327
</label>
326328
<select class="select select-bordered w-full" bind:value={radiusValue}>
327329
{#each radiusOptions as option}
@@ -336,28 +338,28 @@
336338
<button class="btn btn-primary flex-1" on:click={searchRecommendations} disabled={loading}>
337339
{#if loading}
338340
<span class="loading loading-spinner loading-sm"></span>
339-
Searching...
341+
{$t('recomendations.searching')}
340342
{:else}
341343
<MagnifyIcon class="w-5 h-5" />
342-
Search
344+
{$t('navbar.search')}
343345
{/if}
344346
</button>
345347
<button class="btn btn-ghost" on:click={() => (showFilters = !showFilters)}>
346348
<TuneVariant class="w-5 h-5" />
347-
Filters
349+
{$t('adventures.filter')}
348350
</button>
349351
</div>
350352

351353
<!-- Advanced Filters -->
352354
{#if showFilters}
353-
<div class="divider">Filters</div>
355+
<div class="divider">{$t('adventures.filter')}</div>
354356
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
355357
<div class="form-control">
356358
<label class="label">
357-
<span class="label-text">Minimum Rating</span>
359+
<span class="label-text">{$t('recomendations.minimum_rating')}</span>
358360
</label>
359361
<select class="select select-bordered select-sm" bind:value={minRating}>
360-
<option value={0}>Any</option>
362+
<option value={0}>{$t('recomendations.any')}</option>
361363
<option value={3}>3+ ⭐</option>
362364
<option value={3.5}>3.5+ ⭐</option>
363365
<option value={4}>4+ ⭐</option>
@@ -366,11 +368,12 @@
366368
</div>
367369

368370
<div class="form-control">
371+
<!-- svelte-ignore a11y-label-has-associated-control -->
369372
<label class="label">
370-
<span class="label-text">Minimum Reviews</span>
373+
<span class="label-text">{$t('recomendations.minimum_reviews')}</span>
371374
</label>
372375
<select class="select select-bordered select-sm" bind:value={minReviews}>
373-
<option value={0}>Any</option>
376+
<option value={0}>{$t('recomendations.any')}</option>
374377
<option value={10}>10+</option>
375378
<option value={50}>50+</option>
376379
<option value={100}>100+</option>
@@ -380,7 +383,7 @@
380383

381384
<div class="form-control">
382385
<label class="label cursor-pointer">
383-
<span class="label-text">Open Now Only</span>
386+
<span class="label-text">{$t('recomendations.open_now_only')}</span>
384387
<input type="checkbox" class="toggle toggle-primary" bind:checked={showOpenOnly} />
385388
</label>
386389
</div>
@@ -406,11 +409,11 @@
406409
<!-- Results Stats -->
407410
<div class="stats shadow w-full">
408411
<div class="stat">
409-
<div class="stat-title">Total Results</div>
412+
<div class="stat-title">{$t('recomendations.total_results')}</div>
410413
<div class="stat-value text-primary">{filteredResults.length}</div>
411414
</div>
412415
<div class="stat">
413-
<div class="stat-title">Average Rating</div>
416+
<div class="stat-title">{$t('recomendations.average_rating')}</div>
414417
<div class="stat-value text-secondary">
415418
{(
416419
filteredResults.filter((r) => r.rating).reduce((sum, r) => sum + (r.rating || 0), 0) /
@@ -420,15 +423,15 @@
420423
</div>
421424
</div>
422425
<div class="stat">
423-
<div class="stat-title">Search Radius</div>
426+
<div class="stat-title">{$t('recomendations.search_radius_label')}</div>
424427
<div class="stat-value text-accent">{radiusDisplay}</div>
425428
</div>
426429
</div>
427430

428431
<!-- Map View -->
429432
<div class="card bg-base-200 shadow-xl">
430433
<div class="card-body">
431-
<h3 class="card-title text-xl mb-4">📍 Map View</h3>
434+
<h3 class="card-title text-xl mb-4">📍 {$t('recomendations.map_view')}</h3>
432435
<div class="rounded-lg overflow-hidden shadow-lg">
433436
<MapLibre
434437
style={getBasemapUrl()}
@@ -650,10 +653,8 @@
650653
<div class="card bg-base-200 shadow-xl">
651654
<div class="card-body text-center py-12">
652655
<MagnifyIcon class="w-24 h-24 mx-auto opacity-30 mb-4" />
653-
<h3 class="text-2xl font-bold mb-2">No Results Yet</h3>
654-
<p class="opacity-70">
655-
Select a location or enter a search query to discover amazing places nearby!
656-
</p>
656+
<h3 class="text-2xl font-bold mb-2">{$t('recomendations.no_results_yet')}</h3>
657+
<p class="opacity-70">{$t('recomendations.select_location_or_query')}</p>
657658
</div>
658659
</div>
659660
{/if}

frontend/src/lib/components/cards/ChecklistCard.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@
280280
class="flex items-center gap-2"
281281
>
282282
<Globe class="w-4 h-4" />
283-
{$t('itinerary.move_to_trip_wide') || 'Move to Trip-wide'}
283+
{$t('itinerary.move_to_trip_context') || 'Move to Trip Context'}
284284
</button>
285285
</li>
286286
{/if}

frontend/src/lib/components/cards/LocationCard.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@
386386
class=" flex items-center gap-2"
387387
>
388388
<Globe class="w-4 h-4" />
389-
{$t('itinerary.move_to_trip_wide') || 'Move to Trip-wide'}
389+
{$t('itinerary.move_to_trip_context') || 'Move to Trip Context'}
390390
</button>
391391
</li>
392392
{/if}

frontend/src/lib/components/cards/LodgingCard.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@
206206
class="flex items-center gap-2"
207207
>
208208
<Globe class="w-4 h-4" />
209-
{$t('itinerary.move_to_trip_wide') || 'Move to Trip-wide'}
209+
{$t('itinerary.move_to_trip_context') || 'Move to Trip Context'}
210210
</button>
211211
</li>
212212
{/if}

frontend/src/lib/components/cards/NoteCard.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@
201201
class="text-info flex items-center gap-2"
202202
>
203203
<Globe class="w-4 h-4 text-info" />
204-
{$t('itinerary.move_to_trip_wide') || 'Move to Trip-wide'}
204+
{$t('itinerary.move_to_trip_context') || 'Move to Trip Context'}
205205
</button>
206206
</li>
207207
{/if}

frontend/src/lib/components/cards/TransportationCard.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@
240240
class=" flex items-center gap-2"
241241
>
242242
<Globe class="w-4 h-4 " />
243-
{$t('itinerary.move_to_trip_wide') || 'Move to Trip-wide'}
243+
{$t('itinerary.move_to_trip_context') || 'Move to Trip Context'}
244244
</button>
245245
</li>
246246
{/if}

0 commit comments

Comments
 (0)