# Astro Docs i18n Recipe
CHRIS OUTLINE:
- Use lang/ directories to organise different language content (en/, zh/, etc) [this can also be true for Content Collections]
- Use some kind of file to store UI strings in — we use ts to do some fancy stuff, but could also just be json
- Write a small helper function to get the string you need
- Show how to use that on a page/component and get language from the URL
- Option to use an existing library to do that/help with string interpolation? (this would need more research, i18next? typesafe-i18n?)
- Integrate Crowdin if you like
YAN NOTES:
A few things come to mind:
- Small checklist of important things to remember when making an i18n infrastructure:
* Does your page have a corresponding meta lang attribute? (so screen readers know what language they should speak)
```html
<html lang="en">
<!-- /en/index.html -->
<h1>Hi this is in British English we say colour.</h1>
<p lang="de">Hallo, ich bin Deutsch!</p>
</html>
```
* Are your screen-reader-only labels translated?
* Are your components rtl/ltr compatible? logical CSS properties! might be worth linking to https://rtlstyling.com/ (if expecting languages with different writing modes)
(I can see the argument of making this its own page or a blog post or something else that isn't part of the i18n guide though)
- Using Zod/Content Collections for type-safe frontmatter i18n (More like a simple intro example using something like .refine() to show users the possibilities of type-safe i18n)
- Guide users to Web APIs that have i18n methods? (DateTimeFormat, Number.prototype.toLocaleString(), etc)
## Overview
## Prerequisites
## Recipe
### Set up pages for each language
1. Create a directory for each language you want to support. For example, `en/` and `fr/` if you are supporting English and French:
- src/
- pages/
- en/
- about.astro
- index.astro
- fr/
- about.astro
- index.astro
2. Set up `src/pages/index.astro` to redirect to your default language.
```astro
---
// src/pages/index.astro
---
<meta http-equiv="refresh" content="0;url=/en/" />
```
### Translate UI
1. Create a file to store your translation strings in. For this guide we’ll use a `src/i18n/ui.ts` file:
```ts
// src/i18n/ui.ts
export const defaultLang = 'en';
export const ui = {
en: {
'nav.home': 'Home',
'nav.about': 'About',
'nav.twitter': 'Twitter',
},
fr: {
'nav.home': 'Acceuil',
'nav.about': 'À propos',
}
} as const;
```
2. Create a helper to let us get translations strings for different parts of our UI in `src/i18n/utils.ts` and a helper to detect the page language based on the current URL:
```js
// src/i18n/utils.ts
import { ui, defaultLang } from './ui';
export function useTranslations(lang: keyof typeof ui) {
return function t(key: keyof typeof ui[defaultLang]) {
return ui[lang][key] || ui[defaultLang][key];
}
}
export function getLangFromUrl(url: URL) {
const [, lang] = Astro.url.pathname.split('/');
return lang || defaultLang;
}
```
3. Use our helpers to choose the UI string that corresponds to the current language. For example, a nav component might look like:
```astro
---
// src/components/Nav.astro
import { getLangFromUrl, useTranslations } from "../i18n/utils.ts"
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
---
<ul>
<li>
<a href={`/${lang}/home/`}>{t('nav.home')}</a>
</li>
<li>
<a href={`/${lang}/about/`}>{t('nav.about')}</a>
</li>
<li>
<a href="https://twitter.com/astrodotbuild">
{t('nav.twitter')}
</a>
</li>
</ul>
```
4. Each page should have a `lang` attribute on the `<html>` element that matches the language on the page. In this example, a reusable `Layout` grabs the language from the route:
```astro
---
// src/layouts/Base.astro
import { getLangFromUrl } from "../i18n/utils.ts";
const lang = getLangFromUrl(Astro.url);
---
<html lang={lang}>
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>Astro</title>
</head>
<body>
<slot />
</body>
</html>
```
```astro
---
// src/pages/en/about.astro
import Base from "../../layouts/Base.astro"
---
<Base>
<h1>About me</h1>
...
</Base>
```
### Use content collections for translated posts
1. Create a folder in `src/content` for each type of content you want to include, and create subdirectories for each supported language. For example, to support English and French blog posts:
- src/
- content/
- blog/
- en/
- post1.md
- post2.md
- fr/
- post1.md
- post2.md
2. Create a `src/content/config.ts` file and export a collection for each type of content.
```ts
//src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blogCollection = defineCollection({
schema: z.object({
title: z.string(),
author: z.string(),
date: z.date()
})
});
export const collections = {
'blog': blogCollection
};
```
📚 [Read more about Content Collections.](/en/guides/content-collections/)
3. Use [dynamic routes](/en/core-concepts/routing/#dynamic-routes) to fetch and render content based on a `lang` and a `slug` parameter. In static rendering mode, use `getStaticPaths` to map each content entry to a page:
```astro
//src/pages/[lang]/blog/[...slug].astro
---
import { getCollection } from 'astro:content'
export async function getStaticPaths() {
const pages = await getCollection('blog')
const paths = pages.map(page => {
const [lang, ...slug] = page.slug.split('/');
return { params: { lang, slug: slug.join('/') || undefined }, props: page }
})
return paths;
}
const { lang, slug } = Astro.params;
const page = Astro.props;
const formattedDate = page.data.date.toLocaleString(lang);
const { Content } = await page.render();
---
<h1>{page.data.title}</h1>
<p>by {page.data.author} at {formattedDate}</p>
<Content/>
```
In [SSR mode](/en/guides/server-side-rendering/), fetch the requested entry directly:
```astro
//src/pages/[lang]/blog/[...slug].astro
---
import { getEntryBySlug } from 'astro:content';
const { lang, slug } = Astro.params;
const page = await getEntryBySlug('blog', `${lang}/${slug}`);
if (!page) {
return Astro.redirect("/404");
}
const formattedDate = page.data.date.toLocaleString(lang);
const { Content, headings } = await page.render();
---
<h1>{page.data.title}</h1>
<p>by {page.data.author} at {formattedDate}</p>
<Content/>
```
### Use links to switch between languages
## Resources
- [Choosing a Language Tag](https://www.w3.org/International/questions/qa-choosing-language-tags)
- [Right-to-left Styling 101](https://rtlstyling.com/)