feat: i18n

This commit is contained in:
2026-01-20 20:56:18 +01:00
parent 0f13d67f10
commit a31f28c834
17 changed files with 1390 additions and 55 deletions
+10 -10
View File
@@ -1,12 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
<html lang="%paraglide.lang%" dir="%paraglide.textDirection%">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
+3
View File
@@ -0,0 +1,3 @@
import { i18n } from "$lib/i18n"
export const handle = i18n.handle()
+11
View File
@@ -0,0 +1,11 @@
import { createI18n } from "@inlang/paraglide-sveltekit"
import * as runtime from "$lib/paraglide/runtime"
export const i18n = createI18n(runtime, {
pathnames: {
"/about": {
en: "/about",
de: "/ueber-uns"
}
}
})
+28 -3
View File
@@ -54,8 +54,8 @@ export async function getCoverImage(slug: string): Promise<string | null> {
});
}
export async function getPages() {
return withCache("pages", async () => {
export async function getPages(lang: string = 'de') {
return withCache(`pages:${lang}`, async () => {
try {
const items = await client.getDirectoryContents("/");
@@ -71,7 +71,32 @@ export async function getPages() {
const pages = await Promise.all(
directories.map(async (item: any) => {
const slug = item.basename;
const title = slug.charAt(0).toUpperCase() + slug.slice(1);
const baseName = slug.charAt(0).toUpperCase() + slug.slice(1);
const filename = lang === 'de' ? `${baseName}.md` : `${baseName}.${lang}.md`;
const path = `/${slug}/${filename}`;
let title = baseName;
try {
const content = await getPageContent(path);
const headerMatch = content.match(/^#{1,6}\s+(.+)$/m);
if (headerMatch) {
title = headerMatch[1];
}
} catch (e) {
// Fallback to German if English is missing
if (lang !== 'de') {
try {
const fallbackContent = await getPageContent(`/${slug}/${baseName}.md`);
const headerMatch = fallbackContent.match(/^#{1,6}\s+(.+)$/m);
if (headerMatch) {
title = headerMatch[1];
}
} catch (innerE) {
console.warn(`Could not fetch fallback title for ${slug}`);
}
}
}
const coverImage = await getCoverImage(slug);
return {
+5
View File
@@ -0,0 +1,5 @@
import type { ParamMatcher } from '@sveltejs/kit';
export const match: ParamMatcher = (param) => {
return ['de', 'en'].includes(param);
};
+2 -2
View File
@@ -1,8 +1,8 @@
import type { LayoutServerLoad } from './$types';
import { getPages } from '$lib/server/nextcloud';
export const load: LayoutServerLoad = async () => {
const pages = await getPages();
export const load: LayoutServerLoad = async ({ params }) => {
const pages = await getPages(params.lang);
return {
pages
};
+27 -4
View File
@@ -1,11 +1,34 @@
<script lang="ts">
import "../app.css";
import type { Snippet } from 'svelte';
import { ParaglideJS } from '@inlang/paraglide-sveltekit';
import { i18n } from '$lib/i18n';
import { languageTag } from '$lib/paraglide/runtime';
let { children }: { children?: Snippet } = $props();
</script>
<header class="fixed z-50 mix-blend-difference p-1">
<a href="/" class="text-white text-2xl">schreifuchs.ch</a>
</header>
{@render children?.()}
<ParaglideJS {i18n}>
<header class="fixed z-50 mix-blend-difference p-1 w-full flex justify-between items-start">
<a href="/" class="text-white text-2xl">schreifuchs.ch</a>
<nav class="flex gap-2 p-2">
<a
href="/"
hreflang="de"
data-no-translate
class="text-white hover:underline {languageTag() === 'de' ? 'font-bold' : ''}"
>
DE
</a>
<a
href="/en"
hreflang="en"
data-no-translate
class="text-white hover:underline {languageTag() === 'en' ? 'font-bold' : ''}"
>
EN
</a>
</nav>
</header>
{@render children?.()}
</ParaglideJS>
@@ -1,6 +1,8 @@
<script lang="ts">
import ImageLinkTile from "$lib/components/ImageLinkTile.svelte";
import type { PageData } from "./$types";
import { i18n } from "$lib/i18n";
import * as m from "$lib/paraglide/messages";
// Import local images to preserve existing design
import mouse from "$lib/images/home/mouse.webp";
@@ -8,7 +10,6 @@
import scouts from "$lib/images/home/scouts.webp";
import console_image from "$lib/images/home/console.webp";
import lauch from "$lib/images/home/lauch.webp";
import monitor from "$lib/images/informatik/monitor.webp"; // Using monitor for Informatik if console is not preferred
let { data }: { data: PageData } = $props();
@@ -42,7 +43,7 @@
<!-- Static/External Links -->
<ImageLinkTile src={scouts} href="https://pfadifrisco.ch/" target="_blank">
<h2 class="text-3xl font-bold text-white hover:underline text-shadow-lg">
Pfadi
{m.pfadi()}
</h2>
</ImageLinkTile>
</main>
</main>
@@ -0,0 +1,49 @@
import { getCoverImage, getPageContent } from "$lib/server/nextcloud";
import type { PageServerLoad } from "./$types";
import { error } from "@sveltejs/kit";
export const load: PageServerLoad = async ({ params }) => {
const { slug, lang } = params;
const language = lang || 'de';
// Capitalize the first letter to match the convention: fotos -> Fotos.md
const baseName = slug.charAt(0).toUpperCase() + slug.slice(1);
const filename = language === 'de' ? `${baseName}.md` : `${baseName}.${language}.md`;
const path = `/${slug}/${filename}`;
try {
let rawContent;
try {
rawContent = await getPageContent(path);
} catch (e) {
if (language !== 'de') {
console.warn(`Localized content ${filename} not found, falling back to German.`);
rawContent = await getPageContent(`/${slug}/${baseName}.md`);
} else {
throw e;
}
}
// Extract the first header (e.g., # Title or ## Title)
const headerMatch = rawContent.match(/^#{1,6}\s+(.+)$/m);
const title = headerMatch ? headerMatch[1] : baseName;
// Remove the first header from content so it's not rendered twice
const content = headerMatch
? rawContent.replace(headerMatch[0], "").trim()
: rawContent;
const coverImage = await getCoverImage(slug);
return {
content,
title,
slug,
coverImage,
lang: language
};
} catch (e) {
console.error(`Error fetching ${filename} from Nextcloud`, e);
throw error(404, "Page not found");
}
};
-27
View File
@@ -1,27 +0,0 @@
import { getCoverImage, getPageContent } from "$lib/server/nextcloud";
import type { PageServerLoad } from "./$types";
import { error } from "@sveltejs/kit";
export const load: PageServerLoad = async ({ params }) => {
const { slug } = params;
// Capitalize the first letter to match the convention: fotos -> Fotos.md
const filename = slug.charAt(0).toUpperCase() + slug.slice(1) + ".md";
const path = `/${slug}/${filename}`;
try {
const [content, coverImage] = await Promise.all([
getPageContent(path),
getCoverImage(slug)
]);
return {
content,
title: slug.charAt(0).toUpperCase() + slug.slice(1),
slug,
coverImage
};
} catch (e) {
console.error(`Error fetching ${filename} from Nextcloud`, e);
throw error(404, "Page not found");
}
};