Last active
July 25, 2017 14:06
-
-
Save albe/4be3b31697c4e57181f7 to your computer and use it in GitHub Desktop.
A LocalizationService Utility class for Neos Flow 4.x that fetches localization data from the CLDR
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Acme\Foo\Service; | |
use Neos\Flow\Annotations as Flow; | |
use Neos\Flow\I18n\Cldr\CldrModel; | |
/** | |
* A service for localization purposes | |
* | |
* @Flow\Scope("singleton") | |
*/ | |
class LocalizationService | |
{ | |
/** | |
* @var \Neos\Flow\Log\SystemLoggerInterface | |
* @Flow\Inject | |
*/ | |
protected $systemLogger; | |
/** | |
* @var \Neos\Flow\I18n\Translator | |
* @Flow\Inject | |
*/ | |
protected $translator; | |
/** | |
* @var \Neos\Flow\I18n\Parser\DatetimeParser | |
* @Flow\Inject | |
*/ | |
protected $datetimeParser; | |
/** | |
* @var \Neos\Flow\I18n\Parser\NumberParser | |
* @Flow\Inject | |
*/ | |
protected $numberParser; | |
/** | |
* @var \Neos\Flow\I18n\Formatter\DatetimeFormatter | |
* @Flow\Inject | |
*/ | |
protected $datetimeFormatter; | |
/** | |
* @var \Neos\Flow\I18n\Formatter\NumberFormatter | |
* @Flow\Inject | |
*/ | |
protected $numberFormatter; | |
/** | |
* @var \Neos\Flow\I18n\Cldr\CldrRepository | |
* @Flow\Inject | |
*/ | |
protected $cldrRepository; | |
/** | |
* @var \Neos\Flow\I18n\Cldr\Reader\PluralsReader | |
* @Flow\Inject | |
*/ | |
protected $pluralsReader; | |
/** | |
* @var \Neos\Flow\I18n\Service | |
* @Flow\Inject | |
*/ | |
protected $i18nService; | |
/** | |
* @var \Neos\Flow\Core\Bootstrap | |
* @Flow\Inject | |
*/ | |
protected $bootstrap; | |
/** | |
* @var \Neos\Flow\I18n\Detector | |
* @Flow\Inject | |
*/ | |
protected $detector; | |
/** | |
* @var string | |
*/ | |
protected $translationSource = 'Main'; | |
/** | |
* @var string | |
*/ | |
protected $translationPackage = 'Acme.Foo'; | |
protected static $territories = array('001', '002', '003', '005', '009', '011', '013', '014', '015', '017', '018', '019', '021', '029', '030', '034', '035', '039', '053', '054', '057', '061', '062', '142', '143', '145', '150', '151', '154', '155', '172', '419', '830'); | |
/** | |
* Translate a literal identified by a key | |
* | |
* @param string $key | |
* @param array $arguments Optional | |
* @param integer $quantity Optional | |
* @param string $default Optional | |
* @return string | |
*/ | |
public function translate($key, $arguments = array(), $quantity = null, $default = null, $source = null, $package = null) | |
{ | |
$translated = $this->translator->translateById($key, $arguments, $quantity, $this->getLocale(), $source ? : $this->translationSource, $package ? : $this->translationPackage); | |
if ($translated === $key) { | |
if (null === $default) { | |
return 'Untranslated key (' . $key . ')'; | |
} | |
return $default; | |
} | |
return $translated; | |
} | |
/** | |
* Convert a datetime info array into a \DateTime object | |
* | |
* @param array $datetime | |
* @return \DateTime or FALSE on error | |
*/ | |
public function convertToDateTime(array $datetime) | |
{ | |
$formattedString = ''; | |
if (isset($datetime['year'])) { | |
$formattedString .= $datetime['year'] . '-'; | |
} | |
if (isset($datetime['month'])) { | |
$formattedString .= $datetime['month'] . '-'; | |
} | |
if (isset($datetime['day'])) { | |
$formattedString .= $datetime['day']; | |
} | |
$formattedString .= 'T'; | |
if (isset($datetime['hour'])) { | |
$formattedString .= $datetime['hour'] . ':'; | |
} else { | |
$formattedString .= '00:'; | |
} | |
if (isset($datetime['minute'])) { | |
$formattedString .= $datetime['minute'] . ':'; | |
} else { | |
$formattedString .= '00:'; | |
} | |
if (isset($datetime['second'])) { | |
$formattedString .= $datetime['second']; | |
} else { | |
$formattedString .= '00'; | |
} | |
$timezone = null; | |
if (isset($datetime['timezone'])) { | |
try { | |
$timezone = new \DateTimeZone($datetime['timezone']); | |
} catch (\Exception $e) { | |
$timezone = null; | |
} | |
} | |
try { | |
$datetime = new \DateTime($formattedString, $timezone); | |
} catch (\Exception $e) { | |
if ($this->systemLogger) { | |
$this->systemLogger->logException($e); | |
} | |
return false; | |
} | |
return $datetime; | |
} | |
/** | |
* Parse a time string into a dateTime object | |
* | |
* @param string $timeString | |
* @return bool|\DateTime | |
*/ | |
public function parseTime($timeString) | |
{ | |
// We always parse in lenient mode | |
$time = $this->datetimeParser->parseTime($timeString, $this->getLocale(), \Neos\Flow\I18n\Cldr\Reader\DatesReader::FORMAT_LENGTH_DEFAULT, false); | |
return (false !== $time) ? $this->convertToDateTime($time) : false; | |
} | |
/** | |
* Parse a date string into a dateTime object | |
* | |
* @param string $dateString | |
* @return bool|\DateTime | |
*/ | |
public function parseDate($dateString) | |
{ | |
// We always parse in lenient mode | |
$date = $this->datetimeParser->parseDate($dateString, $this->getLocale(), \Neos\Flow\I18n\Cldr\Reader\DatesReader::FORMAT_LENGTH_DEFAULT, false); | |
return (false !== $date) ? $this->convertToDateTime($date) : false; | |
} | |
/** | |
* Parse a datetime string into a dateTime object | |
* | |
* @param string $datetimeString | |
* @return bool|\DateTime | |
*/ | |
public function parseDatetime($datetimeString) | |
{ | |
// We always parse in lenient mode | |
$datetime = $this->datetimeParser->parseDateAndTime($datetimeString, $this->getLocale(), \Neos\Flow\I18n\Cldr\Reader\DatesReader::FORMAT_LENGTH_DEFAULT, false); | |
return (false !== $datetime) ? $this->convertToDateTime($datetime) : false; | |
} | |
/** | |
* Format a datetime object into a time string according to locale | |
* | |
* @param \DateTime $dateTime | |
* @return string | |
*/ | |
public function formatTime(\DateTime $dateTime) | |
{ | |
return $this->datetimeFormatter->formatTime($dateTime, $this->getLocale()); | |
} | |
/** | |
* Format a datetime object into a date string according to locale | |
* | |
* @param \DateTime $dateTime | |
* @return string | |
*/ | |
public function formatDate(\DateTime $dateTime) | |
{ | |
return $this->datetimeFormatter->formatDate($dateTime, $this->getLocale()); | |
} | |
/** | |
* Format a datetime object into a datetime string according to locale | |
* | |
* @param \DateTime $dateTime | |
* @return string | |
*/ | |
public function formatDatetime(\DateTime $dateTime) | |
{ | |
return $this->datetimeFormatter->formatDateTime($dateTime, $this->getLocale()); | |
} | |
/** | |
* Format a float or int into a decimal number string according to locale | |
* | |
* @param mixed $number | |
* @return string | |
*/ | |
public function formatDecimal($number) | |
{ | |
return $this->numberFormatter->formatDecimalNumber($number, $this->getLocale()); | |
} | |
/** | |
* Format a float or int into a percent number string according to locale | |
* | |
* @param mixed $number | |
* @return string | |
*/ | |
public function formatPercent($number) | |
{ | |
return $this->numberFormatter->formatPercentNumber($number, $this->getLocale()); | |
} | |
/** | |
* Format a float or int into a currency string according to locale | |
* | |
* @param mixed $number | |
* @param string $currency Optional. The currency symbol or short name (Default 'EUR') | |
* @return string | |
*/ | |
public function formatCurrency($number, $currency = 'EUR') | |
{ | |
return $this->numberFormatter->formatCurrencyNumber($number, $this->getLocale(), $currency); | |
} | |
/** | |
* @return \Neos\Flow\I18n\Locale | |
*/ | |
public function getLocale() | |
{ | |
return $this->i18nService->getConfiguration()->getCurrentLocale(); | |
} | |
/** | |
* @param \Neos\Flow\I18n\Locale $locale | |
*/ | |
public function setLocale($locale) | |
{ | |
$this->i18nService->getConfiguration()->setCurrentLocale($locale); | |
} | |
/** | |
* @return \Neos\Flow\I18n\Locale | |
*/ | |
public function getDefaultLocale() | |
{ | |
return $this->i18nService->getConfiguration()->getDefaultLocale(); | |
} | |
/** | |
* Get a locale matching the Accept-Language HTTP headers or NULL if no match | |
* | |
* @return \Neos\Flow\I18n\Locale | |
*/ | |
public function getLocaleByAcceptLanguage() | |
{ | |
$requestHandler = $this->bootstrap->getActiveRequestHandler(); | |
if (!$requestHandler instanceof \Neos\Flow\Http\RequestHandler) { | |
return null; | |
} | |
$requestHandler->getHttpResponse()->setHeader('Vary', 'Accept-Language', false); | |
return $this->detector->detectLocaleFromHttpHeader($requestHandler->getHttpRequest()->getHeaders()->get('Accept-Language')); | |
} | |
/** | |
* Get a locale matching the identifier string | |
* | |
* @param string $identifier | |
* @return \Neos\Flow\I18n\Locale | |
*/ | |
public function getLocaleByIdentifier($identifier) | |
{ | |
return $this->detector->detectLocaleFromLocaleTag($identifier); | |
} | |
/** | |
* Returns the count rule of the current locale for an amount of $quantity | |
* | |
* @param int $quantity | |
* @return string One of \Neos\Flow\I18n\Cldr\Reader\PluralsReader::RULE_* constants | |
*/ | |
public function getPluralForm($quantity) | |
{ | |
return $this->pluralsReader->getPluralForm($quantity, $this->getLocale()); | |
} | |
/** | |
* Get an array of all language names | |
* | |
* @return array|boolean | |
*/ | |
public function getLanguages() | |
{ | |
return $this->getKeyValues('localeDisplayNames/languages'); | |
} | |
/** | |
* Get an array of all script names | |
* | |
* @return array|boolean | |
*/ | |
public function getScripts() | |
{ | |
return $this->getKeyValues('localeDisplayNames/scripts'); | |
} | |
/** | |
* Get an array of all delimiters | |
* | |
* @return array|boolean | |
*/ | |
public function getDelimiters() | |
{ | |
$model = $this->cldrRepository->getModelForLocale($this->getLocale()); | |
$data = $model->getRawArray('delimiters'); | |
return $data; | |
} | |
/** | |
* Get an array of all values in the CLDR where the key is the type attribute | |
* | |
* @param string $path The xpath to select values from | |
* @return array|boolean | |
*/ | |
protected function getKeyValues($path) | |
{ | |
$model = $this->cldrRepository->getModelForLocale($this->getLocale()); | |
$data = $model->getRawArray($path); | |
if ($data === false) { | |
return false; | |
} | |
$filteredData = array(); | |
foreach ($data as $nodeString => $children) { | |
if (CldrModel::getAttributeValue($nodeString, 'alt') === false) { | |
$key = CldrModel::getAttributeValue($nodeString, 'type'); | |
$filteredData[$key] = $children; | |
} | |
} | |
return $filteredData; | |
} | |
/** | |
* Get an array of all values from the CLDR calendar sub elements | |
* | |
* @param string $element The elements to return. One of 'month', 'day' or 'quarter'. | |
* @param string $width The width of the names to return. One of 'abbreviated', 'narrow' or 'wide' (Default). | |
* @param string $context The context in which the names should stand. One of 'format' or 'stand-alone' (Default). | |
* @param string $calendar The calendar to relate to. Default 'gregorian'. | |
* @return array|boolean | |
*/ | |
protected function getCalendarElements($element, $width = 'wide', $context = 'stand-alone', $calendar = 'gregorian') | |
{ | |
$path = 'dates/calendars/calendar[@type="' . $calendar . '"]/' . $element . 's/' . $element . 'Context[@type="' . $context . '"]/' . $element . 'Width[@type="' . $width . '"]'; | |
return $this->getKeyValues($path); | |
} | |
/** | |
* Get an array of all month names | |
* | |
* @param string $width The width of the names to return. One of 'abbreviated', 'narrow' or 'wide' (Default). | |
* @param string $context The context in which the names should stand. One of 'format' or 'stand-alone' (Default). | |
* @param string $calendar The calendar to relate to. Default 'gregorian'. | |
* @return array|boolean | |
*/ | |
public function getMonths($width = 'wide', $context = 'stand-alone', $calendar = 'gregorian') | |
{ | |
return $this->getCalendarElements('month', $width, $context, $calendar); | |
} | |
/** | |
* Get an array of all day names | |
* | |
* @param string $width The width of the names to return. One of 'abbreviated', 'narrow', 'short' or 'wide' (Default). | |
* @param string $context The context in which the names should stand. One of 'format' or 'stand-alone' (Default). | |
* @param string $calendar The calendar to relate to. Default 'gregorian'. | |
* @return array|boolean | |
*/ | |
public function getDays($width = 'wide', $context = 'stand-alone', $calendar = 'gregorian') | |
{ | |
return $this->getCalendarElements('day', $width, $context, $calendar); | |
} | |
/** | |
* Get an array of all quarter names | |
* | |
* @param string $width The width of the names to return. One of 'abbreviated', 'narrow' or 'wide' (Default). | |
* @param string $context The context in which the names should stand. One of 'format' or 'stand-alone' (Default). | |
* @param string $calendar The calendar to relate to. Default 'gregorian'. | |
* @return array|boolean | |
*/ | |
public function getQuarters($width = 'wide', $context = 'stand-alone', $calendar = 'gregorian') | |
{ | |
return $this->getCalendarElements('quarter', $width, $context, $calendar); | |
} | |
/** | |
* Get an array of all day period names | |
* | |
* @param string $width The width of the names to return. Only 'wide' (Default). | |
* @param string $context The context in which the names should stand. One of 'format' or 'stand-alone' (Default). | |
* @param string $calendar The calendar to relate to. Default 'gregorian'. | |
* @return array|boolean | |
*/ | |
public function getDayPeriods($width = 'wide', $context = 'stand-alone', $calendar = 'gregorian') | |
{ | |
return $this->getCalendarElements('dayPeriod', $width, $context, $calendar); | |
} | |
/** | |
* Get an array of all currencies | |
* | |
* @return array|boolean | |
*/ | |
protected function getCurrencies() | |
{ | |
$data = $this->getKeyValues('numbers/currencies/currency'); | |
if ($data === false) { | |
return false; | |
} | |
foreach ($data as $currency => $values) { | |
$data[$currency] = (string)reset($values); | |
} | |
return $data; | |
} | |
/** | |
* Get an array of all country names | |
* | |
* @return array|boolean | |
*/ | |
public function getCountries() | |
{ | |
$data = $this->getKeyValues('localeDisplayNames/territories'); | |
if ($data === false) { | |
return false; | |
} | |
return array_diff_key($data, array_flip(self::$territories)); | |
} | |
/** | |
* Get an array of all territory names | |
* | |
* @return array|boolean | |
*/ | |
public function getTerritories() | |
{ | |
$data = $this->getKeyValues('localeDisplayNames/territories'); | |
if ($data === false) { | |
return false; | |
} | |
return array_intersect_key($data, array_flip(self::$territories)); | |
} | |
/** | |
* Get territories containments, i.e. the hierarchy of territories as associative array | |
* | |
* @return array|boolean | |
*/ | |
public function getTerritoriesContainment() | |
{ | |
$model = $this->cldrRepository->getModel('supplemental/supplementalData'); | |
$data = $model->getRawArray('supplementalData/territoryContainment'); | |
if ($data === false) { | |
return false; | |
} | |
$restructuredData = array(); | |
foreach ($data as $nodeString => $children) { | |
if (CldrModel::getAttributeValue($nodeString, 'status') === false && | |
CldrModel::getAttributeValue($nodeString, 'grouping') === false | |
) { | |
$key = CldrModel::getAttributeValue($nodeString, 'type'); | |
$contains = explode(' ', CldrModel::getAttributeValue($nodeString, 'contains')); | |
$restructuredData[$key] = $contains; | |
} | |
} | |
return $restructuredData; | |
} | |
/** | |
* Get a list of postal code regexes by territory as an associative array | |
* | |
* @return array|boolean | |
*/ | |
public function getPostalCodeRegexes() | |
{ | |
$model = $this->cldrRepository->getModel('supplemental/postalCodeData'); | |
$data = $model->getRawArray('supplementalData/postalCodeData'); | |
if ($data === false) { | |
return false; | |
} | |
$restructuredData = array(); | |
foreach ($data as $nodeString => $children) { | |
$key = CldrModel::getAttributeValue($nodeString, 'territoryId'); | |
$restructuredData[$key] = (string)$children; | |
} | |
return $restructuredData; | |
} | |
/** | |
* Get a list of telephone codes by territory as associative array | |
* | |
* @return array|boolean | |
*/ | |
public function getTelephoneCountryCodes() | |
{ | |
$model = $this->cldrRepository->getModel('supplemental/telephoneCodeData'); | |
$data = $model->getRawArray('supplementalData/telephoneCodeData'); | |
if ($data === false) { | |
return false; | |
} | |
$restructuredData = array(); | |
foreach ($data as $nodeString => $children) { | |
$key = CldrModel::getAttributeValue($nodeString, 'territory'); | |
$restructuredData[$key] = array(); | |
foreach ($children as $nodeString => $value) { | |
$restructuredData[$key][] = CldrModel::getAttributeValue($nodeString, 'code'); | |
} | |
} | |
return $restructuredData; | |
} | |
/** | |
* @return string | |
*/ | |
public function getTranslationSource() | |
{ | |
return $this->translationSource; | |
} | |
/** | |
* @param string $translationSource | |
*/ | |
public function setTranslationSource($translationSource) | |
{ | |
$this->translationSource = $translationSource; | |
} | |
/** | |
* @return string | |
*/ | |
public function getTranslationPackage() | |
{ | |
return $this->translationPackage; | |
} | |
/** | |
* @param string $translationPackage | |
*/ | |
public function setTranslationPackage($translationPackage) | |
{ | |
$this->translationPackage = $translationPackage; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment