diff --git a/core/core.services.yml b/core/core.services.yml index a32dd04e07667f91815de458a1b00ebe41bb1d26..c3e6fee3391f269b5f4c63e81f1aea654e968fde 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -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: diff --git a/core/includes/common.inc b/core/includes/common.inc index 9973074f6a641e15299cf65c814b9d6c72bd7351..12ccffd8b636fbd46178d95301293fdaacbeaeb7 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -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); } /** diff --git a/core/lib/Drupal/Core/Datetime/Date.php b/core/lib/Drupal/Core/Datetime/Date.php index 08631834caa865f5d6bdc7a0527cfe565f092cc4..0510fa49d2f196775b94132fd85f55dc6e9111f8 100644 --- a/core/lib/Drupal/Core/Datetime/Date.php +++ b/core/lib/Drupal/Core/Datetime/Date.php @@ -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. * diff --git a/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php b/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php index f0d7b45607c7a9e07de2ad2c3186026ae15ad8d6..c3610ff520f01dfa8dcbcd9434b9ec8c86e38d29 100644 --- a/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php +++ b/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php @@ -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()); + } diff --git a/core/lib/Drupal/Core/StringTranslation/TranslationManager.php b/core/lib/Drupal/Core/StringTranslation/TranslationManager.php index 8fa2fc7f7d13e7b3164349ac28dbb59f61494266..191ece45a91223f00eebc7a6bb3ce1831da53ecd 100644 --- a/core/lib/Drupal/Core/StringTranslation/TranslationManager.php +++ b/core/lib/Drupal/Core/StringTranslation/TranslationManager.php @@ -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. * diff --git a/core/tests/Drupal/Tests/Core/Datetime/DateTest.php b/core/tests/Drupal/Tests/Core/Datetime/DateTest.php new file mode 100644 index 0000000000000000000000000000000000000000..32d2103d527bbd483cf278db4f07d6fec1a70ddd --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Datetime/DateTest.php @@ -0,0 +1,147 @@ +<?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); + } + +} diff --git a/core/tests/Drupal/Tests/Core/StringTranslation/TranslationManagerTest.php b/core/tests/Drupal/Tests/Core/StringTranslation/TranslationManagerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..f75a5fa7272870f9526e8cd0ad01361143d5eaa0 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/StringTranslation/TranslationManagerTest.php @@ -0,0 +1,85 @@ +<?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"); + } +}