Initial commit: apps.supersam24.ru

This commit is contained in:
root committed 2026-10-22 00:04:27 +03:00
commit e955e1da36
6566 files changed
+1271095

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
filter:
paths: ["src/*"]
tools:
external_code_coverage: true
php_code_coverage: true
php_sim: true
php_mess_detector: true
php_pdepend: true
php_analyzer: true
php_cpd: true
+14
View File
@@ -0,0 +1,14 @@
sudo: false
language: php
php:
- 5.6
- hhvm
- 7
before_script:
- composer self-update
- composer install
script:
- phpunit --coverage-clover=coverage.clover
after_script:
- wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover
+66
View File
@@ -0,0 +1,66 @@
# CHANGELOG
## 3.0.0
- Final release. No changes after first beta.
## 3.0.0-beta1
- Removed `aura/installer-default` from composer.json which was used by aura framework version 1 to install in package folder.
- Fixes [issue 17](https://github.com/auraphp/Aura.Intl/issues/17) by removing Aura.Di wiring trait tests.
- Changed directory structure from PSR-0 to PSR-4.
- Supported PHP version : 5.6+.
- There is no other BC breaks from 1.x version
## 1.1.1
Hygiene release: Composer update.
## 1.1.0
- Merge pull request #11 from lorenzo/feature/format-key. If Translator::translate() cannot translate, it now returns the incoming translation key as the translated message. This makes it easier to use the key as a human readable string, which can contain formatting placeholders. With this feature the developer will be able to see immediate feedback of their translation messages without having to translate to a specific locale beforehand.
## 1.0.1
Hygiene release.
- Merge pull request #10 from harikt/fixdoc; fixes #9, add a test for failure. The expected exception need to be fixed.
- Merge pull request #8 from harikt/v2config, adding v2 config files.
- Merge pull request #7 from harikt/issue6, add a test to show it works. Thank you @samdark.
- Point travis status badge to develop branch
## 1.0.0
- [CHG] When interpolating array values into strings, now converts the array
to comma-separated values instead of printing 'Array'.
- [DOC] PHP 5.5 appears to be able to format strings with missing token
replacements; updated tests to account for this.
## 1.0.0-beta2
- [FIX] Multiple typo fixes; thanks, @pborreli.
- [CHG] Use {foo} instead of {:foo} in line with PSR-3 placeholders.
- [NEW] Add a PackageFactory
- [NEW] Add more-detailed exception classes
- [ADD] In config, add 'intl_package_factory' as a service
- [CHG] Throw IcuVersionTooLow Exception if IntlFormatter is instantiated with
ICU Version lower than 4.8; Skip all IntlFormatter tests if the intl
extension is not loaded. Thanks, @mapthegod.
- [CHG] In the FormatterLocator, registry entries *must* be wrapped in a
callable from now on.
## 1.0.0-beta1
Initial release.
+7
View File
@@ -0,0 +1,7 @@
# Contributing
We are happy to review any contributions you want to make. When contributing, please follow the rules outlined at <http://auraphp.com/contributing>.
The time between submitting a contribution and its review one may be extensive; do not be discouraged if there is not immediate feedback.
Thanks!
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017, Aura for PHP
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+59
View File
@@ -0,0 +1,59 @@
# Aura.Intl
The Aura.Intl package provides internationalization (I18N) tools, specifically
package-oriented per-locale message translation.
## Installation and Autoloading
This package is installable and PSR-4 autoloadable via Composer as
[aura/intl][].
Alternatively, [download a release][], or clone this repository, then map the
`Aura\Intl\` namespace to the package `src/` directory.
## Dependencies
This package requires PHP 5.6 or later; it has been tested on PHP 5.6, 7.0
and HHVM. We recommend using the latest available version of PHP as a matter of
principle.
Aura library packages may sometimes depend on external interfaces, but never on
external implementations. This allows compliance with community standards
without compromising flexibility. For specifics, please examine the package
[composer.json][] file.
## Quality
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/auraphp/Aura.Intl/badges/quality-score.png?b=3.x)](https://scrutinizer-ci.com/g/auraphp/Aura.Intl/)
[![Code Coverage](https://scrutinizer-ci.com/g/auraphp/Aura.Intl/badges/coverage.png?b=3.x)](https://scrutinizer-ci.com/g/auraphp/Aura.Intl/)
[![Build Status](https://travis-ci.org/auraphp/Aura.Intl.png?branch=3.x)](https://travis-ci.org/auraphp/Aura.Intl)
This project adheres to [Semantic Versioning](http://semver.org/).
To run the unit tests at the command line, issue `composer install` and then
`phpunit` at the package root. This requires [Composer][] to be available as
`composer`, and [PHPUnit][] to be available as `phpunit`.
This package attempts to comply with [PSR-1][], [PSR-2][], and [PSR-4][]. If
you notice compliance oversights, please send a patch via pull request.
## Community
To ask questions, provide feedback, or otherwise communicate with other Aura
users, please join our [Google Group][], follow [@auraphp][], or chat with us
on Freenode in the #auraphp channel.
## Documentation
This package is fully documented [here](./docs/index.md).
[PSR-1]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md
[PSR-2]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md
[PSR-4]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md
[Composer]: http://getcomposer.org/
[PHPUnit]: http://phpunit.de/
[Google Group]: http://groups.google.com/group/auraphp
[@auraphp]: http://twitter.com/auraphp
[download a release]: https://github.com/auraphp/Aura.Intl/releases
[aura/intl]: https://packagist.org/packages/aura/intl
[composer.json]: ./composer.json
+35
View File
@@ -0,0 +1,35 @@
{
"name": "aura/intl",
"type": "library",
"description": "The Aura Intl package provides internationalization tools, specifically message translation.",
"keywords": [
"intl",
"internationalization",
"i18n",
"l10n",
"localization",
"globalization",
"g11n"
],
"homepage": "https://github.com/auraphp/Aura.Intl",
"license": "MIT",
"authors": [
{
"name": "Aura.Intl Contributors",
"homepage": "https://github.com/auraphp/Aura.Intl/contributors"
}
],
"require": {
"php": "^5.6|^7.0"
},
"autoload": {
"psr-4": {
"Aura\\Intl\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Aura\\Intl\\": "tests/"
}
}
}
+218
View File
@@ -0,0 +1,218 @@
# Aura.Intl
The Aura.Intl package provides internationalization (I18N) tools, specifically
package-oriented per-locale message translation.
## Getting Started
You can instantiate _TranslatorLocator_ object from _TranslatorLocatorFactory_
as below
```php
<?php
use Aura\Intl\TranslatorLocatorFactory;
$factory = new TranslatorLocatorFactory();
$translators = $factory->newInstance();
?>
```
Alternatively, we can add the Aura.Intl package `/path/to/Aura.Intl/src` to
our autoloader and build a translator locator manually:
```php
<?php
use Aura\Intl\PackageLocator;
use Aura\Intl\FormatterLocator;
use Aura\Intl\TranslatorFactory;
use Aura\Intl\TranslatorLocator;
return new TranslatorLocator(
new PackageLocator,
new FormatterLocator([
'basic' => function () { return new \Aura\Intl\BasicFormatter; },
'intl' => function () { return new \Aura\Intl\IntlFormatter; },
]),
new TranslatorFactory,
'en_US'
);
?>
```
## Setting Localized Messages For A Package
We can set localized messages for a package through the `PackageLocator` object
from the translator locator. We create a new `Package` with messages and place
it into the locator as a callable. The messages take the form of a message key and
and message string.
```php
<?php
use Aura\Intl\Package;
// get the package locator
$packages = $translators->getPackages();
// place into the locator for Vendor.Package
$packages->set('Vendor.Package', 'en_US', function() {
// create a US English message set
$package = new Package;
$package->setMessages([
'FOO' => 'The text for "foo."',
'BAR' => 'The text for "bar."',
]);
return $package;
});
// place into the locator for a Vendor.Package
$packages->set('Vendor.Package', 'pt_BR', function() {
// a Brazilian Portuguese message set
$package = new Package;
$package->setMessages([
'FOO' => 'O texto de "foo".',
'BAR' => 'O texto de "bar".',
]);
return $package;
});
?>
```
## Setting The Default Locale
We can set the default locale for translations using the `setLocale()` method:
```php
<?php
$translators->setLocale('pt_BR');
?>
```
## Getting A Localized Message
Now that the translator locator has messages and a default locale, we can get
an individual package translator. The package translator is suitable for
injection into another class, or for standalone use.
```php
<?php
// recall that the default locale is pt_BR
$translator = $translators->get('Vendor.Package');
echo $translator->translate('FOO'); // 'O texto de "foo".'
?>
```
You can get a translator for a non-default locale as well:
```php
<?php
$translator = $translators->get('Vendor.Package', 'en_US');
echo $translator->translate('FOO'); // 'The text for "foo."'
?>
```
## Replacing Message Tokens With Values
We often need to use dynamic values in translated messages. First, the
message string needs to have a token placeholder for the dynamic value:
```php
<?php
// get the packages out of the translator locator
$packages = $translators->getPackages();
$packages->set('Vendor.Dynamic', 'en_US', function() {
// US English messages
$package = new Package;
$package->setMessages([
'PAGE' => 'Page {page} of {pages} pages.',
]);
return $package;
});
$packages->set('Vendor.Dynamic', 'pt_BR', function() {
// Brazilian Portuguese messages
$package = new Package;
$package->setMessages([
'PAGE' => 'Página {page} de {pages} páginas.',
]);
return $package;
});
?>
```
Then, when we translate the message, we provide an array of tokens and
replacement values. These will be interpolated into the message string.
```php
<?php
// recall that the default locale is pt_BR
$translator = $translators->get('Vendor.Dynamic');
echo $translator->translate('PAGE', [
'page' => 1,
'pages' => 1,
]); // 'Página 1 de 1 páginas.'
?>
```
## Pluralized Messages
Usually, we need to use different messages when a value is singular or plural.
The `BasicFormatter` is not capable of presenting different messages based on
different token values. The `IntlFormatter` *is* capable, but the PHP
[`intl`](http://php.net/intl) extension must be loaded to take advantage of
it, and we must specify the `'intl'` formatter for the package in the catalog.
When using the `IntlFormatter`, we can build our message strings to present
singular or plural messages, as in the following example:
```php
<?php
// get the packages out of the translator locator
$packages = $translators->getCatalog();
// get the Vendor.Dynamic package en_US locale and set
// US English messages with pluralization. note the use
// of # instead of {pages} herein; using the placeholder
// "inside itself" with the Intl formatter causes trouble.
$package->setMessages([
'PAGE' => '{pages,plural,'
. '=0{No pages.}'
. '=1{One page only.}'
. 'other{Page {page} of # pages.}'
. '}'
]);
// use the 'intl' formatter for this package and locale
$package->setFormatter('intl');
// now that we have added the pluralizable messages,
// get the US English translator for the package
$translator = $translators->get('Vendor.Dynamic', 'en_US');
// zero translation
echo $translator->translate('PAGE', [
'page' => 0,
'pages' => 0,
]); // 'No pages.'
// singular translation
echo $translator->translate('PAGE', [
'page' => 1,
'pages' => 1,
]); // 'One page only.'
// plural translation
echo $translator->translate('PAGE', [
'page' => 3,
'pages' => 10,
]); // 'Page 3 of 10 pages.'
?>
```
Note that you can use other tokens within a pluralized token string to build
more complex messages. For more information, see the following:
<http://icu-project.org/apiref/icu4j/com/ibm/icu/text/MessageFormat.html>
+9
View File
@@ -0,0 +1,9 @@
<?php
error_reporting(E_ALL);
$autoloader = __DIR__ . '/vendor/autoload.php';
if (! file_exists($autoloader)) {
echo "Composer autoloader not found: $autoloader" . PHP_EOL;
echo "Please issue 'composer install' and try again." . PHP_EOL;
exit(1);
}
require $autoloader;
+12
View File
@@ -0,0 +1,12 @@
<phpunit bootstrap="./phpunit.php">
<testsuites>
<testsuite>
<directory>./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src/</directory>
</whitelist>
</filter>
</phpunit>
+47
View File
@@ -0,0 +1,47 @@
<?php
/**
*
* This file is part of the Aura Project for PHP.
*
* @package Aura.Intl
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Intl;
/**
*
* BasicFormatter
*
* @package Aura.Intl
*
*/
class BasicFormatter implements FormatterInterface
{
/**
*
* Format message
*
* @param string $locale
*
* @param string $string
*
* @param array $tokens_values
*
* @return string A string replaced with the token values
*
*/
public function format($locale, $string, array $tokens_values)
{
$replace = [];
foreach ($tokens_values as $token => $value) {
// convert an array to a CSV string
if (is_array($value)) {
$value = '"' . implode('", "', $value) . '"';
}
$replace['{' . $token . '}'] = $value;
}
return strtr($string, $replace);
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
/**
*
* This file is part of the Aura Project for PHP.
*
* @package Aura.Intl
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Intl;
/**
*
* Exception
*
* @package Aura.Intl
*
*/
class Exception extends \Exception
{
}
+24
View File
@@ -0,0 +1,24 @@
<?php
/**
*
* This file is part of the Aura Project for PHP.
*
* @package Aura.Intl
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Intl\Exception;
use Aura\Intl\Exception;
/**
*
* Could not format a message.
*
* @package Aura.Intl
*
*/
class CannotFormat extends \Exception
{
}
@@ -0,0 +1,24 @@
<?php
/**
*
* This file is part of the Aura Project for PHP.
*
* @package Aura.Intl
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Intl\Exception;
use Aura\Intl\Exception;
/**
*
* Could not instantiate a formatter object.
*
* @package Aura.Intl
*
*/
class CannotInstantiateFormatter extends \Exception
{
}
+24
View File
@@ -0,0 +1,24 @@
<?php
/**
*
* This file is part of the Aura Project for PHP.
*
* @package Aura.Intl
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Intl\Exception;
use Aura\Intl\Exception;
/**
*
* A formatter name is not mapped in the locator.
*
* @package Aura.Intl
*
*/
class FormatterNotMapped extends \Exception
{
}
+24
View File
@@ -0,0 +1,24 @@
<?php
/**
*
* This file is part of the Aura Project for PHP.
*
* @package Aura.Intl
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Intl\Exception;
use Aura\Intl\Exception;
/**
*
* ICU Version is too low for Aura.Intl to work properly.
*
* @package Aura.Intl
*
*/
class IcuVersionTooLow extends \Exception
{
}
+34
View File
@@ -0,0 +1,34 @@
<?php
/**
*
* This file is part of the Aura Project for PHP.
*
* @package Aura.Intl
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Intl;
/**
*
* Formatter Interface
*
* @package Aura.Intl
*
*/
interface FormatterInterface
{
/**
*
* Format message
*
* @param string $locale
*
* @param string $string
*
* @param array $tokens_values
*
*/
public function format($locale, $string, array $tokens_values);
}
+98
View File
@@ -0,0 +1,98 @@
<?php
/**
*
* This file is part of the Aura Project for PHP.
*
* @package Aura.Intl
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Intl;
/**
*
* A ServiceLocator implementation for loading and retaining formatter objects.
*
* @package Aura.Intl
*
*/
class FormatterLocator
{
/**
*
* A registry to retain formatter objects.
*
* @var array
*
*/
protected $registry;
/**
*
* Tracks whether or not a registry entry has been converted from a
* callable to a formatter object.
*
* @var array
*
*/
protected $converted = [];
/**
*
* Constructor.
*
* @param array $registry An array of key-value pairs where the key is the
* formatter name the value is a callable that returns a formatter object.
*
*/
public function __construct(array $registry = [])
{
foreach ($registry as $name => $spec) {
$this->set($name, $spec);
}
}
/**
*
* Sets a formatter into the registry by name.
*
* @param string $name The formatter name.
*
* @param callable $spec A callable that returns a formatter object.
*
* @return void
*
*/
public function set($name, $spec)
{
$this->registry[$name] = $spec;
$this->converted[$name] = false;
}
/**
*
* Gets a formatter from the registry by name.
*
* @param string $name The formatter to retrieve.
*
* @return FormatterInterface A formatter object.
*
* @throws Exception\FormatterNotMapped
*
*/
public function get($name)
{
if (! isset($this->registry[$name])) {
throw new Exception\FormatterNotMapped($name);
}
if (! $this->converted[$name]) {
$func = $this->registry[$name];
$this->registry[$name] = $func();
$this->converted[$name] = true;
}
return $this->registry[$name];
}
}
+98
View File
@@ -0,0 +1,98 @@
<?php
/**
*
* This file is part of the Aura Project for PHP.
*
* @package Aura.Intl
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Intl;
use MessageFormatter;
use Aura\Intl\Exception;
/**
*
* Uses php intl extension to format messages
*
* @package Aura.Intl
*
*/
class IntlFormatter implements FormatterInterface
{
/**
*
* Constructor.
*
* @param string $icu_version The current ICU version; mostly used for
* testing.
*
* @throws Exception\IcuVersionTooLow when the Version of ICU installed
* is too low for Aura.Intl to work properly.
*
*/
public function __construct($icu_version = INTL_ICU_VERSION)
{
if (version_compare($icu_version, '4.8') < 0) {
throw new Exception\IcuVersionTooLow('ICU Version 4.8 or higher required.');
}
}
/**
*
* Format the message with the help of php intl extension
*
* @param string $locale
* @param string $string
* @param array $tokens_values
* @return string
* @throws Exception
*/
public function format($locale, $string, array $tokens_values)
{
$values = [];
foreach ($tokens_values as $token => $value) {
// convert an array to a CSV string
if (is_array($value)) {
$value = '"' . implode('", "', $value) . '"';
}
$values[$token] = $value;
}
try {
$formatter = new MessageFormatter($locale, $string);
if (! $formatter) {
$this->throwCannotInstantiateFormatter();
}
} catch (\Exception $e) {
$this->throwCannotInstantiateFormatter();
}
$result = $formatter->format($values);
if ($result === false) {
throw new Exception\CannotFormat(
$formatter->getErrorMessage(),
$formatter->getErrorCode()
);
}
return $result;
}
/**
*
* Throws exception
*
* @throws Exception\CannotInstantiateFormatter
*/
protected function throwCannotInstantiateFormatter()
{
throw new Exception\CannotInstantiateFormatter(
intl_get_error_message(),
intl_get_error_code()
);
}
}
+197
View File
@@ -0,0 +1,197 @@
<?php
/**
*
* This file is part of the Aura Project for PHP.
*
* @package Aura.Intl
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Intl;
/**
*
* Message Catalog
*
* @package Aura.Intl
*
*/
class Package
{
/**
*
* Message keys and translations in this package.
*
* @var array
*
*/
protected $messages;
/**
*
* The name of a fallback package to use when a message key does not
* exist.
*
* @var string
*
*/
protected $fallback;
/**
*
* The name of the formatter to use when formatting translated messages.
*
* @var string
*
*/
protected $formatter;
/**
*
* Constructor.
*
* @param string $formatter The name of the formatter to use.
*
* @param string $fallback The name of the fallback package to use.
*
* @param array $messages The messages in this package.
*
*/
public function __construct(
$formatter = 'basic',
$fallback = null,
array $messages = []
) {
$this->formatter = $formatter;
$this->fallback = $fallback;
$this->messages = $messages;
}
/**
*
* Sets the messages for this package.
*
* @param array $messages The messages for this package.
*
* @return void
*
*/
public function setMessages(array $messages)
{
$this->messages = $messages;
}
/**
*
* Adds one message for this package.
*
* @param string $key the key of the message
*
* @param string $message the actual message
*
* @return void
*
*/
public function addMessage($key, $message)
{
$this->messages[$key] = $message;
}
/**
*
* Adds new messages for this package.
*
* @param array $messages The messages to add in this package.
*
* @return void
*
*/
public function addMessages($messages)
{
$this->messages = array_merge($this->messages, $messages);
}
/**
*
* Gets the messages for this package.
*
* @return array
*
*/
public function getMessages()
{
return $this->messages;
}
/**
*
* Gets the message of the given key for this package.
*
* @param string $key the key of the message to return
*
* @return mixed The message translation string, or false if not found.
*
*/
public function getMessage($key)
{
if (isset($this->messages[$key])) {
return $this->messages[$key];
}
return false;
}
/**
*
* Sets the formatter name for this package.
*
* @param string $formatter The formatter name for this package.
*
* @return void
*
*/
public function setFormatter($formatter)
{
$this->formatter = $formatter;
}
/**
*
* Gets the formatter name for this package.
*
* @return string
*
*/
public function getFormatter()
{
return $this->formatter;
}
/**
*
* Sets the fallback package name.
*
* @param string $fallback The fallback package name.
*
* @return void
*
*/
public function setFallback($fallback)
{
$this->fallback = $fallback;
}
/**
*
* Gets the fallback package name.
*
* @return string
*
*/
public function getFallback()
{
return $this->fallback;
}
}
+47
View File
@@ -0,0 +1,47 @@
<?php
/**
*
* This file is part of the Aura Project for PHP.
*
* @package Aura.Intl
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Intl;
/**
*
* Creates new package instances.
*
* @package Aura.Intl
*
*/
class PackageFactory
{
/**
*
* Returns a new Package instance.
*
* @param array $info An array of package information with keys
* 'formatter', 'fallback', and 'messages'. Typically read from an
* `intl/xx_XX.php` file.
*
* @return Package
*
*/
public function newInstance(array $info)
{
$package = new Package;
if (isset($info['fallback'])) {
$package->setFallback($info['fallback']);
}
if (isset($info['formatter'])) {
$package->setFormatter($info['formatter']);
}
if (isset($info['messages'])) {
$package->setMessages($info['messages']);
}
return $package;
}
}
+107
View File
@@ -0,0 +1,107 @@
<?php
/**
*
* This file is part of the Aura Project for PHP.
*
* @package Aura.Intl
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Intl;
/**
*
* A ServiceLocator implementation for loading and retaining translator objects.
*
* @package Aura.Intl
*
*/
class PackageLocator implements PackageLocatorInterface
{
/**
*
* A registry of packages.
*
* Unlike many other registries, this one is two layers deep. The first
* key is a package name, the second key is a locale code, and the value
* is a callable that returns a Package object for that name and locale.
*
* @var array
*
*/
protected $registry = [];
/**
*
* Tracks whether or not a registry entry has been converted from a
* callable to a Package object.
*
* @var array
*
*/
protected $converted = [];
/**
*
* Constructor.
*
* @param array $registry A registry of packages.
*
* @see $registry
*
*/
public function __construct(array $registry = [])
{
foreach ($registry as $name => $locales) {
foreach ($locales as $locale => $spec) {
$this->set($name, $locale, $spec);
}
}
}
/**
*
* Sets a Package object.
*
* @param string $name The package name.
*
* @param string $locale The locale for the package.
*
* @param callable $spec A callable that returns a package.
*
* @return void
*
*/
public function set($name, $locale, callable $spec)
{
$this->registry[$name][$locale] = $spec;
$this->converted[$name][$locale] = false;
}
/**
*
* Gets a Package object.
*
* @param string $name The package name.
*
* @param string $locale The locale for the package.
*
* @return Package
*
*/
public function get($name, $locale)
{
if (! isset($this->registry[$name][$locale])) {
throw new Exception("Package '$name' with locale '$locale' is not registered.");
}
if (! $this->converted[$name][$locale]) {
$func = $this->registry[$name][$locale];
$this->registry[$name][$locale] = $func();
$this->converted[$name][$locale] = true;
}
return $this->registry[$name][$locale];
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
/**
*
* This file is part of the Aura Project for PHP.
*
* @package Aura.Intl
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Intl;
/**
*
* Package locator interface.
*
* @package Aura.Intl
*
*/
interface PackageLocatorInterface
{
/**
*
* Sets a Package object.
*
* @param string $name The package name.
*
* @param string $locale The locale for the package.
*
* @param callable $spec A callable that returns a Package object.
*
* @return void
*
*/
public function set($name, $locale, callable $spec);
/**
*
* Gets a Package object.
*
* @param string $name The package name.
*
* @param string $locale The locale for the package.
*
* @return Package
*
*/
public function get($name, $locale);
}
+158
View File
@@ -0,0 +1,158 @@
<?php
/**
*
* This file is part of the Aura Project for PHP.
*
* @package Aura.Intl
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Intl;
/**
*
* Translator to translate the message
*
* @package Aura.Intl
*
*/
class Translator implements TranslatorInterface
{
/**
*
* A fallback translator.
*
* @var TranslatorInterface
*
*/
protected $fallback;
/**
*
* The formatter to use when translating messages.
*
* @var FormatterInterface
*
*/
protected $formatter;
/**
*
* The locale being used for translations.
*
* @var string
*
*/
protected $locale;
/**
* The Package containing keys and translations.
*
* @var Package
*
*/
protected $package;
/**
*
* Constructor
*
* @param string $locale The locale being used.
*
* @param Package $package The Package containing keys and translations.
*
* @param FormatterInterface $formatter A message formatter.
*
* @param TranslatorInterface $fallback A fallback translator.
*
*/
public function __construct(
$locale,
Package $package,
FormatterInterface $formatter,
TranslatorInterface $fallback = null
) {
$this->locale = $locale;
$this->package = $package;
$this->formatter = $formatter;
$this->fallback = $fallback;
}
/**
*
* Gets the message translation by its key.
*
* @param string $key The message key.
*
* @return mixed The message translation string, or false if not found.
*
*/
protected function getMessage($key)
{
$message = $this->package->getMessage($key);
if ($message) {
return $message;
}
if ($this->fallback) {
// get the message from the fallback translator
$message = $this->fallback->getMessage($key);
if ($message) {
// speed optimization: retain locally
$this->package->addMessage($key, $message);
// done!
return $message;
}
}
// no local message, no fallback
return false;
}
/**
*
* Translates the message indicated by they key, replacing token values
* along the way.
*
* @param string $key The message key.
*
* @param array $tokens_values Token values to interpolate into the
* message.
*
* @return string The translated message with tokens replaced.
*
*/
public function translate($key, array $tokens_values = [])
{
// get the message string
$message = $this->getMessage($key);
// do we have a message string?
if (! $message) {
// no, return the message key as-is
$message = $key;
}
// are there token replacement values?
if (empty($tokens_values)) {
// no, return the message string as-is
return $message;
}
// run message string through formatter to replace tokens with values
return $this->formatter->format($this->locale, $message, $tokens_values);
}
/**
*
* An object of type Package
*
* @return Package
*
*/
public function getPackage()
{
return $this->package;
}
}
+57
View File
@@ -0,0 +1,57 @@
<?php
/**
*
* This file is part of the Aura Project for PHP.
*
* @package Aura.Intl
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Intl;
/**
*
* Factory to create Translator objects.
*
* @package Aura.Intl
*
*/
class TranslatorFactory
{
/**
*
* The class to use for new instances.
*
* @var string
*
*/
protected $class = Translator::class;
/**
*
* Returns a new Translator.
*
* @param string $locale The locale code for the translator.
*
* @param Package $package The localized package for the translator.
*
* @param FormatterInterface $formatter The formatter to use for
* interpolating token values.
*
* @param TranslatorInterface $fallback A fallback translator to use, if
* any.
*
* @return Translator
*
*/
public function newInstance(
$locale,
Package $package,
FormatterInterface $formatter,
TranslatorInterface $fallback = null
) {
$class = $this->class;
return new $class($locale, $package, $formatter, $fallback);
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
/**
*
* This file is part of the Aura Project for PHP.
*
* @package Aura.Intl
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Intl;
/**
*
* TranslatorInterface
*
* @package Aura.Intl
*
*/
interface TranslatorInterface
{
/**
*
* translate
*
* @param string $key
*
* @param array $tokens_values
*
*/
public function translate($key, array $tokens_values = []);
}
+197
View File
@@ -0,0 +1,197 @@
<?php
/**
*
* This file is part of the Aura Project for PHP.
*
* @package Aura.Intl
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Intl;
/**
*
* A ServiceLocator implementation for loading and retaining translator objects.
*
* @package Aura.Intl
*
*/
class TranslatorLocator
{
/**
*
* A registry to retain translator objects.
*
* @var array
*
*/
protected $registry;
/**
*
* The current locale code.
*
* @var string
*
*/
protected $locale;
/**
*
* A translator factory.
*
* @var TranslatorFactory
*
*
*/
protected $factory;
/**
*
* A package locator.
*
* @var PackageLocator
*
*/
protected $packages;
/**
*
* A formatter locator.
*
* @var FormatterLocator
*
*/
protected $formatters;
/**
*
* Constructor.
*
* @param PackageLocator $packages The package locator.
*
* @param FormatterLocator $formatters The formatter locator.
*
* @param TranslatorFactory $factory A translator factory to
* create translator objects for the locale and package.
*
* @param string $locale The default locale code to use.
*
*/
public function __construct(
PackageLocator $packages,
FormatterLocator $formatters,
TranslatorFactory $factory,
$locale
) {
$this->packages = $packages;
$this->factory = $factory;
$this->formatters = $formatters;
$this->setLocale($locale);
}
/**
*
* Sets the default locale code.
*
* @param string $locale The new locale code.
*
* @return void
*
*/
public function setLocale($locale)
{
$this->locale = $locale;
}
/**
*
* Returns the default locale code.
*
* @return string
*
*/
public function getLocale()
{
return $this->locale;
}
/**
*
* The TranslatorFactory object
*
* @return TranslatorFactory
*/
public function getFactory()
{
return $this->factory;
}
/**
*
* An object of type PackageLocator
*
* @return PackageLocator
*
*/
public function getPackages()
{
return $this->packages;
}
/**
*
* An object of type FormatterLocator
*
* @return FormatterLocator
*
*/
public function getFormatters()
{
return $this->formatters;
}
/**
*
* Gets a translator from the registry by package for a locale.
*
* @param string $name The translator package to retrieve.
*
* @param string $locale The locale to use; if empty, uses the default
* locale.
*
* @return TranslatorInterface A translator object.
*
*/
public function get($name, $locale = null)
{
if (! $name) {
return null;
}
if (! $locale) {
$locale = $this->getLocale();
}
if (! isset($this->registry[$name][$locale])) {
// get the package descriptor
$package = $this->packages->get($name, $locale);
// build a translator; note the recursive nature of the
// 'fallback' param at the very end.
$translator = $this->factory->newInstance(
$locale,
$package,
$this->formatters->get($package->getFormatter()),
$this->get($package->getFallback(), $locale)
);
// retain in the registry
$this->registry[$name][$locale] = $translator;
}
return $this->registry[$name][$locale];
}
}
+45
View File
@@ -0,0 +1,45 @@
<?php
/**
*
* This file is part of the Aura Project for PHP.
*
* @package Aura.Intl
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Intl;
/**
*
* Formatter Interface
*
* @package Aura.Intl
*
*/
class TranslatorLocatorFactory
{
/**
*
* Returns a new TranslatorLocator.
*
* @return TranslatorLocator
*
*/
public function newInstance()
{
return new TranslatorLocator(
new PackageLocator,
new FormatterLocator([
'basic' => function () {
return new \Aura\Intl\BasicFormatter;
},
'intl' => function () {
return new \Aura\Intl\IntlFormatter;
},
]),
new TranslatorFactory,
'en_US'
);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace Aura\Intl;
class BasicFormatterTest extends \PHPUnit_Framework_TestCase
{
protected function newFormatter()
{
return new BasicFormatter;
}
public function testFormat()
{
$formatter = $this->newFormatter();
$locale = 'en_US';
$expect = 'Hello world 88!';
$tokens_values = ['foo' => 'world', 'bar' => '88', 'baz' => '!'];
$string = 'Hello {foo} {bar}{baz}';
$actual = $formatter->format($locale, $string, $tokens_values);
$this->assertSame($expect, $actual);
$tokens_values = ['array' => ['foo', 'bar', 'baz']];
$string = 'Array {array}';
$expect = 'Array "foo", "bar", "baz"';
$actual = $formatter->format($locale, $string, $tokens_values);
$this->assertSame($expect, $actual);
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace Aura\Intl;
class FormatterLocatorTest extends \PHPUnit_Framework_TestCase
{
public function test__constructAndGet()
{
$formatters = new FormatterLocator([
'mock' => function () {
return new \Aura\Intl\MockFormatter;
},
]);
$expect = 'Aura\Intl\MockFormatter';
$actual = $formatters->get('mock');
$this->assertInstanceOf($expect, $actual);
}
public function testSetAndGet()
{
$formatters = new FormatterLocator;
$formatters->set('mock', function () {
return new \Aura\Intl\MockFormatter;
});
$expect = 'Aura\Intl\MockFormatter';
$actual = $formatters->get('mock');
$this->assertInstanceOf($expect, $actual);
}
public function testGet_noSuchFormatter()
{
$formatters = new FormatterLocator;
$this->setExpectedException('Aura\Intl\Exception\FormatterNotMapped');
$formatters->get('noSuchFormatter');
}
}
+195
View File
@@ -0,0 +1,195 @@
<?php
namespace Aura\Intl;
class IntlFormatterTest extends BasicFormatterTest
{
protected function newFormatter()
{
return new IntlFormatter;
}
public function setUp()
{
if (! extension_loaded('intl')) {
$this->markTestSkipped('This test is skipped if the Intl Extension is not loaded.');
}
}
public function testIntlVersion()
{
$this->setExpectedException('Aura\Intl\Exception\IcuVersionTooLow');
$formatter = new IntlFormatter('4.7');
}
/**
* This test fails on PHP 5.4.4
* The return value expected is No pages , but returns false
*
* var_dump( msgfmt_get_error_message($fmt) );
* U_ZERO_ERROR
*
* It seems $fmt = msgfmt_create($locale, $string); is not creating with
* this string , so the msgfmt_format() throws expects parameter 1 to be
* MessageFormatter, null given error.
*/
public function testFormat_plural()
{
$formatter = $this->newFormatter();
$locale = 'en_US';
$string = '{pages,plural,'
. '=0{No pages.}'
. '=1{One page only.}'
. 'other{Page {page} of # pages.}'
. '}';
$tokens_values = ['page' => 0, 'pages' => 0];
$expect = 'No pages.';
$actual = $formatter->format($locale, $string, $tokens_values);
$this->assertSame($expect, $actual);
$tokens_values = ['page' => 1, 'pages' => 1];
$expect = 'One page only.';
$actual = $formatter->format($locale, $string, $tokens_values);
$this->assertSame($expect, $actual);
$tokens_values = ['page' => 1, 'pages' => 2];
$expect = 'Page 1 of 2 pages.';
$actual = $formatter->format($locale, $string, $tokens_values);
$this->assertSame($expect, $actual);
}
/**
* @dataProvider provide_testFormat_select
*/
public function testFormat_select($tokens_values, $expect)
{
$locale = 'en_US';
$string = "
{gender, select,
female {
{count, plural, offset:1
=0 {{from} does not give a party.}
=1 {{from} invites {to} to her party.}
=2 {{from} invites {to} and one other person to her party.}
other {{from} invites {to} as one of the # people invited to her party.}
}
}
male {
{count, plural, offset:1
=0 {{from} does not give a party.}
=1 {{from} invites {to} to his party.}
=2 {{from} invites {to} and one other person to his party.}
other {{from} invites {to} as one of the # other people invited to his party.}
}
}
other {
{count, plural, offset:1
=0 {{from} does not give a party.}
=1 {{from} invites {to} to their party.}
=2 {{from} invites {to} and one other person to their party.}
other {{from} invites {to} as one of the # other people invited to their party.}
}
}
}";
$formatter = $this->newFormatter();
$actual = $formatter->format($locale, $string, $tokens_values);
$this->assertSame(trim($expect), trim($actual));
}
public function provide_testFormat_select()
{
return [
[array('gender' => 'female', 'count' => 0, 'from' => 'Alice', 'to' => 'Bob'), 'Alice does not give a party.'],
[array('gender' => 'male', 'count' => 1, 'from' => 'Bob', 'to' => 'Alice'), 'Bob invites Alice to his party.'],
[array('gender' => 'none', 'count' => 2, 'from' => 'Alice', 'to' => 'Bob'), 'Alice invites Bob and one other person to their party.'],
[array('gender' => 'female', 'count' => 27, 'from' => 'Alice', 'to' => 'Bob'), 'Alice invites Bob as one of the 26 people invited to her party.'],
];
}
public function testFormat_cannotInstantiateFormatter()
{
$locale = 'en_US';
// uses {count} instead of #, which should fail
$string = "
{gender, select,
female {
{count, plural, offset:1
=0 {{from} does not give a party.}
=1 {{from} invites {to} to her party.}
=2 {{from} invites {to} and one other person to her party.}
other {{from} invites {to} as one of the {count} people invited to her party.}
}
}
male {
{count, plural, offset:1
=0 {{from} does not give a party.}
=1 {{from} invites {to} to his party.}
=2 {{from} invites {to} and one other person to his party.}
other {{from} invites {to} as one of the {count} other people invited to his party.}
}
}
other {
{count, plural, offset:1
=0 {{from} does not give a party.}
=1 {{from} invites {to} to their party.}
=2 {{from} invites {to} and one other person to their party.}
other {{from} invites {to} as one of the {count} other people invited to their party.}
}
}
}";
$formatter = $this->newFormatter();
$this->setExpectedException('Aura\Intl\Exception\CannotFormat');
$actual = $formatter->format($locale, $string, array('gender' => 'female', 'count' => 5, 'from' => 'Alice', 'to' => 'Bob'));
}
// @todo MAKE IT SO THAT WE CHECK FOR TOKENS IN THE ARRAY
public function testFormat_cannotFormat()
{
$locale = 'en_US';
$string = 'Hello {foo}';
$tokens_values = ['bar' => 'baz']; // no 'foo' token
$formatter = $this->newFormatter();
$expect = 'Hello {foo}';
$actual = $formatter->format($locale, $string, $tokens_values);
$this->assertSame($expect, $actual);
}
/**
* @dataProvider provide_testIssue6
*/
public function testIssue6($tokens_values, $expect)
{
$locale = 'en_US';
$string = '{gender, select, female{{name} is {gender} and she report bugs!} male{{name} is {gender} and he report bugs!} other{{name} report bugs!}}';
$formatter = $this->newFormatter();
$actual = $formatter->format($locale, $string, $tokens_values);
$this->assertSame($expect, $actual);
}
public function testEmptyStringThrowsException()
{
$locale = 'en_US';
$string = '';
$formatter = $this->newFormatter();
if (! defined('HHVM_VERSION')) {
$this->setExpectedException('Aura\Intl\Exception\CannotInstantiateFormatter');
}
$actual = $formatter->format($locale, $string, []);
if (defined('HHVM_VERSION')) {
// HHVM will instantiate Formatter even if empty string is passed.
$expect = '';
$this->assertSame($expect, $actual);
}
}
public function provide_testIssue6()
{
return [
[array('gender' => 'female', 'name' => 'Alice'), 'Alice is female and she report bugs!'],
[array('gender' => 'male', 'name' => 'Alexander' ), 'Alexander is male and he report bugs!'],
[array('gender' => '', 'name' => 'Unknown'), 'Unknown report bugs!'],
];
}
}
+10
View File
@@ -0,0 +1,10 @@
<?php
namespace Aura\Intl;
class MockFormatter implements FormatterInterface
{
public function format($locale, $string, array $tokens_values = [])
{
return $string;
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace Aura\Intl;
/**
* Test class for Package.
* Generated by PHPUnit on 2012-10-27 at 22:46:01.
*/
class PackageLocatorTest extends \PHPUnit_Framework_TestCase
{
protected $packages;
protected function setUp()
{
parent::setUp();
$this->packages = new PackageLocator([
'Vendor.Foo' => [
'en_US' => function () {
return new \Aura\Intl\Package;
},
],
]);
}
public function testGet()
{
// get once to create it the first time
$first = $this->packages->get('Vendor.Foo', 'en_US');
$this->assertInstanceOf('Aura\Intl\Package', $first);
// get again to make sure it's the same object
$again = $this->packages->get('Vendor.Foo', 'en_US');
$this->assertSame($first, $again);
// try for an unregistered package
$this->setExpectedException('Aura\Intl\Exception');
$this->packages->get('Vendor.Bar', 'en_US');
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace Aura\Intl;
/**
* Test class for Package.
* Generated by PHPUnit on 2012-10-27 at 22:46:01.
*/
class PackageTest extends \PHPUnit_Framework_TestCase
{
protected $package;
protected function setUp()
{
parent::setUp();
$factory = new PackageFactory;
$this->package = $factory->newInstance([
'fallback' => 'Vendor.Fallback',
'formatter' => 'intl',
'messages' => [
'ERR_NO_SUCH_OPTION' => "The option {option} is not recognized.",
],
]);
}
public function testGet()
{
$expect = 'Vendor.Fallback';
$actual = $this->package->getFallback();
$this->assertSame($expect, $actual);
$expect = 'intl';
$actual = $this->package->getFormatter();
$this->assertSame($expect, $actual);
$expect = [
'ERR_NO_SUCH_OPTION' => "The option {option} is not recognized.",
];
$actual = $this->package->getMessages();
$this->assertSame($expect, $actual);
}
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Aura\Intl;
class TranslatorLocatorFactoryTest extends \PHPUnit_Framework_TestCase
{
public function test__newInstance()
{
$factory = new TranslatorLocatorFactory();
$this->assertInstanceOf('Aura\Intl\TranslatorLocator', $factory->newInstance());
}
}
+113
View File
@@ -0,0 +1,113 @@
<?php
namespace Aura\Intl;
class TranslatorLocatorTest extends \PHPUnit_Framework_TestCase
{
protected $factory;
protected $translators;
protected $packages;
protected $formatters;
protected function setUp()
{
$registry['Vendor.Package']['en_US'] = function () {
return new \Aura\Intl\Package(
'mock',
null,
[
'ERR_NO_SUCH_OPTION' => "The option {option} is not recognized.",
]
);
};
$registry['Vendor.Package']['pt_BR'] = function () {
return new \Aura\Intl\Package(
'mock',
null,
[
'ERR_NO_SUCH_OPTION' => "O {option} opção não é reconhecido.",
]
);
};
$this->packages = new PackageLocator($registry);
$this->formatters = new FormatterLocator([
'mock' => function () {
return new MockFormatter;
},
]);
$this->factory = new TranslatorFactory;
$this->translators = new TranslatorLocator(
$this->packages,
$this->formatters,
$this->factory,
'en_US'
);
}
public function testSetAndGetLocale()
{
$expect = 'pt_BR';
$this->translators->setLocale($expect);
$actual = $this->translators->getLocale();
$this->assertSame($expect, $actual);
}
public function testGetFactory()
{
$actual = $this->translators->getFactory();
$this->assertSame($this->factory, $actual);
}
public function testGet()
{
$actual = $this->translators->get('Vendor.Package');
$this->assertInstanceOf('Aura\Intl\Translator', $actual);
}
public function testGetPackages()
{
$actual = $this->translators->getPackages();
$this->assertSame($this->packages, $actual);
}
public function testGetFormatterLocator()
{
$actual = $this->translators->getFormatters();
$this->assertSame($this->formatters, $actual);
}
public function testIssue9()
{
$this->packages->set('Vendor.Package', 'en_UK', function () {
$package = new Package('mock');
$package->setMessages([
'FOO' => 'The text for "foo."',
'BAR' => 'The text for "bar."',
]);
return $package;
});
$translator = $this->translators->get('Vendor.Package', 'en_UK');
$expect = 'The text for "foo."';
$this->assertSame($translator->translate('FOO'), $expect);
}
public function testIssue9Failure()
{
$package = new Package;
$package->setMessages([
'FOO' => 'The text for "foo."',
'BAR' => 'The text for "bar."',
]);
// $this->packages->set('Vendor.Package', 'en_UK', $package);
// $this->setExpectedException('Exception');
// $translator = $this->translators->get('Vendor.Package', 'en_UK');
}
}
+101
View File
@@ -0,0 +1,101 @@
<?php
namespace Aura\Intl;
class TranslatorTest extends \PHPUnit_Framework_TestCase
{
protected $translator;
protected $factory;
protected $package;
protected $locale = 'en_US';
protected $messages = [
'TEXT_FOO' => 'Foo text',
'TEXT_BAR' => 'Bar text',
];
protected $formatter;
protected function newTranslator(TranslatorInterface $fallback = null)
{
return $this->factory->newInstance(
'en_US',
$this->package,
$this->formatter,
$fallback
);
}
protected function setUp()
{
$this->factory = new TranslatorFactory;
$this->formatter = new MockFormatter;
$this->package = new Package;
$this->package->setMessages($this->messages);
$this->translator = $this->newTranslator();
}
public function testTranslate()
{
// key exists
$expect = 'Foo text';
$actual = $this->translator->translate('TEXT_FOO');
$this->assertSame($expect, $actual);
// key exists, with tokens passed
$expect = 'Foo text';
$actual = $this->translator->translate('TEXT_FOO', ['foo' => 'bar']);
$this->assertSame($expect, $actual);
// key does not exist
$expect = 'TEXT_NONE';
$actual = $this->translator->translate('TEXT_NONE');
$this->assertSame($expect, $actual);
}
public function testTranslate_fallback()
{
$package = new Package;
$package->setMessages([
'TEXT_NONE' => 'Fallback text',
]);
// create fallback translator
$fallback = new Translator(
'en_US',
$package,
$this->formatter
);
// create primary translator with fallback
$translator = $this->newTranslator($fallback);
// key does not exist in primary, but exists in fallback
$expect = 'Fallback text';
$actual = $translator->translate('TEXT_NONE');
$this->assertSame($expect, $actual);
}
public function testTranslateMissingKey()
{
// using getMockBuilder so we can use with phpunit 4.8 or 5+ without warnings
$formatter = $this->getMockBuilder(get_class($this->formatter))
->disableOriginalConstructor()
->disableOriginalClone()
->disableArgumentCloning()
->getMock();
// create fallback translator
$translator = new Translator('en_US', new Package, $formatter);
$formatter->expects($this->once())
->method('format')
->with('en_US', 'TEXT', ['var' => 'SOME'])
->will($this->returnValue('FORMATTED'));
// key does not exist, with tokens passed
$expect = 'FORMATTED';
$actual = $translator->translate('TEXT', ['var' => 'SOME']);
$this->assertEquals($expect, $actual);
}
}