Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support MessageFormatter for translation including plural logic #3286

Merged
merged 10 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion languages/en.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1004,7 +1004,7 @@ password_minimum_length = "Minimum password length is %%minlength%% characters"
password_only_alphanumeric = "Numbers and letters A-Z only"
password_only_numeric = "Numbers only"
Passwords do not match = "Passwords do not match"
past_days = "Past %%range%% Days"
past_days = "{range, plural, =1 {Yesterday} =30 {Past Month} other {Past # Days}}"
patron_account_expires = "Expires"
patron_status_address_missing = "Your address is missing."
patron_status_card_blocked = "This library card is blocked from borrowing."
Expand Down
53 changes: 32 additions & 21 deletions module/VuFind/src/VuFind/I18n/Translator/TranslatorAwareTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,17 @@ public function getTranslatorLocale($default = 'en')
/**
* Translate a string (or string-castable object)
*
* @param string|object|array $target String to translate or an array of text
* @param string|object|array $target String to translate or an array of text
* domain and string to translate
* @param array $tokens Tokens to inject into the translated
* string
* @param string $default Default value to use if no translation is
* @param array $tokens Tokens to inject into the translated string
* @param string $default Default value to use if no translation is
* found (null for no default).
* @param bool $messageFormatter Should we use an ICU message formatter instead
* of the default behavior?
*
* @return string
*/
public function translate($target, $tokens = [], $default = null)
public function translate($target, $tokens = [], $default = null, $messageFormatter = false)
demiankatz marked this conversation as resolved.
Show resolved Hide resolved
{
// Figure out the text domain for the string:
[$domain, $str] = $this->extractTextDomain($target);
Expand All @@ -130,7 +131,7 @@ public function translate($target, $tokens = [], $default = null)
// On this pass, don't use the $default, since we want to fail over
// to getDisplayString before giving up:
$translated = $this
->translateString((string)$str, $tokens, null, $domain);
->translateString((string)$str, $tokens, null, $domain, $messageFormatter);
if ($translated !== (string)$str) {
return $translated;
}
Expand All @@ -142,63 +143,69 @@ public function translate($target, $tokens = [], $default = null)
// least with hierarchical facets where translation key can be the exact
// facet value (e.g. "0/Book/") or a displayable value (e.g. "Book").
if ($str instanceof \VuFind\I18n\TranslatableStringInterface) {
return $this->translate($str, $tokens, $default);
return $this->translate($str, $tokens, $default, $messageFormatter);
} else {
[$domain, $str] = $this->extractTextDomain($str);
}
}

// Default case: deal with ordinary strings (or string-castable objects):
return $this->translateString((string)$str, $tokens, $default, $domain);
return $this->translateString((string)$str, $tokens, $default, $domain, $messageFormatter);
}

/**
* Translate a string (or string-castable object) using a prefix, or without the
* prefix if a prefixed translation is not found.
*
* @param string $prefix Translation key prefix
* @param string|object|array $target String to translate or an array of text
* @param string $prefix Translation key prefix
* @param string|object|array $target String to translate or an array of text
* domain and string to translate
* @param array $tokens Tokens to inject into the translated
* string
* @param string $default Default value to use if no translation is
* @param array $tokens Tokens to inject into the translated string
* @param string $default Default value to use if no translation is
* found (null for no default).
* @param bool $messageFormatter Should we use an ICU message formatter instead
* of the default behavior?
*
* @return string
*/
public function translateWithPrefix(
$prefix,
$target,
$tokens = [],
$default = null
$default = null,
$messageFormatter = false
) {
if (is_string($target)) {
if (null === $default) {
$default = $target;
}
$target = $prefix . $target;
}
return $this->translate($target, $tokens, $default);
return $this->translate($target, $tokens, $default, $messageFormatter);
}

