feat: translate all pages
Some checks failed
TrafficCue CI / check (push) Failing after 32s
TrafficCue CI / build (push) Failing after 35s

This commit is contained in:
Cfp
2025-08-14 16:05:35 +02:00
parent b3f3c4f0b2
commit 4e3f1b06b2
23 changed files with 359 additions and 106 deletions

View File

@ -5,5 +5,122 @@
"school": "Schule", "school": "Schule",
"work": "Arbeit", "work": "Arbeit",
"no-location": "Kein {name} Speicherort gespeichert." "no-location": "Kein {name} Speicherort gespeichert."
},
"location": {
"unlock": "Standort entsperren",
"lock": "Standort sperren",
"code": "Standortcode",
"start": "Standort teilen",
"join": "Remote-Standort beitreten"
},
"vehicles": {
"selector": {
"title": "Fahrzeugauswahl",
"description": "Wählen Sie Ihr Fahrzeug aus, um die Route individuell auf Sie abzustimmen.",
"add": "Fahrzeug hinzufügen"
},
"types": {
"car": "Auto",
"moped": "Moped"
},
"add": {
"name": "Fahrzeugname",
"legal-speed": "Legale Geschwindigkeit",
"actual-speed": "Tatsächliche Geschwindigkeit",
"fuel": "Kraftstoffart",
"preferred-fuel": "Bevorzugter Kraftstoff",
"diesel": "Diesel",
"petrol": "Benzin",
"electric": "Elektrisch",
"errors": {
"enter-name": "Bitte geben Sie einen Fahrzeugnamen ein.",
"enter-speeds": "Bitte geben Sie gültige Geschwindigkeiten ein.",
"select-fuel": "Bitte wählen Sie eine gültige Kraftstoffart und den bevorzugten Kraftstoff aus."
}
},
"default": "Standardfahrzeug"
},
"save": "Speichern",
"cancel": "Abbrechen",
"loading": "Laden...",
"more": "Mehr",
"error": "Fehler",
"sidebar": {
"about": {
"header": "Über",
"powered-by": "Angetrieben von",
"contributors": "Mitwirkende"
},
"developer": {
"header": "Entwicklereinstellungen"
},
"offline-maps": {
"header": "Offline-Karten",
"not-available": "Keine Offline-Karten verfügbar.",
"only-mobile": "Offline-Karten sind nur auf Mobilgeräten verfügbar.",
"downloaded": "{name} wurde heruntergeladen"
},
"language": {
"header": "Sprache"
},
"map-style": {
"header": "Kartenstil"
},
"settings": {
"header": "Einstellungen",
"general": "Allgemein",
"map": "Karte"
},
"info": {
"dropped": "Pin",
"route": "Route",
"email": "E-Mail",
"website": "Webseite",
"call": "Anrufen",
"set-as": "Speichern als {name}",
"payment-methods": "Zahlungsarten",
"fuel-types": "Kraftstoffarten",
"prices": "Kraftstoffpreise",
"no-prices": "Keine Kraftstoffpreise verfügbar.",
"error-loading-prices": "Fehler beim Laden der Kraftstoffpreise",
"internet-access": "Internetzugang",
"fee": "Gebühr",
"opening-hours": "Öffnungszeiten",
"open": "Offen",
"closed": "Geschlossen",
"restaurant": "Restaurant",
"reviews": "Bewertungen",
"no-reviews": "Keine Bewertungen.",
"write-review": "Eigene Bewertung schreiben"
},
"mapai": {
"ask-question": "Stellen Sie eine Frage zu diesem Ort ..."
},
"in-route": {
"left": "links",
"end-trip": "Route beenden",
"share-code": "Code teilen",
"stop-sharing": "Standortfreigabe beenden",
"share-location": "Routenstatus und Standort teilen"
},
"route": {
"header": "Route",
"driving-with": "Fahren mit",
"help": "Sie können <strong>aktuell</strong> für Ihren aktuellen Standort und <strong>home</strong>, <strong>school</strong> oder <strong>work</strong> für gespeicherte Standorte verwenden.",
"calculate": "Berechnen"
},
"search": {
"header": "Suchergebnisse für"
},
"trip": {
"header": "Routendetails",
"start": "Start",
"save": "Speichern",
"send": "Senden"
},
"user": {
"header": "Benutzer",
"login": "Login"
}
} }
} }

View File

