NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Translatable for Laravel

The Translatable facade

Everything that isn’t per-model state lives on the Translatable facade, over the injectable TranslationManager singleton. The HasTranslations trait reads its locale set, fallback settings, locale-key validation and map resolution through the same manager, so the facade and your models always agree:

use RoundlyConsulting\Translatable\DataTransferObjects\TranslationChanges;
use RoundlyConsulting\Translatable\DataTransferObjects\TranslationSearch;
use RoundlyConsulting\Translatable\Enums\FallbackMode;
use RoundlyConsulting\Translatable\Facades\Translatable;

// Locales
Translatable::supported();                        // ['en', 'sk'] — from the bound SupportedLocales
Translatable::isSupported('sk');                  // true — exact, case-sensitive
Translatable::currentLocale();                    // app()->getLocale()
Translatable::ensureLocale($locale);              // returns it, or throws InvalidLocaleException (malformed)
Translatable::ensureLocale($locale, strict: true); // … also throws when it is not supported

// Fallback chain
Translatable::fallbackMode();                     // FallbackMode, from config
Translatable::fallbackLocale();                   // 'en'
Translatable::resolve(['en' => 'Investing', 'sk' => 'Investovanie']);        // current locale + configured fallback
Translatable::resolve($map, 'de', FallbackMode::Fallback, fallbackLocale: 'sk');

// Admin input
Translatable::fromInput($request->input('name')); // clean locale map
Translatable::rules('name', required: true, each: ['max:120']);
Translatable::filledRule('name');                 // just the "at least one locale" rule
Translatable::apply($topic, TranslationChanges::make(['name' => ['sk' => 'Sporenie']])); // PATCH-merge

// Queries and locale scoping
Translatable::search(Topic::query(), new TranslationSearch(fields: ['name'], term: 'invest'));
Translatable::usingLocale('sk', fn (): string => $topic->name);

The facade (RoundlyConsulting\Translatable\Facades\Translatable) shares its short name with the model contract (RoundlyConsulting\Translatable\Contracts\Translatable) — alias one of them in a file that needs both. Per-model reads, writes, status and scopes stay on the model itself (see Reading and writing).

Method reference

MethodReturnsPurpose
supported()list<string>Supported locales, from the bound SupportedLocales.
isSupported($locale)boolExact, case-sensitive membership in supported().
currentLocale()stringThe app locale — app()->getLocale().
ensureLocale($locale, $strict = false)stringReturns the key or throws InvalidLocaleException — always when malformed, and when it’s outside supported() with strict.
resolve($map, $locale = null, $mode = null, $fallbackLocale = null)?stringResolve a raw locale map through the fallback chain; null arguments default to the current locale, fallbackMode() and fallbackLocale(). Blank values never win and it never throws.
fallbackMode()FallbackModetranslatable.fallback as an enum; an unknown value resolves to Any.
fallbackLocale()stringtranslatable.fallback_locale, or '' when it is not set (null or blank) — no fallback locale.
fromInput($input)array<string, string>A bare string becomes the current locale; blank, non-string and unsupported locales are dropped — a bare string’s locale included.
rules($field, $required, $each = [])arrayValidation rules for a locale-map field, plus $each on every locale.
filledRule($field)list<Closure>Only the “at least one locale is filled” rule.
apply($model, $changes)voidPATCH-merge a TranslationChanges set — only supplied locales change, in memory until you save().
search($query, $search)BuilderPer-locale, case-insensitive, literal-substring search across fields; no fields or no supported locale matches no rows.
usingLocale($locale, $callback)mixedRun a closure under a temporary app locale, always restored.

resolve() is covered under Fallback modes, ensureLocale() and the admin helpers under Input and validation, search() under Querying and search.

Reading in another locale

usingLocale() runs a closure with the app locale temporarily switched and always restores it afterwards — even on an exception. Use it to read a model in another locale without mutating global state, e.g. in queued renders or sitemaps:

use RoundlyConsulting\Translatable\Facades\Translatable;

$sk = Translatable::usingLocale('sk', fn (): string => $topic->name); // 'Investovanie'

// Every locale's resolved name — e.g. for a sitemap or a queued export:
$names = collect(Translatable::supported())->mapWithKeys(
    fn (string $locale): array => [$locale => Translatable::usingLocale($locale, fn (): ?string => $topic->name)],
);

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.