Quick start

You will display “Hola” at /es/ and “Hello” at /en/, with links to switch languages. Start with an existing, working static Astro project. You only need two JSON files, the integration and one page.

1. Install astro-intl

Run this in your project folder, alongside package.json. Select your project’s package manager. The Astro wizard asks you to confirm installation and adding the integration to astro.config.mjs.

npx astro add astro-intl

2. Create your messages

Create these folders and files. greeting is the key your code will use; its value changes with the language. Both files must have the same keys.

src/i18n/messages/en.json
{
  "greeting": "Hello"
}
src/i18n/messages/es.json
{
  "greeting": "Hola"
}

3. Connect the messages

Complete the integration added by the wizard with the options below and add the message imports. Keep your other integrations and do not add astroIntl(...) a second time. locales lists the languages and messages connects each one to its JSON. defaultLocale sets the default language; it does not create routes or redirect the root. The with attribute lets Node load JSON from this file. This example is checked with Astro 7 and Node 22.12 or later.

astro.config.mjs
import { defineConfig } from "astro/config";
import astroIntl from "astro-intl";
import en from "./src/i18n/messages/en.json" with { type: "json" };
import es from "./src/i18n/messages/es.json" with { type: "json" };

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

4. Create the translated page

Create a folder literally named [lang]. getStaticPaths generates /en/ and /es/ during the build. setRequestLocale reads the language from the URL and loads its messages; await it before calling getTranslations. t('greeting') reads the text. This example needs neither a layout nor middleware.

src/pages/[lang]/index.astro
---
import { setRequestLocale, getTranslations } from "astro-intl";

export function getStaticPaths() {
  return [{ params: { lang: "en" } }, { params: { lang: "es" } }];
}

await setRequestLocale(Astro.url);
const t = getTranslations();
---

<!doctype html>
<html lang={Astro.params.lang}>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>{t("greeting")}</title>
  </head>
  <body>
    <h1>{t("greeting")}</h1>
    <nav aria-label="Language">
      <a href="/es/" lang="es">Español</a>
      <a href="/en/" lang="en">English</a>
    </nav>
  </body>
</html>

5. Open both languages

Start the server and open the address printed in the terminal, adding /es/ or /en/. You should see Hola and Hello respectively. Try the language links. Your existing page at / stays as it is. Restart the server after changing astro.config.mjs.

npm run dev

6. Add another translation

Add farewell to both JSON files and use it in the same page below the h1. Every new text follows this sequence: the same key in all languages, translated values and a call to t().

src/i18n/messages/en.json
{
  "greeting": "Hello",
  "farewell": "Goodbye"
}
src/i18n/messages/es.json
{
  "greeting": "Hola",
  "farewell": "Adiós"
}
src/pages/[lang]/index.astro — <body>
<p>{t("farewell")}</p>

If something is missing

A 404 at /es/ usually means [lang]/index.astro or its getStaticPaths is missing. If you see a key instead of text, check that it exists in both JSON files. If the language context is missing, check that await setRequestLocale comes before getTranslations. You do not need Astro’s built-in i18n configuration for this tutorial.