avris / localisator
A neat tool to localise messages
Requires
- avris/bag: ^4.0
- avris/container: ^1.0
- avris/dispatcher: ^1.0
Requires (Dev)
- avris/micrus: v4.0.x-dev
- phpunit/phpunit: ^6.5
- squizlabs/php_codesniffer: ^3.2
- symfony/var-dumper: ^4.0
- twig/twig: ^2.4
Suggests
- avris/micrus: Web framework that this library was originally created for
- mustangostang/spyc: Parses YAML files for translations
- twig/twig: Localisator adds a twig filter for localisation
This package is auto-updated.
Last update: 2024-12-07 05:42:38 UTC
README
A neat tool to localise messages.
Localisator uses three elements to translate your messages:
- LocaleOrder -- decides which language(s) should be used,
- Providers -- fetches a translation for a specific key from a source (file, database, cache, ...),
- Transformers -- modifies the translation with more advanced logic (pluralisation, fallback, nesting, ...).
Installation
composer require avris/localisator
Usage
The library offers a simple LocalisatorBuilder
, if you just want to use the default configuration.
With it, the usage is as simple as this:
$locale = $_SESSION['_locale'] ?? 'en';
$dirs = [
__DIR__ . '/translations',
];
$localisator = (new LocalisatorBuilder())
->registerExtension(new LocalisatorExtension($locale, $dirs))
->build(LocalisatorInterface::class);
If the translations
dir you can define your translations, for instance:
app.en.yml
:
homepage:
title: My Website
hello: Hello!
post:
list: List of posts
count: <> One post|%count% posts
app.pl.yml
:
homepage:
title: Moja strona
hello: Witaj!
post:
list: Lista postów
count: <polishDeclination> Post|%count% posty|%count% postów
Then whenever you want to translate stuff, just use:
$localisator->get('post.count', ['count' => 3]); // Returns "3 posts" for "en" or "3 posty" for "pl"
Locales
Locale's identifier is: the ISO 639-1 language code,
then an underscore (_
), then the ISO 3166-1 alpha-2 country code.
Or it might be the language code alone.
Locale with a country is extending/specifying the locale with just the language code. For instance you might have
en
, en_GB
and en_GB
. The first file will contain all words and phrases shared by both British and American English,
while en_GB
could look like this:
color: Colour
dateFormat: d/m/Y
currency: £
and en_US
like this:
color: Color
dateFormat: m/d/Y
currency: $
Namespaces
You can group translations into namespaces, app
being the default.
For instance if you want to keep your form validation messages in one place, put them into validator.*.yml
files
and access those translations with the $translator->get('validator:minLength', ['min' => 5])
notation.
Helpers
Localisation is a thing that's really widely used across any project
and virtually every part of your application might want to translate some strings.
If you don't want to worry about injecting Localisator
into all those places,
you might want to make an exception from pure DI just for the localisation.
It's like using new DateTime('tomorrow')
instead of some hypothetical $this->timeManager->createDateTime('tomorrow')
.
To set it up you just need to call this method once at the beginning of your script:
LocalisedString::setLocalisator($localisator);
And then whenever you need something translated, just create an instance of LocalisedString
.
When casted to a string, it will get translated:
echo new LocalisedString('post.count', ['count' => 5]); // echoes "5 posts"
Or even shorter with a helper:
echo l('post.count', ['count' => 5]); // echoes "5 posts"
Twig
If you register the Avris\Localisator\LocalisatorTwig
extension, you can use the Localisator either as a function or a filter:
{{ 'entity:Post.create.success'|l({title: post.title}) }}
{{ l('entity:Post.create.success', {title: post.title}) }}
Configuration
Providers
TranslationProviders are retrieving data from some source (like file or database) by namespace, key and locale
and return a translated string (or null
if not found).
DirsTranslationProvider
reads the translations from all the files in given directories that can be read using implementations ofFileReader
(so far Yaml and PHP files supported),ModuleDirTranslationProvider
finds all the Micrus modules that have atranslations
directory and loads them,CacheTranslationProvider
wraps another provider and caches its translations using any PSR-6 cache pool; it also provides a function to warm up the cache.
Transformers
Transformers take the translated string (or null
if not found) and the provided replacements, and do operations on them.
They are executed using Avris Dispatcher (event translationTransform
),
which takes care of the order of transformations (using listeners priority).
The built-in providers are, in order of execution:
FallbackToPatternTransformer
(disabled): If the translation is not found, but the key matches a regexp pattern, returns the first match from the pattern. For instance forentity => ['^(.*)\.singular$']
, when you want to translateentity:Post.singular
but it doesn't exist, it would fall back to "Post".FallbackToWordTransformer
(disabled): If the translation is not found, falls back to showing the input key.NestedTransformer
(disabled): if the translation forlike
is "I like [[food.pizza.plural]] and [[food.banana.plural]]", forfood.pizza.plural
it's "Pizzas" and forfood.banana.plural
is "Bananas", the result will be "I like Pizzas and Bananas"SelectorsTransformer
: select one of the versions of the translation based on the data in the replacements arrayCountVersion
:<> a dog|%count% dogs
will select "a dog" ifcount
is equal 1, or "%count% dogs" if it's anything else. You can also specify more complex ranges:<> {0} no dogs|{1} a dog|{2-4} couple dogs|{5-} many dogs
.PolishDeclination
(disabled):<polishDeclination> pies|%count% psy|%count% psów
will select a correct version according to the rules of Polish declination (like "pies", "2 psy", "5 psów").
ReplacementsTransformer
: Replaces parts of the translated string with values from the replacements array, for instancel('dogs.count', ['count' => 5])
->%count% dogs
->5 dogs
.
Framework integration
Micrus
Although Localisator can now be used independently from it, it was originally written as a part of the Micrus framework.
The integration with it is really simple. In your App\App:registerModules
register the Localisator module:
yield new \Avris\Localisator\LocalisatorModule;
and in your config/services.yml
register one of the handlers:
Avris\Localisator\Handler\SessionLocaleHandler: []
# or:
Avris\Localisator\Handler\UrlLocaleHandler: []
The first one will store user's locale in the session, the other in the URI (e.g. /de/login
).
In config/localisation.yml
specify which languages are supported and what's the fallback locale,
for instance when a new user (with no chosen locale stored in the session) comes to the website
and none of the languages in their Accepted-Language
header is supported by you.
supported:
en_GB: English (GB)
en_US: English (USA)
pl: Polski
de: Deutsch
fallback: en_GB
Micrus will automatically load the translations from all the modules that have a translations
directory.
It will also try to figure out the best locale for a given request - from the URI, session, Accept-Language
header and the fallback locale.
In Twig, you can access the current locale
<html lang="{{ currentLocale().language }}">
and generate the links to switch it (SessionLocaleHandler
or UrlLocaleHandler
will take care of them)
{% for code, name in locales %}
<a href="{{ route('changeLocale', { locale: code }) }}">
{{ name }}
</a>
{% endfor %}
The l()
helper is available throughout the application.
Symfony
Symfony has it's own, great tools for localisation. But if you want to use Localisator there anyway (for instance if a library need it, like TimeDiff does), you can use the following container configuration to set it up quickly:
For the LocalisatorBuilder
:
Avris\Localisator\LocalisatorBuilder:
calls:
- [registerExtension, ['@Avris\Localisator\LocalisatorExtension']]
Avris\Localisator\LocalisatorExtension:
arguments:
$locale: '%locale%'
Avris\Localisator\LocalisatorInterface:
factory: ['@Avris\Localisator\LocalisatorBuilder', build]
arguments: ['Avris\Localisator\LocalisatorInterface']
Avris\Localisator\LocalisatorTwig: ~
Or for the full config:
Avris\Localisator\LocalisatorInterface: '@Avris\Localisator\Localisator'
Avris\Localisator\Localisator: ~
Avris\Localisator\LocalisatorTwig: ~
Avris\Localisator\Provider\TranslationProviderInterface: '@Avris\Localisator\Provider\CacheTranslationProvider'
Avris\Localisator\Provider\CacheTranslationProvider:
arguments:
$provider: '@Avris\Localisator\Provider\DirsTranslationProvider'
Avris\Localisator\Provider\DirsTranslationProvider:
arguments:
$dirs:
- '%kernel.project_dir%/vendor/avris/time-diff/translations'
$fileReaders:
- '@Avris\Localisator\Provider\FileReader\YamlFileReader'
- '@Avris\Localisator\Provider\FileReader\PhpFileReader'
Avris\Localisator\Provider\FileReader\YamlFileReader: ~
Avris\Localisator\Provider\FileReader\PhpFileReader: ~
Avris\Localisator\Order\LocaleOrderProviderInterface: '@Avris\Localisator\Order\SimpleLocaleOrderProvider'
Avris\Localisator\Order\SimpleLocaleOrderProvider:
arguments:
$locale: '%locale%'
Avris\Dispatcher\EventDispatcherInterface: '@Avris\Dispatcher\EventDispatcher'
Avris\Dispatcher\EventDispatcher:
calls:
- [registerSubscriber, ['@Avris\Localisator\Transformer\ReplacementsTransformer']]
- [registerSubscriber, ['@Avris\Localisator\Transformer\SelectorsTransformer']]
# - [registerSubscriber, ['@Avris\Localisator\Transformer\NestedTransformer']]
# - [registerSubscriber, ['@Avris\Localisator\Transformer\FallbackToWordTransformer']]
# - [registerSubscriber, ['@Avris\Localisator\Transformer\FallbackToPatternTransformer']]
Avris\Localisator\Transformer\ReplacementsTransformer: ~
Avris\Localisator\Transformer\SelectorsTransformer:
arguments:
$translationSelectors:
- '@Avris\Localisator\Transformer\Selector\CountVersion'
- '@Avris\Localisator\Transformer\Selector\PolishDeclination'
Avris\Localisator\Transformer\Selector\CountVersion: ~
Avris\Localisator\Transformer\Selector\PolishDeclination: ~
Linked projects
Check out also Stringer for a set of useful helpers and Polonisator for support of the Polish language.
Copyright
- Author: Andre Prusinowski (Avris.it)
- Licence: MIT