Back

A Quick Guide to Localizing an Astro Site

A Quick Guide to Localizing an Astro Site

Building a multilingual website with Astro? You need to handle two distinct challenges: organizing your content pages by language and translating UI elements like buttons and navigation. This guide shows you exactly how to set up both static and dynamic localization in Astro, from basic configuration to production-ready features.

Key Takeaways

  • Configure Astro’s built-in i18n routing for automatic URL generation and content organization
  • Use dynamic routes with [locale] folders to avoid duplicating page files
  • Integrate Paraglide for type-safe UI string translations with minimal bundle impact
  • Leverage Astro’s helper functions like getRelativeLocaleUrl() to prevent common routing errors

Setting Up Astro i18n Routing for Static Content

Configuring astro.config.mjs for Multilingual Support

Start by configuring Astro’s built-in i18n routing in your astro.config.mjs file. This tells Astro which languages you support and how to structure your URLs:

import { defineConfig } from 'astro/config';

export default defineConfig({
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'es', 'fr'],
    routing: {
      prefixDefaultLocale: true // Use /en/ for English URLs
    }
  }
});

Setting prefixDefaultLocale: true ensures all languages use consistent URL patterns (/en/about, /es/about), making it easier to manage links and avoid routing conflicts later.

Creating Localized Folder Structure

Organize your pages by creating locale-specific folders in src/pages/:

src/pages/
  en/
    index.astro
    about.astro
  es/
    index.astro
    about.astro
  fr/
    index.astro
    about.astro

Each folder corresponds to a locale defined in your config. Astro automatically generates the correct routes based on this structure.

Managing Static and Dynamic Localization in Astro

Organizing Content Collections by Locale

For blog posts, documentation, or any content collections, mirror the same locale structure:

src/content/
  blog/
    en/
      first-post.md
    es/
      first-post.md
    fr/
      first-post.md

When querying content, filter by the current locale:

import { getCollection } from 'astro:content';

const posts = await getCollection('blog', ({ id }) => {
  return id.startsWith(Astro.currentLocale + '/');
});

Implementing Dynamic Routes with [locale] Folders

Instead of duplicating page files for each language, use dynamic routes. Create a [locale] folder:

src/pages/
  [locale]/
    index.astro
    about.astro
    blog/
      [slug].astro

In your pages, use getStaticPaths() to generate routes for all locales:

export function getStaticPaths() {
  return [
    { params: { locale: 'en' } },
    { params: { locale: 'es' } },
    { params: { locale: 'fr' } }
  ];
}

Handling Dynamic UI Strings with Paraglide

Installing and Configuring Paraglide for Astro

While Astro handles page routing, you need a solution for UI text. Paraglide offers excellent TypeScript support and minimal bundle size:

npm install @inlang/paraglide-js @inlang/paraglide-astro
npx @inlang/paraglide-js init

Creating Translation Files and Message Keys

Store your translations in messages/ at your project root:

// messages/en.json
{
  "nav.home": "Home",
  "nav.about": "About",
  "button.readMore": "Read more"
}

// messages/es.json
{
  "nav.home": "Inicio",
  "nav.about": "Acerca de",
  "button.readMore": "Leer más"
}

Import and use translations in your components:

---
import * as m from '../paraglide/messages.js';
---

<nav>
  <a href={`/${Astro.currentLocale}`}>{m.nav_home()}</a>
  <a href={`/${Astro.currentLocale}/about`}>{m.nav_about()}</a>
</nav>

Building a Language Switcher and Navigation

Using getRelativeLocaleUrl() for Clean URLs

Astro provides helper functions to generate proper localized URLs. Always use getRelativeLocaleUrl() instead of manual string concatenation:

import { getRelativeLocaleUrl } from 'astro:i18n';

const aboutUrl = getRelativeLocaleUrl(Astro.currentLocale, 'about');
const blogUrl = getRelativeLocaleUrl(Astro.currentLocale, 'blog/my-post');

Create a language switcher component:

---
import { getRelativeLocaleUrlList } from 'astro:i18n';

const localeUrls = getRelativeLocaleUrlList();
---

<select onchange="window.location.href = this.value">
  {localeUrls.map(url => {
    const locale = url.split('/')[1];
    return (
      <option value={url} selected={Astro.currentLocale === locale}>
        {locale.toUpperCase()}
      </option>
    );
  })}
</select>

Avoiding Common Pitfalls Like Double-Prefixed Routes

Watch out for these common mistakes:

  1. Double prefixes: When using dynamic routes, clean your slugs to avoid URLs like /en/blog/en/my-post:
const cleanSlug = post.slug.replace(`${locale}/`, '');
  1. Hardcoded locale paths: Always use Astro’s i18n helpers instead of string concatenation
  2. Missing locale in links: Every internal link needs proper localization

Advanced Astro i18n Features

Setting Up Fallback Languages

Configure fallbacks for untranslated content:

export default defineConfig({
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'es', 'fr'],
    fallback: {
      es: 'en',
      fr: 'en'
    },
    routing: {
      fallbackType: 'rewrite' // Show fallback content without redirecting
    }
  }
});

Configuring Domain Mapping for Production

For production sites with separate domains per language:

export default defineConfig({
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'es', 'fr'],
    domains: {
      fr: 'https://fr.example.com',
      es: 'https://example.es'
    }
  }
});

This requires server-side rendering with the @astrojs/node or @astrojs/vercel adapter.

Conclusion

Localizing an Astro site combines two approaches: built-in i18n routing for static content organization and external libraries like Paraglide for dynamic UI strings. Start with the folder structure and routing configuration, then layer in translation management. Remember to use Astro’s URL helpers to avoid common routing issues, and consider fallbacks and domain mapping for production deployments.

FAQs

Yes, you can gradually migrate by keeping your current pages and adding localized versions in locale folders. Use redirects to transition users from old URLs to new localized ones while maintaining SEO value.

Use the JavaScript Intl API with Astro.currentLocale for formatting. Create utility functions that accept the locale and return formatted dates, numbers, and currencies appropriate for each language.

Implement hreflang tags in your layout to tell search engines about language alternatives. Astro's i18n routing automatically handles URL structure, but you need to add proper meta tags for optimal international SEO.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay