first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-06-08 17:09:23 -04:00
commit df3a033196
17887 changed files with 8637778 additions and 0 deletions
@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
/**
* @defgroup i18n I18N
* Implements localization concerns such as locale files, time zones, and country lists.
*/
/**
* @file classes/i18n/translation/IsoCodesTranslationDriver.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 IsoCodesTranslationDriver
*
* @ingroup i18n
*
* @brief Translation provider for the IsoCodes package, faster and optimized to not keep items in memory
*/
namespace PKP\i18n\translation;
use APP\core\Application;
use DateInterval;
use DomainException;
use Illuminate\Support\Facades\Cache;
use PKP\i18n\interfaces\LocaleInterface;
use Sokil\IsoCodes\TranslationDriver\TranslationDriverInterface;
class IsoCodesTranslationDriver implements TranslationDriverInterface
{
/** @var string Max lifetime for the cache, a new cache file is created whenever the translation file is modified */
protected const MAX_CACHE_LIFETIME = '1 year';
protected ?Translator $translator = null;
protected string $locale;
/**
* Constructor
*/
public function __construct(string $locale)
{
$this->setLocale($locale);
}
/**
* Setups the translator
*/
public function configureDirectory(string $isoNumber, string $directory): void
{
if (!preg_match(LocaleInterface::LOCALE_EXPRESSION, $this->locale, $matches)) {
throw new DomainException("Invalid locale \"{$this->locale}\"");
}
$locale = (object) [
'language' => $matches['language'],
'country' => $matches['country'] ?? null,
'script' => $matches['script'] ?? null
];
$locales = [$this->locale, ...($locale->script ? [$locale->language . '@' . $locale->script] : []), $locale->language];
// Attempts to find the best locale
foreach ($locales as $locale) {
$path = "{$directory}/{$locale}/LC_MESSAGES/{$isoNumber}.mo";
if (file_exists($path)) {
// Check if it's installed before caching the ISO codes (huge dataset), just to avoid a slow installation page
$loader = fn () => Translator::createFromTranslationsArray(LocaleFile::loadArray($path, Application::isInstalled()));
$key = __METHOD__ . static::MAX_CACHE_LIFETIME . $path . filemtime($path);
$expiration = DateInterval::createFromDateString(static::MAX_CACHE_LIFETIME);
$this->translator = Application::isInstalled() ? Cache::remember($key, $expiration, $loader) : $loader();
break;
}
}
}
/**
* Setup the driver locale
*/
public function setLocale(string $locale): void
{
$this->locale = $locale;
}
/**
* Attempts to translate an entry
*/
public function translate(string $isoNumber, string $message): string
{
return $this->translator?->getSingular($message) ?: $message;
}
}
@@ -0,0 +1,150 @@
<?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;
}
}
@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
/**
* @defgroup i18n I18N
* Implements localization concerns such as locale files, time zones, and country lists.
*/
/**
* @file classes/i18n/translation/LocaleFile.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 LocaleFile
*
* @ingroup i18n
*
* @brief Loads translations from a locale file
*/
namespace PKP\i18n\translation;
use DateInterval;
use Exception;
use Gettext\Generator\ArrayGenerator;
use Gettext\Loader\LoaderInterface;
use Gettext\Loader\MoLoader;
use Gettext\Loader\PoLoader;
use Gettext\Translations;
use Illuminate\Support\Facades\Cache;
use SplFileInfo;
abstract class LocaleFile
{
/** @var string Max lifetime for the cache, a new cache file is created whenever the translation file is modified */
protected const MAX_CACHE_LIFETIME = '1 year';
/**
* Retrieves a suitable loader
*/
public static function getLoader(string $path): LoaderInterface
{
return match (strtolower((new SplFileInfo($path))->getExtension())) {
'po' => new PoLoader(),
'mo' => new MoLoader(),
default => throw new Exception("There's no suitable gettext loader for this file type")
};
}
/**
* Loads the translations from a file
*/
public static function loadTranslations(string $path): Translations
{
return self::getLoader($path)->loadFile($path);
}
/**
* Loads the translations from a file as an array and caches the content physically as a PHP file in order to use the opcache
*/
public static function loadArray(string $path, bool $useCache = false): array
{
$loader = fn () => (new ArrayGenerator(['includeEmpty' => false]))->generateArray(static::loadTranslations($path));
$key = __METHOD__ . static::MAX_CACHE_LIFETIME . $path . filemtime($path);
return $useCache ? Cache::remember($key, DateInterval::createFromDateString(static::MAX_CACHE_LIFETIME), $loader) : $loader();
}
}
@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
/**
* @defgroup i18n I18N
* Implements localization concerns such as locale files, time zones, and country lists.
*/
/**
* @file classes/i18n/translation/Translator.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 Translator
*
* @ingroup i18n
*
* @brief Extends the default GetText translator with serialization and the possibility to detect when translations failed
*/
namespace PKP\i18n\translation;
use Gettext\Translator as GetTextTranslator;
use PKP\core\ExportableTrait;
class Translator extends GetTextTranslator
{
use ExportableTrait;
/**
* Builds a translator instance from arrays
*/
public static function createFromTranslationsArray(array ...$translations): static
{
$translator = new static();
foreach ($translations as $translationSet) {
$translator->addTranslations($translationSet);
}
return $translator;
}
/**
* Retrieves a singular translation
*/
public function getSingular(string $original): ?string
{
$translation = $this->getTranslation(null, null, $original);
return $translation[0] ?? null;
}
/**
* Retrieves a plural translation
*/
public function getPlural(string $original, int $value): ?string
{
$translation = $this->getTranslation(null, null, $original);
$key = $this->getPluralIndex(null, $value, $translation === null);
return $translation[$key] ?? null;
}
/**
* Retrieves the raw translator data
*/
public function getEntries(string $context = ''): array
{
return $this->dictionary[$this->domain][$context] ?? [];
}
}