Communitygithub.com

agentmemoir/skills

Expertenanleitung für die Entwicklung typsicherer, performanter i18n-Apps mit Paraglide JS 2.x (compilerbasiert, tree-shakable) und Svelte 5 (Runes). Deckt Setup, Nachrichtenerstellung, Lokalisierungsstrategien, i18n-Routing, SSR/SSG, Rich-Text-Markup mit Svelte-5-Snippets, Reaktivitätsmuster, Tests und Fehlerbehebung ab.

Was ist skills?

Agentmemoir/skills bietet eine umfassende Anleitung zur Integration von Paraglide JS 2.x mit Svelte 5. Paraglide JS ist SvelteKits offizielle i18n-Integration: Es kompiliert `messages/{locale}.json` in tree-shakable, vollständig typisierte ESM-Funktionen. Dies führt zu bis zu ~70% kleineren i18n-Bundles im Vergleich zu Laufzeitbibliotheken und eliminiert asynchrone Waterfalls sowie die Notwendigkeit eines i18n-Providers in Svelte-5-Komponenten. Die Anleitung betont ein zentrales mentales Modell: Die Locale wird als Request-/Build-Status behandelt, der durch einen Compiler aufgelöst wird, nicht als reaktiver App-Status. Paraglide übernimmt Locale-Wechsel über die vollständige Dokumentnavigation, während Svelte-5-Runes den gesamten restlichen UI-Status verwalten. Sie behandelt Setup, Nachrichtenverwendung, Locale-Strategien, i18n-Routing, SSR/SSG, Rich-Text-Markup mit `<ParaglideMessage>` und Svelte-5-Snippets, Reaktivitätsmuster, Tests und Fehlerbehebung. Die Skills sind plattformunabhängig definiert, aber speziell auf SvelteKit, Cursor, Claude Code und Codex zugeschnitten.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/agentmemoir/skills/tree/HEAD/skills/frontend/paraglide-svelte5-best-practices

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Paraglide JS + Svelte 5 Best Practices

Behandle die Locale als Request-/Build-Status, der durch einen Compiler aufgelöst wird, nicht als reaktiven App-Status – überlasse Paraglide die vollständige Dokumentnavigation für Locale-Wechsel und Svelte 5 Runes alles andere. Paraglide JS ist SvelteKits offizielle i18n-Integration: Es kompiliert messages/{locale}.json in tree-shakable, vollständig typisierte ESM-Funktionen und ermöglicht so bis zu ~70 % kleinere i18n-Bundles als Laufzeitbibliotheken (≈47 KB vs. ≈205 KB im veröffentlichten Benchmark mit 5 Locales / 200 Nachrichten), ohne asynchrone Waterfalls und ohne dass ein i18n-Provider in Svelte-5-Komponenten eingebunden werden muss.


1. Entscheidungstabelle für den Schnellzugriff

EntscheidungEmpfohlene StandardeinstellungWarum
Installationspfadnpx sv add paraglide="languageTags:en,de" in einer neuen SvelteKit-AppRichtet Vite-Plugin, Hooks, app.html-Attribute und .gitignore in einem Schritt ein
Paket@inlang/paraglide-js v2.xDer v1-Adapter @inlang/paraglide-js-adapter-sveltekit ist veraltet – verwende stattdessen das Kernpaket direkt
Strategie (SSR + lokalisierte URLs)["url", "cookie", "baseLocale"]URL ist die SEO-Quelle der Wahrheit; Cookie speichert die Benutzerauswahl sichtbar für den Server; baseLocale wird immer zuletzt aufgelöst
Strategie (gespeicherte Überschreibung hinzufügen)["localStorage", "cookie", "preferredLanguage", "url", "baseLocale"]localStorage allein ist für die erste SSR-Anfrage unsichtbar; Cookie überbrückt Server/Client
Locale-Wechsel-VerhaltenStandardmäßig setLocale(locale) (vollständige Dokumentnavigation)Framework-Reaktivität rendert nicht <html lang>, dir, <title> oder lokalisierte URLs neu; Navigation schon
AsyncLocalStorageAktiviert lassen (Standard)Vercel Edge und Cloudflare Workers (Node-Kompatibilität) unterstützen es; Deaktivieren riskiert, dass die Locale zwischen gleichzeitigen Requests ausläuft
TS-DeklarationenemitTsDeclarations: true (TypeScript ≥ 5.6)Hält Svelte- und TS-Sprachserver synchron, wenn sich Schlüssel während der Entwicklung ändern
NachrichtenschlüsselFlache, stabile, lesbare Zufallsschlüssel (calm_green_otter)Offizielle Empfehlung; verschachtelte Schlüssel funktionieren, aber flache Schlüssel werden bevorzugt
Vorabrendernexport const prerender = true + entries() pro LocaleSSG benötigt für jede Locale eine eindeutige URL-Datei
Reaktivität für LocaleStandardmäßig keine in KomponentengetLocale() ist Request-bezogen; ein neues Dokument wird pro Locale gerendert, daher müssen Runes es nicht verfolgen

