# Internationalization

> Translate entries with sibling files; the widget picks the user's language and falls back gracefully.

## Declare your locales

```json title="shipdot.config.json"
{
  "defaultLocale": "en",
  "locales": ["en", "es", "pt"]
}
```

`defaultLocale` is the language of your main entry files. `locales` lists every language you translate into (the default one is added automatically).

## Translate an entry

Create a sibling file with the locale before `.md`. It contains only the translated `title` and body:

```md title=".shipdot/entries/2026-10-14-export-excel.es.md"
---
title: Exportar a Excel
---
Ahora puedes exportar cualquier reporte a Excel con un clic.
```

`date`, `type`, `target`, `link` and `image` always come from the main file. `shipdot build` puts translations in the entry's `i18n` map:

```json
{
  "id": "2026-10-14-export-excel",
  "title": "Export to Excel",
  "html": "<p>You can now export…</p>",
  "i18n": { "es": { "title": "Exportar a Excel", "html": "<p>Ahora puedes…</p>" } }
}
```

`shipdot validate` reports translations for undeclared locales, translations without a main file (orphans), translations of the default locale, and translated keys that can't be translated.

## How the widget picks a language

1. The `locale` attribute, if set: `<ship-dot locale="es">`.
2. Otherwise `navigator.languages`, in the user's order of preference.

Each candidate is matched against the locales present in the feed: exact match first (`pt-BR`), then the base language (`pt-BR` → `pt`), then any regional variant (`pt` → `pt-BR`). If nothing matches, the feed's `defaultLocale` is used.

**Per-entry fallback**: an entry without a translation for the chosen locale is shown in the default locale, so a partially translated feed never has holes.

Dates are formatted with `Intl.DateTimeFormat` in the chosen locale.

## Built-in UI strings

The widget's own labels ("What's new", "Got it", "Learn more", "New/Improved/Fixed", the unread count) ship in **English, Spanish and Portuguese**. Other languages fall back to English. Adding a language is a small, welcome pull request to [`packages/shipdot/src/strings.ts`](https://github.com/pipe1os/shipdot/blob/main/packages/shipdot/src/strings.ts).

## Translating with an LLM

In [LLM mode](https://pipe1os.github.io/shipdot/guides/drafting/#llm-mode-bring-your-own-key), `shipdot draft` writes the translation files for every configured locale along with the draft. Review them in the pull request like any other text.
