From d4680a7e4e84166163a32e62d3b90e37e281748c Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 11 Feb 2015 13:02:35 -0800 Subject: [PATCH] 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 --- src/__phutil_library_map__.php | 13 +++---- .../base/controller/PhabricatorController.php | 10 ++--- .../PhabricatorExtraConfigSetupCheck.php | 3 ++ .../PhabricatorTranslationsConfigOptions.php | 13 ------- .../people/storage/PhabricatorUser.php | 13 ------- .../panel/PhabricatorAccountSettingsPanel.php | 28 ++++++++----- .../contributor/internationalization.diviner | 39 ++++++++++--------- src/infrastructure/env/PhabricatorEnv.php | 33 ++++++++++++++-- .../PhabricatorAllCapsTranslation.php | 18 --------- .../PhabricatorBritishEnglishTranslation.php | 31 +++++++++++++++ .../PhabricatorEnglishTranslation.php | 16 -------- .../translation/PhabricatorTranslation.php | 37 ------------------ ...hp => PhabricatorUSEnglishTranslation.php} | 10 ++--- .../PhabricatorVeryWowEnglishTranslation.php | 22 +++++++++++ 14 files changed, 137 insertions(+), 149 deletions(-) delete mode 100644 src/infrastructure/internationalization/translation/PhabricatorAllCapsTranslation.php create mode 100644 src/infrastructure/internationalization/translation/PhabricatorBritishEnglishTranslation.php delete mode 100644 src/infrastructure/internationalization/translation/PhabricatorEnglishTranslation.php delete mode 100644 src/infrastructure/internationalization/translation/PhabricatorTranslation.php rename src/infrastructure/internationalization/translation/{PhabricatorBaseEnglishTranslation.php => PhabricatorUSEnglishTranslation.php} (99%) create mode 100644 src/infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 325887d1cc..02ac60bf77 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1245,7 +1245,6 @@ phutil_register_library_map(array( 'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php', 'PhabricatorActivitySettingsPanel' => 'applications/settings/panel/PhabricatorActivitySettingsPanel.php', 'PhabricatorAdministratorsPolicyRule' => 'applications/policy/rule/PhabricatorAdministratorsPolicyRule.php', - 'PhabricatorAllCapsTranslation' => 'infrastructure/internationalization/translation/PhabricatorAllCapsTranslation.php', 'PhabricatorAlmanacApplication' => 'applications/almanac/application/PhabricatorAlmanacApplication.php', 'PhabricatorAmazonAuthProvider' => 'applications/auth/provider/PhabricatorAmazonAuthProvider.php', 'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php', @@ -1414,7 +1413,6 @@ phutil_register_library_map(array( 'PhabricatorAutoEventListener' => 'infrastructure/events/PhabricatorAutoEventListener.php', 'PhabricatorBarePageUIExample' => 'applications/uiexample/examples/PhabricatorBarePageUIExample.php', 'PhabricatorBarePageView' => 'view/page/PhabricatorBarePageView.php', - 'PhabricatorBaseEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorBaseEnglishTranslation.php', 'PhabricatorBaseProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorBaseProtocolAdapter.php', 'PhabricatorBaseURISetupCheck' => 'applications/config/check/PhabricatorBaseURISetupCheck.php', 'PhabricatorBcryptPasswordHasher' => 'infrastructure/util/password/PhabricatorBcryptPasswordHasher.php', @@ -1435,6 +1433,7 @@ phutil_register_library_map(array( 'PhabricatorBotTarget' => 'infrastructure/daemon/bot/target/PhabricatorBotTarget.php', 'PhabricatorBotUser' => 'infrastructure/daemon/bot/target/PhabricatorBotUser.php', 'PhabricatorBotWhatsNewHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotWhatsNewHandler.php', + 'PhabricatorBritishEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorBritishEnglishTranslation.php', 'PhabricatorBuiltinPatchList' => 'infrastructure/storage/patch/PhabricatorBuiltinPatchList.php', 'PhabricatorBusyUIExample' => 'applications/uiexample/examples/PhabricatorBusyUIExample.php', 'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php', @@ -1717,7 +1716,6 @@ phutil_register_library_map(array( 'PhabricatorEmbedFileRemarkupRule' => 'applications/files/markup/PhabricatorEmbedFileRemarkupRule.php', 'PhabricatorEmojiRemarkupRule' => 'applications/macro/markup/PhabricatorEmojiRemarkupRule.php', 'PhabricatorEmptyQueryException' => 'infrastructure/query/PhabricatorEmptyQueryException.php', - 'PhabricatorEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorEnglishTranslation.php', 'PhabricatorEnv' => 'infrastructure/env/PhabricatorEnv.php', 'PhabricatorEnvTestCase' => 'infrastructure/env/__tests__/PhabricatorEnvTestCase.php', 'PhabricatorEvent' => 'infrastructure/events/PhabricatorEvent.php', @@ -2563,7 +2561,6 @@ phutil_register_library_map(array( 'PhabricatorTransactions' => 'applications/transactions/constants/PhabricatorTransactions.php', 'PhabricatorTransactionsApplication' => 'applications/transactions/application/PhabricatorTransactionsApplication.php', 'PhabricatorTransformedFile' => 'applications/files/storage/PhabricatorTransformedFile.php', - 'PhabricatorTranslation' => 'infrastructure/internationalization/translation/PhabricatorTranslation.php', 'PhabricatorTranslationsConfigOptions' => 'applications/config/option/PhabricatorTranslationsConfigOptions.php', 'PhabricatorTriggerAction' => 'infrastructure/daemon/workers/action/PhabricatorTriggerAction.php', 'PhabricatorTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorTriggerClock.php', @@ -2587,6 +2584,7 @@ phutil_register_library_map(array( 'PhabricatorUIExample' => 'applications/uiexample/examples/PhabricatorUIExample.php', 'PhabricatorUIExampleRenderController' => 'applications/uiexample/controller/PhabricatorUIExampleRenderController.php', 'PhabricatorUIExamplesApplication' => 'applications/uiexample/application/PhabricatorUIExamplesApplication.php', + 'PhabricatorUSEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php', 'PhabricatorUnitsTestCase' => 'view/__tests__/PhabricatorUnitsTestCase.php', 'PhabricatorUnsubscribedFromObjectEdgeType' => 'applications/transactions/edges/PhabricatorUnsubscribedFromObjectEdgeType.php', 'PhabricatorUser' => 'applications/people/storage/PhabricatorUser.php', @@ -2618,6 +2616,7 @@ phutil_register_library_map(array( 'PhabricatorUserTransaction' => 'applications/people/storage/PhabricatorUserTransaction.php', 'PhabricatorUsersPolicyRule' => 'applications/policy/rule/PhabricatorUsersPolicyRule.php', 'PhabricatorVCSResponse' => 'applications/repository/response/PhabricatorVCSResponse.php', + 'PhabricatorVeryWowEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php', 'PhabricatorWatcherHasObjectEdgeType' => 'applications/transactions/edges/PhabricatorWatcherHasObjectEdgeType.php', 'PhabricatorWordPressAuthProvider' => 'applications/auth/provider/PhabricatorWordPressAuthProvider.php', 'PhabricatorWorker' => 'infrastructure/daemon/workers/PhabricatorWorker.php', @@ -4472,7 +4471,6 @@ phutil_register_library_map(array( 'PhabricatorActionView' => 'AphrontView', 'PhabricatorActivitySettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorAdministratorsPolicyRule' => 'PhabricatorPolicyRule', - 'PhabricatorAllCapsTranslation' => 'PhabricatorTranslation', 'PhabricatorAlmanacApplication' => 'PhabricatorApplication', 'PhabricatorAmazonAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorAnchorView' => 'AphrontView', @@ -4658,7 +4656,6 @@ phutil_register_library_map(array( 'PhabricatorAutoEventListener' => 'PhabricatorEventListener', 'PhabricatorBarePageUIExample' => 'PhabricatorUIExample', 'PhabricatorBarePageView' => 'AphrontPageView', - 'PhabricatorBaseEnglishTranslation' => 'PhabricatorTranslation', 'PhabricatorBaseURISetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorBcryptPasswordHasher' => 'PhabricatorPasswordHasher', 'PhabricatorBinariesSetupCheck' => 'PhabricatorSetupCheck', @@ -4675,6 +4672,7 @@ phutil_register_library_map(array( 'PhabricatorBotSymbolHandler' => 'PhabricatorBotHandler', 'PhabricatorBotUser' => 'PhabricatorBotTarget', 'PhabricatorBotWhatsNewHandler' => 'PhabricatorBotHandler', + 'PhabricatorBritishEnglishTranslation' => 'PhutilTranslation', 'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList', 'PhabricatorBusyUIExample' => 'PhabricatorUIExample', 'PhabricatorCacheDAO' => 'PhabricatorLiskDAO', @@ -4986,7 +4984,6 @@ phutil_register_library_map(array( 'PhabricatorEmbedFileRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorEmojiRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorEmptyQueryException' => 'Exception', - 'PhabricatorEnglishTranslation' => 'PhabricatorBaseEnglishTranslation', 'PhabricatorEnvTestCase' => 'PhabricatorTestCase', 'PhabricatorEvent' => 'PhutilEvent', 'PhabricatorEventListener' => 'PhutilEventListener', @@ -5910,6 +5907,7 @@ phutil_register_library_map(array( 'PhabricatorUIConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorUIExampleRenderController' => 'PhabricatorController', 'PhabricatorUIExamplesApplication' => 'PhabricatorApplication', + 'PhabricatorUSEnglishTranslation' => 'PhutilTranslation', 'PhabricatorUnitsTestCase' => 'PhabricatorTestCase', 'PhabricatorUnsubscribedFromObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorUser' => array( @@ -5954,6 +5952,7 @@ phutil_register_library_map(array( 'PhabricatorUserTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorUsersPolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorVCSResponse' => 'AphrontResponse', + 'PhabricatorVeryWowEnglishTranslation' => 'PhutilTranslation', 'PhabricatorWatcherHasObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorWordPressAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorWorkerActiveTask' => 'PhabricatorWorkerTask', diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index cd529b97cc..b5aee9f017 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -96,13 +96,9 @@ abstract class PhabricatorController extends AphrontController { $request->setUser($user); } - $translation = $user->getTranslation(); - if ($translation && - $translation != PhabricatorEnv::getEnvConfig('translation.provider')) { - $translation = newv($translation, array()); - PhutilTranslator::getInstance() - ->setLanguage($translation->getLanguage()) - ->addTranslations($translation->getCleanTranslations()); + $locale_code = $user->getTranslation(); + if ($locale_code) { + PhabricatorEnv::setLocaleCode($locale_code); } $preferences = $user->loadPreferences(); diff --git a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php index c6f5b8b694..e0a9b92830 100644 --- a/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php +++ b/src/applications/config/check/PhabricatorExtraConfigSetupCheck.php @@ -203,6 +203,9 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck { 'the server as the user you want it to run under.'), 'notification.debug' => pht( 'Notifications no longer have a dedicated debugging mode.'), + 'translation.provider' => pht( + 'The translation implementation has changed and providers are no '. + 'longer used or supported.'), ); return $ancient_config; diff --git a/src/applications/config/option/PhabricatorTranslationsConfigOptions.php b/src/applications/config/option/PhabricatorTranslationsConfigOptions.php index 57d86c3844..007c61d197 100644 --- a/src/applications/config/option/PhabricatorTranslationsConfigOptions.php +++ b/src/applications/config/option/PhabricatorTranslationsConfigOptions.php @@ -21,19 +21,6 @@ final class PhabricatorTranslationsConfigOptions public function getOptions() { return array( - $this->newOption( - 'translation.provider', - 'class', - 'PhabricatorEnglishTranslation') - ->setBaseClass('PhabricatorTranslation') - ->setSummary(pht('Translation class that should be used for strings.')) - ->setDescription( - pht( - 'This allows customizing texts used in Phabricator. The class '. - 'must extend PhabricatorTranslation.')) - ->addExample('PhabricatorEnglishTranslation', pht('Valid Setting')), - // TODO: This should be dict I think, but that doesn't - // exist yet. $this->newOption('translation.override', 'wild', array()) ->setSummary(pht('Override translations.')) ->setDescription( diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index db0e91f997..2276a032cb 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -190,19 +190,6 @@ final class PhabricatorUser return '@'.$this->getUsername(); } - public function getTranslation() { - try { - if ($this->translation && - class_exists($this->translation) && - is_subclass_of($this->translation, 'PhabricatorTranslation')) { - return $this->translation; - } - } catch (PhutilMissingSymbolException $ex) { - return null; - } - return null; - } - public function isLoggedIn() { return !($this->getPHID() === null); } diff --git a/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php b/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php index 3fa628ad67..9353d9523f 100644 --- a/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php @@ -64,20 +64,28 @@ final class PhabricatorAccountSettingsPanel extends PhabricatorSettingsPanel { PhutilPerson::SEX_FEMALE => $label_her, ); + $locales = PhutilLocale::loadAllLocales(); + $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); + $is_dev = PhabricatorEnv::getEnvConfig('phabricator.developer-mode'); + $translations = array(); - $symbols = id(new PhutilSymbolLoader()) - ->setType('class') - ->setAncestorClass('PhabricatorTranslation') - ->setConcreteOnly(true) - ->selectAndLoadSymbols(); - foreach ($symbols as $symbol) { - $class = $symbol['name']; - $translations[$class] = newv($class, array())->getName(); + foreach ($locales as $locale) { + if ($is_serious && $locale->isSillyLocale()) { + // Omit silly locales on serious business installs. + continue; + } + if (!$is_dev && $locale->isTestLocale()) { + // Omit test locales on installs which aren't in development mode. + continue; + } + $translations[$locale->getLocaleCode()] = $locale->getLocaleName(); } + asort($translations); - $default = PhabricatorEnv::newObjectFromConfig('translation.provider'); + // TODO: Implement "locale.default" and use it here. + $default = 'en_US'; $translations = array( - '' => pht('Server Default (%s)', $default->getName()), + '' => pht('Server Default: %s', $locales[$default]->getLocaleName()), ) + $translations; $form = new AphrontFormView(); diff --git a/src/docs/contributor/internationalization.diviner b/src/docs/contributor/internationalization.diviner index 25d790e808..401bf4950a 100644 --- a/src/docs/contributor/internationalization.diviner +++ b/src/docs/contributor/internationalization.diviner @@ -1,33 +1,33 @@ @title Internationalization @group developer -What is required from developers to get Phabricator translatable. +Describes Phabricator translation and localization. -= API = +Overview +======== -Translator API is provided by libphutil. It gives us -@{class@libphutil:PhutilTranslator} class and global @{function@libphutil:pht} -function built on top of it. +Phabricator partially supports internationalization, but many of the tools +are missing or in a prototype state. -Developers are supposed to call @{function@libphutil:pht} on all strings that -require translation. +This document very briefly summarizes some of what exists today. -Phabricator provides translations for this translator through -@{class:PhabricatorTranslation} class. +Writing Translatable Code +======== -= Adding a New Translation = +Strings are marked for translation with @{function@libphutil:pht}. -Adding a translation which uses the same language rules as some already existing -translation is relatively simple: Just extend @{class:PhabricatorTranslation} -and you will be able to specify this class in the global configuration -'translation.provider' and users will be able to select it in their preferences. +Adding a New Locale +========= -= Adding a New Language = +To add a new locale, subclass @{class:PhutilLocale}. -Adding a language involves all steps as adding a translation plus specifying the -language rules in @{method@libphutil:PhutilTranslator::chooseVariant}. +Translating Strings +======== -= Singular and Plural = +To translate strings, subclass @{class:PhutilTranslation}. + +Singular and Plural +======== Different languages have various rules for using singular and plural. All you need to do is to call @{function@libphutil:pht} with a text that is suitable for @@ -46,7 +46,8 @@ Translators will translate this text for all different forms the language uses: The ugly identifier passed to @{function@libphutil:pht} will remain in the text only if the translation doesn't exist. -= Male and Female = +Male and Female +======== Different languages use different words for talking about males, females and unknown genders. Callsites have to call @{function@libphutil:pht} passing diff --git a/src/infrastructure/env/PhabricatorEnv.php b/src/infrastructure/env/PhabricatorEnv.php index ac733a909d..4fd790f037 100644 --- a/src/infrastructure/env/PhabricatorEnv.php +++ b/src/infrastructure/env/PhabricatorEnv.php @@ -55,6 +55,7 @@ final class PhabricatorEnv { private static $overrideSource; private static $requestBaseURI; private static $cache; + private static $localeCode; /** * @phutil-external-symbol class PhabricatorStartup @@ -123,10 +124,34 @@ final class PhabricatorEnv { PhabricatorEventEngine::initialize(); - $translation = PhabricatorEnv::newObjectFromConfig('translation.provider'); - PhutilTranslator::getInstance() - ->setLanguage($translation->getLanguage()) - ->addTranslations($translation->getCleanTranslations()); + // TODO: Add a "locale.default" config option once we have some reasonable + // defaults which aren't silly nonsense. + self::setLocaleCode('en_US'); + } + + public static function setLocaleCode($locale_code) { + if ($locale_code == self::$localeCode) { + return; + } + + try { + $locale = PhutilLocale::loadLocale($locale_code); + $translations = PhutilTranslation::getTranslationMapForLocale( + $locale_code); + + $override = PhabricatorEnv::getEnvConfig('translation.override'); + if (!is_array($override)) { + $override = array(); + } + + PhutilTranslator::getInstance() + ->setLocale($locale) + ->setTranslations($override + $translations); + + self::$localeCode = $locale_code; + } catch (Exception $ex) { + // Just ignore this; the user likely has an out-of-date locale code. + } } private static function buildConfigurationSourceStack() { diff --git a/src/infrastructure/internationalization/translation/PhabricatorAllCapsTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorAllCapsTranslation.php deleted file mode 100644 index 0d16ed7c8e..0000000000 --- a/src/infrastructure/internationalization/translation/PhabricatorAllCapsTranslation.php +++ /dev/null @@ -1,18 +0,0 @@ - + '%s set this project\'s colour to %s.', + 'Basic Colors' => + 'Basic Colours', + 'Choose Icon and Color...' => + 'Choose Icon and Colour...', + 'Choose Background Color' => + 'Choose Background Colour', + 'Color' => 'Colour', + 'Colors' => 'Colours', + 'Colors and Transforms' => 'Colours and Transforms', + 'Configure the Phabricator UI, including colors.' => + 'Configure the Phabricator UI, including colours.', + 'Flag Color' => 'Flag Colour', + 'Sets the color of the main header.' => + 'Sets the colour of the main header.', + ); + } + +} diff --git a/src/infrastructure/internationalization/translation/PhabricatorEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorEnglishTranslation.php deleted file mode 100644 index 125cba5668..0000000000 --- a/src/infrastructure/internationalization/translation/PhabricatorEnglishTranslation.php +++ /dev/null @@ -1,16 +0,0 @@ - Translation map with empty translations removed. - */ - public function getCleanTranslations() { - return $this->clean($this->getTranslations()); - } - - - /** - * Removes NULL-valued translation keys from the translation map, to prevent - * echoing out empty strings. - * - * @param dict Translation map, with empty translations. - * @return dict Map with empty translations removed. - */ - protected function clean(array $translation_array) { - foreach ($translation_array as $key => $translation_string) { - if ($translation_string === null) { - unset($translation_array[$key]); - } - } - - return $translation_array; - } - -} diff --git a/src/infrastructure/internationalization/translation/PhabricatorBaseEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php similarity index 99% rename from src/infrastructure/internationalization/translation/PhabricatorBaseEnglishTranslation.php rename to src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index 3e7355ef8b..c2e9463e90 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorBaseEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -1,13 +1,13 @@ array( 'No daemon with id %s exists!', diff --git a/src/infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php new file mode 100644 index 0000000000..f4814ef716 --- /dev/null +++ b/src/infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php @@ -0,0 +1,22 @@ + 'Search! Wow!', + 'Review Code' => 'Wow! Code Review! Wow!', + 'Tasks and Bugs' => 'Much Bug! Very Bad!', + 'Cancel' => 'Nope!', + 'Advanced Search' => 'Much Search!', + 'No search results.' => 'No results! Wow!', + 'Send Message' => 'Bark! Bark Bark!', + ); + } + +}