mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Add i18n pluralisation
This commit is contained in:
parent
954f364264
commit
05973cee55
@ -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.
|
||||
|
118
i18n/i18n.php
118
i18n/i18n.php
@ -2053,42 +2053,20 @@ 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);
|
||||
|
||||
$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) {
|
||||
$returnValue = $translation;
|
||||
break 2;
|
||||
}
|
||||
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)
|
||||
@ -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.
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user