Skip to content

Commit

Permalink
Merge pull request #10569 from bozana/9707
Browse files Browse the repository at this point in the history
#9707 use weblate locales for ui
  • Loading branch information
bozana authored Dec 11, 2024
2 parents aca0fb1 + cd7b8e9 commit 0055c0e
Show file tree
Hide file tree
Showing 123 changed files with 208 additions and 214 deletions.
4 changes: 2 additions & 2 deletions classes/context/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ public function getSupportedFormLocales(): ?array
/**
* Return associative array of all locales supported by forms on the site.
*
* @param int $langLocaleStatus The const value of one of LocaleMetadata:LANGUAGE_LOCALE_*
* @param int $langLocaleStatus The const value of one of LocaleMetadata::LANGUAGE_LOCALE_*
*
* @return array
*/
Expand Down Expand Up @@ -377,7 +377,7 @@ public function getSupportedLocales()
* Return associative array of all locales supported by the site.
* These locales are used to provide a language toggle on the main site pages.
*
* @param int $langLocaleStatus The const value of one of LocaleMetadata:LANGUAGE_LOCALE_*
* @param int $langLocaleStatus The const value of one of LocaleMetadata::LANGUAGE_LOCALE_*
*
* @return array
*/
Expand Down
2 changes: 1 addition & 1 deletion classes/core/DataObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public function &getData(string $key, string $locale = null)
* @param mixed $value can be either a single value or
* an array of of localized values in the form:
* array(
* 'fr_FR' => 'en français',
* 'fr' => 'en français',
* 'en' => 'in English',
* ...
* )
Expand Down
14 changes: 0 additions & 14 deletions classes/core/traits/LocalizedData.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,6 @@

