diff --git a/scripts/build_xhpast.sh b/scripts/build_xhpast.sh
index 3e7b42b..0398ca5 100755
--- a/scripts/build_xhpast.sh
+++ b/scripts/build_xhpast.sh
@@ -1,7 +1,7 @@
 #!/usr/bin/env php
 <?php
 
 require_once dirname(__FILE__).'/__init_script__.php';
 
 PhutilXHPASTBinary::build();
-echo "Build successful!\n";
+echo pht('Build successful!')."\n";
diff --git a/scripts/daemon/exec/exec_daemon.php b/scripts/daemon/exec/exec_daemon.php
index e035bc2..ab74112 100755
--- a/scripts/daemon/exec/exec_daemon.php
+++ b/scripts/daemon/exec/exec_daemon.php
@@ -1,119 +1,125 @@
 #!/usr/bin/env php
 <?php
 
 require_once dirname(__FILE__).'/../../__init_script__.php';
 
 if (!posix_isatty(STDOUT)) {
   $sid = posix_setsid();
   if ($sid <= 0) {
-    throw new Exception('Failed to create new process session!');
+    throw new Exception(pht('Failed to create new process session!'));
   }
 }
 
 $args = new PhutilArgumentParser($argv);
-$args->setTagline('daemon executor');
+$args->setTagline(pht('daemon executor'));
 $args->setSynopsis(<<<EOHELP
 **exec_daemon.php** [__options__] __daemon__ ...
     Run an instance of __daemon__.
 EOHELP
   );
 $args->parse(
   array(
     array(
       'name' => 'trace',
-      'help' => 'Enable debug tracing.',
+      'help' => pht('Enable debug tracing.'),
     ),
     array(
       'name' => 'trace-memory',
-      'help' => 'Enable debug memory tracing.',
+      'help' => pht('Enable debug memory tracing.'),
     ),
     array(
       'name' => 'verbose',
-      'help'  => 'Enable verbose activity logging.',
+      'help'  => pht('Enable verbose activity logging.'),
     ),
     array(
       'name' => 'label',
       'short' => 'l',
       'param' => 'label',
       'help' => pht(
-        'Optional process label. Makes "ps" nicer, no behavioral effects.'),
+        'Optional process label. Makes "%s" nicer, no behavioral effects.',
+        'ps'),
     ),
     array(
       'name'     => 'daemon',
       'wildcard' => true,
     ),
   ));
 
 $trace_memory = $args->getArg('trace-memory');
 $trace_mode = $args->getArg('trace') || $trace_memory;
 $verbose = $args->getArg('verbose');
 
 if (function_exists('posix_isatty') && posix_isatty(STDIN)) {
   fprintf(STDERR, pht('Reading daemon configuration from stdin...')."\n");
 }
 $config = @file_get_contents('php://stdin');
 $config = id(new PhutilJSONParser())->parse($config);
 
 PhutilTypeSpec::checkMap(
   $config,
   array(
     'log' => 'optional string|null',
     'argv' => 'optional list<wild>',
     'load' => 'optional list<string>',
     'autoscale' => 'optional wild',
   ));
 
 $log = idx($config, 'log');
 
 if ($log) {
   ini_set('error_log', $log);
   PhutilErrorHandler::setErrorListener(array('PhutilDaemon', 'errorListener'));
 }
 
 $load = idx($config, 'load', array());
 foreach ($load as $library) {
   $library = Filesystem::resolvePath($library);
   phutil_load_library($library);
 }
 
 PhutilErrorHandler::initialize();
 
 $daemon = $args->getArg('daemon');
 if (!$daemon) {
   throw new PhutilArgumentUsageException(
     pht('Specify which class of daemon to start.'));
 } else if (count($daemon) > 1) {
   throw new PhutilArgumentUsageException(
     pht('Specify exactly one daemon to start.'));
 } else {
   $daemon = head($daemon);
   if (!class_exists($daemon)) {
     throw new PhutilArgumentUsageException(
-      pht('No class "%s" exists in any known library.', $daemon));
+      pht(
+        'No class "%s" exists in any known library.',
+        $daemon));
   } else if (!is_subclass_of($daemon, 'PhutilDaemon')) {
     throw new PhutilArgumentUsageException(
-      pht('Class "%s" is not a subclass of "%s".', $daemon, 'PhutilDaemon'));
+      pht(
+        'Class "%s" is not a subclass of "%s".',
+        $daemon,
+        'PhutilDaemon'));
   }
 }
 
 $argv = idx($config, 'argv', array());
 $daemon = newv($daemon, array($argv));
 
 if ($trace_mode) {
   $daemon->setTraceMode();
 }
 
 if ($trace_memory) {
   $daemon->setTraceMemory();
 }
 
 if ($verbose) {
   $daemon->setVerbose(true);
 }
 
 $autoscale = idx($config, 'autoscale');
 if ($autoscale) {
   $daemon->setAutoscaleProperties($autoscale);
 }
 
 $daemon->execute();
diff --git a/scripts/daemon/torture/resist-death.php b/scripts/daemon/torture/resist-death.php
index a464ce4..8142ff8 100755
--- a/scripts/daemon/torture/resist-death.php
+++ b/scripts/daemon/torture/resist-death.php
@@ -1,19 +1,21 @@
 #!/usr/bin/env php
 <?php
 
+require_once dirname(__FILE__).'/../../__init_script__.php';
+
 // This script just creates a process which is difficult to terminate. It is
 // used for daemon resilience tests.
 
 declare(ticks = 1);
 pcntl_signal(SIGTERM, 'ignore');
 pcntl_signal(SIGINT,  'ignore');
 
 function ignore($signo) {
   return;
 }
 
-echo "Resisting death; sleeping forever...\n";
+echo pht('Resisting death; sleeping forever...')."\n";
 
 while (true) {
   sleep(60);
 }
diff --git a/scripts/example/calculator.php b/scripts/example/calculator.php
index 397e5f2..0e5ceed 100755
--- a/scripts/example/calculator.php
+++ b/scripts/example/calculator.php
@@ -1,65 +1,65 @@
 #!/usr/bin/env php
 <?php
 
 require_once dirname(dirname(__FILE__)).'/__init_script__.php';
 
 $args = new PhutilArgumentParser($argv);
-$args->setTagline('simple calculator example');
+$args->setTagline(pht('simple calculator example'));
 $args->setSynopsis(<<<EOHELP
 **calculator.php** __op__ __n__ ...
   Perform a calculation.
 EOHELP
 );
 
 $add_workflow = id(new PhutilArgumentWorkflow())
   ->setName('add')
   ->setExamples('**add** __n__ ...')
-  ->setSynopsis('Compute the sum of a list of numbers.')
+  ->setSynopsis(pht('Compute the sum of a list of numbers.'))
   ->setArguments(
     array(
       array(
         'name'     => 'numbers',
         'wildcard' => true,
       ),
     ));
 
 $mul_workflow = id(new PhutilArgumentWorkflow())
   ->setName('mul')
   ->setExamples('**mul** __n__ ...')
-  ->setSynopsis('Compute the product of a list of numbers.')
+  ->setSynopsis(pht('Compute the product of a list of numbers.'))
   ->setArguments(
     array(
       array(
         'name'     => 'numbers',
         'wildcard' => true,
       ),
     ));
 
 $flow = $args->parseWorkflows(
   array(
     $add_workflow,
     $mul_workflow,
     new PhutilHelpArgumentWorkflow(),
   ));
 
 $nums = $args->getArg('numbers');
 if (empty($nums)) {
-  echo "You must provide one or more numbers!\n";
+  echo pht('You must provide one or more numbers!')."\n";
   exit(1);
 }
 
 foreach ($nums as $num) {
   if (!is_numeric($num)) {
-    echo "Number '{$num}' is not numeric!\n";
+    echo pht("Number '%s' is not numeric!", $num)."\n";
     exit(1);
   }
 }
 
 switch ($flow->getName()) {
   case 'add':
     echo array_sum($nums)."\n";
     break;
   case 'mul':
     echo array_product($nums)."\n";
     break;
 }
diff --git a/scripts/example/subworkflow.php b/scripts/example/subworkflow.php
index 6ae8ea1..c26e31d 100755
--- a/scripts/example/subworkflow.php
+++ b/scripts/example/subworkflow.php
@@ -1,71 +1,71 @@
 #!/usr/bin/env php
 <?php
 
 require_once dirname(dirname(__FILE__)).'/__init_script__.php';
 
 $args = new PhutilArgumentParser($argv);
-$args->setTagline('crazy workflow delgation');
+$args->setTagline(pht('crazy workflow delegation'));
 $args->setSynopsis(<<<EOHELP
 **subworkflow.php** do echo __args__ ...
   Echo some stuff using a convoluted series of delegate workflows.
 EOHELP
 );
 
 
 // This shows how to do manual parsing of raw arguments.
 final class PhutilEchoExampleArgumentWorkflow extends PhutilArgumentWorkflow {
 
   public function isExecutable() {
     return true;
   }
 
   public function shouldParsePartial() {
     return true;
   }
 
   public function execute(PhutilArgumentParser $args) {
     $unconsumed = $args->getUnconsumedArgumentVector();
     echo implode(' ', $unconsumed)."\n";
     return 0;
   }
 
 }
 
 
 // This shows how to delegate to sub-workflows.
 final class PhutilDoExampleArgumentWorkflow extends PhutilArgumentWorkflow {
 
   public function isExecutable() {
     return true;
   }
 
   public function shouldParsePartial() {
     return true;
   }
 
   public function execute(PhutilArgumentParser $args) {
     $echo_workflow = id(new PhutilEchoExampleArgumentWorkflow())
       ->setName('echo')
       ->setExamples('**echo** __string__ ...')
-      ->setSynopsis('Echo __string__.');
+      ->setSynopsis(pht('Echo __string__.'));
 
     $args->parseWorkflows(
       array(
         $echo_workflow,
         new PhutilHelpArgumentWorkflow(),
       ));
   }
 
 }
 
 
 $do_workflow = id(new PhutilDoExampleArgumentWorkflow())
   ->setName('do')
   ->setExamples('**do** __thing__ ...')
-  ->setSynopsis('Do __thing__.');
+  ->setSynopsis(pht('Do __thing__.'));
 
 $args->parseWorkflows(
   array(
     $do_workflow,
     new PhutilHelpArgumentWorkflow(),
   ));
diff --git a/scripts/format_log.php b/scripts/format_log.php
index a527ef7..591b525 100755
--- a/scripts/format_log.php
+++ b/scripts/format_log.php
@@ -1,10 +1,10 @@
 #!/usr/bin/env php
 <?php
 
-// Simple script to format stacktraces in apache logs, etc., so they are more
+// Simple script to format stack traces in apache logs, etc., so they are more
 // readable.
 
 $f = fopen('php://stdin', 'r');
 while (($line = fgets($f)) !== false) {
   echo stripcslashes($line);
 }
diff --git a/scripts/phutil_rebuild_map.php b/scripts/phutil_rebuild_map.php
index c0cbc4a..8fbdf7d 100755
--- a/scripts/phutil_rebuild_map.php
+++ b/scripts/phutil_rebuild_map.php
@@ -1,75 +1,78 @@
 #!/usr/bin/env php
 <?php
 
 require_once dirname(__FILE__).'/__init_script__.php';
 
 $args = new PhutilArgumentParser($argv);
-$args->setTagline('rebuild the library map file');
+$args->setTagline(pht('rebuild the library map file'));
 $args->setSynopsis(<<<EOHELP
     **phutil_rebuild_map.php** [__options__] __root__
         Rebuild the library map file for a libphutil library.
 
 EOHELP
 );
 
 $args->parseStandardArguments();
 $args->parse(
   array(
     array(
       'name'      => 'quiet',
-      'help'      => 'Do not write status messages to stderr.',
+      'help'      => pht('Do not write status messages to stderr.'),
     ),
     array(
       'name'      => 'drop-cache',
-      'help'      => 'Drop the symbol cache and rebuild the entire map from '.
-                     'scratch.',
+      'help'      => pht(
+        'Drop the symbol cache and rebuild the entire map from scratch.'),
     ),
     array(
       'name'      => 'limit',
       'param'     => 'N',
       'default'   => 8,
-      'help'      => 'Controls the number of symbol mapper subprocesses run '.
-                     'at once. Defaults to 8.',
+      'help'      => pht(
+        'Controls the number of symbol mapper subprocesses run at once. '.
+        'Defaults to 8.'),
     ),
     array(
       'name'      => 'show',
-      'help'      => 'Print symbol map to stdout instead of writing it to the '.
-                     'map file.',
+      'help'      => pht(
+        'Print symbol map to stdout instead of writing it to the map file.'),
     ),
     array(
       'name'      => 'ugly',
-      'help'      => 'Use faster but less readable serialization for --show.',
+      'help'      => pht(
+        'Use faster but less readable serialization for %s.',
+        '--show'),
     ),
     array(
       'name'      => 'root',
       'wildcard'  => true,
     ),
   ));
 
 $root = $args->getArg('root');
 if (count($root) !== 1) {
-  throw new Exception('Provide exactly one library root!');
+  throw new Exception(pht('Provide exactly one library root!'));
 }
 $root = Filesystem::resolvePath(head($root));
 
 $builder = new PhutilLibraryMapBuilder($root);
 $builder->setQuiet($args->getArg('quiet'));
 $builder->setSubprocessLimit($args->getArg('limit'));
 
 if ($args->getArg('drop-cache')) {
   $builder->dropSymbolCache();
 }
 
 if ($args->getArg('show')) {
   $library_map = $builder->buildMap();
 
   if ($args->getArg('ugly')) {
     echo json_encode($library_map);
   } else {
     echo id(new PhutilJSON())->encodeFormatted($library_map);
   }
 } else {
   $builder->buildAndWriteMap();
 }
 
 exit(0);
diff --git a/scripts/phutil_symbols.php b/scripts/phutil_symbols.php
index 86a37c8..c0dc2d8 100755
--- a/scripts/phutil_symbols.php
+++ b/scripts/phutil_symbols.php
@@ -1,553 +1,555 @@
 #!/usr/bin/env php
 <?php
 
 // We have to do this first before we load any symbols, because we define the
-// builtin symbol list through introspection.
+// built-in symbol list through introspection.
 $builtins = phutil_symbols_get_builtins();
 
 require_once dirname(__FILE__).'/__init_script__.php';
 
 $args = new PhutilArgumentParser($argv);
