1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-10 08:52:39 +01:00

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
This commit is contained in:
epriestley 2015-02-11 13:02:35 -08:00
parent 187836b8a9
commit d4680a7e4e
14 changed files with 137 additions and 149 deletions

View file

@ -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',

View file

@ -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();

View file

@ -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;

View file

@ -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<string,string> I think, but that doesn't
// exist yet.
$this->newOption('translation.override', 'wild', array())
->setSummary(pht('Override translations.'))
->setDescription(

View file

@ -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);
}

View file

@ -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();

View file

@ -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

View file

@ -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() {

View file

@ -1,18 +0,0 @@
<?php
final class PhabricatorAllCapsTranslation
extends PhabricatorTranslation {
final public function getLanguage() {
return 'en-ac';
}
public function getName() {
return 'All Caps';
}
public function getTranslations() {
return array();
}
}

View file

@ -0,0 +1,31 @@
<?php
final class PhabricatorBritishEnglishTranslation
extends PhutilTranslation {
public function getLocaleCode() {
return 'en_GB';
}
protected function getTranslations() {
return array(
'%s set this project\'s color to %s.' =>
'%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.',
);
}
}

View file

@ -1,16 +0,0 @@
<?php
final class PhabricatorEnglishTranslation
extends PhabricatorBaseEnglishTranslation {
public function getName() {
return 'English';
}
public function getTranslations() {
return
PhabricatorEnv::getEnvConfig('translation.override') +
parent::getTranslations();
}
}

View file

@ -1,37 +0,0 @@
<?php
abstract class PhabricatorTranslation {
abstract public function getLanguage();
abstract public function getName();
abstract public function getTranslations();
/**
* Return the cleaned translation array.
*
* @return dict<string, wild> 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<string, wild> Translation map, with empty translations.
* @return dict<string, wild> 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;
}
}

View file

@ -1,13 +1,13 @@
<?php
abstract class PhabricatorBaseEnglishTranslation
extends PhabricatorTranslation {
final class PhabricatorUSEnglishTranslation
extends PhutilTranslation {
final public function getLanguage() {
return 'en';
public function getLocaleCode() {
return 'en_US';
}
public function getTranslations() {
protected function getTranslations() {
return array(
'No daemon(s) with id(s) "%s" exist!' => array(
'No daemon with id %s exists!',

View file

@ -0,0 +1,22 @@
<?php
final class PhabricatorVeryWowEnglishTranslation
extends PhutilTranslation {
public function getLocaleCode() {
return 'en_W*';
}
protected function getTranslations() {
return array(
'Search' => '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!',
);
}
}