@ -1,9 +1,126 @@
{ {
"$schema": "https://inlang.com/schema/inlang-message-format", "$schema": "https://inlang.com/schema/inlang-message-format",
"save": "Save",
"cancel": "Cancel",
"loading": "Loading...",
"more": "More",
"error": "Error",
"saved": { "saved": {
"home": "Home", "home": "Home",
"school": "School", "school": "School",
"work": "Work", "work": "Work",
"no-location": "No {name} location saved." "no-location": "No {name} location saved."
},
"location": {
"unlock": "Unlock Location",
"lock": "Lock Location",
"code": "Advertise code",
"start": "Advertise Location",
"join": "Join Remote Location"
},
"vehicles": {
"default": "Default Vehicle",
"selector": {
"title": "Vehicle Selector",
"description": "Select your vehicle to customize routing just for you.",
"add": "Add Vehicle"
},
"types": {
"car": "Car",
"moped": "Moped"
},
"add": {
"name": "Vehicle Name",
"legal-speed": "Legal Speed",
"actual-speed": "Actual Speed",
"fuel": "Fuel Type",
"preferred-fuel": "Preferred Fuel",
"diesel": "Diesel",
"petrol": "Petrol",
"electric": "Electric",
"errors": {
"enter-name": "Please enter a vehicle name.",
"enter-speeds": "Please enter valid speeds.",
"select-fuel": "Please select a valid fuel type and preferred fuel."
}
}
},
"sidebar": {
"about": {
"header": "About",
"powered-by": "Powered by",
"contributors": "contributors"
},
"developer": {
"header": "Developer Settings"
},
"offline-maps": {
"header": "Offline Maps",
"not-available": "No offline maps available.",
"only-mobile": "Offline maps are only available on mobile.",
"downloaded": "Downloaded {name}"
},
"language": {
"header": "Language"
},
"map-style": {
"header": "Map Style"
},
"settings": {
"header": "Settings",
"general": "General",
"map": "Map"
},
"info": {
"dropped": "Dropped Pin",
"route": "Route",
"email": "Email",
"website": "Website",
"call": "Call",
"set-as": "Set as {name}",
"payment-methods": "Payment Methods",
"fuel-types": "Fuel Types",
"prices": "Fuel Prices",
"no-prices": "No fuel prices available.",
"error-loading-prices": "Error loading fuel prices",
"internet-access": "Internet Access",
"fee": "Fee",
"opening-hours": "Opening Hours",
"open": "Open",
"closed": "Closed",
"restaurant": "Restaurant",
"reviews": "Reviews",
"no-reviews": "No reviews.",
"write-review": "Write a review"
},
"mapai": {
"ask-question": "Ask a question about this place..."
},
"in-route": {
"left": "left",
"end-trip": "End Trip",
"share-code": "Share Code",
"stop-sharing": "Stop Sharing Location",
"share-location": "Share Trip Status & Location"
},
"route": {
"header": "Route",
"driving-with": "Driving with",
"help": "You can use <strong>current</strong> for your current location, <strong>home</strong>, <strong>school</strong> or <strong>work</strong> for saved locations.",
"calculate": "Calculate"
},
"search": {
"header": "Search Results for"
},
"trip": {
"header": "Trip Details",
"start": "Start Navigation",
"save": "Save",
"send": "Send"
},
"user": {
"header": "User",
"login": "Login"
}
} }
} }

View File

@ -21,6 +21,7 @@
import * as Select from "../ui/select"; import * as Select from "../ui/select";
import Input from "../ui/input/input.svelte"; import Input from "../ui/input/input.svelte";
import EvConnectorSelect from "./EVConnectorSelect.svelte"; import EvConnectorSelect from "./EVConnectorSelect.svelte";
import { m } from "$lang/messages";
let open = $state(false); let open = $state(false);
@ -62,7 +63,7 @@
</Drawer.Trigger> </Drawer.Trigger>
<Drawer.Content> <Drawer.Content>
<Drawer.Header> <Drawer.Header>
<Drawer.Title>Add Vehicle</Drawer.Title> <Drawer.Title>{m["vehicles.selector.add"]()}</Drawer.Title>
</Drawer.Header> </Drawer.Header>
<div class="p-4 pt-0 flex flex-col gap-2"> <div class="p-4 pt-0 flex flex-col gap-2">
<div class="flex gap-2"> <div class="flex gap-2">
@ -71,75 +72,75 @@
{@const Icon = getVehicleIcon(vehicle.type)} {@const Icon = getVehicleIcon(vehicle.type)}
<Icon /> <Icon />
{vehicle.type === "car" {vehicle.type === "car"
? "Car" ? m["vehicles.types.car"]()
: vehicle.type === "motor_scooter" : vehicle.type === "motor_scooter"
? "Moped" ? m["vehicles.types.moped"]()
: "?"} : "?"}
</Select.Trigger> </Select.Trigger>
<Select.Content> <Select.Content>
<Select.Item value="car"> <Select.Item value="car">
<CarIcon /> <CarIcon />
Car {m["vehicles.types.car"]()}
</Select.Item> </Select.Item>
<Select.Item value="motor_scooter"> <Select.Item value="motor_scooter">
<TractorIcon /> <TractorIcon />
Moped {m["vehicles.types.moped"]()}
</Select.Item> </Select.Item>
</Select.Content> </Select.Content>
</Select.Root> </Select.Root>
<Input <Input
type="text" type="text"
placeholder="Vehicle Name" placeholder={m["vehicles.add.name"]()}
bind:value={vehicle.name} bind:value={vehicle.name}
class="w-full" class="w-full"
aria-label="Vehicle Name" aria-label={m["vehicles.add.name"]()}
aria-required="true" aria-required="true"
/> />
</div> </div>
<div class="flex justify-around mt-4"> <div class="flex justify-around mt-4">
<span>Legal Speed</span> <span>{m["vehicles.add.legal-speed"]()}</span>
<span>/</span> <span>/</span>
<span>Actual Speed</span> <span>{m["vehicles.add.actual-speed"]()}</span>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<Input <Input
type="number" type="number"
placeholder="Legal Speed" placeholder={m["vehicles.add.legal-speed"]()}
bind:value={vehicle.legalMaxSpeed} bind:value={vehicle.legalMaxSpeed}
class="w-full text-center" class="w-full text-center"
aria-label="Legal Max Speed" aria-label={m["vehicles.add.legal-speed"]()}
aria-required="true" aria-required="true"
/> />
<Input <Input
type="number" type="number"
placeholder="Actual Speed" placeholder={m["vehicles.add.actual-speed"]()}
bind:value={vehicle.actualMaxSpeed} bind:value={vehicle.actualMaxSpeed}
class="w-full text-center" class="w-full text-center"
aria-label="Actual Max Speed" aria-label={m["vehicles.add.actual-speed"]()}
aria-required="true" aria-required="true"
/> />
</div> </div>
<div class="flex justify-around mt-4"> <div class="flex justify-around mt-4">
<span>Fuel Type</span> <span>{m["vehicles.add.fuel"]()}</span>
<span>/</span> <span>/</span>
<span>Preferred Fuel</span> <span>{m["vehicles.add.preferred-fuel"]()}</span>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<Select.Root type="single" bind:value={vehicle.fuelType}> <Select.Root type="single" bind:value={vehicle.fuelType}>
<Select.Trigger class="w-full"> <Select.Trigger class="w-full">
{vehicle.fuelType === "diesel" {vehicle.fuelType === "diesel"
? "Diesel" ? m["vehicles.add.diesel"]()
: vehicle.fuelType === "petrol" : vehicle.fuelType === "petrol"
? "Petrol" ? m["vehicles.add.petrol"]()
: "Electric"} : m["vehicles.add.electric"]()}
</Select.Trigger> </Select.Trigger>
<Select.Content> <Select.Content>
<Select.Item value="diesel">Diesel</Select.Item> <Select.Item value="diesel">{m["vehicles.add.diesel"]()}</Select.Item>
<Select.Item value="petrol">Petrol</Select.Item> <Select.Item value="petrol">{m["vehicles.add.petrol"]()}</Select.Item>
<Select.Item value="electric">Electric</Select.Item> <Select.Item value="electric">{m["vehicles.add.electric"]()}</Select.Item>
</Select.Content> </Select.Content>
</Select.Root> </Select.Root>
@ -165,15 +166,15 @@
onclick={() => { onclick={() => {
open = false; open = false;
if (vehicle.name.trim() === "") { if (vehicle.name.trim() === "") {
alert("Please enter a vehicle name."); alert(m["vehicles.add.errors.enter-name"]());
return; return;
} }
if (vehicle.legalMaxSpeed <= 0 || vehicle.actualMaxSpeed <= 0) { if (vehicle.legalMaxSpeed <= 0 || vehicle.actualMaxSpeed <= 0) {
alert("Please enter valid speeds."); alert(m["vehicles.add.errors.enter-speeds"]());
return; return;
} }
if (!isValidFuel(vehicle)) { if (!isValidFuel(vehicle)) {
alert("Please select a valid fuel type and preferred fuel."); alert(m["vehicles.add.errors.select-fuel"]());
return; return;
} }
setVehicles([...vehicles, vehicle]); setVehicles([...vehicles, vehicle]);
@ -182,7 +183,7 @@
}} }}
> >
<SaveIcon /> <SaveIcon />
Save {m.save()}
</Button> </Button>
<Button <Button
variant="secondary" variant="secondary"
@ -191,7 +192,7 @@
}} }}
> >
<XIcon /> <XIcon />
Cancel {m.cancel()}
</Button> </Button>
</Drawer.Footer> </Drawer.Footer>
</Drawer.Content> </Drawer.Content>

