Communitygithub.com

agentmemoir/skills

Orientação especializada para criar aplicações i18n type-safe e performáticas com Paraglide JS 2.x (baseado em compilador, tree-shakable) e Svelte 5 (runes). Aborda configuração, criação de mensagens, estratégias de locale, roteamento i18n, SSR/SSG, marcação rich text com snippets do Svelte 5, padrões de reatividade, testes e solução de problemas.

O que é skills?

Este skill fornece orientação especializada para construir aplicações internacionalizadas (i18n) type-safe e de alto desempenho usando Paraglide JS 2.x (uma biblioteca baseada em compilador que gera funções ESM tree-shakable) e Svelte 5 (que utiliza runes para reatividade). Ele cobre todo o ciclo de vida da internacionalização: desde a configuração inicial e criação de mensagens até estratégias de locale, roteamento i18n, renderização no servidor (SSR) e geração estática (SSG). O skill enfatiza a abordagem idiomática do Paraglide, onde o locale é tratado como estado de requisição/build resolvido pelo compilador, e não como estado reativo da aplicação, o que leva a bundles i18n até 70% menores em comparação com bibliotecas em tempo de execução. Ele também detalha a integração com Svelte 5, incluindo o uso de snippets para rich text, padrões de reatividade com runes e práticas recomendadas para evitar problemas comuns de hidratação e estratégias de persistência de locale. É útil sempre que o usuário mencionar Paraglide, inlang, paraglide-js, internacionalização do SvelteKit, troca de locale, URLs localizadas ou runes do Svelte 5.

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

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Melhores Práticas para Paraglide JS + Svelte 5

Trate o locale como estado de requisição/build resolvido por um compilador, e não como estado reativo da aplicação — deixe a navegação de documento completo do Paraglide cuidar das trocas de locale e deixe os runes do Svelte 5 cuidarem de todo o resto. Paraglide JS é a integração i18n oficial do SvelteKit: ele compila messages/{locale}.json em funções ESM tree-shakable e totalmente tipadas, resultando em bundles i18n até ~70% menores do que bibliotecas em tempo de execução (≈47 KB vs ≈205 KB no benchmark publicado com 5 locales / 200 mensagens), sem waterfalls assíncronos e sem a necessidade de um provedor i18n nos componentes Svelte 5.


1. Tabela de Decisão de Referência Rápida

DecisãoPadrão recomendadoPor quê
Caminho de instalaçãonpx sv add paraglide="languageTags:en,de" em um novo app SvelteKitConfigura o plugin Vite, hooks, atributos app.html e .gitignore em uma única etapa
Pacote@inlang/paraglide-js v2.xO adaptador v1 @inlang/paraglide-js-adapter-sveltekit está obsoleto — use o pacote principal diretamente
Estratégia (SSR + URLs localizadas)["url", "cookie", "baseLocale"]URL é a fonte da verdade para SEO; cookie persiste a escolha do usuário visível ao servidor; baseLocale sempre resolve por último
Estratégia (adicionar substituição armazenada)["localStorage", "cookie", "preferredLanguage", "url", "baseLocale"]localStorage sozinho é invisível para a primeira requisição SSR; cookie faz a ponte servidor/cliente
Comportamento da troca de localePadrão setLocale(locale) (navegação de documento completo)A reatividade do framework não re-renderiza <html lang>, dir, <title> ou URLs localizadas; a navegação sim
AsyncLocalStorageMantenha ativado (padrão)Vercel Edge e Cloudflare Workers (compatibilidade com Node) suportam; desativar corre o risco de vazar locale entre requisições concorrentes
Declarações TSemitTsDeclarations: true (TypeScript ≥ 5.6)Mantém os servidores de linguagem Svelte + TS sincronizados quando as chaves mudam durante o desenvolvimento
Chaves de mensagemChaves aleatórias planas, estáveis e legíveis (calm_green_otter)Recomendação oficial; chaves aninhadas funcionam, mas chaves planas são preferidas
Pré-renderizaçãoexport const prerender = true + entries() por localeSSG precisa que cada locale resolva para um arquivo de URL distinto
Reatividade para localeNenhuma nos componentes por padrãogetLocale() tem escopo de requisição; um novo documento é renderizado por locale, então os runes não precisam rastreá-lo

2. Configuração (Paraglide 2.x + SvelteKit + Svelte 5)

2.1 Caminho mais rápido

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

O complemento da CLI do Svelte configura o projeto inlang, o plugin Vite, os hooks handle/reroute do SvelteKit, %lang%/%dir% em app.html e uma página de demonstração opcional.

2.2 Configuração manual (boilerplate completo)

Instale e crie o projeto, então conecte quatro arquivos:

vite.config.ts — plugin com estratégia 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 os .d.ts conforme você edita as mensagens para que os servidores Svelte e TypeScript permaneçam sincronizados; requer TypeScript 5.6+ (defina false para o caminho mais rápido de inferência JSDoc).

src/app.html — substitua o lang codificado:

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

src/hooks.server.ts — middleware do servidor com locale de escopo de requisição:

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;

O middleware detecta o locale (estratégias em ordem), redireciona requisições de documento quando a URL discorda do locale detectado, deslocaliza /de/about → /about antes do SvelteKit vê-lo e envolve a requisição em AsyncLocalStorage para que getLocale() retorne o locale correto por requisição concorrente.

src/hooks.ts — limite de roteamento (deve ser hooks.ts, não 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(.*)?'],
      ],
    },
  ],
});

Precedência: flags CLI / opções do plugin > arquivo de configuração > padrões embutidos. O modo de observação recarrega a configuração a quente.


3. Padrões de Uso Principais (Mensagens, Locale, Roteamento)

3.1 Mensagens

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

m.hello_world();                       // "Olá Mundo!"
m.greeting({ name: 'Ada' });           // "Olá Ada!" — parâmetros são tipados
m.greeting({ name: 'Ada' }, { locale: 'de' }); // força locale para uma chamada
const title: LocalizedString = m.welcome_title(); // tipo de string seguro

As mensagens ficam em messages/{locale}.json; os locales são declarados em project.inlang/settings.json (baseLocale + locales). Prefira chaves planas e estáveis; chaves aninhadas são suportadas via m['user.profile.title'](). O acesso dinâmico a chaves prejudica o tree-shaking — mapeie IDs dinâmicos para referências de mensagem explícitas.

3.2 Obtendo e definindo o locale

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

getLocale();          // ex. "de" — escopo de requisição no servidor
getTextDirection();   // "ltr" | "rtl" para o locale atual
setLocale('de');      // navegação de documento completo para a URL localizada

setLocale() atualiza suas estratégias configuradas e então realiza uma navegação de documento completo — para a URL localizada quando a estratégia url está ativa, caso contrário um recarregamento. Isso é intencional: <html lang>, dir, <title>, metadados e a URL localizada são estado de nível de documento que a reatividade do framework não pode atualizar.

A escotilha de escape setLocale(locale, { reload: false }) existe apenas para superfícies totalmente renderizadas no cliente (widgets embutidos, páginas de extensão) que devem preservar estado de memória não restaurável. Nunca a use para seletores de locale comuns, em documentos SSR/SSG ou em rotas cuja estratégia inclua url — ela deixa a URL antiga e o shell no lugar, então getLocale() continua resolvendo o locale antigo.

3.3 URLs localizadas e links

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

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

Os padrões de URL usam a sintaxe URLPattern padrão; a ordem importa — coloque padrões específicos antes de curingas e teste-os em urlpattern.com.

3.4 Seletor de locale do Svelte 5 (idiomático)

<script lang="ts">
  import { page } from '$app/state';                       // Svelte 5: objeto, não um 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>

Dois detalhes do Svelte 5 importam aqui: $app/state expõe page como um objeto reativo (o store $page e $app/stores são o padrão legado), e data-sveltekit-reload em links entre locales mantém a URL localizada, o documento renderizado no servidor e o locale do tempo de execução sincronizados — também necessário para crawling pré-renderizado.


4. Rich Text com Snippets do Svelte 5 (ParaglideMessage)

A marcação permite que os tradutores controlem a estrutura enquanto seu aplicativo controla a renderização. Os nomes das tags são definidos pelo aplicativo — nada mapeia para HTML automaticamente.

// messages/en.json
{
  "cta": "{#link to=|/docs|}Leia a documentação{/link}",
  "welcome": "{#b}Olá {name}{/b}{#icon/}"
}
<script lang="ts">
  import { ParaglideMessage } from '@inlang/paraglide-js-svelte';
  import { m } from '$lib/paraglide/messages';
  const name = $props().name; // ou qualquer $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() quando a marcação existe e recorre a message() para texto simples; a sintaxe {#snippet tag({ children, options, attributes })} é exatamente o padrão de snippet do Svelte 5 — sem slots, sem legado <slot>. Observe a sintaxe literal to=|/docs| (delimitada por pipe) e que m.welcome() sozinho retorna texto simples sem marcação.


