@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Update Phabricator to work with more modular translations

Summary:
Ref T7152. Ref T1139. This updates Phabricator so third-party libraries can translate their own stuff. Also:

- Hide "All Caps" when not in development mode, since some users have found this a little confusing.
- With other changes, adds a "Raw Strings" mode (development mode only).
- Add an example silly translation to make sure the serious business flag works.
- Add a basic British English translation.
- Simplify handling of translation overrides.

Test Plan:
- Flipped serious business / development on and off and saw silly/development translations drop off.
- Switched to "All Caps" and saw all caps.
- Switched to Very English, Wow!
- Switched to British english and saw "colour".

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T7152, T1139

Differential Revision: https://secure.phabricator.com/D11747

+137 -149
+6 -7
src/__phutil_library_map__.php
··· 1245 1245 'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php', 1246 1246 'PhabricatorActivitySettingsPanel' => 'applications/settings/panel/PhabricatorActivitySettingsPanel.php', 1247 1247 'PhabricatorAdministratorsPolicyRule' => 'applications/policy/rule/PhabricatorAdministratorsPolicyRule.php', 1248 - 'PhabricatorAllCapsTranslation' => 'infrastructure/internationalization/translation/PhabricatorAllCapsTranslation.php', 1249 1248 'PhabricatorAlmanacApplication' => 'applications/almanac/application/PhabricatorAlmanacApplication.php', 1250 1249 'PhabricatorAmazonAuthProvider' => 'applications/auth/provider/PhabricatorAmazonAuthProvider.php', 1251 1250 'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php', ··· 1414 1413 'PhabricatorAutoEventListener' => 'infrastructure/events/PhabricatorAutoEventListener.php', 1415 1414 'PhabricatorBarePageUIExample' => 'applications/uiexample/examples/PhabricatorBarePageUIExample.php', 1416 1415 'PhabricatorBarePageView' => 'view/page/PhabricatorBarePageView.php', 1417 - 'PhabricatorBaseEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorBaseEnglishTranslation.php', 1418 1416 'PhabricatorBaseProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorBaseProtocolAdapter.php', 1419 1417 'PhabricatorBaseURISetupCheck' => 'applications/config/check/PhabricatorBaseURISetupCheck.php', 1420 1418 'PhabricatorBcryptPasswordHasher' => 'infrastructure/util/password/PhabricatorBcryptPasswordHasher.php', ··· 1435 1433 'PhabricatorBotTarget' => 'infrastructure/daemon/bot/target/PhabricatorBotTarget.php', 1436 1434 'PhabricatorBotUser' => 'infrastructure/daemon/bot/target/PhabricatorBotUser.php', 1437 1435 'PhabricatorBotWhatsNewHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotWhatsNewHandler.php', 1436 + 'PhabricatorBritishEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorBritishEnglishTranslation.php', 1438 1437 'PhabricatorBuiltinPatchList' => 'infrastructure/storage/patch/PhabricatorBuiltinPatchList.php', 1439 1438 'PhabricatorBusyUIExample' => 'applications/uiexample/examples/PhabricatorBusyUIExample.php', 1440 1439 'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php', ··· 1717 1716 'PhabricatorEmbedFileRemarkupRule' => 'applications/files/markup/PhabricatorEmbedFileRemarkupRule.php', 1718 1717 'PhabricatorEmojiRemarkupRule' => 'applications/macro/markup/PhabricatorEmojiRemarkupRule.php', 1719 1718 'PhabricatorEmptyQueryException' => 'infrastructure/query/PhabricatorEmptyQueryException.php', 1720 - 'PhabricatorEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorEnglishTranslation.php', 1721 1719 'PhabricatorEnv' => 'infrastructure/env/PhabricatorEnv.php', 1722 1720 'PhabricatorEnvTestCase' => 'infrastructure/env/__tests__/PhabricatorEnvTestCase.php', 1723 1721 'PhabricatorEvent' => 'infrastructure/events/PhabricatorEvent.php', ··· 2563 2561 'PhabricatorTransactions' => 'applications/transactions/constants/PhabricatorTransactions.php', 2564 2562 'PhabricatorTransactionsApplication' => 'applications/transactions/application/PhabricatorTransactionsApplication.php', 2565 2563 'PhabricatorTransformedFile' => 'applications/files/storage/PhabricatorTransformedFile.php', 2566 - 'PhabricatorTranslation' => 'infrastructure/internationalization/translation/PhabricatorTranslation.php', 2567 2564 'PhabricatorTranslationsConfigOptions' => 'applications/config/option/PhabricatorTranslationsConfigOptions.php', 2568 2565 'PhabricatorTriggerAction' => 'infrastructure/daemon/workers/action/PhabricatorTriggerAction.php', 2569 2566 'PhabricatorTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorTriggerClock.php', ··· 2587 2584 'PhabricatorUIExample' => 'applications/uiexample/examples/PhabricatorUIExample.php', 2588 2585 'PhabricatorUIExampleRenderController' => 'applications/uiexample/controller/PhabricatorUIExampleRenderController.php', 2589 2586 'PhabricatorUIExamplesApplication' => 'applications/uiexample/application/PhabricatorUIExamplesApplication.php', 2587 + 'PhabricatorUSEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php', 2590 2588 'PhabricatorUnitsTestCase' => 'view/__tests__/PhabricatorUnitsTestCase.php', 2591 2589 'PhabricatorUnsubscribedFromObjectEdgeType' => 'applications/transactions/edges/PhabricatorUnsubscribedFromObjectEdgeType.php', 2592 2590 'PhabricatorUser' => 'applications/people/storage/PhabricatorUser.php', ··· 2618 2616 'PhabricatorUserTransaction' => 'applications/people/storage/PhabricatorUserTransaction.php', 2619 2617 'PhabricatorUsersPolicyRule' => 'applications/policy/rule/PhabricatorUsersPolicyRule.php', 2620 2618 'PhabricatorVCSResponse' => 'applications/repository/response/PhabricatorVCSResponse.php', 2619 + 'PhabricatorVeryWowEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php', 2621 2620 'PhabricatorWatcherHasObjectEdgeType' => 'applications/transactions/edges/PhabricatorWatcherHasObjectEdgeType.php', 2622 2621 'PhabricatorWordPressAuthProvider' => 'applications/auth/provider/PhabricatorWordPressAuthProvider.php', 2623 2622 'PhabricatorWorker' => 'infrastructure/daemon/workers/PhabricatorWorker.php', ··· 4472 4471 'PhabricatorActionView' => 'AphrontView', 4473 4472 'PhabricatorActivitySettingsPanel' => 'PhabricatorSettingsPanel', 4474 4473 'PhabricatorAdministratorsPolicyRule' => 'PhabricatorPolicyRule', 4475 - 'PhabricatorAllCapsTranslation' => 'PhabricatorTranslation', 4476 4474 'PhabricatorAlmanacApplication' => 'PhabricatorApplication', 4477 4475 'PhabricatorAmazonAuthProvider' => 'PhabricatorOAuth2AuthProvider', 4478 4476 'PhabricatorAnchorView' => 'AphrontView', ··· 4658 4656 'PhabricatorAutoEventListener' => 'PhabricatorEventListener', 4659 4657 'PhabricatorBarePageUIExample' => 'PhabricatorUIExample', 4660 4658 'PhabricatorBarePageView' => 'AphrontPageView', 4661 - 'PhabricatorBaseEnglishTranslation' => 'PhabricatorTranslation', 4662 4659 'PhabricatorBaseURISetupCheck' => 'PhabricatorSetupCheck', 4663 4660 'PhabricatorBcryptPasswordHasher' => 'PhabricatorPasswordHasher', 4664 4661 'PhabricatorBinariesSetupCheck' => 'PhabricatorSetupCheck', ··· 4675 4672 'PhabricatorBotSymbolHandler' => 'PhabricatorBotHandler', 4676 4673 'PhabricatorBotUser' => 'PhabricatorBotTarget', 4677 4674 'PhabricatorBotWhatsNewHandler' => 'PhabricatorBotHandler', 4675 + 'PhabricatorBritishEnglishTranslation' => 'PhutilTranslation', 4678 4676 'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList', 4679 4677 'PhabricatorBusyUIExample' => 'PhabricatorUIExample', 4680 4678 'PhabricatorCacheDAO' => 'PhabricatorLiskDAO', ··· 4986 4984 'PhabricatorEmbedFileRemarkupRule' => 'PhabricatorObjectRemarkupRule', 4987 4985 'PhabricatorEmojiRemarkupRule' => 'PhutilRemarkupRule', 4988 4986 'PhabricatorEmptyQueryException' => 'Exception', 4989 - 'PhabricatorEnglishTranslation' => 'PhabricatorBaseEnglishTranslation', 4990 4987 'PhabricatorEnvTestCase' => 'PhabricatorTestCase', 4991 4988 'PhabricatorEvent' => 'PhutilEvent', 4992 4989 'PhabricatorEventListener' => 'PhutilEventListener', ··· 5910 5907 'PhabricatorUIConfigOptions' => 'PhabricatorApplicationConfigOptions', 5911 5908 'PhabricatorUIExampleRenderController' => 'PhabricatorController', 5912 5909 'PhabricatorUIExamplesApplication' => 'PhabricatorApplication', 5910 + 'PhabricatorUSEnglishTranslation' => 'PhutilTranslation', 5913 5911 'PhabricatorUnitsTestCase' => 'PhabricatorTestCase', 5914 5912 'PhabricatorUnsubscribedFromObjectEdgeType' => 'PhabricatorEdgeType', 5915 5913 'PhabricatorUser' => array( ··· 5954 5952 'PhabricatorUserTransaction' => 'PhabricatorApplicationTransaction', 5955 5953 'PhabricatorUsersPolicyRule' => 'PhabricatorPolicyRule', 5956 5954 'PhabricatorVCSResponse' => 'AphrontResponse', 5955 + 'PhabricatorVeryWowEnglishTranslation' => 'PhutilTranslation', 5957 5956 'PhabricatorWatcherHasObjectEdgeType' => 'PhabricatorEdgeType', 5958 5957 'PhabricatorWordPressAuthProvider' => 'PhabricatorOAuth2AuthProvider', 5959 5958 'PhabricatorWorkerActiveTask' => 'PhabricatorWorkerTask',
+3 -7
src/applications/base/controller/PhabricatorController.php
··· 96 96 $request->setUser($user); 97 97 } 98 98 99 - $translation = $user->getTranslation(); 100 - if ($translation && 101 - $translation != PhabricatorEnv::getEnvConfig('translation.provider')) { 102 - $translation = newv($translation, array()); 103 - PhutilTranslator::getInstance() 104 - ->setLanguage($translation->getLanguage()) 105 - ->addTranslations($translation->getCleanTranslations()); 99 + $locale_code = $user->getTranslation(); 100 + if ($locale_code) { 101 + PhabricatorEnv::setLocaleCode($locale_code); 106 102 } 107 103 108 104 $preferences = $user->loadPreferences();
+3
src/applications/config/check/PhabricatorExtraConfigSetupCheck.php
··· 203 203 'the server as the user you want it to run under.'), 204 204 'notification.debug' => pht( 205 205 'Notifications no longer have a dedicated debugging mode.'), 206 + 'translation.provider' => pht( 207 + 'The translation implementation has changed and providers are no '. 208 + 'longer used or supported.'), 206 209 ); 207 210 208 211 return $ancient_config;
-13
src/applications/config/option/PhabricatorTranslationsConfigOptions.php
··· 21 21 22 22 public function getOptions() { 23 23 return array( 24 - $this->newOption( 25 - 'translation.provider', 26 - 'class', 27 - 'PhabricatorEnglishTranslation') 28 - ->setBaseClass('PhabricatorTranslation') 29 - ->setSummary(pht('Translation class that should be used for strings.')) 30 - ->setDescription( 31 - pht( 32 - 'This allows customizing texts used in Phabricator. The class '. 33 - 'must extend PhabricatorTranslation.')) 34 - ->addExample('PhabricatorEnglishTranslation', pht('Valid Setting')), 35 - // TODO: This should be dict<string,string> I think, but that doesn't 36 - // exist yet. 37 24 $this->newOption('translation.override', 'wild', array()) 38 25 ->setSummary(pht('Override translations.')) 39 26 ->setDescription(
-13
src/applications/people/storage/PhabricatorUser.php
··· 190 190 return '@'.$this->getUsername(); 191 191 } 192 192 193 - public function getTranslation() { 194 - try { 195 - if ($this->translation && 196 - class_exists($this->translation) && 197 - is_subclass_of($this->translation, 'PhabricatorTranslation')) { 198 - return $this->translation; 199 - } 200 - } catch (PhutilMissingSymbolException $ex) { 201 - return null; 202 - } 203 - return null; 204 - } 205 - 206 193 public function isLoggedIn() { 207 194 return !($this->getPHID() === null); 208 195 }
+18 -10
src/applications/settings/panel/PhabricatorAccountSettingsPanel.php
··· 64 64 PhutilPerson::SEX_FEMALE => $label_her, 65 65 ); 66 66 67 + $locales = PhutilLocale::loadAllLocales(); 68 + $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); 69 + $is_dev = PhabricatorEnv::getEnvConfig('phabricator.developer-mode'); 70 + 67 71 $translations = array(); 68 - $symbols = id(new PhutilSymbolLoader()) 69 - ->setType('class') 70 - ->setAncestorClass('PhabricatorTranslation') 71 - ->setConcreteOnly(true) 72 - ->selectAndLoadSymbols(); 73 - foreach ($symbols as $symbol) { 74 - $class = $symbol['name']; 75 - $translations[$class] = newv($class, array())->getName(); 72 + foreach ($locales as $locale) { 73 + if ($is_serious && $locale->isSillyLocale()) { 74 + // Omit silly locales on serious business installs. 75 + continue; 76 + } 77 + if (!$is_dev && $locale->isTestLocale()) { 78 + // Omit test locales on installs which aren't in development mode. 79 + continue; 80 + } 81 + $translations[$locale->getLocaleCode()] = $locale->getLocaleName(); 76 82 } 83 + 77 84 asort($translations); 78 - $default = PhabricatorEnv::newObjectFromConfig('translation.provider'); 85 + // TODO: Implement "locale.default" and use it here. 86 + $default = 'en_US'; 79 87 $translations = array( 80 - '' => pht('Server Default (%s)', $default->getName()), 88 + '' => pht('Server Default: %s', $locales[$default]->getLocaleName()), 81 89 ) + $translations; 82 90 83 91 $form = new AphrontFormView();
+20 -19
src/docs/contributor/internationalization.diviner
··· 1 1 @title Internationalization 2 2 @group developer 3 3 4 - What is required from developers to get Phabricator translatable. 4 + Describes Phabricator translation and localization. 5 5 6 - = API = 6 + Overview 7 + ======== 7 8 8 - Translator API is provided by libphutil. It gives us 9 - @{class@libphutil:PhutilTranslator} class and global @{function@libphutil:pht} 10 - function built on top of it. 9 + Phabricator partially supports internationalization, but many of the tools 10 + are missing or in a prototype state. 11 11 12 - Developers are supposed to call @{function@libphutil:pht} on all strings that 13 - require translation. 12 + This document very briefly summarizes some of what exists today. 14 13 15 - Phabricator provides translations for this translator through 16 - @{class:PhabricatorTranslation} class. 14 + Writing Translatable Code 15 + ======== 17 16 18 - = Adding a New Translation = 17 + Strings are marked for translation with @{function@libphutil:pht}. 18 + 19 + Adding a New Locale 20 + ========= 19 21 20 - Adding a translation which uses the same language rules as some already existing 21 - translation is relatively simple: Just extend @{class:PhabricatorTranslation} 22 - and you will be able to specify this class in the global configuration 23 - 'translation.provider' and users will be able to select it in their preferences. 22 + To add a new locale, subclass @{class:PhutilLocale}. 24 23 25 - = Adding a New Language = 24 + Translating Strings 25 + ======== 26 26 27 - Adding a language involves all steps as adding a translation plus specifying the 28 - language rules in @{method@libphutil:PhutilTranslator::chooseVariant}. 27 + To translate strings, subclass @{class:PhutilTranslation}. 29 28 30 - = Singular and Plural = 29 + Singular and Plural 30 + ======== 31 31 32 32 Different languages have various rules for using singular and plural. All you 33 33 need to do is to call @{function@libphutil:pht} with a text that is suitable for ··· 46 46 The ugly identifier passed to @{function@libphutil:pht} will remain in the text 47 47 only if the translation doesn't exist. 48 48 49 - = Male and Female = 49 + Male and Female 50 + ======== 50 51 51 52 Different languages use different words for talking about males, females and 52 53 unknown genders. Callsites have to call @{function@libphutil:pht} passing
+29 -4
src/infrastructure/env/PhabricatorEnv.php
··· 55 55 private static $overrideSource; 56 56 private static $requestBaseURI; 57 57 private static $cache; 58 + private static $localeCode; 58 59 59 60 /** 60 61 * @phutil-external-symbol class PhabricatorStartup ··· 123 124 124 125 PhabricatorEventEngine::initialize(); 125 126 126 - $translation = PhabricatorEnv::newObjectFromConfig('translation.provider'); 127 - PhutilTranslator::getInstance() 128 - ->setLanguage($translation->getLanguage()) 129 - ->addTranslations($translation->getCleanTranslations()); 127 + // TODO: Add a "locale.default" config option once we have some reasonable 128 + // defaults which aren't silly nonsense. 129 + self::setLocaleCode('en_US'); 130 + } 131 + 132 + public static function setLocaleCode($locale_code) { 133 + if ($locale_code == self::$localeCode) { 134 + return; 135 + } 136 + 137 + try { 138 + $locale = PhutilLocale::loadLocale($locale_code); 139 + $translations = PhutilTranslation::getTranslationMapForLocale( 140 + $locale_code); 141 + 142 + $override = PhabricatorEnv::getEnvConfig('translation.override'); 143 + if (!is_array($override)) { 144 + $override = array(); 145 + } 146 + 147 + PhutilTranslator::getInstance() 148 + ->setLocale($locale) 149 + ->setTranslations($override + $translations); 150 + 151 + self::$localeCode = $locale_code; 152 + } catch (Exception $ex) { 153 + // Just ignore this; the user likely has an out-of-date locale code. 154 + } 130 155 } 131 156 132 157 private static function buildConfigurationSourceStack() {
-18
src/infrastructure/internationalization/translation/PhabricatorAllCapsTranslation.php
··· 1 - <?php 2 - 3 - final class PhabricatorAllCapsTranslation 4 - extends PhabricatorTranslation { 5 - 6 - final public function getLanguage() { 7 - return 'en-ac'; 8 - } 9 - 10 - public function getName() { 11 - return 'All Caps'; 12 - } 13 - 14 - public function getTranslations() { 15 - return array(); 16 - } 17 - 18 - }
+5 -5
src/infrastructure/internationalization/translation/PhabricatorBaseEnglishTranslation.php src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php
··· 1 1 <?php 2 2 3 - abstract class PhabricatorBaseEnglishTranslation 4 - extends PhabricatorTranslation { 3 + final class PhabricatorUSEnglishTranslation 4 + extends PhutilTranslation { 5 5 6 - final public function getLanguage() { 7 - return 'en'; 6 + public function getLocaleCode() { 7 + return 'en_US'; 8 8 } 9 9 10 - public function getTranslations() { 10 + protected function getTranslations() { 11 11 return array( 12 12 'No daemon(s) with id(s) "%s" exist!' => array( 13 13 'No daemon with id %s exists!',
+31
src/infrastructure/internationalization/translation/PhabricatorBritishEnglishTranslation.php
··· 1 + <?php 2 + 3 + final class PhabricatorBritishEnglishTranslation 4 + extends PhutilTranslation { 5 + 6 + public function getLocaleCode() { 7 + return 'en_GB'; 8 + } 9 + 10 + protected function getTranslations() { 11 + return array( 12 + '%s set this project\'s color to %s.' => 13 + '%s set this project\'s colour to %s.', 14 + 'Basic Colors' => 15 + 'Basic Colours', 16 + 'Choose Icon and Color...' => 17 + 'Choose Icon and Colour...', 18 + 'Choose Background Color' => 19 + 'Choose Background Colour', 20 + 'Color' => 'Colour', 21 + 'Colors' => 'Colours', 22 + 'Colors and Transforms' => 'Colours and Transforms', 23 + 'Configure the Phabricator UI, including colors.' => 24 + 'Configure the Phabricator UI, including colours.', 25 + 'Flag Color' => 'Flag Colour', 26 + 'Sets the color of the main header.' => 27 + 'Sets the colour of the main header.', 28 + ); 29 + } 30 + 31 + }
-16
src/infrastructure/internationalization/translation/PhabricatorEnglishTranslation.php
··· 1 - <?php 2 - 3 - final class PhabricatorEnglishTranslation 4 - extends PhabricatorBaseEnglishTranslation { 5 - 6 - public function getName() { 7 - return 'English'; 8 - } 9 - 10 - public function getTranslations() { 11 - return 12 - PhabricatorEnv::getEnvConfig('translation.override') + 13 - parent::getTranslations(); 14 - } 15 - 16 - }
-37
src/infrastructure/internationalization/translation/PhabricatorTranslation.php
··· 1 - <?php 2 - 3 - abstract class PhabricatorTranslation { 4 - 5 - abstract public function getLanguage(); 6 - abstract public function getName(); 7 - abstract public function getTranslations(); 8 - 9 - 10 - /** 11 - * Return the cleaned translation array. 12 - * 13 - * @return dict<string, wild> Translation map with empty translations removed. 14 - */ 15 - public function getCleanTranslations() { 16 - return $this->clean($this->getTranslations()); 17 - } 18 - 19 - 20 - /** 21 - * Removes NULL-valued translation keys from the translation map, to prevent 22 - * echoing out empty strings. 23 - * 24 - * @param dict<string, wild> Translation map, with empty translations. 25 - * @return dict<string, wild> Map with empty translations removed. 26 - */ 27 - protected function clean(array $translation_array) { 28 - foreach ($translation_array as $key => $translation_string) { 29 - if ($translation_string === null) { 30 - unset($translation_array[$key]); 31 - } 32 - } 33 - 34 - return $translation_array; 35 - } 36 - 37 - }
+22
src/infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php
··· 1 + <?php 2 + 3 + final class PhabricatorVeryWowEnglishTranslation 4 + extends PhutilTranslation { 5 + 6 + public function getLocaleCode() { 7 + return 'en_W*'; 8 + } 9 + 10 + protected function getTranslations() { 11 + return array( 12 + 'Search' => 'Search! Wow!', 13 + 'Review Code' => 'Wow! Code Review! Wow!', 14 + 'Tasks and Bugs' => 'Much Bug! Very Bad!', 15 + 'Cancel' => 'Nope!', 16 + 'Advanced Search' => 'Much Search!', 17 + 'No search results.' => 'No results! Wow!', 18 + 'Send Message' => 'Bark! Bark Bark!', 19 + ); 20 + } 21 + 22 + }