<?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;
}
}
|