feat: translate the page

This commit is contained in:
2023-11-29 15:59:16 +01:00
parent 5d1a7e98c1
commit 7cdab9c68b
19 changed files with 542 additions and 88 deletions

View File

@ -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>

View 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 />

View File

@ -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}

View File

@ -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 {

View 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
}

View File

@ -1,5 +1,8 @@
<script lang="ts">
import ParaglideAdapter from '$lib/components/ParaglideAdapter.svelte';
import './global.css';
</script>
<slot />
<ParaglideAdapter>
<slot />
</ParaglideAdapter>

View File

@ -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>

View File

@ -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
View File

@ -0,0 +1,3 @@
export const load: Handler = async (req, res) => {
};