Locale
Larasell writes every locale as a BCP 47 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
deoragq. - Script: 4 letters with the first one uppercase, like
HansorLatn. Optional. - Region: 2 uppercase letters, like
DEorCH. 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 enum lists
every locale Larasell knows. Each case's value is its tag:
1use Larasell\Larasell\Enums\Locale;
2
3Locale::GERMAN->value; // 'de'
4Locale::GERMAN_GERMANY->value; // 'de-DE'
5Locale::CHINESE_SIMPLIFIED->value; // 'zh-Hans'
6Locale::from('es-MX'); // Locale::SPANISH_MEXICOThe enum lists every locale that exists, not the locales your shop supports. Use the locale config 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:
1use Larasell\Larasell\Enums\Locale;
2
3Locale::canonicalize('de-de'); // 'de-DE'
4Locale::canonicalize('de_DE'); // 'de-DE'
5Locale::canonicalize('ZH-hans'); // 'zh-Hans'
6Locale::canonicalize('germ'); // throws InvalidArgumentExceptionUse it before Locale::from when the tag comes from user input:
1Locale::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:
1use Larasell\Larasell\Enums\Locale;
2
3Locale::lookupChain('zh-Hans-CN'); // ['zh-Hans-CN', 'zh-Hans', 'zh']
4Locale::lookupChain('de'); // ['de']Configuration
config/larasell/locale.php defines which locales your shop serves. Publish it with the
rest of the Larasell config:
1php artisan vendor:publish --tag=larasell-configThis 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:
1use Larasell\Larasell\Enums\Locale;
2
3return [
4 'supported' => [Locale::ENGLISH, Locale::GERMAN, Locale::FRENCH_CANADA],
5 'default' => Locale::ENGLISH,
6];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 ofsupported. Defaults toLocale::ENGLISH.
Both keys also accept tags as strings, which is useful when the value comes from the environment:
1'supported' => explode(',', env('SHOP_LOCALES', 'en,de')),Reading the configuration
LocaleSettings
reads and validates the config. Resolve it from the container:
1use Larasell\Larasell\Settings\LocaleSettings;
2
3$locales = app(LocaleSettings::class);
4
5$locales->supported(); // [Locale::ENGLISH, Locale::GERMAN, Locale::FRENCH_CANADA]
6$locales->default(); // Locale::ENGLISHBoth 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:
1$locales->negotiate('de-CH,de;q=0.9,en;q=0.8'); // Locale::GERMAN
2$locales->negotiate('fr'); // Locale::FRENCH_CANADA
3$locales->negotiate('es'); // Locale::ENGLISH, the default
4$locales->negotiate(null); // Locale::ENGLISH, the defaultIt 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:
- The tag itself and each shorter tag.
de-CHmatches a supportedde. - The first supported locale that starts with the tag.
frmatches a supportedfr-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 shows how to use it in a middleware.
Translatable fields
These fields hold a Translatable
with one text per locale:
Product:name,sluganddescription.descriptioncan benull.Category:nameandslug.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:
1use Larasell\Larasell\Models\ProductAttribute;
2
3$color = ProductAttribute::create([
4 'slug' => 'color',
5 'name' => ['en' => 'Color', 'de' => 'Farbe'],
6]);
7
8$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:
- The requested locale and each shorter tag, from
Locale::lookupChain. - The default locale from
larasell.locale.default, and each shorter tag. - The first translation the field has.
1use Larasell\Larasell\Translatable;
2
3config(['larasell.locale.default' => 'en']);
4
5$name = new Translatable(['en' => 'Bicycle', 'de' => 'Fahrrad', 'zh-Hans' => '自行车']);
6
7$name->get('de-AT'); // 'Fahrrad', from 'de'
8$name->get('zh-Hans-CN'); // '自行车', from 'zh-Hans'
9$name->get('fr'); // 'Bicycle', from the defaultget without an argument uses App::currentLocale().
Where Larasell uses the current locale
Larasell reads App::currentLocale() in these places:
geton translatable fields, like$product->name->get().- Finding products and categories by slug in
ProductDetailRoute,ProductDetailRequestandProductListingRequest. 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.