---
title: "Labels from translation files — Enums for Laravel | Roundly"
description: "#[TranslatedLabels] reads every label from a translation group such as lang/<locale>/enums.php, keeps the headline as fallback and lists missing lines."
url: https://roundly-consulting.com/open-source/docs/enums-for-laravel/label-groups
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Enums for Laravel](https://roundly-consulting.com/open-source/docs/enums-for-laravel.md)

# Labels from translation files

Available from 1.1.0. Put the #\[TranslatedLabels\] attribute on an enum that uses Helpers, and each label is read from a translation group instead of the headline — the layout most apps already keep in lang/<locale>/enums.php:

```php
use RoundlyConsulting\Enums\Attributes\TranslatedLabels;
use RoundlyConsulting\Enums\Helpers;

#[TranslatedLabels]                 // keys: enums.order_status.<value>
enum OrderStatus: string
{
    use Helpers;

    case Pending = 'pending';
    case Shipped = 'shipped';
}
```

```php
// lang/sk/enums.php
return [
    'order_status' => [
        'pending' => 'Čaká na platbu',
        'shipped' => 'Na ceste',
    ],
];
```

```php
app()->setLocale('sk');

OrderStatus::Pending->label();      // 'Čaká na platbu'
OrderStatus::toArray();             // ['pending' => 'Čaká na platbu', 'shipped' => 'Na ceste']
OrderStatus::options();             // EnumOption DTOs with the same labels, shape unchanged
OrderStatus::fromLabel('Na ceste'); // OrderStatus::Shipped
```

Every list built on labels — labels(), toOptions(), toArray(), options(), presentations(), fromLabel() and tryFromLabel() — translates with it, and options() keeps its shape. Enums without the attribute behave exactly as before.

## The group

```php
#[TranslatedLabels]                                // enums.order_status.<value>
#[TranslatedLabels('enums.checkout_status')]       // an explicit group
#[TranslatedLabels('enums.http_method')]           // HTTPMethod would snake to enums.h_t_t_p_method
#[TranslatedLabels('billing::enums.order_status')] // a package's own translation files
```

- With no argument the group is enums. plus the snake\_case class name: OrderStatus → enums.order\_status, ToolAuth → enums.tool\_auth.
- An acronym class name snakes letter by letter (HTTPMethod → enums.h\_t\_t\_p\_method), so pass such a group explicitly.
- A package labels its own enums from its own files with a namespaced group: 'billing::enums.order\_status' reads resources/lang/<locale>/enums.php registered under the billing translation namespace.
- A blank group, or one that starts or ends with whitespace, . or :, can only ever miss, so it throws EnumException::invalidLabelGroup() on first use rather than falling back quietly.
- The attribute goes on the enum itself and does nothing on an enum without the Helpers trait.

## Keys and resolution order

The key is <group>.<value>: the backed value for string and int enums (enums.priority.5, enums.priority.-1) and the case name for pure enums (enums.visibility.Public). For each label, readable() tries:

- First, the grouped key in the current locale, then in fallback\_locale — Laravel’s translator does both. A line wins.
- Otherwise, the headline behaviour, unchanged: the headline (Pending), looked up as a JSON or group line, then the raw headline.

So adding the attribute never breaks an enum whose keys aren’t filled in yet: missing cases keep their headline labels while you add the lines. A key that resolves to a nested array instead of a line counts as a miss. A missing-key handler registered with Lang::handleMissingKeysUsing() fires on a grouped miss just as it does for \_\_(), and its return value becomes the label.

Values holding a . can never match: the translator reads the dot as nesting, so the key enums.release.v1.0 looks for \['release'\]\['v1'\]\['0'\], never the line keyed 'v1.0'. Give such enums a readable() override; untranslated() reports them.

## Pinning every label translated

untranslated(?string $locale = null) returns the cases whose grouped key has no line in exactly that locale (default: the current one), in declaration order — made for a test that fails the moment a case ships untranslated:

```php
it('translates every order status', function (string $locale) {
    expect(OrderStatus::untranslated($locale))->toBeEmpty();
})->with(['en', 'sk']);
```

- The check is strict per locale: a line only in fallback\_locale is still reported, because users of that locale would see the fallback text.
- With a translator other than Laravel’s (one without hasForLocale()), it falls back to get(), so a line in that translator’s fallback locale counts.
- A key that resolves to a nested array, and a value holding a ., are reported, since readable() can’t use them either.
- On an enum without the attribute it throws EnumException::labelsNotTranslated().

The attribute is read once per enum class and cached for the life of the process. An attribute can’t change at runtime, so this is safe under Octane.

## 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](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## 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.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/enums-for-laravel.md)
