astro-i18n

repository·main·Indexed 42 days ago

https://github.com/alexandre-fernandez/astro-i18n

A TypeScript-first internationalization library for Astro providing type-safe translations, internationalized routing, and support for plurals, context, and interpolations. It includes a CLI for scaffolding and key extraction, a middleware for route detection and redirection, and utility functions like t() for translation and l() for localized URL generation.

Tokens
11.9K
Snippets
54
Records
59
Agent score
54%

What's inside astro-i18n

  1. Handle plurals and context with Variants

    main

    Variants allow you to handle plurals and context within a single translation key using a specific syntax: key{{ variable: value }}.

    Plurals: Numbers will match the closest value. Context: Use non-numeric values to match specific contexts.

    {
      "car": "There is a car.",
      "car{{ cars: 2 }}": "There are two cars.",
      "context{{ weather: 'rain' }}": "It's rainy."
    }
    import { t } from "astro-i18n"
    
    t("car", { cars: 2 }) // "There are two cars."
    t("context", { weather: "rain" }) // "It's rainy."
    t("car", { cars: 2 })
    t("context", { weather: "rain" })
  2. How translation groups and loading work

    main

    Translations in astro-i18n are organized into groups. This allows for efficient loading and scoping of data:

    • "common" group: Accessible on every page.
    • Current page group: Named after the route (e.g., "/about"). Only accessible on that specific page.
    • Custom groups: Named by you (e.g., "admin"). You decide which pages can access them using translationLoadingRules.
    • Route groups: If a group name matches a route (e.g., "/posts/[id]"), it loads automatically on that page.

    Priority/Specificity: If duplicate keys exist across groups, the t function returns the most specific one in this order: Custom group $\rightarrow$ Local page group $\rightarrow$ Common group.

    // Example of a custom group in config
    const translations = {
      "common": { "en": { "hello": "Hello" } },
      "admin": { "en": { "welcome": "Admin Welcome" } }
    };
  3. Use interpolation formatters in translations

    main

    Interpolation formatters transform values within translation strings. If multiple formatters are applied, they are chained. You can use the shorthand syntax (without parentheses) for formatters that require no arguments.

    Default Formatters:

    • upper() or upper: Converts to uppercase.
    • lower() or lower: Converts to lowercase.
    • capitalize(): Capitalizes the first character and lowers the rest.
    • json(format = true): Converts value to JSON string (use format = true for pretty-printing).
    • default_falsy(defaultValue) or default(defaultValue): Uses defaultValue if the value is falsy.
    • default_nullish(defaultValue): Uses defaultValue if the value is null or undefined.
    • default_non_string(defaultValue): Uses defaultValue if the value is not a string.
    • intl_format_number(options, locale): Formats using Intl.NumberFormat.
    • intl_format_date(options, locale): Formats using Intl.DateTimeFormat.

    Custom Formatters: To add custom formatters, pass them as the second argument to the useAstroI18n middleware. Note: If you want formatters to work on the client-side, they must not rely on any variables or objects outside their own function scope.

    export function xXify(value: unknown, repeats: unknown = 1) {
    	if (typeof repeats !== "number") {
    		throw new Error("repeats must be a number")
    	}
    	if (repeats <= 0) return value
    	return `${"xX".repeat(repeats)}${value}${"Xx".repeat(repeats)}`
    }
    
    // When registering in middleware:
    // useAstroI18n(config, { xXify });
  4. Manage translation files in the filesystem

    main

    You can store translations in several locations:

    1. Inside the middleware config.
    2. In a root config file (astro-i18n.config.(ts|js|cjs|mjs|json)).
    3. In src/pages (local page translations).
    4. In the src/i18n directory (custom groups).

    File Naming Conventions: Local page translations can be placed in src/pages or src/i18n/pages (nested like Astro routes). Valid formats include:

    • fr.json (Regular)
    • _fr.json (Private)
    • fr.translated-page-name.json (Regular with translation)
    • _fr.translated-page-name.json (Private with translation)

    Custom Groups: Create a directory in src/i18n named after your group. Use translationLoadingRules in your config to specify which routes should load these groups.

    // Example translation file: src/pages/about/_i18n/fr.a-propos.json
    {
      "my_nested": {
        "translation_key": "bonjour"
      }
    }
  5. Generate types and sync routes with `i18n:sync`

    main

    To achieve full TypeScript type-safety and autocompletion for your translations, and to generate your translated routes, run the following command:

    npm run i18n:sync
    npm run i18n:sync
  6. Quick install and middleware setup

    main

    To quickly set up the project, run the install command. This will generate the necessary configuration files and middleware.

    If you are deploying to a serverless environment (where you have no filesystem or Node.js setup), use the --serverless flag. In serverless mode, all initial data must be passed directly to the middleware.

    After installation, ensure your middleware file (src/middleware.ts or src/middleware/index.ts) uses useAstroI18n:

    import { sequence } from "astro/middleware"
    import { useAstroI18n } from "astro-i18n"
    
    // Pass your config and custom formatters here
    const astroI18n = useAstroI18n()
    
    export const onRequest = sequence(astroI18n)
    ./node_modules/.bin/astro-i18n install
    # For serverless environments:
    ./node_modules/.bin/astro-i18n install --serverless
  7. Configure `astro-i18n` options

    main

    The following configuration keys are available in your astro-i18n.config file or middleware setup:

    KeyTypeDescription
    primaryLocalestringThe default locale for your app.
    secondaryLocalesstring[]All other supported locales.
    fallbackLocalestringLocale to search if a key is missing. Defaults to primaryLocale. Set to "" to disable.
    showPrimaryLocalebooleanWhether to show the default locale in the URL.
    trailingSlash"always" | "never"Controls trailing slashes in generated routes. Default is "never".
    run"server" | "client+server"Controls serialization for client-side use. Default is "client+server".
    translationsobjectThe translation data object.
    translationLoadingRulesarrayRules (regex) to map routes to translation groups.
    translationDirectoryobjectConfigures names for i18n and pages directories. Default: { "i18n": "i18n", "pages": "i18n" }.
    routesobjectMap of route segment translations (e.g., "fr": { "about": "a-propos" }).
    {
      "primaryLocale": "en",
      "secondaryLocales": ["fr"],
      "fallbackLocale": "en",
      "showPrimaryLocale": false,
      "trailingSlash": "never",
      "run": "client+server",
      "translationLoadingRules": [
        {
          "routes": ["^/admin.*"],
          "groups": ["^admin$"]
        }
      ]
    }
  8. Define translation data and scoping

    main

    The translations object allows you to define localized strings. You can organize translations in two ways:

    1. Global/Common Scope: Using a key like common to provide translations accessible across the entire application.
    2. Route-based Scope: Defining translations specific to a path (e.g., /page or /page/[id]) or a group.

    Within each scope, keys are mapped to specific locales (e.g., en, fr). The system supports complex keys for variants (using ICU-like syntax), nested objects, and interpolation.

    translations: {
      common: {
        en: {
          key: "value",
          "variant{{ n: 2 }}": "value for n=2"
        },
        fr: {
          key: "valeur"
        }
      },
      "/page": {
        en: {
          title: "Page Title"
        }
      }
    }
  9. Implement a LocaleSwitcher component

    main

    A simple component to allow users to switch between available locales. It uses astroI18n.locales and the l() function to generate correct links.

    ---
    import { astroI18n, l } from "astro-i18n"
    
    interface Props {
    	showCurrent: boolean
    	labels: { [locale: string]: string }
    }
    
    const { showCurrent = true, labels = {} } = Astro.props
    
    const params: Record<string, string> = {}
    for (const [key, value] of Object.entries(Astro.params)) {
    	if (value === undefined) continue
    	params[key] = String(value)
    }
    
    let links = astroI18n.locales.map((locale) => ({
    	locale,
    	href: l(Astro.url.pathname, params, {
    		targetLocale: locale,
    	}),
    	label: labels[locale] || locale.toUpperCase(),
    }))
    
    if (!showCurrent) {
    	links = links.filter((link) => link.locale !== astroI18n.locale)
    }
    ---
    
    <nav>
    	<ul>
    		{
    			links.map(({ href, label }) => (
    				<li>
    					<a href={href}>{label}</a>
    				</li>
    			))
    		}
    	</ul>
    </nav>
  10. Initialize the library with useAstroI18n

    main

    The useAstroI18n function is the mandatory middleware required to make the library work. It accepts a configuration object (or a string path to a config file) and an optional object of custom formatters.

    Signature: useAstroI18n(config?: Partial<AstroI18nConfig> | string, formatters?: { [name: string]: (value: unknown, ...args: unknown[]) => unknown }): AstroMiddleware

    import { useAstroI18n } from "astro-i18n";
    
    // Using a config object
    export default useAstroI18n({
      primaryLocale: 'en',
      secondaryLocales: ['fr', 'es'],
    });
  11. Use the `t` function for translations

    main

    The t function is used to retrieve translation values by key. It supports nested keys using dot notation.

    import { t } from "astro-i18n"
    
    // Returns the value for the key
    t("my_nested.translation_key")
    import { t } from "astro-i18n"
    t("my_nested.translation_key")