From 4067d98ab0f7d14e7f7cab418f00fe6b84777ad9 Mon Sep 17 00:00:00 2001 From: Pppery Date: Mon, 19 Jan 2026 16:26:55 -0500 Subject: [PATCH] Fix calculation of "limited translations" to not include en_US fallback Summary: Broken by me in D25695 last year. Sigh, I really wasn't on my A game then. And also mark locales with zero translations (like "English (Canada)" which doesn't have an upstream translations file) as limited too even if they are varieties of English. Depends on D26559 to work. Test Plan: With the downstream translation extension installed, see more languages (like "Asturian") in "Limited Translations" on http://phorge.localhost/settings/panel/language/. Note that this section is only shown in developer mode (http://phorge.localhost/config/edit/phabricator.developer-mode/ resp `./bin/config set phabricator.developer-mode true`). With the downstream translation extension installed, see "Tarantino" not in Limited Translations (it has < 512 strings by itself but >512 strings if you count the Italian fallback) Without the downstream translation extension installed, see all of the locales upstream in "Limited Translations". With or without the downstream translation extension installed (translatewiki.net doesn't support en-CA), see "English (Canada)" in "Limited Translations". Reviewers: O1 Blessed Committers, aklapper Reviewed By: O1 Blessed Committers, aklapper Subscribers: aklapper, avivey, tobiaswiese, valerio.bozzolan, Matthew, Cigaryno Differential Revision: https://we.phorge.it/D26560 --- .../setting/PhabricatorTranslationSetting.php | 126 ++++++++++++------ 1 file changed, 87 insertions(+), 39 deletions(-) diff --git a/src/applications/settings/setting/PhabricatorTranslationSetting.php b/src/applications/settings/setting/PhabricatorTranslationSetting.php index 42657e396c..b4865e30a9 100644 --- a/src/applications/settings/setting/PhabricatorTranslationSetting.php +++ b/src/applications/settings/setting/PhabricatorTranslationSetting.php @@ -32,24 +32,75 @@ final class PhabricatorTranslationSetting } protected function getSelectOptionGroups() { - $locales = PhutilLocale::loadAllLocales(); + // Loading translations for all locales and determining + // whether they are limited can be expensive so cache it + $cache = PhabricatorCaches::getRuntimeCache(); + $groups = $cache->getKey('locale.groups'); + if (!$groups) { + $groups = $this->getLocaleGroups(); + $cache->setKey('locale.groups', $groups); + } + + // These are done after the cache check so that changes to these config + // settings via the web UI apply immediately + + // Omit silly locales on serious business installs. + $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); + if ($is_serious) { + unset($groups['silly']); + } + // Omit limited and test translations if Phabricator is not in developer + // mode. + $is_dev = PhabricatorEnv::getEnvConfig('phabricator.developer-mode'); + if (!$is_dev) { + unset($groups['limited']); + unset($groups['test']); + } + + // This can't be in the cache since these pht calls + // evaluate based on the locale of the current user $group_labels = array( 'normal' => pht('Translations'), 'limited' => pht('Limited Translations'), 'silly' => pht('Silly Translations'), 'test' => pht('Developer/Test Translations'), ); + $results = array(); + foreach ($groups as $key => $group) { + $label = $group_labels[$key]; + if (!$group) { + continue; + } + + asort($group); + + $results[] = array( + 'label' => $label, + 'options' => $group, + ); + } - $groups = array_fill_keys(array_keys($group_labels), array()); + return $results; + } - $translations = array(); + private function getLocaleGroups() { + $groups = array( + 'normal' => array(), + 'limited' => array(), + 'silly' => array(), + 'test' => array(), + ); + $translations = PhutilTranslation::getAllTranslations(); + $locales = PhutilLocale::loadAllLocales(); foreach ($locales as $locale) { $code = $locale->getLocaleCode(); // Get the locale's localized name if it's available. For example, // "Deutsch" instead of "German". This helps users who do not speak the // current language to find the correct setting. + // This also means that the locale name can be cached as it doesn't + // vary on user settings. $raw_scope = PhabricatorEnv::beginScopedLocale($code); $name = $locale->getLocaleName(); unset($raw_scope); @@ -64,18 +115,46 @@ final class PhabricatorTranslationSetting continue; } - $strings = PhutilTranslation::getTranslationMapForLocale($code); - $size = count($strings); + if (empty($translations[$code])) { + // Locales with zero translations are always "limited" + // even if they are English, even if the fallback has some + // (silly locales that post-process text rather than translating + // like "ENGLISH (ALL CAPS)" are handled above) + $groups['limited'][$code] = $name; + continue; + } // If a translation is English, assume it can fall back to the default // strings and don't caveat its completeness. - $is_english = (substr($code, 0, 3) == 'en_'); + if (substr($code, 0, 3) == 'en_') { + $groups['normal'][$code] = $name; + continue; + } // Arbitrarily pick some number of available strings to promote a // translation out of the "limited" group. The major goal is just to // keep locales with very few strings out of the main group, so users // aren't surprised if a locale has no upstream translations available. - if ($size > 512 || $is_english) { + $limited_max = 512; + + // Grab all fallbacks except the default fallback to en_US + $current = $code; + $strings = array(); + while ($current && $current != 'en_US') { + $strings += $translations[$current]; + $fallbacks = $locales[$current]->getFallbackLocaleCode(); + if (is_array($fallbacks)) { + foreach ($fallbacks as $fb) { + if ($fb != 'en_US' && isset($translations[$fb])) { + $strings += $translations[$fb]; + } + } + break; + } + $current = $fallbacks; + } + + if (count($strings) > $limited_max) { $type = 'normal'; } else { $type = 'limited'; @@ -83,37 +162,6 @@ final class PhabricatorTranslationSetting $groups[$type][$code] = $name; } - - // Omit silly locales on serious business installs. - $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); - if ($is_serious) { - unset($groups['silly']); - } - - // Omit limited and test translations if Phabricator is not in developer - // mode. - $is_dev = PhabricatorEnv::getEnvConfig('phabricator.developer-mode'); - if (!$is_dev) { - unset($groups['limited']); - unset($groups['test']); - } - - $results = array(); - foreach ($groups as $key => $group) { - $label = $group_labels[$key]; - if (!$group) { - continue; - } - - asort($group); - - $results[] = array( - 'label' => $label, - 'options' => $group, - ); - } - - return $results; + return $groups; } - } -- 2.51.2