Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14475793
D11745.id28311.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
29 KB
Referenced Files
None
Subscribers
None
D11745.id28311.diff
View Options
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -83,6 +83,7 @@
'PhutilAWSFuture' => 'future/aws/PhutilAWSFuture.php',
'PhutilAWSS3Future' => 'future/aws/PhutilAWSS3Future.php',
'PhutilAggregateException' => 'error/PhutilAggregateException.php',
+ 'PhutilAllCapsEnglishLocale' => 'internationalization/locales/PhutilAllCapsEnglishLocale.php',
'PhutilAmazonAuthAdapter' => 'auth/PhutilAmazonAuthAdapter.php',
'PhutilArgumentParser' => 'parser/argument/PhutilArgumentParser.php',
'PhutilArgumentParserException' => 'parser/argument/exception/PhutilArgumentParserException.php',
@@ -106,6 +107,7 @@
'PhutilBitbucketAuthAdapter' => 'auth/PhutilBitbucketAuthAdapter.php',
'PhutilBootloader' => 'moduleutils/PhutilBootloader.php',
'PhutilBootloaderException' => 'moduleutils/PhutilBootloaderException.php',
+ 'PhutilBritishEnglishLocale' => 'internationalization/locales/PhutilBritishEnglishLocale.php',
'PhutilBufferedIterator' => 'utils/PhutilBufferedIterator.php',
'PhutilBufferedIteratorTestCase' => 'utils/__tests__/PhutilBufferedIteratorTestCase.php',
'PhutilBugtraqParser' => 'parser/PhutilBugtraqParser.php',
@@ -133,6 +135,7 @@
'PhutilConsoleWrapTestCase' => 'console/__tests__/PhutilConsoleWrapTestCase.php',
'PhutilContextFreeGrammar' => 'grammar/PhutilContextFreeGrammar.php',
'PhutilCsprintfTestCase' => 'xsprintf/__tests__/PhutilCsprintfTestCase.php',
+ 'PhutilCzechLocale' => 'internationalization/locales/PhutilCzechLocale.php',
'PhutilDaemon' => 'daemon/PhutilDaemon.php',
'PhutilDaemonOverseer' => 'daemon/PhutilDaemonOverseer.php',
'PhutilDefaultSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilDefaultSyntaxHighlighter.php',
@@ -211,6 +214,7 @@
'PhutilLibraryMapBuilder' => 'moduleutils/PhutilLibraryMapBuilder.php',
'PhutilLibraryTestCase' => '__tests__/PhutilLibraryTestCase.php',
'PhutilLipsumContextFreeGrammar' => 'grammar/PhutilLipsumContextFreeGrammar.php',
+ 'PhutilLocale' => 'internationalization/PhutilLocale.php',
'PhutilLock' => 'filesystem/PhutilLock.php',
'PhutilLockException' => 'filesystem/PhutilLockException.php',
'PhutilLogFileChannel' => 'channel/PhutilLogFileChannel.php',
@@ -258,6 +262,7 @@
'PhutilQueryStringParser' => 'parser/PhutilQueryStringParser.php',
'PhutilQueryStringParserTestCase' => 'parser/__tests__/PhutilQueryStringParserTestCase.php',
'PhutilRainbowSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilRainbowSyntaxHighlighter.php',
+ 'PhutilRawEnglishLocale' => 'internationalization/locales/PhutilRawEnglishLocale.php',
'PhutilReadableSerializer' => 'readableserializer/PhutilReadableSerializer.php',
'PhutilReadableSerializerTestCase' => 'readableserializer/__tests__/PhutilReadableSerializerTestCase.php',
'PhutilRealNameContextFreeGrammar' => 'grammar/PhutilRealNameContextFreeGrammar.php',
@@ -313,6 +318,7 @@
'PhutilTestCase' => 'infrastructure/testing/PhutilTestCase.php',
'PhutilTestPhobject' => 'object/__tests__/PhutilTestPhobject.php',
'PhutilTortureTestDaemon' => 'daemon/torture/PhutilTortureTestDaemon.php',
+ 'PhutilTranslation' => 'internationalization/PhutilTranslation.php',
'PhutilTranslator' => 'internationalization/PhutilTranslator.php',
'PhutilTranslatorTestCase' => 'internationalization/__tests__/PhutilTranslatorTestCase.php',
'PhutilTwitchAuthAdapter' => 'auth/PhutilTwitchAuthAdapter.php',
@@ -326,6 +332,7 @@
'PhutilTypeSpecTestCase' => 'parser/__tests__/PhutilTypeSpecTestCase.php',
'PhutilURI' => 'parser/PhutilURI.php',
'PhutilURITestCase' => 'parser/__tests__/PhutilURITestCase.php',
+ 'PhutilUSEnglishLocale' => 'internationalization/locales/PhutilUSEnglishLocale.php',
'PhutilUTF8StringTruncator' => 'utils/PhutilUTF8StringTruncator.php',
'PhutilUTF8TestCase' => 'utils/__tests__/PhutilUTF8TestCase.php',
'PhutilUnknownSymbolParserGeneratorException' => 'parser/generator/exception/PhutilUnknownSymbolParserGeneratorException.php',
@@ -333,6 +340,7 @@
'PhutilUnreachableTerminalParserGeneratorException' => 'parser/generator/exception/PhutilUnreachableTerminalParserGeneratorException.php',
'PhutilUrisprintfTestCase' => 'xsprintf/__tests__/PhutilUrisprintfTestCase.php',
'PhutilUtilsTestCase' => 'utils/__tests__/PhutilUtilsTestCase.php',
+ 'PhutilVeryWowEnglishLocale' => 'internationalization/locales/PhutilVeryWowEnglishLocale.php',
'PhutilWordPressAuthAdapter' => 'auth/PhutilWordPressAuthAdapter.php',
'PhutilWordPressFuture' => 'future/wordpress/PhutilWordPressFuture.php',
'PhutilXHPASTBinary' => 'parser/xhpast/bin/PhutilXHPASTBinary.php',
@@ -532,6 +540,7 @@
'PhutilAWSFuture' => 'FutureProxy',
'PhutilAWSS3Future' => 'PhutilAWSFuture',
'PhutilAggregateException' => 'Exception',
+ 'PhutilAllCapsEnglishLocale' => 'PhutilLocale',
'PhutilAmazonAuthAdapter' => 'PhutilOAuthAuthAdapter',
'PhutilArgumentParserException' => 'Exception',
'PhutilArgumentParserTestCase' => 'PhutilTestCase',
@@ -554,6 +563,7 @@
'PhutilAuthUserAbortedException' => 'PhutilAuthException',
'PhutilBitbucketAuthAdapter' => 'PhutilOAuth1AuthAdapter',
'PhutilBootloaderException' => 'Exception',
+ 'PhutilBritishEnglishLocale' => 'PhutilLocale',
'PhutilBufferedIterator' => 'Iterator',
'PhutilBufferedIteratorTestCase' => 'PhutilTestCase',
'PhutilBugtraqParserTestCase' => 'PhutilTestCase',
@@ -573,6 +583,7 @@
'PhutilConsoleTable' => 'Phobject',
'PhutilConsoleWrapTestCase' => 'PhutilTestCase',
'PhutilCsprintfTestCase' => 'PhutilTestCase',
+ 'PhutilCzechLocale' => 'PhutilLocale',
'PhutilDefaultSyntaxHighlighterEngine' => 'PhutilSyntaxHighlighterEngine',
'PhutilDefaultSyntaxHighlighterEnginePygmentsFuture' => 'FutureProxy',
'PhutilDefaultSyntaxHighlighterEngineTestCase' => 'PhutilTestCase',
@@ -626,6 +637,7 @@
'PhutilLibraryConflictException' => 'Exception',
'PhutilLibraryTestCase' => 'PhutilTestCase',
'PhutilLipsumContextFreeGrammar' => 'PhutilContextFreeGrammar',
+ 'PhutilLocale' => 'Phobject',
'PhutilLockException' => 'Exception',
'PhutilLogFileChannel' => 'PhutilChannelChannel',
'PhutilLunarPhaseTestCase' => 'PhutilTestCase',
@@ -661,6 +673,7 @@
'PhutilProxyException' => 'Exception',
'PhutilPythonFragmentLexer' => 'PhutilLexer',
'PhutilQueryStringParserTestCase' => 'PhutilTestCase',
+ 'PhutilRawEnglishLocale' => 'PhutilLocale',
'PhutilReadableSerializerTestCase' => 'PhutilTestCase',
'PhutilRealNameContextFreeGrammar' => 'PhutilContextFreeGrammar',
'PhutilRemarkupBoldRule' => 'PhutilRemarkupRule',
@@ -702,6 +715,7 @@
'PhutilTestCase' => 'ArcanistPhutilTestCase',
'PhutilTestPhobject' => 'Phobject',
'PhutilTortureTestDaemon' => 'PhutilDaemon',
+ 'PhutilTranslation' => 'Phobject',
'PhutilTranslatorTestCase' => 'PhutilTestCase',
'PhutilTwitchAuthAdapter' => 'PhutilOAuthAuthAdapter',
'PhutilTwitchFuture' => 'FutureProxy',
@@ -712,6 +726,7 @@
'PhutilTypeMissingParametersException' => 'Exception',
'PhutilTypeSpecTestCase' => 'PhutilTestCase',
'PhutilURITestCase' => 'PhutilTestCase',
+ 'PhutilUSEnglishLocale' => 'PhutilLocale',
'PhutilUTF8StringTruncator' => 'Phobject',
'PhutilUTF8TestCase' => 'PhutilTestCase',
'PhutilUnknownSymbolParserGeneratorException' => 'PhutilParserGeneratorException',
@@ -719,6 +734,7 @@
'PhutilUnreachableTerminalParserGeneratorException' => 'PhutilParserGeneratorException',
'PhutilUrisprintfTestCase' => 'PhutilTestCase',
'PhutilUtilsTestCase' => 'PhutilTestCase',
+ 'PhutilVeryWowEnglishLocale' => 'PhutilLocale',
'PhutilWordPressAuthAdapter' => 'PhutilOAuthAuthAdapter',
'PhutilWordPressFuture' => 'FutureProxy',
'PhutilXHPASTSyntaxHighlighterFuture' => 'FutureProxy',
diff --git a/src/internationalization/PhutilLocale.php b/src/internationalization/PhutilLocale.php
new file mode 100644
--- /dev/null
+++ b/src/internationalization/PhutilLocale.php
@@ -0,0 +1,211 @@
+<?php
+
+/**
+ * Defines a locale for translations.
+ *
+ * Examples might include "English (US)" or "Japanese".
+ */
+abstract class PhutilLocale extends Phobject {
+
+ /**
+ * Get the local identifier code, like "en_US".
+ *
+ * @return string Locale identifier code.
+ */
+ abstract public function getLocaleCode();
+
+
+ /**
+ * Get the human-readable locale name, like "English (US)".
+ *
+ * @return string Human-readable locale name.
+ */
+ abstract public function getLocaleName();
+
+
+ /**
+ * Set a fallback locale which can be used as a default if this locale is
+ * missing translations.
+ *
+ * For locales like "English (Great Britain)", missing translations can be
+ * sourced from "English (US)".
+ *
+ * @return string|null Locale code of fallback locale, or null if there is
+ * no fallback locale.
+ */
+ public function getFallbackLocaleCode() {
+ return null;
+ }
+
+
+ /**
+ * Flags a locale as silly, like "English (Pirate)".
+ *
+ * These locales are fun but disastrously inappropriate for serious
+ * businesses.
+ *
+ * @return bool True if this locale is silly.
+ */
+ public function isSillyLocale() {
+ return false;
+ }
+
+
+ /**
+ * Flags a locale as a testing locale, like "English (US, ALL CAPS)". These
+ * locales are useful for translation development, but not for normal users.
+ *
+ * @return bool True if this is a locale for testing or development.
+ */
+ public function isTestLocale() {
+ return false;
+ }
+
+
+ /**
+ * Indicates that the translator should post-process translations in this
+ * locale by calling @{method:didTranslateString}.
+ *
+ * Doing this incurs a performance penalty, and is not useful for most
+ * languages. However, it can be used to implement test translations like
+ * "English (US, ALL CAPS)".
+ *
+ * @return bool True to postprocess strings.
+ */
+ public function shouldPostProcessTranslations() {
+ return false;
+ }
+
+
+ /**
+ * Callback for post-processing translations.
+ *
+ * By default, this callback is not invoked. To activate it, return `true`
+ * from @{method:shouldPostProcessTranslations}. Activating this callback
+ * incurs a performance penalty.
+ *
+ * @param string The raw input pattern.
+ * @param string The selected translation pattern.
+ * @param list<wild> The raw input arguments.
+ * @param string The translated string.
+ * @return string Post-processed translation string.
+ */
+ public function didTranslateString(
+ $raw_pattern,
+ $translated_pattern,
+ array $args,
+ $result_text) {
+ return $result_text;
+ }
+
+
+ /**
+ * Load all available locales.
+ *
+ * @return map<string, PhutilLocale> Map from codes to locale objects.
+ */
+ public static function loadAllLocales() {
+ static $locales;
+ if ($locales === null) {
+ $objects = id(new PhutilSymbolLoader())
+ ->setAncestorClass(__CLASS__)
+ ->loadObjects();
+
+ $locale_map = array();
+ foreach ($objects as $object) {
+ $locale_code = $object->getLocaleCode();
+ if (empty($locale_map[$locale_code])) {
+ $locale_map[$locale_code] = $object;
+ } else {
+ throw new Exception(
+ pht(
+ 'Two subclasses of "PhutilLocale" ("%s" and "%s") define '.
+ 'locales with the same locale code ("%s"). Each locale must '.
+ 'have a unique locale code.',
+ get_class($object),
+ get_class($locale_map[$locale_code]),
+ $locale_code));
+ }
+ }
+
+ foreach ($locale_map as $locale_code => $locale) {
+ $fallback_code = $locale->getFallbackLocaleCode();
+ if ($fallback_code !== null) {
+ if (empty($locale_map[$fallback_code])) {
+ throw new Exception(
+ pht(
+ 'The locale "%s" has an invalid fallback locale code ("%s"). '.
+ 'No locale class exists which defines this locale.',
+ get_class($locale),
+ $fallback_code));
+ }
+ }
+ }
+
+ foreach ($locale_map as $locale_code => $locale) {
+ $seen = array($locale_code => get_class($locale));
+ self::checkLocaleFallback($locale_map, $locale, $seen);
+ }
+
+ $locales = $locale_map;
+ }
+ return $locales;
+ }
+
+
+ /**
+ * Load a specific locale using a locale code.
+ *
+ * @param string Locale code.
+ * @return PhutilLocale Locale object.
+ */
+ public static function loadLocale($locale_code) {
+ $all_locales = self::loadAllLocales();
+ $locale = idx($all_locales, $locale_code);
+
+ if (!$locale) {
+ throw new Exception(
+ pht(
+ 'There is no locale with the locale code "%s".',
+ $locale_code));
+ }
+
+ return $locale;
+ }
+
+
+ /**
+ * Recursively check locale fallbacks for cycles.
+ *
+ * @param map<string, PhutilLocale> Map of locales.
+ * @param PhutilLocale Current locale.
+ * @param map<string, string> Map of visited locales.
+ * @return void
+ */
+ private static function checkLocaleFallback(
+ array $map,
+ PhutilLocale $locale,
+ array $seen) {
+
+ $fallback_code = $locale->getFallbackLocaleCode();
+ if ($fallback_code === null) {
+ return;
+ }
+
+ if (isset($seen[$fallback_code])) {
+ $seen[] = get_class($locale);
+ $seen[] = pht('...');
+ throw new Exception(
+ pht(
+ 'Locale "%s" is part of a cycle of locales which fall back on '.
+ 'one another in a loop (%s). Locales which fall back on other '.
+ 'locales must not loop.',
+ get_class($locale),
+ implode(' -> ', $seen)));
+ }
+
+ $seen[$fallback_code] = get_class($locale);
+ self::checkLocaleFallback($map, $map[$fallback_code], $seen);
+ }
+
+}
diff --git a/src/internationalization/PhutilTranslation.php b/src/internationalization/PhutilTranslation.php
new file mode 100644
--- /dev/null
+++ b/src/internationalization/PhutilTranslation.php
@@ -0,0 +1,89 @@
+<?php
+
+abstract class PhutilTranslation extends Phobject {
+
+ /**
+ * Get the locale code which this class translates text for, like
+ * "en_GB".
+ *
+ * This should correspond to a valid subclass of @{class:PhutilLocale}.
+ *
+ * @return string Locale code for this translation.
+ */
+ abstract public function getLocaleCode();
+
+
+ /**
+ * Return a map of all translations.
+ *
+ * @return map<string, wild> Map of raw strings to translations.
+ */
+ abstract protected function getTranslations();
+
+
+ /**
+ * Return a filtered map of all strings in this translation.
+ *
+ * Filters out empty/placeholder translations.
+ *
+ * @return map<string, wild> Map of raw strings to translations.
+ */
+ final public function getFilteredTranslations() {
+ $translations = $this->getTranslations();
+
+ foreach ($translations as $key => $translation) {
+ if ($translation === null) {
+ unset($translations[$key]);
+ }
+ }
+
+ return $translations;
+ }
+
+
+ /**
+ * Load all available translation objects.
+ *
+ * @return list<PhutilTranslation> List of available translation sources.
+ */
+ public static function loadAllTranslations() {
+ static $translations;
+ if ($translations === null) {
+ $translations = id(new PhutilSymbolLoader())
+ ->setAncestorClass(__CLASS__)
+ ->loadObjects();
+ }
+ return $translations;
+ }
+
+
+ /**
+ * Load the complete translation map for a locale.
+ *
+ * This will compile primary and fallback translations into a single
+ * translation map.
+ *
+ * @param string Locale code, like "en_US".
+ * @return map<string, wild> Map of all avialable translations.
+ */
+ public static function getTranslationMapForLocale($locale_code) {
+ $locale = PhutilLocale::loadLocale($locale_code);
+
+ $translations = self::loadAllTranslations();
+
+ $results = array();
+ foreach ($translations as $translation) {
+ if ($translation->getLocaleCode() == $locale_code) {
+ $results += $translation->getFilteredTranslations();
+ }
+ }
+
+ $fallback_code = $locale->getFallbackLocaleCode();
+ if ($fallback_code !== null) {
+ $results += self::getTranslationMapForLocale($fallback_code);
+ }
+
+ return $results;
+ }
+
+}
diff --git a/src/internationalization/PhutilTranslator.php b/src/internationalization/PhutilTranslator.php
--- a/src/internationalization/PhutilTranslator.php
+++ b/src/internationalization/PhutilTranslator.php
@@ -4,7 +4,9 @@
static private $instance;
- private $language = 'en';
+ private $locale;
+ private $localeCode;
+ private $shouldPostProcess;
private $translations = array();
public static function getInstance() {
@@ -18,8 +20,10 @@
self::$instance = $instance;
}
- public function setLanguage($language) {
- $this->language = $language;
+ public function setLocale(PhutilLocale $locale) {
+ $this->locale = $locale;
+ $this->localeCode = $locale->getLocaleCode();
+ $this->shouldPostProcess = $locale->shouldPostProcessTranslations();
return $this;
}
@@ -53,8 +57,8 @@
* @param array Identifier in key, translation in value.
* @return PhutilTranslator Provides fluent interface.
*/
- public function addTranslations(array $translations) {
- $this->translations = array_merge($this->translations, $translations);
+ public function setTranslations(array $translations) {
+ $this->translations = $translations;
return $this;
}
@@ -97,8 +101,12 @@
$result = '[Invalid Translation!] '.$translation;
}
- if ($this->language == 'en-ac') {
- $result = strtoupper($result);
+ if ($this->shouldPostProcess) {
+ $result = $this->locale->didTranslateString(
+ $text,
+ $translation,
+ $args,
+ $result);
}
if ($is_html) {
@@ -118,17 +126,23 @@
$variant = $variant->getNumber();
}
- switch ($this->language) {
+ // TODO: Move these into PhutilLocale if benchmarks show we aren't
+ // eating too much of a performance cost.
- case 'en':
- case 'en-ac':
+ switch ($this->localeCode) {
+
+ case 'en_US':
+ case 'en_GB':
+ case 'en_W*':
+ case 'en_R*':
+ case 'en_A*':
list($singular, $plural) = $translations;
if ($variant == 1) {
return $singular;
}
return $plural;
- case 'cs':
+ case 'cs_CZ':
if ($variant instanceof PhutilPerson) {
list($male, $female) = $translations;
if ($variant->getSex() == PhutilPerson::SEX_FEMALE) {
diff --git a/src/internationalization/__tests__/PhutilPhtTestCase.php b/src/internationalization/__tests__/PhutilPhtTestCase.php
--- a/src/internationalization/__tests__/PhutilPhtTestCase.php
+++ b/src/internationalization/__tests__/PhutilPhtTestCase.php
@@ -11,15 +11,18 @@
$this->assertEqual('beer', pht('beer'));
$this->assertEqual('1 beer(s)', pht('%d beer(s)', 1));
- PhutilTranslator::getInstance()->addTranslations(
+ $english_locale = PhutilLocale::loadLocale('en_US');
+ PhutilTranslator::getInstance()->setLocale($english_locale);
+ PhutilTranslator::getInstance()->setTranslations(
array(
'%d beer(s)' => array('%d beer', '%d beers'),
));
$this->assertEqual('1 beer', pht('%d beer(s)', 1));
- PhutilTranslator::getInstance()->setLanguage('cs');
- PhutilTranslator::getInstance()->addTranslations(
+ $czech_locale = PhutilLocale::loadLocale('cs_CZ');
+ PhutilTranslator::getInstance()->setLocale($czech_locale);
+ PhutilTranslator::getInstance()->setTranslations(
array(
'%d beer(s)' => array('%d pivo', '%d piva', '%d piv'),
));
diff --git a/src/internationalization/__tests__/PhutilTranslatorTestCase.php b/src/internationalization/__tests__/PhutilTranslatorTestCase.php
--- a/src/internationalization/__tests__/PhutilTranslatorTestCase.php
+++ b/src/internationalization/__tests__/PhutilTranslatorTestCase.php
@@ -3,8 +3,8 @@
final class PhutilTranslatorTestCase extends PhutilTestCase {
public function testEnglish() {
- $translator = new PhutilTranslator();
- $translator->addTranslations(
+ $translator = $this->newTranslator('en_US');
+ $translator->setTranslations(
array(
'%d line(s)' => array('%d line', '%d lines'),
'%d char(s) on %d row(s)' => array(
@@ -31,11 +31,10 @@
}
public function testSingleVariant() {
- $translator = new PhutilTranslator();
- $translator->setLanguage('en');
+ $translator = $this->newTranslator('en_US');
// In this translation, we have no alternatives for the first conversion.
- $translator->addTranslations(
+ $translator->setTranslations(
array(
'Run the command %s %d time(s).' => array(
array(
@@ -54,9 +53,8 @@
}
public function testCzech() {
- $translator = new PhutilTranslator();
- $translator->setLanguage('cs');
- $translator->addTranslations(
+ $translator = $this->newTranslator('cs_CZ');
+ $translator->setTranslations(
array(
'%d beer(s)' => array('%d pivo', '%d piva', '%d piv'),
));
@@ -70,9 +68,8 @@
}
public function testPerson() {
- $translator = new PhutilTranslator();
- $translator->setLanguage('cs');
- $translator->addTranslations(
+ $translator = $this->newTranslator('cs_CZ');
+ $translator->setTranslations(
array(
'%s wrote.' => array('%s napsal.', '%s napsala.'),
));
@@ -95,13 +92,13 @@
public function testTranslateDate() {
$date = new DateTime('2012-06-21');
+ $translator = $this->newTranslator('en_US');
- $translator = new PhutilTranslator();
$this->assertEqual('June', $translator->translateDate('F', $date));
$this->assertEqual('June 21', $translator->translateDate('F d', $date));
$this->assertEqual('F', $translator->translateDate('\F', $date));
- $translator->addTranslations(
+ $translator->setTranslations(
array(
'June' => 'correct',
'21' => 'wrong',
@@ -113,12 +110,17 @@
}
public function testSetInstance() {
- PhutilTranslator::setInstance(new PhutilTranslator());
+ $english_translator = $this->newTranslator('en_US');
+
+ PhutilTranslator::setInstance($english_translator);
$original = PhutilTranslator::getInstance();
$this->assertEqual('color', pht('color'));
+ $british_locale = PhutilLocale::loadLocale('en_GB');
+
$british = new PhutilTranslator();
- $british->addTranslations(
+ $british->setLocale($british_locale);
+ $british->setTranslations(
array(
'color' => 'colour',
));
@@ -130,12 +132,13 @@
}
public function testFormatNumber() {
- $translator = new PhutilTranslator();
+ $translator = $this->newTranslator('en_US');
+
$this->assertEqual('1,234', $translator->formatNumber(1234));
$this->assertEqual('1,234.5', $translator->formatNumber(1234.5, 1));
$this->assertEqual('1,234.5678', $translator->formatNumber(1234.5678, 4));
- $translator->addTranslations(
+ $translator->setTranslations(
array(
',' => ' ',
'.' => ',',
@@ -146,8 +149,9 @@
}
public function testNumberTranslations() {
- $translator = new PhutilTranslator();
- $translator->addTranslations(
+ $translator = $this->newTranslator('en_US');
+
+ $translator->setTranslations(
array(
'%s line(s)' => array('%s line', '%s lines'),
));
@@ -192,7 +196,8 @@
),
);
- $translator = new PhutilTranslator();
+ $translator = $this->newTranslator('en_US');
+
foreach ($tests as $original => $translations) {
foreach ($translations as $translation => $expect) {
$valid = ($expect ? 'valid' : 'invalid');
@@ -208,7 +213,7 @@
$string = '%s awoke <strong>suddenly</strong> at %s.';
$when = '<4 AM>';
- $translator = new PhutilTranslator();
+ $translator = $this->newTranslator('en_US');
// When no components are HTML, everything is treated as a string.
$who = '<span>Abraham</span>';
@@ -243,4 +248,10 @@
$translation->getHTMLContent());
}
+ private function newTranslator($locale_code) {
+ $locale = PhutilLocale::loadLocale($locale_code);
+ return id(new PhutilTranslator())
+ ->setLocale($locale);
+ }
+
}
diff --git a/src/internationalization/locales/PhutilAllCapsEnglishLocale.php b/src/internationalization/locales/PhutilAllCapsEnglishLocale.php
new file mode 100644
--- /dev/null
+++ b/src/internationalization/locales/PhutilAllCapsEnglishLocale.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * A test locale which transforms strings into uppercase.
+ *
+ * This can be helpful for identifying untranslated strings.
+ */
+final class PhutilAllCapsEnglishLocale extends PhutilLocale {
+
+ public function getLocaleCode() {
+ return 'en_A*';
+ }
+
+ public function getLocaleName() {
+ return pht('English (US, ALL CAPS)');
+ }
+
+ public function getFallbackLocaleCode() {
+ return 'en_US';
+ }
+
+ public function isTestLocale() {
+ return true;
+ }
+
+ public function shouldPostProcessTranslations() {
+ return true;
+ }
+
+ public function didTranslateString(
+ $raw_pattern,
+ $translated_pattern,
+ array $args,
+ $result_text) {
+ return phutil_utf8_strtoupper($result_text);
+ }
+
+}
diff --git a/src/internationalization/locales/PhutilBritishEnglishLocale.php b/src/internationalization/locales/PhutilBritishEnglishLocale.php
new file mode 100644
--- /dev/null
+++ b/src/internationalization/locales/PhutilBritishEnglishLocale.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Serious, legitimate British English locale.
+ */
+final class PhutilBritishEnglishLocale extends PhutilLocale {
+
+ public function getLocaleCode() {
+ return 'en_GB';
+ }
+
+ public function getLocaleName() {
+ return pht('English (Great Britain)');
+ }
+
+ public function getFallbackLocaleCode() {
+ return 'en_US';
+ }
+
+}
diff --git a/src/internationalization/locales/PhutilCzechLocale.php b/src/internationalization/locales/PhutilCzechLocale.php
new file mode 100644
--- /dev/null
+++ b/src/internationalization/locales/PhutilCzechLocale.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Locale for "Czech (Czech Republic)".
+ */
+final class PhutilCzechLocale extends PhutilLocale {
+
+ public function getLocaleCode() {
+ return 'cs_CZ';
+ }
+
+ public function getLocaleName() {
+ return pht('Czech (Czech Republic)');
+ }
+
+ public function isTestLocale() {
+ // This doesn't have any translations yet so mark it as a test locale
+ // for now.
+ return true;
+ }
+
+}
diff --git a/src/internationalization/locales/PhutilRawEnglishLocale.php b/src/internationalization/locales/PhutilRawEnglishLocale.php
new file mode 100644
--- /dev/null
+++ b/src/internationalization/locales/PhutilRawEnglishLocale.php
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * A test locale which displays the raw strings which are fed into
+ * the translation engine.
+ */
+final class PhutilRawEnglishLocale extends PhutilLocale {
+
+ public function getLocaleCode() {
+ return 'en_R*';
+ }
+
+ public function getLocaleName() {
+ return pht('English (Raw Strings)');
+ }
+
+ public function isTestLocale() {
+ return true;
+ }
+
+}
diff --git a/src/internationalization/locales/PhutilUSEnglishLocale.php b/src/internationalization/locales/PhutilUSEnglishLocale.php
new file mode 100644
--- /dev/null
+++ b/src/internationalization/locales/PhutilUSEnglishLocale.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * The default English locale.
+ */
+final class PhutilUSEnglishLocale extends PhutilLocale {
+
+ public function getLocaleCode() {
+ return 'en_US';
+ }
+
+ public function getLocaleName() {
+ return pht('English (US)');
+ }
+
+}
diff --git a/src/internationalization/locales/PhutilVeryWowEnglishLocale.php b/src/internationalization/locales/PhutilVeryWowEnglishLocale.php
new file mode 100644
--- /dev/null
+++ b/src/internationalization/locales/PhutilVeryWowEnglishLocale.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * Much locale! Very English! Wow!
+ */
+final class PhutilVeryWowEnglishLocale extends PhutilLocale {
+
+ public function getLocaleCode() {
+ return 'en_W*';
+ }
+
+ public function getLocaleName() {
+ return pht('English (Very Wow)');
+ }
+
+ public function getFallbackLocaleCode() {
+ return 'en_US';
+ }
+
+ public function isSillyLocale() {
+ return true;
+ }
+
+}
diff --git a/src/internationalization/pht.php b/src/internationalization/pht.php
--- a/src/internationalization/pht.php
+++ b/src/internationalization/pht.php
@@ -3,8 +3,8 @@
/**
* Translate a string. It uses a translator set by
* `PhutilTranslator::setInstance()` or translations specified by
- * `PhutilTranslator::getInstance()->addTranslations()` and language rules set
- * by `PhutilTranslator::getInstance()->setLanguage()`.
+ * `PhutilTranslator::getInstance()->setTranslations()` and language rules set
+ * by `PhutilTranslator::getInstance()->setLocale()`.
*
* @param string Translation identifier with `sprintf()` placeholders.
* @param mixed Value to select the variant from (e.g. singular or plural).
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Dec 28, 9:00 PM (7 h, 21 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6941142
Default Alt Text
D11745.id28311.diff (29 KB)
Attached To
Mode
D11745: Make translations more modular so third-party libraries can translate their strings
Attached
Detach File
Event Timeline
Log In to Comment