DI and actions
The package has two equivalent entry points. The Translatable facade is the shortest and the recommended default. The manager, RoundlyConsulting\Translatable\Support\TranslationManager, is the facade’s root: bound as a singleton and injected through the constructor, it exposes the identical API as an explicit dependency, with no static calls:
use RoundlyConsulting\Translatable\Support\TranslationManager;
final class UpdateTopicTranslations
{
public function __construct(private TranslationManager $translations) {}
public function handle(Topic $topic, array $input): void
{
$topic->setTranslations('name', $this->translations->fromInput($input['name'] ?? []));
}
}Both paths reach the same singleton:
use RoundlyConsulting\Translatable\Facades\Translatable;
use RoundlyConsulting\Translatable\Support\TranslationManager;
app(TranslationManager::class) === Translatable::getFacadeRoot(); // true — one singletonNo action classes
There are no action classes to compose into your own jobs or commands: the package does pure locale-map computation — no database write, queue, mail, event or HTTP call — and the manager serves it directly. Call the manager (or the facade) from your own actions instead.
Swapping the manager
TranslationManager is deliberately not final — it’s the package’s one designed extension seam. Extend it, override a method and swap it in: its sibling methods observe the override polymorphically, and facade calls and every HasTranslations model observe the subclass:
use RoundlyConsulting\Translatable\Facades\Translatable;
use RoundlyConsulting\Translatable\Support\TranslationManager;
Translatable::swap(new class extends TranslationManager
{
public function supported(): array
{
return ['en', 'sk', 'de'];
}
});
Translatable::supported(); // ['en', 'sk', 'de']
Translatable::fromInput(['de' => 'Investieren']); // ['de' => 'Investieren']
$topic->missingLocales('name'); // now also reports 'de'Internals
Support\FallbackResolver and Support\LocaleGuard are @internal building blocks — call Translatable::resolve(), ensureLocale() or isSupported() instead. The locale-key format check stays a fixed floor on every write path and scope; it never depends on a method you override.
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.