slendium / localization
A Slendium recommendation on handling localization across libraries and frameworks.
Requires
- php: ^8.4
- ext-intl: *
Requires (Dev)
- phpstan/phpstan: ^2.1
README
An implementation-agnostic standard for localization in PHP. It is intended to facilitate the exchange of localizable information between PHP frameworks and libraries. The primary motivation for creating this standard is to provide common interfaces for other Slendium libraries, so it will be biased towards these use-cases. Suggestions are always welcome.
Requires PHP 8.4+ and assumes the same standards that PHP uses natively (RFC 4646, CLDR). It also assumes use of a static analyzer such as PHPStan.
The problem
There are different ways of doing localization in PHP (array dictionaries, gettext, framework-specific implementations, etc.). Sometimes end-users can enter their own translations on a per-object basis - for example, using a database table. Code that is responsible for producing a locale-specific output (such as an HTML page, a JSON object, a PDF, an e-mail, etc.), or other libraries that want to work with localizable objects, should not become dependent on the underlying dictionary implementation of such objects. Common interfaces prevent this dependency.
Example of a basic use-case
Consider the following application-specific object which has a $name
property that should change based on the current locale.
class Product { /** @var Localizable<non-empty-string> */ public Localizable $name; }
Now consider the following class for generating an outgoing e-mail. Imagine EmailGenerator
is a template class provided by a framework which returns an intermediate representation of the structure of the e-mail to be sent. The framework will later convert this structure to the actual text to be sent, using a localization algorithm of its own choosing. The implementor of this API does not have to worry about the specifics of localizing this e-mail, but can focus instead on its general structure. From their perspective it does not matter whether the text comes from a static dictionary, a database object or even an external source.
class OrderEmailGenerator extends EmailGenerator { public function __construct( /** @var list<Product> */ private array $products ) { } #[Override] public function generateBody(): iterable { yield $this->getSalutation($this->getCustomerName()); // combines localizable information with a customer name yield '<br><br>'; yield $this->translate('thanks_for_ordering'); // a Localizable from a static dictionary yield from [ '<table><tr><th>', $this->translate('product_category'), '</th></tr>' ]; foreach ($this->products as $product) { yield from [ '<tr><td>', $product->name, '</td></tr>' ]; } yield '</table>'; yield $this->getSignature(); // another localizable } }
Post-processing
Work in progress.
Fallback values
Work in progress.
Pluralization
Pluralization has been left out of this standard, but may be added later if a good case can be made.