trait LocalizedData
{
/** @var Conversion table for locales */
public array $_localesTable = [
'be@cyrillic' => 'be',
'bs' => 'bs_Latn',
'fr_FR' => 'fr',
'nb' => 'nb_NO',
'sr@cyrillic' => 'sr_Cyrl',
'sr@latin' => 'sr_Latn',
'uz@cyrillic' => 'uz',
'uz@latin' => 'uz_Latn',
'zh_CN' => 'zh_Hans',
];

/**
* Get a localized value from a multilingual data array
*
Expand Down Expand Up @@ -77,7 +64,6 @@ public function getLocalePrecedence(?string $preferredLocale = null): array
return array_unique(
array_filter([
$preferredLocale ?? Locale::getLocale(),
$this->_localesTable[$preferredLocale ?? Locale::getLocale()] ?? null,
$this->getDefaultLocale(),
$request->getContext()?->getPrimaryLocale(),
$request->getSite()->getPrimaryLocale(),
Expand Down
1 change: 0 additions & 1 deletion classes/galley/Galley.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

use APP\facades\Repo;
use PKP\facades\Locale;
use PKP\i18n\LocaleMetadata;
use PKP\services\PKPSchemaService;
use PKP\submission\Representation;
use PKP\submissionFile\SubmissionFile;
Expand Down
117 changes: 41 additions & 76 deletions classes/i18n/Locale.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@
use Closure;
use DateInterval;
use DirectoryIterator;
use Exception;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use InvalidArgumentException;
use PKP\config\Config;
use PKP\core\Core;
use PKP\core\PKPRequest;
use PKP\facades\Repo;
use PKP\core\PKPSessionGuard;
use PKP\facades\Repo;
use PKP\i18n\interfaces\LocaleInterface;
use PKP\i18n\translation\LocaleBundle;
use PKP\i18n\ui\UITranslator;
Expand All @@ -42,7 +44,6 @@
use RecursiveIteratorIterator;
use RecursiveRegexIterator;
use RegexIterator;
use ResourceBundle;
use Sokil\IsoCodes\Database\Countries;
use Sokil\IsoCodes\Database\Currencies;
use Sokil\IsoCodes\Database\LanguagesInterface;
Expand All @@ -56,7 +57,7 @@ class Locale implements LocaleInterface
protected const MAX_CACHE_LIFETIME = '1 hour';

/** @var string Max lifetime for the submission locales cache. */
protected const MAX_SUBMISSION_LOCALES_CACHE_LIFETIME = '1 year';
protected const MAX_WEBLATE_LOCALES_CACHE_LIFETIME = '1 year';

/**
* @var callable Formatter for missing locale keys
Expand Down Expand Up @@ -100,8 +101,8 @@ class Locale implements LocaleInterface
/** Keeps cached data related only to the current locale */
protected array $cache = [];

/** @var string[]|null Available submission locales cache, where key = locale and value = name */
protected ?array $submissionLocaleNames = null;
/** @var string[]|null Available weblate locales cache, where key = locale and value = weblate name */
protected ?array $weblateLocaleNames = null;

/**
* @copy \Illuminate\Contracts\Translation\Translator::get()
Expand Down Expand Up @@ -154,7 +155,8 @@ public function setLocale($locale): void

$this->locale = $locale;
setlocale(LC_ALL, 'C.utf8', 'C');
\Locale::setDefault(\Locale::lookup(ResourceBundle::getLocales(''), $locale, true));
$locales = array_keys($this->getWeblateLocaleNames());
\Locale::setDefault(\Locale::lookup($locales, $locale, true));
}

/**
Expand Down Expand Up @@ -207,15 +209,8 @@ public function registerLoader(callable $fileLoader, int $priority = 0): void
*/
public function isLocaleValid(?string $locale): bool
{
return !empty($locale) && preg_match(LocaleInterface::LOCALE_EXPRESSION, $locale);
}

/**
* @copy LocaleInterface::isSubmissionLocaleValid()
*/
public function isSubmissionLocaleValid(?string $locale): bool
{
return !empty($locale) && preg_match(LocaleInterface::LOCALE_EXPRESSION_SUBMISSION, $locale);
$locales = $this->getWeblateLocaleNames();
return !empty($locale) && array_key_exists($locale, $locales);
}

/**
Expand Down Expand Up @@ -389,13 +384,12 @@ public function getFormattedDisplayNames(?array $filterByLocales = null, ?array
$locales ??= $this->getLocales();

if ($filterByLocales !== null) {
$filterByLocales = array_intersect_key($locales, array_flip($filterByLocales));
$locales = array_intersect_key($locales, array_flip($filterByLocales));
$filterByLocales = array_keys($locales);
}

$locales = $this->getFilteredLocales($locales, $filterByLocales ? array_keys($filterByLocales) : null);

$localeCodesCount = array_count_values(
collect(array_keys($filterByLocales ?? $locales))
collect($filterByLocales ?? array_keys($locales))
->map(fn (string $value) => trim(explode('@', explode('_', $value)[0])[0]))
->toArray()
);
Expand All @@ -419,6 +413,28 @@ public function getUiTranslator(): UITranslator
return new UITranslator($locale, $this->paths, $localeBundleCacheKey);
}

/**
* Get Weblate languages to array
* Combine app's language names with weblate's in English.
* Weblate's names override app's if same locale key
*
* @throws Exception
*
* @return string[]
*
*/
public function getWeblateLocaleNames(): array
{
return $this->weblateLocaleNames ??= (function (): array {
$file = Core::getBaseDir() . '/' . PKP_LIB_PATH . '/lib/weblateLanguages/languages.json';
$key = __METHOD__ . self::MAX_WEBLATE_LOCALES_CACHE_LIFETIME . filemtime($file);
$expiration = DateInterval::createFromDateString(self::MAX_WEBLATE_LOCALES_CACHE_LIFETIME);
return Cache::remember($key, $expiration, fn (): array => collect(json_decode(file_get_contents($file) ?: throw new Exception('Failed to load Weblate locales'), true))
->sortKeys()
->all());
})();
}

/**
* Get appropriately localized display names for submission locales to array
* If $filterByLocales empty, return all languages.
Expand All @@ -431,42 +447,16 @@ public function getUiTranslator(): UITranslator
*/
public function getSubmissionLocaleDisplayNames(array $filterByLocales = [], ?string $displayLocale = null): array
{
$convDispLocale = $this->convertSubmissionLocaleCode($displayLocale ?: $this->getLocale());
return collect($this->_getSubmissionLocaleNames())
->when($filterByLocales, fn ($sln) => $sln->intersectByKeys(array_is_list($filterByLocales) ? array_flip(array_filter($filterByLocales)) : $filterByLocales))
->when($convDispLocale !== 'en', fn ($sln) => $sln->map(function ($nameEn, $l) use ($convDispLocale) {
$cl = $this->convertSubmissionLocaleCode($l);
$dn = locale_get_display_name($cl, $convDispLocale);
return ($dn && $dn !== $cl) ? $dn : "*$nameEn";
$displayLocale = $displayLocale ?: $this->getLocale();
return collect($this->getWeblateLocaleNames())
->when($filterByLocales, fn (Collection $sln) => $sln->intersectByKeys(array_is_list($filterByLocales) ? array_flip(array_filter($filterByLocales)) : $filterByLocales))
->when($displayLocale !== 'en', fn (Collection $sln) => $sln->map(function ($nameEn, $l) use ($displayLocale) {
$dn = locale_get_display_name($l, $displayLocale);
return ($dn && $dn !== $l) ? $dn : "*{$nameEn}";
}))
->toArray();
}

/**
* Convert submission locale code
*/
public function convertSubmissionLocaleCode(string $locale): string
{
return str_replace(['@cyrillic', '@latin'], ['_Cyrl', '_Latn'], $locale);
}

/**
* Get the filtered locales by locale codes
*
* @param array $locales List of available all locales
* @param array $filterByLocales List of locales code to filter by the returned formatted names list
*
* @return array The list of locales with formatted display name
*/
protected function getFilteredLocales(array $locales, ?array $filterByLocales = null): array
{
if (!$filterByLocales) {
return $locales;
}

return array_intersect_key($locales, array_flip($filterByLocales));
}

/**
* Translates the texts
*
Expand Down Expand Up @@ -559,31 +549,6 @@ private function _getSupportedLocales(): array
return $this->supportedLocales = array_combine($locales, $locales);
}

/**
* Get Weblate submission languages to array
* Combine app's language names with weblate's in English.
* Weblate's names override app's if same locale key
*
* @return string[]
*/
private function _getSubmissionLocaleNames(): array
{
return $this->submissionLocaleNames ??= (function (): array {
$file = Core::getBaseDir() . '/' . PKP_LIB_PATH . '/lib/weblateLanguages/languages.json';
$key = __METHOD__ . self::MAX_SUBMISSION_LOCALES_CACHE_LIFETIME . filemtime($file);
$expiration = DateInterval::createFromDateString(self::MAX_SUBMISSION_LOCALES_CACHE_LIFETIME);
return Cache::remember($key, $expiration, fn (): array => collect($this->getLocales())
->map(function (LocaleMetadata $lm, string $l): string {
$cl = $this->convertSubmissionLocaleCode($l);
$n = locale_get_display_name($cl, 'en');
return ($n && $n !== $cl) ? $n : $lm->getDisplayName('en', true);
})
->merge(json_decode(file_get_contents($file) ?: '', true) ?: [])
->sortKeys()
->toArray());
})();
}

/**
* Retrieve the preferred user locale from our supported locales using the Accept-Language header
* If there's no match, it falls back to the server's primary locale
Expand Down
47 changes: 37 additions & 10 deletions classes/i18n/LocaleConversion.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,36 @@ public static function get2LetterFrom3LetterIsoLanguage(?string $iso3Letter): ?s
/**
* Translate the PKP locale identifier into an ISO639-2b compatible 3-letter string.
*/
public static function get3LetterIsoFromLocale(?string $locale): ?string
public static function get3LetterIsoFromLocale(string $locale): ?string
{
$iso2Letter = substr($locale, 0, 2);
return static::get3LetterFrom2LetterIsoLanguage($iso2Letter);
if (!Locale::isLocaleValid($locale)) {
return null;
}
try {
$languages = self::getISO6392b();
} catch (Exception $e) {
error_log($e->getMessage());
return null;
}
$language = \Locale::getPrimaryLanguage($locale);

foreach (reset($languages) as $languageRaw) {
if (($languageRaw['alpha_2'] ?? null) === $language || $languageRaw['alpha_3'] === $language) {
if ($languageRaw['bibliographic'] ?? null) {
return $languageRaw['bibliographic'];
}
return $languageRaw['alpha_3'] ?? null;
}
}
return null;
}

/**
* Translate an ISO639-2b compatible 3-letter string into the PKP locale identifier.
* This can be ambiguous if several locales are defined for the same language. In this case we'll use the primary locale to disambiguate.
* If that still doesn't determine a unique locale then we'll choose the first locale found.
*
* @deprecated 3.5
*/
public static function getLocaleFrom3LetterIso(?string $iso3Letter): ?string
{
Expand Down Expand Up @@ -140,7 +160,7 @@ public static function getLocaleFrom3LetterIso(?string $iso3Letter): ?string
/**
* Translate the ISO 2-letter language string (ISO639-1) into ISO639-3.
*/
public static function getIso3FromIso1(?string $iso1): ?string
public static function getIso3FromIso1(string $iso1): ?string
{
$locale = Arr::first(Locale::getLocales(), fn (LocaleMetadata $locale) => $locale->getIsoAlpha2() === $iso1);
return $locale ? $locale->getIsoAlpha3() : null;
Expand All @@ -149,7 +169,7 @@ public static function getIso3FromIso1(?string $iso1): ?string
/**
* Translate the ISO639-3 into ISO639-1.
*/
public static function getIso1FromIso3(?string $iso3): ?string
public static function getIso1FromIso3(string $iso3): ?string
{
$locale = Arr::first(Locale::getLocales(), fn (LocaleMetadata $locale) => $locale->getIsoAlpha3() === $iso3);
return $locale ? $locale->getIsoAlpha2() : null;
Expand All @@ -158,17 +178,24 @@ public static function getIso1FromIso3(?string $iso3): ?string
/**
* Translate the PKP locale identifier into an ISO639-3 compatible 3-letter string.
*/
public static function getIso3FromLocale(?string $locale): ?string
public static function getIso3FromLocale(string $locale): ?string
{
$iso1 = substr($locale, 0, 2);
return static::getIso3FromIso1($iso1);
if (!Locale::isLocaleValid($locale)) {
return null;
}
$localeMetadata = new LocaleMetadata($locale);
return $localeMetadata->getIsoAlpha3();
}

/**
* Translate the PKP locale identifier into an ISO639-1 compatible 2-letter string.
*/
public static function getIso1FromLocale(?string $locale): string
public static function getIso1FromLocale(string $locale): ?string
{
return substr($locale, 0, 2);
if (!Locale::isLocaleValid($locale)) {
return null;
}
$localeMetadata = new LocaleMetadata($locale);
return $localeMetadata->getIsoAlpha2();
}
}
Loading

0 comments on commit 0055c0e

Please sign in to comment.