---
title: Locale
published: 2026-11-02
lastmod: 2026-11-02
url: https://larasell.dev/docs/reference/locale
---

## Table of contents
- [Locale tags](#locale-tags)
- [The Locale enum](#the-locale-enum)
  - [Canonical form](#canonical-form)
  - [Lookup chain](#lookup-chain)
- [Configuration](#configuration)
- [Reading the configuration](#reading-the-configuration)
  - [Negotiating a locale](#negotiating-a-locale)
- [Translatable fields](#translatable-fields)
- [Fallback](#fallback)
- [Where Larasell uses the current locale](#where-larasell-uses-the-current-locale)

# Locale

Larasell writes every locale as a [BCP 47](https://www.rfc-editor.org/info/bcp47) language tag,
like `en`, `de-DE` or `zh-Hans`. It's the same format browsers send in `Accept-Language`, HTML uses
in `lang` and `hreflang`, and search engines expect. You use the same string everywhere: in
translations, in the config, in URLs and in HTTP headers.

## Locale tags

A tag has up to three parts, separated by hyphens:

- **Language:** 2 or 3 lowercase letters, like `de` or `agq`.
- **Script:** 4 letters with the first one uppercase, like `Hans` or `Latn`. Optional.
- **Region:** 2 uppercase letters, like `DE` or `CH`. Optional.

So `de` is German, `de-CH` is German as spoken in Switzerland, `zh-Hans` is Chinese written in
simplified characters and `es-MX` is Spanish as spoken in Mexico.

Larasell supports these three parts. Variants and extensions, like `de-CH-1996` or `en-US-u-ca-buddhist`,
are not supported.

## The `Locale` enum

The `Locale`{href="https://raw.githubusercontent.com/Larasell-dev/larasell/refs/heads/main/src/Enums/Locale.php"} enum lists
every locale Larasell knows. Each case's value is its tag:

```php
use Larasell\Larasell\Enums\Locale;

Locale::GERMAN->value; // 'de'
Locale::GERMAN_GERMANY->value; // 'de-DE'
Locale::CHINESE_SIMPLIFIED->value; // 'zh-Hans'
Locale::from('es-MX'); // Locale::SPANISH_MEXICO
```

The enum lists every locale that exists, not the locales your shop supports. Use the
[locale config](#configuration) for that.

### Canonical form

Tags are case-insensitive, so `de-de` and `DE-de` mean the same locale. Larasell stores each tag in
one canonical form, so you never end up with two translations for the same locale.

`Locale::canonicalize` returns the canonical form of a tag. It also accepts Laravel's
underscore style, which is handy for values that come from `App::currentLocale()` or `lang/` folders:

```php
use Larasell\Larasell\Enums\Locale;

Locale::canonicalize('de-de'); // 'de-DE'
Locale::canonicalize('de_DE'); // 'de-DE'
Locale::canonicalize('ZH-hans'); // 'zh-Hans'
Locale::canonicalize('germ'); // throws InvalidArgumentException
```

Use it before `Locale::from` when the tag comes from user input:

```php
Locale::from(Locale::canonicalize($request->input('locale')));
```

### Lookup chain

`Locale::lookupChain` returns a tag followed by each shorter tag, which is the order Larasell
falls back in:

```php
use Larasell\Larasell\Enums\Locale;

Locale::lookupChain('zh-Hans-CN'); // ['zh-Hans-CN', 'zh-Hans', 'zh']
Locale::lookupChain('de'); // ['de']
```

## Configuration

`config/larasell/locale.php` defines which locales your shop serves. Publish it with the
rest of the Larasell config:

```bash
php artisan vendor:publish --tag=larasell-config
```

This copies the file to `config/larasell/locale.php` in your app. List your locales as `Locale`
cases and pick one of them as the default. A store that serves English, German and Canadian French,
with English as the default, looks like this:

```php
use Larasell\Larasell\Enums\Locale;

return [
    'supported' => [Locale::ENGLISH, Locale::GERMAN, Locale::FRENCH_CANADA],
    'default' => Locale::ENGLISH,
];
```

- **`supported`:** the locales your shop serves. Defaults to `[Locale::ENGLISH]`.
- **`default`:** the locale for visitors that match no supported locale, and the locale
  translations fall back to. It must be one of `supported`. Defaults to `Locale::ENGLISH`.

Both keys also accept tags as strings, which is useful when the value comes from the environment:

```php
'supported' => explode(',', env('SHOP_LOCALES', 'en,de')),
```

## Reading the configuration

`LocaleSettings`{href="https://raw.githubusercontent.com/Larasell-dev/larasell/refs/heads/main/src/Settings/LocaleSettings.php"}
reads and validates the config. Resolve it from the container:

```php
use Larasell\Larasell\Settings\LocaleSettings;

$locales = app(LocaleSettings::class);

$locales->supported(); // [Locale::ENGLISH, Locale::GERMAN, Locale::FRENCH_CANADA]
$locales->default(); // Locale::ENGLISH
```

Both methods throw an `InvalidArgumentException` when the config holds an unknown locale, when
`supported` is empty, or when `default` is not one of `supported`.

### Negotiating a locale

`negotiate` picks the supported locale that best matches an `Accept-Language` header:

```php
$locales->negotiate('de-CH,de;q=0.9,en;q=0.8'); // Locale::GERMAN
$locales->negotiate('fr'); // Locale::FRENCH_CANADA
$locales->negotiate('es'); // Locale::ENGLISH, the default
$locales->negotiate(null); // Locale::ENGLISH, the default
```

It goes through the languages in the header, highest quality first. Languages with the same
quality keep the order the browser sent them in. For each language, it tries:

1. The tag itself and each shorter tag. `de-CH` matches a supported `de`.
2. The first supported locale that starts with the tag. `fr` matches a supported `fr-CA`.

The first match wins. Regions are not matched against each other, so `de-CH` alone does not
match `de-DE`. Browsers usually send the bare language too, as in `de-CH,de;q=0.9`, and `de` then
matches `de-DE`. Languages with `q=0` are skipped, and a `*` stops the search and returns the default.

The [localization guide](https://larasell.dev/docs/localization.md#choosing-the-locale-for-each-request) shows how to use
it in a middleware.

## Translatable fields

These fields hold a `Translatable`{href="https://raw.githubusercontent.com/Larasell-dev/larasell/refs/heads/main/src/Translatable.php"}
with one text per locale:

- `Product`: `name`, `slug` and `description`. `description` can be `null`.
- `Category`: `name` and `slug`.
- `ProductAttribute`: `name`, like "Color".
- `ProductAttributeValue`: `name`, like "Red".
- `OrderItem`: `product_name`, a copy of every translation of the product name at checkout.

Read them with `get`, and write them with an array keyed by locale or a plain string for the
current locale:

```php
use Larasell\Larasell\Models\ProductAttribute;

$color = ProductAttribute::create([
    'slug' => 'color',
    'name' => ['en' => 'Color', 'de' => 'Farbe'],
]);

$color->name->get('de'); // 'Farbe'
```

Slugs of attributes and attribute values are plain strings. They identify a value in
`$product->variantFor(['color' => 'red'])`, so they're the same in every language.

`ProductVariant::name()` and `ProductVariant::options()` return strings in the current locale,
like "Klein / Schwarz". Order items keep `variant_name` and `variant_options` in the locale of the
checkout, which is the language the customer bought in.

## Fallback

When a translatable field has no translation for the requested locale, `get` returns the first
translation it finds in this order:

1. The requested locale and each shorter tag, from `Locale::lookupChain`.
2. The default locale from `larasell.locale.default`, and each shorter tag.
3. The first translation the field has.

```php
use Larasell\Larasell\Translatable;

config(['larasell.locale.default' => 'en']);

$name = new Translatable(['en' => 'Bicycle', 'de' => 'Fahrrad', 'zh-Hans' => '自行车']);

$name->get('de-AT'); // 'Fahrrad', from 'de'
$name->get('zh-Hans-CN'); // '自行车', from 'zh-Hans'
$name->get('fr'); // 'Bicycle', from the default
```

`get` without an argument uses `App::currentLocale()`.

## Where Larasell uses the current locale

Larasell reads `App::currentLocale()` in these places:

- `get` on translatable fields, like `$product->name->get()`.
- Finding products and categories by slug in `ProductDetailRoute`, `ProductDetailRequest` and
  `ProductListingRequest`. Slugs are matched in the current locale only. They don't fall back.
- Sorting product listings by name.
- `Price::format`, when you don't pass a locale.
