Translatable descriptions
The description on both Role and Permission is a real translatable attribute, backed by translatable-for-laravel. It is stored as a per-locale JSON map (jsonb) and resolves to a plain string for the current app locale. There is nothing to wire — the trait is applied for you:
use Illuminate\Support\Facades\App;
$permission->update(['description' => ['en' => 'View users', 'sk' => 'Zobraziť používateľov']]);
App::setLocale('sk');
$permission->description; // "Zobraziť používateľov" (current locale)
App::setLocale('en');
$permission->description; // "View users"
$permission->getTranslations('description'); // ['en' => 'View users', 'sk' => 'Zobraziť …']
$permission->getTranslation('description', 'sk'); // "Zobraziť používateľov"Write a per-locale array to set several locales at once, or a bare string to set only the current locale:
App::setLocale('sk');
$role->description = 'Správca'; // a bare string writes the current locale only
$role->save();
$role->getTranslations('description'); // ['sk' => 'Správca']
$role->setTranslation('description', 'en', 'Administrator')->save();toArray(), toJson() and API Resources emit the resolved locale string, matching property access — not the raw JSON map.
Fallback modes
When the current locale has no value, permissions.description_fallback (env PERMISSIONS_DESCRIPTION_FALLBACK) decides how far the read reaches:
| Mode | Chain | Notes |
|---|---|---|
Fallback | current locale → app fallback_locale → null | The default. A description you have not translated for a locale never surfaces content from an unrelated language. |
None | current locale → null | Exact locale only. |
Any | current locale → app fallback_locale → first available locale | Descriptions never render blank, but an untranslated value can surface in another locale. Opt in deliberately. |
Descriptions are developer- and admin-facing labels, not personal data, so Any is a reasonable opt-in — it is simply not the default, because silently reaching into an unrelated locale is a surprise:
# Opt into first-available fallback (descriptions never render blank):
PERMISSIONS_DESCRIPTION_FALLBACK=anyTo set the mode for one model, declare $translatableFallbackMode on your own subclass and register it in permissions.models.role or permissions.models.permission. The config fills the property only when it is null, so an explicit per-model mode always wins over description_fallback:
use RoundlyConsulting\Permissions\Models\Role;
use RoundlyConsulting\Translatable\Enums\FallbackMode;
final class AppRole extends Role
{
protected ?FallbackMode $translatableFallbackMode = FallbackMode::None;
}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 cryptoBy 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.