Communitygithub.com

agentmemoir/skills

Guía experta para construir aplicaciones i18n con seguridad de tipos y alto rendimiento usando Paraglide JS 2.x (basado en compilador, tree-shakable) y Svelte 5 (runes). Cubre configuración, creación de mensajes, estrategias de locale, enrutamiento i18n, SSR/SSG, marcado de texto enriquecido con Snippets de Svelte 5, patrones de reactividad, pruebas y resolución de problemas. Úsalo cuando mencione Paraglide, inlang, paraglide-js, SvelteKit i18n, cambio de locale, URLs localizadas o runes de Svelte 5 en un contexto de internacionalización.

¿Qué es skills?

Este skill proporciona una guía de mejores prácticas para implementar internacionalización (i18n) en aplicaciones Svelte 5 utilizando Paraglide JS 2.x. Está diseñado para desarrolladores que buscan una solución i18n eficiente, con seguridad de tipos (type-safe) y que aproveche al máximo el compilador de Paraglide para lograr bundles más pequeños (hasta un ~70% más pequeños que las bibliotecas en tiempo de ejecución). El skill explica conceptos clave como tratar la configuración regional (locale) como estado de solicitud/compilación, gestionado por el compilador en lugar de como estado reactivo de la aplicación, y cómo Svelte 5 runes se encargan del resto del estado de la UI. El skill cubre desde la configuración básica (usando el comando `npx sv add paraglide`) hasta patrones avanzados como enrutamiento i18n, SSR/SSG, texto enriquecido con Snippets de Svelte 5, y optimización de rendimiento con tree-shaking. También aborda la integración con estrategias de detección de locale (URL, cookie, localStorage), el manejo de la reactividad (explicando qué es reactivo y qué no lo es en el contexto de Paraglide + Svelte 5), y la generación de sitios estáticos (SSG) con el adaptador static de SvelteKit. El skill es útil para cualquier desarrollador que trabaje con SvelteKit y Paraglide, ya sea en proyectos nuevos o existentes, y busca una referencia completa y práctica para implementar i18n de manera correcta y eficiente.

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

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Mejores Prácticas de Paraglide JS + Svelte 5

Trata la configuración regional como estado de solicitud/compilación resuelto por un compilador, no como estado reactivo de la aplicación — deja que la navegación de documento completo de Paraglide maneje los cambios de locale, y deja que los runes de Svelte 5 se encarguen de todo lo demás. Paraglide JS es la integración i18n oficial de SvelteKit: compila messages/{locale}.json en funciones ESM tree-shakable y completamente tipadas, lo que reduce los bundles i18n hasta ~70% en comparación con las bibliotecas en tiempo de ejecución (≈47 KB vs ≈205 KB en el benchmark publicado con 5 locales / 200 mensajes), sin cascadas asíncronas y sin necesidad de conectar un proveedor i18n a los componentes de Svelte 5.


1. Tabla de Decisiones de Referencia Rápida

DecisiónValor recomendado por defectoPor qué
Ruta de instalaciónnpx sv add paraglide="languageTags:en,de" en una nueva app SvelteKitConfigura el plugin de Vite, hooks, atributos de app.html, .gitignore en un solo paso
Paquete@inlang/paraglide-js v2.xEl adaptador v1 @inlang/paraglide-js-adapter-sveltekit está obsoleto — usa el paquete principal directamente
Estrategia (SSR + URLs localizadas)["url", "cookie", "baseLocale"]La URL es la fuente de verdad para SEO; la cookie persiste la elección del usuario de manera visible en el servidor; baseLocale siempre se resuelve al final
Estrategia (añadir anulación almacenada)["localStorage", "cookie", "preferredLanguage", "url", "baseLocale"]localStorage solo es invisible para la primera solicitud SSR; la cookie actúa como puente entre servidor/cliente
Comportamiento del cambio de localePor defecto setLocale(locale) (navegación de documento completo)La reactividad del framework no vuelve a renderizar <html lang>, dir, <title> ni las URLs localizadas; la navegación sí lo hace
AsyncLocalStorageMantenerlo habilitado (por defecto)Vercel Edge y Cloudflare Workers (compatibles con Node) lo soportan; deshabilitarlo corre el riesgo de filtrar la configuración regional entre solicitudes concurrentes
Declaraciones TSemitTsDeclarations: true (TypeScript ≥ 5.6)Mantiene sincronizados los servidores de lenguaje Svelte + TypeScript cuando las claves cambian durante el desarrollo
Claves de mensajeClaves planas, estables y legibles para humanos (calm_green_otter)Recomendación oficial; las claves anidadas funcionan pero se prefieren las planas
Prerenderizadoexport const prerender = true + entries() por localeSSG necesita que cada locale se resuelva en un archivo URL distinto
Reactividad para localeNinguna en los componentes por defectogetLocale() tiene ámbito de solicitud; se renderiza un nuevo documento por locale, por lo que los runes no necesitan rastrearlo