2. Einrichtung & Konfiguration (Paraglide 2.x + SvelteKit + Svelte 5)

2.1 Schnellster Weg

npx sv create my-app
npx sv add paraglide="languageTags:en,de,demo:yes"

Das Svelte-CLI-Add-on konfiguriert das Inlang-Projekt, das Vite-Plugin, die SvelteKit handle/reroute-Hooks, %lang%/%dir% in app.html sowie eine optionale Demoseite.

2.2 Manuelle Einrichtung (vollständiger Boilerplate)

Installiere und richte das Projekt ein, verdrahte dann vier Dateien:

vite.config.ts – Plugin mit expliziter Strategie:

import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import { paraglideVitePlugin } from '@inlang/paraglide-js';

export default defineConfig({
  plugins: [
    sveltekit(),
    paraglideVitePlugin({
      project: './project.inlang',
      outdir: './src/lib/paraglide',
      emitTsDeclarations: true,
      strategy: ['url', 'cookie', 'baseLocale'],
    }),
  ],
});

emitTsDeclarations: true generiert .d.ts-Dateien neu, während du Nachrichten bearbeitest, sodass der Svelte- und der TypeScript-Server synchron bleiben; es erfordert TypeScript 5.6+ (setze false für den schnelleren JSDoc-Inferenzpfad).

src/app.html – Ersetze die hartcodierte Sprache:

<!doctype html>
<html lang="%lang%" dir="%dir%">

src/hooks.server.ts – Server-Middleware mit Request-bezogener Locale:

import type { Handle } from '@sveltejs/kit';
import { paraglideMiddleware } from '$lib/paraglide/server';
import { getTextDirection } from '$lib/paraglide/runtime';

const paraglideHandle: Handle = ({ event, resolve }) =>
  paraglideMiddleware(event.request, ({ request: localizedRequest, locale }) => {
    event.request = localizedRequest;
    return resolve(event, {
      transformPageChunk: ({ html }) =>
        html.replace('%lang%', locale).replace('%dir%', getTextDirection(locale)),
    });
  });

export const handle: Handle = paraglideHandle;

Die Middleware erkennt die Locale (Strategien in der angegebenen Reihenfolge), leitet Dokumentanfragen um, wenn die URL nicht mit der erkannten Locale übereinstimmt, delokalisiert /de/about → /about, bevor SvelteKit es sieht, und verpackt die Anfrage in AsyncLocalStorage, sodass getLocale() die richtige Locale pro gleichzeitiger Anfrage zurückgibt.

src/hooks.ts – Routing-Grenze (muss hooks.ts sein, nicht hooks.server.ts):

import type { Reroute } from '@sveltejs/kit';
import { deLocalizeUrl } from '$lib/paraglide/runtime';

export const reroute: Reroute = (request) => deLocalizeUrl(request.url).pathname;
// project.inlang/paraglide.config.ts
import { defineConfig } from '@inlang/paraglide-js';

export default defineConfig({
  outdir: './src/lib/paraglide',
  strategy: ['url', 'cookie', 'baseLocale'],
  urlPatterns: [
    { pattern: '/', localized: [['en', '/']] },
    {
      pattern: '/:path(.*)?',
      localized: [
        ['en', '/en/:path(.*)?'],
        ['de', '/de/:path(.*)?'],
      ],
    },
  ],
});