View File

@ -31,6 +31,7 @@
import AboutSidebar from "./sidebar/settings/AboutSidebar.svelte"; import AboutSidebar from "./sidebar/settings/AboutSidebar.svelte";
import OfflineMapsSidebar from "./sidebar/settings/OfflineMapsSidebar.svelte"; import OfflineMapsSidebar from "./sidebar/settings/OfflineMapsSidebar.svelte";
import DeveloperSidebar from "./sidebar/settings/DeveloperSidebar.svelte"; import DeveloperSidebar from "./sidebar/settings/DeveloperSidebar.svelte";
import { m } from "$lang/messages";
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const views: Record<string, Component<any>> = { const views: Record<string, Component<any>> = {
@ -188,10 +189,10 @@
location.toggleLock(); location.toggleLock();
}} }}
> >
{location.locked ? "Unlock Location" : "Lock Location"} {location.locked ? m["location.unlock"]() : m["location.unlock"]()}
</Button> </Button>
{#if location.code} {#if location.code}
<span>Advertise code: {location.code}</span> <span>{m["location.code"]()}: {location.code}</span>
{/if} {/if}
<Button <Button
variant="outline" variant="outline"
@ -199,7 +200,7 @@
advertiseRemoteLocation(); advertiseRemoteLocation();
}} }}
> >
Advertise Location {m["location.start"]()}
</Button> </Button>
<Button <Button
variant="outline" variant="outline"
@ -207,7 +208,7 @@
remoteLocation(prompt("Code?") || ""); remoteLocation(prompt("Code?") || "");
}} }}
> >
Join Remote Location {m["location.join"]()}
</Button> </Button>
</div> </div>
</Popover.Content> </Popover.Content>

View File

@ -16,6 +16,7 @@
type VehicleType, type VehicleType,
} from "$lib/vehicles/vehicles.svelte"; } from "$lib/vehicles/vehicles.svelte";
import AddVehicleDrawer from "./AddVehicleDrawer.svelte"; import AddVehicleDrawer from "./AddVehicleDrawer.svelte";
import { m } from "$lang/messages";
let open = $state(false); let open = $state(false);
@ -48,9 +49,9 @@
</Drawer.Trigger> </Drawer.Trigger>
<Drawer.Content> <Drawer.Content>
<Drawer.Header> <Drawer.Header>
<Drawer.Title>Vehicle Selector</Drawer.Title> <Drawer.Title>{m["vehicles.selector.title"]()}</Drawer.Title>
<Drawer.Description <Drawer.Description
>Select your vehicle to customize routing just for you.</Drawer.Description >{m["vehicles.selector.description"]()}</Drawer.Description
> >
</Drawer.Header> </Drawer.Header>
<div class="p-4 pt-0 flex flex-col gap-2"> <div class="p-4 pt-0 flex flex-col gap-2">
@ -72,7 +73,7 @@
<AddVehicleDrawer> <AddVehicleDrawer>
<Button variant="secondary" class="w-full p-5"> <Button variant="secondary" class="w-full p-5">
<PlusCircleIcon /> <PlusCircleIcon />
Add Vehicle {m["vehicles.selector.add"]()}
</Button> </Button>
</AddVehicleDrawer> </AddVehicleDrawer>
</div> </div>

