Page MenuHomePhabricator

D11745.id28320.diff
No OneTemporary

D11745.id28320.diff

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

Mime Type
text/plain
Expires
Sat, Dec 28, 9:20 PM (7 h, 27 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6941142
Default Alt Text
D11745.id28320.diff (29 KB)

Event Timeline