Skip to content

Internationalisation ​

Slab est multilingue et multidevise. La traduction se joue sur trois plans distincts — le contenu (sur les modèles), l'interface (fichiers de langue) et la navigation (routes localisées) — tandis que langues, devises et zones sont des données gérées en base.

Langues, devises et zones en base ​

Language (locale, name, is_active), Currency (iso, name, exchange_rate, is_active) et Area (iso, name) sont des modèles persistés et activables. Point clé : la liste des locales supportées du site n'est pas figée en config — LanguageServiceProvider la (re)construit au boot à partir des langues actives en base, puis la passe à laravel-localization. Activer une langue dans le back-office l'ajoute donc au site ; LanguageObserver matérialise au passage son dossier lang/<locale> en déléguant à TranslationService (création, renommage, suppression). La langue de repli reste app.fallback_locale, la devise par défaut shop.default_currency_iso.

Interroger les langues (LanguageService) ​

Le code qui a besoin des langues ne requête pas le modèle : il passe par le contrat Slab\Framework\Core\Language\Contracts\LanguageService.

php
use Slab\Framework\Core\Language\Contracts\LanguageService;

$languages = app(LanguageService::class)->getActiveLanguages();   // Collection<int, Language>
$mandatory = app(LanguageService::class)->getMandatoryLanguage(); // ?Language

