Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Translate] add an intl MessageFormatter interpolator #892

Merged
merged 13 commits into from
Mar 18, 2019
32 changes: 0 additions & 32 deletions Library/Phalcon/Translate/Adapter/Base.php

This file was deleted.

31 changes: 4 additions & 27 deletions Library/Phalcon/Translate/Adapter/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
use Phalcon\Translate\AdapterInterface;
use Phalcon\Translate\Exception;

class Database extends Base implements AdapterInterface, \ArrayAccess
class Database extends Adapter implements AdapterInterface, \ArrayAccess
{
/**
* @var array
Expand All @@ -44,13 +44,6 @@ class Database extends Base implements AdapterInterface, \ArrayAccess
*/
protected $stmtSelect;

/**
* Use ICU MessageFormatter to parse message
*
* @var boolean
*/
protected $useIcuMessageFormatter = false;

/**
* Class constructor.
*
Expand All @@ -71,14 +64,6 @@ public function __construct(array $options)
throw new Exception("Parameter 'language' is required");
}

if (isset($options['useIcuMessageFormatter'])) {
if (!class_exists('\MessageFormatter')) {
throw new Exception('"MessageFormatter" class is required');
}

$this->useIcuMessageFormatter = (boolean) $options['useIcuMessageFormatter'];
}

$this->stmtSelect = sprintf(
'SELECT value FROM %s WHERE language = :language AND key_name = :key_name',
$options['table']
Expand All @@ -90,6 +75,8 @@ public function __construct(array $options)
);

$this->options = $options;

parent::__construct($options);
}

/**
Expand All @@ -109,17 +96,7 @@ public function query($translateKey, $placeholders = null)
);
$value = empty($translation['value']) ? $translateKey : $translation['value'];

if (is_array($placeholders) && !empty($placeholders)) {
if (true === $this->useIcuMessageFormatter) {
$value = \MessageFormatter::formatMessage($options['language'], $value, $placeholders);
} else {
foreach ($placeholders as $placeHolderKey => $placeHolderValue) {
$value = str_replace('%' . $placeHolderKey . '%', $placeHolderValue, $value);
}
}
}

return $value;
return $this->replacePlaceholders($value, $placeholders);
}

/**
Expand Down
49 changes: 5 additions & 44 deletions Library/Phalcon/Translate/Adapter/Mongo.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

use Phalcon\Translate\Exception;
use Phalcon\Mvc\CollectionInterface;
use Phalcon\Translate\Adapter;
use Phalcon\Translate\AdapterInterface;

/**
Expand All @@ -33,11 +34,10 @@
*
* @package Phalcon\Translate\Adapter
*/
class Mongo extends Base implements AdapterInterface, \ArrayAccess
class Mongo extends Adapter implements AdapterInterface, \ArrayAccess
{
protected $language;
protected $collection;
protected $formatter;

/**
* Mongo constructor.
Expand All @@ -59,10 +59,8 @@ public function __construct($options)
}

$this->setLanguage($options['language']);

if (isset($options['formatter'])) {
$this->setFormatter($options['formatter']);
}

parent::__construct($options);
}

/**
Expand All @@ -86,16 +84,6 @@ protected function setLanguage($language)
$this->language = $language;
}

/**
* Sets the formatter to use if necessary.
*
* @param \MessageFormatter $formatter Message formatter.
*/
protected function setFormatter(\MessageFormatter $formatter)
{
$this->formatter = $formatter;
}

/**
* Gets the translations set.
*
Expand Down Expand Up @@ -132,34 +120,7 @@ public function query($translateKey, $placeholders = null)
$translation = $translations->{$this->language};
}

if (!empty($placeholders)) {
return $this->format($translation, $placeholders);
}

return $translation;
}

/**
* Formats a translation.
*
* @param string $translation Translated text.
* @param array $placeholders Placeholders to apply.
*
* @return string
*/
protected function format($translation, $placeholders = [])
{
if ($this->formatter) {
$formatter = $this->formatter;

return $formatter::formatMessage($this->language, $translation, $placeholders);
}

foreach ($placeholders as $key => $value) {
$translation = str_replace("%$key%", $value, $translation);
}

return $translation;
return $this->replacePlaceholders($value, $placeholders);
}

/**
Expand Down
17 changes: 1 addition & 16 deletions Library/Phalcon/Translate/Adapter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ class IndexController extends \Phalcon\Mvc\Controller
'db' => $this->di->get('db'), // Here we're getting the database from DI
'table' => 'translations', // The table that is storing the translations
'language' => $this->request->getBestLanguage(), // Now we're getting the best language for the user
'useIcuMessageFormatter' => true, // Optional, if need formatting message using ICU MessageFormatter
]);
}

Expand Down Expand Up @@ -98,14 +97,6 @@ Or, if you wish you can use [Volt][2]:
<h1>{{ expression._("IndexPage_Hello_World") }}</h1>
```

ICU MessageFormatter Example
```php
// Example plural message with key 'cats'
// Peter has {nbCats, plural, =0{no cat} =1{a cat} other{# cats}}

$this->_getTranslation()->_('cats', ['nbCats' => rand(0, 10)]);
```

## Mongo

Implements a Mongo adapter for translations.
Expand All @@ -117,15 +108,9 @@ use MessageFormatter;
use Phalcon\Translate\Adapter\Mongo;
use My\Application\Collections\Translate;

$fmt = new MessageFormatter(
"en_US",
"{0,number,integer} monkeys on {1,number,integer} trees make {2,number} monkeys per tree"
);

$translate = new Mongo([
'collection' => Translate::class,
'language' => 'en',
'formatter' => $fmt,
'language' => 'en'
]);

echo $translate->t('application.title');
Expand Down
9 changes: 7 additions & 2 deletions Library/Phalcon/Translate/Adapter/Redis.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php
namespace Phalcon\Translate\Adapter;

use Phalcon\Translate\Adapter;
use Phalcon\Translate\AdapterInterface;
use Phalcon\Translate\Exception;

Expand All @@ -9,7 +10,7 @@
*
* @package Phalcon\Translate\Adapter
*/
class Redis extends Base implements AdapterInterface
class Redis extends Adapter implements AdapterInterface
{
/**
* Redis object.
Expand Down Expand Up @@ -61,6 +62,8 @@ public function __construct(array $options)
if (isset($options['levels'])) {
$this->levels = $options['levels'];
}

parent::__construct($options);
}

/**
Expand Down Expand Up @@ -93,9 +96,11 @@ public function query($translateKey, $placeholders = null)

$this->loadValueByKey($key);

return isset($this->cache[$key]) && isset($this->cache[$key][$index])
$value = isset($this->cache[$key]) && isset($this->cache[$key][$index])
? $this->cache[$key][$index]
: $translateKey;

return $this->replacePlaceholders($value, $placeholders);
}

/**
Expand Down
16 changes: 4 additions & 12 deletions Library/Phalcon/Translate/Adapter/ResourceBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
/**
* ResourceBundle adapter
*/
class ResourceBundle extends Base implements AdapterInterface
class ResourceBundle extends Adapter implements AdapterInterface
{
/**
* @var \ResourceBundle
Expand Down Expand Up @@ -42,10 +42,6 @@ public function __construct($options)
throw new Exception('"ResourceBundle" class is required');
}

if (!class_exists('\MessageFormatter')) {
throw new Exception('"MessageFormatter" class is required');
}

if (!isset($options['bundle'])) {
throw new Exception('"bundle" option is required');
}
Expand All @@ -60,6 +56,8 @@ public function __construct($options)

$this->options = $options;
$this->bundle = new \ResourceBundle($this->options['locale'], $this->options['bundle'], $this->fallback);

parent::__construct($options);
}

/**
Expand Down Expand Up @@ -88,13 +86,7 @@ public function query($index, $placeholders = null)
return $index;
}

$formatter = new \MessageFormatter($this->options['locale'], $this->get($index, $this->bundle));

if (null !== $formatter) {
return $formatter->format((array) $placeholders);
} else {
return $index;
}
return $this->replacePlaceholders($this->get($index, $this->bundle), $placeholders);
}

/**
Expand Down
52 changes: 52 additions & 0 deletions Library/Phalcon/Translate/Interpolator/Intl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace Phalcon\Translate\Interpolator;

use Phalcon\Translate\InterpolatorInterface;
use Phalcon\Translate\Exception;
use MessageFormatter;
use IntlException;

class Intl implements InterpolatorInterface
{
private $locale;

public function __construct($locale)
{
$this->locale = $locale;
}

/**
* Replaces placeholders by the values passed
* Use the MessageFormatter class,
* See http://php.net/manual/en/class.messageformatter.php
*/
public function replacePlaceholders($translation, $placeholders = null)
{
if (is_array($placeholders) && count($placeholders)) {
try {
// TODO (?) : keep an internal cache of the MessageFormatter objects (key = locale.translation)
$fmt = new MessageFormatter($this->locale, $translation);
} catch (IntlException $e) {
$fmt = null;
} finally {
// for php 7.x the original exception message is "Constructor failed"
// for php 5.6 the constructor returns null, see this wont fix bug https://bugs.php.net/bug.php?id=58631
// make it a bit more understandable
if (is_null($fmt)) {
throw new Exception(
"Unable to instantiate a MessageFormatter. Check locale and string syntax.",
0,
isset($e) ? $e : null
);
}
}

$translation = $fmt->format($placeholders);
if ($translation === false) {
throw new Exception($fmt->getErrorMessage(), $fmt->getErrorCode());
}
}
return $translation;
}
}
36 changes: 36 additions & 0 deletions Library/Phalcon/Translate/Interpolator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Phalcon\Translate\Interpolator

Usage examples of the interpolators available here:

## Intl

It needs the extension [intl](php.net/manual/book.intl.php) to be installed in PHP, and it uses [MessageFormatter](http://php.net/manual/en/class.messageformatter.php) objects in an interpolator interface.
More about the syntax convention can be read on this [formating guide](https://www.sitepoint.com/localization-demystified-understanding-php-intl/) and on the [ICU documentation](http://userguide.icu-project.org/formatparse/messages).

```php
<?php
use Phalcon\Translate\Adapter\NativeArray;
use Phalcon\Translate\Interpolator\Intl;

$translate = new NativeArray([
'interpolator' => new Intl('en_US'), // this interpolator must be locale aware
'content' => ['hi-name' => 'Hello {name}, it\'s {time, number, integer} o\'clock']
]);

$name = 'Henry';
$translate->_('hi-name', ['name' => $name, 'time' => 8]); // Hello Henry, it's 8 o'clock
```

```php
<?php
use Phalcon\Translate\Adapter\NativeArray;
use Phalcon\Translate\Interpolator\Intl;

$translate = new NativeArray([
'interpolator' => new Intl('fr_FR'), // this interpolator must be locale aware
'content' => ['apples' => "{count, plural, =0{Je n'ai aucune pomme} =1{J'ai une pomme} other{J'ai # pommes}}."]
]);

// thousands separator is " " (blank space) for fr_FR
echo $translate->_('apples', ['count' => 1000]); // J'ai 1 000 pommes
```
Loading