Paraglide JS

repository·main·Indexed 20 days ago

https://github.com/opral/paraglide-js

A compiler-first internationalization (i18n) library that turns translation files into type-safe, tree-shakable ESM functions. Designed for high performance in frameworks like React, SvelteKit, TanStack Start, Astro, and Next.js, it offers smaller bundle sizes than runtime-based libraries by shipping only the messages used on a page. Includes a Runtime API for locale management and URL localization, a ServerRuntime for server-side environments, and support for number, datetime, and relative-time formatting.

Tokens
73.3K
Snippets
244
Records
299
Agent score
64%

What's inside @inlang/paraglide-js

  1. Compare Paraglide JS with other i18n libraries

    main

    Paraglide JS uses a compiler-based architecture, which allows for optimizations that runtime-only libraries like i18next or React-Intl cannot achieve.

    Key Advantages

    • Tree-shaking & Bundle Size: Because it is a compiler, Paraglide JS supports tree-shaking, potentially reducing i18n bundle sizes by up to 70% compared to runtime libraries that ship all messages by default.
    • Type Safety & IDE Support: Provides full type safety and IDE autocomplete for both message keys and their required parameters.
    • Framework & Metaframework Agnostic: Works across React, Svelte, Vue, and frameworks like Next.js, SvelteKit, and Astro without needing specific wrappers.
    • Advanced Features: Built-in support for localized (i18n) routing, SSR/SSG with request isolation (via AsyncLocalStorage), message variants, and multi-tenancy.
    • Message Syntax: While it supports ICU MessageFormat 1 via the inlang-icu-messageformat-1 plugin, it is generally message syntax agnostic through Inlang plugins.
  2. Handling AsyncLocalStorage in Astro runtimes

    main

    Paraglide JS uses AsyncLocalStorage to manage locale state per request.

    • Vercel Edge / Cloudflare Workers: Keep AsyncLocalStorage enabled. These runtimes support it, and it is the recommended setup.
    • Fallback: If you are using a runtime that does not provide AsyncLocalStorage or node:async_hooks but still guarantees per-request isolation, you may use the disableAsyncLocalStorage fallback.

    Warning: Only use the fallback if your runtime guarantees per-request isolation. In a multi-request server environment without isolation, using this fallback could leak locale state between concurrent requests.

  3. How router composition works with Paraglide

    main

    Paraglide coexists with your router by mapping localized URLs to your application's canonical routes. Your router remains responsible for route definitions, loaders, navigation, and typing, while Paraglide handles locale detection, localized URL generation, and message functions.

    • Incoming requests: Use deLocalizeUrl() to map a localized URL (e.g., /de/ueber) back to a canonical route (e.g., /about).
    • Outgoing links: Use localizeUrl() to generate a localized URL from a canonical route.

    For routers with rewrite hooks, apply these functions at the routing boundary.

    import { deLocalizeUrl, localizeUrl } from "./paraglide/runtime.js";
    
    // Incoming request: localized URL -> canonical app route
    deLocalizeUrl("https://example.com/de/ueber").href; // https://example.com/about
    
    // Outgoing link: canonical app route -> localized URL
    localizeUrl("https://example.com/about", { locale: "de" }).href; // https://example.com/de/ueber
  4. Configure locale detection strategies

    main

    The strategy option defines the precedence for detecting the user's locale. The order in the array determines which method is tried first.

    Supported strategies include:

    • url (via URL patterns)
    • cookie (using cookieName and cookieDomain)
    • localStorage (using localStorageKey)
    • globalVariable (fallback)
    • baseLocale (final fallback)
    • Custom strategies using the pattern custom-[A-Za-z0-9]+.

    Default: ["cookie", "globalVariable", "baseLocale"] (optimized for browser/server compatibility).

    Example of custom precedence:

    strategy: ['url', 'cookie', 'baseLocale']
    // Example strategy configuration
    await compile({
      project: "./project.inlang",
      outdir: "./src/paraglide",
      strategy: ['url', 'cookie', 'baseLocale']
    });
  5. How to handle interpolation within arrays or objects

    main

    You cannot use message-format interpolation (e.g., {name}) inside a JSON blob stored in a translation key. If your array items or object values require dynamic data, you should use separate message keys for each item and construct the array manually in your code.

    // messages/en.json
    {
      "step_0": "Welcome, {name}!",
      "step_1": "You have {count} items in your cart",
      "step_2": "Proceed to checkout"
    }
    const steps = [
      m.step_0({ name: "Alex" }),
      m.step_1({ count: 3 }),
      m.step_2(),
    ];
  6. Ensure hydration consistency between server and client

    main

    For successful client-side hydration, the server and the client must agree on the locale source.

    Warning: Do not use localStorage as a primary locale source for SSR. localStorage is unavailable during the initial server request. If you need to persist a user's locale so it affects the first response, use a server-visible strategy like cookie.

    Common causes of hydration mismatches:

    • Different strategy order on server vs client.
    • Server reads from URL while client reads from localStorage.
    • Serving cached HTML that contains a stale locale.
  7. Order URL patterns and localized patterns correctly

    main

    Both urlPatterns and the localized array within them are order-dependent. The first match wins.

    1. urlPatterns Order

    Place more specific patterns before general patterns. If a general wildcard pattern appears first, specific routes will never be reached.

    2. localized Array Order

    Within a pattern's localized array, the order of locale patterns matters for both localization and delocalization.

    • Localization: The first matching pattern for the target locale is used.
    • Delocalization: When removing a locale from a URL, put the more specific pattern (with prefix) first. If a generic pattern is first, it might match a prefixed URL incorrectly.
    // CORRECT ORDER for urlPatterns: Specific patterns first
    urlPatterns: [
    	{
    		pattern: "https://example.com/blog/:id",
    		localized: [
    			["en", "https://example.com/blog/:id"],
    			["de", "https://example.com/de/blog/:id"],
    		],
    	},
    	{
    		pattern: "https://example.com/:path(.*)?",
    		localized: [
    			["en", "https://example.com/:path(.*)?"],
    			["de", "https://example.com/de/:path(.*)?"],
    		],
    	},
    ];
    
    // CORRECT ORDER for localized array (Delocalization):
    {
      pattern: "/:path(.*)?",
      localized: [
        ["de", "/de/:path(.*)?"],   // Specific pattern with prefix first
        ["en", "/:path(.*)?"],      // Generic pattern last
      ],
    }
  8. Understand Paraglide JS bundle size advantages

    main

    Paraglide JS is a compiler-based i18n library that typically ships 3-10x smaller bundles than runtime-based libraries like i18next.

    Key advantages include:

    • Tree-shaking: Paraglide only ships the messages you actually use on a page. Unused messages are removed at build time, making the bundle size immune to the total number of messages in your project.
    • Compiled Format: Messages are compiled into functions rather than being shipped as a large JSON dictionary.
    • No Manual Namespacing Required: Unlike runtime libraries that require developers to manually split messages into namespaces to keep bundles small, Paraglide's tree-shaking makes manual namespacing redundant.
  9. Understand Lazy Locale Loading in Paraglide JS

    main

    By default, Paraglide compiles messages into functions that contain all locales. For applications with a very large number of locales, you may want to use Lazy Locale Loading, which fetches only the current locale's messages on-demand.

    When to use Lazy Loading

    • Under ~20 locales: Tree-shaking unused messages is typically more efficient than the overhead of lazy loading. Paraglide's standard compiler approach is recommended.
    • Over ~20 locales: Lazy loading may become beneficial depending on your application's specific message usage patterns.

    Note: There is no hard limit on the number of locales you can use in Paraglide. Lazy loading is an optimization, not a requirement. An experimental locale splitting option is available for apps that require this behavior.

  10. Configure locale-aware formatting

    main

    Paraglide supports several declaration formatters for locale-aware output:

    • plural: Uses Intl.PluralRules for plural and ordinal categories.
    • number: Uses Intl.NumberFormat for numbers, currency, and compact notation.
    • datetime: Uses Intl.DateTimeFormat for dates and times.
    • relativetime: Uses Intl.RelativeTimeFormat for relative values (e.g., "yesterday").

    Message syntax is plugin-based. You can use the default inlang format or the ICU MessageFormat via the ICU plugin (e.g., {count, plural, one {# item} other {# items}}).

    // Pluralization example
    m.items_in_cart({ count: 1 }); // "1 item in cart"
    m.items_in_cart({ count: 5 }); // "5 items in cart"
  11. Choose between message-modules and locale-modules output structure

    main

    The outputStructure option determines how the generated files are organized in the outdir.

    • message-modules (Default): Each message has its own module containing all language versions. This is optimized for production because it enables better tree-shaking for bundlers.
    • locale-modules: Each locale has its own module containing all messages. This is recommended for development because it results in fewer files, which speeds up bundlers by reducing HTTP requests in dev mode.

    Note: locale-modules can lead to larger bundle sizes in production because bundlers often struggle to tree-shake this structure effectively.

    // Recommended for development
    await compile({
      project: "./project.inlang",
      outdir: "./src/paraglide",
      outputStructure: "locale-modules"
    });
  12. The four main parts of Paraglide JS

    main

    Paraglide JS is composed of four distinct parts that work together to manage translations and locale state:

    PartFileKey Exports
    CompilerCLI / Plugincompile(), bundler plugins
    Messagesmessages.jsm.hello_world(), m.greeting(), etc.
    Runtimeruntime.jsgetLocale(), setLocale(), locales
    Strategyruntime.jsstrategy, localizeHref(), urlPatterns