-$args->setTagline('identify symbols in a PHP source file');
+$args->setTagline(pht('identify symbols in a PHP source file'));
 $args->setSynopsis(<<<EOHELP
     **phutil_symbols.php** [__options__] __path.php__
         Identify the symbols (clases, functions and interfaces) in a PHP
         source file. Symbols are divided into "have" symbols (symbols the file
         declares) and "need" symbols (symbols the file depends on). For example,
         class declarations are "have" symbols, while object instantiations
         with "new X()" are "need" symbols.
 
         Dependencies on builtins and symbols marked '@phutil-external-symbol'
         in docblocks are omitted without __--all__.
 
         Symbols are reported in JSON on stdout.
 
         This script is used internally by libphutil/arcanist to build maps of
         library symbols.
 
         It would be nice to eventually implement this as a C++ xhpast binary,
         as it's relatively stable and performance is currently awful
         (500ms+ for moderately large files).
 
 EOHELP
 );
 $args->parseStandardArguments();
 $args->parse(
   array(
     array(
       'name'      => 'all',
-      'help'      => 'Report all symbols, including builtins and declared '.
-                     'externals.',
+      'help'      => pht(
+        'Report all symbols, including built-ins and declared externals.'),
     ),
     array(
       'name'      => 'ugly',
-      'help'      => 'Do not prettify JSON output.',
+      'help'      => pht('Do not prettify JSON output.'),
     ),
     array(
       'name'      => 'path',
       'wildcard'  => true,
-      'help'      => 'PHP Source file to analyze.',
+      'help'      => pht('PHP Source file to analyze.'),
     ),
   ));
 
 $paths = $args->getArg('path');
 if (count($paths) !== 1) {
-  throw new Exception('Specify exactly one path!');
+  throw new Exception(pht('Specify exactly one path!'));
 }
 $path = Filesystem::resolvePath(head($paths));
 
 $show_all = $args->getArg('all');
 
 $source_code = Filesystem::readFile($path);
 
 try {
   $tree = XHPASTTree::newFromData($source_code);
 } catch (XHPASTSyntaxErrorException $ex) {
   $result = array(
     'error' => $ex->getMessage(),
     'line'  => $ex->getErrorLine(),
     'file'  => $path,
   );
   $json = new PhutilJSON();
   echo $json->encodeFormatted($result);
   exit(0);
 }
 
 $root = $tree->getRootNode();
 $root->buildSelectCache();
 
 // -(  Unsupported Constructs  )------------------------------------------------
 
 $namespaces = $root->selectDescendantsOfType('n_NAMESPACE');
 foreach ($namespaces as $namespace) {
   phutil_fail_on_unsupported_feature($namespace, $path, pht('namespaces'));
 }
 
 $uses = $root->selectDescendantsOfType('n_USE');
 foreach ($namespaces as $namespace) {
   phutil_fail_on_unsupported_feature(
-    $namespace, $path, pht('namespace `use` statements'));
+    $namespace, $path, pht('namespace `%s` statements', 'use'));
 }
 
 $possible_traits = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
 foreach ($possible_traits as $possible_trait) {
   $attributes = $possible_trait->getChildByIndex(0);
   // Can't use getChildByIndex here because not all classes have attributes
   foreach ($attributes->getChildren() as $attribute) {
     if (strtolower($attribute->getConcreteString()) === 'trait') {
       phutil_fail_on_unsupported_feature($possible_trait, $path, pht('traits'));
     }
   }
 }
 
 
 // -(  Marked Externals  )------------------------------------------------------
 
 
 // Identify symbols marked with "@phutil-external-symbol", so we exclude them
 // from the dependency list.
 
 $externals = array();
 $doc_parser = new PhutilDocblockParser();
 foreach ($root->getTokens() as $token) {
   if ($token->getTypeName() === 'T_DOC_COMMENT') {
     list($block, $special) = $doc_parser->parse($token->getValue());
 
     $ext_list = idx($special, 'phutil-external-symbol');
     $ext_list = explode("\n", $ext_list);
     $ext_list = array_filter($ext_list);
 
     foreach ($ext_list as $ext_ref) {
       $matches = null;
       if (preg_match('/^\s*(\S+)\s+(\S+)/', $ext_ref, $matches)) {
         $externals[$matches[1]][$matches[2]] = true;
       }
     }
   }
 }
 
 
 // -(  Declarations and Dependencies  )-----------------------------------------
 
 
 // The first stage of analysis is to find all the symbols we declare in the
 // file (like functions and classes) and all the symbols we use in the file
 // (like calling functions and invoking classes). Later, we filter this list
 // to exclude builtins.
 
 
 $have = array();  // For symbols we declare.
 $need = array();  // For symbols we use.
 $xmap = array();  // For extended classes and implemented interfaces.
 
 
 // -(  Functions  )-------------------------------------------------------------
 
 
 // Find functions declared in this file.
 
 // This is "function f() { ... }".
 $functions = $root->selectDescendantsOfType('n_FUNCTION_DECLARATION');
 foreach ($functions as $function) {
   $name = $function->getChildByIndex(2);
   if ($name->getTypeName() === 'n_EMPTY') {
     // This is an anonymous function; don't record it into the symbol
     // index.
     continue;
   }
   $have[] = array(
     'type'    => 'function',
     'symbol'  => $name,
   );
 }
 
 
 // Find functions used by this file. Uses:
 //
 //  - Explicit Call
 //  - String literal passed to call_user_func() or call_user_func_array()
 //  - String literal in array literal in call_user_func()/call_user_func_array()
 //
 // TODO: Possibly support these:
 //
 //  - String literal in ReflectionFunction().
 
 // This is "f();".
 $calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
 foreach ($calls as $call) {
   $name = $call->getChildByIndex(0);
   if ($name->getTypeName() === 'n_VARIABLE' ||
       $name->getTypeName() === 'n_VARIABLE_VARIABLE') {
     // Ignore these, we can't analyze them.
     continue;
   }
   if ($name->getTypeName() === 'n_CLASS_STATIC_ACCESS') {
     // These are "C::f()", we'll pick this up later on.
     continue;
   }
   $call_name = $name->getConcreteString();
   if ($call_name === 'call_user_func' ||
       $call_name === 'call_user_func_array') {
     $params = $call->getChildByIndex(1)->getChildren();
     if (!count($params)) {
       // This is a bare call_user_func() with no arguments; just ignore it.
       continue;
     }
     $symbol = array_shift($params);
     $type = 'function';
     $symbol_value = $symbol->getStringLiteralValue();
     $pos = strpos($symbol_value, '::');
     if ($pos) {
       $type = 'class';
       $symbol_value = substr($symbol_value, 0, $pos);
     } else if ($symbol->getTypeName() === 'n_ARRAY_LITERAL') {
       try {
         $type = 'class';
         $symbol_value = idx($symbol->evalStatic(), 0);
       } catch (Exception $ex) {}
     }
     if ($symbol_value && strpos($symbol_value, '$') === false) {
       $need[] = array(
         'type'    => $type,
         'name'    => $symbol_value,
         'symbol'  => $symbol,
       );
     }
   } else {
     $need[] = array(
       'type'    => 'function',
       'symbol'  => $name,
     );
   }
 }
 
 
 // -(  Classes  )---------------------------------------------------------------
 
 
 // Find classes declared by this file.
 
 
 // This is "class X ... { ... }".
 $classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
 foreach ($classes as $class) {
   $class_name = $class->getChildByIndex(1);
   $have[] = array(
     'type'    => 'class',
     'symbol'  => $class_name,
   );
 }
 
 
 // Find classes used by this file. We identify these:
 //
 //  - class ... extends X
 //  - new X
 //  - Static method call
 //  - Static property access
 //  - Use of class constant
 //  - typehints
 //  - catch
 //  - instanceof
 //  - newv()
 //
 // TODO: Possibly support these:
 //
 //  - String literal in ReflectionClass().
 
 
 // This is "class X ... { ... }".
 $classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
 foreach ($classes as $class) {
   $class_name = $class->getChildByIndex(1)->getConcreteString();
   $extends = $class->getChildByIndex(2);
   foreach ($extends->selectDescendantsOfType('n_CLASS_NAME') as $parent) {
     $need[] = array(
       'type'    => 'class',
       'symbol'  => $parent,
     );
 
     // Track all 'extends' in the extension map.
     $xmap[$class_name][] = $parent->getConcreteString();
   }
 }
 
 // This is "new X()".
 $uses_of_new = $root->selectDescendantsOfType('n_NEW');
 foreach ($uses_of_new as $new_operator) {
   $name = $new_operator->getChildByIndex(0);
   if ($name->getTypeName() === 'n_VARIABLE' ||
       $name->getTypeName() === 'n_VARIABLE_VARIABLE') {
     continue;
   }
   $need[] = array(
     'type'    => 'class',
     'symbol'  => $name,
   );
 }
 
 // This covers all of "X::$y", "X::y()" and "X::CONST".
 $static_uses = $root->selectDescendantsOfType('n_CLASS_STATIC_ACCESS');
 foreach ($static_uses as $static_use) {
   $name = $static_use->getChildByIndex(0);
   if ($name->getTypeName() !== 'n_CLASS_NAME') {
     continue;
   }
   $need[] = array(
     'type'    => 'class',
     'symbol'  => $name,
   );
 }
 
 // This is "function (X $x)".
 $parameters = $root->selectDescendantsOfType('n_DECLARATION_PARAMETER');
 foreach ($parameters as $parameter) {
   $hint = $parameter->getChildByIndex(0);
   if ($hint->getTypeName() !== 'n_CLASS_NAME') {
     continue;
   }
   $need[] = array(
     'type'    => 'class/interface',
     'symbol'  => $hint,
   );
 }
 
 // This is "catch (Exception $ex)".
 $catches = $root->selectDescendantsOfType('n_CATCH');
 foreach ($catches as $catch) {
   $need[] = array(
     'type'    => 'class/interface',
     'symbol'  => $catch->getChildOfType(0, 'n_CLASS_NAME'),
   );
 }
 
 // This is "$x instanceof X".
 $instanceofs = $root->selectDescendantsOfType('n_BINARY_EXPRESSION');
 foreach ($instanceofs as $instanceof) {
   $operator = $instanceof->getChildOfType(1, 'n_OPERATOR');
   if ($operator->getConcreteString() !== 'instanceof') {
     continue;
   }
   $class = $instanceof->getChildByIndex(2);
   if ($class->getTypeName() !== 'n_CLASS_NAME') {
     continue;
   }
   $need[] = array(
     'type'    => 'class/interface',
     'symbol'  => $class,
   );
 }
 
 // This is "newv('X')".
 $calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
 foreach ($calls as $call) {
   $call_name = $call->getChildByIndex(0)->getConcreteString();
   if ($call_name !== 'newv') {
     continue;
   }
   $params = $call->getChildByIndex(1)->getChildren();
   if (!count($params)) {
     continue;
   }
   $symbol = reset($params);
   $symbol_value = $symbol->getStringLiteralValue();
   if ($symbol_value && strpos($symbol_value, '$') === false) {
     $need[] = array(
       'type'    => 'class',
       'name'    => $symbol_value,
       'symbol'  => $symbol,
     );
   }
 }
 
 
 // -(  Interfaces  )------------------------------------------------------------
 
 
 // Find interfaces declared in this file.
 
 
 // This is "interface X .. { ... }".
 $interfaces = $root->selectDescendantsOfType('n_INTERFACE_DECLARATION');
 foreach ($interfaces as $interface) {
   $interface_name = $interface->getChildByIndex(1);
   $have[] = array(
     'type'    => 'interface',
     'symbol'  => $interface_name,
   );
 }
 
 
 // Find interfaces used by this file. We identify these:
 //
 //  - class ... implements X
 //  - interface ... extends X
 
 
 // This is "class X ... { ... }".
 $classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
 foreach ($classes as $class) {
   $class_name = $class->getChildByIndex(1)->getConcreteString();
   $implements = $class->getChildByIndex(3);
   $interfaces = $implements->selectDescendantsOfType('n_CLASS_NAME');
   foreach ($interfaces as $interface) {
     $need[] = array(
       'type'    => 'interface',
       'symbol'  => $interface,
     );
 
     // Track 'class ... implements' in the extension map.
     $xmap[$class_name][] = $interface->getConcreteString();
   }
 }
 
 
 // This is "interface X ... { ... }".
 $interfaces = $root->selectDescendantsOfType('n_INTERFACE_DECLARATION');
 foreach ($interfaces as $interface) {
   $interface_name = $interface->getChildByIndex(1)->getConcreteString();
 
   $extends = $interface->getChildByIndex(2);
   foreach ($extends->selectDescendantsOfType('n_CLASS_NAME') as $parent) {
     $need[] = array(
       'type'    => 'interface',
       'symbol'  => $parent,
     );
 
     // Track 'interface ... extends' in the extension map.
     $xmap[$interface_name][] = $parent->getConcreteString();
   }
 }
 
 
 // -(  Analysis  )--------------------------------------------------------------
 
 
 $declared_symbols = array();
 foreach ($have as $key => $spec) {
   $name = $spec['symbol']->getConcreteString();
   $declared_symbols[$spec['type']][$name] = $spec['symbol']->getOffset();
 }
 
 $required_symbols = array();
 foreach ($need as $key => $spec) {
   $name = idx($spec, 'name');
   if (!$name) {
     $name = $spec['symbol']->getConcreteString();
   }
 
   $type = $spec['type'];
   foreach (explode('/', $type) as $libtype) {
     if (!$show_all) {
       if (!empty($externals[$libtype][$name])) {
         // Ignore symbols declared as externals.
         continue 2;
       }
       if (!empty($builtins[$libtype][$name])) {
         // Ignore symbols declared as builtins.
         continue 2;
       }
     }
     if (!empty($declared_symbols[$libtype][$name])) {
       // We declare this symbol, so don't treat it as a requirement.
       continue 2;
     }
   }
   if (!empty($required_symbols[$type][$name])) {
     // Report only the first use of a symbol, since reporting all of them
     // isn't terribly informative.
     continue;
   }
   $required_symbols[$type][$name] = $spec['symbol']->getOffset();
 }
 
 $result = array(
   'have'  => $declared_symbols,
   'need'  => $required_symbols,
   'xmap'  => $xmap,
 );
 
 
 // -(  Output  )----------------------------------------------------------------
 
 
 if ($args->getArg('ugly')) {
   echo json_encode($result);
 } else {
   $json = new PhutilJSON();
   echo $json->encodeFormatted($result);
 }
 
 
 // -(  Library  )---------------------------------------------------------------
 
 function phutil_fail_on_unsupported_feature(XHPASTNode $node, $file, $what) {
   $line = $node->getLineNumber();
-  $message = phutil_console_wrap(pht(
-      '`arc liberate` has limited support for features introduced after PHP '.
-      '5.2.3. This library uses an unsupported feature (%s) on line %d of %s',
-    $what,
-    $line,
-    Filesystem::readablePath($file)));
+  $message = phutil_console_wrap(
+    pht(
+      '`%s` has limited support for features introduced after PHP 5.2.3. '.
+      'This library uses an unsupported feature (%s) on line %d of %s.',
+      'arc liberate',
+      $what,
+      $line,
+      Filesystem::readablePath($file)));
 
   $result = array(
     'error' => $message,
     'line'  => $line,
     'file'  => $file,
   );
   $json = new PhutilJSON();
   echo $json->encodeFormatted($result);
   exit(0);
 }
 
 function phutil_symbols_get_builtins() {
   $builtin = array();
   $builtin['classes']    = get_declared_classes();
   $builtin['interfaces'] = get_declared_interfaces();
 
   $funcs = get_defined_functions();
   $builtin['functions']  = $funcs['internal'];
 
   $compat = json_decode(
     file_get_contents(
       dirname(__FILE__).'/../resources/php_compat_info.json'),
     true);
 
   foreach (array('functions', 'classes', 'interfaces') as $type) {
     // Developers may not have every extension that a library potentially uses
     // installed. We supplement the list of declared functions and classes with
     // a list of known extension functions to avoid raising false positives just
     // because you don't have pcntl, etc.
     $extensions = array_keys($compat[$type]);
     $builtin[$type] = array_merge($builtin[$type], $extensions);
   }
 
   return array(
     'class'     => array_fill_keys($builtin['classes'], true) + array(
       'static' => true,
       'parent' => true,
       'self'   => true,
 
       'PhutilBootloader' => true,
     ),
     'function'  => array_filter(
       array(
         'empty' => true,
         'isset' => true,
         'die'   => true,
 
         // These are provided by libphutil but not visible in the map.
 
         'phutil_is_windows'   => true,
         'phutil_load_library' => true,
         'phutil_is_hiphop_runtime' => true,
 
         // HPHP/i defines these functions as 'internal', but they are NOT
         // builtins and do not exist in vanilla PHP. Make sure we don't mark
         // them as builtin since we need to add dependencies for them.
         'idx'   => false,
         'id'    => false,
       ) + array_fill_keys($builtin['functions'], true)),
     'interface' => array_fill_keys($builtin['interfaces'], true),
   );
 }
