API Add i18n pluralisation

This commit is contained in:
Damian Mooyman 2016-04-07 12:48:40 +12:00
parent 954f364264
commit 05973cee55
4 changed files with 226 additions and 35 deletions

View File

@ -234,6 +234,7 @@ JSON;
],
'ID' => $changeSet->ID,
'Name' => $changeSet->Name,
'Description' => $changeSet->getDescription(),
'Created' => $changeSet->Created,
'LastEdited' => $changeSet->LastEdited,
'State' => $changeSet->State,
@ -258,6 +259,7 @@ JSON;
* @return array
*/
protected function getChangeSetItemResource(ChangeSetItem $changeSetItem) {
$objectSingleton = DataObject::singleton($changeSetItem->ObjectClass);
$hal = [
'_links' => [
'self' => [
@ -270,6 +272,10 @@ JSON;
'Title' => $changeSetItem->getTitle(),
'ChangeType' => $changeSetItem->getChangeType(),
'Added' => $changeSetItem->Added,
'ObjectClass' => $changeSetItem->ObjectClass,
'ObjectID' => $changeSetItem->ObjectID,
'ObjectSingular' => $objectSingleton->i18n_singular_name(),
'ObjectPlural' => $objectSingleton->i18n_plural_name(),
];
// Depending on whether the object was added implicitly or explicitly, set
// other related objects.

View File

@ -2053,43 +2053,21 @@ class i18n extends Object implements TemplateGlobalProvider, Flushable {
}
}
// get current locale (either default or user preference)
// Find best translation
$locale = i18n::get_locale();
$lang = i18n::get_lang_from_locale($locale);
// Only call getter if static isn't already defined (for performance reasons)
$translatorsByPrio = self::$translators;
if(!$translatorsByPrio) $translatorsByPrio = self::get_translators();
$returnValue = (is_string($string)) ? $string : ''; // Fall back to default string argument
foreach($translatorsByPrio as $priority => $translators) {
foreach($translators as $name => $translator) {
$adapter = $translator->getAdapter();
// at this point, we need to ensure the language and locale are loaded
// as include_by_locale() doesn't load a fallback.
// TODO Remove reliance on global state, by refactoring into an i18nTranslatorManager
// which is instanciated by core with a $clean instance variable.
if(!$adapter->isAvailable($lang)) {
i18n::include_by_locale($lang);
}
if(!$adapter->isAvailable($locale)) {
i18n::include_by_locale($locale);
}
$translation = $adapter->translate($entity, $locale);
// Return translation only if we found a match thats not the entity itself (Zend fallback)
if($translation && $translation != $entity) {
$returnValue = $translation;
break 2;
}
}
$returnValue = static::with_translators(function(Zend_Translate_Adapter $adapter) use ($entity, $locale) {
// Return translation only if we found a match thats not the entity itself (Zend fallback)
$translation = $adapter->translate($entity, $locale);
if($translation && $translation != $entity) {
return $translation;
}
return null;
});
// Fall back to default string argument
if($returnValue === null) {
$returnValue = (is_string($string)) ? $string : '';
}
// inject the variables from injectionArray (if present)
if($injectionArray) {
@ -2136,6 +2114,82 @@ class i18n extends Object implements TemplateGlobalProvider, Flushable {
return $returnValue;
}
/**
* Pluralise an item or items.
*
* @param string $singular Singular form
* @param string $plural Plural form
* @param int $number Number of items (natural number only)
* @param bool $prependNumber Include number in result
* @return string Result with the number and pluralised form appended. E.g. '1 page'
*/
public static function pluralise($singular, $plural, $number, $prependNumber = true) {
$locale = static::get_locale();
$form = static::with_translators(
function(Zend_Translate_Adapter $adapter) use ($singular, $plural, $number, $locale) {
// Return translation only if we found a match thats not the entity itself (Zend fallback)
$result = $adapter->plural($singular, $plural, $number, $locale);
if($result) {
return $result;
}
return null;
}
);
if($prependNumber) {
return _t('i18n.PLURAL', '{number} {form}', [
'number' => $number,
'form' => $form
]);
} else {
return $form;
}
}
/**
* Loop over all translators in order of precedence, and return the first non-null value
* returned via $callback
*
* @param callable $callback Callback which is given the translator
* @return mixed First non-null result from $callback, or null if none matched
*/
protected static function with_translators($callback) {
// get current locale (either default or user preference)
$locale = i18n::get_locale();
$lang = i18n::get_lang_from_locale($locale);
// Only call getter if static isn't already defined (for performance reasons)
$translatorsByPrio = self::$translators ?: self::get_translators();
foreach($translatorsByPrio as $priority => $translators) {
/** @var Zend_Translate $translator */
foreach($translators as $name => $translator) {
$adapter = $translator->getAdapter();
// at this point, we need to ensure the language and locale are loaded
// as include_by_locale() doesn't load a fallback.
// TODO Remove reliance on global state, by refactoring into an i18nTranslatorManager
// which is instanciated by core with a $clean instance variable.
if(!$adapter->isAvailable($lang)) {
i18n::include_by_locale($lang);
}
if(!$adapter->isAvailable($locale)) {
i18n::include_by_locale($locale);
}
$result = call_user_func($callback, $adapter);
if($result !== null) {
return $result;
}
}
}
// Nothing matched
return null;
}
/**
* @return array Array of priority keys to instances of Zend_Translate, mapped by name.

View File

@ -722,6 +722,24 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return true;
}
/**
* Pluralise this item given a specific count.
*
* E.g. "0 Pages", "1 File", "3 Images"
*
* @param string $count
* @param bool $prependNumber Include number in result. Defaults to true.
* @return string
*/
public function i18n_pluralise($count, $prependNumber = true) {
return i18n::pluralise(
$this->i18n_singular_name(),
$this->i18n_plural_name(),
$count,
$prependNumber
);
}
/**
* Get the user friendly singular name of this DataObject.
* If the name is not defined (by redefining $singular_name in the subclass),

View File

@ -338,4 +338,117 @@ class ChangeSet extends DataObject {
$this->extend('updateCMSFields', $fields);
return $fields;
}
/**
* Gets summary of items in changeset
*
* @return string
*/
public function getDescription() {
// Initialise list of items to count
$counted = [];
$countedOther = 0;
foreach($this->config()->important_classes as $type) {
if(class_exists($type)) {
$counted[$type] = 0;
}
}
// Check each change item
/** @var ChangeSetItem $change */
foreach($this->Changes() as $change) {
$found = false;
foreach($counted as $class => $num) {
if(is_a($change->ObjectClass, $class, true)) {
$counted[$class]++;
$found = true;
break;
}
}
if(!$found) {
$countedOther++;
}
}
// Describe set based on this output
$counted = array_filter($counted);
// Empty state
if(empty($counted) && empty($countedOther)) {
return '';
}
// Put all parts together
$parts = [];
foreach($counted as $class => $count) {
$parts[] = DataObject::singleton($class)->i18n_pluralise($count);
}
// Describe non-important items
if($countedOther) {
if ($counted) {
$parts[] = i18n::pluralise(
_t('ChangeSet.DESCRIPTION_OTHER_ITEM', 'other item'),
_t('ChangeSet.DESCRIPTION_OTHER_ITEMS', 'other items'),
$countedOther
);
} else {
$parts[] = i18n::pluralise(
_t('ChangeSet.DESCRIPTION_ITEM', 'item'),
_t('ChangeSet.DESCRIPTION_ITEMS', 'items'),
$countedOther
);
}
}
// Figure out how to join everything together
if(empty($parts)) {
return '';
}
if(count($parts) === 1) {
return $parts[0];
}
// Non-comma list
if(count($parts) === 2) {
return _t(
'ChangeSet.DESCRIPTION_AND',
'{first} and {second}',
[
'first' => $parts[0],
'second' => $parts[1],
]
);
}
// First item
$string = _t(
'ChangeSet.DESCRIPTION_LIST_FIRST',
'{item}',
['item' => $parts[0]]
);
// Middle items
for($i = 1; $i < count($parts) - 1; $i++) {
$string = _t(
'ChangeSet.DESCRIPTION_LIST_MID',
'{list}, {item}',
[
'list' => $string,
'item' => $parts[$i]
]
);
}
// Oxford comma
$string = _t(
'ChangeSet.DESCRIPTION_LIST_LAST',
'{list}, and {item}',
[
'list' => $string,
'item' => end($parts)
]
);
return $string;
}
}