5. Integração com Runes do Svelte 5: O Que É Reativo (e O Que Não É)

O modelo mental principal: o Paraglide é dono do locale como estado de documento/requisição; os runes do Svelte 5 são donos do estado da UI. Como as mudanças de locale enviam um novo documento, raramente você precisa de runes para rastrear getLocale(). Padrões que surgem na prática:

Valores derivados dependentes de locale dentro de um único documento (por exemplo, uma superfície somente cliente usando reload: false, ou um painel de pré-visualização renderizando vários locales explicitamente):

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

  let { locale = getLocale() } = $props();          // props tipadas
  // $derived reavalia quando a prop `locale` muda — não quando
  // o locale interno do Paraglide muda (isso não acontece, sem navegação):
  const greeting = $derived(m.greeting({ name: 'Ada' }, { locale }));
  const isRtl = $derived(getTextDirection(locale) === 'rtl');
</script>

Passar o locale forçado para baixo em vez de ler o estado global é o padrão amigável a runes: as opções { locale } em cada chamada de mensagem tornam os componentes funções puras das props, que é exatamente como o Svelte 5 quer que o estado flua. Reserve $effect para efeitos colaterais verdadeiros (por exemplo, persistir um locale de pré-visualização) — nunca atribua a um $derived e não escreva $state de dentro de $effect para "empurrar" o locale; passe props.

Estado global do cliente (por exemplo, "último locale pré-visualizado" para uma ferramenta de tradução no aplicativo):

// src/lib/locale-preview.svelte.ts — arquivos .svelte.ts suportam runes no escopo do módulo
export const preview = $state({ locale: 'en' as string | undefined });

$state no nível do módulo em arquivos .svelte.ts é compartilhado entre todos os importadores — bom para singletons do aplicativo inteiro, mas lembre-se de que não é como o locale de produção funciona (isso é a cadeia de estratégia + navegação de documento).


6. SSR, Hidratação e SSG

6.1 Regras do SSR

  • Uma ordem da cadeia handle: coloque paraglideHandle primeiro (ou antes de manipuladores de autenticação que precisam de event.locals chaveado por locale) para que os hooks downstream vejam a requisição deslocalizada.
  • Atrás de um proxy terminador de TLS, passe effectiveRequestUrl para que a correspondência de URL e os redirecionamentos usem a origem voltada para o navegador.
  • Mantenha AsyncLocalStorage ativado; é ele que torna getLocale() seguro sob requisições concorrentes e streaming SSR. O fallback disableAsyncLocalStorage é apenas para runtimes sem node:async_hooks e isolamento de requisição garantido — usá-lo em outros lugares pode vazar locale entre usuários.

6.2 Correção da hidratação

Servidor e cliente devem resolver o locale a partir da mesma ordem de estratégia. A incompatibilidade clássica: o servidor resolve da URL, mas a estratégia do cliente lê localStorage primeiro, que não existe no tempo do SSR. Corrija incluindo uma estratégia cookie visível ao servidor quando uma escolha persistida deve moldar a primeira resposta.

6.3 SSG (adapter-static / prerender)

A pré-renderização nunca executa o middleware — defina o locale por página durante a construção:

// 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 }));
}

O SSG requer que cada locale tenha um prefixo de URL (configure urlPatterns para que /en/… e /de/… existam), caso contrário o SvelteKit não pode emitir arquivos distintos por locale. Para o fallback SPA do adaptador estático, defina kit.paths.relative = false para evitar erros 404 de ativos com prefixo de locale e use generateStaticLocalizedUrls() para sitemaps.


7. Performance e Tree-Shaking

  • Chame as funções de mensagem diretamente. m.hello() compila para uma pequena função ESM; seu bundler descarta automaticamente mensagens não utilizadas e suas traduções. O tamanho do bundle permanece estável à medida que o catálogo cresce (benchmark: 47 KB quer o projeto tenha 200 ou 1.000 mensagens).
  • Use chaves planas em vez de aninhadas. Objetos aninhados (m.user.profile.title()) são suportados, mas impedem que o compilador elimine subárvores não utilizadas; chaves planas permitem tree-shaking total.
  • Evite acesso dinâmico a chaves. m[key]() impede o tree-shaking completamente — toda a mensagem é retida. Em vez disso, mapeie IDs dinâmicos para referências estáticas de mensagem.
  • O padrão de estratégia padrão usa apenas url. Se você tem apenas um locale, nenhuma estratégia é necessária — Paraglide usa baseLocale.

