---
title: "Translate your app"
description: "Translate Pinecart with t(), format money and dates per locale with i18n, fall back along a locale chain, and let mainsdk check catch missing strings."
section: "guides"
status: "Preview"
spec: "i18n"
canonical: "https://mainsdk.dev/docs/guides/i18n"
updated: "2026-10-28"
---

# Translate your app

> Translate Pinecart with t(), format money and dates per locale with i18n, fall back along a locale chain, and let mainsdk check catch missing strings.

Status: Preview · Canonical: https://mainsdk.dev/docs/guides/i18n · Updated: 2026-10-28

Show Pinecart in English, Hindi and Tamil, with money and dates formatted for each locale, from one deployment.

> **Note:** Built and run from the repository (spec `i18n`, ADR-0060); not published to npm.

**Before you start:** a MainSDK app with a `main.yaml`. The calls are described on [Languages and formats](https://mainsdk.dev/docs/interfaces/i18n).

## 1. Set your locales

```yaml title="main.yaml"
i18n:
  driver: files            # files | tolgee | crowdin | lokalise | ai-draft
  locales: [en-IN, hi-IN, ta-IN]
```

## 2. Write the messages

Messages live in `locales/`, one file per locale:

```yaml title="locales/en-IN.yaml"
orders.count: "{count, plural, one {# order} other {# orders}}"
orders.total: "Total: {total}"
```

```yaml title="locales/hi-IN.yaml"
orders.count: "{count, plural, one {# ऑर्डर} other {# ऑर्डर}}"
orders.total: "कुल: {total}"
```

Messages use Unicode MessageFormat 2; ICU MessageFormat strings, like the plurals above, are converted when they load. Both cover plurals, genders and selections.

## 3. Use them in your UI

```ts title="app/orders/order-summary.ts"
import { t, i18n } from "mainsdk";

export function orderSummary(orders: { total: number }[]) {
  const total = orders.reduce((sum, o) => sum + o.total, 0);

  return {
    dir: i18n.dir(),
    heading: t("orders.count", { count: orders.length }),
    total: t("orders.total", { total: i18n.format.money(total, "INR") }),
  };
}
```

Render these values in your component, and set `dir` on its root element.

`i18n.format.money(125000, "INR")` gives ₹1,25,000.00 in `en-IN`. Formats come from Unicode CLDR. `i18n.format.date()` and `i18n.format.number()` work the same way, and `i18n.dir()` returns the text direction for right-to-left scripts.

## 4. Know how the locale is chosen

The user's choice comes first, then their account, then the browser. The locale is never guessed from the IP address alone. Market and locale resolve per request, so one deployment serves every market.

Wrap a handler in `i18n.handle(req, () => …, { user })` to run it in the negotiated locale; `t()` and `i18n.format.*` then use it.

A missing message falls back along a chain: `hi-IN`, then `hi`, then `en-IN`, then `en`. Production never shows a key: a message missing everywhere returns an empty string and logs `MAIN-I18N-0009`. Locale-specific email and notification templates (`templates/order.shipped.hi-IN.yaml`) are planned.

## 5. Verify it worked

```bash
npx mainsdk check
```

A missing translation fails the check, not the user: a key missing from a locale under `i18n.required` is `MAIN-I18N-0001`, with the file and line of the `t()` call. Run `npx mainsdk i18n pseudo` to get long, accented and right-to-left pseudo-locales that catch layout breaks before a translator sees them.

## Draft new strings with AI

With the `ai-draft` driver, `npx mainsdk i18n draft --locale hi-IN` drafts missing strings through `ai`, following the glossary in `i18n.ai-draft.glossary`. Each draft is held as pending in `locales/.drafts/`; a person approves it with `i18n.drafts.approve(id)` before any user sees it. An app agent can't approve (`MAIN-I18N-0007`).

## Keep your translators' tools

`npx mainsdk i18n export --format xliff` and `npx mainsdk i18n import <file>` move strings to and from XLIFF, iOS `.xcstrings`, Android strings and ICU JSON. Generating Android `values-hi/` and iOS `.xcstrings` from the same locale files is planned for phase 3.

## Next

- [Languages and formats](https://mainsdk.dev/docs/interfaces/i18n)
- [main.yaml and overlays](https://mainsdk.dev/docs/concepts/main-yaml)
- [The check](https://mainsdk.dev/docs/concepts/the-check)