diff --git a/scripts/sandpit/harden_directory.php b/scripts/sandpit/harden_directory.php
index e3895c6..1b1e820 100755
--- a/scripts/sandpit/harden_directory.php
+++ b/scripts/sandpit/harden_directory.php
@@ -1,197 +1,197 @@
 #!/usr/bin/env php
 <?php
 
 require_once dirname(__FILE__).'/../__init_script__.php';
 
 if ($argc !== 4) {
   usage();
   exit(1);
 }
 
 function usage() {
   echo phutil_console_format(<<<EOUSAGE
 usage: harden_directory.php soft_directory link scratch_dir
 
 Massively reduce the disk usage of a readonly directory by using hardlinks to
 represent its data. Pass a normal __soft_directory__ to compress, and a
 __link__ to symlink to the compressed version once compression is complete.
 The directory will be cloned into __scratch_dir__ and replaced with hardlinks.
 
 This is primarily useful if you want to have checkouts of hundreds of different
 commits in a version-controlled repository, so you can, e.g., run unit tests or
 other readonly processes on them.
 
 EOUSAGE
   );
 }
 
 $soft = Filesystem::resolvePath($argv[1]);
 $dst  = $argv[2];
 $root = Filesystem::resolvePath($argv[3]);
 
 $hash_map = map_directory($soft);
 $hardened_id = sha1(Filesystem::readRandomBytes(128));
 $hardened_id = hash_path($hardened_id);
 $hardened = $root.'/pit/'.$hardened_id;
 execx('mkdir -p %s', $hardened);
 
 $obj_root = $root.'/obj/';
 foreach ($hash_map as $path => $info) {
   $hash = $info['hash'];
   $type = $info['type'];
 
   $obj = $obj_root.hash_path($hash);
 
   $link = $hardened.'/'.$path;
   $dir = dirname($link);
   if (!is_dir($dir)) {
     $ok = mkdir($dir, 0777, $recursive = true);
     if (!$ok) {
-      throw new Exception("Failed to make directory for '{$link}'!");
+      throw new Exception(pht("Failed to make directory for '%s'!", $link));
     }
   }
 
   // We need to use actual symlinks in this case.
   if ($type == 'link') {
     $ok = symlink(readlink($soft.'/'.$path), $link);
     if (!$ok) {
-      throw new Exception("Failed to create symlink '{$link}'!");
+      throw new Exception(pht("Failed to create symlink '%s'!", $link));
     }
     continue;
   }
 
   if ($type === 'exec') {
     // Multiple hardlinks share a single executable bit, so we need to keep
-    // executable versions separate from nonexecutable versions.
+    // executable versions separate from non-executable versions.
     $obj .= '.x';
   }
 
   // Make copies of each object, obj.0, obj.1, etc., after there are too many
   // hardlinks. This can occur for the empty file, particularly.
   $n = 0;
   do {
     $stat = @lstat($obj.'.'.$n);
     if (!$stat) {
       break;
     }
     if ($stat[3] < 32000) {
       // TODO: On NTFS, this needs to be 1023. It is not apparently trivial to
       // determine if a disk is NTFS or not, or what the link limit for a disk
       // is. On linux "df -T /path/to/dir" may be useful, but on OS X this does
       // something totally different...
       break;
     }
     ++$n;
   } while (true);
 
   $obj = $obj.'.'.$n;
 
   if ($stat === false) {
     $ok = mkdir(dirname($obj), 0777, $recursive = true);
     if (!$ok) {
-      throw new Exception("Failed to make directory for '{$obj}'.");
+      throw new Exception(pht("Failed to make directory for '%s'.", $obj));
     }
     $ok = copy($soft.'/'.$path, $obj);
     if (!$ok) {
-      throw new Exception("Failed to copy file '{$soft}/{$path}'!");
+      throw new Exception(pht("Failed to copy file '%s/%s'!", $soft, $path));
     }
     if ($type === 'exec') {
       $ok = chmod($obj, 0755);
       if (!$ok) {
-        throw new Exception("Failed to chmod file '{$obj}'!");
+        throw new Exception(pht("Failed to chmod file '%s'!", $obj));
       }
     }
   }
 
   $ok = link($obj, $link);
   if (!$ok) {
-    throw new Exception("Failed to hardlink '{$obj}' to '{$link}'!");
+    throw new Exception(pht("Failed to hardlink '%s' to '%s'!", $obj, $link));
   }
 }
 
 // TODO: Replace link to soft directory with link to hardened directory.
 // execx('ln -sf %s %s', $dst, $hardened);
 
 echo $hardened."\n";
 exit(0);
 
 function hash_path($hash) {
   return preg_replace('/([a-z0-9]{2})/', '\1/', $hash, 3);
 }
 
 function map_directory($dir) {
   try {
     if (Filesystem::pathExists($dir.'/.git')) {
       list($list) = execx(
         '(cd %s && git ls-tree -r --full-tree --abbrev=40 HEAD)',
         $dir);
       $list = trim($list);
       $list = explode("\n", $list);
 
       $map = array();
       foreach ($list as $line) {
         $matches = null;
         $regexp  = '/^(\d{6}) (\w+) ([a-z0-9]{40})\t(.*)$/';
         if (!preg_match($regexp, $line, $matches)) {
-          throw new Exception("Unable to parse line '{$line}'!");
+          throw new Exception(pht("Unable to parse line '%s'!", $line));
         }
         $flag = $matches[1];
         $type = $matches[2];
         $hash = $matches[3];
         $file = $matches[4];
         if ($type === 'commit') {
           // Deal with Git submodules.
           $submap = map_directory($dir.'/'.$file);
           foreach ($submap as $subfile => $info) {
             $map[$file.'/'.$subfile] = $info;
           }
         } else {
           $mask = (int)base_convert($flag, 8, 10);
           $type = 'file';
           if ($mask & 0111) {
-            echo "EXEC: {$file}\n";
+            echo pht('EXEC: %s', $file)."\n";
             $type = 'exec';
           } else if (($mask & 0120000) === 0120000) {
             $type = 'link';
           }
           $map[$file] = array(
             'hash' => $hash,
             'type' => $type,
           );
         }
       }
       return $map;
     }
   } catch (Exception $ex) {
     phlog($ex);
     // Just drop down and go with the non-git approach.
   }
 
   $files = id(new FileFinder($dir))
     ->withType('f')
     ->excludePath('*/.git/*')
     ->excludePath('*/.svn/*')
     ->find();
   foreach ($files as $file) {
     if (!strncmp($file, './', 2)) {
       $file = substr($file, 2);
     }
     $data = Filesystem::readFile($dir.'/'.$file);
     $len  = strlen($data);
     $hash = sha1("blob {$len}\0{$data}");
 
     $type = 'file';
     if (is_link($dir.'/'.$file)) {
       $type = 'link';
     } else if (is_executable($dir.'/'.$file)) {
       $type = 'exec';
     }
 
     $map[$file] = array(
       'hash' => $hash,
       'type' => $type,
     );
   }
 
   return $map;
 }
diff --git a/scripts/test/http.php b/scripts/test/http.php
index f6a5840..636c67e 100755
--- a/scripts/test/http.php
+++ b/scripts/test/http.php
@@ -1,45 +1,45 @@
 #!/usr/bin/env php
 <?php
 
 require_once dirname(__FILE__).'/../__init_script__.php';
 
 $args = new PhutilArgumentParser($argv);
 $args->parseStandardArguments();
 $args->parse(
   array(
     array(
       'name'     => 'attach',
       'param'    => 'file',
       'help'     => pht('Attach a file to the request.'),
     ),
     array(
       'name'     => 'url',
       'wildcard' => true,
     ),
   ));
 
 $uri = $args->getArg('url');
 if (count($uri) !== 1) {
   throw new PhutilArgumentUsageException(
-    'Specify exactly one URL to retrieve.');
+    pht('Specify exactly one URL to retrieve.'));
 }
 $uri = head($uri);
 
 $method  = 'GET';
 $data    = '';
 $timeout = 30;
 
 $future = id(new HTTPSFuture($uri, $data))
   ->setMethod($method)
   ->setTimeout($timeout);
 
 $attach_file = $args->getArg('attach');
 if ($attach_file !== null) {
   $future->attachFileData(
     'file',
     Filesystem::readFile($attach_file),
     basename($attach_file),
     Filesystem::getMimeType($attach_file));
 }
 
 print_r($future->resolve());
diff --git a/scripts/test/interactive_editor.php b/scripts/test/interactive_editor.php
index b2ba04b..727e06f 100755
--- a/scripts/test/interactive_editor.php
+++ b/scripts/test/interactive_editor.php
@@ -1,61 +1,59 @@
 #!/usr/bin/env php
 <?php
 
 require_once dirname(__FILE__).'/../__init_script__.php';
 
 $args = new PhutilArgumentParser($argv);
-$args->setTagline('test InteractiveEditor class');
+$args->setTagline(pht('test %s class', 'InteractiveEditor'));
 $args->setSynopsis(<<<EOHELP
 **interactive_editor.php** [__options__]
     Edit some content via the InteractiveEditor class. This script
     makes it easier to test changes to InteractiveEditor, which is
     difficult to unit test.
 EOHELP
   );
 $args->parseStandardArguments();
 $args->parse(
   array(
     array(
       'name'  => 'fallback',
       'param' => 'editor',
-      'help'  => 'Set the fallback editor.',
+      'help'  => pht('Set the fallback editor.'),
     ),
     array(
       'name'  => 'line',
       'short' => 'l',
       'param' => 'number',
-      'help'  => 'Open at line number __number__.',
+      'help'  => pht('Open at line number __number__.'),
     ),
     array(
       'name'  => 'name',
       'param' => 'filename',
-      'help'  => 'Set edited file name.',
+      'help'  => pht('Set edited file name.'),
     ),
   ));
 
 if ($args->getArg('help')) {
   $args->printHelpAndExit();
 }
 
 $editor = new PhutilInteractiveEditor(
-  "The wizard quickly\n".
-  "jinxed the gnomes\n".
-  "before they vaporized.");
+  pht("The wizard quickly\njinxed the gnomes\nbefore they vaporized."));
 
 $name = $args->getArg('name');
 if ($name) {
   $editor->setName($name);
 }
 
 $line = $args->getArg('line');
 if ($line) {
   $editor->setLineOffset($line);
 }
 
 $fallback = $args->getArg('fallback');
 if ($fallback) {
   $editor->setFallbackEditor($fallback);
 }
 
 $result = $editor->editInteractively();
-echo "Edited Text:\n{$result}\n";
+echo pht('Edited Text:')."\n{$result}\n";
diff --git a/scripts/test/lipsum.php b/scripts/test/lipsum.php
index 1ad890c..19361cd 100755
--- a/scripts/test/lipsum.php
+++ b/scripts/test/lipsum.php
@@ -1,42 +1,46 @@
 #!/usr/bin/env php
 <?php
 
 require_once dirname(__FILE__).'/../__init_script__.php';
 
 $args = new PhutilArgumentParser($argv);
-$args->setTagline('test context-free grammars');
+$args->setTagline(pht('test context-free grammars'));
 $args->setSynopsis(<<<EOHELP
 **lipsum.php** __class__
     Generate output from a named context-free grammar.
 EOHELP
   );
 $args->parseStandardArguments();
 $args->parse(
   array(
     array(
       'name'     => 'class',
       'wildcard' => true,
     ),
   ));
 
 $class = $args->getArg('class');
 if (count($class) !== 1) {
   $args->printHelpAndExit();
 }
 $class = reset($class);
 
 $symbols = id(new PhutilSymbolLoader())
   ->setAncestorClass('PhutilContextFreeGrammar')
   ->setConcreteOnly(true)
   ->selectAndLoadSymbols();
 $symbols = ipull($symbols, 'name', 'name');
 
 if (empty($symbols[$class])) {
   $available = implode(', ', array_keys($symbols));
   throw new PhutilArgumentUsageException(
-    "Class '{$class}' is not a defined, concrete subclass of ".
-    "PhutilContextFreeGrammar. Available classes are: {$available}");
+    pht(
+      "Class '%s' is not a defined, concrete subclass of %s. ".
+      "Available classes are: %s",
+      $class,
+      'PhutilContextFreeGrammar',
+      $available));
 }
 
 $object = newv($class, array());
 echo $object->generate()."\n";
diff --git a/scripts/test/mime.php b/scripts/test/mime.php
index 8c95b37..ac0ab53 100755
--- a/scripts/test/mime.php
+++ b/scripts/test/mime.php
@@ -1,39 +1,40 @@
 #!/usr/bin/env php
 <?php
 
 require_once dirname(__FILE__).'/../__init_script__.php';
 
 $args = new PhutilArgumentParser($argv);
-$args->setTagline('test Filesystem::getMimeType()');
+$args->setTagline(pht('test %s', 'Filesystem::getMimeType()'));
 $args->setSynopsis(<<<EOHELP
 **mime.php** [__options__] __file__
     Determine the mime type of a file.
 EOHELP
   );
 $args->parseStandardArguments();
 $args->parse(
   array(
     array(
       'name'  => 'default',
       'param' => 'mimetype',
-      'help'  => 'Use __mimetype__ as default instead of builtin default.',
+      'help'  => pht(
+        'Use __mimetype__ as default instead of built-in default.'),
     ),
     array(
       'name'     => 'file',
       'wildcard' => true,
     ),
   ));
 
 $file = $args->getArg('file');
 if (count($file) !== 1) {
   $args->printHelpAndExit();
 }
 
 $file = reset($file);
 
 $default = $args->getArg('default');
 if ($default) {
   echo Filesystem::getMimeType($file, $default)."\n";
 } else {
   echo Filesystem::getMimeType($file)."\n";
 }
diff --git a/scripts/test/progress_bar.php b/scripts/test/progress_bar.php
index 55b7ddd..97d73aa 100755
--- a/scripts/test/progress_bar.php
+++ b/scripts/test/progress_bar.php
@@ -1,54 +1,58 @@
 #!/usr/bin/env php
 <?php
 
 require_once dirname(__FILE__).'/../__init_script__.php';
 
 $args = new PhutilArgumentParser($argv);
 $args->parseStandardArguments();
 
-echo "PROGRESS BAR TEST SCRIPT\n\n";
-echo "This script is a test script for `PhutilConsoleProgressBar`. It will ".
-     "draw some progress bars, and generally allow you to test bar behaviors ".
-     "and changes.\n\n";
-
-echo "GENERAL NOTES\n\n";
-echo "  - When run as `php -f progress_bar.php 2>&1 | more`, no progress bars ".
-     "should be shown (stderr is not a tty).\n";
-echo "  - When run in a narrow terminal, the bar should resize automatically ".
-     "to fit the terminal.\n";
-echo "  - When run with `--trace`, the bar should not be drawn.\n";
+echo pht(
+  "PROGRESS BAR TEST SCRIPT\n\n".
+  "This script is a test script for `%s`. It will draw some progress bars, ".
+  "and generally allow you to test bar behaviors and changes.",
+  'PhutilConsoleProgressBar');
+echo "\n\n";
+echo pht(
+  "GENERAL NOTES\n\n".
+  "  - When run as `%s`, no progress bars should be shown ".
+  "(stderr is not a tty).\n".
+  "  - When run in a narrow terminal, the bar should resize automatically ".
+  "to fit the terminal.\n".
+  "  - When run with `%s`, the bar should not be drawn.\n",
+  'php -f progress_bar.php 2>&1 | more',
+  '--trace');
 echo "\n\n";
 
-echo "STANDARD PROGRESS BAR\n";
+echo pht('STANDARD PROGRESS BAR')."\n";
 $n = 80;
 $bar = id(new PhutilConsoleProgressBar())
   ->setTotal($n);
 for ($ii = 0; $ii < $n; $ii++) {
   $bar->update(1);
   usleep(10000);
 }
 $bar->done();
 
-echo "\n";
-echo "INTERRUPTED PROGRESS BAR\n";
-echo "This bar will be interrupted by an exception.\n";
-echo "It should clean itself up.\n";
+echo "\n".pht(
+  "INTERRUPTED PROGRESS BAR\n".
+  "This bar will be interrupted by an exception.\n".
+  "It should clean itself up.")."\n";
 try {
   run_interrupt_bar();
 } catch (Exception $ex) {
-  echo "Caught exception!\n";
+  echo pht('Caught exception!')."\n";
 }
 
 
 function run_interrupt_bar() {
   $bar = id(new PhutilConsoleProgressBar())
     ->setTotal(100);
 
   for ($ii = 0; $ii < 100; $ii++) {
     if ($ii === 20) {
-      throw new Exception('Boo!');
+      throw new Exception(pht('Boo!'));
     }
     $bar->update(1);
     usleep(10000);
   }
 }
diff --git a/scripts/test/prompt.php b/scripts/test/prompt.php
index 7c08b9a..65ab2a4 100755
--- a/scripts/test/prompt.php
+++ b/scripts/test/prompt.php
@@ -1,35 +1,35 @@
 #!/usr/bin/env php
 <?php
 
 require_once dirname(__FILE__).'/../__init_script__.php';
 
 $args = new PhutilArgumentParser($argv);
-$args->setTagline('test console prompting');
+$args->setTagline(pht('test console prompting'));
 $args->setSynopsis(<<<EOHELP
 **prompt.php** __options__
     Test console prompting.
 EOHELP
 );
 $args->parseStandardArguments();
 $args->parse(
   array(
     array(
       'name'    => 'history',
       'param'   => 'file',
       'default' => '',
-      'help'    => 'Use specified history __file__.',
+      'help'    => pht('Use specified history __file__.'),
     ),
     array(
       'name'    => 'prompt',
       'param'   => 'text',
       'default' => 'Enter some text:',
-      'help'    => 'Change the prompt text to __text__.',
+      'help'    => pht('Change the prompt text to __text__.'),
     ),
   ));
 
 $result = phutil_console_prompt(
   $args->getArg('prompt'),
   $args->getArg('history'));
 
 $console = PhutilConsole::getConsole();
