The theme's own strings are translated in seven locales. A site serves the subset
it wants, translates the strings it configured itself, and — if it wants to —
the pages too.

## Steps

::steps
1. **Narrow the list with `duxt.locales`.**

   ```ts [app/app.config.ts]
   locales: ['en-GB', 'de-DE']
   ```

   Not i18n's own `locales` array: that one merges across layers instead of
   replacing, so declaring two there leaves the other five routed and announced
   in `hreflang`.

2. **Pick the unprefixed one** in `nuxt.config.ts` with `i18n.defaultLocale`.
   It is URL-relevant: changing it later moves every page in both directions at
   once.

3. **Translate your own strings.** For a handful, the record form costs less than
   a locale file per language:

   ```ts
   sections: [
     {
       label: { 'en-GB': 'Guides', 'de-DE': 'Anleitungen' },
       to: '/guides',
       icon: 'lucide:book-open'
     }
   ]
   ```

   For more than a handful, register your own i18n keys and write the key. Do not
   reach for `duxt.defaults.*` — those are the layer's internal keys, and a
   rename there would break your site silently.

4. **Set the origin.** `hreflang` is not valid relative, so it needs
   `i18n.baseUrl` or `NUXT_PUBLIC_I18N_BASE_URL`.

5. **Translate the pages, if you want to.** A folder per language inside the
   source; the default locale stays where it is and moves no URL:

   ```ts [app/app.config.ts]
   sources: [{ path: 'docs', locales: ['en-GB', 'de-DE'] }]
   ```

   ::file-tree{title="docs/"}
   ---
   tree:
     - name: index.md
     - name: 1.getting-started/
       children:
         - name: index.md
     - name: de-DE/
       children:
         - name: index.md
   ---
   ::

   Translate as few pages as you like — everything else falls back, with a
   banner telling the reader why.

6. **Open a page under each locale** and check that the interface changed
   language, the path still resolves, and no configured label prints as a raw
   key.
::

## Only some versions, or another repository

`locales` on a ref overrides the source's, which is the usual shape: the current
version is translated and the two behind it are not.

```ts
{
  path: 'docs',
  locales: ['en-GB', 'de-DE'],
  refs: [
    { tag: 'v2.0.0' },                     // both languages
    { tag: 'v1.0.0', locales: ['en-GB'] }  // original only
  ]
}
```

The object form moves a language somewhere else entirely — its own repository,
with its own maintainers and its own pace:

```ts
locales: ['en-GB', { locale: 'de-DE', repo: 'acme/docs-de', path: 'docs' }]
```

That is what React and Vue do outside their tooling, with `de.react.dev` and the
`vuejs-translations` org, because translators work to their own schedule and
review. Here it is a source entry rather than a second website — one search
index, one language switcher.

::callout{type="warning" title="What a translation costs to build"}
Roughly **two seconds of build and 0.6 MB of database per language × version ×
repository**, measured linear to at least 200 collections. Translating every
version of every repository multiplies all three; translating only the current
version is what most projects actually do.
::

## Writing direction

The active locale supplies the document direction as well as its language.
Navigation, sheets, arrows and page layout mirror when a consuming site adds an
RTL locale because duxt components use logical start and end. Technical material
does not mirror blindly: code, commands, paths, API names and other LTR syntax
keep the direction in which they are readable.

duxt does not currently ship an RTL locale. Direction-aware means a site can add
one without forking the theme; it does not mean one of the seven built-in locales
is RTL.

## When a page has no translation

The reader gets the nearest language that has it, with a banner, and the page
carries `noindex` plus a canonical pointing at the language it is really in. The
chain — locale, base language, sibling region, `fallbackLocale`, original — is in
[Localisation](/concepts/localisation).

## Checklist

- [ ] `duxt.locales` lists exactly the locales you serve
- [ ] `/sitemap.xml` contains one entry per page per locale, and no others
- [ ] `hreflang` links are absolute
- [ ] No configured label prints as a raw key
- [ ] Switching locale keeps the reader on the same page
- [ ] A page you did not translate shows the fallback banner, not a 404
- [ ] An added RTL locale mirrors the interface while technical syntax remains LTR
- [ ] `duxt.sourceOptions.defaultLocale` matches `i18n.defaultLocale`