feat: translate the page
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import * as m from '$paraglide/messages';
|
||||
export let min = 0;
|
||||
export let max = 100;
|
||||
export let value = 50;
|
||||
@ -6,8 +7,8 @@
|
||||
|
||||
<div class="wrapper">
|
||||
<input type="range" {min} {max} bind:value step={10} />
|
||||
<p class="left">gar nicht</p>
|
||||
<p class="right">sehr</p>
|
||||
<p class="left">{m.notAtAll()}</p>
|
||||
<p class="right">{m.very()}</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
81
src/lib/components/ParaglideAdapter.svelte
Normal file
81
src/lib/components/ParaglideAdapter.svelte
Normal file
@ -0,0 +1,81 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
availableLanguageTags,
|
||||
setLanguageTag,
|
||||
onSetLanguageTag,
|
||||
sourceLanguageTag,
|
||||
languageTag
|
||||
} from '$paraglide/runtime';
|
||||
import { getContext, setContext } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
// We check if the language tag is valid, if it is, we set it,
|
||||
// if not, we redirect to the same page without the language tag
|
||||
setContext(
|
||||
'languageTag',
|
||||
$page.params.lang
|
||||
? (availableLanguageTags as readonly string[]).includes($page.params.lang)
|
||||
? $page.params.lang
|
||||
: (() => {
|
||||
throw redirect(
|
||||
302,
|
||||
('/' + $page.url.href.split($page.params.lang)[1]).replace('//', '/')
|
||||
);
|
||||
})()
|
||||
: sourceLanguageTag
|
||||
);
|
||||
|
||||
setLanguageTag(() => getContext('languageTag'));
|
||||
|
||||
// We save the old language tag to check if the language tag has changed
|
||||
let oldLanguageTag = languageTag();
|
||||
|
||||
if (import.meta.env.SSR === false) {
|
||||
onSetLanguageTag((newLanguageTag) => {
|
||||
// If the language tag is the same as the current language tag, we don't want to do anything
|
||||
if (newLanguageTag === oldLanguageTag) return;
|
||||
|
||||
// If we set the language tag to the source language tag, we want to remove the language tag from the url
|
||||
if (newLanguageTag === sourceLanguageTag) {
|
||||
// this returns the route without the language tag
|
||||
const route = window.location.href.match(/^https?:\/\/[^\/]+\/[^\/]*(\/.*)/);
|
||||
|
||||
// Redirect to the same page with the new language
|
||||
window.location.href = route ? route[1] : '/';
|
||||
|
||||
// renew the old language tag
|
||||
oldLanguageTag = newLanguageTag;
|
||||
|
||||
// if we set the language tag from the source language tag, we want to add the language tag to the url
|
||||
} else if (oldLanguageTag === sourceLanguageTag) {
|
||||
// this simply returns the route, since we don't have to remove the language tag,
|
||||
// beacuse the preveous language was the source language.
|
||||
// It also does not matter if for some reason "en" language tag is still given, it removes it anyway.
|
||||
const route = window.location.href
|
||||
.replace(/^https?:\/\/[^\/]+\/(en)(\/|$)/, '')
|
||||
.match(/^https?:\/\/[^\/]+(\/.*)/);
|
||||
|
||||
// Redirect to the same page with the new language
|
||||
window.location.href = route ? '/' + newLanguageTag + route[1] : '/' + newLanguageTag;
|
||||
|
||||
// renew the old language tag
|
||||
oldLanguageTag = newLanguageTag;
|
||||
|
||||
// if we change the language tag not from and not to the source language, we want to keep the url
|
||||
} else {
|
||||
// this returns the route without the language tag
|
||||
const route = window.location.href.match(/^https?:\/\/[^\/]+\/[^\/]*(\/.*)/);
|
||||
|
||||
// Redirect to the same page with the new language
|
||||
window.location.href =
|
||||
route && route[1] ? '/' + newLanguageTag + route[1] : '/' + newLanguageTag;
|
||||
|
||||
// renew the old language tag
|
||||
oldLanguageTag = newLanguageTag;
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<slot />
|
@ -7,6 +7,7 @@
|
||||
import InputRange from './InputRange.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import Loader from './Loader.svelte';
|
||||
import * as m from '$paraglide/messages';
|
||||
|
||||
let data = persisted<{
|
||||
name: string;
|
||||
@ -104,32 +105,29 @@
|
||||
|
||||
<div class="wrapper">
|
||||
<section in:slide={{ delay: 500 }}>
|
||||
<TextSplit content="Wie lauten Euer Vor- und Nachname, edler Gast?" />
|
||||
<TextSplit content={m.howName()} />
|
||||
<input placeholder="Name" type="text" bind:value={$data.name} />
|
||||
|
||||
{#if $data.name.length > 99}
|
||||
<p class="error">
|
||||
Wir bitten um Entschuldigung, aber dieser Name ist zu lang. (maximal 100 Zeichen)
|
||||
{m.nameTooLong()}
|
||||
</p>
|
||||
{:else if $data.showNameLengthError}
|
||||
<p class="hint">{$data.name.length}/100 Zeichen</p>
|
||||
<p class="hint">{$data.name.length}{m.hundredChars()}</p>
|
||||
{/if}
|
||||
{#if !$data.nameAccepted}
|
||||
<button
|
||||
disabled={$data.name.length > 99}
|
||||
on:click={() => {
|
||||
$data.nameAccepted = true;
|
||||
}}>Name speichern</button
|
||||
}}>{m.saveName()}</button
|
||||
>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
{#if ($data.name && $data.nameAccepted) || $data.confidence !== undefined}
|
||||
<section in:slide={{ delay: 0, duration: 500 }}>
|
||||
<TextSplit
|
||||
content="Wie sicher dürfen wir mit Eurem glanzvollen Erscheinen rechnen?"
|
||||
delay={0}
|
||||
/>
|
||||
<TextSplit content={m.howSureAppearing()} delay={0} />
|
||||
<div in:fade={{ delay: 1200 }}>
|
||||
<InputRange bind:value={$data.confidence} />
|
||||
</div>
|
||||
@ -145,19 +143,17 @@
|
||||
|
||||
{#if $data.confidence !== undefined && $data.confidenceAccepted && $data.createPersonality === undefined}
|
||||
<section in:slide out:slide>
|
||||
<TextSplit
|
||||
content="Möchtet Ihr, dass wir eine alternative Persönlichkeit für Euch erschaffen?"
|
||||
/>
|
||||
<TextSplit content={m.wantAlternativePersonality()} />
|
||||
<div>
|
||||
<button
|
||||
on:click={() => {
|
||||
$data.createPersonality = true;
|
||||
}}>Ja</button
|
||||
}}>{m.yes()}</button
|
||||
>
|
||||
<button
|
||||
on:click={() => {
|
||||
$data.createPersonality = false;
|
||||
}}>Nein</button
|
||||
}}>{m.no()}</button
|
||||
>
|
||||
</div>
|
||||
{#if $data.adelsTitel}
|
||||
@ -169,7 +165,7 @@
|
||||
{#if $data.createPersonality === true}
|
||||
<section in:fade out:slide>
|
||||
{#if $data.provideAdelsTitel === undefined}
|
||||
<TextSplit content="Möchtet Ihr einen würdigen Adelsnamen für Euch in Erwägung ziehen?" />
|
||||
<TextSplit content={m.wantNobleName()} />
|
||||
<div>
|
||||
<button
|
||||
on:click={() => {
|
||||
@ -177,7 +173,7 @@
|
||||
$data.provideAdelsTitel = true;
|
||||
}}
|
||||
>
|
||||
ja</button
|
||||
{m.yes()}</button
|
||||
>
|
||||
<button
|
||||
on:click={() => {
|
||||
@ -185,7 +181,7 @@
|
||||
$data.provideAdelsTitel = true;
|
||||
}}
|
||||
>
|
||||
ich habe bereits einen
|
||||
{m.alreadyHaveOne()}
|
||||
</button>
|
||||
<button
|
||||
on:click={() => {
|
||||
@ -193,7 +189,7 @@
|
||||
$data.provideAdelsTitel = false;
|
||||
}}
|
||||
>
|
||||
nein</button
|
||||
{m.no()}</button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
@ -201,34 +197,34 @@
|
||||
{#if loadingAdelsTitel}
|
||||
<div style="display: flex; gap: 20px; align-items: center;">
|
||||
<Loader />
|
||||
<p>Adelsnamen werden geschrieben</p>
|
||||
<p>{m.nobleTitlesAreWritten()}</p>
|
||||
</div>
|
||||
{:else if typeof $data.adelsTitel === 'string'}
|
||||
<TextSplit content="Euer Adelsname" />
|
||||
<input placeholder="Name" type="text" bind:value={$data.adelsTitel} />
|
||||
<TextSplit content={m.yourNobleName()} />
|
||||
<input placeholder={m.nobleName()} type="text" bind:value={$data.adelsTitel} />
|
||||
{#if $data.adelsTitel.length > 98}
|
||||
<p class="error">
|
||||
Wir bitten um Entschuldigung, aber dieser Titel ist zu lang. (maximal 100 Zeichen)
|
||||
{m.titleTooLong()}
|
||||
</p>
|
||||
{:else if $data.showAdelsTitelLengthError}
|
||||
<p class="hint">{$data.adelsTitel.length}/100 Zeichen</p>
|
||||
<p class="hint">{$data.adelsTitel.length}{m.hundredChars()}</p>
|
||||
{/if}
|
||||
{#if !$data.adelsTitelAccepted}
|
||||
<button
|
||||
disabled={$data.adelsTitel.length > 99}
|
||||
on:click={() => {
|
||||
$data.adelsTitelAccepted = true;
|
||||
}}>akzeptieren</button
|
||||
}}>{m.accept()}</button
|
||||
>
|
||||
<button
|
||||
on:click={() => {
|
||||
$data.adelsTitel = undefined;
|
||||
fetchAdelsTitel();
|
||||
}}>neue vorschläge</button
|
||||
}}>{m.newSuggestions()}</button
|
||||
>
|
||||
{/if}
|
||||
{:else if $data.adelsTitelSuggestions?.length}
|
||||
<p>Adelsname Vorschläge</p>
|
||||
<p>{m.titleSuggestions()}</p>
|
||||
<hr />
|
||||
{#each $data.adelsTitelSuggestions as suggestion}
|
||||
<button
|
||||
@ -241,7 +237,7 @@
|
||||
<button
|
||||
on:click={() => {
|
||||
fetchAdelsTitel();
|
||||
}}>neue vorschläge</button
|
||||
}}>{m.newSuggestions()}</button
|
||||
>
|
||||
{/if}
|
||||
</section>
|
||||
@ -250,56 +246,55 @@
|
||||
{#if $data.adelsTitel && $data.adelsTitelAccepted && $data.providePortrait !== false}
|
||||
<section transition:slide class="portrait-frame" class:loaded={!!$data.portraitUrl}>
|
||||
{#if $data.providePortrait === undefined}
|
||||
<TextSplit
|
||||
content="Sollten unsere begabten Künstler ein majestätisches Porträt von Euch anfertigen?"
|
||||
/>
|
||||
<TextSplit content={m.shouldCreatePortrait()} />
|
||||
<div>
|
||||
<button
|
||||
on:click={() => {
|
||||
$data.providePortrait = true;
|
||||
}}>ja</button
|
||||
}}>{m.yes()}</button
|
||||
>
|
||||
<button
|
||||
on:click={() => {
|
||||
$data.providePortrait = true;
|
||||
}}>false</button
|
||||
}}>{m.no()}</button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $data.providePortrait && !loadingPortrait && !$data.portraitUrl}
|
||||
<p>
|
||||
Wir werden {$data.adelsTitel || $data.name} mit
|
||||
{m.createNameWith({ name: $data.adelsTitel || $data.name })}
|
||||
<select placeholder="Typ" bind:value={$data.portraitSkinColor}>
|
||||
<option value="very light">sehr heller</option>
|
||||
<option value="light">heller</option>
|
||||
<option value="medium">medium</option>
|
||||
<option value="dark">dunkler</option>
|
||||
<option value="very dark">sehr dunkler</option>
|
||||
<option value="very light">{m.skinVeryBright()}</option>
|
||||
<option value="light">{m.skinBright()}</option>
|
||||
<option value="medium">{m.skinMedium()}</option>
|
||||
<option value="dark">{m.skinDark()}</option>
|
||||
<option value="very dark">{m.skinVeryDark()}</option>
|
||||
</select>
|
||||
Haut und
|
||||
{m.skinAnd()}
|
||||
<select placeholder="Typ" bind:value={$data.portraitHairType}>
|
||||
<option value="straight">glatten</option>
|
||||
<option value="curly">lockigen</option>
|
||||
<option value="wavy">welligen</option>
|
||||
<option value="straight">{m.hairTypeStraight()}</option>
|
||||
<option value="curly">{m.hairTypeCurly()}</option>
|
||||
<option value="wavy">{m.hairTypeWavy()}</option>
|
||||
</select>
|
||||
<select placeholder="Länge" bind:value={$data.portraitHairLength}>
|
||||
<option value="long">langen</option>
|
||||
<option value="medium">medium-langen</option>
|
||||
<option value="short">kurzen</option>
|
||||
<option value="long">{m.hairLengthLong()}</option>
|
||||
<option value="medium">{m.hairLengthMedium()}</option>
|
||||
<option value="short">{m.hairLengthShort()}</option>
|
||||
</select>,
|
||||
<select placeholder="Farbe" bind:value={$data.portraitHairColor}>
|
||||
<option value="red">roten</option>
|
||||
<option value="black">schwarzen</option>
|
||||
<option value="blond">blonden</option>
|
||||
<option value="brown">braunen</option>
|
||||
</select> Haaren zeichnen
|
||||
<option value="red">{m.hairColorRed()}</option>
|
||||
<option value="black">{m.hairColorBlack()}</option>
|
||||
<option value="blond">{m.hairColorBlond()}</option>
|
||||
<option value="brown">{m.hairColorBrown()}</option>
|
||||
</select>
|
||||
{m.hairDrawing()}
|
||||
</p>
|
||||
<button on:click={() => fetchPortrait()}> porträt malen (~15 Sekunden)</button>
|
||||
<button on:click={() => fetchPortrait()}> {m.paintPortrait()}</button>
|
||||
{:else if loadingPortrait}
|
||||
<div style="display: flex; gap: 20px; align-items: center;">
|
||||
<Loader />
|
||||
<p>Euer edles Antlitz wird gemalt</p>
|
||||
<p>{m.portraitIsBeingDrawn()}</p>
|
||||
</div>
|
||||
{:else if $data.portraitUrl}
|
||||
<div in:slide={{ duration: 2000 }} class="portrait">
|
||||
@ -309,12 +304,12 @@
|
||||
<button
|
||||
on:click={() => {
|
||||
$data.portraitAccepted = true;
|
||||
}}>Porträt annehmen</button
|
||||
}}>{m.acceptPortrait()}</button
|
||||
>
|
||||
<button
|
||||
on:click={() => {
|
||||
$data.portraitUrl = undefined;
|
||||
}}>Neues Porträt anfordern</button
|
||||
}}>{m.requestNewPortrait()}</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
@ -327,12 +322,12 @@
|
||||
<section in:slide style="border: none;">
|
||||
<div style="display: flex; gap: 20px; align-items: center;">
|
||||
<Loader />
|
||||
<p>Einladung wird abgeschickt</p>
|
||||
<p>{m.invitationIsBeingSend()}</p>
|
||||
</div>
|
||||
</section>
|
||||
{:else}
|
||||
<div class="button-wrapper">
|
||||
<Button on:click={() => submit()}>Einladung versenden</Button>
|
||||
<Button on:click={() => submit()}>{m.sendInvitation()}</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import * as m from '$paraglide/messages';
|
||||
</script>
|
||||
|
||||
<p>Willkommen zum Maskenball</p>
|
||||
<p>{m.welcome()}</p>
|
||||
|
||||
<style>
|
||||
p {
|
||||
|
27
src/routes/+layout.server.ts
Normal file
27
src/routes/+layout.server.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import type { LayoutServerLoad } from "./$types";
|
||||
import { availableLanguageTags } from "$paraglide/runtime";
|
||||
import { redirect } from "@sveltejs/kit";
|
||||
|
||||
type AvailableLanguages = typeof availableLanguageTags[number];
|
||||
|
||||
export const load: LayoutServerLoad = function ({ request, cookies }) {
|
||||
|
||||
const url = new URL(request.url);
|
||||
let language = cookies.get("lang") as unknown as AvailableLanguages;
|
||||
const acceptLanguage = request.headers.get('accept-language')?.split(",")?.[0]?.split("-")?.[0] as AvailableLanguages;
|
||||
|
||||
if (!language || !availableLanguageTags.includes(language)) {
|
||||
if (availableLanguageTags.includes(acceptLanguage)) {
|
||||
language = acceptLanguage;
|
||||
cookies.set("lang", language);
|
||||
} else {
|
||||
language = "de"
|
||||
}
|
||||
}
|
||||
|
||||
if (!url.pathname.startsWith("/" + language) && language !== "de") {
|
||||
throw redirect(302, `/${language}/${url.pathname}`.replaceAll("//", "/"))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
<script lang="ts">
|
||||
import ParaglideAdapter from '$lib/components/ParaglideAdapter.svelte';
|
||||
import './global.css';
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
<ParaglideAdapter>
|
||||
<slot />
|
||||
</ParaglideAdapter>
|
||||
|
@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import Loader from '$lib/components/Loader.svelte';
|
||||
import Questions from '$lib/components/Questions.svelte';
|
||||
import TextSplit from '$lib/components/TextSplit.svelte';
|
||||
import Button from '$lib/components/button.svelte';
|
||||
@ -9,6 +8,7 @@
|
||||
import Maskenball from '$lib/components/maskenball.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import * as m from '$paraglide/messages';
|
||||
|
||||
let curtainsVisible = false;
|
||||
let buttonVisible = false;
|
||||
@ -59,10 +59,7 @@
|
||||
|
||||
{#if contentVisible}
|
||||
<div class="einladung" out:fade>
|
||||
<TextSplit
|
||||
center
|
||||
content="Wir laden dich herzlich ein, an unserer exklusiven Silvesterparty teilzunehmen, die dieses Jahr im magischen Ambiente eines Maskenballs stattfindet. Tauche ein in eine Nacht voller Geheimnisse, Eleganz und festlichem Glanz."
|
||||
/>
|
||||
<TextSplit center content={m.invite()} />
|
||||
|
||||
<span in:fade={{ delay: 8000, duration: 1000 }}>
|
||||
<Button
|
||||
@ -70,7 +67,7 @@
|
||||
on:click={() => {
|
||||
contentVisible = false;
|
||||
questionVisible = true;
|
||||
}}>Einladung annehmen</Button
|
||||
}}>{m.acceptInvite()}</Button
|
||||
>
|
||||
</span>
|
||||
</div>
|
@ -2,6 +2,7 @@
|
||||
import ImageFrame from '$lib/components/ImageFrame.svelte';
|
||||
import Confetti from '$lib/components/confetti.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import * as m from '$paraglide/messages';
|
||||
|
||||
export let data;
|
||||
|
||||
@ -29,7 +30,7 @@
|
||||
</svelte:head>
|
||||
|
||||
<div class="wrapper">
|
||||
<h1>Gallerie der Gäste</h1>
|
||||
<h1>{m.guestGallery()}</h1>
|
||||
<div class="grid">
|
||||
{#each items as item}
|
||||
<div
|
3
src/routes/load.ts
Normal file
3
src/routes/load.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const load: Handler = async (req, res) => {
|
||||
};
|
||||
|
Reference in New Issue
Block a user