-$console->writeOut("Input is: %s\n", $result);
+$console->writeOut("%s\n", pht('Input is: %s', $result));
diff --git a/scripts/test/service_profiler.php b/scripts/test/service_profiler.php
index 8e93986..584622e 100755
--- a/scripts/test/service_profiler.php
+++ b/scripts/test/service_profiler.php
@@ -1,15 +1,15 @@
 #!/usr/bin/env php
 <?php
 
 require_once dirname(__FILE__).'/../__init_script__.php';
 
 // Simple test script for PhutilServiceProfiler.
 
 PhutilServiceProfiler::installEchoListener();
 
 execx('ls %s', '/tmp');
 exec_manual('sleep %d', 1);
 phutil_passthru('cat');
 
-echo "\n\nSERVICE CALL LOG\n";
+echo "\n\n".pht('SERVICE CALL LOG')."\n";
 var_dump(PhutilServiceProfiler::getInstance()->getServiceCallLog());
diff --git a/scripts/utils/directory_fixture.php b/scripts/utils/directory_fixture.php
index dd25258..17547cb 100755
--- a/scripts/utils/directory_fixture.php
+++ b/scripts/utils/directory_fixture.php
@@ -1,90 +1,94 @@
 #!/usr/bin/env php
 <?php
 
 require_once dirname(__FILE__).'/../__init_script__.php';
 
 $args = new PhutilArgumentParser($argv);
-$args->setTagline('edit directory fixtures');
+$args->setTagline(pht('edit directory fixtures'));
 $args->setSynopsis(<<<EOHELP
 **directory_fixture.php** __file__ --create
   Create a new directory fixture.
 
 **directory_fixture.php** __file__
   Edit an existing directory fixture.
 
 EOHELP
 );
 $args->parseStandardArguments();
 $args->parse(array(
   array(
     'name'      => 'create',
     'help'      => pht('Create a new fixture.'),
   ),
   array(
     'name'      => 'read-only',
     'help'      => pht('Do not save changes made to the fixture.'),
   ),
   array(
     'name'      => 'files',
     'wildcard'  => true,
   ),
 ));
 
 $is_create    = $args->getArg('create');
 $is_read_only = $args->getArg('read-only');
 $console = PhutilConsole::getConsole();
 
 $files = $args->getArg('files');
 if (count($files) !== 1) {
   throw new PhutilArgumentUsageException(
     pht('Specify exactly one file to create or edit.'));
 }
 $file = head($files);
 
 if ($is_create) {
   if (Filesystem::pathExists($file)) {
     throw new PhutilArgumentUsageException(
-      pht('File "%s" already exists, so you can not --create it.', $file));
+      pht(
+        'File "%s" already exists, so you can not %s it.',
+        $file,
+        '--create'));
   }
   $fixture = PhutilDirectoryFixture::newEmptyFixture();
 } else {
   if (!Filesystem::pathExists($file)) {
     throw new PhutilArgumentUsageException(
       pht(
-        'File "%s" does not exist! Use --create to create a new fixture.',
-        $file));
+        'File "%s" does not exist! Use %s to create a new fixture.',
+        $file,
+        '--create'));
   }
   $fixture = PhutilDirectoryFixture::newFromArchive($file);
 }
 
 $console->writeOut(
   "%s\n\n",
   pht('Spawning an interactive shell. Working directory is:'));
 $console->writeOut(
   "    %s\n\n",
   $fixture->getPath());
 if ($is_read_only) {
   $console->writeOut(
     "%s\n",
     pht('Exit the shell when done (this fixture is read-only).'));
 } else {
   $console->writeOut(
     "%s\n",
     pht('Exit the shell after making changes.'));
 }
 
 $err = phutil_passthru('cd %s && sh', $fixture->getPath());
 if ($err) {
   $console->writeOut(
     "%s\n",
     pht('Shell exited with error %d, discarding changes.', $err));
   exit($err);
 } else if ($is_read_only) {
   $console->writeOut(
     "%s\n",
     pht('Invoked in read-only mode, discarding changes.'));
 } else {
   $console->writeOut("%s\n", pht('Updating archive...'));
   $fixture->saveToArchive($file);
   $console->writeOut("%s\n", pht('Done.'));
 }
diff --git a/scripts/utils/lock.php b/scripts/utils/lock.php
index 7449282..4714c03 100755
--- a/scripts/utils/lock.php
+++ b/scripts/utils/lock.php
@@ -1,77 +1,82 @@
 #!/usr/bin/env php
 <?php
 
 require_once dirname(__FILE__).'/../__init_script__.php';
 
 $args = new PhutilArgumentParser($argv);
-$args->setTagline('acquire and hold a lockfile');
+$args->setTagline(pht('acquire and hold a lockfile'));
 $args->setSynopsis(<<<EOHELP
 **lock.php** __file__ [__options__]
     Acquire a lockfile and hold it until told to unlock it.
 
 EOHELP
 );
 
 $args->parseStandardArguments();
 $args->parse(array(
   array(
     'name'      => 'test',
-    'help'      => 'Instead of holding the lock, release it and exit.',
+    'help'      => pht('Instead of holding the lock, release it and exit.'),
   ),
   array(
     'name'      => 'hold',
-    'help'      => 'Hold indefinitely without prompting.',
+    'help'      => pht('Hold indefinitely without prompting.'),
   ),
   array(
     'name'      => 'wait',
     'param'     => 'n',
-    'help'      => 'Block for up to __n__ seconds waiting for the lock.',
+    'help'      => pht('Block for up to __n__ seconds waiting for the lock.'),
     'default'   => 0,
   ),
   array(
     'name'      => 'file',
     'wildcard'  => true,
   ),
 ));
 
 
 $file = $args->getArg('file');
 if (count($file) !== 1) {
   $args->printHelpAndExit();
 }
 $file = head($file);
 
 $console = PhutilConsole::getConsole();
-$console->writeOut("This process has PID %d. Acquiring lock...\n", getmypid());
+$console->writeOut(
+  "%s\n",
+  pht('This process has PID %d. Acquiring lock...', getmypid()));
 
 $lock = PhutilFileLock::newForPath($file);
 
 try {
   $lock->lock($args->getArg('wait'));
 } catch (PhutilFileLockException $ex) {
-  $console->writeOut("**UNABLE TO ACQUIRE LOCK:** Lock is already held.\n");
+  $console->writeOut(
+    "**%s** %s\n",
+    pht('UNABLE TO ACQUIRE LOCK:'),
+    pht('Lock is already held.'));
   exit(1);
 }
 
 // NOTE: This string is magic, the unit tests look for it.
-$console->writeOut("LOCK ACQUIRED\n");
+$console->writeOut("%s\n", pht('LOCK ACQUIRED'));
 if ($args->getArg('test')) {
   $lock->unlock();
   exit(0);
 }
 
 if ($args->getArg('hold')) {
   while (true) {
     sleep(1);
   }
 }
 
-while (!$console->confirm('Release lock?')) {
+while (!$console->confirm(pht('Release lock?'))) {
   // Keep asking until they say yes.
 }
 
-$console->writeOut("Unlocking...\n");
+$console->writeOut("%s\n", pht('Unlocking...'));
 $lock->unlock();
 
-$console->writeOut("Done.\n");
+$console->writeOut("%s\n", pht('Done.'));
 exit(0);
diff --git a/scripts/utils/utf8.php b/scripts/utils/utf8.php
index 1463712..85eccdf 100755
--- a/scripts/utils/utf8.php
+++ b/scripts/utils/utf8.php
@@ -1,169 +1,170 @@
 #!/usr/bin/env php
 <?php
 
 require_once dirname(dirname(__FILE__)).'/__init_script__.php';
 
 $args = new PhutilArgumentParser($argv);
-$args->setTagline('utf8 charset test script');
+$args->setTagline(pht('utf8 charset test script'));
 $args->setSynopsis(<<<EOHELP
 **utf8.php** [-C n] __file__ ...
     Show regions in files which are not valid UTF-8. With "-C n",
     show __n__ lines of context instead of the default of 3. Use
     "-" to read stdin.
 
 **utf8.php** --test __file__ ...
     Test for files which are not valid UTF-8. For example, this
     will find all ".php" files under the working directory which
     aren't valid UTF-8:
 
         find . -type f -name '*.php' | xargs -n256 ./utf8.php -t
 
     If the script exits with no output, all input files were
     valid UTF-8.
 EOHELP
 );
 
 $args->parseStandardArguments();
 $args->parse(array(
   array(
     'name'      => 'context',
     'short'     => 'C',
     'param'     => 'lines',
     'default'   => 3,
-    'help'      => 'Show __lines__ lines of context instead of the default 3.',
+    'help'      => pht(
+      'Show __lines__ lines of context instead of the default 3.'),
     'conflicts' => array(
-      'test' => 'with --test, context is not shown.',
+      'test' => pht('with %s, context is not shown.', '--test'),
     ),
   ),
   array(
     'name'      => 'test',
     'short'     => 't',
-    'help'      => 'Print file names containing invalid UTF-8 to stdout.',
+    'help'      => pht('Print file names containing invalid UTF-8 to stdout.'),
   ),
   array(
     'name'      => 'files',
     'wildcard'  => true,
   ),
 ));
 
 
 $is_test = $args->getArg('test');
 $context = $args->getArg('context');
 $files   = $args->getArg('files');
 
 if (empty($files)) {
   $args->printHelpAndExit();
 }
 
 if ($is_test) {
   $err = test($files);
 } else {
   $err = show($files, $context);
 }
 exit($err);
 
 
 function read($file) {
   if ($file === '-') {
     return file_get_contents('php://stdin');
   } else {
     return Filesystem::readFile($file);
   }
 }
 
 function name($file) {
   if ($file === '-') {
     return 'stdin';
   } else {
     return $file;
   }
 }
 
 function test(array $files) {
   foreach ($files as $file) {
     $data = read($file);
     if (!phutil_is_utf8($data)) {
       echo name($file)."\n";
     }
   }
 
   return 0;
 }
 
 function show(array $files, $context) {
   foreach ($files as $file) {
     $data = read($file);
     $ok = phutil_is_utf8($data);
     if ($ok) {
-      echo 'OKAY';
+      echo pht('OKAY');
     } else {
-      echo 'FAIL';
+      echo pht('FAIL');
     }
-    echo "  ".name($file)."\n";
+    echo '  '.name($file)."\n";
 
     if (!$ok) {
       $lines = explode("\n", $data);
       $len = count($lines);
       $map = array();
       $bad = array();
       foreach ($lines as $n => $line) {
         if (phutil_is_utf8($line)) {
           continue;
         }
         $bad[$n] = true;
         for ($jj = max(0, $n - $context);
              $jj < min($len, $n + 1 + $context);
              $jj++) {
           $map[$jj] = true;
         }
       }
 
       $width = strlen(max(array_keys($map)));
 
       // Set $last such that we print a newline on the first iteration through
       // the loop.
       $last = -2;
       foreach ($map as $idx => $ignored) {
         if ($idx !== $last + 1) {
           echo "\n";
         }
         $last = $idx;
 
         $line = $lines[$idx];
         if (!empty($bad[$idx])) {
           $line = show_problems($line);
         }
 
         printf("  % {$width}d  %s\n", $idx + 1, $line);
       }
       echo "\n";
     }
   }
 
   return 0;
 }
 
 function show_problems($line) {
   $regex =
     "/^(".
       "[\x01-\x7F]+".
     "|([\xC2-\xDF][\x80-\xBF])".
     "|([\xE0-\xEF][\x80-\xBF][\x80-\xBF])".
     "|([\xF0-\xF4][\x80-\xBF][\x80-\xBF][\x80-\xBF]))/";
 
   $out = '';
   while (strlen($line)) {
     $match = null;
     if (preg_match($regex, $line, $match)) {
       $out .= $match[1];
       $line = substr($line, strlen($match[1]));
     } else {
       $chr = sprintf('<0x%0X>', ord($line[0]));
       $chr = phutil_console_format('##%s##', $chr);
       $out .= $chr;
       $line = substr($line, 1);
     }
   }
 
   return $out;
 }
diff --git a/src/auth/PhutilOAuth1AuthAdapter.php b/src/auth/PhutilOAuth1AuthAdapter.php
index 77d465a..f2d0bba 100644
--- a/src/auth/PhutilOAuth1AuthAdapter.php
+++ b/src/auth/PhutilOAuth1AuthAdapter.php
@@ -1,202 +1,205 @@
 <?php
 
 /**
  * Abstract adapter for OAuth1 providers.
  */
 abstract class PhutilOAuth1AuthAdapter extends PhutilAuthAdapter {
 
   private $consumerKey;
   private $consumerSecret;
   private $token;
   private $tokenSecret;
   private $verifier;
   private $handshakeData;
   private $callbackURI;
   private $privateKey;
 
   public function setPrivateKey(PhutilOpaqueEnvelope $private_key) {
     $this->privateKey = $private_key;
     return $this;
   }
 
   public function getPrivateKey() {
     return $this->privateKey;
   }
 
   public function setCallbackURI($callback_uri) {
     $this->callbackURI = $callback_uri;
     return $this;
   }
 
   public function getCallbackURI() {
     return $this->callbackURI;
   }
 
   public function setVerifier($verifier) {
     $this->verifier = $verifier;
     return $this;
   }
 
   public function getVerifier() {
     return $this->verifier;
   }
 
   public function setConsumerSecret(PhutilOpaqueEnvelope $consumer_secret) {
     $this->consumerSecret = $consumer_secret;
     return $this;
   }
 
   public function getConsumerSecret() {
     return $this->consumerSecret;
   }
 
   public function setConsumerKey($consumer_key) {
     $this->consumerKey = $consumer_key;
     return $this;
   }
 
   public function getConsumerKey() {
     return $this->consumerKey;
   }
 
   public function setTokenSecret($token_secret) {
     $this->tokenSecret = $token_secret;
     return $this;
   }
 
   public function getTokenSecret() {
     return $this->tokenSecret;
   }
 
   public function setToken($token) {
     $this->token = $token;
     return $this;
   }
 
   public function getToken() {
     return $this->token;
   }
 
   protected function getHandshakeData() {
     if ($this->handshakeData === null) {
       $this->finishOAuthHandshake();
     }
     return $this->handshakeData;
   }
 
   abstract protected function getRequestTokenURI();
   abstract protected function getAuthorizeTokenURI();
   abstract protected function getValidateTokenURI();
 
   protected function getSignatureMethod() {
     return 'HMAC-SHA1';
   }
 
   protected function newOAuth1Future($uri, $data = array()) {
     $future = id(new PhutilOAuth1Future($uri, $data))
       ->setMethod('POST')
       ->setSignatureMethod($this->getSignatureMethod());
 
     $consumer_key = $this->getConsumerKey();
     if (strlen($consumer_key)) {
       $future->setConsumerKey($consumer_key);
     } else {
-      throw new Exception('setConsumerKey() is required!');
+      throw new Exception(
+        pht(
+          '%s is required!',
+          'setConsumerKey()'));
     }
 
     $consumer_secret = $this->getConsumerSecret();
     if ($consumer_secret) {
       $future->setConsumerSecret($consumer_secret);
     }
 
     if (strlen($this->getToken())) {
       $future->setToken($this->getToken());
     }
 
     if (strlen($this->getTokenSecret())) {
       $future->setTokenSecret($this->getTokenSecret());
     }
 
     if ($this->getPrivateKey()) {
       $future->setPrivateKey($this->getPrivateKey());
     }
 
     return $future;
   }
 
   public function getClientRedirectURI() {
     $request_token_uri = $this->getRequestTokenURI();
 
     $future = $this->newOAuth1Future($request_token_uri);
     if (strlen($this->getCallbackURI())) {
       $future->setCallbackURI($this->getCallbackURI());
     }
 
     list($body) = $future->resolvex();
     $data = id(new PhutilQueryStringParser())->parseQueryString($body);
 
     // NOTE: Per the spec, this value MUST be the string 'true'.
     $confirmed = idx($data, 'oauth_callback_confirmed');
     if ($confirmed !== 'true') {
       throw new Exception(
         pht("Expected '%s' to be '%s'!", 'oauth_callback_confirmed', 'true'));
     }
 
     $this->readTokenAndTokenSecret($data);
 
     $authorize_token_uri = new PhutilURI($this->getAuthorizeTokenURI());
     $authorize_token_uri->setQueryParam('oauth_token', $this->getToken());
 
     return (string)$authorize_token_uri;
   }
 
   protected function finishOAuthHandshake() {
     $this->willFinishOAuthHandshake();
 
     if (!$this->getToken()) {
       throw new Exception(pht('Expected token to finish OAuth handshake!'));
     }
     if (!$this->getVerifier()) {
       throw new Exception(pht('Expected verifier to finish OAuth handshake!'));
     }
 
     $validate_uri = $this->getValidateTokenURI();
     $params = array(
       'oauth_verifier' => $this->getVerifier(),
     );
 
     list($body) = $this->newOAuth1Future($validate_uri, $params)->resolvex();
     $data = id(new PhutilQueryStringParser())->parseQueryString($body);
 
     $this->readTokenAndTokenSecret($data);
 
     $this->handshakeData = $data;
   }
 
   private function readTokenAndTokenSecret(array $data) {
     $token = idx($data, 'oauth_token');
     if (!$token) {
       throw new Exception(pht("Expected '%s' in response!", 'oauth_token'));
     }
 
     $token_secret = idx($data, 'oauth_token_secret');
     if (!$token_secret) {
       throw new Exception(
         pht("Expected '%s' in response!", 'oauth_token_secret'));
     }
 
     $this->setToken($token);
     $this->setTokenSecret($token_secret);
 
     return $this;
   }
 
   /**
    * Hook that allows subclasses to take actions before the OAuth handshake
    * is completed.
    */
   protected function willFinishOAuthHandshake() {
     return;
   }
 
 }
diff --git a/src/console/PhutilConsoleServer.php b/src/console/PhutilConsoleServer.php
index d8b14bd..a1226e6 100644
--- a/src/console/PhutilConsoleServer.php
+++ b/src/console/PhutilConsoleServer.php
@@ -1,156 +1,158 @@
 <?php
 
 final class PhutilConsoleServer {
 
   private $clients = array();
   private $handler;
   private $enableLog;
 
   public function handleMessage(PhutilConsoleMessage $message) {
     $data = $message->getData();
     $type = $message->getType();
 
     switch ($type) {
 
       case PhutilConsoleMessage::TYPE_CONFIRM:
         $ok = phutil_console_confirm($data['prompt'], !$data['default']);
         return $this->buildMessage(
           PhutilConsoleMessage::TYPE_INPUT,
           $ok);
 
       case PhutilConsoleMessage::TYPE_PROMPT:
         $response = phutil_console_prompt(
           $data['prompt'],
           idx($data, 'history'));
         return $this->buildMessage(
           PhutilConsoleMessage::TYPE_INPUT,
           $response);
 
       case PhutilConsoleMessage::TYPE_OUT:
         $this->writeText(STDOUT, $data);
         return null;
 
       case PhutilConsoleMessage::TYPE_ERR:
         $this->writeText(STDERR, $data);
         return null;
 
       case PhutilConsoleMessage::TYPE_LOG:
         if ($this->enableLog) {
           $this->writeText(STDERR, $data);
         }
         return null;
 
       case PhutilConsoleMessage::TYPE_ENABLED:
         switch ($data['which']) {
           case PhutilConsoleMessage::TYPE_LOG:
             $enabled = $this->enableLog;
             break;
           default:
             $enabled = true;
             break;
         }
         return $this->buildMessage(
           PhutilConsoleMessage::TYPE_IS_ENABLED,
           $enabled);
 
       case PhutilConsoleMessage::TYPE_TTY:
       case PhutilConsoleMessage::TYPE_COLS:
         switch ($data['which']) {
           case PhutilConsoleMessage::TYPE_OUT:
             $which = STDOUT;
             break;
           case PhutilConsoleMessage::TYPE_ERR:
             $which = STDERR;
             break;
         }
         switch ($type) {
           case PhutilConsoleMessage::TYPE_TTY:
             if (function_exists('posix_isatty')) {
               $is_a_tty = posix_isatty($which);
             } else {
               $is_a_tty = null;
             }
             return $this->buildMessage(
               PhutilConsoleMessage::TYPE_IS_TTY,
               $is_a_tty);
           case PhutilConsoleMessage::TYPE_COLS:
             // TODO: This is an approximation which might not be perfectly
             // accurate.
             $width = phutil_console_get_terminal_width();
             return $this->buildMessage(
               PhutilConsoleMessage::TYPE_COL_WIDTH,
               $width);
         }
         break;
 
       default:
         if ($this->handler) {
           return call_user_func($this->handler, $message);
         } else {
           throw new Exception(
-            "Received unknown console message of type '{$type}'.");
+            pht(
+              "Received unknown console message of type '%s'.",
+              $type));
         }
 
     }
   }
 
   /**
    * Set handler called for unknown messages.
    *
    * @param callable Signature: (PhutilConsoleMessage $message).
    */
   public function setHandler($callback) {
     $this->handler = $callback;
     return $this;
   }
 
   private function buildMessage($type, $data) {
     $response = new PhutilConsoleMessage();
     $response->setType($type);
     $response->setData($data);
     return $response;
   }
 
   public function addExecFutureClient(ExecFuture $future) {
     $io_channel = new PhutilExecChannel($future);
     $protocol_channel = new PhutilPHPObjectProtocolChannel($io_channel);
     $server_channel = new PhutilConsoleServerChannel($protocol_channel);
     $io_channel->setStderrHandler(array($server_channel, 'didReceiveStderr'));
     return $this->addClient($server_channel);
   }
 
   public function addClient(PhutilConsoleServerChannel $channel) {
     $this->clients[] = $channel;
     return $this;
   }
 
   public function setEnableLog($enable) {
     $this->enableLog = $enable;
     return $this;
   }
 
   public function run() {
     while ($this->clients) {
       PhutilChannel::waitForAny($this->clients);
       foreach ($this->clients as $key => $client) {
         if (!$client->update()) {
           // If the client has exited, remove it from the list of clients.
           // We still need to process any remaining buffered I/O.
           unset($this->clients[$key]);
         }
         while ($message = $client->read()) {
           $response = $this->handleMessage($message);
           if ($response) {
             $client->write($response);
           }
         }
       }
     }
   }
 
   private function writeText($where, array $argv) {
     $text = call_user_func_array('phutil_console_format', $argv);
     fprintf($where, '%s', $text);
   }
 
 }
diff --git a/src/daemon/PhutilDaemonOverseer.php b/src/daemon/PhutilDaemonOverseer.php
index 33fe554..5b276aa 100644
--- a/src/daemon/PhutilDaemonOverseer.php
+++ b/src/daemon/PhutilDaemonOverseer.php
@@ -1,485 +1,485 @@
 <?php
 
 /**
  * Oversees a daemon and restarts it if it fails.
  */
 final class PhutilDaemonOverseer {
 
   private $argv;
   private $moreArgs;
   private $inAbruptShutdown;
   private $inGracefulShutdown;
   private static $instance;
 
   private $config;
   private $daemons = array();
   private $traceMode;
   private $traceMemory;
   private $daemonize;
   private $piddir;
   private $log;
   private $libraries = array();
   private $verbose;
   private $err = 0;
   private $lastPidfile;
   private $startEpoch;
   private $autoscale = array();
 
   public function __construct(array $argv) {
     PhutilServiceProfiler::getInstance()->enableDiscardMode();
 
     $args = new PhutilArgumentParser($argv);
-    $args->setTagline('daemon overseer');
+    $args->setTagline(pht('daemon overseer'));
     $args->setSynopsis(<<<EOHELP
 **launch_daemon.php** [__options__] __daemon__
     Launch and oversee an instance of __daemon__.
 EOHELP
       );
     $args->parseStandardArguments();
     $args->parse(
       array(
         array(
           'name' => 'trace-memory',
           'help' => pht('Enable debug memory tracing.'),
         ),
         array(
           'name'  => 'verbose',
           'help'  => pht('Enable verbose activity logging.'),
         ),
         array(
           'name' => 'label',
           'short' => 'l',
           'param' => 'label',
           'help' => pht(
             'Optional process label. Makes "%s" nicer, no behavioral effects.',
             'ps'),
         ),
       ));
     $argv = array();
 
     if ($args->getArg('trace')) {
       $this->traceMode = true;
       $argv[] = '--trace';
     }
 
     if ($args->getArg('trace-memory')) {
       $this->traceMode = true;
       $this->traceMemory = true;
       $argv[] = '--trace-memory';
     }
     $verbose = $args->getArg('verbose');
     if ($verbose) {
       $this->verbose = true;
       $argv[] = '--verbose';
     }
 
     $label = $args->getArg('label');
     if ($label) {
       $argv[] = '-l';
       $argv[] = $label;
     }
 
     $this->argv = $argv;
 
     if (function_exists('posix_isatty') && posix_isatty(STDIN)) {
       fprintf(STDERR, pht('Reading daemon configuration from stdin...')."\n");
     }
     $config = @file_get_contents('php://stdin');
     $config = id(new PhutilJSONParser())->parse($config);
 
     $this->libraries = idx($config, 'load');
     $this->log = idx($config, 'log');
     $this->daemonize = idx($config, 'daemonize');
     $this->piddir = idx($config, 'piddir');
 
     $this->config = $config;
 
     if (self::$instance) {
       throw new Exception(
         pht('You may not instantiate more than one Overseer per process.'));
     }
 
     self::$instance = $this;
 
     $this->startEpoch = time();
 
     // Check this before we daemonize, since if it's an issue the child will
     // exit immediately.
     if ($this->piddir) {
       $dir = $this->piddir;
       try {
         Filesystem::assertWritable($dir);
       } catch (Exception $ex) {
         throw new Exception(
           pht(
             "Specified daemon PID directory ('%s') does not exist or is ".
             "not writable by the daemon user!",
             $dir));
       }
     }
 
     if (!idx($config, 'daemons')) {
       throw new PhutilArgumentUsageException(
         pht('You must specify at least one daemon to start!'));
     }
 
     if ($this->log) {
       // NOTE: Now that we're committed to daemonizing, redirect the error
       // log if we have a `--log` parameter. Do this at the last moment
       // so as many setup issues as possible are surfaced.
       ini_set('error_log', $this->log);
     }
 
     if ($this->daemonize) {
       // We need to get rid of these or the daemon will hang when we TERM it
       // waiting for something to read the buffers. TODO: Learn how unix works.
       fclose(STDOUT);
       fclose(STDERR);
       ob_start();
 
       $pid = pcntl_fork();
       if ($pid === -1) {
         throw new Exception(pht('Unable to fork!'));
       } else if ($pid) {
         exit(0);
       }
     }
 
     declare(ticks = 1);
     pcntl_signal(SIGUSR2, array($this, 'didReceiveNotifySignal'));
 
     pcntl_signal(SIGHUP,  array($this, 'didReceiveReloadSignal'));
     pcntl_signal(SIGINT,  array($this, 'didReceiveGracefulSignal'));
     pcntl_signal(SIGTERM, array($this, 'didReceiveTerminalSignal'));
   }
 
   public function addLibrary($library) {
     $this->libraries[] = $library;
     return $this;
   }
 
   public function run() {
     $this->daemons = array();
 
     foreach ($this->config['daemons'] as $config) {
       $config += array(
         'argv' => array(),
         'autoscale' => array(),
       );
 
       $daemon = new PhutilDaemonHandle(
         $this,
         $config['class'],
         $this->argv,
         array(
           'log' => $this->log,
           'argv' => $config['argv'],
           'load' => $this->libraries,
           'autoscale' => $config['autoscale'],
         ));
 
       $daemon->setSilent((!$this->traceMode && !$this->verbose));
       $daemon->setTraceMemory($this->traceMemory);
 
       $this->addDaemon($daemon, $config);
     }
 
     while (true) {
       $futures = array();
       foreach ($this->getDaemonHandles() as $daemon) {
         $daemon->update();
         if ($daemon->isRunning()) {
           $futures[] = $daemon->getFuture();
         }
 
         if ($daemon->isDone()) {
           $this->removeDaemon($daemon);
         }
       }
 
       $this->updatePidfile();
       $this->updateAutoscale();
 
       if ($futures) {
         $iter = id(new FutureIterator($futures))
           ->setUpdateInterval(1);
         foreach ($iter as $future) {
           break;
         }
       } else {
         if ($this->inGracefulShutdown) {
           break;
         }
         sleep(1);
       }
     }
 
     exit($this->err);
   }
 
   private function addDaemon(PhutilDaemonHandle $daemon, array $config) {
     $id = $daemon->getDaemonID();
     $this->daemons[$id] = array(
       'handle' => $daemon,
       'config' => $config,
     );
 
     $autoscale_group = $this->getAutoscaleGroup($daemon);
     if ($autoscale_group) {
       $this->autoscale[$autoscale_group][$id] = true;
     }
 
     return $this;
   }
 
   private function removeDaemon(PhutilDaemonHandle $daemon) {
     $id = $daemon->getDaemonID();
 
     $autoscale_group = $this->getAutoscaleGroup($daemon);
     if ($autoscale_group) {
       unset($this->autoscale[$autoscale_group][$id]);
     }
 
     unset($this->daemons[$id]);
 
     return $this;
   }
 
   private function getAutoscaleGroup(PhutilDaemonHandle $daemon) {
     return $this->getAutoscaleProperty($daemon, 'group');
   }
 
   private function getAutoscaleProperty(
     PhutilDaemonHandle $daemon,
     $key,
     $default = null) {
 
     $id = $daemon->getDaemonID();
     $autoscale = $this->daemons[$id]['config']['autoscale'];
     return idx($autoscale, $key, $default);
   }
 
   public function didBeginWork(PhutilDaemonHandle $daemon) {
     $id = $daemon->getDaemonID();
     $busy = idx($this->daemons[$daemon->getDaemonID()], 'busy');
     if (!$busy) {
       $this->daemons[$id]['busy'] = time();
     }
   }
 
   public function didBeginIdle(PhutilDaemonHandle $daemon) {
     $id = $daemon->getDaemonID();
     unset($this->daemons[$id]['busy']);
   }
 
   public function updateAutoscale() {
     foreach ($this->autoscale as $group => $daemons) {
       $daemon = $this->daemons[head_key($daemons)]['handle'];
       $scaleup_duration = $this->getAutoscaleProperty($daemon, 'up', 2);
       $max_pool_size = $this->getAutoscaleProperty($daemon, 'pool', 8);
       $reserve = $this->getAutoscaleProperty($daemon, 'reserve', 0);
 
       // Don't scale a group if it is already at the maximum pool size.
       if (count($daemons) >= $max_pool_size) {
         continue;
       }
 
       $should_scale = true;
       foreach ($daemons as $daemon_id => $ignored) {
         $busy = idx($this->daemons[$daemon_id], 'busy');
         if (!$busy) {
           // At least one daemon in the group hasn't reported that it has
           // started work.
           $should_scale = false;
           break;
         }
 
         if ((time() - $busy) < $scaleup_duration) {
           // At least one daemon in the group was idle recently, so we have
           // not fullly
           $should_scale = false;
           break;
         }
       }
 
       // If we have a configured memory reserve for this pool, it tells us that
       // we should not scale up unless there's at least that much memory left
       // on the system (for example, a reserve of 0.25 means that 25% of system
       // memory must be free to autoscale).
       if ($should_scale && $reserve) {
         // On some systems this may be slightly more expensive than other
         // checks, so only do it once we're prepared to scale up.
         $memory = PhutilSystem::getSystemMemoryInformation();
         $free_ratio = ($memory['free'] / $memory['total']);
 
         // If we don't have enough free memory, don't scale.
         if ($free_ratio <= $reserve) {
           continue;
         }
       }
 
       if ($should_scale) {
         $config = $this->daemons[$daemon_id]['config'];
 
         $config['autoscale']['clone'] = true;
 
         $clone = new PhutilDaemonHandle(
           $this,
           $config['class'],
           $this->argv,
           array(
             'log' => $this->log,
             'argv' => $config['argv'],
             'load' => $this->libraries,
             'autoscale' => $config['autoscale'],
           ));
 
         $this->addDaemon($clone, $config);
 
         // Don't scale more than one pool up per iteration. Otherwise, we could
         // break the memory barrier if we have a lot of pools and scale them
         // all up at once.
         return;
       }
     }
   }
 
   public function didReceiveNotifySignal($signo) {
     foreach ($this->getDaemonHandles() as $daemon) {
       $daemon->didReceiveNotifySignal($signo);
     }
   }
 
   public function didReceiveReloadSignal($signo) {
     foreach ($this->getDaemonHandles() as $daemon) {
       $daemon->didReceiveReloadSignal($signo);
     }
   }
 
   public function didReceiveGracefulSignal($signo) {
     // If we receive SIGINT more than once, interpret it like SIGTERM.
     if ($this->inGracefulShutdown) {
       return $this->didReceiveTerminalSignal($signo);
     }
     $this->inGracefulShutdown = true;
 
     foreach ($this->getDaemonHandles() as $daemon) {
       $daemon->didReceiveGracefulSignal($signo);
     }
   }
 
   public function didReceiveTerminalSignal($signo) {
     $this->err = 128 + $signo;
     if ($this->inAbruptShutdown) {
       exit($this->err);
     }
     $this->inAbruptShutdown = true;
 
     foreach ($this->getDaemonHandles() as $daemon) {
       $daemon->didReceiveTerminalSignal($signo);
     }
   }
 
   private function getDaemonHandles() {
     return ipull($this->daemons, 'handle');
   }
 
   /**
    * Identify running daemons by examining the process table. This isn't
    * completely reliable, but can be used as a fallback if the pid files fail
    * or we end up with stray daemons by other means.
    *
    * Example output (array keys are process IDs):
    *
    *   array(
    *     12345 => array(
    *       'type' => 'overseer',
    *       'command' => 'php launch_daemon.php --daemonize ...',
    *       'pid' => 12345,
    *     ),
    *     12346 => array(
    *       'type' => 'daemon',
    *       'command' => 'php exec_daemon.php ...',
    *       'pid' => 12346,
    *     ),
    *  );
    *
    * @return dict   Map of PIDs to process information, identifying running
    *                daemon processes.
    */
   public static function findRunningDaemons() {
     $results = array();
 
     list($err, $processes) = exec_manual('ps -o pid,command -a -x -w -w -w');
     if ($err) {
       return $results;
     }
 
     $processes = array_filter(explode("\n", trim($processes)));
     foreach ($processes as $process) {
       list($pid, $command) = preg_split('/\s+/', trim($process), 2);
 
       $pattern = '/((launch|exec)_daemon.php|phd-daemon)/';
       $matches = null;
       if (!preg_match($pattern, $command, $matches)) {
         continue;
       }
 
       switch ($matches[1]) {
         case 'exec_daemon.php':
           $type = 'daemon';
           break;
         case 'launch_daemon.php':
         case 'phd-daemon':
         default:
           $type = 'overseer';
           break;
       }
 
       $results[(int)$pid] = array(
         'type' => $type,
         'command' => $command,
         'pid' => (int) $pid,
       );
     }
 
     return $results;
   }
 
   private function updatePidfile() {
     if (!$this->piddir) {
       return;
     }
 
     $daemons = array();
 
     foreach ($this->daemons as $daemon) {
       $handle = $daemon['handle'];
       $config = $daemon['config'];
 
       if (!$handle->isRunning()) {
         continue;
       }
 
       $daemons[] = array(
         'pid' => $handle->getPID(),
         'id' => $handle->getDaemonID(),
         'config' => $config,
       );
     }
 
     $pidfile = array(
       'pid' => getmypid(),
       'start' => $this->startEpoch,
       'config' => $this->config,
       'daemons' => $daemons,
     );
 
     if ($pidfile !== $this->lastPidfile) {
       $this->lastPidfile = $pidfile;
       $pidfile_path = $this->piddir.'/daemon.'.getmypid();
       Filesystem::writeFile($pidfile_path, json_encode($pidfile));
     }
   }
 
 }
diff --git a/src/future/aws/PhutilAWSException.php b/src/future/aws/PhutilAWSException.php
index 58b291a..d48abf2 100644
--- a/src/future/aws/PhutilAWSException.php
+++ b/src/future/aws/PhutilAWSException.php
@@ -1,45 +1,45 @@
 <?php
 
 final class PhutilAWSException extends Exception {
 
   private $httpStatus;
   private $requestID;
 
   public function __construct($http_status, array $params) {
     $this->httpStatus = $http_status;
     $this->requestID = idx($params, 'RequestID');
 
     $this->params = $params;
 
     $desc = array();
     $desc[] = pht('AWS Request Failed');
     $desc[] = pht('HTTP Status Code: %d', $http_status);
 
     if ($this->requestID) {
-      $desc[] = 'AWS Request ID: '.$this->requestID;
+      $desc[] = pht('AWS Request ID: %s', $this->requestID);
       $errors = idx($params, 'Errors');
       if ($errors) {
-        $desc[] = 'AWS Errors:';
+        $desc[] = pht('AWS Errors:');
         foreach ($errors as $error) {
           list($code, $message) = $error;
           $desc[] = "    - {$code}: {$message}\n";
         }
       }
     } else {
       $desc[] = pht('Response Body: %s', idx($params, 'body'));
     }
 
     $desc = implode("\n", $desc);
 
     parent::__construct($desc);
   }
 
   public function getRequestID() {
     return $this->requestID;
   }
 
   public function getHTTPStatus() {
     return $this->httpStatus;
   }
 
 }
diff --git a/src/markup/__tests__/PhutilMarkupTestCase.php b/src/markup/__tests__/PhutilMarkupTestCase.php
index 0197ab6..073ee7e 100644
--- a/src/markup/__tests__/PhutilMarkupTestCase.php
+++ b/src/markup/__tests__/PhutilMarkupTestCase.php
@@ -1,265 +1,265 @@
 <?php
 
 final class PhutilMarkupTestCase extends PhutilTestCase {
 
   public function testTagDefaults() {
     $this->assertEqual(
       (string)phutil_tag('br'),
       (string)phutil_tag('br', array()));
 
     $this->assertEqual(
       (string)phutil_tag('br', array()),
       (string)phutil_tag('br', array(), null));
   }
 
   public function testTagEmpty() {
     $this->assertEqual(
       '<br />',
       (string)phutil_tag('br', array(), null));
 
     $this->assertEqual(
       '<div></div>',
       (string)phutil_tag('div', array(), null));
 
     $this->assertEqual(
       '<div></div>',
       (string)phutil_tag('div', array(), ''));
   }
 
   public function testTagBasics() {
     $this->assertEqual(
       '<br />',
       (string)phutil_tag('br'));
 
     $this->assertEqual(
       '<div>y</div>',
       (string)phutil_tag('div', array(), 'y'));
   }
 
   public function testTagAttributes() {
     $this->assertEqual(
       '<div u="v">y</div>',
       (string)phutil_tag('div', array('u' => 'v'), 'y'));
 
     $this->assertEqual(
       '<br u="v" />',
       (string)phutil_tag('br', array('u' => 'v')));
   }
 
   public function testTagEscapes() {
     $this->assertEqual(
       '<br u="&lt;" />',
       (string)phutil_tag('br', array('u' => '<')));
 
     $this->assertEqual(
       '<div><br /></div>',
       (string)phutil_tag('div', array(), phutil_tag('br')));
   }
 
   public function testTagNullAttribute() {
     $this->assertEqual(
       '<br />',
       (string)phutil_tag('br', array('y' => null)));
   }
 
   public function testDefaultRelNoreferrer() {
     $map = array(
       // These should not have rel="nofollow" inserted implicitly.
       '/' => false,
       '/path/to/local.html' => false,
       '#example' => false,
       '' => false,
 
       // These should get the implicit insertion.
       'http://www.example.org/' => true,
       '///evil.com/' => true,
       '  http://www.example.org/' => true,
       'ftp://filez.com' => true,
       'mailto:santa@northpole.com' => true,
       'tel:18005555555' => true,
     );
 
     foreach ($map as $input => $expect) {
       $tag = phutil_tag(
         'a',
         array(
           'href' => $input,
         ),
         'link');
       $tag = (string)$tag;
       $this->assertEqual($expect, (bool)preg_match('/noreferrer/', $tag));
     }
 
     // With an explicit `rel` present, we should not override it.
     $tag = phutil_tag(
       'a',
       array(
         'href' => 'http://www.example.org/',
         'rel' => 'nofollow',
       ),
       'link');
 
     $this->assertFalse((bool)preg_match('/noreferrer/', (string)$tag));
 
     // For tags other than `a`, we should not insert `rel`.
     $tag = phutil_tag(
       'link',
       array(
         'href' => 'http://www.example.org/',
       ),
       'link');
 
     $this->assertFalse((bool)preg_match('/noreferrer/', (string)$tag));
   }
 
 
   public function testTagJavascriptProtocolRejection() {
     $hrefs = array(
       'javascript:alert(1)'         => true,
       'JAVASCRIPT:alert(2)'         => true,
 
       // NOTE: When interpreted as a URI, this is dropped because of leading
       // whitespace.
       '     javascript:alert(3)'    => array(true, false),
       '/'                           => false,
       '/path/to/stuff/'             => false,
       ''                            => false,
       'http://example.com/'         => false,
       '#'                           => false,
       'javascript://anything'       => true,
 
       // Chrome 33 and IE11, at a minimum, treat this as Javascript.
       "javascript\n:alert(4)"       => true,
 
       // Opera currently accepts a variety of unicode spaces. This test case
       // has a smattering of them.
       "\xE2\x80\x89javascript:"     => true,
       "javascript\xE2\x80\x89:"     => true,
       "\xE2\x80\x84javascript:"     => true,
       "javascript\xE2\x80\x84:"     => true,
 
       // Because we're aggressive, all of unicode should trigger detection
       // by default.
       "\xE2\x98\x83javascript:"     => true,
       "javascript\xE2\x98\x83:"     => true,
       "\xE2\x98\x83javascript\xE2\x98\x83:" => true,
 
       // We're aggressive about this, so we'll intentionally raise false
       // positives in these cases.
       'javascript~:alert(5)'        => true,
       '!!!javascript!!!!:alert(6)'  => true,
 
       // However, we should raise true negatives in these slightly more
       // reasonable cases.
       'javascript/:docs.html'       => false,
       'javascripts:x.png'           => false,
       'COOLjavascript:page'         => false,
       '/javascript:alert(1)'        => false,
     );
 
     foreach (array(true, false) as $use_uri) {
       foreach ($hrefs as $href => $expect) {
         if (is_array($expect)) {
           $expect = ($use_uri ? $expect[1] : $expect[0]);
         }
 
         if ($use_uri) {
           $href = new PhutilURI($href);
         }
 
         $caught = null;
         try {
           phutil_tag('a', array('href' => $href), 'click for candy');
         } catch (Exception $ex) {
           $caught = $ex;
         }
         $this->assertEqual(
           $expect,
           $caught instanceof Exception,
-          "Rejected href: {$href}");
+          pht('Rejected href: %s', $href));
       }
     }
   }
 
   public function testURIEscape() {
     $this->assertEqual(
       '%2B/%20%3F%23%26%3A%21xyz%25',
       phutil_escape_uri('+/ ?#&:!xyz%'));
   }
 
   public function testURIPathComponentEscape() {
     $this->assertEqual(
       'a%252Fb',
       phutil_escape_uri_path_component('a/b'));
 
     $str = '';
     for ($ii = 0; $ii <= 255; $ii++) {
       $str .= chr($ii);
     }
 
     $this->assertEqual(
       $str,
       phutil_unescape_uri_path_component(
         rawurldecode( // Simulates webserver.
           phutil_escape_uri_path_component($str))));
   }
 
   public function testHsprintf() {
     $this->assertEqual(
       '<div>&lt;3</div>',
       (string)hsprintf('<div>%s</div>', '<3'));
   }
 
   public function testAppendHTML() {
     $html = phutil_tag('hr');
     $html->appendHTML(phutil_tag('br'), '<evil>');
     $this->assertEqual('<hr /><br />&lt;evil&gt;', $html->getHTMLContent());
   }
 
   public function testArrayEscaping() {
     $this->assertEqual(
       '<div>&lt;div&gt;</div>',
       phutil_escape_html(
         array(
           hsprintf('<div>'),
           array(
             array(
               '<',
               array(
                 'd',
                 array(
                   array(
                     hsprintf('i'),
                   ),
                   'v',
                 ),
               ),
               array(
                 array(
                   '>',
                 ),
               ),
             ),
           ),
           hsprintf('</div>'),
         )));
 
     $this->assertEqual(
       '<div><br /><hr /><wbr /></div>',
       phutil_tag(
         'div',
         array(),
         array(
           array(
             array(
               phutil_tag('br'),
               array(
                 phutil_tag('hr'),
               ),
               phutil_tag('wbr'),
             ),
           ),
         ))->getHTMLContent());
   }
 
 }
diff --git a/src/parser/__tests__/PhutilJSONParserTestCase.php b/src/parser/__tests__/PhutilJSONParserTestCase.php
index 998e39c..e24a75c 100644
--- a/src/parser/__tests__/PhutilJSONParserTestCase.php
+++ b/src/parser/__tests__/PhutilJSONParserTestCase.php
@@ -1,139 +1,139 @@
 <?php
 
 final class PhutilJSONParserTestCase extends PhutilTestCase {
 
   public function testValidJSON() {
     $parser = new PhutilJSONParser();
 
     $tests = array(
       '{}' => array(),
       '[]' => array(),
       '{"foo": "bar"}' => array('foo' => 'bar'),
       '[1, "foo", true, null]' => array(1, 'foo', true, null),
       '{"foo": {"bar": "baz"}}' => array('foo' => array('bar' => 'baz')),
       '{"foo": "bar", "bar": ["baz"]}'
         => array('foo' => 'bar', 'bar' => array('baz')),
       '{"foo": "bar", "bar": {"baz": "foo"}}'
         => array('foo' => 'bar', 'bar' => array('baz' => 'foo')),
       '{"": ""}' => array('' => ''),
       '{"test":"\u00c9v\u00e9nement"}'
         => array('test' => "\xC3\x89v\xC3\xA9nement"),
       '["\u00c9v\u00e9nement"]' => array("\xC3\x89v\xC3\xA9nement"),
       '{"test":"http:\/\/foo\\\\zomg"}'
         => array('test' => 'http://foo\\zomg'),
       '["http:\/\/foo\\\\zomg"]' => array('http://foo\\zomg'),
       Filesystem::readFile(dirname(__FILE__).'/json/base64.json') => array(
         'action' => 'candidate.create',
         'actionId' => '80653a26cc46357ff79ff83b47e27c3cb7a668bd',
         'params' => array(
           'attachments' => array(
             Filesystem::readFile(dirname(__FILE__).'/json/base64.data'),
           ),
         ),
       ),
     );
 
     foreach ($tests as $input => $expect) {
       $this->assertEqual(
         $expect,
         $parser->parse($input),
-        'Parsing JSON: '.$input);
+        pht('Parsing JSON: %s', $input));
     }
   }
 
   public function testInvalidJSON() {
     $parser = new PhutilJSONParser();
 
     $tests = array(
       '{' => array(
         'line' => 1,
         'char' => 1,
         'token' => 'EOF',
       ),
       '[' => array(
         'line' => 1,
         'char' => 1,
         'token' => 'EOF',
       ),
       '{"foo":' => array(
         'line' => 1,
         'char' => 7,
         'token' => 'EOF',
       ),
       '{"foo":"bar",}' => array(
         'line' => 1,
         'char' => 13,
         'token' => '}',
       ),
       '{{}' => array(
         'line' => 1,
         'char' => 1,
         'token' => '{',
       ),
       '{}}' => array(
         'line' => 1,
         'char' => 2,
         'token' => '}',
       ),
       "{\"foo\":\"bar\",\n\"bar\":\"baz\",}" => array(
         'line' => 2,
         'char' => 12,
         'token' => '}',
       ),
       "{'foo': 'bar'}" => array(
         'line' => 1,
         'char' => 1,
         'token' => 'INVALID',
       ),
       "{\"foo\": \"bar\nbaz\"}" => array(
         'line' => 1,
         'char' => 7,
         'token' => 'INVALID',
       ),
       '{"foo": "bar\z"}' => array(
         'line' => 1,
         'char' => 7,
         'token' => 'INVALID',
       ),
     );
 
     foreach ($tests as $input => $expected) {
       $caught = null;
       try {
         $parser->parse($input);
       } catch (Exception $ex) {
         $caught = $ex;
       }
       $this->assertTrue($caught instanceof PhutilJSONParserException);
       $this->assertEqual($expected['line'], $caught->getSourceLine());
       $this->assertEqual($expected['char'], $caught->getSourceChar());
       $this->assertEqual($expected['token'], $caught->getSourceToken());
     }
   }
 
   public function testDuplicateKeys() {
     $parser = new PhutilJSONParser();
 
     $tests = array(
       '{"foo": "bar", "foo": "baz"}' => array('foo' => 'baz'),
     );
 
     foreach ($tests as $input => $expect) {
       $parser->setAllowDuplicateKeys(true);
       $this->assertEqual(
         $expect,
         $parser->parse($input),
-        'Parsing JSON: '.$input);
+        pht('Parsing JSON: %s', $input));
 
       $parser->setAllowDuplicateKeys(false);
       $caught = null;
       try {
         $parser->parse($input);
       } catch (Exception $ex) {
         $caught = $ex;
       }
       $this->assertTrue($caught instanceof PhutilJSONParserException);
     }
   }
 
 }
diff --git a/src/phage/__tests__/PhageAgentTestCase.php b/src/phage/__tests__/PhageAgentTestCase.php
index 77403a4..368ea8a 100644
--- a/src/phage/__tests__/PhageAgentTestCase.php
+++ b/src/phage/__tests__/PhageAgentTestCase.php
@@ -1,47 +1,47 @@
 <?php
 
 final class PhageAgentTestCase extends PhutilTestCase {
 
   public function testPhagePHPAgent() {
     return $this->runBootloaderTests(new PhagePHPAgentBootloader());
   }
 
   private function runBootloaderTests(PhageAgentBootloader $boot) {
     $name = get_class($boot);
 
     $exec = new ExecFuture('%C', $boot->getBootCommand());
     $exec->write($boot->getBootSequence(), $keep_open = true);
 
     $exec_channel = new PhutilExecChannel($exec);
     $agent = new PhutilJSONProtocolChannel($exec_channel);
 
     $agent->write(
       array(
         'type'    => 'EXEC',
         'key'     => 1,
         'command' => 'echo phage',
       ));
 
     $this->agentExpect(
       $agent,
       array(
         'type'    => 'RSLV',
         'key'     => 1,
         'err'     => 0,
         'stdout'  => "phage\n",
         'stderr'  => '',
       ),
-      "'echo phage' for {$name}");
+      pht("'%s' for %s", 'echo phage', $name));
 
     $agent->write(
       array(
         'type'    => 'EXIT',
       ));
   }
 
   private function agentExpect(PhutilChannel $agent, $expect, $what) {
     $message = $agent->waitForMessage();
     $this->assertEqual($expect, $message, $what);
   }
 
 }
diff --git a/src/serviceprofiler/PhutilServiceProfiler.php b/src/serviceprofiler/PhutilServiceProfiler.php
index 969dfe9..504bf69 100644
--- a/src/serviceprofiler/PhutilServiceProfiler.php
+++ b/src/serviceprofiler/PhutilServiceProfiler.php
@@ -1,161 +1,163 @@
 <?php
 
 /**
  * Simple event store for service calls, so they can be printed to stdout or
  * displayed in a debug console.
  */
 final class PhutilServiceProfiler {
 
   private static $instance;
   private $listeners = array();
   private $events = array();
   private $logSize = 0;
   private $discardMode = false;
 
   private function __construct() {}
 
   public function enableDiscardMode() {
     $this->discardMode = true;
   }
 
   public static function getInstance() {
     if (empty(self::$instance)) {
       self::$instance = new PhutilServiceProfiler();
     }
     return self::$instance;
   }
 
   public function beginServiceCall(array $data) {
     $data['begin'] = microtime(true);
     $id = $this->logSize++;
     $this->events[$id] = $data;
     foreach ($this->listeners as $listener) {
       call_user_func($listener, 'begin', $id, $data);
     }
     return $id;
   }
 
   public function endServiceCall($call_id, array $data) {
     $data = ($this->events[$call_id] + $data);
     $data['end'] = microtime(true);
     $data['duration'] = ($data['end'] - $data['begin']);
     $this->events[$call_id] = $data;
     foreach ($this->listeners as $listener) {
       call_user_func($listener, 'end', $call_id, $data);
     }
     if ($this->discardMode) {
       unset($this->events[$call_id]);
     }
   }
 
   public function getServiceCallLog() {
     return $this->events;
   }
 
   public function addListener($callback) {
     $this->listeners[] = $callback;
   }
 
   public static function installEchoListener() {
     $instance = self::getInstance();
     $instance->addListener(array(__CLASS__, 'echoListener'));
   }
 
   public static function echoListener($type, $id, $data) {
     $is_begin = false;
     $is_end   = false;
 
     switch ($type) {
       case 'begin':
         $is_begin = true;
         $mark = '>>>';
         break;
       case 'end':
         $is_end = true;
         $mark = '<<<';
         break;
       default:
         $mark = null;
         break;
     }
 
     $type = idx($data, 'type', 'mystery');
 
     $desc = null;
     if ($is_begin) {
       switch ($type) {
         case 'connect':
           $desc = $data['database'];
           break;
         case 'query':
           $desc = substr($data['query'], 0, 512);
           break;
         case 'multi-query':
           $desc = array();
           foreach ($data['queries'] as $query) {
             $desc[] = substr($query, 0, 256);
           }
           $desc = implode('; ', $desc);
           break;
         case 'exec':
           $desc = '$ '.$data['command'];
           break;
         case 'conduit':
           if (isset($data['size'])) {
-            $desc = $data['method'].'() <bytes = '.$data['size'].'>';
+            $desc = $data['method'].'() '.pht('<bytes = %d>', $data['size']);
           } else {
             $desc = $data['method'].'()';
           }
           break;
         case 'http':
           $desc = phutil_censor_credentials($data['uri']);
           break;
         case 'lint':
           $desc = $data['linter'];
           if (isset($data['paths'])) {
-            $desc .= ' <paths = '.count($data['paths']).'>';
+            $desc .= ' '.pht('<paths = %d>', count($data['paths']));
           }
           break;
         case 'lock':
           $desc = $data['name'];
           break;
         case 'event':
-          $desc = $data['kind'].' <listeners = '.$data['count'].'>';
+          $desc = $data['kind'].' '.pht('<listeners = %d>', $data['count']);
           break;
         case 'ldap':
           $call = idx($data, 'call', '?');
           $params = array();
           switch ($call) {
             case 'connect':
               $params[] = $data['host'].':'.$data['port'];
               break;
             case 'start-tls':
               break;
             case 'bind':
               $params[] = $data['user'];
               break;
             case 'search':
               $params[] = $data['dn'];
               $params[] = $data['query'];
               break;
             default:
               $params[] = '?';
               break;
           }
           $desc = "{$call} (".implode(', ', $params).")";
           break;
       }
     } else if ($is_end) {
-      $desc = number_format((int)(1000000 * $data['duration'])).' us';
+      $desc = pht(
+        '%d us',
+        number_format((int)(1000000 * $data['duration'])));
     }
 
     $console = PhutilConsole::getConsole();
     $console->writeLog(
       "%s [%s] <%s> %s\n",
       $mark,
       $id,
       $type,
       $desc);
   }
 
 }
diff --git a/src/utils/__tests__/PhutilUTF8TestCase.php b/src/utils/__tests__/PhutilUTF8TestCase.php
index 16bbe1f..6b2e6a9 100644
--- a/src/utils/__tests__/PhutilUTF8TestCase.php
+++ b/src/utils/__tests__/PhutilUTF8TestCase.php
@@ -1,571 +1,631 @@
 <?php
 
 /**
  * Test cases for functions in utf8.php.
  */
 final class PhutilUTF8TestCase extends PhutilTestCase {
 
   public function testUTF8izeASCIIIgnored() {
     $input = "this\x01 is a \x7f test string";
     $this->assertEqual($input, phutil_utf8ize($input));
   }
 
   public function testUTF8izeUTF8Ignored() {
     $input = "\xc3\x9c \xc3\xbc \xe6\x9d\xb1!";
     $this->assertEqual($input, phutil_utf8ize($input));
   }
 
   public function testUTF8izeLongStringNosegfault() {
     // For some reason my laptop is segfaulting on long inputs inside
     // preg_match(). Forestall this craziness in the common case, at least.
     phutil_utf8ize(str_repeat('x', 1024 * 1024));
     $this->assertTrue(true);
   }
 
   public function testUTF8izeInvalidUTF8Fixed() {
     $input =
       "\xc3 this has \xe6\x9d some invalid utf8 \xe6";
     $expect =
       "\xEF\xBF\xBD this has \xEF\xBF\xBD\xEF\xBF\xBD some invalid utf8 ".
       "\xEF\xBF\xBD";
     $result = phutil_utf8ize($input);
     $this->assertEqual($expect, $result);
   }
 
   public function testUTF8izeOwlIsCuteAndFerocious() {
     // This was once a ferocious owl when we used to use "?" as the replacement
     // character instead of U+FFFD, but now he is sort of not as cute or
     // ferocious.
     $input = "M(o\xEE\xFF\xFFo)M";
     $expect = "M(o\xEF\xBF\xBD\xEF\xBF\xBD\xEF\xBF\xBDo)M";
     $result = phutil_utf8ize($input);
     $this->assertEqual($expect, $result);
   }
 
   public function testUTF8len() {
     $strings = array(
       ''                => 0,
       'x'               => 1,
       "\xEF\xBF\xBD"    => 1,
       "x\xe6\x9d\xb1y"  => 3,
       'xyz'             => 3,
       'quack'           => 5,
     );
     foreach ($strings as $str => $expect) {
       $this->assertEqual($expect, phutil_utf8_strlen($str), 'Length of '.$str);
     }
   }
 
   public function testUTF8v() {
     $strings = array(
       ''                  => array(),
       'x'                 => array('x'),
       'quack'             => array('q', 'u', 'a', 'c', 'k'),
       "x\xe6\x9d\xb1y"    => array('x', "\xe6\x9d\xb1", 'y'),
 
       // This is a combining character.
       "x\xCD\xA0y"        => array('x', "\xCD\xA0", 'y'),
     );
     foreach ($strings as $str => $expect) {
       $this->assertEqual($expect, phutil_utf8v($str), 'Vector of '.$str);
     }
   }
 
   public function testUTF8vCodepoints() {
     $strings = array(
       ''                  => array(),
       'x'                 => array(0x78),
       'quack'             => array(0x71, 0x75, 0x61, 0x63, 0x6B),
       "x\xe6\x9d\xb1y"    => array(0x78, 0x6771, 0x79),
 
       "\xC2\xBB"          => array(0x00BB),
       "\xE2\x98\x83"      => array(0x2603),
       "\xEF\xBF\xBF"      => array(0xFFFF),
       "\xF0\x9F\x92\xA9"  => array(0x1F4A9),
 
       // This is a combining character.
       "x\xCD\xA0y"        => array(0x78, 0x0360, 0x79),
     );
     foreach ($strings as $str => $expect) {
       $this->assertEqual(
         $expect,
         phutil_utf8v_codepoints($str),
         pht('Codepoint Vector of %s', $str));
     }
   }
 
   public function testUTF8ConsoleStrlen() {
     $strings = array(
       ''              => 0,
       "\0"            => 0,
       'x'             => 1,
 
       // Double-width chinese character.
       "\xe6\x9d\xb1"  => 2,
 
       // Combining character.
       "x\xCD\xA0y"    => 2,
 
       // Combining plus double-width.
       "\xe6\x9d\xb1\xCD\xA0y"  => 3,
 
       // Colors and formatting.
       "\x1B[1mx\x1B[m" => 1,
       "\x1B[1m\x1B[31mx\x1B[m" => 1,
     );
     foreach ($strings as $str => $expect) {
       $this->assertEqual(
         $expect,
         phutil_utf8_console_strlen($str),
         pht('Console Length of %s', $str));
     }
   }
 
   public function testUTF8shorten() {
     $inputs = array(
       array('1erp derp derp', 9, '', '1erp derp'),
       array('2erp derp derp', 12, '...', '2erp derp...'),
       array('derpxderpxderp', 12, '...', 'derpxderp...'),
       array("derp\xE2\x99\x83derpderp", 12, '...', "derp\xE2\x99\x83derp..."),
       array('', 12, '...', ''),
       array('derp', 12, '...', 'derp'),
       array('11111', 5, '2222', '11111'),
       array('111111', 5, '2222', '12222'),
 
       array('D1rp. Derp derp.', 7, '...', 'D1rp.'),
 
       // "D2rp." is a better shortening of this, but it's dramatically more
       // complicated to implement with the newer byte/glyph/character
       // shortening code.
       array('D2rp. Derp derp.', 5, '...', 'D2...'),
       array('D3rp. Derp derp.', 4, '...', 'D...'),
       array('D4rp. Derp derp.', 14, '...', 'D4rp. Derp...'),
       array('D5rpderp, derp derp', 16, '...', 'D5rpderp...'),
       array('D6rpderp, derp derp', 17, '...', 'D6rpderp, derp...'),
 
       // Strings with combining characters.
       array("Gr\xCD\xA0mpyCatSmiles", 8, '...', "Gr\xCD\xA0mpy..."),
       array("X\xCD\xA0\xCD\xA0\xCD\xA0Y", 1, '', "X\xCD\xA0\xCD\xA0\xCD\xA0"),
 
       // This behavior is maybe a little bad, but it seems mostly reasonable,
       // at least for latin languages.
       array(
         'Derp, supercalafragalisticexpialadoshus', 30, '...',
         'Derp...',
       ),
 
       // If a string has only word-break characters in it, we should just cut
       // it, not produce only the terminal.
       array('((((((((((', 8, '...', '(((((...'),
 
       // Terminal is longer than requested input.
       array('derp', 3, 'quack', 'quack'),
     );
 
     foreach ($inputs as $input) {
       list($string, $length, $terminal, $expect) = $input;
       $result = id(new PhutilUTF8StringTruncator())
         ->setMaximumGlyphs($length)
         ->setTerminator($terminal)
         ->truncateString($string);
       $this->assertEqual($expect, $result, pht('Shortening of %s', $string));
     }
   }
 
   public function testUTF8StringTruncator() {
     $cases = array(
       array(
         "o\xCD\xA0o\xCD\xA0o\xCD\xA0o\xCD\xA0o\xCD\xA0",
         6, "o\xCD\xA0!",
         6, "o\xCD\xA0o\xCD\xA0!",
         6, "o\xCD\xA0o\xCD\xA0o\xCD\xA0o\xCD\xA0o\xCD\xA0",
       ),
       array(
         "X\xCD\xA0\xCD\xA0\xCD\xA0Y",
         6, '!',
         6, "X\xCD\xA0\xCD\xA0\xCD\xA0Y",
         6, "X\xCD\xA0\xCD\xA0\xCD\xA0Y",
       ),
       array(
         "X\xCD\xA0\xCD\xA0\xCD\xA0YZ",
         6, '!',
         5, "X\xCD\xA0\xCD\xA0\xCD\xA0!",
         2, "X\xCD\xA0\xCD\xA0\xCD\xA0!",
       ),
       array(
         "\xE2\x98\x83\xE2\x98\x83\xE2\x98\x83\xE2\x98\x83",
         4, "\xE2\x98\x83!",
         3, "\xE2\x98\x83\xE2\x98\x83!",
         3, "\xE2\x98\x83\xE2\x98\x83!",
       ),
     );
 
     foreach ($cases as $case) {
       list($input, $b_len, $b_out, $p_len, $p_out, $g_len, $g_out) = $case;
 
       $result = id(new PhutilUTF8StringTruncator())
         ->setMaximumBytes($b_len)
         ->setTerminator('!')
         ->truncateString($input);
       $this->assertEqual($b_out, $result, pht('byte-short of %s', $input));
 
       $result = id(new PhutilUTF8StringTruncator())
         ->setMaximumCodepoints($p_len)
         ->setTerminator('!')
         ->truncateString($input);
       $this->assertEqual($p_out, $result, pht('codepoint-short of %s', $input));
 
       $result = id(new PhutilUTF8StringTruncator())
         ->setMaximumGlyphs($g_len)
         ->setTerminator('!')
         ->truncateString($input);
       $this->assertEqual($g_out, $result, pht('glyph-short of %s', $input));
     }
   }
 
   public function testUTF8Wrap() {
     $inputs = array(
       array(
         'aaaaaaa',
         3,
         array(
           'aaa',
           'aaa',
           'a',
         ),
       ),
       array(
         'aa<b>aaaaa',
         3,
         array(
           'aa<b>a',
           'aaa',
           'a',
         ),
       ),
       array(
         'aa&amp;aaaa',
         3,
         array(
           'aa&amp;',
           'aaa',
           'a',
         ),
       ),
       array(
         "aa\xe6\x9d\xb1aaaa",
         3,
         array(
           "aa\xe6\x9d\xb1",
           'aaa',
           'a',
         ),
       ),
       array(
         '',
         80,
         array(
         ),
       ),
       array(
         'a',
         80,
         array(
           'a',
         ),
       ),
     );
 
     foreach ($inputs as $input) {
       list($string, $width, $expect) = $input;
       $this->assertEqual(
         $expect,
         phutil_utf8_hard_wrap_html($string, $width),
         pht("Wrapping of '%s'.", $string));
     }
   }
 
   public function testUTF8NonHTMLWrap() {
     $inputs = array(
       array(
         'aaaaaaa',
         3,
         array(
           'aaa',
           'aaa',
           'a',
         ),
       ),
       array(
         'abracadabra!',
         4,
         array(
           'abra',
           'cada',
           'bra!',
         ),
       ),
       array(
         '',
         10,
         array(
         ),
       ),
       array(
         'a',
         20,
         array(
           'a',
         ),
       ),
       array(
         "aa\xe6\x9d\xb1aaaa",
         3,
         array(
           "aa\xe6\x9d\xb1",
           'aaa',
           'a',
         ),
       ),
       array(
         "mmm\nmmm\nmmmm",
         3,
         array(
           'mmm',
           'mmm',
           'mmm',
           'm',
         ),
       ),
     );
 
     foreach ($inputs as $input) {
       list($string, $width, $expect) = $input;
       $this->assertEqual(
         $expect,
         phutil_utf8_hard_wrap($string, $width),
-        "Wrapping of '".$string."'");
+        pht("Wrapping of '%s'", $string));
     }
   }
 
   public function testUTF8ConvertParams() {
     $caught = null;
     try {
       phutil_utf8_convert('', 'utf8', '');
     } catch (Exception $ex) {
       $caught = $ex;
     }
     $this->assertTrue((bool)$caught, pht('Requires source encoding.'));
 
     $caught = null;
     try {
       phutil_utf8_convert('', '', 'utf8');
     } catch (Exception $ex) {
       $caught = $ex;
     }
     $this->assertTrue((bool)$caught, pht('Requires target encoding.'));
   }
 
 
   public function testUTF8Convert() {
     if (!function_exists('mb_convert_encoding')) {
       $this->assertSkipped(pht('Requires %s extension.', 'mbstring'));
     }
 
     // "[ae]gis se[n]or [(c)] 1970 [+/-] 1 [degree]"
     $input = "\xE6gis SE\xD1OR \xA9 1970 \xB11\xB0";
     $expect = "\xC3\xA6gis SE\xC3\x91OR \xC2\xA9 1970 \xC2\xB11\xC2\xB0";
     $output = phutil_utf8_convert($input, 'UTF-8', 'ISO-8859-1');
 
     $this->assertEqual($expect, $output, pht('Conversion from ISO-8859-1.'));
 
     $caught = null;
     try {
       phutil_utf8_convert('xyz', 'moon language', 'UTF-8');
     } catch (Exception $ex) {
       $caught = $ex;
     }
 
     $this->assertTrue((bool)$caught, pht('Conversion with bogus encoding.'));
   }
 
 
   public function testUTF8ucwords() {
     $tests = array(
       '' => '',
       'x' => 'X',
       'X' => 'X',
       'five short graybles' => 'Five Short Graybles',
       'xXxSNiPeRKiLLeRxXx' => 'XXxSNiPeRKiLLeRxXx',
     );
 
     foreach ($tests as $input => $expect) {
       $this->assertEqual(
         $expect,
         phutil_utf8_ucwords($input),
         'phutil_utf8_ucwords("'.$input.'")');
     }
   }
 
   public function testUTF8strtolower() {
     $tests = array(
       '' => '',
       'a' => 'a',
       'A' => 'a',
       '!' => '!',
       'OMG!~ LOLolol ROFLwaffle11~' => 'omg!~ lololol roflwaffle11~',
       "\xE2\x98\x83" => "\xE2\x98\x83",
     );
 
     foreach ($tests as $input => $expect) {
       $this->assertEqual(
         $expect,
         phutil_utf8_strtolower($input),
         'phutil_utf8_strtolower("'.$input.'")');
     }
   }
 
   public function testUTF8strtoupper() {
     $tests = array(
       '' => '',
       'a' => 'A',
       'A' => 'A',
       '!' => '!',
       'Cats have 9 lives.' => 'CATS HAVE 9 LIVES.',
       "\xE2\x98\x83" => "\xE2\x98\x83",
     );
 
     foreach ($tests as $input => $expect) {
       $this->assertEqual(
         $expect,
         phutil_utf8_strtoupper($input),
         'phutil_utf8_strtoupper("'.$input.'")');
     }
   }
 
   public function testUTF8IsCombiningCharacter() {
     $character = "\xCD\xA0";
     $this->assertEqual(
       true,
       phutil_utf8_is_combining_character($character));
 
     $character = 'a';
     $this->assertEqual(
       false,
       phutil_utf8_is_combining_character($character));
   }
 
   public function testUTF8vCombined() {
     // Empty string.
     $string = '';
     $this->assertEqual(array(), phutil_utf8v_combined($string));
 
     // Single character.
     $string = 'x';
     $this->assertEqual(array('x'), phutil_utf8v_combined($string));
 
     // No combining characters.
     $string = 'cat';
     $this->assertEqual(array('c', 'a', 't'), phutil_utf8v_combined($string));
 
     // String with a combining character in the middle.
     $string = "ca\xCD\xA0t";
     $this->assertEqual(
       array('c', "a\xCD\xA0", 't'),
       phutil_utf8v_combined($string));
 
     // String starting with a combined character.
     $string = "c\xCD\xA0at";
     $this->assertEqual(
       array("c\xCD\xA0", 'a', 't'),
       phutil_utf8v_combined($string));
 
     // String with trailing combining character.
     $string = "cat\xCD\xA0";
     $this->assertEqual(
       array('c', 'a', "t\xCD\xA0"),
       phutil_utf8v_combined($string));
 
     // String with muliple combined characters.
     $string = "c\xCD\xA0a\xCD\xA0t\xCD\xA0";
     $this->assertEqual(
       array("c\xCD\xA0", "a\xCD\xA0", "t\xCD\xA0"),
       phutil_utf8v_combined($string));
 
     // String with multiple combining characters.
     $string = "ca\xCD\xA0\xCD\xA0t";
     $this->assertEqual(
       array('c', "a\xCD\xA0\xCD\xA0", 't'),
       phutil_utf8v_combined($string));
 
     // String beginning with a combining character.
     $string = "\xCD\xA0\xCD\xA0c";
     $this->assertEqual(
       array(" \xCD\xA0\xCD\xA0", 'c'),
       phutil_utf8v_combined($string));
   }
 
   public function testUTF8BMPSegfaults() {
     // This test case fails by segfaulting, or passes by not segfaulting. See
     // the function implementation for details.
     $input = str_repeat("\xEF\xBF\xBF", 1024 * 32);
     phutil_is_utf8_with_only_bmp_characters($input);
 
     $this->assertTrue(true);
   }
 
   public function testUTF8BMP() {
     $tests = array(
-      ''  => array(true, true, 'empty string'),
-      'a' => array(true, true, 'a'),
-      "a\xCD\xA0\xCD\xA0" => array(true, true, 'a with combining'),
-      "\xE2\x98\x83" => array(true, true, 'snowman'),
+      ''  => array(
+        true,
+        true,
+        pht('empty string'),
+      ),
+      'a' => array(
+        true,
+        true,
+        'a',
+      ),
+      "a\xCD\xA0\xCD\xA0" => array(
+        true,
+        true,
+        pht('%s with combining', 'a'),
+      ),
+      "\xE2\x98\x83" => array(
+        true,
+        true,
+        pht('snowman'),
+      ),
 
       // This is the last character in BMP, U+FFFF.
-      "\xEF\xBF\xBF" => array(true, true, 'U+FFFF'),
+      "\xEF\xBF\xBF" => array(
+        true,
+        true,
+        'U+FFFF',
+      ),
 
       // This isn't valid.
-      "\xEF\xBF\xC0" => array(false, false, 'Invalid, byte range.'),
+      "\xEF\xBF\xC0" => array(
+        false,
+        false,
+        pht('Invalid, byte range.'),
+      ),
 
       // This is an invalid nonminimal representation.
-      "\xF0\x81\x80\x80" => array(false, false, 'Nonminimal 4-byte characer.'),
+      "\xF0\x81\x80\x80" => array(
+        false,
+        false,
+        pht('Nonminimal 4-byte character.'),
+      ),
 
       // This is the first character above BMP, U+10000.
-      "\xF0\x90\x80\x80" => array(true, false, 'U+10000'),
-      "\xF0\x9D\x84\x9E" => array(true, false, 'gclef'),
+      "\xF0\x90\x80\x80" => array(
+        true,
+        false,
+        'U+10000',
+      ),
+      "\xF0\x9D\x84\x9E" => array(
+        true,
+        false,
+        'gclef',
+      ),
 
-      "musical \xF0\x9D\x84\x9E g-clef" => array(true, false, 'gclef text'),
-      "\xF0\x9D\x84" => array(false, false, 'Invalid, truncated.'),
+      "musical \xF0\x9D\x84\x9E g-clef" => array(
+        true,
+        false,
+        pht('gclef text'),
+      ),
+      "\xF0\x9D\x84" => array(
+        false,
+        false,
+        pht('Invalid, truncated.'),
+      ),
 
-      "\xE0\x80\x80" => array(false, false, 'Nonminimal 3-byte character.'),
+      "\xE0\x80\x80" => array(
+        false,
+        false,
+        pht('Nonminimal 3-byte character.'),
+      ),
 
       // Partial BMP characters.
-      "\xCD" => array(false, false, 'Partial 2-byte character.'),
-      "\xE0\xA0" => array(false, false, 'Partial BMP 0xE0 character.'),
-      "\xE2\x98" => array(false, false, 'Partial BMP cahracter.'),
+      "\xCD" => array(
+        false,
+        false,
+        pht('Partial 2-byte character.'),
+      ),
+      "\xE0\xA0" => array(
+        false,
+        false,
+        pht('Partial BMP 0xE0 character.'),
+      ),
+      "\xE2\x98" => array(
+        false,
+        false,
+        pht('Partial BMP cahracter.'),
+      ),
     );
 
     foreach ($tests as $input => $test) {
       list($expect_utf8, $expect_bmp, $test_name) = $test;
 
       // Depending on what's installed on the system, this may use an
       // extension.
       $this->assertEqual(
         $expect_utf8,
         phutil_is_utf8($input),
         pht('is_utf(%s)', $test_name));
 
       // Also test this against the pure PHP implementation, explicitly.
       $this->assertEqual(
         $expect_utf8,
         phutil_is_utf8_slowly($input),
         pht('is_utf_slowly(%s)', $test_name));
 
       $this->assertEqual(
         $expect_bmp,
         phutil_is_utf8_with_only_bmp_characters($input),
         pht('is_utf_bmp(%s)', $test_name));
     }
   }
 
 }
diff --git a/support/xhpast/generate_nodes.php b/support/xhpast/generate_nodes.php
index 55fd33d..0987125 100755
--- a/support/xhpast/generate_nodes.php
+++ b/support/xhpast/generate_nodes.php
@@ -1,146 +1,146 @@
 #!/usr/local/bin/php
 <?php
 
 $nodes = <<<EONODES
 n_PROGRAM
 n_SYMBOL_NAME
 n_HALT_COMPILER
 n_NAMESPACE
 n_STATEMENT
 n_EMPTY
 n_STATEMENT_LIST
 n_OPEN_TAG
 n_CLOSE_TAG
 n_USE_LIST
 n_USE
 n_CONSTANT_DECLARATION_LIST
 n_CONSTANT_DECLARATION
 n_STRING
 n_LABEL
 n_CONDITION_LIST
 n_CONTROL_CONDITION
 n_IF
 n_ELSEIF
 n_ELSE
 n_WHILE
 n_DO_WHILE
 n_FOR
 n_FOR_EXPRESSION
 n_SWITCH
 n_BREAK
 n_CONTINUE
 n_RETURN
 n_GLOBAL_DECLARATION_LIST
 n_GLOBAL_DECLARATION
 n_STATIC_DECLARATION_LIST
 n_STATIC_DECLARATION
 n_ECHO_LIST
 n_ECHO
 n_INLINE_HTML
 n_UNSET_LIST
 n_UNSET
 n_FOREACH
 n_FOREACH_EXPRESSION
 n_THROW
 n_GOTO
 n_TRY
 n_CATCH_LIST
 n_CATCH
 n_DECLARE
 n_DECLARE_DECLARATION_LIST
 n_DECLARE_DECLARATION
 n_VARIABLE
 n_REFERENCE
 n_VARIABLE_REFERENCE
 n_FUNCTION_DECLARATION
 n_CLASS_DECLARATION
 n_CLASS_ATTRIBUTES
 n_EXTENDS
 n_EXTENDS_LIST
 n_IMPLEMENTS_LIST
 n_INTERFACE_DECLARATION
 n_CASE
 n_DEFAULT
 n_DECLARATION_PARAMETER_LIST
 n_DECLARATION_PARAMETER
 n_TYPE_NAME
 n_VARIABLE_VARIABLE
 n_CLASS_MEMBER_DECLARATION_LIST
 n_CLASS_MEMBER_DECLARATION
 n_CLASS_CONSTANT_DECLARATION_LIST
 n_CLASS_CONSTANT_DECLARATION
 n_METHOD_DECLARATION
 n_METHOD_MODIFIER_LIST
 n_FUNCTION_MODIFIER_LIST
 n_CLASS_MEMBER_MODIFIER_LIST
 n_EXPRESSION_LIST
 n_LIST
 n_ASSIGNMENT
 n_NEW
 n_UNARY_PREFIX_EXPRESSION
 n_UNARY_POSTFIX_EXPRESSION
 n_BINARY_EXPRESSION
 n_TERNARY_EXPRESSION
 n_CAST_EXPRESSION
 n_CAST
 n_OPERATOR
 n_ARRAY_LITERAL
 n_EXIT_EXPRESSION
 n_BACKTICKS_EXPRESSION
 n_LEXICAL_VARIABLE_LIST
 n_NUMERIC_SCALAR
 n_STRING_SCALAR
 n_MAGIC_SCALAR
 n_CLASS_STATIC_ACCESS
 n_CLASS_NAME
 n_MAGIC_CLASS_KEYWORD
 n_OBJECT_PROPERTY_ACCESS
 n_ARRAY_VALUE_LIST
 n_ARRAY_VALUE
 n_CALL_PARAMETER_LIST
 n_VARIABLE_EXPRESSION
 n_INCLUDE_FILE
 n_HEREDOC
 n_FUNCTION_CALL
 n_INDEX_ACCESS
 n_ASSIGNMENT_LIST
 n_METHOD_CALL
 n_CONCATENATION_LIST
 n_PARENTHETICAL_EXPRESSION
 n_TRAIT_USE
 n_TRAIT_USE_LIST
 n_TRAIT_ADAPTATION_LIST
 n_TRAIT_INSTEADOF
 n_TRAIT_REFERENCE_LIST
 n_TRAIT_METHOD_REFERENCE
 n_TRAIT_AS
 n_YIELD
 n_FINALLY
 EONODES;
 
 $seq = 9000;
 $map = array();
 foreach (explode("\n", trim($nodes)) as $node) {
   $map[$node] = $seq++;
 }
 
 $hpp = '';
 foreach ($map as $node => $value) {
   $hpp .= "#define {$node} {$value}\n";
 }
 file_put_contents('node_names.hpp', $hpp);
-echo "Wrote C++ definition.\n";
+echo pht('Wrote C++ definition.')."\n";
 
 $at = '@';
 $php =
   "<?php\n\n".
   "/* {$at}generated {$at}undivinable */\n\n".
   "function xhp_parser_node_constants() {\n".
   "  return array(\n";
 foreach ($map as $node => $value) {
   $php .= "    {$value} => '{$node}',\n";
 }
 $php .= "  );\n";
 $php .= "}\n";
 file_put_contents('parser_nodes.php', $php);
-echo "Wrote PHP definition.\n";
+echo pht('Wrote PHP definition.')."\n";