Skip to content

marko/translation

Translation and i18n interface with placeholder replacement, pluralization, and fallback locale support --- so your application speaks every language your users need. This package provides the TranslatorInterface and TranslationLoaderInterface contracts for loading and resolving translated strings. It supports dot-notation keys, namespaced translations for packages, :placeholder replacement, and pluralization with labeled variants (zero, one, few, many, other). When a key is missing for the current locale, the translator falls back automatically.

This package defines contracts and core logic. Install an implementation package such as marko/translation-file to load translations from disk.

Terminal window
composer require marko/translation

Note: You typically install an implementation package (like marko/translation-file) which requires this automatically.

Inject TranslatorInterface and call get() with a dot-notation key:

use Marko\Translation\Contracts\TranslatorInterface;
class WelcomeController
{
public function __construct(
private readonly TranslatorInterface $translator,
) {}
public function greet(): string
{
return $this->translator->get('messages.welcome');
}
}

Pass an array of replacements for :placeholder tokens:

$this->translator->get(
'messages.hello',
['name' => 'Alice'],
);
// Translation string: "Hello, :name!" => "Hello, Alice!"

Use choice() with a count to select the correct plural form:

$this->translator->choice(
'messages.items',
$count,
['count' => (string) $count],
);

Plural strings use pipe-separated labeled forms:

lang/en/messages.php
return [
'items' => 'zero:No items|one:One item|other::count items',
];

Supported labels: zero, one, few (2—4), many (5+), other (default).

Package translations use the namespace::group.key format:

$this->translator->get('blog::posts.title');
$this->translator->setLocale('fr');
$greeting = $this->translator->get('messages.welcome');

The TranslationConfig class provides typed access to translation configuration values:

use Marko\Translation\Config\TranslationConfig;
class MyService
{
public function __construct(
private TranslationConfig $translationConfig,
) {}
public function setup(): void
{
$locale = $this->translationConfig->locale;
$fallback = $this->translationConfig->fallbackLocale;
}
}

Replace the Translator with a custom implementation via Preferences:

use Marko\Core\Attributes\Preference;
use Marko\Translation\Translator;
#[Preference(replaces: Translator::class)]
class MyTranslator extends Translator
{
public function get(
string $key,
array $replacements = [],
?string $locale = null,
): string {
// Custom behavior
return parent::get($key, $replacements, $locale);
}
}
use Marko\Translation\Contracts\TranslatorInterface;
public function get(string $key, array $replacements = [], ?string $locale = null): string;
public function choice(string $key, int $count, array $replacements = [], ?string $locale = null): string;
public function setLocale(string $locale): void;
public function getLocale(): string;

Both get() and choice() throw MissingTranslationException when a key cannot be resolved in the current or fallback locale.

use Marko\Translation\Contracts\TranslationLoaderInterface;
public function load(string $locale, string $group, ?string $namespace = null): array;
use Marko\Translation\Config\TranslationConfig;
public string $locale;
public string $fallbackLocale;
ExceptionDescription
TranslationExceptionBase exception for all translation errors --- includes getContext() and getSuggestion() methods
MissingTranslationExceptionThrown when a translation key is not found for the current or fallback locale