#Build an internationalized Astro App
In this guide, we will be learning how to build an multilingual Astro.
Astro comes with great internationalization out of the box
For stuff that's shared between pages, such as layouts or components, we still need an i18n library to inject the correct messages. We'll be using ParaglideJS.
Paraglide offers some unique features that make it a great fit for Astro
- Only messages that are used on šļøIslands are shipped to the client
- Fully typesafe
- Tiny runtime (<100bytes on the client)
#Setup
Set up an Astro app with:
pnpm create astro@latest
Then set up Astro i18nastro.config.mjs
:
export default defineConfig({
i18n: {
defaultLocale: "en", // the default locale
locales: ["en", "de"] // the locales you want to support
},
})
This doesn't affect the routing in any way, but it describes which paths will have which language.
/*
will use the default language (english)/en/*
will use english/de/*
will use german
Let's set up our routes to match that:
āāā src/ ā āāā pages/ ā ā āāā index.astro //default language ā ā āāā about.astro //default language ā ā āāā de/ ā ā ā āāā index.astro //german ā ā ā āāā about.astro//german
It's easiest to author content directly in these files.
If you are using the content/
directory, you should set up different collections for each langauge. Follow the Astro i18n recipie
Inside .astro
files, we can access the current language with Astro.currentLocale
. We can use this to query content in the correct language.
#Adding ParaglideJS
For translating layouts and components we need an i18n library. Let's install ParaglideJS and the Paraglide Astro Integration.
npx @inlang/paraglide-js init npm i @inlang/paraglide-astro
This will have genrated all the files needed for Paraglid & added the necessary dependencies.
Then register the Integration in your astro.config.mjs
:
import paraglide from '@inlang/paraglide-astro'
export default {
integrations: [
paraglide({
project: "./project.inlang",
outdir: "./src/paraglide",
}),
],
i18n: {
defaultLocale: "en",
locales: ["en", "de"],
}
}
This integration will do a few things:
- Run the Paraglide compiler when you run
npm run dev
ornpm run build
. - Run the Paraglide compiler when messages are changed.
- Set Paraglide's language based on your astro i18n routing config.
You need to tell Paraglide which languages are available, and which is the default language in project.inlang/settings.json
.
{
"languageTags": ["en", "de"],
"sourceLanguageTag": "en",
}
Paraglide is a compiler that runs outside of your dev server, so it unfortunately can't just read the i18n config from
astro.config.mjs
. Oh well...
#Adding Messages
Messages are located in ./messages/{lang}.json
. These files should have been generated by the init CLI.
The files contain a key-calue pair of the messageID and the translation.
// messages/en.json
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"hello_world": "Hello World",
"greeting": "Hello {name}"
}
If you already have a lot of hardcoded text in your app you should use the Sherlock VS Code extension to extract them automatically.
#Using Messages
You can import messages from src/paraglide/messages.js
. By convention we do a wildcard import.
import * as m from "../paraglide/messages.js"
m.hello_world() // Hello World
m.greeting({ name: "John" }) // Hello John
Each message is a function that returns the message in the current language. If the message requires parameters, typescript will enforce that you pass them in.
#Passing the Language to the Client
To save bundle size, Paraglide doesn't ship language detection logic to the client. Instead it just reads the lang
attribute on the <html>
tag. Make sure this is set correctly.
In your global Astro layout, add the following:
<html lang={Astro.currentLocale}>
<slot/>
</html>
#Translating the Page Shell
Now it's really just a matter of going through your app and extracting any hard-coded strings into messages. This is easiest to do with the Sherlock VSCode extension.
Then you just import the messages and use them in your components.
---
import * as m from "../paraglide/messages.js"
---
<aside>
<nav>
<a href="/">{m.home()}</a>
<a href="/about">{m.about()}</a>
</nav>
</aside>
#Translating Components
Let's translate an example Counter.svelte
component.
You use messages in components the same way you use messages in layouts. By importing from src/paraglide/messages.js
.
<sciprt> import * as m from "../paraglide/messages" let count = 0; function increment() { count += 1; } </script> <div> <span>{m.count({ count })}</span> <button on:click={increment}>+</button> </div>
Since all messages are separate exports, Vite will be able to treeshake them. Only messages that are used in hydrated components will be sent to the client. This drastically reduces bundle size & requires no extra work.
In components you can access the current language using the languageTag()
function.
<sciprt> import { languageTag } from "../paraglide/runtime" </script> <h1>{languageTag()}</h1>
#What's Next?
You can read the Paraglide and Paraglide-Astro documentation to get a more complete understanding of what's possible. You can also check out our Paraglide-Astro Example
If you have any suggestions for this guide, please reach out to us on Discord