Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(guides): experimental i18n routing #5187

Merged
merged 22 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
325 changes: 325 additions & 0 deletions src/content/docs/en/guides/internationalization.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
---
title: Internationalization
description: Learn how to use i18n routing in Astro.
i18nReady: false
---

import FileTree from '~/components/FileTree.astro'
import Since from '~/components/Since.astro'

Astro's internationalization (i18n) features allow you to adapt your project for an international audience.


## i18n Routing (experimental)

{/*

Enable the experimental routing option by adding an `i18n` object to your Astro configuration with a default location and a list of all languages to support:

```js
// astro.config.mjs
import {defineConfig} from "astro/config";

export default defineConfig({
experimental: {
i18n: {
defaultLocale: "en",
locales: ["en", "es", "pt_BR"]
}
}
})
```

Organize your content folders by locale, including your `defaultLocale` and `src/pages/index.astro` will now automatically default to the `index.astro` file of your default language.

```
├── src
│ ├── pages
│ │ ├── en
│ │ │ ├── about.astro
│ │ │ ├── index.astro
│ │ ├── es
│ │ │ ├── about.astro
│ │ │ ├── index.astro
│ │ ├── pt_BR
│ │ │ ├── about.astro
│ │ │ ├── index.astro
│ ├── index.astro
```

Compute relative URLs for your links with `getLocaleRelativeURL` from the new `astro:i18n` module:

```astro
---
import {getLocaleRelativeUrl} from "astro:i18n";
const aboutUrl = getLocaleRelativeUrl("pt_Br", "about");
---
<p>Learn more <a href={aboutURL}>About</a> this site!</p>
```

Enabling i18n routing also provides


## Glossary

The current page will use some specific terminology related to internationalization:
- Locale: a code that represents a language.
- Localized folder: a folder/directory that is named after a locale.

*/}


<Since v="3.5.0" />

Astro's experimental i18n routing allows you to add your multilingual content with support for configuring a default language and URL path convention, computing relative page URLs, and accepting preferred languages provided by your visitor's browser.

This routing API helps you generate, use, and verify the URLs that your multi-language site produces. Check back and update regularly for the latest changes as this API continues to develop!


### Configure i18n routing