2. Configuración y Ajustes (Paraglide 2.x + SvelteKit + Svelte 5)

2.1 Ruta más rápida

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

El complemento de la CLI de Svelte configura el proyecto inlang, el plugin de Vite, los hooks handle/reroute de SvelteKit, %lang%/%dir% en app.html y una página de demostración opcional.

2.2 Configuración manual (boilerplate completo)

Instala y configura el proyecto, luego conecta cuatro archivos:

vite.config.ts — plugin con estrategia explícita:

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 regenera los .d.ts a medida que editas los mensajes para que los servidores Svelte y TypeScript se mantengan sincronizados; requiere TypeScript 5.6+ (establece false para la ruta de inferencia JSDoc más rápida).

src/app.html — reemplaza el lang codificado:

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

src/hooks.server.ts — middleware del servidor con locale de ámbito de solicitud:

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;

El middleware detecta la configuración regional (estrategias en orden), redirige las solicitudes de documentos cuando la URL no coincide con la detectada, deslocaliza /de/about → /about antes de que SvelteKit lo vea y envuelve la solicitud en AsyncLocalStorage para que getLocale() devuelva la configuración regional correcta por solicitud concurrente.

src/hooks.ts — límite de enrutamiento (debe ser hooks.ts, no 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(.*)?'],
      ],
    },
  ],
});

Precedencia: banderas de CLI / opciones del plugin > archivo de configuración > valores predeterminados integrados. El modo de observación recarga la configuración en caliente.


3. Patrones de Uso Principales (Mensajes, Locale, Enrutamiento)

3.1 Mensajes

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

m.hello_world();                       // "¡Hola Mundo!"
m.greeting({ name: 'Ada' });           // "¡Hola Ada!" — los parámetros están tipados
m.greeting({ name: 'Ada' }, { locale: 'de' }); // forzar locale para una llamada
const title: LocalizedString = m.welcome_title(); // tipo de cadena seguro para la marca

Los mensajes residen en messages/{locale}.json; los locales se declaran en project.inlang/settings.json (baseLocale + locales). Prefiere claves planas y estables; las claves anidadas se soportan mediante m['user.profile.title'](). El acceso dinámico a claves anula el tree-shaking — mapea los IDs dinámicos a referencias de mensajes explícitas en su lugar.

3.2 Obtener y establecer la configuración regional

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

getLocale();          // ej. "de" — ámbito de solicitud en el servidor
getTextDirection();   // "ltr" | "rtl" para el locale actual
setLocale('de');      // navegación de documento completo a la URL localizada

setLocale() actualiza tus estrategias configuradas y luego realiza una navegación de documento completo — a la URL localizada cuando la estrategia url está activa, de lo contrario una recarga. Esto es intencional: <html lang>, dir, <title>, metadatos y la URL localizada son estado a nivel de documento que la reactividad del framework no puede actualizar.

La opción de escape setLocale(locale, { reload: false }) existe solo para superficies completamente renderizadas en el cliente (widgets incrustados, páginas de extensiones) que deben preservar el estado en memoria no restaurable. Nunca lo uses para selectores de locale ordinarios, en documentos SSR/SSG, o en rutas cuya estrategia incluya url — deja la URL antigua y el shell en su lugar, por lo que getLocale() seguirá resolviendo la configuración regional antigua.

3.3 URLs y enlaces localizados

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

localizeHref('/blog');                          // "/de/blog" para el locale actual
localizeHref('/blog', { locale: 'de' });        // explícito
localizeUrl('https://example.com/about', { locale: 'de' }).href; // formulario de URL completa

Los patrones de URL utilizan la sintaxis estándar de URLPattern; el orden importa — coloca patrones específicos antes que comodines y pruébalos en urlpattern.com.

3.4 Selector de locale de Svelte 5 (idiomático)

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

