Commit 4b06b8bd authored by webchick's avatar webchick

Issue #2111349 by dawehner, tim.plunkett: Move format_plural to the string...

Issue #2111349 by dawehner, tim.plunkett: Move format_plural to the string translation service and format_interval to the date service.
parent 6d0ba6bf
......@@ -585,7 +585,7 @@ services:
arguments: ['@module_handler']
date:
class: Drupal\Core\Datetime\Date
arguments: ['@entity.manager', '@language_manager']
arguments: ['@entity.manager', '@language_manager', '@string_translation']
feed.bridge.reader:
class: Drupal\Component\Bridge\ZfExtensionManagerSfContainer
calls:
......
......@@ -897,39 +897,11 @@ function format_xml_elements($array) {
*
* @see t()
* @see format_string()
*
* @deprecated as of Drupal 8.0. Use \Drupal::translation()->formatPlural()
*/
function format_plural($count, $singular, $plural, array $args = array(), array $options = array()) {
$args['@count'] = $count;
// Join both forms to search a translation.
$tranlatable_string = implode(LOCALE_PLURAL_DELIMITER, array($singular, $plural));
// Translate as usual.
$translated_strings = t($tranlatable_string, $args, $options);
// Split joined translation strings into array.
$translated_array = explode(LOCALE_PLURAL_DELIMITER, $translated_strings);
if ($count == 1) {
return $translated_array[0];
}
// Get the plural index through the gettext formula.
// @todo implement static variable to minimize function_exists() usage.
$index = (function_exists('locale_get_plural')) ? locale_get_plural($count, isset($options['langcode']) ? $options['langcode'] : NULL) : -1;
if ($index == 0) {
// Singular form.
return $translated_array[0];
}
else {
if (isset($translated_array[$index])) {
// N-th plural form.
return $translated_array[$index];
}
else {
// If the index cannot be computed or there's no translation, use
// the second plural form as a fallback (which allows for most flexiblity
// with the replaceable @count value).
return $translated_array[1];
}
}
return \Drupal::translation()->formatPlural($count, $singular, $plural, $args, $options);
}
/**
......@@ -1007,31 +979,11 @@ function format_size($size, $langcode = NULL) {
*
* @return
* A translated string representation of the interval.
*
* @deprecated as of Drupal 8.0. Use \Drupal::service('date')->formatInterval().
*/
function format_interval($interval, $granularity = 2, $langcode = NULL) {
$units = array(
'1 year|@count years' => 31536000,
'1 month|@count months' => 2592000,
'1 week|@count weeks' => 604800,
'1 day|@count days' => 86400,
'1 hour|@count hours' => 3600,
'1 min|@count min' => 60,
'1 sec|@count sec' => 1
);
$output = '';
foreach ($units as $key => $value) {
$key = explode('|', $key);
if ($interval >= $value) {
$output .= ($output ? ' ' : '') . format_plural(floor($interval / $value), $key[0], $key[1], array(), array('langcode' => $langcode));
$interval %= $value;
$granularity--;
}
if ($granularity == 0) {
break;
}
}
return $output ? $output : t('0 sec', array(), array('langcode' => $langcode));
return \Drupal::service('date')->formatInterval($interval, $granularity, $langcode);
}
/**
......
......@@ -12,6 +12,7 @@
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageManager;
use Drupal\Core\StringTranslation\TranslationInterface;
/**
* Provides a service to handler various date related functionality.
......@@ -42,6 +43,25 @@ class Date {
protected $country = NULL;
protected $dateFormats = array();
/**
* Contains the different date interval units.
*
* This array is keyed by strings representing the unit (e.g.
* '1 year|@count years') and with the amount of values of the unit in
* seconds.
*
* @var array
*/
protected $units = array(
'1 year|@count years' => 31536000,
'1 month|@count months' => 2592000,
'1 week|@count weeks' => 604800,
'1 day|@count days' => 86400,
'1 hour|@count hours' => 3600,
'1 min|@count min' => 60,
'1 sec|@count sec' => 1,
);
/**
* Constructs a Date object.
*
......@@ -49,10 +69,13 @@ class Date {
* The entity manager.
* @param \Drupal\Core\Language\LanguageManager $language_manager
* The language manager.
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation
* The string translation.
*/
public function __construct(EntityManagerInterface $entity_manager, LanguageManager $language_manager) {
public function __construct(EntityManagerInterface $entity_manager, LanguageManager $language_manager, TranslationInterface $translation) {
$this->dateFormatStorage = $entity_manager->getStorageController('date_format');
$this->languageManager = $language_manager;
$this->stringTranslation = $translation;
}
/**
......@@ -127,6 +150,47 @@ public function format($timestamp, $type = 'medium', $format = '', $timezone = N
return Xss::filter($date->format($format, $settings));
}
/**
* Formats a time interval with the requested granularity.
*
* @param int $interval
* The length of the interval in seconds.
* @param int $granularity
* (optional) How many different units to display in the string (2 by
* default).
* @param string $langcode
* (optional) Language code to translate to a language other than what is
* used to display the page. Defaults to NULL.
*
* @return string
* A translated string representation of the interval.
*/
public function formatInterval($interval, $granularity = 2, $langcode = NULL) {
$output = '';
foreach ($this->units as $key => $value) {
$key = explode('|', $key);
if ($interval >= $value) {
$output .= ($output ? ' ' : '') . $this->stringTranslation->formatPlural(floor($interval / $value), $key[0], $key[1], array(), array('langcode' => $langcode));
$interval %= $value;
$granularity--;
}
if ($granularity == 0) {
break;
}
}
return $output ? $output : $this->t('0 sec', array(), array('langcode' => $langcode));
}
/**
* Translates a string to the current language or to a given language.
*
* See the t() documentation for details.
*/
protected function t($string, array $args = array(), array $options = array()) {
return $this->stringTranslation->translate($string, $args, $options);
}
/**
* Loads the given format pattern for the given langcode.
*
......
......@@ -31,4 +31,53 @@ interface TranslationInterface {
*/
public function translate($string, array $args = array(), array $options = array());
/**
* Formats a string containing a count of items.
*
* This function ensures that the string is pluralized correctly. Since t() is
* called by this function, make sure not to pass already-localized strings to
* it.
*
* For example:
* @code
* $output = $string_translation->formatPlural($node->comment_count, '1 comment', '@count comments');
* @endcode
*
* Example with additional replacements:
* @code
* $output = $string_translation->formatPlural($update_count,
* 'Changed the content type of 1 post from %old-type to %new-type.',
* 'Changed the content type of @count posts from %old-type to %new-type.',
* array('%old-type' => $info->old_type, '%new-type' => $info->new_type));
* @endcode
*
* @param int $count
* The item count to display.
* @param string $singular
* The string for the singular case. Make sure it is clear this is singular,
* to ease translation (e.g. use "1 new comment" instead of "1 new"). Do not
* use @count in the singular string.
* @param string $plural
* The string for the plural case. Make sure it is clear this is plural, to
* ease translation. Use @count in place of the item count, as in
* "@count new comments".
* @param array $args
* An associative array of replacements to make after translation. Instances
* of any key in this array are replaced with the corresponding value.
* Based on the first character of the key, the value is escaped and/or
* themed. See format_string(). Note that you do not need to include @count
* in this array; this replacement is done automatically for the plural case.
* @param array $options
* An associative array of additional options. See t() for allowed keys.
*
* @return string
* A translated string.
*
* @see self::translate
* @see \Drupal\Component\Utility\String
* @see t()
* @see format_string()
*/
public function formatPlural($count, $singular, $plural, array $args = array(), array $options = array());
}
......@@ -126,6 +126,43 @@ public function translate($string, array $args = array(), array $options = array
}
}
/**
* {@inheritdoc}
*/
public function formatPlural($count, $singular, $plural, array $args = array(), array $options = array()) {
$args['@count'] = $count;
// Join both forms to search a translation.
$translatable_string = implode(LOCALE_PLURAL_DELIMITER, array($singular, $plural));
// Translate as usual.
$translated_strings = $this->translate($translatable_string, $args, $options);
// Split joined translation strings into array.
$translated_array = explode(LOCALE_PLURAL_DELIMITER, $translated_strings);
if ($count == 1) {
return $translated_array[0];
}
// Get the plural index through the gettext formula.
// @todo implement static variable to minimize function_exists() usage.
$index = (function_exists('locale_get_plural')) ? locale_get_plural($count, isset($options['langcode']) ? $options['langcode'] : NULL) : -1;
if ($index == 0) {
// Singular form.
return $translated_array[0];
}
else {
if (isset($translated_array[$index])) {
// N-th plural form.
return $translated_array[$index];
}
else {
// If the index cannot be computed or there's no translation, use
// the second plural form as a fallback (which allows for most flexiblity
// with the replaceable @count value).
return $translated_array[1];
}
}
}
/**
* Sets the default langcode.
*
......
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Datetime\DateTest.
*/
namespace Drupal\Tests\Core\Datetime;
use Drupal\Core\Datetime\Date;
use Drupal\Tests\UnitTestCase;
/**
* Tests the date service.
*
* @group Drupal
*
* @see \Drupal\Core\Datetime\Date
*/
class DateTest extends UnitTestCase {
/**
* The mocked entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entityManager;
/**
* The mocked language manager.
*
* @var \Drupal\Core\Language\LanguageManager|\PHPUnit_Framework_MockObject_MockObject
*/
protected $languageManager;
/**
* The mocked string translation.
*
* @var \Drupal\Core\StringTranslation\TranslationInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $stringTranslation;
/**
* The tested date service class.
*
* @var \Drupal\Core\Datetime\Date
*/
protected $date;
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Date service test.',
'description' => 'Tests the date service.',
'group' => 'System'
);
}
protected function setUp() {
$this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
$this->languageManager = $this->getMockBuilder('Drupal\Core\Language\LanguageManager')
->disableOriginalConstructor()
->getMock();
$this->stringTranslation = $this->getMock('Drupal\Core\StringTranslation\TranslationInterface');
$this->date = new Date($this->entityManager, $this->languageManager, $this->stringTranslation);
}
/**
* Tests the formatPlugin method.
*
* @dataProvider providerTestFormatInterval
*
* @see \Drupal\Core\Datetime\Date::formatInterval()
*/
public function testFormatInterval($interval, $granularity, $expected, $langcode = NULL) {
// Mocks a simple formatPlural implementation.
$this->stringTranslation->expects($this->any())
->method('formatPlural')
->with($this->anything(), $this->anything(), $this->anything(), array(), array('langcode' => $langcode))
->will($this->returnCallback(function($count, $one, $multiple) {
return $count == 1 ? $one : str_replace('@count', $count, $multiple);
}));
// Check if the granularity is specified.
if ($granularity) {
$result = $this->date->formatInterval($interval, $granularity, $langcode);
}
else {
$result = $this->date->formatInterval($interval);
}
$this->assertEquals($expected, $result);
}
/**
* Provides some test data for the format interval test.
*/
public function providerTestFormatInterval() {
$data = array(
// Checks for basic seconds.
array(1, 1, '1 sec'),
array(1, 2, '1 sec'),
array(2, 1, '2 sec'),
array(2, 2, '2 sec'),
// Checks for minutes with seconds.
array(61, 1, '1 min'),
array(61, 2, '1 min 1 sec'),
array(62, 2, '1 min 2 sec'),
array(121, 1, '2 min'),
array(121, 2, '2 min 1 sec'),
// Check for hours with minutes and seconds.
array(3601, 1, '1 hour'),
array(3601, 2, '1 hour 1 sec'),
// Check for higher units.
array(86401, 1, '1 day'),
array(604800, 1, '1 week'),
array(2592000 * 2, 1, '2 months'),
array(31536000 * 2, 1, '2 years'),
// Check for a complicated one with months weeks and days.
array(2592000 * 2 + 604800 * 3 + 86400 * 4, 3, '2 months 3 weeks 4 days'),
// Check for the langcode.
array(61, 1, '1 min', 'xxx-lolspeak'),
// Check with an unspecified granularity.
array(61, NULL, '1 min 1 sec'),
);
return $data;
}
/**
* Tests the formatInterval method for 0 second.
*/
public function testFormatIntervalZeroSecond() {
$this->stringTranslation->expects($this->once())
->method('translate')
->with('0 sec', array(), array('langcode' => 'xxx-lolspeak'))
->will($this->returnValue('0 sec'));
$result = $this->date->formatInterval(0, 1, 'xxx-lolspeak');
$this->assertEquals('0 sec', $result);
}
}
<?php
/**
* @file
* Contains \Drupal\Tests\Core\StringTranslation\TranslationManagerTest.
*/
namespace Drupal\Tests\Core\StringTranslation {
use Drupal\Core\StringTranslation\TranslationManager;
use Drupal\Tests\UnitTestCase;
/**
* Tests the translation manager.
*
* @see \Drupal\Core\StringTranslation\TranslationManager
*/
class TranslationManagerTest extends UnitTestCase {
/**
* The tested translation manager.
*
* @var \Drupal\Core\StringTranslation\TranslationManager
*/
protected $translationManager;
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Translation manager',
'description' => 'Tests the translation manager.',
'group' => 'Translation',
);
}
protected function setUp() {
$this->translationManager = new TestTranslationManager();
}
/**
* Provides some test data for formatPlural()
* @return array
*/
public function providerTestFormatPlural() {
return array(
array(1, 'Singular', '@count plural', array(), array(), 'Singular'),
array(2, 'Singular', '@count plural', array(), array(), '2 plural'),
// @todo support locale_get_plural
array(2, 'Singular', '@count plural @arg', array('@arg' => 3), array(), '2 plural 3'),
);
}
/**
* @dataProvider providerTestFormatPlural
*/
public function testFormatPlural($count, $singular, $plural, array $args = array(), array $options = array(), $expected) {
$translator = $this->getMock('\Drupal\Core\StringTranslation\Translator\TranslatorInterface');
$translator->expects($this->once())
->method('getStringTranslation')
->will($this->returnCallback(function ($langcode, $string) {
return $string;
}));
$this->translationManager->addTranslator($translator);
$result = $this->translationManager->formatPlural($count, $singular, $plural, $args, $options);
$this->assertEquals($expected, $result);
}
}
class TestTranslationManager extends TranslationManager {
public function __construct() {
}
}
}
namespace {
if (!defined('LOCALE_PLURAL_DELIMITER')) {
define('LOCALE_PLURAL_DELIMITER', "\03");
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment