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 @@ -297,6 +297,7 @@ 'PhutilTestCase' => 'infrastructure/testing/PhutilTestCase.php', 'PhutilTestPhobject' => 'object/__tests__/PhutilTestPhobject.php', 'PhutilTortureTestDaemon' => 'daemon/torture/PhutilTortureTestDaemon.php', + 'PhutilTranslatableText' => 'internationalization/PhutilTranslatableText.php', 'PhutilTranslator' => 'internationalization/PhutilTranslator.php', 'PhutilTranslatorTestCase' => 'internationalization/__tests__/PhutilTranslatorTestCase.php', 'PhutilTwitchFuture' => 'future/twitch/PhutilTwitchFuture.php', @@ -663,6 +664,7 @@ 'PhutilTestCase' => 'ArcanistPhutilTestCase', 'PhutilTestPhobject' => 'Phobject', 'PhutilTortureTestDaemon' => 'PhutilDaemon', + 'PhutilTranslatableText' => 'PhutilSafeHTMLProducerInterface', 'PhutilTranslatorTestCase' => 'PhutilTestCase', 'PhutilTwitchFuture' => 'FutureProxy', 'PhutilTypeCheckException' => 'Exception', diff --git a/src/internationalization/PhutilTranslatableText.php b/src/internationalization/PhutilTranslatableText.php new file mode 100644 --- /dev/null +++ b/src/internationalization/PhutilTranslatableText.php @@ -0,0 +1,49 @@ +result = $result; + $this->argv = $argv; + $this->file = $file; + $this->line = $line; + } + + public function __toString() { + return (string)$this->result; + } + + public function producePhutilSafeHTML() { + $this->didRenderAsHTML = true; + + $orig = array(head($this->argv)); + + $argv = array(); + foreach (array_slice($this->argv, 1) as $item) { + $argv[] = '?'; + } + + return phutil_tag( + 'span', + array( + 'class' => 'pht-translatable pht-translation-none', + 'data-sigil' => 'pht-translatable', + 'data-pht' => json_encode( + array( + 'file' => $this->file, + 'line' => $this->line, + 'orig' => $orig, + 'argv' => $argv, + )), + ), + $this->result); + } + +} diff --git a/src/internationalization/PhutilTranslator.php b/src/internationalization/PhutilTranslator.php --- a/src/internationalization/PhutilTranslator.php +++ b/src/internationalization/PhutilTranslator.php @@ -9,6 +9,9 @@ private $language = 'en'; private $translations = array(); + private $translateMode = false; + private $seen = array(); + public static function getInstance() { if (self::$instance === null) { self::$instance = new PhutilTranslator(); @@ -60,6 +63,11 @@ return $this; } + public function setTranslateMode($translate_mode) { + $this->translateMode = $translate_mode; + return $this; + } + public function translate($text /* , ... */) { $translation = idx($this->translations, $text, $text); $args = func_get_args(); @@ -100,6 +108,14 @@ $result = phutil_safe_html($result); } + // If translation mode is enabled, wrap the result in a a translatable text + // object. + if ($this->translateMode) { + list($file, $line) = $this->getFileAndLine(); + $argv = func_get_args(); + $result = new PhutilTranslatableText($result, $argv, $file, $line); + } + return $result; } @@ -154,6 +170,8 @@ * @return string Formatted and translated date. */ public function translateDate($format, DateTime $date) { + $format = (string)$format; + static $format_cache = array(); if (!isset($format_cache[$format])) { $translatable = 'DlSFMaA'; @@ -209,4 +227,14 @@ return true; } + private function getFileAndLine() { + $trace = debug_backtrace(); + foreach ($trace as $frame) { + if (idx($frame, 'function') == 'pht') { + return array(idx($frame, 'file'), idx($frame, 'line')); + } + } + return array(null, null); + } + } diff --git a/src/markup/render.php b/src/markup/render.php --- a/src/markup/render.php +++ b/src/markup/render.php @@ -105,7 +105,7 @@ if ($v === null) { continue; } - $v = phutil_escape_html($v); + $v = phutil_escape_html((string)$v); $attr_string .= ' '.$k.'="'.$v.'"'; } diff --git a/src/utils/utf8.php b/src/utils/utf8.php --- a/src/utils/utf8.php +++ b/src/utils/utf8.php @@ -228,6 +228,8 @@ * @group utf8 */ function phutil_utf8v($string) { + $string = (string)$string; + $res = array(); $len = strlen($string); $ii = 0;