151 lines
5.0 KiB
PHP
151 lines
5.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* @file classes/i18n/translation/LocaleBundle.php
|
|
*
|
|
* Copyright (c) 2014-2021 Simon Fraser University
|
|
* Copyright (c) 2000-2021 John Willinsky
|
|
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
|
*
|
|
* @class LocaleBundle
|
|
*
|
|
* @ingroup i18n
|
|
*
|
|
* @brief Bundles several locale files for a given locale into a single object
|
|
*/
|
|
|
|
namespace PKP\i18n\translation;
|
|
|
|
use DateInterval;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use PKP\facades\Locale;
|
|
|
|
class LocaleBundle
|
|
{
|
|
/** @var string Max lifetime for the bundle cache. A new cache is created anytime a locale file in the bundle is modified */
|
|
protected const MAX_CACHE_LIFETIME = '1 year';
|
|
|
|
/** The locale assigned to this bundle */
|
|
public string $locale;
|
|
|
|
/** @var int[] Keeps the locale filenames (key) and their loading priorities (value) */
|
|
protected array $paths = [];
|
|
|
|
/** Keeps the translations, lazy initialized when a translation is requested */
|
|
protected ?Translator $translator = null;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param string $locale Locale assigned to this locale bundle
|
|
* @param int[] $paths Optional list of gettext files to load, where the key must contain the locale path and the value its priority
|
|
*/
|
|
public function __construct(string $locale, ?array $paths = null)
|
|
{
|
|
$this->locale = $locale;
|
|
$this->paths = $paths ?? [];
|
|
asort($this->paths);
|
|
}
|
|
|
|
/**
|
|
* Translate a string using the selected locale.
|
|
* Substitution works by replacing tokens like "{$foo}" with the value of
|
|
* the parameter named "foo" (if supplied).
|
|
*
|
|
* @param string $key Locale key
|
|
* @param array $params Named substitution parameters
|
|
*
|
|
* @return ?string
|
|
*/
|
|
public function translateSingular(string $key, array $params = []): ?string
|
|
{
|
|
$message = $this->getTranslator()->getSingular($key);
|
|
return $message !== null ? $this->_format($message, $params) : null;
|
|
}
|
|
|
|
/**
|
|
* Translate a string using the selected locale with support for plurals.
|
|
* Substitution works by replacing tokens like "{$foo}" with the value of
|
|
* the parameter named "foo" (if supplied).
|
|
*
|
|
* @param string $key Locale key
|
|
* @param int $count Count of items
|
|
* @param array $params Named substitution parameters
|
|
*
|
|
* @return string
|
|
*/
|
|
public function translatePlural(string $key, int $count, array $params = []): ?string
|
|
{
|
|
$message = $this->getTranslator()->getPlural($key, $count);
|
|
return $message !== null ? $this->_format($message, $params) : null;
|
|
}
|
|
|
|
/**
|
|
* Adds a new locale to the bundle
|
|
*/
|
|
public function addPath(string $path, int $priority = 0): void
|
|
{
|
|
$this->paths[$path] = $priority;
|
|
$this->setEntries($this->paths);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the locale paths (keys) that are part of this bundle together with their priorities (values)
|
|
*
|
|
* @return int[]
|
|
*/
|
|
public function getEntries(): array
|
|
{
|
|
return $this->paths;
|
|
}
|
|
|
|
/**
|
|
* Sets the locale paths (keys) that are part of this bundle together with their priorities (values)
|
|
*
|
|
* @param int[] $paths
|
|
*/
|
|
public function setEntries(array $paths): void
|
|
{
|
|
$this->paths = $paths;
|
|
asort($this->paths);
|
|
// Clears the cache
|
|
$this->translator = null;
|
|
}
|
|
|
|
/**
|
|
* Lazily build and retrieves the Translator instance
|
|
*/
|
|
public function getTranslator(): Translator
|
|
{
|
|
if($this->translator) {
|
|
return $this->translator;
|
|
}
|
|
// Caches only the supported locales (avoid spending time with one-offs)
|
|
$isSupported = Locale::isSupported($this->locale);
|
|
$loader = function () use ($isSupported): Translator {
|
|
$translator = new Translator();
|
|
// Merge all the locale files into a single structure
|
|
$firstPath = array_key_first($this->paths);
|
|
foreach (array_keys($this->paths) as $path) {
|
|
$translations = LocaleFile::loadArray($path, $isSupported);
|
|
// Once the first locale file is added, ensures only messages are merged
|
|
$translator->addTranslations($firstPath === $path ? $translations : ['messages' => $translations['messages']]);
|
|
}
|
|
return $translator;
|
|
};
|
|
$key = __METHOD__ . static::MAX_CACHE_LIFETIME . array_reduce(array_keys($this->paths), fn (string $hash, string $path): string => sha1($hash . $path . filemtime($path)), '');
|
|
$expiration = DateInterval::createFromDateString(static::MAX_CACHE_LIFETIME);
|
|
return $this->translator ??= $isSupported ? Cache::remember($key, $expiration, $loader) : $loader();
|
|
}
|
|
|
|
/**
|
|
* Formats the translation
|
|
*/
|
|
private function _format(string $message, array $params = [])
|
|
{
|
|
return count($params) ? str_replace(array_map(fn (string $search): string => "{\${$search}}", array_keys($params)), array_values($params), $message) : $message;
|
|
}
|
|
}
|