View File

@ -1,4 +1,5 @@
<script> <script>
import { m } from "$lang/messages";
import Badge from "$lib/components/ui/badge/badge.svelte"; import Badge from "$lib/components/ui/badge/badge.svelte";
import { getStations } from "$lib/services/MTSK"; import { getStations } from "$lib/services/MTSK";
import RequiresCapability from "../RequiresCapability.svelte"; import RequiresCapability from "../RequiresCapability.svelte";
@ -6,7 +7,7 @@
let { tags, lat, lng } = $props(); let { tags, lat, lng } = $props();
</script> </script>
<h3 class="text-lg font-bold mt-2">Fuel Types</h3> <h3 class="text-lg font-bold mt-2">{m["sidebar.info.fuel-types"]()}</h3>
<ul class="flex gap-2 flex-wrap"> <ul class="flex gap-2 flex-wrap">
{#each Object.entries(tags).filter( ([key]) => key.startsWith("fuel:"), ) as [key, tag] (key)} {#each Object.entries(tags).filter( ([key]) => key.startsWith("fuel:"), ) as [key, tag] (key)}
<!-- <li>{key.replace("fuel:", "")}: {tag}</li> --> <!-- <li>{key.replace("fuel:", "")}: {tag}</li> -->
@ -19,9 +20,9 @@
</ul> </ul>
<RequiresCapability capability="fuel"> <RequiresCapability capability="fuel">
<h3 class="text-lg font-bold mt-2">Prices</h3> <h3 class="text-lg font-bold mt-2">{m["sidebar.info.prices"]()}</h3>
{#await getStations(lat, lng)} {#await getStations(lat, lng)}
<p>Loading fuel prices...</p> <p>{m.loading()}</p>
{:then stations} {:then stations}
{#if stations.stations.length > 0} {#if stations.stations.length > 0}
{@const station = stations.stations[0]} {@const station = stations.stations[0]}
@ -35,9 +36,9 @@
<p>E5: {station.e5}</p> <p>E5: {station.e5}</p>
{/if} {/if}
{:else} {:else}
<p>No fuel prices available.</p> <p>{m["sidebar.info.no-prices"]()}</p>
{/if} {/if}
{:catch err} {:catch err}
<p>Error loading fuel prices: {err.message}</p> <p>{m["sidebar.info.error-loading-prices"]()}: {err.message}</p>
{/await} {/await}
</RequiresCapability> </RequiresCapability>

View File

@ -1,4 +1,5 @@
<script> <script>
import { m } from "$lang/messages";
import Badge from "$lib/components/ui/badge/badge.svelte"; import Badge from "$lib/components/ui/badge/badge.svelte";
let { tags } = $props(); let { tags } = $props();
@ -8,12 +9,12 @@
</script> </script>
{#if tag != "no"} {#if tag != "no"}
<h3 class="text-lg font-bold mt-2">Internet Access</h3> <h3 class="text-lg font-bold mt-2">{m["sidebar.info.internet-access"]()}</h3>
{#each splitter as value, _index (value)} {#each splitter as value, _index (value)}
<Badge>{value}</Badge> <Badge>{value}</Badge>
{/each} {/each}
{#if tags["internet_access:fee"] && tags["internet_access:fee"] != "no"} {#if tags["internet_access:fee"] && tags["internet_access:fee"] != "no"}
<Badge>Fee: {tags["internet_access:fee"]}</Badge> <Badge>{m["sidebar.info.fee"]()}: {tags["internet_access:fee"]}</Badge>
{/if} {/if}
{#if tags["internet_access:ssid"]} {#if tags["internet_access:ssid"]}
<Badge>{tags["internet_access:ssid"]}</Badge> <Badge>{tags["internet_access:ssid"]}</Badge>

View File

@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { m } from "$lang/messages";
import Input from "$lib/components/ui/input/input.svelte"; import Input from "$lib/components/ui/input/input.svelte";
import { ai } from "$lib/services/lnv"; import { ai } from "$lib/services/lnv";
import { SparklesIcon } from "@lucide/svelte"; import { SparklesIcon } from "@lucide/svelte";
@ -23,17 +24,17 @@
<SparklesIcon /> <SparklesIcon />
<div class="flex gap-2 flex-col w-full"> <div class="flex gap-2 flex-col w-full">
{#await ai(question, { lat, lon })} {#await ai(question, { lat, lon })}
<p>Loading...</p> <p>{m.loading()}</p>
{:then data} {:then data}
{@const text = getText(data)} {@const text = getText(data)}
<p>{text}</p> <p>{text}</p>
{:catch error} {:catch error}
<p>Error: {error.message}</p> <p>{m.error()}: {error.message}</p>
{/await} {/await}
<Input <Input
type="text" type="text"
value="" value=""
placeholder="Ask a question about this place..." placeholder={m["sidebar.mapai.ask-question"]()}
onchange={(e) => { onchange={(e) => {
question = (e.target! as HTMLInputElement).value; question = (e.target! as HTMLInputElement).value;
}} }}

View File

@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { m } from "$lang/messages";
import Badge from "$lib/components/ui/badge/badge.svelte"; import Badge from "$lib/components/ui/badge/badge.svelte";
import opening_hours from "opening_hours"; import opening_hours from "opening_hours";
@ -18,11 +19,11 @@
</script> </script>
<h3 class="text-lg font-bold mt-2"> <h3 class="text-lg font-bold mt-2">
Opening Hours {m["sidebar.info.opening-hours"]()}
{#if oh.getState()} {#if oh.getState()}
<Badge>Open</Badge> <Badge>{m["sidebar.info.open"]()}</Badge>
{:else} {:else}
<Badge variant="destructive">Closed</Badge> <Badge variant="destructive">{m["sidebar.info.closed"]()}</Badge>
{/if} {/if}
</h3> </h3>

View File

@ -1,4 +1,5 @@
<script> <script>
import { m } from "$lang/messages";
import Badge from "$lib/components/ui/badge/badge.svelte"; import Badge from "$lib/components/ui/badge/badge.svelte";
let { tags } = $props(); let { tags } = $props();
@ -15,7 +16,7 @@
</script> </script>
{#if matchingTags.length > 0} {#if matchingTags.length > 0}
<h3 class="text-lg font-bold mt-2">Restaurant</h3> <h3 class="text-lg font-bold mt-2">{m["sidebar.info.restaurant"]()}</h3>
<ul class="flex gap-2 flex-wrap"> <ul class="flex gap-2 flex-wrap">
{#each matchingTags as tag, _index (tag)} {#each matchingTags as tag, _index (tag)}
{#if tag != "no"} {#if tag != "no"}

View File

@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { m } from "$lang/messages";
import * as Avatar from "$lib/components/ui/avatar"; import * as Avatar from "$lib/components/ui/avatar";
import { Button } from "$lib/components/ui/button"; import { Button } from "$lib/components/ui/button";
import { getReviews, postReview } from "$lib/services/lnv"; import { getReviews, postReview } from "$lib/services/lnv";
@ -7,7 +8,7 @@
let { lat, lng }: { lat: number; lng: number } = $props(); let { lat, lng }: { lat: number; lng: number } = $props();
</script> </script>
<h3 class="text-lg font-bold mt-2">Reviews</h3> <h3 class="text-lg font-bold mt-2">{m["sidebar.info.reviews"]()}</h3>
{#await getReviews({ lat, lon: lng }) then reviews} {#await getReviews({ lat, lon: lng }) then reviews}
{#if reviews.length > 0} {#if reviews.length > 0}
<ul class="list-disc pl-5"> <ul class="list-disc pl-5">
@ -25,7 +26,7 @@
{/each} {/each}
</ul> </ul>
{:else} {:else}
<p>No reviews available.</p> <p>{m["sidebar.info.no-reviews"]()}</p>
{/if} {/if}
<Button <Button
variant="secondary" variant="secondary"
@ -46,8 +47,8 @@
alert("Review submission cancelled."); alert("Review submission cancelled.");
} }
}} }}
disabled>Write a review</Button disabled>{m["sidebar.info.write-review"]()}</Button
><br /> ><br />
{:catch error} {:catch error}
<p>Error loading reviews: {error.message}</p> <p>{m.error()}: {error.message}</p>
{/await} {/await}

View File

@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { m } from "$lang/messages";
import { Button } from "$lib/components/ui/button"; import { Button } from "$lib/components/ui/button";
import { import {
decodePolyline, decodePolyline,
@ -122,7 +123,7 @@
}); });
</script> </script>
{fullDistanceText} left {fullDistanceText} {m["sidebar.in-route.left"]()}
<Button <Button
onclick={() => { onclick={() => {
@ -133,12 +134,12 @@
<Button <Button
onclick={() => { onclick={() => {
stopNavigation(); stopNavigation();
}}>End Trip</Button }}>{m["sidebar.in-route.end-trip"]()}</Button
> >
<div class="flex flex-col gap-2 mt-5"> <div class="flex flex-col gap-2 mt-5">
{#if location.code} {#if location.code}
<span>Share Code: {location.code}</span> <span>{m["sidebar.in-route.share-code"]()}: {location.code}</span>
<Button <Button
variant="secondary" variant="secondary"
onclick={() => { onclick={() => {
@ -147,7 +148,7 @@
location.code = null; location.code = null;
}} }}
> >
Stop Sharing Location {m["sidebar.in-route.stop-sharing"]()}
</Button> </Button>
{:else} {:else}
<Button <Button
@ -156,7 +157,7 @@
advertiseRemoteLocation(); advertiseRemoteLocation();
}} }}
> >
Share Trip Status & Location {m["sidebar.in-route.share-location"]()}
</Button> </Button>
{/if} {/if}
</div> </div>

View File

@ -26,6 +26,7 @@
import { getDeveloperToggle } from "./settings/developer.svelte"; import { getDeveloperToggle } from "./settings/developer.svelte";
import InternetAccess from "../info/InternetAccess.svelte"; import InternetAccess from "../info/InternetAccess.svelte";
import RestaurantInfo from "../info/RestaurantInfo.svelte"; import RestaurantInfo from "../info/RestaurantInfo.svelte";
import { m } from "$lang/messages";
// let { feature }: { feature: Feature } = $props(); // let { feature }: { feature: Feature } = $props();
@ -88,9 +89,9 @@
pin.liftPin(); pin.liftPin();
}} }}
> >
Dropped Pin {m["sidebar.info.dropped"]()}
</SidebarHeader> </SidebarHeader>
<p>Loading...</p> <p>{m.loading()}</p>
{:then res} {:then res}
{#if res.elements.length === 0} {#if res.elements.length === 0}
<SidebarHeader <SidebarHeader
@ -98,7 +99,7 @@
pin.liftPin(); pin.liftPin();
}} }}
> >
Dropped Pin {m["sidebar.info.dropped"]()}
</SidebarHeader> </SidebarHeader>
<span style="color: #acacac;">&copy; OpenStreetMap</span> <span style="color: #acacac;">&copy; OpenStreetMap</span>
<pre>{JSON.stringify(res, null, 2)}</pre> <pre>{JSON.stringify(res, null, 2)}</pre>
@ -132,7 +133,7 @@
}} }}
> >
<RouteIcon /> <RouteIcon />
Route {m["sidebar.info.route"]()}
</Button> </Button>
{#if tags.email || tags["contact:email"]} {#if tags.email || tags["contact:email"]}
<Button <Button
@ -141,7 +142,7 @@
target="_blank" target="_blank"
> >
<MailIcon /> <MailIcon />
Email {m["sidebar.info.email"]()}
</Button> </Button>
{/if} {/if}
{#if tags.website || tags["contact:website"]} {#if tags.website || tags["contact:website"]}
@ -151,7 +152,7 @@
target="_blank" target="_blank"
> >
<GlobeIcon /> <GlobeIcon />
Website {m["sidebar.info.website"]()}
</Button> </Button>
{/if} {/if}
{#if tags.phone || tags["contact:phone"]} {#if tags.phone || tags["contact:phone"]}
@ -161,14 +162,14 @@
target="_blank" target="_blank"
> >
<PhoneIcon /> <PhoneIcon />
Call {m["sidebar.info.call"]()}
</Button> </Button>
{/if} {/if}
<Popover.Root> <Popover.Root>
<Popover.Trigger> <Popover.Trigger>
<Button variant="secondary"> <Button variant="secondary">
<EllipsisIcon /> <EllipsisIcon />
More {m.more()}
</Button> </Button>
</Popover.Trigger> </Popover.Trigger>
<Popover.Content> <Popover.Content>
@ -185,7 +186,7 @@
}} }}
> >
<HomeIcon /> <HomeIcon />
Set as Home {m["sidebar.info.set-as"]({ name: m["saved.home"]() })}
</Button> </Button>
<Button <Button
variant="outline" variant="outline"
@ -195,7 +196,7 @@
}} }}
> >
<SchoolIcon /> <SchoolIcon />
Set as School {m["sidebar.info.set-as"]({ name: m["saved.school"]() })}
</Button> </Button>
<Button <Button
variant="outline" variant="outline"
@ -209,7 +210,7 @@
}} }}
> >
<BriefcaseIcon /> <BriefcaseIcon />
Set as Work {m["sidebar.info.set-as"]({ name: m["saved.work"]() })}
</Button> </Button>
{#if dev.current} {#if dev.current}
<Button <Button
@ -255,7 +256,7 @@
<!-- any payment:* tag --> <!-- any payment:* tag -->
{#if Object.keys(tags).some((key) => key.startsWith("payment:"))} {#if Object.keys(tags).some((key) => key.startsWith("payment:"))}
<h3 class="text-lg font-bold mt-2">Payment Methods</h3> <h3 class="text-lg font-bold mt-2">{m["sidebar.info.payment-methods"]()}</h3>
<ul style="display: flex; flex-wrap: wrap; gap: 0.5rem;"> <ul style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
{#each Object.entries(tags).filter( ([key]) => key.startsWith("payment:"), ) as [key, value] (key)} {#each Object.entries(tags).filter( ([key]) => key.startsWith("payment:"), ) as [key, value] (key)}
<Badge <Badge

View File

@ -22,12 +22,12 @@
onclick={() => { onclick={() => {
const loc = saved.home; const loc = saved.home;
if (!loc) { if (!loc) {
alert(m["saved.no-location"](m["saved.home"])); alert(m["saved.no-location"]({ name: m["saved.home"]() }));
return; return;
} }
const { lat, lon } = loc; const { lat, lon } = loc;
if (!lat || !lon) { if (!lat || !lon) {
alert(m["saved.no-location"](m["saved.home"])); alert(m["saved.no-location"]({ name: m["saved.home"]() }));
return; return;
} }
pin.dropPin(lat, lon); pin.dropPin(lat, lon);
@ -48,12 +48,12 @@
console.log(saved); console.log(saved);
const loc = saved.school; const loc = saved.school;
if (!loc) { if (!loc) {
alert(m["saved.no-location"](m["saved.school"])); alert(m["saved.no-location"]({ name: m["saved.school"]() }));
return; return;
} }
const { lat, lon } = loc; const { lat, lon } = loc;
if (!lat || !lon) { if (!lat || !lon) {
alert(m["saved.no-location"](m["saved.school"])); alert(m["saved.no-location"]({ name: m["saved.school"]() }));
return; return;
} }
pin.dropPin(lat, lon); pin.dropPin(lat, lon);
@ -73,12 +73,12 @@
onclick={() => { onclick={() => {
const loc = saved.work; const loc = saved.work;
if (!loc) { if (!loc) {
alert(m["saved.no-location"](m["saved.work"])); alert(m["saved.no-location"]({ name: m["saved.work"]() }));
return; return;
} }
const { lat, lon } = loc; const { lat, lon } = loc;
if (!lat || !lon) { if (!lat || !lon) {
alert(m["saved.no-location"](m["saved.work"])); alert(m["saved.no-location"]({ name: m["saved.work"]() }));
return; return;
} }
pin.dropPin(lat, lon); pin.dropPin(lat, lon);

View File

@ -19,6 +19,7 @@
} from "$lib/vehicles/vehicles.svelte"; } from "$lib/vehicles/vehicles.svelte";
import { location } from "../location.svelte"; import { location } from "../location.svelte";
import { saved } from "$lib/saved.svelte"; import { saved } from "$lib/saved.svelte";
import { m } from "$lang/messages";
let { let {
from, from,
@ -46,11 +47,11 @@
removeAllRoutes(); removeAllRoutes();
}} }}
> >
Route {m["sidebar.route.header"]()}
</SidebarHeader> </SidebarHeader>
<span <span
>Driving with <strong>{(selectedVehicle() ?? DefaultVehicle).name}</strong >{m["sidebar.route.driving-with"]()} <strong>{(selectedVehicle() ?? DefaultVehicle).name}</strong
></span ></span
> >
<div class="flex flex-col gap-2 w-full mb-2"> <div class="flex flex-col gap-2 w-full mb-2">
@ -66,9 +67,7 @@
<Input bind:value={toLocation} /> <Input bind:value={toLocation} />
</div> </div>
<span> <span>
You can use <strong>current</strong> for your current location, {@html m["sidebar.route.help"]()}
<strong>home</strong>
or <strong>work</strong> for saved locations.
</span> </span>
</div> </div>
<Button <Button
@ -110,7 +109,7 @@
} }
drawAllRoutes(routes); drawAllRoutes(routes);
zoomToPoints(FROM, TO, map.value!); zoomToPoints(FROM, TO, map.value!);
}}>Calculate</Button }}>{m["sidebar.route.calculate"]()}</Button
> >
{#if routes} {#if routes}

View File

@ -6,6 +6,7 @@
import type { Feature } from "$lib/services/Search"; import type { Feature } from "$lib/services/Search";
import SidebarHeader from "./SidebarHeader.svelte"; import SidebarHeader from "./SidebarHeader.svelte";
import { searchbar } from "../view.svelte"; import { searchbar } from "../view.svelte";
import { m } from "$lang/messages";
let { let {
results, results,
@ -21,7 +22,7 @@
searchbar.text = ""; searchbar.text = "";
}} }}
> >
Search Results for "{query}" {m["sidebar.search.header"]()} "{query}"
</SidebarHeader> </SidebarHeader>
<div <div
id="results" id="results"