<nav aria-label="Idiomas">
  {#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>

Dos detalles de Svelte 5 importan aquí: $app/state expone page como un objeto reactivo (el store $page y $app/stores son el patrón heredado), y data-sveltekit-reload en enlaces entre locales mantiene sincronizados la URL localizada, el documento renderizado en el servidor y el locale en tiempo de ejecución — también es necesario para el rastreo prerenderizado.


4. Texto Enriquecido con Snippets de Svelte 5 (ParaglideMessage)

El marcado permite a los traductores controlar la estructura mientras tu aplicación controla el renderizado. Los nombres de las etiquetas son definidos por la aplicación — nada se asigna a HTML automáticamente.

// messages/en.json
{
  "cta": "{#link to=|/docs|}Lee la documentación{/link}",
  "welcome": "{#b}Hola {name}{/b}{#icon/}"
}
<script lang="ts">
  import { ParaglideMessage } from '@inlang/paraglide-js-svelte';
  import { m } from '$lib/paraglide/messages';
  const name = $props().name; // o cualquier $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> usa message.parts() cuando existe marcado y recurre a message() para texto plano; la sintaxis {#snippet tag({ children, options, attributes })} es exactamente el patrón de snippets de Svelte 5 — sin slots, sin el legado de <slot>. Ten en cuenta la sintaxis literal to=|/docs| (delimitada por tuberías) y que m.welcome() solo devuelve texto plano sin marcado.


5. Integración de Runes de Svelte 5: Qué es Reactivo (y Qué No)

El modelo mental clave: Paraglide posee la configuración regional como estado de documento/solicitud; los runes de Svelte 5 poseen el estado de la UI. Dado que los cambios de locale envían un nuevo documento, rara vez necesitas runes para rastrear getLocale(). Patrones que surgen en la práctica:

Valores derivados dependientes de locale dentro de un solo documento (por ejemplo, una superficie solo de cliente que usa reload: false, o un panel de vista previa que renderiza múltiples locales explícitamente):

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

  let { locale = getLocale() } = $props();          // props tipados
  // $derived se reevalúa cuando la prop `locale` cambia — no cuando
  // el locale interno de Paraglide cambia (no lo hace, sin navegación):
  const greeting = $derived(m.greeting({ name: 'Ada' }, { locale }));
  const isRtl = $derived(getTextDirection(locale) === 'rtl');
</script>

Pasar el locale forzado hacia abajo en lugar de leer el estado global es el patrón compatible con runes: las opciones { locale } en cada llamada de mensaje hacen que los componentes sean funciones puras de las props, que es exactamente como Svelte 5 quiere que fluya el estado. Reserva $effect para efectos secundarios reales (por ejemplo, persistir un locale de vista previa) — nunca asignes a un $derived, y no escribas $state desde dentro de $effect para "empujar" el locale; pasa props en su lugar.

Estado global del cliente (por ejemplo, "último locale visto" para una herramienta de traducción dentro de la aplicación):

// src/lib/locale-preview.svelte.ts — los archivos .svelte.ts soportan runes en el ámbito del módulo
export const preview = $state({ locale: 'en' as string | undefined });

El $state a nivel de módulo en archivos .svelte.ts se comparte entre todos los importadores — está bien para singletons de toda la aplicación, pero recuerda que no es cómo funciona el locale de producción (esa es la cadena de estrategias + navegación de documento).


6. SSR, Hidratación y SSG

6.1 Reglas de SSR

  • Un orden de cadena handle: coloca paraglideHandle primero (o antes de los manejadores de autenticación que necesiten event.locals claveados por locale) para que los hooks aguas abajo vean la solicitud deslocalizada.
  • Detrás de un proxy de terminación TLS, pasa effectiveRequestUrl para que la coincidencia de URL y las redirecciones utilicen el origen orientado al navegador.
  • Mantén AsyncLocalStorage activado; es lo que hace que getLocale() sea seguro bajo solicitudes concurrentes y SSR por streaming. La alternativa disableAsyncLocalStorage es solo para entornos de ejecución sin node:async_hooks y con aislamiento de solicitud garantizado — usarlo en otros lugares puede filtrar la configuración regional entre usuarios.

6.2 Corrección de la hidratación

El servidor y el cliente deben resolver la configuración regional desde el mismo orden de estrategias. La discrepancia clásica: el servidor resuelve desde la URL, pero la estrategia del cliente lee localStorage primero, que no existe en el momento de SSR. Soluciónalo incluyendo una estrategia cookie visible en el servidor cuando una elección persistida debe dar forma a la primera respuesta.

6.3 SSG (adapter-static / prerender)

El prerenderizado nunca ejecuta el middleware — establece la configuración regional por página durante la compilación:

// 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 requiere que cada locale tenga un prefijo de URL (configura urlPatterns para que /en/… y /de/… existan ambos), de lo contrario SvelteKit no puede emitir archivos distintos por locale. Para el fallback SPA del adaptador estático, establece kit.paths.relative = false para evitar 404 de assets con prefijo de locale, y usa generateStaticLocalizedUrls() para sitemaps.


7. Rendimiento y Tree-Shaking

  • Llama a las funciones de mensaje directamente. m.hello() se compila en una pequeña función ESM; tu bundler elimina automáticamente los mensajes no utilizados y sus traducciones. El tamaño del bundle se mantiene plano a medida que crece el catálogo (benchmark: 47 KB tanto si el proyecto tiene 200 como 1,000 mensajes).

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.

Skills relacionados