/**
* Get translation for a string
*
* @param string $str String to translate
* @param array $tokens Tokens to inject into the translated string
* @param string $default Default value to use if no translation is found
* @param string $str String to translate
* @param array $tokens Tokens to inject into the translated string
* @param string $default Default value to use if no translation is found
* (null for no default).
* @param string $domain Text domain (omit for default)
* @param string $domain Text domain (omit for default)
* @param bool $messageFormatter Should we use an ICU message formatter instead
* of the default behavior?
*
* @return string
*/
protected function translateString(
$str,
$tokens = [],
$default = null,
$domain = 'default'
$domain = 'default',
$messageFormatter = false
) {
$msg = (null === $this->translator)
// Try a normal translation
$msg ??= (null === $this->translator)
? $str : $this->translator->translate($str, $domain);

// Did the translation fail to change anything? If so, use default:
Expand All @@ -207,8 +214,12 @@ protected function translateString(
? $default->getDisplayString() : $default;
}


// Do we need to perform substitutions?
if (!empty($tokens)) {
if ($messageFormatter) {
return \MessageFormatter::formatMessage($this->getTranslatorLocale(), $msg, $tokens);
}
$in = $out = [];
foreach ($tokens as $key => $value) {
$in[] = $key;
Expand Down
15 changes: 9 additions & 6 deletions module/VuFind/src/VuFind/View/Helper/Root/TransEsc.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,20 @@ class TransEsc extends AbstractHelper
/**
* Translate and escape a string
*
* @param string $str String to escape and translate
* @param array $tokens Tokens to inject into the translated string
* @param string $default Default value to use if no translation is found (null
* for no default).
* @param string|object|array $str String to translate or an array of text
* domain and string to translate
* @param array $tokens Tokens to inject into the translated string
* @param string $default Default value to use if no translation is
* found (null for no default).
* @param bool $messageFormatter Should we use an ICU message formatter instead
* of the default behavior?
*
* @return string
*/
public function __invoke($str, $tokens = [], $default = null)
public function __invoke($str, $tokens = [], $default = null, $messageFormatter = false)
{
$escaper = $this->getView()->plugin('escapeHtml');
$translator = $this->getView()->plugin('translate');
return $escaper($translator($str, $tokens, $default));
return $escaper($translator($str, $tokens, $default, $messageFormatter));
}
}
15 changes: 9 additions & 6 deletions module/VuFind/src/VuFind/View/Helper/Root/TransEscAttr.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,20 @@ class TransEscAttr extends AbstractHelper
/**
* Translate and escape a string for an HTML attribute
*
* @param string $str String to escape and translate
* @param array $tokens Tokens to inject into the translated string
* @param string $default Default value to use if no translation is found (null
* for no default).
* @param string|object|array $str String to translate or an array of text
* domain and string to translate
* @param array $tokens Tokens to inject into the translated string
* @param string $default Default value to use if no translation is
* found (null for no default).
* @param bool $messageFormatter Should we use an ICU message formatter instead
* of the default behavior?
*
* @return string
*/
public function __invoke($str, $tokens = [], $default = null)
public function __invoke($str, $tokens = [], $default = null, $messageFormatter = false)
{
$escaper = $this->getView()->plugin('escapeHtmlAttr');
$translator = $this->getView()->plugin('translate');
return $escaper($translator($str, $tokens, $default));
return $escaper($translator($str, $tokens, $default, $messageFormatter));
}
}
19 changes: 11 additions & 8 deletions module/VuFind/src/VuFind/View/Helper/Root/TransEscWithPrefix.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,24 @@ class TransEscWithPrefix extends AbstractHelper implements TranslatorAwareInterf
use \VuFind\I18n\Translator\TranslatorAwareTrait;

/**
* Translate and escape a location
* Translate and escape a value while applying a prefix
*
* @param string $prefix Translation key prefix
* @param string|object $str String to translate
* @param array $tokens Tokens to inject into the translated string
* @param string $default Default value to use if no translation is found
* (null for no default).
* @param string $prefix Translation key prefix
* @param string|object|array $str String to translate or an array of text
* domain and string to translate
* @param array $tokens Tokens to inject into the translated string
* @param string $default Default value to use if no translation is
* found (null for no default).
* @param bool $messageFormatter Should we use an ICU message formatter instead
* of the default behavior?
*
* @return string
*/
public function __invoke($prefix, $str, $tokens = [], $default = null)
public function __invoke($prefix, $str, $tokens = [], $default = null, $messageFormatter = false)
{
$escaper = $this->getView()->plugin('escapeHtml');
return $escaper(
$this->translateWithPrefix($prefix, $str, $tokens, $default)
$this->translateWithPrefix($prefix, $str, $tokens, $default, $messageFormatter)
);
}
}
15 changes: 9 additions & 6 deletions module/VuFind/src/VuFind/View/Helper/Root/Translate.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,18 @@ class Translate extends \Laminas\View\Helper\AbstractHelper implements \VuFind\I
/**
* Translate a string
*
* @param string|object $str String to translate
* @param array $tokens Tokens to inject into the translated string
* @param string $default Default value to use if no translation is found
* (null for no default).
* @param string|object|array $str String to translate or an array of text
* domain and string to translate
* @param array $tokens Tokens to inject into the translated string
* @param string $default Default value to use if no translation is
* found (null for no default).
* @param bool $messageFormatter Should we use an ICU message formatter instead
* of the default behavior?
*
* @return string
*/
public function __invoke($str, $tokens = [], $default = null)
public function __invoke($str, $tokens = [], $default = null, $messageFormatter = false)
{
return $this->translate($str, $tokens, $default);
return $this->translate($str, $tokens, $default, $messageFormatter);
}
}
2 changes: 1 addition & 1 deletion themes/bootstrap3/templates/search/newitem.phtml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<?php foreach ($this->ranges as $key => $range): ?>
<label class="btn btn-primary<?php if ($key == 0): ?> active<?php endif ?>">
<input type="radio" name="range" id="newitem_range_<?=$this->escapeHtmlAttr($key)?>" value="<?=$this->escapeHtmlAttr($range)?>"<?=($key == 0) ? ' checked="checked"' : ''?>>
<?=($range == 1) ? $this->transEsc('Yesterday') : $this->transEsc('past_days', ['%%range%%' => $this->escapeHtml($range)])?>
<?=$this->transEsc('past_days', ['range' => $this->escapeHtml($range)], null, true)?>
</label>
<?php endforeach; ?>
</div>
Expand Down
Loading