View File

@ -9,6 +9,7 @@
import { Button } from "$lib/components/ui/button"; import { Button } from "$lib/components/ui/button";
import { RouteIcon, SaveIcon, SendIcon } from "@lucide/svelte"; import { RouteIcon, SaveIcon, SendIcon } from "@lucide/svelte";
import { map } from "../map.svelte"; import { map } from "../map.svelte";
import { m } from "$lang/messages";
let { let {
route, route,
@ -27,7 +28,7 @@
removeAllRoutes(); removeAllRoutes();
}} }}
> >
Trip Details {m["sidebar.trip.header"]()}
</SidebarHeader> </SidebarHeader>
<div id="actions" class="flex gap-2"> <div id="actions" class="flex gap-2">
@ -40,15 +41,15 @@
}} }}
> >
<RouteIcon /> <RouteIcon />
Start Navigation {m["sidebar.trip.start"]()}
</Button> </Button>
<Button variant="secondary" disabled> <Button variant="secondary" disabled>
<SaveIcon /> <SaveIcon />
Save {m["sidebar.trip.save"]()}
</Button> </Button>
<Button variant="secondary" disabled> <Button variant="secondary" disabled>
<SendIcon /> <SendIcon />
Send {m["sidebar.trip.send"]()}
</Button> </Button>
</div> </div>