1. Enable the experimental routing option by adding an `i18n` object to your Astro configuration with a [default location (`defaultLocale`) and a list of all languages to support (`locales`)](#defaultlocale-and-locales):

```js title="astro.config.mjs"
import { defineConfig } from "astro/config"
export default defineConfig({
experimental: {
i18n: {
defaultLocale: "en",
locales: ["en", "es", "pt_BR"]
}
}
})
```

You can also specify a [`routingStrategy`](#routing-strategy) to customize the URLs for your default language.


2. Organize your content folders with localized content by language, including your `defaultLocale`. Your folder names must match your lthe items in `locales` exactly. `src/pages/index.astro` will now automatically redirect to the `index.astro` file of your default language.
<FileTree>
- src
- pages
- en
- about.astro
- index.astro
- es
- about.astro
- index.astro
- pt_BR
- about.astro
- index.astro
- index.astro (redirects to the route created for `/en/index.astro`)
</FileTree>

:::note
The localised folders do not need to be at the root of the `/pages/` folder.

Create individual `/[lang]/` folders anywhere within `src/pages/` and Astro's [file-based routing](/en/core-concepts/routing/) will create your pages at corresponding URL paths.

```
src/pages/blog/[lang]/about.astro -> example.com/blog/es/about/
```
:::

3. When writing links to pages on your site, use a localized URL. You can write this URL directly, or compute it using the [`getRelativeLocaleURL()`](#getrelativelocaleurl) helper available from the [`astro:i18n` module](#virtual-module-astroi18n). This will always provide the correct, localized route and can help you correctly use, or check, URLs on your site.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When writing links to pages on your site, use a localized URL.

@ematipico Just checking on a case like:

  • prefix-other-locales so that a default locale URL route is example.com/blog/
  • you want to hardcode (not compute) the href and write it by hand
  • you write href="/blog/" ??? <-- if this is true, then I should NOT say write a localized URL

Is the above example correct? should I change the wording?

Copy link
Member Author

@ematipico ematipico Nov 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you write href="/blog/" ??? <-- if this is true, then I should NOT say write a localized URL

That's correct. The URL /blog should be reflected in the file src/pages/blog/index.astro. With prefix-other-locales, it's assumed that the content outside of a localized folder belongs to the default locale.

Is the above example correct? should I change the wording?

I think so, we should mention "localized URL" only for those locales that aren't the default locale.


```astro title="src/pages/es/index.astro"
---
import { getRelativeLocaleUrl } from 'astro:i18n';
const aboutURL=getRelativeLocaleUrl("es", "about");
---

<a href="/es/get-started/">¡Vamos!</a>
<a href={getRelativeLocaleUrl('es', 'blog')}>Blog</a>
<a href={aboutURL}>Acerca</a>
```

### `defaultLocale` and `locales`

Both a default language ([`defaultLocale`](en/reference/configuration-reference/#experimentali18ndefaultlocale)) and a list of all supported languages ([`locales`](en/reference/configuration-reference/#experimentali18nlocales)) must be specified in your `i18n` routing configuration.

Each language must be a string (e.g. `"fr"`, `"pt_BR"`), but no particular language format or syntax is enforced while this feature is still experimental and under development. This may be subject to change in future versions.

Your `/[lang]/` folder names must match exactly the `locales` in the list, and every supported language must have its own localized folder.

Depending on your deploy host, you may discover transformations in URL paths, so check your deployed site to determine the best syntax for your project.

### Routing strategy

You can choose whether or not pages in your `defaultLocale` should be prefixed with their `/lang/` in their URL path. Every other supported locale will automatically be prefixed.

#### `'prefix-other-locales'`

```js title="astro.config.mjs" ins={7}
import { defineConfig } from "astro/config"
export default defineConfig({
experimental: {
i18n: {
defaultLocale: "en",
locales: ["es", "en", "fr"],
routingStrategy: "prefix-other-locales"
}
}
})
```

This is the **default** value. With this option, URLs in your default language will **not** have a `/[lang]/` prefix.

For example, if `i18n.defaultLocal: "en"` is configured:

- `src/pages/en/blog.astro` will produce the route `example.com/blog/`
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't correct. With the 'prefix-other-locales', developers must have src/pages/blog.astro in their file system, because it's "assumed" that pages outside of a lang/locale folder belong to the default locale. This is related to my comment above.

- `src/pages/fr/blog.astro` will produce the route `example.com/fr/blog/`
- If there is no file at `src/pages/es/blog.astro`, then the route `example.com/es/blog/` will 404 unless you specify a [fallback strategy](#fallback).


#### `'prefix-always'`

```js title="astro.config.mjs" ins={7}
import { defineConfig } from "astro/config"
export default defineConfig({
experimental: {
i18n: {
defaultLocale: "en",
locales: ["es", "en", "fr"],
routingStrategy: "prefix-always"
}
}
})
```

With this option, all routes will have their locale code (prefix) in their URL.

- `src/pages/[lang]/blog.astro` will all reproduce routes of the form `example.com/[locale]/blog/`.
- The URL `example.com/blog/` will return a 404 (not found) status code.


### Browser language detection

Astro's i18n routing combined with one of Astro's [on-demand server rendering modes](/en/guides/server-side-rendering/) (`output:'server'` or `output:'hybrid'`) allow you to access two properties for browser language detection: `Astro.preferredLocale` and `Astro.preferredLocaleList`.

These combine the browser's `Accept-Langauge` header, and your `locales` to automatically respect your visitor's preferred languages.

- `Astro.preferredLocale`: Astro can compute a **preferred locale** for your visitor if their browser's preferred locale is included in your `locales` array. This value is undefined if no such match exists.


- `Astro.preferredLocaleList`: An array of all locales that are both requested by the browser and supported by your website. This produces a list of all compatible languages between your site and your visitor. The value is `[]` if none of the browser's requested languages are found in your `locales` array. If the browser does not specify any preferred languages, then this value will be [`i18n.locales`].
Comment on lines +171 to +174
Copy link
Member Author

@ematipico ematipico Nov 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if it's worth mentioning it, but I leave the information here, and you decide: the locales coming from Accept-Language have their format, e.g. pt-BR, although it's possible that developers decide to use a different format inside i18n.locales, e.g. pt_BR, pt-br.

Astro under to hood does some transformations (no need to mention this) and eventually will return the format specified in i18n.locales.

So if Accept-Language is fr-CA;q=0.1, pt-BR;q=0.5", and i18n.locales is ["fr_CA", "pt_BR"]:

  • Astro.preferredLocale is "pt_BR";
  • Astro.preferredLocaleList is ["pt_BR", "fr_CA"], the order is important because it's kept from Accept-Language

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me see what I can do here!



### Fallback

Astro's i18n routing allows you to configure a **fallback routing strategy**. When a page in one language doesn't exist (e.g. a page that is not yet translated), instead of displaying a 404 page, you can redirect a user from one locale to another on a per-language basis. This is useful when you do not yet have a page for every route, but you want to still provide some content to your visitors.

For example, the configuration below sets `es` as the fallback locale for any missing `fr` routes. This means that a user visiting `example.com/fr/my-page/` will be redirected to and shown the content for `example.com/es/my-page/` instead of being taken to a 404 page when `src/pages/fr/my-page.astro` does not exist.

```js title="astro.config.mjs" ins={6,7-9}
import { defineConfig } from "astro/config"
export default defineConfig({
experimental: {
i18n: {
defaultLocale: "en",
locales: ["es", "en", "fr"],
fallback: {
fr: "es"
}
}
}
})
```

Astro will ensure that a page is built in `src/pages/fr` for every page that exists in `src/pages/es/`. If the page does not already exist, then a page with a redirect to the corresponding `es` route will be be created.

## Virtual module `astro:i18n`

This module provides functions that can help you to create URLs using locales.

Creating routes for your project with the i18n router will depend on certain configuration values you have set that affect your page routes. When creating routes with these functions, be sure to take into account your individual settings for:

- [`base`](/en/reference/configuration-reference/#base)
- [`trailingSlash`](/en/reference/configuration-reference/#trailingslash)
- [`build.format`](/en/reference/configuration-reference/#buildformat)
- [`site`](/en/reference/configuration-reference/#site)


Also note that the returned URLs created by these functions for your `defaultLocale` will reflect your `i18n.routingStrategy` configuration.

URLs created when `prefix-always` configured will include a `/lang/` path in the URL. URLs created with `prefix-other-locales` will not include a language prefix.

### `getRelativeLocaleUrl()`


`getRelativeLocaleUrl(locale: string, path: string, options?: GetLocaleOptions): string`

Use this function to retrieve a relative path for a locale. If the locale doesn't exist, Astro throws an error.

```astro
---
getRelativeLocaleUrl("fr", "");
// returns /fr

getRelativeLocaleUrl("fr", "getting-started");
// returns /fr/getting-started

getRelativeLocaleUrl("fr_CA", "getting-started", {
prependWith: "blog"
});
// returns /blog/fr-ca/getting-started

getRelativeLocaleUrl("fr_CA", "getting-started", {
prependWith: "blog",
normalizeLocale: false
});
// returns /blog/fr_CA/getting-started
---
```

### `getAbsoluteLocaleUrl()`

`getAbsoluteLocaleUrl(locale: string, path: string, options?: GetLocaleOptions): string`


Use this function to retrieve an absolute path for a locale when [`site`] has a value. If [`site`] isn't configured, the function returns a relative URL. If the locale doesn't exist, Astro throws an error.


```astro
---
// If `site` is set to be `https://example.com`

getAbsoluteLocaleUrl("fr", "");
// returns https://example.com/fr

getAbsoluteLocaleUrl("fr", "getting-started");
// returns https://example.com/fr/getting-started

getAbsoluteLocaleUrl("fr_CA", "getting-started", {
prependWith: "blog"
});
// returns https://example.com/blog/fr-ca/getting-started

getAbsoluteLocaleUrl("fr_CA", "getting-started", {
prependWith: "blog",
normalizeLocale: false
});
// returns https://example.com/blog/fr_CA/getting-started
---
```

### `getRelativeLocaleUrlList()`

Use this like [`getRelativeLocaleUrl`](#getrelativelocaleurl) to return a list of relative paths for all the locales.


`getRelativeLocaleUrlList(locale: string, options?: GetLocaleOptions): string[]`

### `getAbsoluteLocaleUrlList()`

`getAbsoluteLocaleUrlList(locale: string, options?: GetLocaleOptions): string[]`


Use this like [`getAbsoluteLocaleUrl`](#getabsolutelocaleurl) to return a list of absolute paths for all the locales.





[`site`]: /en/reference/configuration-reference/#site
[`i18n.locales`]: /en/reference/configuration-reference/#experimentali18nlocales
1 change: 1 addition & 0 deletions src/i18n/en/nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export default [
{ text: 'Imports', slug: 'guides/imports', key: 'guides/imports' },
{ text: 'Endpoints', slug: 'core-concepts/endpoints', key: 'core-concepts/endpoints' },
{ text: 'Data Fetching', slug: 'guides/data-fetching', key: 'guides/data-fetching' },
{ text: 'Internationalization', slug: 'guides/internationalization', key: 'guides/internationalization' },
{ text: 'Middleware', slug: 'guides/middleware', key: 'guides/middleware' },
{ text: 'Testing', slug: 'guides/testing', key: 'guides/testing' },
{ text: 'Troubleshooting', slug: 'guides/troubleshooting', key: 'guides/troubleshooting' },
Expand Down