Rangfolge: CLI-Flags / Plugin-Optionen > Konfigurationsdatei > integrierte Standardeinstellungen. Der Watch-Modus lädt die Konfiguration heiß neu.


3. Kernnutzungsmuster (Nachrichten, Locale, Routing)

3.1 Nachrichten

import { m } from '$lib/paraglide/messages';
import type { LocalizedString } from '$lib/paraglide/runtime';

m.hello_world();                       // "Hallo Welt!"
m.greeting({ name: 'Ada' });           // "Hallo Ada!" – Parameter sind typisiert
m.greeting({ name: 'Ada' }, { locale: 'de' }); // Erzwingt Locale für einen Aufruf
const title: LocalizedString = m.welcome_title(); // Markensicherer Zeichenkettentyp

Nachrichten leben in messages/{locale}.json; Locales werden in project.inlang/settings.json deklariert (baseLocale + locales). Bevorzuge flache, stabile Schlüssel; verschachtelte Schlüssel werden über m['user.profile.title']() unterstützt. Der dynamische Schlüsselzugriff verhindert Tree-Shaking – ordne stattdessen dynamische IDs expliziten Nachrichtenreferenzen zu.

3.2 Locale abrufen und setzen

import { getLocale, locales, setLocale, getTextDirection } from '$lib/paraglide/runtime';

getLocale();          // z. B. "de" – Request-bezogen auf dem Server
getTextDirection();   // "ltr" | "rtl" für die aktuelle Locale
setLocale('de');      // Vollständige Dokumentnavigation zur lokalisierten URL

setLocale() aktualisiert Ihre konfigurierten Strategien und führt dann eine vollständige Dokumentnavigation durch – zur lokalisierten URL, wenn die url-Strategie aktiv ist, andernfalls ein Neuladen. Dies ist beabsichtigt: <html lang>, dir, <title>, Metadaten und die lokalisierte URL sind Dokumentzustände, die die Framework-Reaktivität nicht aktualisieren kann.

Der Notausstieg setLocale(locale, { reload: false }) existiert nur für vollständig clientgerenderte Oberflächen (eingebettete Widgets, Erweiterungsseiten), die nicht wiederherstellbaren In-Memory-Status bewahren müssen. Verwende ihn niemals für gewöhnliche Locale-Auswahlen, auf SSR/SSG-Dokumenten oder auf Routen, deren Strategie url enthält – er lässt die alte URL und Shell an Ort und Stelle, sodass getLocale() weiterhin die alte Locale auflöst.

3.3 Lokalisierte URLs und Links

import { localizeHref, deLocalizeUrl, localizeUrl } from '$lib/paraglide/runtime';

localizeHref('/blog');                          // "/de/blog" für aktuelle Locale
localizeHref('/blog', { locale: 'de' });        // Explizit
localizeUrl('https://example.com/about', { locale: 'de' }).href; // Vollständige URL-Form

URL-Muster verwenden die standardmäßige URLPattern-Syntax; die Reihenfolge ist wichtig – setze spezifische Muster vor Platzhalter und teste sie auf urlpattern.com.

3.4 Svelte 5 Locale-Wechsler (idiomatisch)

<script lang="ts">
  import { page } from '$app/state';                       // Svelte 5: Objekt, kein Store
  import { resolve } from '$app/paths';
  import { locales, localizeHref } from '$lib/paraglide/runtime';
</script>

