Skip to content

Instantly share code, notes, and snippets.

@albe
Last active July 25, 2017 14:06
Show Gist options
  • Save albe/4be3b31697c4e57181f7 to your computer and use it in GitHub Desktop.
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
<?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