Configuration

1. Create message files

Create one JSON file per locale inside src/i18n/messages/. You can organize keys into namespaces (nested objects).

src/i18n/messages/en.json
{
  "nav": {
    "home": "Home",
    "docs": "Documentation"
  },
  "hero": {
    "title": "Hello, World!",
    "description": "Welcome to <bold>astro-intl</bold>"
  }
}

2. Choose your approach

astro-intl offers three ways to load messages. Pick the one that fits your project best.

If you are not using createIntlMiddleware, you must pass defaultLocale and locales directly in astro.config.mjs so that functions like getLocales(), getDefaultLocale(), and isValidLocale() work correctly.

Option A — Messages in config (recommended)

Pass your message imports along with defaultLocale and locales directly in astro.config.mjs. No extra files needed.

astro.config.mjs
import { defineConfig } from "astro/config";
import astroIntl from "astro-intl";

export default defineConfig({
  integrations: [
    astroIntl({
      defaultLocale: "en",
      locales: ["en", "es"],
      messages: {
        en: () => import("./src/i18n/messages/en.json"),
        es: () => import("./src/i18n/messages/es.json"),
      },
    }),
  ],
});

Option B — Custom request.ts (advanced)

For full control, use defineRequestConfig to create a request file — similar to next-intl. This lets you add custom logic like fetching messages from a CMS. You still need to pass defaultLocale and locales in astro.config.mjs.

src/i18n/request.ts
import { defineRequestConfig } from "astro-intl";

export default defineRequestConfig(async (locale) => {
  return {
    locale,
    messages: (await import(`./messages/${locale}.json`)).default,
  };
});

astro.config.mjs with locales:

astro.config.mjs
import { defineConfig } from "astro/config";
import astroIntl from "astro-intl";

export default defineConfig({
  integrations: [
    astroIntl({
      defaultLocale: "en",
      locales: ["en", "es"],
    }),
  ],
});

Option C — messagesDir (simplified JSON)

Set messagesDir to a directory path and the integration will automatically load {locale}.json files. No manual imports needed — perfect for JSON-based workflows.

astro.config.mjs
import { defineConfig } from "astro/config";
import astroIntl from "astro-intl";

export default defineConfig({
  integrations: [
    astroIntl({
      defaultLocale: "en",
      locales: ["en", "es"],
      messagesDir: "./src/i18n/messages",
    }),
  ],
});

3. Set up your Layout

Best practice: Call setRequestLocale once in a shared layout (e.g., src/layouts/Layout.astro) instead of repeating it on every page. This initializes the global i18n context before any component reads translations.

With Option A (messages in config):

src/layouts/Layout.astro
---
import { setRequestLocale } from "astro-intl";

// No need to import anything — messages come from astro.config
await setRequestLocale(Astro.url);
---

<!DOCTYPE html>
<html>
  <head>
    <title>My Site</title>
  </head>
  <body>
    <slot />
  </body>
</html>

With Option B (defineRequestConfig):

src/layouts/Layout.astro
---
import { setRequestLocale } from "astro-intl";
import "../i18n/request"; // registers defineRequestConfig

await setRequestLocale(Astro.url);
---

<!DOCTYPE html>
<html>
  <head>
    <title>My Site</title>
  </head>
  <body>
    <slot />
  </body>
</html>

3b. Middleware approach (alternative)

Instead of calling setRequestLocale in every layout, you can use createIntlMiddleware to handle it automatically for all routes. This also sets locales and defaultLocale in the store, so you don't need to pass them in astro.config.mjs.

Create a routing config:

src/i18n/routing.ts
export const routing = {
  locales: ["en", "es"],
  defaultLocale: "en",
} as const;

Create the middleware:

src/middleware.ts
import "@/i18n/request";
import { createIntlMiddleware } from "astro-intl/middleware";
import { routing } from "@/i18n/routing";

export const onRequest = createIntlMiddleware(routing);

Your astro.config.mjs stays simple:

astro.config.mjs
import { defineConfig } from "astro/config";
import astroIntl from "astro-intl";

export default defineConfig({
  integrations: [astroIntl()],
});

4. Export getStaticPaths

In every [lang] page, export getStaticPaths returning all supported locales so Astro can pre-render them.

src/pages/[lang]/index.astro
export function getStaticPaths() {
  return [
    { params: { lang: "en" } },
    { params: { lang: "es" } },
  ];
}