Lightweight translator for Nette Framework using NEON files. Translation directories can be declared automatically in Composer packages and in the root application, with an optional manual NEON configuration for special cases.
- PHP >= 8.3, < 9
- Nette Framework
- Composer 2.1+
composer require drago-ex/translatorThe files are copied into the project by drago-ex/project-tools,
as set in extra.drago-tools of this package's composer.json.
Register the DI extension in your NEON configuration:
extensions:
translator: Drago\Localization\DI\TranslatorExtensionNo translation directories are required in NEON for the normal case. The translator discovers them from Composer metadata.
Each Composer package can declare one directory containing its translation files in composer.json:
"extra": {
"drago-translator": {
"translation": "src/Commerce/Translate"
}
}The path is relative to the root of that Composer package. If the directory is not there (for example because the package resources were copied into the application), the same path is looked up in the project root.
For example, a package installed as:
vendor/drago-ex/commerce/
with:
"translation": "src/Commerce/Translate"provides translations from:
vendor/drago-ex/commerce/src/Commerce/Translate/
The package does not need to depend on drago-ex/translator. The metadata is simply available for applications that use this translator. Other translation systems can ignore it.
The root composer.json can declare the application's translation directory in exactly the same way:
"extra": {
"drago-translator": {
"translation": "app/Presentation/Sign/Translate"
}
}The path is then resolved relative to the project root.
This makes the setup automatic after installing a package. The application does not need to add a new NEON entry for every vendor package.
Translation sources are loaded in this order:
- Composer vendor packages.
- The root application's Composer translation directory.
- Translation directories configured manually in NEON.
Later sources override translations loaded earlier. This means an application can override a translation supplied by a vendor package.
For example, if both a package and the application define:
"Add to cart": "Přidat do košíku"the application's value wins.
Composer discovery is the recommended approach, but a manual fallback is available for special cases:
translator:
translateDirs:
- %appDir%/Special/TranslateMultiple directories are supported when needed:
translator:
translateDirs:
- %appDir%/First/Translate
- %appDir%/Second/TranslateThey are loaded after Composer-discovered translations, in the configured order. Later directories override earlier ones. A directory that does not exist throws an exception, so a mistyped path is not silently ignored.
There is no automatic recursive scan of the whole application directory. Every translation source must therefore be explicitly declared either in Composer metadata or in translateDirs.
Translation files must use the language code as their filename, for example:
cs.neon
en.neon
de.neon
Files like cs-CZ.neon are loaded too when cs is requested. The base file cs.neon is loaded first, the others follow alphabetically. The language code must look like cs or cs-CZ; anything else finds no files.
In production the list of found files is cached in the application cache. After adding or removing a
translation file on a production server, clear temp/cache. In debug mode the directories are searched on every request.
"Hello, world!": "Ahoj, světe!"
"Hello, %s!": "Ahoj, %s!"
"You have %d items in your cart.": "V košíku máte %d položek."Values must be strings; anything else (nested arrays, booleans) is ignored.
Translations can contain sprintf-style placeholders. Pass their values after the message key; the translator inserts them in order using PHP's vsprintf():
$translator->translate('Hello, %s!', 'Jane');
// Ahoj, Jane!
$translator->translate('You have %d items in your cart.', 3);
// V košíku máte 3 položek.The same works with the translator registered in Latte:
{_'Hello, %s!', $name}
{_'You have %d items in your cart.', $itemCount}Use a matching placeholder for each argument, in the same order. Common placeholders include %s for text, %d for an integer, and %.2f for a decimal number with two digits after the decimal point.
When no translation exists, the original message is used and its placeholders are formatted in the same way. If the placeholders do not match the arguments, the unformatted text is returned instead of throwing.
In debug mode the Tracy bar shows the current language, manually configured directories, loaded files and the messages that had no translation. Messages are reported only when the language has at least one translation file loaded, so a source-language file is not needed.
Add the TranslatorAdapter trait to your presenter:
use Drago\Localization\TranslatorAdapter;
class BasePresenter extends Presenter
{
use TranslatorAdapter;
}The trait provides:
- persistent language parameter (
$lang) - automatic translator initialization
- template integration
You can access the currently set language using:
$this->lang;To get the initialized translator for the current language:
$this->getTranslator();The translator is automatically registered in templates.
{_"Hello, world!"}
{$label|translate}To enable translations in forms, set the translator explicitly:
$form->setTranslator($this->getTranslator());To support language prefixes, configure your routes accordingly:
$router->addRoute('[<lang=en cs|en>/]<presenter>/<action>', 'Presenter:action');Give the lang parameter a default value in the route, as above. Without a language, the TranslatorAdapter
returns messages untranslated.
You can switch languages by passing the lang parameter:
<a n:href="this, lang => cs">Czech</a>
<a n:href="this, lang => en">English</a>The package provides a reusable Latte widget for language switching.
When project file copying is handled by drago-ex/project-tools, the widget is copied to:
app/Presentation/Accessory/Widget/@lang-switch.latte
Import the widget in your layout:
{import 'path/to/@lang-switch.latte'}Render language links:
{include lang-switch, lang: 'cs', name: 'Czech'}
<span class="small ps-1 pe-1 text-secondary">|</span>
{include lang-switch, lang: 'en', name: 'English'}The current language link automatically receives the current class.
Available options:
lang- target language code.name- visible translated label.class- optional class added to the link.tag- optional wrapper tag:li,div, orspan.tagClass- optional class added to the wrapper tag.
Use class when the link needs a custom class:
{include lang-switch, lang: 'cs', name: 'Czech', class: 'nav-link'}Use tag when the link must be wrapped, for example in a dropdown menu:
{include lang-switch, lang: 'cs', name: 'Czech', tag: 'li'}
{include lang-switch, lang: 'en', name: 'English', tag: 'li'}Use tagClass when the wrapper needs styling:
{include lang-switch, lang: 'cs', name: 'Czech', tag: 'li', tagClass: 'item-wrapper'}- Composer package translation directories are discovered from
extra.drago-translator.translation. - Each Composer package declares one translation directory.
- The root project can declare its own translation directory using the same metadata.
- Manual
translateDirsremain available for exceptional cases. - Translations are loaded lazily on first use.
- Missing keys return the original message.