Restored context parameter to i18n, and added to a “comment” key

Rolled pluralisation functionality into the i18n::_t() method
Warnings on missing default can now be turned off
This commit is contained in:
Damian Mooyman 2017-01-25 16:35:13 +13:00
parent 8a07c56bdf
commit de02a3f733
No known key found for this signature in database
GPG Key ID: 78B823A10DE27D1A
25 changed files with 660 additions and 229 deletions

View File

@ -23,8 +23,8 @@
"symfony/yaml": "~2.7",
"embed/embed": "^2.6",
"swiftmailer/swiftmailer": "~5.4",
"symfony/config": "^2.8|^3",
"symfony/translation": "^2.8|^3"
"symfony/config": "^2.8|^3",
"symfony/translation": "^2.8|^3"
},
"require-dev": {
"phpunit/PHPUnit": "~4.8",

View File

@ -172,8 +172,12 @@ It can be used to translate strings in both PHP files and template files. The us
elsewhere in your code.
* **$default:** The original language string to be translated. This should be declared
whenever used, and will get picked up the [text collector](#collecting-text).
* **$string:** (optional) Natural language comment (particularly short phrases and individual words)
are very context dependent. This parameter allows the developer to convey this information
to the translator.
* **$injection::** (optional) An array of injecting variables into the second parameter
## Pluralisation
i18n also supports locale-respective pluralisation rules. Many languages have more than two plural forms,
@ -182,8 +186,11 @@ unlike English which has two only; One for the singular, and another for any oth
More information on what forms these plurals can take for various locales can be found on the
[CLDR documentation](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html)
The ability to pluralise strings is provided through the `i18n::pluaralise` method, which is similar to the
`i18n::_t` method, other than that it takes an additional `$count` argument.
The ability to pluralise strings is provided through the `i18n::_t` method when supplied with a
`{count}` argument and `|` pipe-delimiter provided with the default string.
Plural forms can also be explicitly declared via the i18nEntityProvider interface in array-format
with both a 'one' and 'other' key (as per the CLDR for the default `en` language).
For instance, this is an example of how to correctly declare pluralisations for an object
@ -234,19 +241,11 @@ Please ensure that any required plurals are exposed via provideI18nEntities.
"Restored {value} successfully",
array('value' => $itemRestored)
);
// Plurals are invoked via a `|` pipe-delimeter with a {count} argument
_t('MyObject.PLURALS', 'An object|{count} objects', [ 'count' => '$count ]);
You can invoke plurals for any object using the new `i18n::pluralise` method.
In addition to array form, you can also pass in a pipe-delimited string as a default
argument for brevity.
:::php
public function pluralise($count)
{
return i18n::pluralise('MyObject.PLURALS', 'An object|{count} objects', $count);
}
#### Usage in Template Files
<div class="hint" markdown='1'>
@ -267,11 +266,8 @@ the PHP version of the function.
// Using injection to add variables into the translated strings (note that $Name and $Greeting must be available in the current template scope).
<%t Header.Greeting "Hello {name} {greeting}" name=$Name greeting=$Greeting %>
Pluralisation in templates is available via the global `$pluralise` method.
:::ss
You have $pluralise('i18nTestModule.PLURALS', 'An item|{count} items', $Count) in your cart
// Plurals follow the same convention, required a `|` and `{count}` in the default string
<%t MyObject.PLURALS 'An item|{count} items' count=$Count %>
#### Caching in Template Files with locale switching

View File

@ -219,10 +219,10 @@ Where your code once used SQLQuery you should now use SQLSelect in all cases, as
In many cases, localisation strings which worked in 3.x will continue to work in 4.0, however certain patterns
have been deprecated and will be removed in 5.0. These include:
- _t calls with a $context parameter, which is ignored.
- _t calls with sprintf-style placeholders (`%s`). Replace with named placeholders instead.
- _t calls with non-associative injection arguments. Please use an associative array for all arguments.
- _t calls which do not include a default value.
- _t calls which do not include a default value will now raise a warning. This can be disabled by setting
the `i18n.missing_default_warning` config to false.
Note: If you attempt to use non-associative injection arguments with named placeholders, the result will
now trigger an exception.
@ -232,7 +232,7 @@ The non-associative array return type is deprecated. If returning a default stri
other than itself, it should return an array with the `default` and `module` keys respectively.
Full locale-rule respecting localisation for plural forms is now supported. The default
key for an object plural form is `<fqn>.PLURALS`, and follows CLDR array form for each
key for an object plural form is `<Namespaced\ClassName>.PLURALS`, and follows CLDR array form for each
pluralisation. See [the CLDR chart](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html)
for reference.
@ -285,33 +285,23 @@ In YML format this will be expressed as the below:
DESCRIPTION: 'This is the description for this section'
You can invoke plurals for any object using the new `i18n::pluralise` method.
In addition to array form, you can also pass in a pipe-delimited string as a default
argument for brevity.
Usage of these pluralised strings is through the existing _t() method,
and require a `|` pipe-delimeter with a {count} argument.
:::php
public function pluralise($count)
{
return i18n::pluralise('MyObject.PLURALS', 'An object|{count} objects', $count);
return _t('MyObject.PLURALS', 'An object|{count} objects', [ 'count' => $count ]);
}
Which is equivalent to the below:
In templates this can also be invoked as below:
:::php
public function pluralise($count)
{
return i18n::pluralise('MyObject.PLURALS', [
'one' => 'An object',
'other' => '{count} objects',
], $count);
}
:::ss
<%t MyObject.PLURALS 'An item|{count} items' count=$Count %>
Note: Template syntax for pluralisation is not yet available.
#### New asset storage mechanism
File system has been abstracted into an abstract interface. By default, the out of the box filesystem
@ -1322,20 +1312,18 @@ handle field-level and form-level messages. This has the following properties:
* Upgrade of i18n to symfony/translation
* Localisation based on language-only (without any specific locale) is now supported
* i18nEntityProvider::provideI18nEntities() Now is expected to return only a single array
* `i18nEntityProvider::provideI18nEntities()` Now is expected to return only a single array
map of key to default values.
* i18n keys for '.PLURAL_NAME' and '.SINGULAR_NAME' have been changed back to FQN class names
for all DataObject subclasses.
* i18n keys for '.PLURAL_NAME' and '.SINGULAR_NAME' have been changed back to use the namespaced class names
for all DataObject subclasses, rather than just the basename without namespace.
* i18n key for locale-respective pluralisation rules added as '.PLURALS'. These can be configured
within yaml in array format as per [ruby i18n pluralization rules](http://guides.rubyonrails.org/i18n.html#pluralization).
#### <a name="overview-i18n-removed"></a>i18n API Removed API
* Zend_Translate removed
* i18n::_t $context parameter deprecated
* i18n::_t Support for sprintf-style `%s` arguments deprecated
* i18n::_t Using non-associative injection with named parameters is now an error
* i18nEntityProvider no longer can collect strings for other modules.
* `Zend_Translate` removed
* `i18n::_t` Support for sprintf-style `%s` arguments deprecated
* `i18n::_t` Using non-associative injection with named parameters is now an error
### <a name="overview-mailer"></a>Email and Mailer

View File

@ -744,11 +744,11 @@ en:
other: '{count} Permission Role Codes'
SINGULARNAME: 'Permission Role Code'
SilverStripe\Security\RememberLoginHash:
PLURALNAME: 'Remember Login Hashs'
PLURALNAME: 'Login Hashes'
PLURALS:
one: 'A Remember Login Hash'
other: '{count} Remember Login Hashs'
SINGULARNAME: 'Remember Login Hash'
one: 'A Login Hash'
other: '{count} Login Hashes'
SINGULARNAME: 'Login Hash'
SiteTree:
TABMAIN: Main
TableListField:

View File

@ -143,14 +143,24 @@ function project()
}
/**
* @see i18n::_t()
*
* @param string $entity
* @param string $string
* @param array $injection
* @return string
*/
function _t($entity, $string = "", $injection = [])
* This is the main translator function. Returns the string defined by $entity according to the
* currently set locale.
*
* Also supports pluralisation of strings. Pass in a `count` argument, as well as a
* default value with `|` pipe-delimited options for each plural form.
*
* @param string $entity Entity that identifies the string. It must be in the form
* "Namespace.Entity" where Namespace will be usually the class name where this
* string is used and Entity identifies the string inside the namespace.
* @param mixed $arg,... Additional arguments are parsed as such:
* - Next string argument is a default. Pass in a `|` pipe-delimeted value with `{count}`
* to do pluralisation.
* - Any other string argument after default is context for i18nTextCollector
* - Any array argument in any order is an injection parameter list. Pass in a `count`
* injection parameter to pluralise.
* @return string
*/
function _t($entity, $arg = null)
{
// Pass args directly to handle deprecation
return call_user_func_array([i18n::class, '_t'], func_get_args());

View File

@ -21,6 +21,9 @@ use DateInterval;
*/
class RememberLoginHash extends DataObject
{
private static $singular_name = 'Login Hash';
private static $plural_name = 'Login Hashes';
private static $db = array (
'DeviceID' => 'Varchar(40)',
@ -29,7 +32,7 @@ class RememberLoginHash extends DataObject
);
private static $has_one = array (
'Member' => 'SilverStripe\\Security\\Member',
'Member' => Member::class,
);
private static $indexes = array(

View File

@ -22,9 +22,9 @@ interface MessageProvider
*
* @param string $entity Identifier for this message in Namespace.key format
* @param array|string $default Default message with pipe-separated delimiters, or array
* @param int $count Number to pluralise against
* @param array $injection List of injection variables
* @param int $count Number to pluralise against
* @return string Localised string
*/
public function pluralise($entity, $default, $count, $injection);
public function pluralise($entity, $default, $injection, $count);
}

View File

@ -4,6 +4,7 @@ namespace SilverStripe\i18n\Messages\Symfony;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Dev\Debug;
use SilverStripe\i18n\i18n;
use SilverStripe\i18n\Messages\Reader;
use Symfony\Component\Translation\Loader\ArrayLoader;
use Symfony\Component\Translation\PluralizationRules;
@ -15,24 +16,6 @@ use Symfony\Component\Translation\PluralizationRules;
*/
class ModuleYamlLoader extends ArrayLoader
{
use Configurable;
/**
* Map of rails plurals into symfony standard order
*
* @see PluralizationRules For symfony's implementation of this logic
* @config
* @var array
*/
private static $plurals = [
'zero',
'one',
'two',
'few',
'many',
'other',
];
/**
* Message reader
*
@ -123,7 +106,7 @@ class ModuleYamlLoader extends ArrayLoader
protected function normalisePlurals($key, $map, $locale)
{
$parts = [];
foreach ($this->config()->get('plurals') as $form) {
foreach (i18n::config()->get('plurals') as $form) {
if (isset($map[$form])) {
$parts[] = $map[$form];
}

View File

@ -104,7 +104,7 @@ class SymfonyMessageProvider implements MessageProvider
return $result;
}
public function pluralise($entity, $default, $count, $injection)
public function pluralise($entity, $default, $injection, $count)
{
if (is_array($default)) {
$default = $this->normalisePlurals($default);

View File

@ -67,6 +67,7 @@ class YamlReader implements Reader
}
}
}
ksort($messages);
return $messages;
}
}

View File

@ -3,6 +3,7 @@
namespace SilverStripe\i18n\Messages;
use SilverStripe\Assets\Filesystem;
use SilverStripe\i18n\i18n;
use Symfony\Component\Yaml\Dumper;
use SilverStripe\i18n\Messages\Symfony\ModuleYamlLoader;
use LogicException;
@ -10,6 +11,9 @@ use LogicException;
/**
* Write yml files compatible with ModuleYamlLoader
*
* Note: YamlWriter may not correctly denormalise plural strings if writing outside of the
* default locale (en).
*
* @see ModuleYamlLoader
*/
class YamlWriter implements Writer
@ -69,30 +73,78 @@ class YamlWriter implements Writer
*/
protected function denormaliseMessages($messages)
{
// Sort prior to denormalisation
ksort($messages);
$entities = [];
foreach ($messages as $entity => $value) {
// Skip un-namespaced keys
$value = $this->denormaliseValue($value);
// Non-nested key
if (strstr($entity, '.') === false) {
$entities[$entity] = $value;
continue;
}
$parts = explode('.', $entity);
$class = array_shift($parts);
// Ensure the `.ss` suffix gets added to the top level class rather than the key
if (count($parts) > 1 && reset($parts) === 'ss') {
$class .= '.ss';
array_shift($parts);
}
$key = implode('.', $parts);
// Get key nested within class
list($class, $key) = $this->getClassKey($entity);
if (!isset($entities[$class])) {
$entities[$class] = [];
}
$entities[$class][$key] = $value;
}
return $entities;
}
/**
* Convert entities array format into yml-ready string / array
*
* @param array|string $value Input value
* @return array|string denormalised value
*/
protected function denormaliseValue($value)
{
// Check plural form
$plurals = $this->getPluralForm($value);
if ($plurals) {
return $plurals;
}
// Non-plural non-array is already denormalised
if (!is_array($value)) {
return $value;
}
// Denormalise from default key
if (!empty($value['default'])) {
return $this->denormaliseValue($value['default']);
}
// No value
return null;
}
/**
* Get array-plural form for any value
*
* @param array|string $value
* @return array List of plural forms, or empty array if not plural
*/
protected function getPluralForm($value)
{
// Strip non-plural keys away
if (is_array($value)) {
$forms = i18n::config()->get('plurals');
$forms = array_combine($forms, $forms);
return array_intersect_key($value, $forms);
}
// Parse from string
// Note: Risky outside of en locale.
return i18n::parse_plurals($value);
}
/**
* Convert messages to yml ready to write
*
@ -108,4 +160,24 @@ class YamlWriter implements Writer
], 99);
return $content;
}
/**
* Determine class and key for a localisation entity
*
* @param string $entity
* @return array Two-length array with class and key as elements
*/
protected function getClassKey($entity)
{
$parts = explode('.', $entity);
$class = array_shift($parts);
// Ensure the `.ss` suffix gets added to the top level class rather than the key
if (count($parts) > 1 && reset($parts) === 'ss') {
$class .= '.ss';
array_shift($parts);
}
$key = implode('.', $parts);
return array($class, $key);
}
}

View File

@ -2,6 +2,7 @@
namespace SilverStripe\i18n\TextCollection;
use SilverStripe\i18n\i18n;
use SilverStripe\View\SSTemplateParser;
/**
@ -10,64 +11,105 @@ use SilverStripe\View\SSTemplateParser;
class Parser extends SSTemplateParser
{
/**
* Current entity
* List of all entities
*
* @var array
*/
protected $entities = [];
/**
* List of all entities
* Current entity
*
* @var array
*/
protected $currentEntity = [];
/**
* @param string $string
* Key of current entity
*
* @var string
*/
public function __construct($string)
protected $currentEntityKey = null;
/*
* Show warning if default omitted
*
* @var bool
*/
protected $warnIfEmpty = true;
/**
* @param string $string
* @param bool $warnIfEmpty
*/
public function __construct($string, $warnIfEmpty = true)
{
parent::__construct();
$this->string = $string;
$this->pos = 0;
$this->depth = 0;
$this->regexps = array();
$this->warnIfEmpty = $warnIfEmpty;
}
public function Translate__construct(&$res)
{
$this->currentEntity = [null, null];
$this->currentEntity = [];
$this->currentEntityKey = null;
}
public function Translate_Entity(&$res, $sub)
{
$this->currentEntity[0] = $sub['text']; // key
$this->currentEntityKey = $sub['text']; // key
}
public function Translate_Default(&$res, $sub)
{
$this->currentEntity[1] = $sub['String']['text']; // default
$this->currentEntity['default'] = $sub['String']['text']; // default
}
public function Translate_Context(&$res, $sub)
{
$this->currentEntity['comment'] = $sub['String']['text']; //comment
}
public function Translate__finalise(&$res)
{
// Capture entity if, and only if, a default vaule is provided
if ($this->currentEntity[1]) {
$this->entities[$this->currentEntity[0]] = $this->currentEntity[1];
// Validate entity
$entity = $this->currentEntity;
if (empty($entity['default'])) {
if ($this->warnIfEmpty) {
trigger_error("Missing localisation default for key " . $this->currentEntityKey, E_USER_NOTICE);
}
return;
}
// Detect plural forms
$plurals = i18n::parse_plurals($entity['default']);
if ($plurals) {
unset($entity['default']);
$entity = array_merge($entity, $plurals);
}
// If only default is set, simplify
if (count($entity) === 1 && !empty($entity['default'])) {
$entity = $entity['default'];
}
$this->entities[$this->currentEntityKey] = $entity;
}
/**
* Parses a template and returns any translatable entities
*
* @param string $template String to parse for translations
* @param bool $warnIfEmpty Show warnings if default omitted
* @return array Map of keys -> values
*/
public static function getTranslatables($template)
public static function getTranslatables($template, $warnIfEmpty = true)
{
// Run the parser and throw away the result
$parser = new Parser($template);
$parser = new Parser($template, $warnIfEmpty);
if (substr($template, 0, 3) == pack("CCC", 0xef, 0xbb, 0xbf)) {
$parser->pos = 3;
}

View File

@ -49,6 +49,13 @@ class i18nTextCollector
*/
protected $defaultLocale;
/**
* Trigger if warnings should be shown if default is omitted
*
* @var bool
*/
protected $warnOnEmptyDefault = false;
/**
* The directory base on which the collector should act.
* Usually the webroot set through {@link Director::baseFolder()}.
@ -95,6 +102,7 @@ class i18nTextCollector
: i18n::get_lang_from_locale(i18n::config()->get('default_locale'));
$this->basePath = Director::baseFolder();
$this->baseSavePath = Director::baseFolder();
$this->setWarnOnEmptyDefault(i18n::config()->get('missing_default_warning'));
}
/**
@ -418,20 +426,28 @@ class i18nTextCollector
// @see CMSMenu::provideI18nEntities for an example usage
foreach ($entitiesByModule[$module] as $fullName => $spec) {
$specModule = $module;
$specDefault = $spec;
// Rewrite spec if module is specified
if (is_array($spec) && isset($spec['module'])) {
$specModule = $spec['module'];
$specDefault = $spec['default'];
unset($spec['module']);
// If only element is defalt, simplify
if (count($spec) === 1 && !empty($spec['default'])) {
$spec = $spec['default'];
}
}
// Remove from source module
if ($specModule !== $module) {
unset($entitiesByModule[$module][$fullName]);
}
// Write to target module
if (!isset($entitiesByModule[$specModule])) {
$entitiesByModule[$specModule] = [];
}
$entitiesByModule[$specModule][$fullName] = $specDefault;
$entitiesByModule[$specModule][$fullName] = $spec;
}
}
return $entitiesByModule;
@ -532,30 +548,43 @@ class i18nTextCollector
$tokens = token_get_all("<?php\n" . $content);
$inTransFn = false;
$inConcat = false;
$finalTokenDueToArray = false;
$inArrayClosedBy = false; // Set to the expected closing token, or false if not in array
$currentEntity = array();
foreach ($tokens as $token) {
if (is_array($token)) {
list($id, $text) = $token;
if ($inTransFn && $id == T_ARRAY) {
//raw 'array' token found in _t function, stop processing the tokens for this _t now
$finalTokenDueToArray = true;
// Suppress tokenisation within array
if ($inTransFn && !$inArrayClosedBy && $id == T_ARRAY) {
$inArrayClosedBy = ')'; // Array will close with this element
continue;
}
// Start definition
if ($id == T_STRING && $text == '_t') {
// start definition
$inTransFn = true;
} elseif ($inTransFn && (
in_array($id, [T_VARIABLE, T_STATIC, T_CLASS_C]) ||
continue;
}
// Skip rest of processing unless we are in a translation, and not inside a nested array
if (!$inTransFn || $inArrayClosedBy) {
continue;
}
// If inside this translation, some elements might be unreachable
if (in_array($id, [T_VARIABLE, T_STATIC, T_CLASS_C]) ||
($id === T_STRING && in_array($text, ['self', 'static', 'parent']))
)) {
) {
// Un-collectable strings such as _t(static::class.'.KEY').
// Should be provided by i18nEntityProvider instead
$inTransFn = false;
$inArrayClosedBy = false;
$inConcat = false;
$currentEntity = array();
} elseif ($inTransFn && $id == T_CONSTANT_ENCAPSED_STRING) {
continue;
}
if ($id == T_CONSTANT_ENCAPSED_STRING) {
// Fixed quoting escapes, and remove leading/trailing quotes
if (preg_match('/^\'/', $text)) {
$text = str_replace("\\'", "'", $text);
@ -578,30 +607,75 @@ class i18nTextCollector
$currentEntity[] = $text;
}
}
} elseif ($inTransFn && $token == '.') {
$inConcat = true;
} elseif ($inTransFn && $token == ',') {
$inConcat = false;
} elseif ($inTransFn && ($token == ')' || $finalTokenDueToArray || $token == '[')) {
// finalize definition
$inTransFn = false;
$inConcat = false;
// Only collect translations with default values provided
if (!empty($currentEntity[1])) {
$entities[$currentEntity[0]] = $currentEntity[1];
} elseif (!empty($currentEntity[0])) {
// Add minor notice
trigger_error("Missing localisation default for key ".$currentEntity[0], E_USER_NOTICE);
}
$currentEntity = array();
$finalTokenDueToArray = false;
continue; // is_array
}
// Test we can close this array
if ($inTransFn && $inArrayClosedBy && ($token === $inArrayClosedBy)) {
$inArrayClosedBy = false;
continue;
}
// Continue only if in translation and not in array
if (!$inTransFn || $inArrayClosedBy) {
continue;
}
switch ($token) {
case '.':
$inConcat = true;
break;
case ',':
$inConcat = false;
break;
case '[':
// Enter array
$inArrayClosedBy = ']';
break;
case ')':
// finalize definition
$inTransFn = false;
$inConcat = false;
// Ensure key is valid before saving
if (!empty($currentEntity[0])) {
$key = $currentEntity[0];
$default = '';
$comment = '';
if (!empty($currentEntity[1])) {
$default = $currentEntity[1];
if (!empty($currentEntity[2])) {
$comment = $currentEntity[2];
}
}
// Save in appropriate format
if ($default) {
$plurals = i18n::parse_plurals($default);
// Use array form if either plural or metadata is provided
if ($plurals) {
$entity = $plurals;
} elseif ($comment) {
$entity = ['default' => $default];
} else {
$entity = $default;
}
if ($comment) {
$entity['comment'] = $comment;
}
$entities[$key] = $entity;
} elseif ($this->getWarnOnEmptyDefault()) {
trigger_error("Missing localisation default for key " . $currentEntity[0], E_USER_NOTICE);
}
}
$currentEntity = array();
$inArrayClosedBy = false;
break;
}
}
// Normalise all keys
foreach ($entities as $key => $default) {
foreach ($entities as $key => $entity) {
unset($entities[$key]);
$entities[$this->normalizeEntity($key, $module)] = $default;
$entities[$this->normalizeEntity($key, $module)] = $entity;
}
ksort($entities);
@ -620,7 +694,7 @@ class i18nTextCollector
public function collectFromTemplate($content, $fileName, $module, &$parsedFiles = array())
{
// use parser to extract <%t style translatable entities
$entities = Parser::getTranslatables($content);
$entities = Parser::getTranslatables($content, $this->getWarnOnEmptyDefault());
// use the old method of getting _t() style translatable entities
// Collect in actual template
@ -675,15 +749,18 @@ class i18nTextCollector
// Detect non-associative result for any key
if (is_array($value) && $value === array_values($value)) {
Deprecation::notice('5.0', 'Non-associative translations from providei18nEntities is deprecated');
if (!empty($value[2])) {
$provided[$key] = [
'default' => $value[0],
'module' => $value[2],
];
} else {
$entity = array_filter([
'default' => $value[0],
'comment' => isset($value[1]) ? $value[1] : null,
'module' => isset($value[2]) ? $value[2] : null,
]);
if (count($entity) === 1) {
$provided[$key] = $value[0];
} elseif ($entity) {
$provided[$key] = $entity;
} else {
unset($provided[$key]);
}
}
}
$entities = array_merge($entities, $provided);
@ -777,4 +854,22 @@ class i18nTextCollector
{
$this->defaultLocale = $locale;
}
/**
* @return bool
*/
public function getWarnOnEmptyDefault()
{
return $this->warnOnEmptyDefault;
}
/**
* @param bool $warnOnEmptyDefault
* @return $this
*/
public function setWarnOnEmptyDefault($warnOnEmptyDefault)
{
$this->warnOnEmptyDefault = $warnOnEmptyDefault;
return $this;
}
}

View File

@ -1956,56 +1956,80 @@ class i18n implements TemplateGlobalProvider
);
/**
* This is the main translator function. Returns the string defined by $class and $entity according to the
* Map of rails plurals into standard order (fewest to most)
* Note: Default locale only supplies one|other, but non-default locales
* can specify custom plurals.
*
* @config
* @var array
*/
private static $plurals = [
'zero',
'one',
'two',
'few',
'many',
'other',
];
/**
* Plural forms in default (en) locale
*
* @var array
*/
private static $default_plurals = [
'one',
'other',
];
/**
* Warn if _t() invoked without a default.
*
* @config
* @var bool
*/
private static $missing_default_warning = true;
/**
* This is the main translator function. Returns the string defined by $entity according to the
* currently set locale.
*
* Also supports pluralisation of strings. Pass in a `count` argument, as well as a
* default value with `|` pipe-delimited options for each plural form.
*
* @param string $entity Entity that identifies the string. It must be in the form
* "Namespace.Entity" where Namespace will be usually the class name where this
* string is used and Entity identifies the string inside the namespace.
* @param string $default The original string itself. In a usual call this is a
* mandatory parameter, but if you are reusing a string which has already been
* "declared" (using another call to this function, with the same class and entity),
* you can omit it.
* @param array $injection (optional) array of key value pairs that are used
* to replace corresponding expressions in {curly brackets} in the $string.
* The injection array can also be used as the their argument to the _t() function
* @return string The translated string, according to the currently set locale {@link i18n::set_locale()}
* @param mixed $arg,... Additional arguments are parsed as such:
* - Next string argument is a default. Pass in a `|` pipe-delimited value with `{count}`
* to do pluralisation.
* - Any other string argument after default is context for i18nTextCollector
* - Any array argument in any order is an injection parameter list. Pass in a `count`
* injection parameter to pluralise.
* @return string
*/
public static function _t($entity, $default = '', $injection = [])
public static function _t($entity, $arg = null)
{
// Deprecate passing in injection as second param
if (is_array($default)) {
Deprecation::notice('5.0', 'Passing in $injection as second parameter is deprecated');
$injection = $default;
$default = '';
// Detect args
$default = null;
$injection = [];
foreach (array_slice(func_get_args(), 1) as $arg) {
if (is_array($arg)) {
$injection = $arg;
} elseif (!isset($default)) {
$default = $arg ?: '';
}
}
// Encourage the provision of default values so that text collector can discover new strings
if (!$default) {
user_error(
"Localisation without a default is deprecated (key: $entity) and will be an exception in 5.0",
E_USER_WARNING
);
}
// Deprecate old $context param
if (!is_array($injection)) {
// Don't need to show warning if only mistake is passing in null instead of empty array
if ($injection || func_num_args() > 3) {
Deprecation::notice('5.0', '$context parameter is deprecated');
}
// Find best injection array
if (func_num_args() > 3 && is_array(func_get_arg(3))) {
$injection = func_get_arg(3);
} else {
$injection = [];
}
if (!$default && static::config()->get('missing_default_warning')) {
user_error("Missing default for localisation key $entity", E_USER_WARNING);
}
// Deprecate legacy injection format (`string %s, %d`)
// inject the variables from injectionArray (if present)
$sprintfArgs = [];
if ($default && $injection && !preg_match('/\{[\w\d]*\}/i', $default) && preg_match('/%[s,d]/', $default)) {
if ($default && !preg_match('/\{[\w\d]*\}/i', $default) && preg_match('/%[s,d]/', $default)) {
Deprecation::notice('5.0', 'sprintf style localisation variables are deprecated');
$sprintfArgs = array_values($injection);
$injection = [];
@ -2019,8 +2043,20 @@ class i18n implements TemplateGlobalProvider
$injection = [];
}
// Detect plurals: Has a {count} argument as well as a `|` pipe delimited string (if provided)
$isPlural = isset($injection['count']);
$count = $isPlural ? $injection['count'] : null;
// Refine check against default
if ($isPlural && $default && !static::parse_plurals($default)) {
$isPlural = false;
}
// Pass back to translation backend
$result = static::getMessageProvider()->translate($entity, $default, $injection);
if ($isPlural) {
$result = static::getMessageProvider()->pluralise($entity, $default, $injection, $count);
} else {
$result = static::getMessageProvider()->translate($entity, $default, $injection);
}
// Sometimes default is omitted, so we don't know we have %s injection format until after translation
if (!$default && !preg_match('/\{[\w\d]*\}/i', $result) && preg_match('/%[s,d]/', $result)) {
@ -2043,37 +2079,44 @@ class i18n implements TemplateGlobalProvider
}
/**
* Pluralise an item or items.
* Split plural string into standard CLDR array form.
* A string is considered a pluralised form if it has a {count} argument, and
* a single `|` pipe-delimiting character.
*
* Yaml form of these strings should be set via the rails i18n standard format
* http://guides.rubyonrails.org/i18n.html#pluralization. For example:
* Note: Only splits in the default (en) locale as the string form contains limited metadata.
*
* <code>
* en:
* ChangeSet:
* DESCRIPTION_ITEM_PLURALS:
* one: 'one item'
* other: '{count} items'
* </code>
*
* Some languages support up to 6 plural forms:
* @link http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
*
* @todo text collection support for pluralised strings
*
* @param string $entity Entity that identifies the string. It must be in the form
* "Namespace.Entity" where Namespace will be usually the class name where this
* string is used and Entity identifies the string inside the namespace.
* Standard convention is to have a `Class.<FIELD>_PLURALS` key for a <Field> on a class
* @param string|array $default If passed as a string, treated as a symfony format specifier.
* If passed as an array, treated as a ruby i18n pluralised form.
* @param int $count Number to pluralise against
* @param array $injection Additional parameters
* @return string Localised number
* @param string $string Input string
* @return array List of plural forms, or empty array if not plural
*/
public static function pluralise($entity, $default = '', $count = 0, $injection = [])
public static function parse_plurals($string)
{
return static::getMessageProvider()->pluralise($entity, $default, $count, $injection);
if (strstr($string, '|') && strstr($string, '{count}')) {
$keys = i18n::config()->get('default_plurals');
$values = explode('|', $string);
if (count($keys) == count($values)) {
return array_combine($keys, $values);
}
}
return [];
}
/**
* Convert CLDR array plural form to `|` pipe-delimited string.
* Unlike parse_plurals, this supports all locale forms (not just en)
*
* @param array $plurals
* @return string Delimited string, or null if not plurals
*/
public static function encode_plurals($plurals)
{
// Validate against global plural list
$forms = i18n::config()->get('plurals');
$forms = array_combine($forms, $forms);
$intersect = array_intersect_key($plurals, $forms);
if ($intersect) {
return implode('|', $intersect);
}
return null;
}
/**
@ -2452,7 +2495,6 @@ class i18n implements TemplateGlobalProvider
'i18nLocale' => 'get_locale',
'get_locale',
'i18nScriptDirection' => 'get_script_direction',
'pluralise',
);
}

View File

@ -172,7 +172,10 @@ class MemberAuthenticatorTest extends SapphireTest
);
$form->restoreFormState();
$this->assertEmpty($result);
$this->assertEquals(_t('Member.ERRORWRONGCRED'), $form->getMessage());
$this->assertEquals(
_t('Member.ERRORWRONGCRED', 'The provided details don\'t seem to be correct. Please try again.'),
$form->getMessage()
);
$this->assertEquals(ValidationResult::TYPE_ERROR, $form->getMessageType());
$this->assertEquals(ValidationResult::CAST_TEXT, $form->getMessageCast());
}

View File

@ -2,7 +2,6 @@
namespace SilverStripe\i18n\Tests;
use SilverStripe\Dev\Debug;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\i18n\Messages\YamlReader;
@ -27,11 +26,15 @@ class YamlReaderTest extends SapphireTest
'i18nTestModule.WITHNAMESPACE' => 'Include Entity with Namespace',
'i18nTestModule.LAYOUTTEMPLATE' => 'Layout Template',
'i18nTestModule.SPRINTFNAMESPACE' => 'My replacement: %s',
'i18nTestModule.PLURAL' => [
'i18nTestModuleInclude.ss.SPRINTFINCLUDENAMESPACE' => 'My include replacement: %s',
'i18nTestModule.PLURALS' => [
'one' => 'A test',
'other' => '{count} tests',
],
'i18nTestModuleInclude.ss.SPRINTFINCLUDENAMESPACE' => 'My include replacement: %s',
'Month.PLURALS' => [
'one' => 'A month',
'other' => '{count} months',
],
];
$this->assertEquals($expected, $output);
}

View File

@ -15,25 +15,47 @@ class YamlWriterTest extends SapphireTest
'Level1.Level2.EntityName' => 'Text',
'Level1.OtherEntityName' => 'Other Text',
'Level1.Plurals' => [
'context' => 'Some ignored context',
'one' => 'An item',
'other' => '{count} items',
],
'Level1.PluralString1' => 'An item|{count} items',
'Level1.PluralString2' => [
'context' => 'Another ignored context',
'default' => 'An item|{count} items',
],
// Some near-false-positives for plurals
'Level1.NotPlural1' => 'Not a plural|string', // no count
'Level1.NotPlural2' => 'Not|a|plural|string{count}', // unexpected number
'Level1.NotPlural3' => 'Not a plural string {count}', // no pipe
'Level1.BoolTest' => 'True',
'Level1.FlagTest' => 'No',
'Level1.TextTest' => 'Maybe',
'Template.ss.Key' => 'Template var',
'TopLevel' => 'The Top',
];
$yaml = <<<YAML
de:
Level1:
BoolTest: 'True'
FlagTest: 'No'
Level2.EntityName: Text
NotPlural1: 'Not a plural|string'
NotPlural2: 'Not|a|plural|string{count}'
NotPlural3: 'Not a plural string {count}'
OtherEntityName: 'Other Text'
PluralString1:
one: 'An item'
other: '{count} items'
PluralString2:
one: 'An item'
other: '{count} items'
Plurals:
one: 'An item'
other: '{count} items'
BoolTest: 'True'
FlagTest: 'No'
TextTest: Maybe
Template.ss:
Key: 'Template var'
TopLevel: 'The Top'
YAML;

View File

@ -49,6 +49,8 @@ class i18nTest extends SapphireTest
'fr_FR',
'de_AT',
'de_DE',
'ja_JP',
'pl_PL',
'es_AR',
'es_ES',
'mi_NZ',
@ -147,6 +149,7 @@ class i18nTest extends SapphireTest
public function testTemplateTranslation()
{
$oldLocale = i18n::get_locale();
i18n::config()->update('missing_default_warning', false);
/** @var SymfonyMessageProvider $provider */
$provider = Injector::inst()->get(MessageProvider::class);
@ -322,6 +325,8 @@ class i18nTest extends SapphireTest
* */
public function testNewTemplateTranslation()
{
i18n::config()->update('missing_default_warning', false);
/** @var SymfonyMessageProvider $provider */
$provider = Injector::inst()->get(MessageProvider::class);
$provider->getTranslator()->addResource(
@ -432,6 +437,46 @@ class i18nTest extends SapphireTest
);
}
public function pluralisationDataProvider()
{
return [
// English - 2 plural forms
['en_NZ', 0, '0 months'],
['en_NZ', 1, 'A month'],
['en_NZ', 2, '2 months'],
['en_NZ', 5, '5 months'],
['en_NZ', 10, '10 months'],
// Polish - 4 plural forms
['pl_PL', 0, '0 miesięcy'],
['pl_PL', 1, '1 miesiąc'],
['pl_PL', 2, '2 miesiące'],
['pl_PL', 5, '5 miesięcy'],
['pl_PL', 10, '10 miesięcy'],
// Japanese - 1 plural form
['ja_JP', 0, '0日'],
['ja_JP', 1, '1日'],
['ja_JP', 2, '2日'],
['ja_JP', 5, '5日'],
['ja_JP', 10, '10日'],
];
}
/**
* @dataProvider pluralisationDataProvider()
* @param string $locale
* @param int $count
* @param string $expected
*/
public function testPluralisation($locale, $count, $expected)
{
i18n::set_locale($locale);
$this->assertEquals(
$expected,
_t('Month.PLURALS', 'A month|{count} months', ['count' => $count]),
"Plural form in locale $locale with count $count should be $expected"
);
}
public function testGetLanguageName()
{
i18n::config()->update(

View File

@ -9,10 +9,12 @@ class i18nProviderClass implements i18nEntityProvider
return [
'i18nProviderClass.TITLE' => 'My Provider Class',
'i18nProviderClass.PLURALS' => [
'comment' => 'Plural forms for the test class',
'one' => 'A class',
'other' => '{count} classes',
],
'i18nProviderClass.OTHER_MODULE' => [
'comment' => 'Test string in another module',
'default' => 'i18ntestmodule string defined in i18nothermodule',
'module' => 'i18ntestmodule'
],

View File

@ -11,8 +11,12 @@ en:
WITHNAMESPACE: Include Entity with Namespace
LAYOUTTEMPLATE: Layout Template
SPRINTFNAMESPACE: My replacement: %s
PLURAL:
PLURALS:
one: 'A test'
other: '{count} tests'
i18nTestModuleInclude.ss:
SPRINTFINCLUDENAMESPACE: My include replacement: %s
Month:
PLURALS:
one: 'A month'
other: '{count} months'

View File

@ -0,0 +1,4 @@
ja:
Month:
PLURALS:
other: '{count}日'

View File

@ -0,0 +1,7 @@
pl:
Month:
PLURALS:
one: '1 miesiąc'
few: '{count} miesiące'
many: '{count} miesięcy'
other: '{count} miesiąca'

View File

@ -8,4 +8,5 @@
<%t i18nTestModule.INJECTIONS_DOES_NOT_EXIST "Hello {name} {greeting}. But it is late, {goodbye}" name="Mark" greeting="welcome" goodbye="bye" %>
<%t i18nTestModule.INJECTIONS "Hello {name} {greeting}. But it is late, {goodbye}" name="Paul" greeting="good you are here" goodbye="see you" %>
<%t i18nTestModule.INJECTIONS "Hello {name} {greeting}. But it is late, {goodbye}" is "New context (this should be ignored)" name="Steffen" greeting="willkommen" goodbye="wiedersehen" %>
<%t i18nTestModule.INJECTIONS "Hello {name} {greeting}. But it is late, {goodbye}" name=$absoluteBaseURL greeting=$get_locale goodbye="global calls" %>
<%t i18nTestModule.INJECTIONS name="Cat" greeting='meow' goodbye="meow" %>
<%t i18nTestModule.INJECTIONS name=$absoluteBaseURL greeting=$get_locale goodbye="global calls" %>

View File

@ -2,6 +2,6 @@
$Layout
lonely _t() call that should be ignored
<% _t('i18nTestModule.NEWENTITY',"Not stored in master file yet") %>
Single: $pluralise('i18nTestModule.PLURALS', 'An item|{count} items', 1)
Multiple: $pluralise('i18nTestModule.PLURALS', 'An item|{count} items', 4)
None: $pluralise('i18nTestModule.PLURALS', 'An item|{count} items', 0)
Single: <%t i18nTestModule.PLURALS 'An item|{count} items' count=1 %>
Multiple: <%t i18nTestModule.PLURALS 'An item|{count} items' count=4 %>
None: <%t i18nTestModule.PLURALS 'An item|{count} items' count=0 %>

View File

@ -60,7 +60,10 @@ _t(
PHP;
$this->assertEquals(
array(
'Test.CONCATENATED' => "Line 1 and Line '2' and Line \"3\"",
'Test.CONCATENATED' => [
'default' => "Line 1 and Line '2' and Line \"3\"",
'comment' => 'Comment'
],
'Test.CONCATENATED2' => "Line \"4\" and Line 5"
),
$c->collectFromCode($php, 'mymodule')
@ -70,6 +73,8 @@ PHP;
public function testCollectFromNewTemplateSyntaxUsingParserSubclass()
{
$c = i18nTextCollector::create();
$c->setWarnOnEmptyDefault(false);
$html = <<<SS
<% _t('Test.SINGLEQUOTE','Single Quote'); %>
<%t i18nTestModule.NEWMETHODSIG "New _t method signature test" %>
@ -78,6 +83,7 @@ PHP;
<%t i18nTestModule.INJECTIONS_2 "Hello {name} {greeting}" is "context (ignored)" name="Steffen" greeting="Wilkommen" %>
<%t i18nTestModule.INJECTIONS_3 name="Cat" greeting='meow' goodbye="meow" %>
<%t i18nTestModule.INJECTIONS_4 name=\$absoluteBaseURL greeting=\$get_locale goodbye="global calls" %>
<%t i18nTestModule.INJECTIONS_9 "An item|{count} items" is "Test Pluralisation" count=4 %>
SS;
$c->collectFromTemplate($html, 'mymodule', 'Test');
@ -87,10 +93,26 @@ SS;
'i18nTestModule.NEWMETHODSIG' => "New _t method signature test",
'i18nTestModule.INJECTIONS_0' => "Hello {name} {greeting}, and {goodbye}",
'i18nTestModule.INJECTIONS_1' => "Hello {name} {greeting}, and {goodbye}",
'i18nTestModule.INJECTIONS_2' => "Hello {name} {greeting}",
'i18nTestModule.INJECTIONS_2' => [
'default' => "Hello {name} {greeting}",
'comment' => 'context (ignored)',
],
'i18nTestModule.INJECTIONS_9' => [
'one' => 'An item',
'other' => '{count} items',
'comment' => 'Test Pluralisation'
],
],
$c->collectFromTemplate($html, 'mymodule', 'Test')
);
// Test warning is raised on empty default
$c->setWarnOnEmptyDefault(true);
$this->setExpectedException(
PHPUnit_Framework_Error_Notice::class,
'Missing localisation default for key i18nTestModule.INJECTIONS_3'
);
$c->collectFromTemplate($html, 'mymodule', 'Test');
}
public function testCollectFromTemplateSimple()
@ -125,6 +147,7 @@ SS;
public function testCollectFromTemplateAdvanced()
{
$c = i18nTextCollector::create();
$c->setWarnOnEmptyDefault(false);
$html = <<<SS
<% _t(
@ -145,7 +168,10 @@ SS;
) %>
SS;
$this->assertEquals(
[ 'Test.PRIOANDCOMMENT' => ' Prio and Value with "Double Quotes"' ],
[ 'Test.PRIOANDCOMMENT' => [
'default' => ' Prio and Value with "Double Quotes"',
'comment' => 'Comment with "Double Quotes"',
]],
$c->collectFromTemplate($html, 'mymodule', 'Test')
);
@ -158,9 +184,29 @@ SS;
) %>
SS;
$this->assertEquals(
[ 'Test.PRIOANDCOMMENT' => " Prio and Value with 'Single Quotes'" ],
[ 'Test.PRIOANDCOMMENT' => [
'default' => " Prio and Value with 'Single Quotes'",
'comment' => "Comment with 'Single Quotes'",
]],
$c->collectFromTemplate($html, 'mymodule', 'Test')
);
// Test empty
$html = <<<SS
<% _t('Test.PRIOANDCOMMENT') %>
SS;
$this->assertEquals(
[],
$c->collectFromTemplate($html, 'mymodule', 'Test')
);
// Test warning is raised on empty default
$c->setWarnOnEmptyDefault(true);
$this->setExpectedException(
PHPUnit_Framework_Error_Notice::class,
'Missing localisation default for key Test.PRIOANDCOMMENT'
);
$c->collectFromTemplate($html, 'mymodule', 'Test');
}
@ -209,7 +255,12 @@ _t(
);
PHP;
$this->assertEquals(
[ 'Test.PRIOANDCOMMENT' => ' Value with "Double Quotes"' ],
[
'Test.PRIOANDCOMMENT' => [
'default' => ' Value with "Double Quotes"',
'comment' => 'Comment with "Double Quotes"',
]
],
$c->collectFromCode($php, 'mymodule')
);
@ -222,7 +273,10 @@ _t(
);
PHP;
$this->assertEquals(
[ 'Test.PRIOANDCOMMENT' => " Value with 'Single Quotes'" ],
[ 'Test.PRIOANDCOMMENT' => [
'default' => " Value with 'Single Quotes'",
'comment' => "Comment with 'Single Quotes'"
] ],
$c->collectFromCode($php, 'mymodule')
);
@ -241,6 +295,8 @@ PHP;
_t(
'Test.PRIOANDCOMMENT',
"Doublequoted Value with 'Unescaped Single Quotes'"
);
PHP;
$this->assertEquals(
@ -287,6 +343,7 @@ PHP;
public function testCollectFromCodeNewSignature()
{
$c = i18nTextCollector::create();
$c->setWarnOnEmptyDefault(false); // Disable warnings for tests
$php = <<<PHP
_t('i18nTestModule.NEWMETHODSIG',"New _t method signature test");
@ -295,25 +352,39 @@ _t('i18nTestModule.INJECTIONS2', "Hello {name} {greeting}. But it is late, {good
_t("i18nTestModule.INJECTIONS3", "Hello {name} {greeting}. But it is late, {goodbye}",
"New context (this should be ignored)",
array("name"=>"Steffen", "greeting"=>"willkommen", "goodbye"=>"wiedersehen"));
_t('i18nTestModule.INJECTIONS4', array("name"=>"Cat", "greeting"=>"meow", "goodbye"=>"meow"));
_t('i18nTestModule.INJECTIONS6', "Hello {name} {greeting}. But it is late, {goodbye}",
["name"=>"Paul", "greeting"=>"good you are here", "goodbye"=>"see you"]);
_t("i18nTestModule.INJECTIONS7", "Hello {name} {greeting}. But it is late, {goodbye}",
"New context (this should be ignored)",
["name"=>"Steffen", "greeting"=>"willkommen", "goodbye"=>"wiedersehen"]);
_t('i18nTestModule.INJECTIONS8', ["name"=>"Cat", "greeting"=>"meow", "goodbye"=>"meow"]);
_t('i18nTestModule.INJECTIONS9', "An item|{count} items", ['count' => 4], "Test Pluralisation");
PHP;
$collectedTranslatables = $c->collectFromCode($php, 'mymodule');
$expectedArray = [
'i18nTestModule.INJECTIONS2' => "Hello {name} {greeting}. But it is late, {goodbye}",
'i18nTestModule.INJECTIONS3' => "Hello {name} {greeting}. But it is late, {goodbye}",
'i18nTestModule.INJECTIONS3' => [
'default' => "Hello {name} {greeting}. But it is late, {goodbye}",
'comment' => 'New context (this should be ignored)'
],
'i18nTestModule.INJECTIONS6' => "Hello {name} {greeting}. But it is late, {goodbye}",
'i18nTestModule.INJECTIONS7' => "Hello {name} {greeting}. But it is late, {goodbye}",
'i18nTestModule.INJECTIONS7' => [
'default' => "Hello {name} {greeting}. But it is late, {goodbye}",
'comment' => "New context (this should be ignored)",
],
'i18nTestModule.INJECTIONS9' => [
'one' => 'An item',
'other' => '{count} items',
'comment' => 'Test Pluralisation',
],
'i18nTestModule.NEWMETHODSIG' => "New _t method signature test",
];
$this->assertEquals($expectedArray, $collectedTranslatables);
// Test warning is raised
// Test warning is raised on empty default
$this->setExpectedException(
PHPUnit_Framework_Error_Notice::class,
'Missing localisation default for key i18nTestModule.INJECTIONS4'
@ -321,6 +392,7 @@ PHP;
$php = <<<PHP
_t('i18nTestModule.INJECTIONS4', array("name"=>"Cat", "greeting"=>"meow", "goodbye"=>"meow"));
PHP;
$c->setWarnOnEmptyDefault(true);
$c->collectFromCode($php, 'mymodule');
}
@ -345,6 +417,7 @@ PHP;
public function testCollectFromIncludedTemplates()
{
$c = i18nTextCollector::create();
$c->setWarnOnEmptyDefault(false); // Disable warnings for tests
$templateFilePath = $this->alternateBasePath . '/i18ntestmodule/templates/Layout/i18nTestModule.ss';
$html = file_get_contents($templateFilePath);
@ -419,6 +492,7 @@ PHP;
public function testCollectMergesWithExisting()
{
$c = i18nTextCollector::create();
$c->setWarnOnEmptyDefault(false);
$c->setWriter(new YamlWriter());
$c->basePath = $this->alternateBasePath;
$c->baseSavePath = $this->alternateBaseSavePath;
@ -441,18 +515,22 @@ PHP;
$entitiesByModule['i18ntestmodule']
);
$this->assertEquals(
'i18ntestmodule string defined in i18nothermodule',
[
'comment' => 'Test string in another module',
'default' => 'i18ntestmodule string defined in i18nothermodule',
],
$entitiesByModule['i18ntestmodule']['i18nProviderClass.OTHER_MODULE']
);
}
public function testCollectFromFilesystemAndWriteMasterTables()
{
$local = i18n::get_locale();
i18n::set_locale('en_US'); //set the locale to the US locale expected in the asserts
i18n::config()->update('default_locale', 'en_US');
i18n::config()->update('missing_default_warning', false);
$c = i18nTextCollector::create();
$c->setWarnOnEmptyDefault(false);
$c->setWriter(new YamlWriter());
$c->basePath = $this->alternateBasePath;
$c->baseSavePath = $this->alternateBaseSavePath;
@ -564,8 +642,6 @@ PHP;
" MAINTEMPLATE: 'Theme2 Main Template'\n",
$theme2LangFileContent
);
i18n::set_locale($local); //set the locale to the US locale expected in the asserts
}
public function testCollectFromEntityProvidersInCustomObject()
@ -574,6 +650,8 @@ PHP;
$this->popManifests();
$c = i18nTextCollector::create();
// Collect from MyObject.php
$filePath = __DIR__ . '/i18nTest/MyObject.php';
$matches = $c->collectFromEntityProviders($filePath);
$this->assertEquals(
@ -593,6 +671,36 @@ PHP;
);
}
public function testCollectFromEntityProvidersInWebRoot()
{
// Collect from i18nProviderClass
$c = i18nTextCollector::create();
$c->setWarnOnEmptyDefault(false);
$c->setWriter(new YamlWriter());
$c->basePath = $this->alternateBasePath;
$c->baseSavePath = $this->alternateBaseSavePath;
$entitiesByModule = $c->collect(null, false);
$this->assertEquals(
[
'comment' => 'Plural forms for the test class',
'one' => 'A class',
'other' => '{count} classes',
],
$entitiesByModule['i18nothermodule']['i18nProviderClass.PLURALS']
);
$this->assertEquals(
'My Provider Class',
$entitiesByModule['i18nothermodule']['i18nProviderClass.TITLE']
);
$this->assertEquals(
[
'comment' => 'Test string in another module',
'default' => 'i18ntestmodule string defined in i18nothermodule',
],
$entitiesByModule['i18ntestmodule']['i18nProviderClass.OTHER_MODULE']
);
}
/**
* Test that duplicate keys are resolved to the appropriate modules
*/