<nav aria-label="Sprachen">
  {#each locales as locale (locale)}
    <a
      href={resolve(localizeHref(page.url.pathname, { locale }))}
      data-sveltekit-reload
      aria-current={page.params.locale === locale ? 'page' : undefined}
    >
      {locale}
    </a>
  {/each}
</nav>

Zwei Svelte-5-Details sind hier wichtig: $app/state gibt page als reaktives Objekt frei (der $page-Store und $app/stores sind das Legacy-Muster), und data-sveltekit-reload auf sprachübergreifenden Links hält die lokalisierte URL, das servergerenderte Dokument und die Laufzeit-Locale synchron – auch erforderlich für vorgerendertes Crawling.


4. Rich-Text mit Svelte 5 Snippets (ParaglideMessage)

Markup ermöglicht es Übersetzern, die Struktur zu kontrollieren, während deine App das Rendering steuert. Tag-Namen sind app-definiert – nichts wird automatisch auf HTML abgebildet.

// messages/en.json
{
  "cta": "{#link to=|/docs|}Lies die Doku{/link}",
  "welcome": "{#b}Hallo {name}{/b}{#icon/}"
}
<script lang="ts">
  import { ParaglideMessage } from '@inlang/paraglide-js-svelte';
  import { m } from '$lib/paraglide/messages';
  const name = $props().name; // oder beliebiges $state
</script>

<ParaglideMessage message={m.welcome} inputs={{ name }}>
  {#snippet b({ children })}
    <strong>{@render children?.()}</strong>
  {/snippet}
  {#snippet icon({ attributes })}
    <Icon variant={attributes?.variant} />
  {/snippet}
</ParaglideMessage>

<ParaglideMessage> verwendet message.parts(), wenn Markup vorhanden ist, und fällt auf message() für Klartext zurück; die {#snippet tag({ children, options, attributes })}-Syntax ist genau das Svelte-5-Snippet-Muster – keine Slots, kein <slot>-Legacy. Beachte die Literalsyntax to=|/docs| (durch Pipe getrennt) und dass m.welcome() allein den markierungslosen Klartext zurückgibt.


5. Svelte 5 Runes-Integration: Was reaktiv ist (und was nicht)

Das zentrale mentale Modell: Paraglide besitzt die Locale als Dokument-/Request-Status; Svelte 5 Runes besitzen den UI-Status. Da Locale-Wechsel ein neues Dokument ausliefern, brauchst du selten Runes, um getLocale() zu verfolgen. Muster, die in der Praxis vorkommen:

Lokalisierungsabhängige abgeleitete Werte innerhalb eines einzelnen Dokuments (z. B. eine reine Client-Oberfläche mit reload: false oder ein Vorschaufenster, das mehrere Locales explizit rendert):

<script lang="ts">
  import { getLocale, locales } from '$lib/paraglide/runtime';
  import { m } from '$lib/paraglide/messages';

  let { locale = getLocale() } = $props();          // Typisierte Props
  // $derived wertet neu aus, wenn sich die `locale`-Prop ändert – nicht, wenn
  // sich Paraglides interne Locale ändert (was ohne Navigation nicht passiert):
  const greeting = $derived(m.greeting({ name: 'Ada' }, { locale }));
  const isRtl = $derived(getTextDirection(locale) === 'rtl');
</script>

Das Weitergeben der erzwungenen Locale nach unten anstelle des Lesens des globalen Status ist das Runen-freundliche Muster: Die { locale }-Optionen bei jedem Nachrichtenaufruf machen Komponenten zu reinen Funktionen von Props, was genau der Art und Weise entspricht, wie Svelte 5 den Statusfluss haben möchte. Reserviere $effect für echte Seiteneffekte (z. B. Persistieren einer Vorschau-Locale) – weise niemals einem $derived zu und schreibe kein $state von innerhalb eines $effect, um Locale zu "pushen"; übergib stattdessen Props.

Globaler Client-Status (z. B. "zuletzt angezeigte Locale" für ein In-App-Übersetzungstool):

// src/lib/locale-preview.svelte.ts – .svelte.ts-Dateien unterstützen Runes auf Modulebene
export const preview = $state({ locale: 'en' as string | undefined });

$state auf Modulebene in .svelte.ts-Dateien wird von allen Importierenden gemeinsam genutzt – in Ordnung für appweite Singletons, aber denk daran, dass dies nicht die produktive Locale ist (das ist die Strategiekette + Dokumentnavigation).


6. SSR, Hydration und SSG

6.1 SSR-Regeln

  • Eine handle-Kettenreihenfolge: Setze paraglideHandle an die erste Stelle (oder vor Auth-Handler, die einen nach Locale indizierten event.locals benötigen), damit nachgelagerte Hooks die delokalisierte Anfrage sehen.
  • Hinter einem TLS-terminierenden Proxy übergib effectiveRequestUrl, sodass URL-Matching und Weiterleitungen den browserzugewandten Ursprung verwenden.
  • Lasse AsyncLocalStorage aktiviert; es macht getLocale() unter gleichzeitigen Anfragen und Streaming SSR sicher. Der Fallback disableAsyncLocalStorage ist nur für Laufzeiten ohne node:async_hooks und garantierter Request-Isolation gedacht – die Verwendung an anderer Stelle kann dazu führen, dass die Locale über verschiedene Benutzer hinweg ausläuft.

6.2 Hydrationskorrektheit

Server und Client müssen die Locale in derselben Strategiereihenfolge auflösen. Der klassische Fehler: Der Server löst aus der URL auf, aber die Client-Strategie liest zuerst localStorage, das zur SSR-Zeit nicht existiert. Behebe dies, indem du eine sichtbare cookie-Strategie für den Server einfügst, wenn eine persistierte Auswahl die erste Antwort prägen muss.

6.3 SSG (adapter-static / prerender)

Vorabrendern führt die Middleware niemals aus – setze die Locale während des Builds pro Seite:

// src/routes/[locale]/+layout.ts
import { setLocale, assertIsLocale } from '$lib/paraglide/runtime';
import type { LayoutLoad } from './$types';

export const prerender = true;
export function load({ params }: { params: { locale: string } }) {
  setLocale(assertIsLocale(params.locale));
}
// src/routes/[locale]/+page.ts
import { locales } from '$lib/paraglide/runtime';
export const prerender = true;
export function entries() {
  return locales.map((locale) => ({ locale }));
}

SSG erfordert, dass jede Locale ein URL-Präfix trägt (konfiguriere urlPatterns, sodass /en/… und /de/… beide existieren), sonst kann SvelteKit keine separaten Dateien pro Locale ausgeben. Für den SPA-Fallback des statischen Adapters setze kit.paths.relative = false, um lokalisierte Asset-404-Fehler zu vermeiden, und verwende generateStaticLocalizedUrls() für Sitemaps.


7. Leistung & Tree-Shaking

  • Rufe Nachrichtenfunktionen direkt auf. m.hello() kompiliert zu einer kleinen ESM-Funktion; dein Bundler verwirft unbenutzte Nachrichten und ihre Übersetzungen automatisch. Die Bundle-Größe bleibt flach, während der Katalog wächst (Benchmark: 47 KB, unabhängig davon, ob das Projekt 200 oder 1.000 Nachrichten enthält). Verwende niemals m['hello']() oder m[someKey]() – dynamischer Zugriff zwingt den Bundler, alle Nachrichten zu behalten, da er nicht statisch erkennen kann, welche Schlüssel verwendet werden.

  • **--

    // Fortsetzung folgt

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

agentmemoir/skills

>- Build, migrate, or review server-rendered Svelte 5 apps using runes ($state, $derived, $effect, $props, $bindable) with SvelteKit SSR (adapter-auto/node/cloudflare/vercel/netlify) or plain Vite SSR using svelte/server render() and hydrate(). Use whenever I mention Svelte 5 SSR, server-side rendering, SvelteKit load functions, +page.server.js, +layout.server.js, hooks.server.js, form actions, streaming SSR, CSP nonces, hydration mismatches, or request-scoped state. Also use to review .svelte and server files for SSR-safety bugs: effects that never run on the server, module-level state leaking between users, non-deterministic render values, and secrets reaching the client. Trigger even on a bare pasted component that "renders differently on the server and client".

agentmemoir/skills

>- Build, migrate, or review static (no server) Svelte 5 apps using runes ($state, $derived, $effect, $props, $bindable) instead of legacy Svelte 4 reactivity (export let, reactive dollar-colon statements, stores-as-default). Use whenever I mention Svelte 5, runes, a "static Svelte site", "Svelte SPA", "SvelteKit static site"/"adapter-static"/prerendered SvelteKit, deploying Svelte to GitHub Pages/Netlify/Cloudflare Pages/S3/any CDN, or converting Svelte 4 code (export let, on:click, writable stores) to Svelte 5. Also use when writing or reviewing .svelte files that must ship with no backend, to flag accidental server-only SvelteKit files (+page.server.js, +server.js, form actions) or exposed secrets, and to apply the bundled XSS/CSP/secrets security checklist. Trigger even for a bare pasted .svelte file or a vague "make this a static site" request.

Verwandte Skills