8. Padrões de Teste

8.1 Testes unitários de mensagens

import { describe, it, expect } from 'vitest';
import { m } from '$lib/paraglide/messages';
import { setLocale } from '$lib/paraglide/runtime';

describe('saudações', () => {
  it('saúda em inglês', () => {
    setLocale('en', { reload: false });
    expect(m.hello()).toBe('Hello!');
  });

  it('saúda em alemão', () => {
    setLocale('de', { reload: false });
    expect(m.hello()).toBe('Hallo!');
  });
});

8.2 Testes de integração do SvelteKit (Playwright)

import { expect, test } from '@playwright/test';

test('troca de locale via link', async ({ page }) => {
  await page.goto('/');
  await page.click('a[href="/de"]');
  await expect(page.locator('html')).toHaveAttribute('lang', 'de');
});

9. Solução de Problemas

SintomaCausa provávelCorreção
Erro getLocale() retorna locale errado no servidorAsyncLocalStorage desativado ou substituídoVerifique se disableAsyncLocalStorage não está definido; coloque paraglideHandle primeiro na cadeia de hooks
Hidratação com conteúdo piscando (locale incorreto no carregamento)Estratégia servidor/cliente incompatívelInclua cookie na estratégia; certifique-se de que ele seja definido no SSR
Mensagens não encontradas em desenvolvimentoServidor de linguagem TS desatualizadoAtive emitTsDeclarations: true e reinicie o servidor de linguagem
Sitemap SSG contém apenas um localeentries() ausente ou urlPatterns cobre apenas um localeAdicione entries() à página [locale] e garanta que cada locale tenha um padrão de URL
setLocale({ reload: false }) não atualiza a UINavegação nunca acontece; estado antigo permaneceUse setLocale(locale) sem opções, ou force a re-renderização manualmente em superfícies somente cliente

10. Estratégias Avançadas

10.1 Negociação de locale do servidor (cabeçalho Accept-Language)

// Em hooks.server.ts
import { detectLocaleFromAcceptLanguageHeader } from '$lib/paraglide/runtime';

// Dentro do paraglideHandle
const preferred = detectLocaleFromAcceptLanguageHeader(
  event.request.headers.get('Accept-Language')
);
if (preferred) {
  // Redireciona ou define cookie com base na preferência
}

10.2 Conteúdo localizado dinâmico com dados de API

Para conteúdo que já está localizado por uma API externa (por exemplo, CMS), passe o locale para a chamada da API:

import { getLocale } from '$lib/paraglide/runtime';
import type { PageLoad } from './$types';

export const load: PageLoad = async ({ fetch, params }) => {
  const locale = getLocale();
  const res = await fetch(`/api/content?locale=${locale}`);
  return { content: await res.json() };
};

10.3 Roteamento com base em dados localizados

Se você tem slugs localizados de um CMS, use um parâmetro [slug] não localizado em vez de [locale]/[slug] e resolva o conteúdo por locale internamente.


11. Migração do v1

Se você está migrando do @inlang/paraglide-js-adapter-sveltekit v1:

  1. Remova a dependência do adaptador.
  2. Adicione @inlang/paraglide-js v2.x.
  3. Substitua paraglideVitePlugin conforme mostrado na seção de configuração.
  4. Substitua os hooks do adaptador pelos hooks paraglideMiddleware + reroute.
  5. Atualize as importações: $lib/paraglide/… (o diretório de saída padrão mudou para src/lib/paraglide).
  6. Altere as chamadas da API se usava locale de $app/stores — agora use getLocale().

12. Notas Específicas do Svelte 5

  • Use $app/state em vez de $app/stores. O acesso ao page via $app/state é o padrão Svelte 5.
  • Snippets em vez de slots. Para rich text, use {#snippet} e <ParaglideMessage> conforme mostrado.
  • $effect não é para rastrear locale. A mudança de locale é uma navegação de documento, então $effect não é acionado. Se você precisar reagir a mudanças de locale em uma superfície somente cliente, use uma prop ou $derived de um estado gerenciado externamente.
  • Componentes como funções puras. Passe locale como prop para componentes que precisam dele, em vez de ler o estado global.

13. Recursos


14. Convenções de Commit

  • feat(i18n): adiciona suporte a locale
  • fix(paraglide): corrige hidratação de locale
  • docs(i18n): atualiza guia de melhores práticas
  • chore(deps): atualiza @inlang/paraglide-js

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.

Habilidades Relacionadas