getActiveLanguages() renvoie les langues actives, la locale de repli triée en premier, puis par name. getMandatoryLanguage() renvoie la langue active dont le locale vaut app.fallback_locale (null si elle n'existe pas) : c'est la langue obligatoire des champs traduisibles du back-office (TranslatableField, son LanguageSelector et son Wrapper s'en servent comme langue présélectionnée). L'implémentation par défaut est DefaultLanguageService, liée par bind — remplacez-la pour changer la source, l'ordre ou la langue obligatoire.

WARNING

DefaultLanguageService mémoïse ses résultats dans des propriétés statiques : la liste est calculée une fois par process. Dans un worker de longue durée, une langue activée entre-temps n'est pas vue avant le redémarrage du process.

Traduire le contenu (modèles) ​

Le contenu éditorial est porté par les modèles via le trait HasTranslations (spatie/laravel-translatable), stocké en colonnes JSON : Product, Page et Category traduisent slug + name ; la plupart des autres (Attribute, Tax, OrderStatus…) traduisent name. La lecture suit la locale courante ; getTranslations('name') donne toutes les langues (ce que consomment les éditeurs du BO). Pour rendre votre propre modèle multilingue, voir modèles.

Traduire l'interface (fichiers de langue) ​

Les libellés, e-mails et messages vivent dans des fichiers de langue sous le namespace de chaque package : framework:: et frontend:: les déclarent via loadTranslationsFrom ; le skeleton porte les groupes globaux (auth, validation, passwords, et les chaînes JSON). L'éditeur de traductions du back-office découvre ces namespaces automatiquement — il interroge le loader du translator Laravel, donc tout package qui appelle loadTranslationsFrom apparaît sans réglage. Il permet l'édition en ligne et l'export/import JSON par langue.

Concrètement, l'éditeur agrège deux familles de groupes :

  • les groupes globaux, un par fichier lang/<repli>/*.php (messages Laravel, surcharges projet) ;
  • les groupes namespacés, un par fichier <paquet>/lang/<repli>/*.php pour chaque namespace enregistré dans le loader, affichés sous la clé namespace::groupe (framework::back, frontend::routes…).

S'y ajoutent les chaînes libres du groupe general, lues dans lang/<locale>.json. Les clés à niveaux sont aplaties (a.b.c) et triées A→Z sans casse ni accent pour l'affichage.

getContents() renvoie cette agrégation pour toutes les langues en base, indexée par locale, les groupes à clés étant rangés sous php :

php
[
    'fr' => [
        'general' => ['Chaîne source' => 'Traduction'],           // lang/fr.json
        'php' => [
            'validation' => ['required' => 'Le champ …'],          // lang/fr/validation.php
            'framework::back' => ['menu.orders' => 'Commandes'],   // <framework>/lang/fr/back.php
        ],
    ],
]

setContents() attend la valeur d'une seule locale dans cette même forme (general + php).

Où l'édition écrit

L'enregistrement ne touche jamais les fichiers d'un package : il écrit à l'emplacement de surcharge Laravel. Groupe global → lang/<locale>/<groupe>.php ; groupe namespacé → lang/vendor/<namespace>/<locale>/<groupe>.php ; chaînes libres → lang/<locale>.json. Les valeurs vides sont ignorées (pas de clé écrasée par du vide).

TIP

Sans loadTranslationsFrom, les traductions de votre package restent invisibles à l'éditeur BO.

Le contrat TranslationService ​

Slab\Framework\Core\Translation\Contracts\TranslationService porte toute la manipulation des fichiers de langue :

php
public function createLocaleDirectory(string $locale): void;
public function moveLocaleDirectory(string $from, string $to): void;
public function deleteLocaleDirectory(string $locale): void;

/** @return array<string, array<string, mixed>> */
public function getContents(): array;

/** @param array<string, mixed> $contents */
public function setContents(string $locale, array $contents): void;

public function export(Language $language): StreamedResponse;
public function import(Language $language, UploadedFile $translations_file): void;

Trois comportements à connaître dans DefaultTranslationService :

  • la locale de repli n'est jamais échafaudée — createLocaleDirectory() sort immédiatement si $locale === config('app.fallback_locale'), pour ne pas écraser la source de vérité (créer une Language pour cette locale, en seed ou en factory, est donc sans effet sur les fichiers) ;
  • l'échafaudage est idempotent — les fichiers du repli sont copiés vers la nouvelle locale sans jamais écraser un fichier déjà présent, php.json étant initialisé vide ;
  • tout passe par le disque nommé lang (config/filesystems.php), ce qui permet de l'isoler en test avec Storage::fake('lang') — sauf moveLocaleDirectory(), qui exécute un mv sur lang_path().

L'export() produit un téléchargement JSON dont les clés reprennent cette structure : <locale>.general.<chaîne de repli> pour les chaînes libres, <locale>.php.<groupe>.<clé> pour les groupes à clés — avec pour chaque entrée la ligne de repli et celle de la langue exportée (vide si non traduite). L'import() ne retient que les clés préfixées par la locale ciblée, les regroupe par famille (general / php), puis repasse par setContents(). L'implémentation est liée par bind : remplacez-la pour changer le stockage ou le format.

Routes localisées ​

Le front est servi sous un préfixe de locale (laravel-localization), avec les middlewares localeSessionRedirect, localizationRedirect, localeViewPath et localize. Les segments d'URL eux-mêmes sont traduits : les routes passent par LaravelLocalization::transRoute('frontend::routes.…'), résolu depuis <paquet>/lang/<locale>/routes.php (surchargeable en lang/vendor/frontend/<locale>/routes.php). Les verbes de Route::resource sont eux aussi traduits, depuis framework::routes.verbs.*. La locale par défaut n'apparaît pas dans l'URL (hideDefaultLocaleInURL).

Devise d'affichage courante ​

La devise d'affichage est résolue par CurrentCurrencyResolver : un cookie (shop.cookies.currency) sinon la devise par défaut. Le binding est scopé à la requête et substituable ; un changement émet CurrencyChanged, dont un listener persiste la préférence sur le User/Guest. Le calcul en devise relève du pipeline de prix, pas de l'affichage.

Par où l'étendre ​

Je veux…Point d'extensionMécanique
traduire les libellés de mon packageloadTranslationsFrom(__DIR__.'/../lang', 'mon-pkg')namespaces de lang
surcharger les traductions d'un packagelang/vendor/<namespace>/<locale>/<groupe>.phpsurcharge Laravel (ce qu'écrit le BO)
rendre un modèle multilinguetrait HasTranslations (colonnes JSON)modèles
ajouter une langue au siteactiver une ligne dans Langues (BO) → table languages—
changer la sélection des langues (source, ordre, langue obligatoire)LanguageService (bind)conteneur
changer le stockage ou le format des traductionsTranslationService (bind)conteneur
changer la devise d'affichage couranteCurrentCurrencyResolver (bind)résolveurs

Voir aussi ​