View File

@ -4,6 +4,7 @@
import Button from "$lib/components/ui/button/button.svelte"; import Button from "$lib/components/ui/button/button.svelte";
import { getAuthURL, getOIDCUser } from "$lib/services/oidc"; import { getAuthURL, getOIDCUser } from "$lib/services/oidc";
import * as Avatar from "$lib/components/ui/avatar"; import * as Avatar from "$lib/components/ui/avatar";
import { m } from "$lang/messages";
interface OIDCUser { interface OIDCUser {
sub: string; sub: string;
@ -26,7 +27,7 @@
</script> </script>
{#if !user} {#if !user}
<SidebarHeader>User</SidebarHeader> <SidebarHeader>{m["sidebar.user.header"]()}</SidebarHeader>
<Button <Button
onclick={async () => { onclick={async () => {
@ -57,7 +58,7 @@
atob((localStorage.getItem("lnv-id") || "").split(".")[1]), atob((localStorage.getItem("lnv-id") || "").split(".")[1]),
); );
}); });
}}>Login</Button }}>{m["sidebar.user.login"]()}</Button
> >
{:else} {:else}
<SidebarHeader> <SidebarHeader>

View File

@ -1,4 +1,5 @@
<script> <script>
import { m } from "$lang/messages";
import SidebarHeader from "../SidebarHeader.svelte"; import SidebarHeader from "../SidebarHeader.svelte";
import { getDeveloperToggle } from "./developer.svelte"; import { getDeveloperToggle } from "./developer.svelte";
@ -6,7 +7,7 @@
const dev = getDeveloperToggle(); const dev = getDeveloperToggle();
</script> </script>
<SidebarHeader>About</SidebarHeader> <SidebarHeader>{m["sidebar.about.header"]()}</SidebarHeader>
<!-- svelte-ignore a11y_no_noninteractive_element_interactions --> <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events --> <!-- svelte-ignore a11y_click_events_have_key_events -->
@ -21,9 +22,9 @@
> >
TrafficCue TrafficCue
</h1> </h1>
<span>Powered by:</span> <span>{m["sidebar.about.powered-by"]()}:</span>
<ul> <ul>
<li>© OpenStreetMap contributors</li> <li>© OpenStreetMap {m["sidebar.about.contributors"]()}</li>
<li>Natural Earth</li> <li>Natural Earth</li>
<li>MapLibre</li> <li>MapLibre</li>
<li>OpenMapTiles</li> <li>OpenMapTiles</li>

