first commit
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @defgroup i18n I18N
|
||||
* Implements localization concerns such as locale files, time zones, and country lists.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file classes/i18n/LocaleMetadata.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 LocaleMetadata
|
||||
*
|
||||
* @ingroup i18n
|
||||
*
|
||||
* @brief Holds metadata about a system locale
|
||||
*/
|
||||
|
||||
namespace PKP\i18n;
|
||||
|
||||
use DomainException;
|
||||
use Exception;
|
||||
use PKP\core\ExportableTrait;
|
||||
use PKP\core\PKPString;
|
||||
use PKP\facades\Locale;
|
||||
use PKP\i18n\interfaces\LocaleInterface;
|
||||
use Sokil\IsoCodes\Database\Languages\Language;
|
||||
|
||||
class LocaleMetadata
|
||||
{
|
||||
use ExportableTrait;
|
||||
|
||||
/**
|
||||
* The following constants define how the locale information will be presented
|
||||
*
|
||||
* LANGUAGE_LOCALE_WITHOUT : The locale will be presented in the current selected language
|
||||
* So, if English and French is available and user selected locale is French, it will be
|
||||
* shown as Français | Anglais
|
||||
*
|
||||
* LANGUAGE_LOCALE_WITH : The locale will be presented in current selected language along
|
||||
* with each locale's own translated name . So, if English and French is available and user
|
||||
* selected locale is French, it will be shown as Français/French | Anglais/English
|
||||
*
|
||||
* LANGUAGE_LOCALE_ONLY : The locale will be presented only in each locale's translated
|
||||
* name . So, if English and French is available and user
|
||||
* selected locale is French, it will be shown as Français | English
|
||||
*/
|
||||
public const LANGUAGE_LOCALE_WITHOUT = 1;
|
||||
public const LANGUAGE_LOCALE_WITH = 2;
|
||||
public const LANGUAGE_LOCALE_ONLY = 3;
|
||||
|
||||
private ?object $_parsedLocale = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct(
|
||||
/** Locale identification */
|
||||
public ?string $locale = null
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the language locale conversion status
|
||||
*
|
||||
*/
|
||||
public static function getLanguageLocaleStatuses(): array
|
||||
{
|
||||
return [
|
||||
self::LANGUAGE_LOCALE_WITHOUT,
|
||||
self::LANGUAGE_LOCALE_WITH,
|
||||
self::LANGUAGE_LOCALE_ONLY
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the display name
|
||||
*
|
||||
* @param string $locale The locale code
|
||||
* @param bool $withCountry Whether to append the country name to language
|
||||
* @param int $langLocaleStatus The language locale conversion value specified by const LocaleMetadata::LANGUAGE_LOCALE_*
|
||||
*
|
||||
* @return string The fully qualified locale with/without own translated locale and with/without country name
|
||||
*/
|
||||
public function getDisplayName(?string $locale = null, bool $withCountry = false, int $langLocaleStatus = self::LANGUAGE_LOCALE_WITHOUT): string
|
||||
{
|
||||
if (!in_array($langLocaleStatus, static::getLanguageLocaleStatuses())) {
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
'Invalid language locale conversion status %s given, must be among [%s]',
|
||||
$langLocaleStatus,
|
||||
implode(',', static::getLanguageLocaleStatuses())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$name = PKPString::regexp_replace(
|
||||
'/\s*\([^)]*\)\s*/',
|
||||
'',
|
||||
PKPString::ucfirst(
|
||||
$this
|
||||
->_getLanguage(
|
||||
$langLocaleStatus === self::LANGUAGE_LOCALE_ONLY ? $this->locale : $locale,
|
||||
$langLocaleStatus === self::LANGUAGE_LOCALE_WITH
|
||||
)
|
||||
->getLocalName()
|
||||
)
|
||||
);
|
||||
|
||||
if ($langLocaleStatus === self::LANGUAGE_LOCALE_WITH) {
|
||||
// Get the translated language name in language's own locale
|
||||
$nameInLangLocale = PKPString::regexp_replace(
|
||||
'/\s*\([^)]*\)\s*/',
|
||||
'',
|
||||
PKPString::ucfirst($this->_getLanguage($this->locale)->getLocalName())
|
||||
);
|
||||
|
||||
$name = __(
|
||||
'common.withForwardSlash',
|
||||
[
|
||||
'item' => $name,
|
||||
'afterSlash' => $nameInLangLocale,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if (!$withCountry) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
$country = $this->getCountry($locale);
|
||||
|
||||
if (!$country) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
if ($langLocaleStatus !== self::LANGUAGE_LOCALE_WITHOUT) {
|
||||
$localizedCountryName = $this->getCountry($this->locale);
|
||||
|
||||
if ($langLocaleStatus === self::LANGUAGE_LOCALE_ONLY) {
|
||||
$country = $localizedCountryName;
|
||||
} else {
|
||||
if (strcmp($localizedCountryName, $country) !== 0) {
|
||||
$country = __(
|
||||
'common.withForwardSlash',
|
||||
[
|
||||
'item' => $country,
|
||||
'afterSlash' => $localizedCountryName
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return __(
|
||||
'common.withParenthesis',
|
||||
[
|
||||
'item' => $name,
|
||||
'inParenthesis' => $country,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the language name
|
||||
*/
|
||||
public function getLanguage(?string $locale = null): string
|
||||
{
|
||||
return $this->_getLanguage($locale)->getLocalName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the country name
|
||||
*/
|
||||
public function getCountry(?string $locale = null): ?string
|
||||
{
|
||||
return $this->_parse()->country ? Locale::getCountries($locale)->getByAlpha2($this->_parse()->country)?->getLocalName() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the script name
|
||||
*/
|
||||
public function getScript(?string $locale = null): ?string
|
||||
{
|
||||
$script = ucfirst($this->_parse()->script);
|
||||
return $this->_parse()->script ? Locale::getScripts($locale)->getByAlpha4($script)->getLocalName() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the locale expects text on the right-to-left format
|
||||
*/
|
||||
public function isRightToLeft(): bool
|
||||
{
|
||||
$locale = $this->_parse();
|
||||
$language = strtolower($locale->language ?? '');
|
||||
$script = strtolower($locale->script ?? '');
|
||||
$rightToLeftLanguages = array_fill_keys(['ar', 'dv', 'fa', 'he', 'ku', 'nqo', 'prs', 'ps', 'sd', 'syr', 'ug', 'ur', 'yi'], true);
|
||||
$languageScriptExceptions = ['sd-deva' => false, 'tzm-arab' => true, 'pa-arab' => true];
|
||||
return $languageScriptExceptions["{$language}-{$script}"]
|
||||
?? $rightToLeftLanguages[$language]
|
||||
?? $languageScriptExceptions[$this->getIsoAlpha3() . "-{$script}"]
|
||||
?? $rightToLeftLanguages[$this->getIsoAlpha3()]
|
||||
?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two locales and retrieves the completeness ratio (source locale keys which are present in the reference)
|
||||
* If a locale isn't supplied, LocaleInterface::DEFAULT_LOCALE will be used as reference
|
||||
*/
|
||||
public function getCompletenessRatio(string $referenceLocale = null): float
|
||||
{
|
||||
$destiny = Locale::getBundle($referenceLocale ?? LocaleInterface::DEFAULT_LOCALE)->getTranslator()->getEntries();
|
||||
$source = Locale::getBundle($this->locale, false)->getTranslator()->getEntries();
|
||||
$intersection = array_intersect_key($source, $destiny);
|
||||
return min(1, count($intersection) / max(1, count($destiny)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves whether the locale can be considered complete respecting a threshold level of completeness
|
||||
*/
|
||||
public function isComplete(float $minimumThreshold = 0.9, ?string $referenceLocale = null): bool
|
||||
{
|
||||
return $this->getCompletenessRatio($referenceLocale) >= $minimumThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the ISO639-1 representation
|
||||
*/
|
||||
public function getIsoAlpha2(): string
|
||||
{
|
||||
return $this->_getLanguage()->getAlpha2();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the ISO639-3 representation
|
||||
*/
|
||||
public function getIsoAlpha3(): string
|
||||
{
|
||||
return $this->_getLanguage()->getAlpha3();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the language
|
||||
*/
|
||||
private function _getLanguage(?string $locale = null, bool $fromCache = true): ?Language
|
||||
{
|
||||
return Locale::getLanguages($locale, $fromCache)->getByAlpha2($this->_parse()->language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the locale string and retrieve its pieces
|
||||
*/
|
||||
private function _parse(): object
|
||||
{
|
||||
if (isset($this->_parsedLocale)) {
|
||||
return $this->_parsedLocale;
|
||||
}
|
||||
if (!preg_match(LocaleInterface::LOCALE_EXPRESSION, $this->locale, $matches)) {
|
||||
throw new DomainException("Invalid locale \"{$this->locale}\"");
|
||||
}
|
||||
return $this->_parsedLocale = (object) [
|
||||
'language' => $matches['language'],
|
||||
'country' => $matches['country'] ?? null,
|
||||
// Updates our script definitions to match the ISO 15924
|
||||
'script' => isset($matches['script']) ? str_replace(['cyrillic', 'latin'], ['latn', 'cyrl'], strtolower($matches['script'])) : null
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user