# Localization

> Merx ships English and German. Everything a customer gets to see can be translated, and prices are formatted by the language’s locale.

Translating a shop means replacing its words. Localizing it means everything else that changes with where a customer is: how a number is written, which currency a price is in, how much tax is on it. A German customer and an Austrian one read the same German sentences, and still need different prices and different tax.

Merx keeps those apart, and they’re set in three different places:

- **Messages** — error messages and Panel labels, from Merx’ [translation strings](https://merx2.wagnerwagner.de/reference/options/translations.md). This is the translation part.
- **Number formatting** — how `1045.12` becomes `€ 1,045.12` or `1.045,12 €`, from the locale of the language.
- **Currency and tax** — which currency a price is in and which rate applies, from the [pricing rules](https://merx2.wagnerwagner.de/guide/configuration/currencies.md) and [tax rules](https://merx2.wagnerwagner.de/guide/configuration/taxes.md). Both usually follow the country rather than the language, so they have guides of their own.

## Messages

Merx ships `en` and `de`. Every string is listed in the [translations reference](https://merx2.wagnerwagner.de/reference/options/translations.md), together with what it says in both languages.

The `error.merx.*` strings are the ones your customers see: they’re the messages behind the errors the [API](https://merx2.wagnerwagner.de/guide/api/concept.md) returns when an item can’t be added or the checkout form doesn’t validate. The `field.*`, `page.*`, `section.*` and `tab.*` strings only appear in the Panel.

### Overriding a string

Register the strings under `extensions` in your config, keyed by language code. This works on any site, with or without languages, and needs no plugin of its own.

```php title="/site/config/config.php"
<?php

return [
  'extensions' => [
    'translations' => [
      'en' => [
        'error.merx.emptycart' => 'Your cart is empty.',
      ],
      'de' => [
        'error.merx.emptycart' => 'Dein Warenkorb ist leer.',
      ],
    ],
  ],
];
```

On a [multi-language site](https://getkirby.com/docs/guide/languages) you can also keep the strings in the language file, next to the language definition itself.

```php title="/site/languages/de.php"
<?php

return [
  'code' => 'de',
  'name' => 'Deutsch',
  'locale' => 'de_DE',
  'translations' => [
    'error.merx.emptycart' => 'Dein Warenkorb ist leer.',
  ],
];
```

Pick one of the two. A string set in both places is taken from the language file, and anything you leave alone still comes from Merx.

### Adding a language

Merx falls back to English for any language it does not ship. To add one, register the full set of keys the same way — the [translations reference](https://merx2.wagnerwagner.de/reference/options/translations.md) lists them all.

## Number formatting

[`Merx::formatCurrency()`](https://merx2.wagnerwagner.de/reference/classes/merx/format-currency.md) and [`Merx::formatPercent()`](https://merx2.wagnerwagner.de/reference/classes/merx/format-percent.md) format by locale, not by currency. A price in euros is written `€1,045.12` for an English visitor and `1.045,12 €` for a German one. Everything that prints a price goes through them, including [`$price->toString()`](https://merx2.wagnerwagner.de/reference/classes/price/to-string.md) and [`$field->toFormattedPrice()`](https://merx2.wagnerwagner.de/reference/options/field-methods/to-formatted-price.md).

The locale comes from `LC_NUMERIC`. On a multi-language site Kirby sets it per language:

```php title="/site/languages/de.php"
<?php

return [
  'code' => 'de',
  'locale' => 'de_DE',
];
```

On a single-language site set Kirby’s [`locale`](https://getkirby.com/docs/reference/system/options/locale) option instead.

```php title="/site/config/config.php"
<?php

return [
  'locale' => 'de_DE',
];
```

Both methods also take a locale as an argument, for the cases where you need a different one than the visitor’s.

```php title="/site/templates/product.php"
<?= Merx::formatCurrency(1045.12, 'EUR', 'de_DE') ?>
```

## Currency and tax

Neither is taken from the language. The currency comes from the [pricing rule](https://merx2.wagnerwagner.de/guide/configuration/currencies.md) that applies to the request and the rate from the [tax rule](https://merx2.wagnerwagner.de/guide/configuration/taxes.md) of the product — and those rules are where you decide what a language, a country or anything else should mean for your prices.

## Language in API requests

The [API](https://merx2.wagnerwagner.de/guide/api/concept.md) is not a page request, so Merx cannot infer the language from the URL. Send it with the request, either as an `X-Language` header or as a `language` query parameter.

```js title="script.js"
const response = await fetch('/api/shop/cart', {
  headers: {
    'X-Language': document.documentElement.lang,
  },
});
```

Without it, Merx answers in Kirby’s fallback language (English) — which means a customer can end up with an error message in a language they aren’t browsing in.