View File

@ -6,11 +6,12 @@
import { downloadPMTiles } from "$lib/services/OfflineTiles"; import { downloadPMTiles } from "$lib/services/OfflineTiles";
import { getDeveloperToggle } from "./developer.svelte"; import { getDeveloperToggle } from "./developer.svelte";
import { view } from "../../view.svelte"; import { view } from "../../view.svelte";
import { m } from "$lang/messages";
const dev = getDeveloperToggle(); const dev = getDeveloperToggle();
</script> </script>
<SidebarHeader>Developer Settings</SidebarHeader> <SidebarHeader>{m["sidebar.developer.header"]()}</SidebarHeader>
<div id="sections"> <div id="sections">
<section> <section>

View File

@ -3,22 +3,23 @@
import { DownloadCloudIcon } from "@lucide/svelte"; import { DownloadCloudIcon } from "@lucide/svelte";
import SettingsButton from "./SettingsButton.svelte"; import SettingsButton from "./SettingsButton.svelte";
import SidebarHeader from "../SidebarHeader.svelte"; import SidebarHeader from "../SidebarHeader.svelte";
import { m } from "$lang/messages";
let progresses: Record<string, number> = $state({}); let progresses: Record<string, number> = $state({});
</script> </script>
<SidebarHeader>Offline Maps</SidebarHeader> <SidebarHeader>{m["sidebar.offline-maps.header"]()}</SidebarHeader>
{#await getRemoteList()} {#await getRemoteList()}
<p>Loading...</p> <p>{m.loading()}</p>
{:then list} {:then list}
<div style="display: flex; flex-direction: column; gap: 0.5rem;"> <div style="display: flex; flex-direction: column; gap: 0.5rem;">
{#if list.length === 0} {#if list.length === 0}
<p>No offline maps available.</p> <p>{m["sidebar.offline-maps.not-available"]()}</p>
{/if} {/if}
{#if !window.__TAURI__} {#if !window.__TAURI__}
<p>Offline maps are only available on mobile.</p> <p>{m["sidebar.offline-maps.only-mobile"]()}</p>
{/if} {/if}
{#each list as item, _index (item.file)} {#each list as item, _index (item.file)}
@ -35,7 +36,7 @@
progresses[item.file] = (progress / total) * 100; progresses[item.file] = (progress / total) * 100;
}, },
); );
alert(`Downloaded ${item.name}`); alert(m["sidebar.offline-maps.downloaded"]({ name: item.name }));
location.reload(); location.reload();
}} }}
/> />

View File

@ -9,34 +9,35 @@
import SidebarHeader from "../SidebarHeader.svelte"; import SidebarHeader from "../SidebarHeader.svelte";
import SettingsButton from "./SettingsButton.svelte"; import SettingsButton from "./SettingsButton.svelte";
import { getDeveloperToggle } from "./developer.svelte"; import { getDeveloperToggle } from "./developer.svelte";
import { m } from "$lang/messages";
const dev = getDeveloperToggle(); const dev = getDeveloperToggle();
</script> </script>
<SidebarHeader>Settings</SidebarHeader> <SidebarHeader>{m["sidebar.settings.header"]()}</SidebarHeader>
<div id="sections"> <div id="sections">
<section> <section>
<h2>General</h2> <h2>{m["sidebar.settings.general"]()}</h2>
<SettingsButton icon={LanguagesIcon} text="Language" disabled /> <SettingsButton icon={LanguagesIcon} text={m["sidebar.language.header"]()} disabled />
</section> </section>
<section> <section>
<h2>Map</h2> <h2>{m["sidebar.settings.map"]()}</h2>
<SettingsButton icon={MapIcon} text="Offline Maps" view="offline-maps" /> <SettingsButton icon={MapIcon} text={m["sidebar.offline-maps.header"]()} view="offline-maps" />
<SettingsButton icon={PaintbrushIcon} text="Map Style" disabled /> <SettingsButton icon={PaintbrushIcon} text={m["sidebar.map-style.header"]()} disabled />
</section> </section>
<section> <section>
<h2>About</h2> <h2>{m["sidebar.about.header"]()}</h2>
{#if dev.current == "true"} {#if dev.current == "true"}
<SettingsButton <SettingsButton
icon={CodeIcon} icon={CodeIcon}
text="Developer Settings" text={m["sidebar.developer.header"]()}
view="dev-options" view="dev-options"
/> />
{/if} {/if}
<SettingsButton icon={InfoIcon} text="About" view="about" /> <SettingsButton icon={InfoIcon} text={m["sidebar.about.header"]()} view="about" />
</section> </section>
</div> </div>

View File

@ -1,3 +1,5 @@
import { m } from "$lang/messages";
/* /*
Valhalla costing: Valhalla costing:
auto, prioritizes motorways = car, (truck), motorcycle auto, prioritizes motorways = car, (truck), motorcycle
@ -57,7 +59,7 @@ export interface Vehicle {
} }
export const DefaultVehicle: Vehicle = { export const DefaultVehicle: Vehicle = {
name: "Default Vehicle", name: m["vehicles.default"](),
legalMaxSpeed: 45, legalMaxSpeed: 45,
actualMaxSpeed: 45, actualMaxSpeed: 45,
type: "motor_scooter", type: "motor_scooter",